虚函数和多态+虚析构函数 知识点总结 C++程序设计与算法笔记总结(五) 北京大学 郭炜

简介: 虚函数和多态+虚析构函数 知识点总结 C++程序设计与算法笔记总结(五) 北京大学 郭炜

虚函数和多态

虚函数

在 C++ 中,虚函数(Virtual Function)是一种在基类中使用的特殊函数,它在基类中被声明为虚函数后,在派生类中也可以被重新定义。虚函数实现了多态特性,可以通过基类指针或引用以及动态绑定的方式,来访问派生类中的同名函数。

虚函数的定义格式如下:

class Base{
public:
    virtual void func() {
        // function body
    }
};

在上述代码中,func() 函数被声明为虚函数。在派生类中,可以重新定义该函数,实现多态。

当以基类的指针或引用调用虚函数时,程序会在运行时判断当前指针或引用所指向的对象的类型,然后动态地绑定该函数的调用地址。因此,当指针或引用指向派生类对象时,调用的就是派生类中的函数。

下面是一个简单的例子,演示了虚函数的用法:

#include <iostream>
using namespace std;
class Shape {
public:
    virtual float area() {
        cout << "Parent class area :" << endl;
        return 0;
    }
};
class Rectangle : public Shape {
public:
    float area() {
        cout << "Rectangle class area :" << endl;
        return (width * height);
    }
private:
    int width;
    int height;
public:
    Rectangle(int w, int h) {
        width = w;
        height = h;
    }
};
class Triangle : public Shape {
public:
    float area() {
        cout << "Triangle class area :" << endl;
        return (0.5 * base * height);
    }
private:
    int base;
    int height;
public:
    Triangle(int b, int h) {
        base = b;
        height = h;
    }
};
int main() {
    Shape* shape;
    Rectangle rec(10, 7);
    Triangle tri(10, 5);
    shape = &rec;
    shape->area();
    shape = &tri;
    shape->area();
}

在上述代码中,Shape 是基类,RectangleTriangle 是派生类。Shape 中的 area() 函数被声明为虚函数,因此可以在派生类中进行重载。在 main() 函数中,声明了一个 Shape 类型的指针变量 shape,通过它来访问派生类中的同名函数。当 shape 指向 Rectangle 对象时,调用的是 Rectangle 中的 area() 函数;当 shape 指向 Triangle 对象时,调用的是 Triangle 中的 area() 函数。

在类的定义中,前面有 virtual 关键字的成员函数就是虚函数。

class base {
virtual int get() ;
};
int base::get() 
{ }
 virtual 关键字只用在类定义里的函数声明中,

写函数体时不用

多态的表现形式

在面向对象编程中,多态(Polymorphism)是指同一个函数或方法能够接受不同类型的参数或者返回不同类型的结果。多态是面向对象编程的三大特征之一(封装、继承、多态)。

在 C++ 中,多态的表现形式主要有以下两种:

  1. 重载函数
    在 C++ 中,函数重载(Function Overloading)也是一种多态的表现形式。同一个函数名可以被用于多个参数类型或者参数个数不同的函数,编译器会根据函数的参数类型和个数来决定调用哪个函数。例如:
void add(int a, int b) {
    cout << "调用的是int类型加法函数:" << a + b << endl;
}
void add(double a, double b) {
    cout << "调用的是double类型加法函数:" << a + b << endl;
}
int main() {
    add(1, 2); // 调用的是int类型加法函数:3
    add(1.5, 2.6); // 调用的是double类型加法函数:4.1
    return 0;
}

在上述代码中,add() 函数被重载了两次,分别针对 intdouble 类型的参数。在 main() 函数中,根据传入的参数类型,编译器会自动选择调用对应的函数。

2. 虚函数

前面已经介绍了虚函数的概念和用法,虚函数实现了运行时多态性。通过基类指针或引用以及动态绑定的方式,来访问派生类中的同名函数。

例如:

