
C++ 的继承
继承
以前, 在C语言时, 我们为了实现代码复用, 使用了函数来封装一些 重复操作的代码. 而当我们过渡到C++时, 面向对象的特性使得代码复用发生了一些困难. 对于使用类进行封装的代码, 怎么在另一个类中访问它们呢?这就需要使用到继承了.
1.继承的基本概念
class Person{
public:
char[1024] gender;
};
class Chinese:public Person { //这里Chinese是派生类, Person是基类, 继承方式是public
public:
int id;
};
- 继承时, 被继承的类叫做基类, 继承的类叫做派生类.形象一点的话: 基类 = 父类, 派生类 = 子类.
- 继承方式有三种: public, protetced, private.
使用不同的继承方式, 派生类能访问基类的范围也不一样. 具体范围可以看下面的表格:
public继承 | protetced继承 | private继承 | |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protetced成员 | 派生类的private成员 |
基类的protetced成员 | 派生类的protetced成员 | 派生类的protetced成员 | 派生类的private成员 |
基类的private成员 | 派生类中不可见 | 派生类中不可见 | 派生类中不可见 |
从上表中我们可以看出, protetced成员和private成员的区别就体现在继承中. 如果没有继承, 他们两个没有任何区别, 但有了继承, protetced成员和private成员便有了不同的含义. 基类的private成员在派生类中无论以何种方式继承, 都是不可见的. 而protetced成员可以通过继承让派生类访问.
继承方式可以不显式写出, 编译器会根据使用的关键字来判断默认的继承方式: 使用class时, 默认的继承方式是private继承, 使用struct时, 默认的继承方式时public继承.
2.基类和派生类的赋值
派生类对象的实例可以直接给基类对象的实例赋值, 派生类对象实例会将继承自基类的那部分成员的值赋值给基类对象实例.
Chinese ch;
Person p = ch;
Person* pp = &ch;
Person& ppp = ch;
而基类实例不能给派生类实例赋值.
3.继承的作用域
继承之后, 派生类和基类拥有各自独立的作用域, 他们之间的成员变量相互之间不会影响.(即使是派生类继承自原来基类的部分, 在实例化时也不会从已经实例化的基类中获取数据)
但有两种情况除外:
- 第一是同名隐藏. 一旦在派生类中定义了一个和基类相同名字的成员, 就会构成隐藏. 派生类会优先访问在自己的定义域中定义的成员. 对于成员函数来说, 只需要函数名相同就会构成同名隐藏.
- 第二个是static成员. 基类中如果定义了static成员那么整个继承体系中就只能有一个这样的成员. 无论派生出多少个子类, 都只能有一个static成员实例
4.派生类的默认成员函数
在类和对象中我们都知道有6个默认成员函数. 继承过后的派生类, 他们的默认成员函数在使用时的规则也会有相应的变化.
- 构造函数: 派生类的构造函数会调用基类的构造函数来初始化来自基类的那一部分成员. 先调用基类构造函数再调用派生类构造函数. 如果基类没有默认的构造函数, 那么就必须在派生类中显式调用基类的构造函数.
- 拷贝构造: 派生类的拷贝构造函数会调用基类的拷贝构造函数
- 赋值运算符重载: 派生类的赋值运算符重载会调用基类的赋值运算符重载函数
- 析构函数: 派生类的析构函数被调用完后, 会自动调用基类的构造函数. 保证先清理派生类对象成员, 再清理基类成员.
一个面试题:
实现一个不能继承的类
class Myclass{
public:
static Create Myclass(){
return Myclass()
}
private:
Myclass(){};
}; //TODO
class Myclass2 final{//C++11的心关键字final可以禁止继承
};
5.多继承
C++允许多继承, 即一个派生类可以有多个基类.
class Myclass: public class1, public class2{
};//myclass继承自class1和class2
但是有了多继承, 就有可能发生各种各样复杂的继承关系. 像什么菱形继承什么的, 简直就像是看着一堆代码在乱伦.
而且也引发了一些问题, 比如菱形继承中代码的二义性问题.
class A{
int num;
};
class B: public A{
};
class C: public A{
};
class D: public B, public C{
};
D d;//实例化一个D类对象
d.num = 1;//现在问题来了,这个num是来自B类继承的A,还是来自C类继承的A?
d.B::num = 1;//这样可以解决二义性的问题, 但是数据冗余的问题依然存在
虚拟继承
虚拟继承解决了菱形继承中二义性和数据冗余的问题.
class A{
int num;
};
class B: virtual public A{
};
class C: virtual public A{
};
class D: public B, public C{
};
D d;//实例化一个D类对象
d.num = 1;//这样只会访问到一个num成员.

