C++中的 继承

目录

1 继承

1.1 概念

1.2 基本语法

1.3 继承的方式

1.4 示例

1.4.1 public

1.4.2 protected

1.4.3 private

1.5 继承中的对象模型

1.6 继承中构造和析构顺序

1.7 继承同名成员处理方式

1.7.1 示例

1.8 继承同名静态成员处理方式

1.8.1 示例

1.9 多继承

1.9.1 示例

1.10 菱形继承


1 继承

1.1 概念

首先,对于一个学校,他有自己的学院/部门,学院下有自己的专业,如图

img

继承就是承接上一级的内容,同时延续自己下一级的内容

1.2 基本语法

 class 子类 : 继承方式 父类
 {}
  • 子类又称派生类 ,体现了个性

  • 父类又称基类,体现了共性

1.3 继承的方式

三种:

公共基础 public/ 保护继承protected / 私有继承private

他们有如下关系

img

显而易见的,父类中的private,无论是哪种继承方式,子类都不可访问

1.4 示例

1.4.1 public

 class B :public A
 {
 public:
   void func()
   {
     a = 1;// 父类中公共权限成员,到子类中仍然是公共权限成员
     b = 2;// 父类中保护权限成员,到子类中仍然是保护权限成员
     c = 3;// 父类中私有权限成员,不可访问
   }
 };

img

c不可访问

同时,带上测试函数

img

因为b是保护权限成员,只能在类内访问,类外不可访问

1.4.2 protected

然后是protected保护继承

 class B :protected A // 保护继承
 {
 public:
   void func()
   {
     a = 1;// 父类中公共权限成员,到子类中变成是保护权限成员
     b = 2;// 父类中保护权限成员,到子类中仍然是保护权限成员
     c = 3;// 父类中私有权限成员,不可访问
   }
 };

img

c不可访问

接下来是测试函数

img

可以看到,由于a/b是保护权限成员,因此类外都不能访问


