(接上节)钻石继承
1)一个子类的多个基类源自共同的祖先,这样的继承结构体称为钻石继承
A
/ \
B C
\ /
D
2)派生多个中间子类的公共基类(A)子对象,在继承自多个中间子类的汇聚子类(D)对象中,存在多个实例。在汇聚子类中,或通过汇聚子类对象(d)访问公共基类成员,会因继承路径的不同导致结果不一致
3)通过虚继承可以让公共基类子对象在汇聚子类对象中实例唯一,并且为所有的中间子类对象共享,这样即使通过不同的路径,所访问的公共基类成员一定是一致的。
图:
#include<iostream>
using namespace std;
class A{
public:
A(int data):m_data(data){
cout<<"A::A(int)"<<this<<','<<sizeof(*this)<<endl;
}
protected:
int m_data;
};
class B:virtual public A{//虚继承
public:
B(int data): A(data){
cout<<"B::B(int)"<<this<<','<<sizeof(*this)<<endl;
}
void set(int data){ //设值
m_data=data;
}
};
class C:virtual public A{//虚继承
public:
C(int data):A(data){
cout<<"C::C(int)"<<this<<','<<sizeof(*this)<<endl;
}
int get(void){ //取值
return m_data;
}
};
class D:public B,public C{
public:
D(int data):B(data),C(data),A(data){//末端必须有A
cout<<"D::D(int)"<<this<<','<<sizeof(*this)<<endl;
}
};
int main(){
Dd(100);
cout<<"sizeof(d):"<<sizeof(d)<<endl;//12
cout<<d.get()<<endl;//100
d.set(200);
cout<<d.get()<<endl;//200
return 0;
}
tarena@tarena-virtual-machine:~/day44$./a.out
A::A(int)0xbffbe19c,4
B::B(int)0xbffbe194,8
C::C(int)0xbffbe198,8
D::D(int)0xbffbe194,12
sizeof(d):12
100
200
PS:sizeof(d):12 (图)虚指针
A:m_data:12
B:__ptr_B(4)+m_data(4)=8
C: __ptr_C(4)+m_data(4)=8
D:m_data(4)+__ptr_B(4)+__ptr_C(4)=12
1、虚继承语法(掌握)
1)在继承表中使用virtual关键字
2)位于继承链末端的子类(D)的构造函数负责构造虚基类(A)子对象
3)虚基类的所有子类(B/C/A)都必须在其构造函数中显示指明该虚基类子对象的构造方式,否则编译器将选择无参的方式构造虚基类子对象。
图:
2、虚继承的拷贝构造/赋值函数
#include<iostream>
using namespace std;
class A{
public:
A(int data):m_data(data){
cout<<"A::A(int)"<<this<<','<<sizeof(*this)<<endl;
}
A(const A& that):m_data(that.m_data){}
A& operator=(const A & that){
if(&that!=this){
m_data=that.m_data;
}
return *this;
}
protected:
int m_data;
};
//练习:实现B、C、D三个子类的拷贝构造和拷贝赋值
class B:virtual public A{//虚继承
public:
B(int data): A(data){
cout<<"B::B(int)"<<this<<','<<sizeof(*this)<<endl;
}
B(const B& that):A(that){}
B& operator=(const B & that){
if(&that!=this){
A::operator=(that);
}
return *this;
}
void set(int data){ //设值
m_data=data;
}
};
class C:virtual public A{//虚继承
public:
C(int data):A(data){
cout<<"C::C(int)"<<this<<','<<sizeof(*this)<<endl;
}
C(const C& that):A(that){}
C& operator=(const C & that){
if(&that!=this){
A::operator=(that);
}
return *this;
}
int get(void){ //取值
return m_data;
}
};
class D:public B,public C{
public:
D(int data):B(data),C(data),A(data){//末端必须有A
cout<<"D::D(int)"<<this<<','<<sizeof(*this)<<endl;
}
D(const D& that):B(that),C(that),A(that){}
D& operator=(const D & that){
if(&that!=this){
B::operator=(that);
C::operator=(that);
//A::operator=(that);B、C中已经包含
}
return *this;
}
};
int main(){
Dd(100);
cout<<"sizeof(d):"<<sizeof(d)<<endl;//8
cout<<d.get()<<endl;//100
d.set(200);
cout<<d.get()<<endl;//200
De(d);
cout<<e.get()<<endl; i
Df=d;
cout<<f.get()<<endl;
return 0;
}
一、 多态(重点)
图形(绘制)
/ \
正方形(长宽绘制) 圆(半径绘制)
举例:
#include<iostream>
using namespace std;
class Shape{
public:
Shape(int x,int y):m_x(x),m_y(y){}
virtual void draw(void){
cout<<"图形"<<m_x<<','<<m_y<<endl;
}
protected:
int m_x;//坐标
int m_y;
};
class Rect:public Shape{
public:
Rect(int x,int y,int w,int h):
Shape(x,y),m_w(w),m_h(h){}
/*virtual*/void draw(void){
cout<<"矩形:"<<m_x<<','<<m_y<<','
<<m_w<<','<<m_h<<endl;
}
private:
int m_w;
int m_h;
};
class Circle:public Shape{
public:
Circle(int x,int y,int r):Shape(x,y),m_r(r){}
/*virtual*/void draw(void){
cout<<"圆形"<<m_x<<','<<m_y<<','<<m_r<<endl;
}
private:
int m_r;
};
void render(Shape* shapes[]){
for(int i=0;shapes[i];i++){
shapes[i]->draw();
}
}
int main(){
Shape* shapes[1024]={};
shapes[0]=new Rect(1,2,3,4);
shapes[1]=new Rect(11,12,13,14);
shapes[2]=new Circle(2,3,8);
shapes[3]=new Circle(5,5,6);
shapes[4]=new Rect(12,22,23,24);
render(shapes);
return 0;
}
tarena@tarena-virtual-machine:~/day44$./a.out
矩形:1,2,3,4
矩形:11,12,13,14
圆形2,3,8
圆形5,5,6
矩形:12,22,23,24
(如果不加virtual ,则随机指定一种类型如下)
图形1,2
图形11,12
图形2,3
图形5,5
图形12,22
1、多态概念:(!!!笔试题)
如果将基类中的某个成员函数声明为虚函数,那么其子类中与该函数具有相同类型的函数也将变成虚函数,并且对基类中的版本形成覆盖。
这时,通过指向子类对象的基类指针,或者引用子类对象的基类引用,调用该虚函数时,实际被执行的将是子类中的覆盖版本,而不再是基类中的原始版本,这种语法现象称为多态
举例:
1#include<iostream>
2using namespace std;
3class Base{
4public:
5 virtual void foo(void){
6 cout<<"Bace::foo()"<<endl;
7 }
8};
9class Derived:public Base{
10public:
11 /*virtual*/void foo(void){
12 cout<<"Derived::foo()"<<endl;
13 }
14};
15int main(void){
16 Derived d;
17 Base* pb=&d;//Pb指向子类对象的基类指针
18 pb->foo();//Derived::foo()
19 Base& rb=d;//rb引用子类对象的基类引用
20 rb.foo();//Derived::foo()
21 return 0;
22 }
2、虚函数覆盖的条件
1)只有类的成员函数才能被声明为虚函数,全局函数、构造函数、静态函数都不能声明为虚函数。
2)只有在基类中以virtual关键字修饰的成员函数才能作为虚函数被子类覆盖,而与子类中的virtual关键字无关
3)虚函数的子类中的覆盖版本和基类中的原始版本必须具有相同的函数签名:即函数名、形参表、常属性一致
4)如果基类的虚函数返回除类类型指针和引用以外的数据,那么该函数在子类的覆盖版本必须返回相同的数据(返回值要一样)
5)如果基类中的虚函数返回类类型的指针(A*)或引用(A&),那么允许子类中覆盖版本返回其子类的指针(B*)或引用(B&)-----类型协变。
#include <iostream>
using namespace std;
class Base{
public:
virtual void foo(void){
cout << "Base::foo()" << endl;
}
virtual void bar(void){
cout << "Base::bar()" << endl;
}
virtual void hum(int){}
};
class Derived:public Base{
public:
void bar(int x){
cout << "Derived::bar()" << endl;
}
void foo(void){
cout << "Derived::foo()" << endl;
}
};
int main(void)
{
Derived d;
Base* pb = &d;
pb->foo();//Derived::foo()
pb->bar();//Base::bar()
Base& pd = d;
pd.foo();//Derived::foo()
return 0;
}
3、多态的条件
1)多态特性除了需要在基类声明虚函数,并在其子类才能形成有效的覆盖,还必须通过指针或引用来调用该虚函数,才能表现出来
2)调用虚函数的指针也可能是this指针,只要它是一个指向子类对象的基类指针,同样可以表现多态的特性
#include <iostream>
using namespace std;
class Base{
public:
virtual int cal(int x,int y)const{
return x + y;
}
void foo(void){ //foo(Base*this=&d)
cout <</*this->*/ cal(100,200) << endl;
}
};
class Derived:public Base{
public:
int cal(int a,int b)const{
return a * b;
}
};
int main(void)
{
Derived d;
//Base b = d;//300
//cout << b.cal(100,200) << endl; //300,不是多态
d.foo();//foo(&d) &d:Derived*
return 0;
}
4、纯虚函数、抽象类、纯抽象类
4.1纯虚函数(仅仅作为函数的接口)
virtual 返回类型函数名(形参表)[const ]=0
例:
3class Shape{
4public:
5 Shape(int x,inty):m_x(x),m_y(y){}
6 void draw(void)=0;//纯虚函数
7 /* {
8 cout<<"图形"<<m_x<<','<<m_y<<endl;
9 } */
4.2 抽象类
1)如果一个类包含了纯虚函数,那么这个类就是抽象类。
2)抽象类不能实例化对象
3)如果子类没有覆盖抽象基类中的全部虚函数,那么该子类就也是一个抽象类,类的抽象属性可以被继承
#include <iostream>
using namespace std;
class Shape{ //抽象类,不能实例化对象
public:
Shape(int x,int y):m_x(x),m_y(y){}
virtual void draw(void) = 0;//纯虚函数
/*{
cout << "圆形:" << m_x << ',' << m_y << endl;
}*/
protected:
int m_x;//坐标
int m_y;
};
//类的抽象属性可以被继承
class Triangle:public Shape{ //继承了Shape的纯虚函数,同样成为抽象类
public:
Triangle(int x,int y):Shape(x,y){}
};
class Rect:public Shape{
public:
Rect(int x,int y,int w,int h):
Shape(x,y),m_w(w),m_h(h){}
void draw(void){
cout << "矩形:" << m_x << ',' << m_y <<
',' << m_w << ',' << m_h << endl;
}
private:
int m_w;//宽
int m_h;//高
};
class Circle:public Shape{
public:
Circle(int x,int y,int r):Shape(x,y),m_r(r){}
void draw(void){
cout << "圆形:" << m_x << ',' << m_y <<
',' << m_r << endl;
}
private:
int m_r;
};
void render(Shape* shapes[]){
for(int i=0;shapes[i];i++){
shapes[i]->draw();
}
}
int main(void)
{
Shape* shapes[1024]={};
shapes[0] = new Rect(1,2,3,4);
shapes[1] = new Rect(11,12,13,14);
shapes[2] = new Circle(5,5,8);
shapes[3]= new Circle(2,3,5);
shapes[4] = new Rect(10,21,30,44);
render(shapes);
//Shape s(10,20);抽象类不能实例化对象
Triangle t(10,20);
return 0;
}
4.3 纯抽象类(有名接口类)
如果一个类中除了构造和析构函数以外的所有函数都是纯虚函数,那么该抽象类就是一个纯抽象类
举例:
//简单的工厂模式
#include<iostream>
using namespace std;
class PDFParser{
public:
void parse(const char *pdffile){
cout<<"解析出一个矩形"<<endl;
onRect();
cout<<"解析出一个圆形"<<endl;
onCircle();
cout<<"解析出一行文本"<<endl;
onText();
cout<<"解析出一副图片"<<endl;
onImage();
}
private:/*纯虚函数接口 多态*/
virtual void onRect(void)=0;
virtual void onCircle(void)=0;
virtual void onText(void)=0;
virtual void onImage(void)=0;
};
class PDFRender:public PDFParser{
private:
void onImage(void){
cout<<"显示图片"<<endl;
}
void onRect(void){
cout<<"显示矩形"<<endl;
}
void onCircle(void){
cout<<"显示圆形"<<endl;
}
void onText(void){
cout<<"显示文本"<<endl;
}
};
int main(void){
PDFRender render;
render.parse("some.pdf");
}
5、多态的实现原理(虚函数表和动态绑定)
(图)
1)编译器会为包含虚函数的类生成一张虚函数表,用来存放每个函数地址,简称虚表,每个虚函数对应虚函数表中的索引号
2)编译器还会为包含虚函数的类增加一个隐式的成员变量,用于存放虚函数表的首地址,该变量称为虚函数表指针,简称虚指针。
3)所谓的虚函数的覆盖,本质就是用子类的虚函数地址覆盖基类虚表中对应的基类虚函数的地址
4)当编译器看到通过指针或引用调用虚函数的语句时,并不会急于生成有关函数调用的语句,而是生成一段代码替换该语句,这段代码在程序运行阶段完成:完成如下工作
>> 确定调用指针或引用的目标对象的真实类型,并从中找到虚指针;
>>根据虚指针找到相应的虚表,并从中获取所调用虚函数的入口地址;
>>根据函数的入口地址执行对应的虚函数代码
这个虚函数绑定的过程在程序运行阶段完成,因此被称为动态绑定。
6、动态绑定对性能的影响
1)动态绑定会增加内存开销
2)虚函数会增加时间开销
3)虚函数不能被内联优化(因为是在程序运行阶段完成)
4)如果没有多态要求,最好不要使用虚函数
7、虚析构函数
之前说过的内存泄露问题可以用多态解决
#include<iostream>
using namespace std;
class Base{
public:
Base(void){
cout<<"Base::Base()"<<endl;
}
~Base(void){
cout<<"Base::~Base()"<<endl;
}
};
class Derived:public Base{
public:
Derived(void){
cout<<"Derived::Derived()"<<endl;
}
~Derived(void){
cout<<"Derived::~Derived()"<<endl;
}
};
int main(){
Base* pb=new Derived;
delete pb;
return 0;
}
tarena@tarena-virtual-machine:~/day44$./a.out
Base::Base()
Derived::Derived()
Base::~Base()
子类资源没有被释放
---------
1) delete一个指向子类对象基类指针,实际被调用的仅仅是基类析构函数,释放的仅仅是基类子对象的动态资源,而子类的析构函数不会被调用,有内存泄露风险
2) 如果将基类的析构函数声明为虚函数,那么子类的析构函数就将变成虚函数,并且对基类的虚函数构成有效的覆盖,可以表现多态的特性
这时delete一个指向子类对象的基类指针,实际被调用的将是指向子类的析构函数。子类析构函数会自动调用基类的析构函数,实现资源完美释放,避免内存泄露
代码如下:、
#include<iostream>
using namespace std;
class Base{
public:
Base(void){
cout<<"Base::Base()"<<endl;
}
virtual ~Base(void){
cout<<"Base::~Base()"<<endl;
}
};
class Derived:public Base{
public:
Derived(void){
cout<<"Derived::Derived()"<<endl;
}
~Derived(void){
cout<<"Derived::~Derived()"<<endl;
}
};
int main(){
Base* pb=new Derived;
delete pb;
return 0;
}
tarena@tarena-virtual-machine:~/day44$ ./a.out
Base::Base()
Derived::Derived()
Derived::~Derived()
Base::~Base()
二、 typeid运算符和动态类型转换
1、 typeid运算符
#include<typeinfo>
typeinfo typeid(对象/类型/变量)
1)typeid(对象/类型/变量) ,返回typeinfo 常引的常引用
typeinfo包含一个name()成员函数返回char*字符串,通过它可获取类型的标记
举例:
#include<iostream>
#include<typeinfo>
using namespace std;
namespace tarena{
class Student{};
}
class X{
virtual void foo(void){}
};
class Y:public X{
void foo(void){}
};
class Z:public X{
void foo(void){}
};
void func(X& x){//想知道函数真实的实参类型
if(typeid(x)==typeid(Y)){
cout<<"这是一个Y对象"<<endl;
}
else if(typeid(x)==typeid(Z)){
cout<<"这是一个Z对象"<<endl;
}
else {
cout<<"这是一个X对象"<<endl;
}
}
int main(){
int x;
char ch;
cout<<typeid(x).name()<<endl;//i
cout<<typeid(ch).name()<<endl;//c
cout<<typeid(unsigned int).name()<<endl;//j
cout<<typeid(double[10]).name()<<endl;//A10_d A表示数组
cout<<typeid(int* [5]).name()<<endl;//A5_Pi 数组指针 P指针 i整型
cout<<typeid(int(*) [5]).name()<<endl;//PA5_i 指针数组 P指针 i整型
cout<<typeid(int(*)(unsigned char*)).name()<<endl;//PFiPhE 函数指针
/*数组指针(指向数组的指针)
指针数组(一个数组,成员是指针)
函数指针(指向函数的指针)*/
cout<<typeid(int(*)(unsigned char*)).name()<<endl;//PFiPhE 函数指针
cout<<typeid(int(*[5])(void)).name()<<endl;//A5_PFivE 函数指针数组(是一个数组指针,每个成员是一个函数入口)
cout<<typeid(tarena::Student).name()<<endl;//N6tarena7StudentE
X* px=new Y;
cout<<typeid(*px).name()<<endl;//1Y 虚函数构成覆盖
Yy;
func(y);//这是一个Y对象
Zz;
func(z);//这是一个Z对象
return 0;
}
2)typeinfo提供了“= =”,“!=”运算符支持,通过他们可以直接进行类型之间的比较,如果类型之间存在多态的继承关系。
typeid还可以利用多态的特性确定实际对象的类型
2、 动态类型转换
1) dynamic_cast 用于具有多态继承关系的父子类指针和引用之间的显示类型转换
2) dynamic_cast在转换过程中,会检查目标对象和期望转换的对象是否一致,如果一致则转换成功,否则失败;如果转换的是指针,返回NULL代表失败,如果转换的是引用,跑出异常“bad_cast”表示失败
1#include<iostream>
2using namespace std;
3class A{
4 virtual void foo(void){}
5};
6class B:public A{
7 void foo(void){}
8};
9class C:public A{
10 void foo(void){}
11};
12class D{};
13int main(){
14 Bb;
15 A* pa=&b;
16 B* pb=dynamic_cast<B*>(pa);
17 cout<<"pa="<<pa<<endl;
18 cout<<"pb="<<pb<<endl;
19 //动态转换检测合理性,失败返回NULL
20 C* pc=dynamic_cast<C*>(pc);
21 cout<<"pc="<<pc<<endl;//NULL
22
23 A& ra=b;
24 //C&rc=dynamic_cast<C&>(ra);
25 }