概念
定义:继承机制是面向对象程序设计使代码可以复用的最重要手段,它允许程序员在保留原有类特性的基础上进行扩展,增加功能,这样产生的类,称为派生类,被继承的类称为基类或父类。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承是类设计层次的复用。
简单来说:基类实际上是派生类的共性,将其它类都有的属性和方法进行提取,再定义其它类时只需要继承父类,并写出该类独有的属性即可
继承格式
class 新类的名字: [关键字] 继承方式 继承类的名字{};
访问限定符与继承权限
类成员/继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 派生类中不可见 | 派生类中不可见 | 派生类中不可见 |
public继承不会改变类成员的访问权限;protected继承方式会改变原来访问权限为public的成员;private继承方式会影响原来访问权限为public和protected的成员。
PS:
- 父类的private成员被子类继承了,但是子类不能访问父类的private成员,通过查看子类的大小可以得知,子类中包含继承自父类的私有成员变量。在子类中访问父类私有成员会报错。
- protected成员访问限定符只因为继承体系才出现的,因为protected在继承中才有意义。
- 使用关键字class默认的继承方式是private,使用struct默认的继承方式是public,一般最好显式给出继承权限。
class和struct的区别
- 定义类的默认访问权限不同,class为私有,struct为公有,兼容C语言
- 模板参数列表中可以使用class,不能使用struct
- 继承中的默认继承权限不同,class默认private,struct默认public
赋值兼容规则
- 可以使用子类对象给父类对象赋值赋值,但是不能使用父类对象给子类对象赋值。
- 可以使用父类指针指向子类对象,但不能使用子类指针指向父类对象,如果一定要指向,进行强制类型转换后可以,但是会有指针越界访问的问题.
- 可以使用父类的引用去引用子类,不能使用子类的引用引用父类,与指针原理相同。
void extend()
{
class Person
{
protected:
std::string name_;
int age_;
};
class Student : public Person
{
int class_;
};
Person per;
Student stu;
{
// 1. 可以使用子类对象给父类对象赋值赋值,但是不能使用父类对象给子类对象赋值。
Person tPer = stu;
Student tStu = per; // error: 不存在用户定义的从 "Person" 到 "Student" 的适当转换
}
{
// 2. 可以使用父类指针指向子类对象,但不能使用子类指针指向父类对象,如果一定要指向,进行强制类型转换后可以,但是会有指针越界访问的问题。
Person* tPer = &stu;
Student* tStu = &per; // error: "Person *" 类型的值不能用于初始化 "Student *" 类型的实体
}
{
// 3. 可以使用父类的引用去引用子类,不能使用子类的引用引用父类,与指针原理相同。
Person& tPer = stu;
Student& tStu = per; // error: 无法用 "Person" 类型的值初始化 "Student &" 类型的引用(非常量限定)
}
}
原因:父类的指针可以指向子类中继承自父类的部分;但是子类的指针如果指向父类,访问_name和_age时不会有问题,访问到_num时就会超出父类对象的范围,越界访问,所以编译器禁止了子类指针指向父类对象。
继承作用域
- 在继承体系中,父类和子类都有独立的作用域
- 如果父类和子类中有同名成员,子类成员会屏蔽对父类同名成员的直接访问,优先访问自己类中的成员,即同名隐藏,也叫重定义。
- 对于成员函数,只要函数名相同就构成重定义,与类型无关。
class Person
{
protected:
std::string name_;
int age_;
void print()
{
std::cout << __FUNCTION__;
}
};
class Student : public Person
{
public:
int class_;
int print()
{
std::cout << __FUNCTION__;
return 1;
}
};
Person per;
Student stu;
stu.print(); // extend::Student::print
子类的默认成员函数
父类没有显式定义构造函数或者父类有全缺省的构造函数或者无参的构造函数,子类可以不定义构造函数。
但是如果父类显式定义了构造函数,且不是无参或者全缺省的,子类必须显式定义构造函数,并在初始化列表显式调用父类的构造函数,因为如果不显式定义,编译器会自动调用父类默认拷贝构造函数,而父类没有默认的构造函数,便会报错:
正确写法:
构造一个Student类的对象分两步:
- 将从父类继承的成员初始化
- 将子类新增加的成员初始化
拷贝构造函数
子类的拷贝构造函数必须在初始化列表中显式调用父类的拷贝构造函数。
父类没有定义拷贝构造函数,子类可以定义也可以不定义;父类如果定义了拷贝构造函数,子类一般要定义,并且要在初始化列表中调用父类的拷贝构造函数完成从父类继承的成员的拷贝初始化,否则会报错:
正确写法:
赋值运算符重载
子类的赋值运算符重载函数必须调用父类的赋值运算符重载完成对父类的赋值。
父类的赋值运算符重载:
子类:
PS:构造和析构调用顺序,构造子类对象时,先调用父类的构造函数,再调用子类的构造函数,清理对象时,先调用子类的析构函数,再调用父类的析构函数
友元继承
静态继承
菱形继承
虚继承
参考文章
[1] https://zhuanlan.zhihu.com/p/562224109
[2] https://blog.csdn.net/qq_62718027/article/details/125922249