本篇文章将在基础篇的基础上进行相应的拓展,依次介绍:多层次派生、公有派生的赋值兼容规则、派生类与基类的指针互换以及私有派生和保护派生。
书中对应章节如下
多层次派生
基本概念
多层次派生就像一系列的亲情关系一样:父亲继承了爷爷的东西,儿子继承了父亲的东西,孙子继承了儿子的东西...
假设A派生出B,B派生出C,C派生出D。那么A为B的直接基类,B为C的直接基类;A为C的间接基类。
书写
在书写多层次派生的时候,必须注意:
1,只写
直接基类,不写间接基类。
2,派生类沿着类的层次,自动向上继承它的所有间接基类。
3,与前面的继承概念相同,在定义使用派生时,必须
一连串的完成基类的赋值。
4,
构造时,首先构造
层级最高的基类;
析构时恰好相反,析构
层级最低的派生类。
实例
通过一个例子对多层次派生进行说明,定义类A,B,C,A派生出B,B派生出C,A有成员变量。
class A {
public:
int n;
A(int i) :n(i) {
cout << "A" << n << "constructed" << endl;
}
~A() {
cout << "A" << n << "deconstructed" << endl;
}
};
class B :public A {
public:
B(int i) :A(i) {
cout << "B constructed" << endl;
}
~B() {
cout << "B decontructed" << endl;
}
};
class C :public B {
public:
C() :B(2) {
cout << "C constructed" << endl;
}
~C() {
cout << "C deconstructed" << endl;
}
};
int main() {
C Obj;
return 0;
}
程序运行后依次输出A,B,C类的构造内容,与C,B,A类的析构内容。
因此首先验证了构造与析构的顺序。
其次,在派生的最底层,C通过赋值传递给B,B把2传递给A的i,从而完成了A类的成员构造。
包含成员对象的派生类
这一部分没有什么内容,就是在类中多了成员对象的内容。构造函数的初始化列表不但要指明基类对象的初始化方式,还要指明成员对象的初始化方式。
公有派生的赋值兼容规则
这一部分的内容主要是一个青出于蓝而胜于蓝的基本原理。
兼容规则
如下
看完这三条,可能都会感觉:你说的很对,但我不知道你在讲什么。
这些规则主要想说明的问题是:派生出来的成员可以直接对基类成员进行操作,反过来就不行。
其原理是:派生类除了具备基类的技术基础外,还具备其他的功能,因此可以直接使用基类的东西。但是反过来的话,基类不具有派生类的新成员,如果用基类对象对派生类赋值或者进行地址赋值的话,将出现有一些变量值落空的现象。
这就好比人在年轻的时候由他父母抚养长大;而到了父母老的时候,应当做的是反哺,而不是继续衣来伸手。
程序
class A {
};
class B :public A {
};
int main() {
A a;
B b;
a = b;
b = a;
A* pa = &b;
B* pb = &a;
return 0;
}
不用运行,编译器的错误提示给了我们答案
继承类可以反哺给基类,但是反过来不行。
基类与派生类指针的相互转换
由上一小节可知:派生类对象可以赋值给基类对象,派生类指针可以对基类指针进行赋值。
但有时出于方便,还是需要使得基类指针为派生类指针进行赋值,这个时候一般是使用强制类型转换实现这一功能。
基本规则归纳如下
One more Coding
class CBase {
protected:
int n;
public:
CBase(int i) :n(i) {
}
void Print() {
cout << "CBase:n= " << n << endl;
}
};
class CDerived :public CBase {
public:
int v;
CDerived(int i):CBase(i),v(2*i){}
void Func(){}
void Print() {
cout << "CDerived:n= " << n << endl;
cout << "CDerived:v= " << v << endl;
}
};
int main() {
CDerived objDerived(3);
CBase objBase(5);
CBase* pBase = &objDerived;
pBase->Func();
pBase->v = 5;
pBase->Print();
cout << "1)---------------" << endl;
CDerived* pDerived = &objBase;
CDerived* pDerived = (CDerived*)(&objBase);
pDerived->Print();
cout << "2)---------------" << endl;
objDerived.Print();
cout << "3)---------------" << endl;
pDerived->v = 128;
objDerived.Print();
return 0;
}
程序分析
内容
程序包含两类:一种为CBase类,另外一种为CDerived类;CDerived由CBase派生而来。
CBase类拥有一个成员变量n、一个构造函数以及一个打印的Print()函数。
CDerived类包含一个成员变量v(还有个传家宝i在这先不包括在里面),一个构造函数,一个Func()函数以及一个Print()打印函数。
主程序分析
错误
先不分析,看编译器:
一共有三个报错点,从上往下可知:
报错1:Func为CDerived类的内容,而不是CBase类的内容,不能瞎指。
报错2:同理,v属于CDerived。
报错3:反哺原则
运行
emmm.....
除了黑框,还很带感的出现了个白框
白框中说的:你丫的对objBase做了什么?“
Stack around the variable 'objBase' was corrupted”
让我们看看到底做了什么
-------------------------------------------------------------------
功能:
CBase有n,构造函数以及Print
CDerived有v,构造函数,Func以及Print
CBase的Print直接输出n
CDerived的Print输出n=n以及v=2n
-------------------------------------------------------------------
分析
第一部分
CDerived objDerived(3);
CBase objBase(5);
CBase* pBase = &objDerived;
// pBase->Func();
// pBase->v = 5;
pBase->Print();
主程序前两行相安无事;第三行把Derived的对象赋值给pBase,符合反哺原则,正确。
因为pBase的类型为CBase,所以pBase->Print()将调用CBase的Print()
本着
什么类型的指针调用什么类型成员的原则,该段程序输出结果为
CBase:n= 3
接着打印输出1)------
第二部分
CDerived*pDerived=(CDerived*)(&objBase);
通过强制类型转换,使得基类能够继续提供给派生类东西,调用Print将打印CDerived类的东西
也就是输出
CBase类的成员变量n,以及CDerived的成员变量
v?
不对啊,我CDerived的指针指的是一个CBase类的地址,并不包含CDerived的内容,因此Stack was corrupted!
第三部分
objDerived.Print()
应当输出CDerived类的Print,也就是打印出
3
6
3)--------
第四部分
pDerived->v=128,
回顾一下pDerived, pDerived指向的是一个objBase的地址,但CBase类里面并不包含v成员变量,因此,不把这一行注释掉,栈依然会崩
最后
objDerived.Print()
打印输出
3
6
因此,排除会崩的代码段
输出的东西应当为
3
1)------
2)------
3
6
3)-------
3
6
贴个图
当然C++这种发展了多年的程序对于这种bug肯定有所察觉,因此提供了dynamic_cast的强制转换运算符对基类与派生类指针相互指向是否导致内存错误进行判断。
最终代码
class CBase {
protected:
int n;
public:
CBase(int i) :n(i) {
}
void Print() {
cout << "CBase:n= " << n << endl;
}
};
class CDerived :public CBase {
public:
int v;
CDerived(int i):CBase(i),v(2*i){}
void Func(){}
void Print() {
cout << "CDerived:n= " << n << endl;
cout << "CDerived:v= " << v << endl;
}
};
int main() {
CDerived objDerived(3);
CBase objBase(5);
CBase* pBase = &objDerived;
// pBase->Func();
// pBase->v = 5;
pBase->Print();
cout << "1)---------------" << endl;
// CDerived* pDerived = &objBase;
CDerived* pDerived = (CDerived*)(&objBase);
//pDerived->Print();
cout << "2)---------------" << endl;
objDerived.Print();
cout << "3)---------------" << endl;
//pDerived->v = 128;
objDerived.Print();
return 0;
}
本小段总结
私有派生与保护派生
所谓私有派生与保护派生,只不过是将派生类的基类形参前的public对应的改成了private以及protected两个关键字。
相同点是:都不能访问基类的私有成员
不同点是:
对于私有派生,继承完成后原先基类的protected以及public成员均变成了private类型。
对于保护派生,继承完后,原先基类的protected以及public成员均变成了protected类型。