#include <iostream>
using namespace std;
class Shape {
public:
    virtual float area() {
        cout << "Parent class area :" << endl;
        return 0;
    }
};
class Rectangle : public Shape {
public:
    float area() {
        cout << "Rectangle class area :" << endl;
        return (width * height);
    }
private:
    int width;
    int height;
public:
    Rectangle(int w, int h) {
        width = w;
        height = h;
    }
};
class Triangle : public Shape {
public:
    float area() {
        cout << "Triangle class area :" << endl;
        return (0.5 * base * height);
    }
private:
    int base;
    int height;
public:
    Triangle(int b, int h) {
        base = b;
        height = h;
    }
};
int main() {
    Shape* shape;
    Rectangle rec(10, 7);
    Triangle tri(10, 5);
    shape = &rec;
    shape->area();
    shape = &tri;
    shape->area();
}

在上述代码中,Shape 是基类,RectangleTriangle 是派生类。Shape 中的 area() 函数被声明为虚函数,因此可以在派生类中进行重载。在 main() 函数中,声明了一个 Shape 类型的指针变量 shape,通过它来访问派生类中的同名函数。当 shape 指向 Rectangle 对象时,调用的是 Rectangle 中的 area() 函数;当 shape 指向 Triangle 对象时,调用的是 Triangle 中的 area() 函数。这就是运行时多态性的表现。

派生类的对象可以赋给基类引用

通过基类引用调用基类和派生类中的同名虚函数时:

(1)若该引用引用的是一个基类的对象,那么被调用是基类的虚函数;

(2)若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数。这种机制也叫做“多态”。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8d9yz08-1687264779367)(2023-06-20-20-11-19.png)]

多态的作用

在面向对象的程序设计中使用多态,能够增强程序的可扩充性,即程序需要修改或增加功能的时候,需要改动和增加的代码较少。

使用多态的游戏程序实例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZqkStrLm-1687264779368)(2023-06-20-20-13-01.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FdVlSDLM-1687264779369)(2023-06-20-20-13-30.png)]

基本思路:

为每个怪物类编写 Attack、FightBack和 Hurted成员函数。

Attact函数表现攻击动作,攻击某个怪物,并调用被攻击怪物的

Hurted函数,以减少被攻击怪物的生命值,同时也调用被攻击怪物的 FightBack成员函数,遭受被攻击怪物反击。

Hurted函数减少自身生命值,并表现受伤动作。

FightBack成员函数表现反击动作,并调用被反击对象的Hurted成

员函数,使被反击对象受伤。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lFtMMvOU-1687264779369)(2023-06-20-20-14-16.png)]

非多态的实现方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yQQ01rGL-1687264779369)(2023-06-20-20-14-39.png)]

代码示例

下面是一个使用多态的游戏程序实例,该程序模拟了一个简单的 RPG 游戏,其中包含了不同类型的角色,每种角色都有自己的攻击力和防御力。具体实现如下:

#include <iostream>
#include <string>
using namespace std;
// 角色基类
class Character {
protected:
    string name;
    int attack;
    int defense;
public:
    Character(string name, int attack, int defense) {
        this->name = name;
        this->attack = attack;
        this->defense = defense;
    }
    virtual int getAttack() { return attack; } // 获取攻击力
    virtual int getDefense() { return defense; } // 获取防御力
    virtual void attackTarget(Character* target) {} // 攻击目标
};
// 具体角色类:战士
class Warrior : public Character {
public:
    Warrior(string name, int attack, int defense) : Character(name, attack, defense) {}
    int getAttack() { return attack * 2; } // 攻击力加倍
    void attackTarget(Character* target) {
        int damage = getAttack() - target->getDefense();
        damage = max(damage, 0);
        cout << name << " 对 " << target->name << " 造成了 " << damage << " 点伤害!" << endl;
    }
};
// 具体角色类:法师
class Mage : public Character {
public:
    Mage(string name, int attack, int defense) : Character(name, attack, defense) {}
    int getDefense() { return defense / 2; } // 防御力减半
    void attackTarget(Character* target) {
        int damage = getAttack() - target->getDefense();
        damage = max(damage, 0);
        cout << name << " 对 " << target->name << " 造成了 " << damage << " 点伤害!" << endl;
    }
};
// 游戏主程序
int main() {
    Warrior warrior("战士", 50, 30);
    Mage mage("法师", 40, 40);
    Character* player = &warrior;
    Character* enemy = &mage;
    cout << player->name << " 攻击 " << enemy->name << ":" << endl;
    player->attackTarget(enemy);
    cout << enemy->name << " 攻击 " << player->name << ":" << endl;
    enemy->attackTarget(player);
    return 0;
}

