多态
多态
允许不同类的对象对同一消息做出不同的响应
- 编译时多态:通过
函数重载和运算符重载实现,编译时确定调用。 - 运行时多态:通过
虚函数和继承实现,运行时确定调用。 - 抽象类:包含纯虚函数的类,不能实例化,派生类必须实现纯虚函数。
编译时多态(静态多态)
编译时多态主要通过函数重载和运算符重载实现,编译器在编译阶段就能确定调用哪个函数。
函数重载
void print(int i) {
std::cout << "Integer: " << i << std::endl;
}
void print(double d) {
std::cout << "Double: " << d << std::endl;
}
int main() {
print(5); // 调用 print(int)
print(3.14); // 调用 print(double)
return 0;
}
编译器根据参数类型决定调用哪个print函数。
运行时多态(动态多态)
运行时多态通过虚函数和继承实现,具体调用哪个函数在运行时决定。
虚函数
class Base {
public:
virtual void show() {
std::cout << "Base class show" << std::endl;
}
};
class Derived : public Base {
public:
void show() override {
std::cout << "Derived class show" << std::endl;
}
};
int main() {
Base* b = new Derived();
b->show(); // 调用 Derived::show()
delete b;
return 0;
}
Base类中的show函数声明为virtual,Derived类重写了show。通过基类指针调用show时,实际调用的是派生类的show函数。
class Animal {
public:
virtual void eat() {//加上virtual关键字 成为虚函数
cout << "动物在吃东西" << endl;
}
};
class Cat:public Animal {
public:
void eat() {
cout << "猫在吃东西" << endl;
}
};
class Pig :public Animal {
public:
void eat() {
cout << "猪在吃东西" << endl;
}
};
//调用链
//main -> Test -> eat -> Animal::eat
//Animal::eat 是个虚函数,函数传参是个动物,但是传入不同的动物,会重写虚函数产生不同的行为,这就叫多态
void Test(Animal& a) {
a.eat();
}
int main() {
Cat c;
Pig p;
Test(c);
Test(p);
return 0;
}
类里有虚函数,编译器会创建一个虚函数指针,虚函数指针指向一个数组,数组里保存的是所有虚函数的地址,在子类继承时,同名函数会覆盖掉数组里对应的虚函数。
class Animal {
public:
virtual void eat() {
cout << "动物在吃东西" << endl;
}
virtual void run() {
cout << "动物在跑" << endl;
}
};
class Cat:public Animal {
public:
void eat() {
cout << "猫在吃东西" << endl;
}
};
void Test(Animal& a) {
Animal b;
a.eat();
}
int main() {
Cat c;
Test(c);
return 0;
}

C++中多态的原理主要依赖于虚函数表(vtable)和虚函数表指针(vptr)。这些机制使得程序能够在运行时动态决定调用哪个函数,从而实现运行时多态。
1. 虚函数表(vtable)
- 虚函数表是一个存储虚函数地址的数组,每个包含虚函数的类都有一个对应的虚函数表。
- 表中按声明顺序存储类的虚函数地址。如果派生类重写了基类的虚函数,表中会更新为派生类的函数地址。
2. 虚函数表指针(__vptr)
- 虚函数表指针是一个隐藏的指针,存在于每个包含虚函数的对象中,指向该类的虚函数表。
- 当对象调用虚函数时,通过
__vptr找到虚函数表,再通过表中的函数地址调用正确的函数。
3. 多态的实现过程
- 编译阶段:
- 编译器为每个包含虚函数的类生成一个虚函数表。
- 每个对象在构造时,其
__vptr会被初始化为指向该类的虚函数表。
- 运行时:
- 当通过基类指针或引用调用虚函数时,程序通过对象的
__vptr找到虚函数表。 - 通过虚函数表中的函数地址调用正确的函数。
- 当通过基类指针或引用调用虚函数时,程序通过对象的
纯虚函数与抽象类
纯虚函数在基类中声明但不实现,派生类必须重写。包含纯虚函数的类称为抽象类,不能实例化。
class Shape {
public:
virtual void draw() = 0; // 纯虚函数
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing Circle" << std::endl;
}
};
int main() {
Shape* s = new Circle();
s->draw(); // 调用 Circle::draw()
delete s;
return 0;
}
Shape类是抽象类,Circle类实现了draw函数。
虚析构和纯虚析构
用于解决内存泄漏问题
如:父类指针指向子类对象,在对它进行销毁时,如果父类的析构函数不是虚析构,就会无法调用子类的析构函数,从而导致内存泄漏
class BaseA {
public:
BaseA() {}
~BaseA() {
cout << "BaseA销毁了" << endl;
}
};
class SonA:public BaseA {
public:
SonA():m_Data(NULL){
m_Data = new int(10);
}
~SonA() {
cout << "SonA销毁了" << endl;
delete m_Data;
}
int* m_Data;
};
class BaseB {
public:
BaseB() {}
virtual ~BaseB() = 0;
/*virtual ~BaseB() {
cout << "BaseB销毁了" << endl;
}
*/
};
BaseB::~BaseB() {
cout << "BaseB销毁了" << endl;
}
class SonB :public BaseB {
public:
SonB() :m_Data(NULL) {
m_Data = new int(10);
}
~SonB() {
cout << "SonB销毁了" << endl;
delete m_Data;
}
int* m_Data;
};
int main() {
BaseA* a = new SonA();
delete a;
BaseB* b = new SonB();
delete b;
//BaseB x;//报错,纯虚函数无法实例化
return 0;
}
浙公网安备 33010602011771号