C++入门小馆:继承
嘿,各位技术潮人!好久不见甚是想念。生活就像一场奇妙冒险,而编程就是那把超酷的万能钥匙。此刻,阳光洒在键盘上,灵感在指尖跳跃,让我们抛开一切束缚,给平淡日子加点料,注入满满的passion。准备好和我一起冲进代码的奇幻宇宙了吗?Let's go!
我的博客:yuanManGan
我的专栏:C++入门小馆 C言雅韵集 数据结构漫游记 闲言碎语小记坊 题山采玉 领略算法真谛
说起继承,在现实世界中,比如有一天你给你爸爸打了个电话,你跟你爸爸述说你学习的认真,述说你的不容易,而你的爸爸听见了你这么辛苦,决定不再瞒着你了,儿啊其实我们家有几十套房子,你别学了,学出来还没有我出去收租钱多。你没有花费任何代价就继承了你爸的遗产,这就是继承。
1.继承的概念与定义
1.1继承的概念:
继承是针对类的,它的作用是增加复用。比如我们如果要实现一个图书管理系统,涉及到人物这些类中,有老师,学生等。我们描述老师和学生都会用到姓名啊地址电话年龄这些,如果我们分开实现这些就成了:
class Student
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){// ...}// 学习void study(){// ...}
protected:string _name = "peter"; // 姓名string _address; // 地址string _tel; // 电话int _age = 18; // 年龄int _stuid; // 学号
};
class Teacher
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){// ...}// 授课void teaching(){//...}
protected:string _name = "张三"; // 姓名int _age = 18; // 年龄string _address; // 地址string _tel; // 电话string _title; // 职称
};
下面这些都重复出现了。
我们可以将公共部分放到一个Person类中,Student和Teacher都继承Person,就可以复用这些成员,就不需要重复定义了,省去了很多麻烦。
class Person
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){cout << "void identity()" << _name << endl;}
protected:string _name = "张三"; // 姓名string _address; // 地址string _tel; // 电话int _age = 18; // 年龄
};
class Student : public Person
{
public:// 学习void study(){// ...}
protected:int _stuid; // 学号
};
class Teacher : public Person
{
public:// 授课void teaching(){//...}
protected:string title; // 职称
};
int main()
{Student s;Teacher t;s.identity();t.identity();return 0;
}
先来看看代码,下面讲解其继承的格式。
1.2.继承的定义:
1.2.1 定义格式
我们上面实现的Person继承给被人的类,称为父类也叫做基类。而Student和Teacher是子类,也称为派生类。
1.2.2继承基类成员访问⽅式的变化
虽然有那么多的继承方式,但真正常用的还是public继承的基类public和protected成员。
public | 类里面外面都可以访问 |
protected | 类里面可以访问,类外面不能访问, 继承的时候派生类可以访问 |
private | 类里面可以访问,类外面不能访问, 继承的时候派生类可以访问 |
1. 基类private成员在派⽣类中⽆论以什么⽅式继承都是不可⻅的。这⾥的不可⻅是指基类的私有成员 还是被继承到了派⽣类对象中,但是语法上限制派⽣类对象不管在类⾥⾯还是类外⾯都不能去访问 它。
这里继承的private成员但无法访问它。
2. 基类private成员在派⽣类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派⽣类 中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3. 实际上⾯的表格我们进⾏⼀下总结会发现,基类的私有成员在派⽣类都是不可⻅。基类的其他成员 在 派 ⽣ 类 的 访 问 ⽅ 式 == Min (成员在基类的访问限定符,继承⽅式) ,public > protected > private。
4. 使⽤关键字class时默认的继承⽅式是private,使⽤struct时默认的继承⽅式是public,不过最好显 ⽰的写出继承⽅式。
这个特点与struct的不写时默认的成员是public和class默认不写时的成员是private一样的。
5. 在实际运⽤中⼀般使⽤都是public继承,⼏乎很少使⽤protetced/private继承,也不提倡使⽤
protetced/private继承,因为protetced/private继承下来的成员都只能在派⽣类的类⾥⾯使⽤,实
际中扩展维护性不强。
// 实例演⽰三种继承关系下基类成员的各类型成员访问关系的变化
class Person
{
public:void Print(){cout << _name << endl;}
protected:string _name; // 姓名
private:int _age; // 年龄
};
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected:int _stunum; // 学号
};
1.3 继承类模板
namespace refrain
{//template<class T>//class vector//{};template<class T>class stack : public std::vector<T>{public:void push(const T& x){// 基类是类模板时,需要指定⼀下类域,// 否则编译报错:error C3861: “push_back”: 找不到标识符// 因为stack<int>实例化时,也实例化vector<int>了
// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到vector<T>::push_back(x);//push_back(x);}void pop(){vector<T>::pop_back();}const T& top(){return vector<T>::back();}bool empty(){return vector<T>::empty();}};
}
但这里没有我们利用适配器写的爽。
2. 基类和派⽣类间的转换
class Person
{
public:Person() = default;//{}Person(Person& p): _name(p._name), _sex(p._sex), _age(p._age){}
protected:string _name; // 姓名string _sex; // 性别int _age; // 年龄
};
class Student : public Person
{
public:int _No; // 学号
};
int main()
{Student sobj;// 1.派⽣类对象可以赋值给基类的指针/引⽤Person* pp = &sobj;Person& rp = sobj;// 派⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的Person pobj = sobj;//2.基类对象不能赋值给派⽣类对象,这⾥会编译报错//sobj = pobj;return 0;
}
3. 继承中的作⽤域
3.1 隐藏规则:

4. 派⽣类的默认成员函数
4.1 4个常⻅默认成员函数

这里传入的是Student类型会进行赋值兼容转化,进行截断然后称为Peson类型进行运算。


咦这里怎么会调用两次Person的析构呢?我们编译器它有个逻辑,它先会自己调用子类的析构函数,然后再析构父类的对象,那为什么不能先析构父类的呢?如果我们析构了父类的对象,那如果后面我们还会用到父类的变量呢?我们这里就不用调用父类的析构了编译器会帮我们调用的。
5. 派⽣类对象初始化先调⽤基类构造再调派⽣类构造。
6. 派⽣类对象析构清理先调⽤派⽣类析构再调基类的析构。
4.2 实现⼀个不能被继承的类
一个类怎么让他不被继承呢?
1.将构造放在私有:
我们在不使用Derive对象时不会报错,但我们一旦实例化就会报错。
2.加final关键字:
后面的方法更常用。
5. 继承与友元

6. 继承与静态成员
7. 多继承及其菱形继承问题
7.1 继承模型:

这个就是菱形继承:
我们发现Assistant有两个Person,造成了数据冗余和二义性 。

这就导致我们编译器不知道去访问那个_name
7.2 虚继承


这里的监视窗口能看见三个num但这里的监视窗口进行了优化,其实实际上只有一个name。
8.1 继承和组合