在上述代码中,角色基类 Character 中定义了角色的基本属性和行为,其中 getAttack()getDefense() 函数是虚函数,分别获取角色的攻击力和防御力。具体的角色类 WarriorMage 继承自角色基类 Character,并分别重载了虚函数 getAttack()getDefense(),以及定义了攻击目标的具体行为。

在游戏主程序中,先创建了一个战士和一个法师角色,然后通过基类指针来访问角色的属性和行为。当战士攻击法师时,调用的是战士类中的 attackTarget() 函数,而当法师攻击战士时,调用的是法师类中的 attackTarget() 函数。由于这两个函数都是虚函数,并且被重新定义了,所以实际上调用的是派生类中的函数,实现了运行时多态性。

!!!更多多态程序实例!!!

几何形体处理程序

几何形体处理程序: 输入若干个几何形体的参数,

要求按面积排序输出。输出时要指明形状。

Input:

第一行是几何形体数目n(不超过100).下面有n行,每行以一个字母c开头.

若 c 是 ‘R’,则代表一个矩形,本行后面跟着两个整数,分别是矩形的宽和高;

若 c 是 ‘C’,则代表一个圆,本行后面跟着一个整数代表其半径

若 c 是 ‘T’,则代表一个三角形,本行后面跟着三个整数,代表三条边的长度

Output:

按面积从小到大依次输出每个几何形体的种类及面积。每行一个几何形体,输出格式为:

形体名称:面积

课本代码示例

#include <iostream>
#include <stdlib.h>
#include <math.h>
using namespace std;
class CShape
{
public:
virtual double Area() = 0; //纯虚函数
virtual void PrintInfo() = 0;
}; 
class CRectangle:public CShape
{
public:
int w,h; 
virtual double Area();
virtual void PrintInfo();
};
class CCircle:public CShape {
public:
int r; 
virtual double Area();
virtual void PrintInfo();
};
class CTriangle:public CShape {
public:
int a,b,c; 
virtual double Area();
virtual void PrintInfo();
}; 
double CRectangle::Area() { 
return w * h; 
}
void CRectangle::PrintInfo() {
cout << "Rectangle:" << Area() << endl;
}
double CCircle::Area() {
return 3.14 * r * r ;
}
void CCircle::PrintInfo() {
cout << "Circle:" << Area() << endl;
}
double CTriangle::Area() {
double p = ( a + b + c) / 2.0;
return sqrt(p * ( p - a)*(p- b)*(p - c));
}
void CTriangle::PrintInfo() {
cout << "Triangle:" << Area() << endl; 
}
CShape * pShapes[100];
int MyCompare(const void * s1, const void * s2);
int main()
{ 
int i; int n;
CRectangle * pr; CCircle * pc; CTriangle * pt;
cin >> n;
for( i = 0;i < n;i ++ ) {
char c;
cin >> c;
switch(c) {
case 'R':
pr = new CRectangle();
cin >> pr->w >> pr->h;
pShapes[i] = pr; 
break; 
case 'C':
pc = new CCircle();
cin >> pc->r;
pShapes[i] = pc;
break;
case 'T':
pt = new CTriangle();
cin >> pt->a >> pt->b >> pt->c;
pShapes[i] = pt; 
break;
} 
}
qsort(pShapes,n,sizeof( CShape*),MyCompare);
for( i = 0;i <n;i ++)
pShapes[i]->PrintInfo(); 
return 0;
}
int MyCompare(const void * s1, const void * s2)
{
double a1,a2;
CShape * * p1 ; // s1,s2 是 void * ,不可写 “* s1”来取得s1指向的内容
CShape * * p2;
p1 = ( CShape * * ) s1; //s1,s2指向pShapes数组中的元素,数组元素的类型是CShape *
p2 = ( CShape * * ) s2; // 故 p1,p2都是指向指针的指针,类型为 CShape ** 
a1 = (*p1)->Area(); // * p1 的类型是 Cshape * ,是基类指针,故此句为多态
a2 = (*p2)->Area();
if( a1 < a2 ) 
return -1;
else if ( a2 < a1 )
return 1;
else
return 0;
} 
case 'C':
pc = new CCircle();
cin >> pc->r;
pShapes[i] = pc;
break;
case 'T':
pt = new CTriangle();
cin >> pt->a >> pt->b >> pt->c;
pShapes[i] = pt; 
break;
} 
}
qsort(pShapes,n,sizeof( CShape*),MyCompare);
for( i = 0;i <n;i ++)
pShapes[i]->PrintInfo(); 
return 0;
}