1.4.3 private

 class B :private A // 私有继承
 {
 public:
   void func()
   {
     a = 1;// 父类中公共权限成员,到子类中变成是私有权限成员
     b = 2;// 父类中保护权限成员,到子类中仍然是私有权限成员
     c = 3;// 父类中私有权限成员,不可访问
   }

img

c不可访问

测试函数:

img

同样的,由于a/b是私有权限成员,因此类外都不能访问

1.5 继承中的对象模型

  • 父类中所有非静态成员属性都会继承给子类

  • 而私有成员属性同样继承过去,但是被编译器隐藏,因此无法访问

 class Base // 父类
 {
 public:
   int a;
 protected:
   int b;
 private:
   int c;
 };
 class Son :public Base
 {
 public:
   int d;
 };
 void test01()
 {
   cout << sizeof(Son) << endl;
 }

img


接下来我们使用vs的开发人员命令提示符工具展示真实的分布情况

  • 打开 vs2022的开发人员命令提示符工具

img

  • 跳转盘符:输入F:,点击回车

img

  • 再接着输入 cd 具体路径, 点击回车

img

  • 接下来输入 cl d1 reportSingleClassLayout子类名 文件名

文件名可以输入首字符,然后按一下Tab,就会自动补全

img

可以看到子类Son父类Base,总Size是16。

子类包含父类的abc变量和自身的d

1.6 继承中构造和析构顺序

子类继承父类后,创建子类对象,也会调用父类的构造函数

那么构造和析构的顺序是什么?


下面创建父类Base子类Son,并添加各自的构造和析构函数

 class Base // 父类
 {
 public:
   Base()
   {
     cout << "父类构造函数" << endl;
   }
   ~Base()
   {
     cout << "父类析构函数" << endl;
   }
 };
 class Son :public Base // 子类
 {
 public:
   Son()
   {
     cout << "子类构造函数" << endl;
   }
   ~Son()
   {
     cout << "子类析构函数" << endl;
   }
 };
 void test01()
 {
   Son s;
 }

img

因此,

构造的顺序: 父类 > 子类

析构的顺序: 子类 > 父类

1.7 继承同名成员处理方式

当父类和子类出现同名对象或函数

  • 访问类同名成员,直接访问

  • 访问类同名成员,加作用域(所有同名)

1.7.1 示例

创建父类Base子类Son,并在2者种加入属性m_A,在测试函数test01中尝试输出m_A

 class Base
 {
 public:
   Base()
   {
     m_A = 50; 
   }
   int m_A;
 };
 class Son :public Base
 {
 public:
   Son()
   {
     m_A = 100;
   }
   int m_A;
 };

img

直接输出,是子类同名成员

若要输出父类同名成员,要加上作用域

img

img


同理,如果要输出同名成员函数,也是要加父类的作用域

 class Base
 {
 public:
   Base()
   {
     m_A = 50; 
   }
   void func()
   {
     cout << "Base_func" << endl;
   }
   int m_A;
 };
 class Son :public Base
 {
 public:
   Son()
   {
     m_A = 100;
   } 
   void func()
   {
     cout << "Son_func" << endl;
   }
   int m_A;
 };
 void test02()
 {
   Son s;
   s.func();
   s.Base::func();
 }

img


同时,如果子类出现了和父类的同名函数,子类将会隐藏掉父类中所有的同名函数,如下

 class Base
 {
 public:
   Base()
   {
     m_A = 50; 
   }
   void func()
   {
     cout << "Base_func" << endl;
   }
   void func(int a) // 同名函数,而且重载
   {
     cout << "Base_fun(int a)" << endl;
   }
   int m_A;
 };
 class Son :public Base
 {
 public:
   Son()
   {
     m_A = 100;
   } 
   void func()
   {
     cout << "Son_func" << endl;
   }
 ​
   int m_A;
 };

子类中有func函数,而父类也func函数以及其重载函数func(int a)

此时即使子类没有同名重载函数,也不能直接调用重载函数,因为被隐藏

img

需要加父类的作用域

img

img

1.8 继承同名静态成员处理方式

与同名成员相同的处理方式

  • 访问子类同名成员,直接访问

  • 访问父类同名成员,加作用域(所有)

1.8.1 示例

父类Base与子类Son,都有静态成员static m_a

 class Base
 {
 public:
   static int m_a; // 类内声明
 };
 int Base::m_a = 10; // 类外初始化
 class Son :public Base
 {
 public:
 ​
   static int m_a;
 };
 int Son::m_a = 5;

接下来使用2种方式访问:

  • 通过对象访问

  • 通过类名访问

 void test01()
 {
   // 通过对象访问
   Son s;
   cout << s.m_a << endl;
   cout << s.Base::m_a << endl;
 ​
   // 通过类名访问
   cout << Son::m_a << endl;
   cout << Base::m_a << endl;
   cout << Son::Base::m_a << endl;
 }

img

值得注意的是,Son::Base::m_a,意思是,

Son::通过类名访问Base::作用域下的m_a数据

Base::m_a是直接通过类名访问m_a


同样的,如果要访问静态成员函数,也是加作用域或不加作用域

 class Base
 {
 public:
   static int m_a;
   static void func()
   {
     cout << "Base的静态成员函数" << endl;
   }
 };
 int Base::m_a = 10;
 class Son :public Base
 {
 public:
 ​
   static int m_a;
   static void func()
   {
     cout << "Son的静态成员函数" << endl;
   }
 };
 int Son::m_a = 5;
 void test02()
 {
   // 通过对象访问
   Son s;
   s.func();
   s.Base::func();
 ​
   // 通过类名访问
   Son::func();
   Base::func();
   Son::Base::func();
 }

img

同理,如果父类中出现了同名静态成员函数的重载,也会被隐藏,在调用时必须要加父类的作用域

1.9 多继承

语法:

 class 子类: 继承方式 父类1,继承方式 父类 2......

不建议使用

1.9.1 示例

 class Base1 // 父类1
 {
 public:
   Base1()
   {
     m_a = 100;
   }
   int m_a;
 };
 class Base2 // 父类2
 {
 public:
   Base2()
   {
     m_b = 200;
   }
   int m_b;
 };
 //class 子类: 继承方式 父类1,继承方式 父类 2......
 class Son :public Base1, public Base2
 {
 public:
   Son()
   {
     m_c = 300;
     m_d = 400;
   }
   int m_c;
   int m_d;
 };

使用Son类创建对象,并查看其大小

 void test01()
 {
   Son s;
   cout << sizeof(s) << endl;
 }

img

4个int,大小16

img

使用开发人员命令提示符也可以看到包含父类1和父类2的两个int,以及自己的2个int,一共是16


而当父类中出现同名成员时,不可以直接输出

 class Base1 // 父类1
 {
 public:
   Base1()
   {
     m_a = 100;
   }
   int m_a;
 };
 class Base2 // 父类2
 {
 public:
   Base2()
   {
     m_a = 200;
   }
   int m_a;
 };
 //class 子类: 继承方式 父类1,继承方式 父类 2......
 class Son :public Base1, public Base2
 {
 public:
   Son()
   {
     m_c = 300;
     m_d = 400;
   }
   int m_c;
   int m_d;
 };

可以看到,父类1和父类2都有m_a

img

尝试输出时,会提示不明确,因为重名了

因此,需要加作用域

img

1.10 菱形继承

img

  • 传电动汽车继承了汽车的price,纯汽油汽车同样继承了汽车的price,当混合动力汽车使用公共属性price时,就会产生二义性

  • 混合动力汽车继承自汽车的属性price继承了2份,造成额外开销,只需一份即可

代码:

 class car // 父类 车类
 {
 public:
   int price;
 };
 ​
 class pure_gasoline_car:public car // 纯汽油汽车
 {};
 class pure_electric_vehicle:public car // 纯电动汽车
 {};
 class hybrid_electric_vehicle :public pure_gasoline_car, public pure_electric_vehicle //混合动力汽车
 {};

而在测试函数中,不可直接调用任何一个的price

 void test01()
 {
   hybrid_electric_vehicle c;
   c.price = 10;
 }

img

必须要加上作用域

 void test01()
 {
   hybrid_electric_vehicle c;
   c.pure_gasoline_car::price = 10;
   c.pure_electric_vehicle::price = 20;
 ​
   cout << c.pure_gasoline_car::price << endl;
   cout << c.pure_electric_vehicle::price << endl;
 }

打印输出

img

但是,菱形继承导致数据有2份,造成资源浪费

使用开发人员命令提示符工具查看

img

确实会有2份

接下来,使用虚继承解决

   //        在public前加上virtua,变为虚继承
 class pure_gasoline_car:virtual public car // 纯汽油汽车
 {};
 class pure_electric_vehicle:virtual public car // 纯电动汽车
 {};
  • 在2个子类的继承方式前加上virtual,即变成虚继承

  • 此时,原父类/基类称为虚基类

再次输出

img

可以发现 打印出了同样的数据,因为此时两个子类的price共用同一块空间

因此我们不再需要区分作用域,也可以直接打印出price

img

使用开发人员命令提示符工具查看

img

可以看到,pure_gasoline_carvbptr(虚基类指针)指向它的虚基类表(下面红色),起始位置是0(左边红色),偏移量是8(下面虚基类表里写的8),因此指向8的位置

同理,pure_electric_vehicle的虚基类指针从4偏移4到8的位置,也指向8

因此,两个虚基类指针指向同一块空间

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值