C/C++总述:Study C/C++-CSDN博客
目录
多态概念
多态分类
多态实现
虚函数&虚函数表
虚函数的重写(覆盖)
多态的构成条件
虚函数重写的两个特例
协变
析构
关键字final和override(C++11)
抽象类
纯虚函数
实现继承与接口继承
多态概念
对于同一个行为对于不同的对象,有不同的表现。
eg:买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人 买票时是优先买票。
多态分类
多态在 c++ 中分为 静态多态 与 动态多态,也称 编译期多态 和 运行时多态 。
静态多态:是基于 函数重载 与 泛型编程 实现的。
动态多态:是基于虚函数实现的。
多态实现
虚函数&虚函数表
- 虚函数:即被 virtual 修饰的类成员函数称为虚函数。
class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
- 虚函数表本质是一个存虚函数指针 的 指针数组,一般情况这个数组最后面放了一个nullptr。
- 虚函数表:虚函数表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚函数表中。
- 一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表
- 注意:对象的前四个字节就是虚表的地址,虚表存放在常量区(虚表是不能人为更改的)
虚函数的重写(覆盖)
- 虚函数的重写(覆盖): 派生类中有一个跟基类完全相同的虚函数 (即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同) ,称子类的虚函数 重写 了基类的虚函数。
多态的构成条件
- 必须通过 基类的指针 来 引用 调用虚函数
- 被调用的函数 必须是虚函数,且 派生类必须对基类的虚函数进行重写
class Person { //多态条件2:被调用的函数 必须是虚函数
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};class Student : public Person {
public: //多态条件2:派生类必须对基类的虚函数进行重写virtual void BuyTicket() { cout << "买票-半价" << endl; }
/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因
为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议
这样使用*//*void BuyTicket() { cout << "买票-半价" << endl; }*/
};void Func(Person& p) //多态条件1:必须通过基类的指针来“引用”调用虚函数
{ p.BuyTicket();
}int main()
{Person ps;Student st;Func(ps); //输出:买票-全价Func(st); //输出:买票-半价return 0;
}
虚函数重写的两个特例
协变
派生类重写基类虚函数时 ,与基类虚函数 返回值类型不同 。即如下代码所示:【基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时】,称为协变。
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;}
};
析构
如果 基类的析构函数为虚函数 ,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。 虽然函数名不相同~Person() ,~Student() ,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成 destructor 。
class Person
{
public: //基类的析构函数为虚函数virtual ~Person() {cout << "~Person()" << endl;}
};class Student : public Person
{
public:virtual ~Student() { cout << "~Student()" << endl; }
};
//只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}
关键字final和override(C++11)
final:修饰虚函数,使虚函数不能被覆盖。(加在父类中)
final修饰类时,表示这个类不能被继承。
override:修饰虚函数,检测是否正确覆盖。(加在子类中)
class A
{
public:virtual void test()final //使该虚函数不能覆盖,若被覆盖就报错{}
}class B: public A
{
public:virtual void test()override //检查虚函数是否正确覆盖,若未覆盖就报错{}
}
抽象类
含纯虚函数的类,称为抽象类
纯虚函数
- 在虚函数后面加上 =0,则这个函数为纯虚函数。
- 包含纯虚函数的类叫做抽象类(接口类),抽象类不能实例化出对象。
- 子类继承抽象类后也不能实例化出对象,只有覆盖(重写)纯虚函数,子类才能实例化出对象。
- 纯虚函数强制子类必须覆盖函数,更加体现出接口继承。
- 纯虚函数不需要实现功能,只需要声明(重写时再实现功能)
class Car // 抽象类
{
public:virtual void Drive() = 0; //在虚函数的后面写上 =0,这个函数为 纯虚函数
};class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl; //只有 重写纯虚函数 ,派生类才能实例化出对象}
};class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};void Test()
{Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}
实现继承与接口继承
- 普通函数的继承是一种 实现继承 ,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
- 虚函数的继承是一种 接口继承 ,派生类继承的是基类虚函数的接口, 目的是为了重写,达成多态 ,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。