C++类的继承
1:类的继承
2:共性与个性
3:共性表示为父类(基类),个性表示为子类(派生类)
4:继承的语法
5:继承表
6:继承方式
7:继承的基本特点
公共特点
1:向上和向下转换(造型)
2:子类会继承父类的所有成员(公开、私有和保护)
3:子类会隐藏父类的同名成员
4:继承方式影响访问控制
5:访问控制限定符
继承方式的影响范围
1:私有继承、保护继承
2:多重继承、钻石继承、虚继承
3:多重继承
4:名字冲突
5:钻石继承
6:虚继承 virtual
7:构造函数
8:拷贝构造
类的继承
共性与个性
共性表达不同类型事物之间工友的属性和行为。
个性用于刻画每种事物特有的属性和行为。
共性表示为父类(基类),个性表示为子类(派生类)
子类继承自父类
基类派生出子类
继承的语法
继承表
一个子类可以同时继承0~多个父类,每个父类的继承方式可以相同也可以不相同。
class 子类:继承方式1 父类1,继承方式2 父类2,...
{
}
继承方式
public 公有继承:父类的特性可以通过子类向外扩展。
private 私有继承:父类的特性只能为子类所有。
protected 保护继承:父类的特性只能在继承链内扩展。
继承的基本特点
公共特点
1、子类对象可以当作父类对象使用,子类对象与父类对象没有本质上的区别。
2、子类的逻辑空间小于父类,但它的物理空间要大于等于父类。
3、子类对象 IS A 父类对象
向上和向下转换(造型)
1、从子类到父类:子类的指针或引用可以隐式转换成父类的指针或引用,这是一种缩小类型的转换,对于编译器来说是安全的(父类指针指向子类对象是安全的)。
2、从父类到子类:父类的指针或引用不可转换成子类的指针或引用,这种操作是一种扩大类型的转换,在编译器看来是非常危险的(子类的指针指向父类的对象,是不安全的)。
3、编译器仅仅是检查指针或引用的数据类型,而对实际引用的目标对象不关心(构成多态的基础)。
4、类型一致:父类的指针或引用实际的目标类型是否需要转换成实际的指针或引用由程序员自己决定。
class A
{
private:
int a;
public:
int b;
A(void)
{
a = 1;
b = 2;
c = 3;
cout << "A构造函数" << endl;
}
void show(void)
{
cout << a << b << c << endl;
}
protected:
int c;
};
class B:public A
{
public:
int b;
B(void)
{
b = 4;
cout << "B构造函数" << endl;
}
void show(void)
{
cout << A::b << c << endl; // 访问A中被隐藏的成员b通过 类名::成员名 访问
}
};
int main()
{
B b;
// 子类的指针或引用可以隐式转换成父类的指针或引用
A* a = &b;
cout << b.c << endl;
b.show();
// 父类的指针或引用不能转换成子类的指针或引用
// error B* p = a;
}
子类会继承父类的所有成员(公开、私有和保护)
虽然子类中继承了所有父类中的成员,但不能访问父类中的私有成员
子类会隐藏父类的同名成员
1、可以通过域限定符 父类名::隐藏成员 进行访问父类中的隐藏成员。
2、可以使用父类的指针或引用来指向子类对象,然后访问父类中的隐藏成员。
继承方式影响访问控制
访问控制限定符
访问限定符 | 内部 | 子类 | 外部 | 友元 |
---|---|---|---|---|
public | OK | OK | OK | OK |
private | OK | NO | NO | OK |
protected | OK | OK | NO | OK |
继承方式的影响范围
父类中 | 公有子类 | 私有子类 | 保护子类 |
---|---|---|---|
public | public | private | protected |
private | private | private | private |
protected | protected | protected | protected |
私有继承、保护继承
1、使用private方式继承父类,公开成员在子类中会变成私有的,保护的依然是保护的,其他的不变,这种继承方式防止父类的成员扩散。
2、使用protected方式继承父类,公开成员在子类中变成保护的,其他的不变,这种继承方式可以有限的防止父类的成员扩散。
3、子类以私有或保护方式继承父类,禁止向上造型(子类的指针或引用不能隐式转换成父类的指针或引用,要想实现多态只能以公开方式继承父类)。
多重继承、钻石继承、虚继承
多重继承
在C++中一个子类可以有多个父类,在继承表中按照顺序继承多个父类中的属性和行为,按照顺序表中的调用父类的构造函数。按照从低到高的地址顺序排列父类,子类中会标记每个父类的存储位置,当子类指针转换成父类的隐式指针时候,编译器会自动计算父类中的内容所在子类中的位置,地址会自动进行偏移计算。
名字冲突
如果父类中有同名的成员,可以正常继承,但如果直接使用会造成歧义,需要“类名::成员名”进行访问
钻石继承
假如有一个类A,类B继承类A,类C也继承类A,然后类D继承类B和类C。
一个子类继承多个父类,这些父类有一个共同的祖先,这种继承叫钻石继承。
注意:钻石继承不会导致继承错误,但当访问祖先类中的成员时,每次需要使用“类名::成员名”进行访问,这种继承会造成冗余。
虚继承 virtual
当进行钻石继承时,祖先类中的内容会有冗余,而进行虚继承后,在子类中的内容只会保留一份。
注意:在使用虚继承时子类中会多了一些内容。(指向从祖先类继承来的成员)
构造函数
一旦进行了虚继承(钻石)祖先辈的构造函数只执行了一次,由孙子类直接调用,祖先类的有参构造也需要在孙子类中显式调用。
拷贝构造
在虚继承(钻石)中祖先类的拷贝构造也由孙子类直接调用,子类中不再调用祖先类的拷贝构造,在手动实现的拷贝构造时(深拷贝),祖先类中的内容也由孙子类负责拷贝,赋值同理。