C++中的多态
一、多态的概念与实现
1.概念
多态是C++面向对象的三大特性之一,通俗来讲,就是多种形态,当不同的对象去完成某个行为时,会产生不同的状态。
2.定义及实现
1.多态分为两类:
**静态多态 ** :函数重载,运算符重载
动态多态 :派生类和虚函数实现运行时多态(也就是这节我们要讲的内容,并不难~)
2.构成条件
(1) 必须通过基类指针或引用指向子类对象,调用虚函数。
(2)子类必须对父类的虚函数进行重写。
3.虚函数
即被关键字virtual 修饰的类成员函数。
class Animal
{
public:
virtual void Speak(){cout<<"动物在说话"<<endl;}//Speak就是虚函数
};
虚函数的重写
子类中有一个跟父类完全相同的虚函数(返回值类型、函数名、参数列表都相同)。
class Person{
public:
virtual void BuyTicket(){cout<<"全价票"<<endl;}
};
class Student:public Person{
public:
virtual void BuyTicket(){cout<<"半价票"<<endl;}
}
建议子类在重写虚函数时, 也加上virtual。
虚函数重写的例外(感兴趣可了解)
协变 (基类与派生类虚函数的返回值不同)
概念:在派生类中重写虚函数时,允许返回类型是基类返回类型的派生类型。
规则:基类虚函数的返回类型必须是指针或引用类型。
派生类重写虚函数时返回的类型必须是基类返回类型的派生类。
下面我们写一个例子当作了解(还挺有意思的)
class A{};
class B : public A {};
class Person {
public:
virtual A* f() {return new A;}
};
class Student : public Person {
public:
virtual B* f() {return new B;}
};
3.重载 重写 重定义的对比
重载:两函数在同一作用域,函数名/参数相同。
重写(覆盖):两函数分别在基类和派生类的作用域,函数名/参数/返回值都相同,都为虚函数。
重定义(隐藏):两函数分别在基类和派生类的作用域,函数名相同,两个同名函数不构成重写就是重定义。
二、抽象类
1.概念
在虚函数后面加上=0,则这个函数是纯虚函数。包含纯虚函数的类叫做抽象类,且抽象类不能实例化出对象。 这时候就体现重写纯虚函数的重要性了,派生类才能实例化出对象。
class Base
{
public:
virtual void func() = 0;//纯虚函数
};
class Son:public Base{
public:
virtual void func()
{
cout << "func调用" << endl;
}
};
void test()
{
Base *base = NULL;
base = new Son;//大家仔细观察这两行代码,体会指针的用处
base->func();
}
2.接口继承与实现继承
普通函数的继承就是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
观察上面的代码,可以发现,虚函数的继承是一种接口继承,派生类继承了基类虚函数的接口,目的是进行重写。
三、多态的原理
1.虚函数表
我们先讲一个例子:
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
通过监视窗口,可以看到除了_b成员,还有一个 _vfptr放在对象前面,这个指针叫做虚函数表指针(v表示virtual,f表示function)。含有虚函数的类中都至少有一个虚函数表指针。
注意: 虚函数表本质是一个存放虚函数指针的指针数组。
派生类的虚表: 先将基类中的虚表内容拷贝一份到派生类虚表中,如果派生类中重写了基类的某个虚函数,则用派生类自己的虚函数覆盖虚表中基类的虚函数,派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
2.多态的原理
当通过基类指针或引用调用虚函数时,实际调用的函数是由对象的虚函数表决定的。编译器在生成代码时,会插入相关代码,通过虚函数指针查找虚函数表,进而调用具体的函数实现。
简单来说就是,满足多态后的函数调用,通过虚函数表和虚函数指针,使得在代码运行时能够去需要的对象中提取,进而完成多种功能。
3.动态/静态绑定
- 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态, 比如:函数重载(这里就照应了我们开头提到的)。
- 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体 行为,调用具体的函数,也称为动态多态。
四、虚析构和纯虚析构
1.作用
虚析构函数确保当通过基类指针删除派生类对象时,派生类的析构函数能够被正确调用,从而避免资源泄漏。
纯虚析构函数和虚析构功能相同,知识用于抽象类,无法实例出对象。
2.语法与实现
虚析构:virtual ~类名(){}。
纯虚析构:virtual ~类名(){}=0
类名::~类名(){}
实例:
class Base {
public:
virtual ~Base() { // 虚析构函数
cout << "Base Destructor" << endl;
}
};
class Derived : public Base {
public:
~Derived() {
cout << "Derived Destructor" << endl;
}
};
int main() {
Base* b = new Derived();
delete b; // 调用Derived和Base的析构函数
return 0;
}
class Base {
public:
virtual ~Base() = 0; // 纯虚析构函数
};
Base::~Base() { // 纯虚析构函数的定义
cout << "Base Destructor" << endl;
}
感谢你的阅读!