如果添加新的几何形体,比如五边形,则只需要从CShape派生出CPentagon,以及在main中switch语句中增加一个case,其余部分不变有木有!

用基类指针数组存放指向各种派生类对象的指针,然后遍历该数组,就能对各个派生类对象做各种操作,是很常用的做法

下面是一个利用 C++ 实现的几何形体处理程序,可以输入若干个几何形体的参数,并按照面积排序输出各个几何形体的种类及面积:
```c++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const double pi = 3.14159265358979323846;
// 几何形体基类
class Shape {
public:
    virtual double getArea() = 0; // 获取面积
    virtual string getName() = 0; // 获取名称
};
// 具体几何形体类:矩形
class Rectangle : public Shape {
private:
    double width;
    double height;
public:
    Rectangle(double w, double h) {
        width = w;
        height = h;
    }
    double getArea() { return width * height; }
    string getName() { return "矩形"; }
};
// 具体几何形体类:圆形
class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) { radius = r; }
    double getArea() { return pi * radius * radius; }
    string getName() { return "圆形"; }
};
// 具体几何形体类:三角形
class Triangle : public Shape {
private:
    double a, b, c;
public:
    Triangle(double aa, double bb, double cc) {
        a = aa;
        b = bb;
        c = cc;
    }
    double getArea() {
        double p = (a + b + c) / 2;
        return sqrt(p * (p - a) * (p - b) * (p - c));
    }
    string getName() { return "三角形"; }
};
// 比较函数,用于排序
bool cmp(Shape* s1, Shape* s2) { return s1->getArea() < s2->getArea(); }
// 主程序
int main() {
    int n;
    cin >> n;
    vector<Shape*> shapes;
    for (int i = 0; i < n; i++) {
        char c;
        cin >> c;
        if (c == 'R') {
            double w, h;
            cin >> w >> h;
            shapes.push_back(new Rectangle(w, h));
        }
        else if (c == 'C') {
            double r;
            cin >> r;
            shapes.push_back(new Circle(r));
        }
        else if (c == 'T') {
            double a, b, c;
            cin >> a >> b >> c;
            shapes.push_back(new Triangle(a, b, c));
        }
    }
    sort(shapes.begin(), shapes.end(), cmp);
    for (int i = 0; i < n; i++) {
        cout << shapes[i]->getName() << ":" << shapes[i]->getArea() << endl;
        delete shapes[i];
    }
    return 0;
}
```
在上述代码中,几何形体基类 `Shape` 定义了接口函数 `getArea()` 和 `getName()`,分别用于获取几何形体的面积和名称。具体的几何形体类 `Rectangle`、`Circle` 和 `Triangle` 继承自 `Shape`,并实现了这两个接口函数。
在主程序中,首先输入几何形体的数目 `n`,然后根据每个几何形体的名称和参数创建相应的对象,并添加到 `shapes` 向量中。最后利用 `sort()` 函数和比较函数 `cmp()` 对 `shapes` 向量进行排序,按照面积从小到大排序。最后遍历 `shapes` 向量,输出每个几何形体的名称和面积,并释放相应的内存。

