前言
继承是C++中十分重要的一部分内容,主要功能是实现类的复用,可以在原有功能的基础上继续添加新功能,是面向对象编程由简易变为复杂的重要组成。
基本概念
继承的逻辑类似于生活中的房子/车子继承,原来有多少内容,继承之后就有多少,而C++中的继承相当于拷贝了一份父类/基类的内容(但是访问可能会受父类和子类的权限所限制)。父类/基类,子类/派生类,以后在本文统一叫做父类/子类。
继承的定义
继承关系和访问限定符
父类访问限定符
private访问
protected访问
public访问
子类继承方式
private继承
protected继承
public继承
子类访问权限
在父类访问限定符和子类继承方式中,以权限较小的为子类的访问权限。
访问权限:public > protected > private
- 例如:父类:protected,子类:private,因为private < protected,所以父类的成员/成员函数将以private的权限被子类访问。
各个权限的访问原则
不使用继承:
- private 和 protected 的权限一致,均是外部不可访问,类内部可以访问
- public类的内外都可访问
使用继承(最终继承结果): - private:继承后,子类不可访问
- protected:继承后,子类中可以访问
- public:继承后,保留父类的继承方式,即:子类的内外都可访问
总结
- struct 创建的类,默认访问权限是public,class的类默认是private
- 实际运用中一般使用
public
继承,访问权限取决于父类,方便拓展
基类和派生类对象赋值转换
在string中,相同类型的变量可以互相转换,那么在有一部分相同(继承父类),有一部分不同(自己拓展)的情况下赋值的规则又是什么?
子类对象,可以赋值给父类的对象/引用/指针
,但是子类自己创建的成员和函数不会赋值给父类的对象,只让父类的那部分内容赋值,这样的操作也叫做"切片"。
- 如果子类对象赋值给父类指针,那么该指针会指向父类的那部分
- 如果子类对象赋值给父类引用,那么则会引用父类的那部分
只能子类赋值给父类,因为可以切片,但是父类不能父类不能赋值给子类,因为子类自己的成员和函数不会被创建。
继承中的作用域
继承后,子类部分和父类部分的作用域不同,所以在子类和父类中,可以存在相同名字的成员和函数。
子类中访问变量的规则
- 如果你要访问子类,直接访问即可。
- 如果要访问父类有两种情况:
- 编译器先去子类作用域找成员/函数,其次到父类找,再到全局域去找。
- 也就是说,如果要用的父类变量/函数且子类没有,编译器会先找子类,找不到之后再到父类。
- 如果要用的父类变量/函数子类中也有,那么编译器会优先用子类的,这时,我们就必须用到
范围解析运算符
也就是直接去父类的域中查找:Father::_name
- 隐藏:因此,也可以得出,子类与父类中,相同名字的函数并不构成重载(尽管父类的那部分已经被子类继承),因为不在同一作用域,他们构成的关系是隐藏。
- 成员函数名相同就构成隐藏
class Father
{
protected:
string _name;
int _i;
};
class Child : public Father
{
public:
string _name;
// 打印父类和子类的成员
Func()
{
cout << _name << endl; // 由于子类有_name,直接用
cout << _i << endl; // 子类中找不到_i,去父类找,打印父类的
cout << Father::_name << endl; // 直接指定访问父类的_name,跳过子类
}
};
int main()
{
Child ch;
ch.Func();
return 0;
}
继承中的默认成员函数
子类初始化
1.构造函数初始化
子类的初始化包括了两个过程:
- 父类部分用构造函数的初始化
- 如果父类没有默认构造函数,需要显式调用父类的构造函数
- 调用方式:在子类的初始化列表中显式调用
- 父类的构造函数必须最先调用(父类的成员必须第一个初始化)
- 因为编译器按照声明顺序初始化,而父类的声明顺序默认排在子类前面,所以一般都是父类的优先初始化
- 如果父类没有默认构造函数,需要显式调用父类的构造函数
- 子类部分用构造函数的初始化
- 子类初始化和一般的类初始化一样,正常初始化就好
2.拷贝构造函数初始化
同上,子类需要用到拷贝构造函数时,先调用父类的拷贝构造函数,拷贝父类部分,再调用子类拷贝构造函数
3.operator=
先调用父类的operator=进行父类部分的赋值,再调用子类的operator=
4.析构函数的调用
析构函数系统会自动调用,先调用子类的,再调用父类的,顺序和构造函数正好相反,类似于栈的后进先出,先构造的最后析构。不用显式掉用
5.继承与友元
规定:父类中的友元函数并不会继承到子类中,如果需要用到该函数,子类需要手动添加友元函数
class Father
{
protected:
friend void Add(int* a, int* b);
};
class Child : public Father
{
// 子类需要手动写出友元函数
friend void Add(int* a, int* b);
};
6.继承与静态成员
静态成员继承时不会再进行创建,所有子类与父类共用一个静态成员
class Father
{
static int a;
};
// 静态成员变量定义
int Father::a = 0;
class Child : Father
{
};
int main()
{
// 把a赋值为1
Father::a = 1;
// 由于共用同一个静态成员,所以这里是把a从1改为3
Child::a = 3;
return 0;
}