认识多态
多态的形成:
多态是在不同继承关系的类的对象,去调用同一函数,产生了不同的行
多态的条件:
1.必须通过基类的指针或者引用调用虚函数
2.被调用的函数必须是虚函数 ,且派生类必须对基类的虚函数进行重写
图解:
注意区分:
普通调用:函数类型是谁,就调用什么类型
多态调用:调用的指针或引用的本体(实际)是什么类,就调用哪一类里的函数
代码:
cpp
#include<iostream>
using namespace std;
class A
{public:
virtual void options(){cout << "选择A" << endl;}
};
class B :public A
{public:
virtual void options(){cout << "选择B"<< endl;}
};
void Func_one(A& choose){choose.options();}
void Func_two(A* choose){
//两个方法都可以,A* choose接收的是类型对象的地址
// 将类型对象地址接收后第一步解引用*后是真正的类的对象
// 再.options才能调用对象里的内容
//(*choose).options();
choose->options();
}
int main()
{
B b; Func_one(b);
A a; Func_one(a);
A aa; Func_two(&aa);
B bb; Func_two(&bb);
return 0;
}
虚函数
定义:被virtual修饰的类成员函数称为虚函数,
注意:构成多态中,虚函数需要重写
cpp
class A
{public:
virtual void options(){cout << "选择A" << endl;}
};
虚函数重写(覆盖)
定义:派生类中有一个跟基类完全相同的虚函数 (即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表(类型)完全相同),称子类的虚函数重写了基类的虚函数。
区分隐藏:只需要函数名相同 就构成隐藏
cpp
class A
{public:
virtual void options(){cout << "选择A" << endl;}
};
class B :public A
{public:
virtual void options(){cout << "选择B"<< endl;}
//void options(){cout << "选择B"<< endl;}//不推荐
};
派生类中构成重写的虚函数前可以不加virtual,但不推荐
析构函数的重写
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的 析构函数构成重写,虽然基类与派生类析构函数名字不同。
虽然函数名不相同,看起来违背了重写的规 则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处 理成destructor
cpp
//析构的重写
class A
{
public:
//~A() { cout << "析构A" << endl; }
virtual ~A() { cout << "析构A" << endl; }
};
class B :public A
{
public:
//~B() { cout << "析构B" << endl; }
virtual ~B() { cout << "析构B" << endl; }
};
int main()
{
A* a = new A;
B* b = new B;
delete a;//析构A
cout << endl;
delete b;//析构B 析构A
cout << endl;
A* aa = new B;
delete aa;//析构A--不加virtual有误
//申请的是B对象,A* aa只是截取了B对象中A对象部分去使用,但实际传过去的依旧是B类型对象
//多以当我析构时,程序只析构的A就有错误
//加上virtual后,类似A* aa = new B,截取片段的析构才能正常运行
return 0;
}
结果对比:
通过结果对比看出,加上virtual后,不管是正常申请还是截取申请都是可以正常析构的
所以在以后使用类似具有继承(多态是继承的一种)关系时,必须在析构上加virtual让其变成虚函数
拓展
-
inline函数可以是虚函数吗?答:不能,因为inline函数没有地址,在编译时就展开了,无法把地址放到虚函数表中。
-
静态成员可以是虚函数吗?答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
-
构造函数可以是虚函数吗?答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。比识别虚函数要晚,具体看6
-
析构函数可以是虚函数吗?什么场景下析构函数是虚函数?答:可以,并且最好把基类的析构函数定义 成虚函数。参考本节课件内容
-
对象访问普通函数快还是虚函数更快?答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
-
虚函数表是在什么阶段生成的,存在哪的?答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的
C++11 override和final
C++11为什么要在新增关键字?
是为了防止代码量过多时,程序猿疏忽导致的无法构成重载的问题,如果程序无法构成重载,在编译时是不会报错只有没有运行到预期结果才会知道错误。
override
override:: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
即:检查派生类是否重写了基类的虚函数,主要放在派生类里
cpp
class A
{public:
//virtual void options(){cout << "选择A" << endl;}
};
class B :public A
{public:
virtual void options() override
{
cout << "选择B"<< endl;
}
};
在函数整体后面加override
当把A中虚函数注释后会弹出:
final
修饰虚函数
修饰虚函数,表示该虚函数不能再被继承,主要放在不想被继承的父类里
cpp
class A
{
public:
//final修饰虚函数
virtual void options() final { cout << "选择A" << endl; }
};
class B :public A
{
public:
virtual void options()
{
cout << "选择B" << endl;
}
};
修饰类
修饰类,不能被继承
cpp
class A final//final修饰类
{
public:
virtual void options() { cout << "选择A" << endl; }
};
class B :public A
{
public:
virtual void options()
{
cout << "选择B" << endl;
}
};
重载、覆盖(重写)、隐藏(重定义)的对比
抽象类(接口类)
纯虚函数
虚函数的后面写上 =0 ,则这个函数为纯虚函数。
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
间接强制去派生类中去重写
抽象类--不能实例化出对象
cpp
class A
{
public:
virtual void options() = 0;//纯虚函数-直接在函数后加=0
};
class B :public A//B和C为抽象类
{
public:
virtual void options(){cout << "选择B" << endl;}
};
class C :public A
{
public:
virtual void options(){cout << "选择C" << endl;}
};
int main()
{
B b;
b.options();
B* pb = new B;
pb->options();//申请B类型指针直接访问到B类的options
C c;
c.options();
C* pc = new C;
pc->options();
return 0;
}
接口继承和实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
虚函数表个数
ABCD菱形继承无virtual修饰,会有两张虚表,分别为B和C的,在D中新加virtual虚函数时,会放到D:public的类的虚表中。
因为此时B和C同时都单独继承了A,所以B和C各单独有张虚函数表,而在D中新加virtual虚函数时,D不能同时和B与C继承会造成歧义,所以D单独一张
ABCD菱形继承有virtual修饰,会有两张虚表,分别为BC共享的一张A的和D单独一个,在D中新加virtual虚函数时,会放到D的虚表中
BC共享的一张A的是因为B和C继承的为同一个A类,不像无virtual修饰B和C同时各继承一个A(B->A,C->A)