构造函数和析构函数中调用虚函数

在构造函数和析构函数中调用虚函数,不是多态。编译时即可确定,调用的函数是自己的类或基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数

在构造函数和析构函数中调用虚函数是一种危险的做法,因为虚函数的调用是运行时决定的,而在构造函数和析构函数中,对象的状态可能还没有完全初始化或已经被销毁,此时调用虚函数可能会产生未定义的行为。

具体来说,在构造函数中调用虚函数可能会导致以下问题:

  1. 对象可能还没有完全初始化,此时调用虚函数可能会访问到未初始化的成员变量或无效的指针,导致程序崩溃或产生未定义的行为。
  2. 对象的动态类型可能还没有被确定,此时调用虚函数会默认使用基类的实现,而不是派生类的实现,导致程序产生错误的行为。
    在析构函数中调用虚函数可能会导致以下问题:
  3. 对象的动态类型可能已经被销毁,此时调用虚函数会导致程序崩溃或产生未定义的行为。
  4. 调用虚函数可能会触发虚函数表的查找和调用,而在对象被销毁的过程中,虚函数表和虚函数指针可能已经被销毁,导致程序出现未定义的行为。

因此,在构造函数和析构函数中应该尽量避免调用虚函数,或者采用其他解决方案,如在构造函数中使用初始化列表初始化成员变量,或者在析构函数中只进行简单的资源释放操作。如果确实需要在构造函数或析构函数中调用虚函数,应该尽可能保证对象的状态已经完全初始化或还没有被销毁,并且应该注意虚函数的实现是否能够正确地处理这种情况。

多态实现的关键 — 虚函数表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M8dnzr3H-1687264779370)(2023-06-20-20-22-42.png)]

多态是面向对象编程的重要特性之一,其中的关键就是通过运行时绑定来实现函数调用的动态分派。在 C++ 中,实现多态的一种常见方式是通过虚函数来实现,其中的关键就是需要使用虚函数表(vtable)来存储类的虚函数指针,从而实现动态绑定。

虚函数表是一个由编译器自动生成的静态变量,用于存储类的虚函数指针。对于每个含有虚函数的类,编译器都会在编译时为其生成一个虚函数表,并将其作为类的元数据存储在程序的数据段中。虚函数表中的每个条目都是一个指向虚函数的指针,其中的顺序和声明顺序相同。因此,当类的对象被创建时,虚函数表的地址会被存储在对象的头部,成为对象的虚函数指针(vptr)。

当使用基类类型的指针或引用调用虚函数时,会根据对象的实际类型来查找虚函数表,并通过虚函数指针调用相应的虚函数。具体来说,编译器会在对象的虚函数指针(vptr)所指向的虚函数表中查找相应的虚函数,然后通过函数指针调用相应的函数。因此,由于虚函数的调用是在运行时动态决定的,因此可以实现多态的目的。

需要注意的是,在使用虚函数时,需要满足以下条件:

  1. 虚函数必须是类的成员函数。
  2. 虚函数必须在基类中被声明为虚函数,并在派生类中被覆盖(override)或实现(implement)。
  3. 虚函数的参数和返回类型必须与基类中的虚函数完全相同。
  4. 虚函数表的大小和顺序必须与类的继承关系相对应。
  5. 虚函数的调用必须通过基类类型的指针或引用进行,才能触发动态绑定。

通过虚函数表的实现,C++ 实现了多态的功能,使得程序具有更加灵活和可扩展的特性。但需要注意的是,虚函数表的实现也增加了程序的开销和复杂性,因此在设计和实现程序时需要谨慎考虑。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9zSU9uGz-1687264779370)(2023-06-20-20-24-16.png)]

虚析构函数

通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数

但是,删除一个派生类的对象时,应该先调用派生类的析构函数,然后调用基类的析构函数。

解决办法:把基类的析构函数声明为virtual 派生类的析构函数可以virtual不进行声明

通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数

一般来说,一个类如果定义了虚函数,则应该将析构函数也定义成虚函数。或者,一个类打算作为基类使用,也应该将析构函数定义成虚函数。

