C++(第十一篇):继承(基类与派生类、菱形继承和菱形虚拟继承问题)

本文深入探讨C++中的继承概念,包括继承方式、基类与派生类的关系、继承中的作用域和隐藏、派生类的默认成员函数以及菱形继承和菱形虚拟继承问题。强调了在继承中使用public继承的普遍性,以及在多继承情况下,菱形继承可能导致的数据冗余和二义性问题,解释了虚拟继承如何解决这些问题。最后,文章提醒开发者在设计时应谨慎使用多继承,优先考虑对象组合,以降低耦合度并提高代码的可维护性。
摘要由CSDN通过智能技术生成

📒博客主页:Morning_Yang丶
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文所属专栏:【C++拒绝从入门到跑路】
🙏作者水平有限,如果发现错误,敬请指正!感谢感谢!

一、前言

面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。

当创建一个类时,不需要重新编写新的数据成员和成员函数,只需指定新建的类去继承一个已有的类即可。这个已有的类称为基类(父类),新建的类称为派生类(子类)


二、继承的概念

继承(inheritance)机制是面向对象程序设计中使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

如果我们需要描述一个学校里面的学生和教职工,有姓名、性别、专业/部门、学号/工号等信息,该怎么做呢?把学生和教职工中共有的信息拿出来创建一个 Person 类,新建两个类 Student 和 Teacher,继承 Person 类,它们就拥有了 Person 类中的数据成员和成员函数,实现代码复用。

image-20220126205713461

image-20220809171638843

// 基类
class Person {
   
	// _name 姓名
	// _age 性别
};

// 派生类
class Student : public Person {
   
	// _stuID 学号
	// _major 专业
};

// 派生类
class Teacher : public Person {
   
	// _teaID 工号
	// _department 部门
};

三、继承的方式

继承方式有以下三种,通过不同的访问限定符来指定继承的方式。

但我们几乎不使用 protectedprivate 继承,通常使用 public 继承。

  • 公有继承(public)
  • 保护继承(protected)
  • 私有继承(private)

继承产生了多种组合:

继承方式 / 基类成员 基类的 [ 公有 ] 成员 基类的 [ 保护 ] 成员 基类的 [ 私有 ] 成员
公有继承(public) 成为派生类的公有成员 成为派生类的保护成员 在派生类中不可见
保护继承(protected) 成为派生类的保护成员 成为派生类的保护成员 在派生类中不可见
私有继承(private) 成为派生类的私有成员 成为派生类的私有成员 在派生类中不可见

我们如何给[基类]成员设置合适的访问限定符:

  • 基类成员想让它被所有人访问,就设置成公有
  • 基类成员不想让它在类外被直接访问,但需要在派生类中被访问,就设置成保护
  • 基类成员不想让它在类外被直接访问,也不想让它在派生类中被访问,就设置成私有
  • 但我们使用继承时,很少在基类中设计 private 成员,通常设计 publicprotected 成员。

总结

  1. 基类 private 成员在派生类中无论以什么方式继承都是不可见的。这里的不可见并不是说不存在,而是指基类的私有成员还是被继承到了派生类中(派生类对象的内存空间中存在),只是语法上限制派生类对象不管在类里面还是类外面都不能直接去访问它。只能通过调用基类的公有保护成员函数来访问。

    image-20220126215233020

  2. 基类 private 成员在派生类中是不能被访问,如果基类成员不想让它在类外被直接访问,但需要在派生类中能访问,就定义为 protected。可以看出保护成员限定符是因继承才出现的。(我们之前说过,创建类时,不想在类外被访问的成员设置成 private / protected,可私有和保护到底有啥区别?继承这里就正好体现出来了)。

  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在派生类都是不可见。基类的其他成员在派生类的访问方式 == Min(成员在基类的访问限定符与继承方式比较,取权限更小的),public > protected > private。

  4. 使用关键字 class 时默认的继承方式是 private,使用 struct 时默认的继承方式是 public,不过最好显示的写出继承方式

  5. 在实际运用中一般使用都是 public 继承,几乎很少使用 protetced / private 继承,也不提倡使用 protetced / private 继承,因为 protetced / private 继承下来的成员都只能在派生类的类里面使用,甚至不能使用,实际中扩展维护性不强。


一个派生类继承了所有的基类方法,但下列情况除外:

  • 基类的构造函数、析构函数和拷贝构造函数。
  • 基类的重载运算符。
  • 基类的友元函数。

四、基类 & 派生类的赋值转换

派生类继承了基类的所有成员,那么我们可以把派生类对象赋值给基类吗?

注意:==我们这里讨论都是公有继承的情况下!==因为保护和私有继承后,「派生类中的基类数据成员」访问权限可能会发生变化,赋值过去后,基类又可以按照它原来的访问权限来操作「派生类中的基类数据成员」,这是有问题的!!!

  • 派生类对象 可以赋值给 基类对象 / 基类对象指针 / 基类对象引用。这里有个形象的说法叫切片或者切割。寓意把派生类中基类那部分数据成员切来赋值过去。(切的是数据成员赋值过去,因为同一个类的各对象中只储存各自的数据成员,成员函数共用一份,单独存放在对象之外的另一段存储空间中)

    image-20220127120335218

  • 基类对象不能赋值给派生类对象。

  • 基类对象指针(或引用)可以通过强制类型转换赋值给派生类对象指针。但是必须是基类指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用 RTTI(Run-Time Type Information)的 dynamic_cast 来进行识别后进行安全转换。

代码如下:

#include<iostream>
#include<string>
using namespace std;

// 基类
class Person {
   
protected:
	string _name;  //姓名
	string _sex;   //性别
	int _age;      //年龄
};

// 派生类
class Student : public Person {
   
protected:
	int id;  //学号
};

int main() {
   
	Student stu;
	// 1.派生类对象赋值给基类对象、基类指针、基类引用
	Person per = stu;
	Person* ptr = &stu;
	Person& ref = stu;

	// 2.基类对象不能赋值给派生类对象
	// stu = per;
	
	// 3.基类的指针可以通过强制类型转换赋值给派生类的指针,但最好用dynamic_cast进行转换,这样才是安全的

	return 0;
}

五、继承中的作用域 & 隐藏

  1. 在继承体系中 基类派生类 都有 独立的作用域
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
  4. 建议自己实际使用中,在继承体系里面最好不要定义同名的成员

【实例1】如下代码中, Student_num 成员和 Person_num 成员构成隐藏关系

// 当派生类和基类有同名成员时,派生类会隐藏基类成员,可以看出这样代码虽然能跑,但是非常容易混淆

// 基类
class Person {
   
protected:
	int _num = 10;  //序号
};

// 派生类
class Student : public Person {
   
public:
	void print() {
   
		cout << _num << endl;          //打印的是派生类的
		cout << Person::_num << endl;  //打印基类的必须指明类域
	}
protected:
	int _num = 20;  //序号
};

int main() {
   
	Student stu;
	stu.print();
	return 0;
}

【实例2】如下代码中, Bfun(int i) 成员函数和 Afun() 成员函数构成隐藏关系

// B中的fun和A中的fun不构成函数重载,因为在不同作用域
// B中的fun和A中的fun构成隐藏关系,在继承中,只要函数名相同就构成隐藏

// 基类
class A {
   
public:
	void fun() {
    
		cout << "fun()" << endl; 
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Morning_Yang丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值