C/C++总述:Study C/C++-CSDN博客
目录
面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
继承代表了 is a 关系。

- // 基类
- class Animal {
- // eat() 函数
- // sleep() 函数
- };
-
-
- //派生类
- class Dog : public Animal {
- // bark() 函数
- };
一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:

其中,继承方式 是 public、protected 或 private 其中的一个,基类 是之前定义过的某个类的名称。如果未使用继承方式,则默认为 private。
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
| 访问 | public | protected | private |
|---|---|---|---|
| 同一个类 | yes | yes | yes |
| 派生类 | yes | yes | no |
| 外部的类 | yes | no | no |
一个派生类继承了所有的基类方法,但下列情况除外:
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
| 类成员/继承方式 | public继承 | protected继承 | private继承 |
|---|---|---|---|
| 基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
| 基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
| 基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
总结:
使用 using 关键字可以改变基类成员在派生类中的访问权限。
注意:using 只能改变基类中 public 和 protected 成员的访问权限,不能改变 private 成员的访问权限,因为基类中 private 成员在派生类中是不可见的,根本不能使用,所以基类中的 private 成员在派生类中无论如何都不能访问。
- #include
- using namespace std;
-
- //基类People
- class People {
- public:
- void show();
- protected:
- char *m_name;
- int m_age;
- };
-
- void People::show() {
- cout << m_name << "的年龄是" << m_age << endl;
- }
-
- //派生类Student
- class Student : public People {
- public:
- void learning();
- public:
- using People::m_name; //将protected改为public
- using People::m_age; //将protected改为public
- float m_score;
- private:
- using People::show; //将public改为private
- };
-
- void Student::learning() {
- cout << "我是" << m_name << ",今年" << m_age << "岁,这次考了" << m_score << "分!" << endl;
- }
-
- int main() {
- Student stu;
- stu.m_name = "gh";
- stu.m_age = 22;
- stu.m_score = 100;
- stu.show(); //compile error
- stu.learning();
-
- return 0;
- }
代码中首先定义了基类 People,它包含两个 protected 属性的成员变量和一个 public 属性的成员函数。定义 Student 类时采用 public 继承方式,People 类中的成员在 Student 类中的访问权限默认是不变的。
不过,我们使用 using 改变了它们的默认访问权限,将 show() 函数修改为 private 属性的,是降低访问权限,将 name、age 变量修改为 public 属性的,是提高访问权限。
因为 show() 函数是 private 属性的,所以代码第 36 行会报错。把该行注释掉,程序即可正常运行。
- #include
- using namespace std;
-
- //基类People
- class People {
- public:
- void show();
- protected:
- char *m_name;
- int m_age;
- };
-
- void People::show() {
- cout << "嗨,大家好,我叫" << m_name << ",今年" << m_age << "岁" << endl;
- }
-
- //派生类Student
- class Student : public People {
- public:
- Student(char *name, int age, float score);
- public:
- void show(); //隐藏基类的show()
- private:
- float m_score;
- };
-
- Student::Student(char *name, int age, float score) {
- m_name = name;
- m_age = age;
- m_score = score;
- }
-
- void Student::show() {
- cout << m_name << "的年龄是" << m_age << ",成绩是" << m_score << endl;
- }
-
- int main() {
- Student stu("gh", 22, 100);
- //使用的是派生类新增的成员函数,而不是从基类继承的
- stu.show();
- //使用的是从基类继承来的成员函数
- stu.People::show();
-
- return 0;
- }
基类 People 和派生类 Student 都定义了成员函数 show(),它们的名字一样,会造成隐藏。第 37 行代码中,stu 是 Student 类的对象,默认使用 Student 类的 show() 函数。
但是,基类 People 中的 show() 函数仍然可以访问,不过要加上类名和域解析符,如第 39 行代码所示。

菱形继承有数据冗余和二义性的问题存在
数据冗余:Assistant类中有Student和Teacher类的,Teacher和Stuent类中又有Person类的,会导致Assistant中重复出现两份一样的Person类的成员的相关代码,并且在Assistant类调用构造函数,拷贝函数等函数时,也会重复调用两次Preson类的相同函数,造成空间浪费等问题,从而导致数据冗余问题
二义性:Assistant类继承了Student和Teacher类,那Student和Teacher类继承的Preson类的成员,在Assistant类中想访问Preson类成员,不能明确知道到底是Student类的还是Teacher类的,从而形成了二义性问题。二义性问题只能在Preson类成员前加Student::或Teacher:: 来明确到底是哪个类的
要解决菱形继承的这两个问题,就需要用菱形虚拟继承来解决
解决方法是在“腰部”类增加virtual关键字

这样做可以做到访问Person类中的成员时,访问的都是同一个成员
注意:在VS环境下,使用菱形虚拟继承后,内存中就不是之前那种成员挨个存储了,变为了下图所示的情形,内存中变为了A类,B类,C类,最下面才是D类,并且在加virtual的两个类中,他们内存中的第一个位置存储的是一个地址(虚基表),那个地址是此时的这个存储位置距离父类D中成员位置的字节数,可以通过这个地址,来找到父类的成员

在VS环境中,内存的情况就是上面图片所显示的, 地址中存储的就是该位置距离最下方存储的公共的D类成员的字节数,比如说A类和B类都只有一个成员,并且字节数都是4,那么A类所存储的地址中存的数值就应该是20,因为父类成员的位置与该位置中间隔了20个字节的距离
地址中存储的字节数称为距离或偏移量
所以根据这个知识点,我们在计算A类和B类的大小时,还需要加上那个地址的大小结合上面所说,一般我们不建议使用多继承,尤其是菱形继承,会在代码的复杂度和相关的性能上都有问题
组合也是一种类复用的手段。
eg:
- class Tire
- {
- protected:
- string _brand = "Michelin"; // 品牌
- size_t _size = 18; // 尺寸
- };
-
- class Car{
- protected:
- string _colour = "白色"; // 颜色
- string _num = "xxxxx"; // 车牌号
- Tire _t; // 轮胎
- };
区别:
public继承是一种is-a的关系,每个子类对象都是一个父类对象,例如“学生”是“人”(子类学生,父类人)
组合是一种has-a的关系,B组合了A,每个B对象中都有一个A,例如“车”包含“轮胎”
如果两个类既可以是is-a,又可以是has-a的关系,那么优先使用组合。