注意:不允许以虚函数作为构造函数

虚析构函数是指在析构函数前加上关键字 “virtual”,用于实现多态的析构函数。

当使用基类的指针或引用来删除一个派生类的对象时,如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数。这样就会导致派生类中分配的内存资源没有被正确释放,从而导致内存泄漏的问题。

而通过将基类的析构函数声明为虚函数,可以实现在析构对象时动态绑定,调用派生类的析构函数。这样就能够正确地释放派生类中分配的内存资源,避免内存泄漏的问题。

例如:

class Base {
public:
    virtual ~Base() { // 声明为虚函数
        std::cout << "Base::~Base()" << std::endl;
    }
};
class Derived : public Base {
public:
    ~Derived() override { // 实现派生类的析构函数
        std::cout << "Derived::~Derived()" << std::endl;
    }
};
int main() {
    Base *p = new Derived();
    delete p; // 动态绑定,调用派生类的析构函数
    return 0;
}

在上述代码中,基类的析构函数被声明为虚函数,派生类的析构函数被实现并重写了基类的虚析构函数。在删除派生类对象时,通过基类的指针进行删除,并利用动态绑定机制实现了对派生类析构函数的调用。

虚析构函数是为了解决基类指针删除派生类对象时可能出现的内存泄漏问题,是实现多态的关键。在设计和实现对象继承体系时,应该尽可能地将析构函数声明为虚函数。

需要注意的是,如果派生类没有显式地提供析构函数,那么编译器会自动生成一个默认的析构函数。这个默认的析构函数也会被声明为虚函数,因为它继承了基类的虚析构函数。

另外,需要注意的是,虚析构函数只适用于基类指针或引用删除派生类对象时的情况。如果使用对象的直接名字进行删除,那么只会调用对象自身的析构函数,而不会触发动态绑定,也不会调用派生类的析构函数。

例如:

class Base {
public:
    virtual ~Base() { // 声明为虚函数
        std::cout << "Base::~Base()" << std::endl;
    }
};
class Derived : public Base {
public:
    ~Derived() override { // 实现派生类的析构函数
        std::cout << "Derived::~Derived()" << std::endl;
    }
};
int main() {
    Derived d;
    Base *p = &d;
    p->~Base(); // 不会触发动态绑定,只会调用 Base 的析构函数
    return 0;
}

在上述代码中,对象 d 的析构函数是派生类 Derived 的析构函数,在删除对象 d 时会被自动调用。但是,如果使用基类指针直接调用对象的析构函数,不会触发动态绑定,只会调用基类的析构函数,从而导致派生类的析构函数没有被正确调用。

因此,需要在使用虚析构函数时,注意使用基类指针或引用来删除派生类对象,以确保正确调用派生类的析构函数,避免内存泄漏的问题。

此外,需要注意的是,如果派生类的析构函数需要进行特殊的资源释放操作,例如释放动态分配的内存或关闭文件等,那么必须在派生类的析构函数中显式地调用基类的虚析构函数。这样才能保证在析构派生类对象时,先调用派生类的析构函数,再调用基类的析构函数,从而避免资源泄漏的问题。

例如:

class Base {
public:
    virtual ~Base() {
        std::cout << "Base::~Base()" << std::endl;
    }
};
class Derived : public Base {
public:
    Derived() {
        data = new int[10];
    }
    ~Derived() override {
        delete[] data; // 释放动态分配的内存
        std::cout << "Derived::~Derived()" << std::endl;
        // 显式调用基类的虚析构函数
        // ensure the base class's destructor is executed
        Base::~Base();
    }
private:
    int *data;
};
int main() {
    Base *p = new Derived();
    delete p;
    return 0;
}

在上述代码中,派生类 Derived 在构造函数中分配了一个 int 数组的内存空间,然后在析构函数中释放了这个内存空间。同时,派生类的析构函数中显式地调用了基类的虚析构函数,以确保在析构对象时,先调用派生类的析构函数,再调用基类的析构函数,从而避免资源泄漏的问题。

