——基于VS2017环境下
我们知道C++的三大特性:封装,继承,多态。下面是对继承的一些总结和应用。
继承机制:是面向对象程序设计使代码可以复用的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。
如:一个1.0版本的计算器,它有加法和减法的功能。而在实际运用中,我们发现还需要计算乘法和除法。这时我们就需要对计算器进行升级。那么升级后的2.0版本计算器,拥有加法,减法,乘法和除法的功能。为了方便,我们可以直接复用1.0版本的计算器的加法和减法。大大简化了升级的工作量,又满足了运用需求。
C++中继承格式:
在C++继承机制中,有三种权限的继承:私有继承(private),公有继承(public),保护继承(protected)
三种继承权限下,基类内的私有成员,公有成员,保护成员分别在派生类的权限关系如下表:
我们知道三种权限中的访问权限大小关系:私有继承(private) < 保护继承(protected) < 公有继承(public)
如果我们的继承权限为最大的,则比它小的权限在派生类中的权限不变。如:公有继承,这基类中的保护成员和私有成员在派生类中的权限也是保护和私有权限,基类和派生类的关系为is-a的关系。如果为私有继承权限,则基类中的公有成员和保护成员在派生类中权限都会变成私有成员。看上表可以看出,基类中的私有成员,在派生类中都是不可见的。保护继承,基类中的公有成员和保护成员,在派生类中都是保护成员,如果基类的成员不被外面调用,那么就定义为保护继承。这就是为什么有保护成员,保护继承。
继承体系下,如果派生类没有显式定义这六个函数,则编译器会自动合成这六个默认成员函数。
我们会提出一下疑问:
如果基类没有显式定义构造函数,派生类也没有显式定义,编译器是否会合成构造函数?
如果基类有显式定义非缺省构造函数,则派生类必须定义构造函数,并且在初始化列表对基类构造函数进行调用,那么基类和派生类的构造函数的那个先调用?析构函数呢?
从打印结果看出,如果生成派生类对象,先调用派生类的构造函数,在派生类的构造函数的初始化列表再调用基类的构造函数。就是基类的先构造完,再到派生类构造对象。而析构函数正好与构造函数相反,编译器自动先调用派生类的析构对资源的清理,再调用基类的析构进行对象资源清理。
在继承体系下,基类和派生类是两个不同的作用域,我们在调用定义函数时,应该注意。
class A
{
public:
A(int a)
:_a(a)
{
cout << "A::A" << endl;
}
void Fun()
{
cout << "A::Fun()" << endl;
}
private:
int _a;
};
class B :public A
{
public:
B(int c)
:A(c)
,_b(c)
{
cout << "B::B" << endl;
}
void Fun()
{
cout << "B::Fun()" << endl;
}
private:
int _b;
};
int main()
{
B b(1);
b.Fun();
return 0;
}
可以看到由于基类和派生类中都有Fun()函数,如果用派生类的对象调用Fun(),则调用的是派生类的Fun(),对基类的Fun()进行了隐藏。我们可以把main()改成:
int main()
{
B b(1);
b.A::Fun();
return 0;
}
赋值兼容规则:
在public继承权限下,父类和子类对象之间有:
1.子类对象可以赋值给父类对象
2.父类对象不能赋值给子类对象
3.父类的指针或者引用可以指向子类对象
4.子类的指针或者对象不能指向父类的对象(可以通过类型强转完成)
为什么呢?下面我们了解继承体系下,对象的模型:
我们先看A类的大小为4个字节,再看派生类B类的大小为8个字节,定义了一个B类对象b,通过对对象b中的成员赋值,从内存看出大概的模型是这样的。
可以看出,父类的对象大小为4字节,它的指针或者引用可以指向子类,只取子类的前4个字节,这是完全可以的。
子类的对象大小为8个字节,如果子类指向父类,可父类的大小只有4字节,如果冒然的使子类的指针或者引用指向父类,不清楚父类下一个4字节是不是合法的区域。有可能访问错误,代码崩溃。