总之,虚析构函数是实现多态和避免内存泄漏的关键,需要注意使用基类指针或引用来删除派生类对象,以及在派生类的析构函数中显式地调用基类的虚析构函数,以确保正确地释放资源。

纯虚函数和抽象类

包含纯虚函数的类叫抽象类

 抽象类只能作为基类来派生新类使用,不能创建独立的抽象类的对象

 抽象类的指针和引用可以指向由抽象类派生出来的类的对象

A a ; // 错,A 是抽象类,不能创建对象

A * pa ; // ok,可以定义抽象类的指针和引用

pa = new A ; //错误, A 是抽象类,不能创建对象

 在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部不能调用纯虚函数。

 如果一个类从抽象类派生而来,那么当且仅当它实现了基类中的所有纯虚函数,它才能成为非抽象类。

纯虚函数和抽象类是 C++ 中用于实现接口和多态的重要特性。

纯虚函数是指在函数声明的结尾处使用 “= 0” 指明的函数,它是一种没有实现的虚函数。纯虚函数的作用是为了实现接口,即定义一个接口,而不需要提供具体的实现。由于纯虚函数没有具体实现,因此不能直接创建该类的对象,必须在派生类中实现该函数才能使用。例如:

class Shape {
public:
    virtual double area() = 0; // 纯虚函数
};
class Circle : public Shape {
public:
    double area() override { // 实现纯虚函数
        return 3.14 * radius * radius;
    }
private:
    double radius;
};

抽象类是指含有纯虚函数的类,它不能被直接实例化,只能被用作派生其他类的基类。抽象类的作用在于,定义了一组接口,而不需要提供具体的实现。由于抽象类含有纯虚函数,因此必须在派生类中实现所有的纯虚函数才能使用。例如:

class Shape {
public:
    virtual double area() = 0; // 纯虚函数,使得 Shape 变成了抽象类
    virtual void draw() = 0;   // 纯虚函数
};
class Circle : public Shape {
public:
    double area() override {
        return 3.14 * radius * radius;
    }
    void draw() override {
        // 绘制圆形
    }
private:
    double radius;
};

需要注意的是,抽象类可以包含非纯虚函数,但是含有纯虚函数的类一定是抽象类。因为含有纯虚函数的类不能被直接实例化,必须被用作派生其他类的基类,提供接口的定义。

纯虚函数和抽象类的作用是为了实现接口和多态,使得程序具有更加灵活和可扩展的特性。在实际的程序设计中,可以使用抽象类或纯虚函数来定义接口,从而使得程序的设计更加模块化和可维护。

目录
相关文章
|
1月前
|
算法 索引
❤️算法笔记❤️-(每日一刷-141、环形链表)
❤️算法笔记❤️-(每日一刷-141、环形链表)
46 0
|
1月前
|
算法 API 计算机视觉
人脸识别笔记(一):通过yuface调包(参数量54K更快更小更准的算法) 来实现人脸识别
本文介绍了YuNet系列人脸检测算法的优化和使用,包括YuNet-s和YuNet-n,以及通过yuface库和onnx在不同场景下实现人脸检测的方法。
46 1
|
1月前
|
JSON 算法 数据可视化
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
这篇文章是关于如何通过算法接口返回的目标检测结果来计算性能指标的笔记。它涵盖了任务描述、指标分析(包括TP、FP、FN、TN、精准率和召回率),接口处理,数据集处理,以及如何使用实用工具进行文件操作和数据可视化。文章还提供了一些Python代码示例,用于处理图像文件、转换数据格式以及计算目标检测的性能指标。
63 0
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
|
1月前
|
算法
❤️算法笔记❤️-(每日一刷-160、相交链表)
❤️算法笔记❤️-(每日一刷-160、相交链表)
18 1
|
1月前
|
数据可视化 搜索推荐 Python
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
14 0
|
1月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
31 0
|
1月前
|
算法
❤️算法笔记❤️-(每日一刷-26、删除有序数组的重复项)
❤️算法笔记❤️-(每日一刷-26、删除有序数组的重复项)
23 0
|
10天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
39 4
|
12天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
35 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4