C++继承

Posted SuchABigBug

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++继承相关的知识,希望对你有一定的参考价值。

目录


前言:我们先来回忆一下面向对象的三大特性。1.封装(更好的管理) 2.继承(是一种复用) 3. 多态(后续讲解,一种形态,不同的对象去完成时会产生不同的状态),今天就详细说下继承


一、继承的概念及定义

继承可以简单的理解为复用,允许程序在保持原有类特性的基础上进行扩展,增加新的功能,这样产生新的类称为派生

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

class Person 
public:
	void Print() 
		cout << "name: " << _name << endl;
		cout << "age: " << _age << endl;
	
protected:
	string _name = "henry";
	int _age = 18;
;

class Student : public Person

protected:
	int _stu_id;
;

class Teacher : public Person 
protected:
	int _work_id;
;

int main() 
	Student s;
	Teacher t;
	s.Print();
	t.Print();
	return 0;


上述例子可以看到,派生类继承了基类的成员函数和变量,复用了父类Person里的所有成员

继承基类访问方式的变化:

总结:

  1. 对于派生类而言基类的private成员是不可见的。不可见是指基类的私有成员还是被继承到了派生类对象中,语法上派生类对象限制不管在类里面还是类外面都不能去访问
  2. 基类的private成员在派生类中是不可被访问的,如果基类成员不能在类外直接访问,但需要在派生类中能访问,就定义为protected。不难看出protected限定符是因继承出现的
  3. 不能看出上述访问方式变化的规律为Min(在基类中的访问限定符,继承方式),始终取最小的那一个
  4. 使用class默认继承方式是private,struct默认继承方式是public
  5. 实际运用中一般使用public继承,因为protected/private继承下来的成员只能在派生类的类里面使用,实际扩展维护性不强

二、父类和子类对象赋值转换

派生类可以赋值给基类的对象、指针、引用。或者可以形象的叫做切片。寓意把派生类中父类那部分切来赋值过去

*但注意基类对象不能赋值给派生类对象

基类的指针可以通过强制类型转换赋值给派生类的指针。但必须是基类指针是指向派生类对象才是安全的

三、继承的作用域

  1. 基类和派生类都有各自独立的作用域
  2. 当子类和父类有同名成员时,子类成员会隐藏父类成员,这里会局部优先,如果硬要是访问父类则指定作用域,如Person::_num
  3. 如果是函数呢?只需要函数名相同就构成了隐藏
  4. 继承体系里最好不要定义同名的成员

WARNING:这里函数重载,是要求在同一个作用域,如果A类和B类中有同名函数,这是在不同作用域,不构成函数重载,而是构成隐藏关系,继承者函数同名就是隐藏

下列为同名时的例子,子类成员局部优先原则:

class Person

protected:
	string _name = "henry"; // name
	int _num = 111; // Personal ID
;
class Student : public Person

public:
	void Print()
	
		cout << " name:" << _name << endl;
		cout << " Personal ID:" << Person::_num << endl; //指定为父类的num
		cout << " student num:" << _num << endl;
	
protected:
	int _num = 999; // Stu num
;
void Test()

	Student s1;
	s1.Print();
;

int main() 
	Test(); //可以看出调用的是num=999,虽然继承了身份证,但父类的num隐藏起来了

四、派生类的默认成员函数

默认顾名思义,在派生类中不写,编译器会自动生成一个

  1. 对于构造函数:一般派生类调用基类的构造函数来初始化基类的那一部分。如果基类没有默认构造函数,那么派生类就必须写一个
  2. 对于拷贝构造:派生类拷贝构造函数也是一样的,必须调用基类的拷贝构造完成基类的拷贝初始化
  3. 对于operator=,必须调用父类的operator=完成父类的复制
  4. 对于析构函数:调用的顺序和构造的是完全相反的,我们只要写派生类的析构,而基类的析构在我们调用完成后会自动调用基类的析构函数清理基类成员。这样才能保证派生类对象先清理派生类成员的顺序
  5. 派生类对象初始化先调用基类构造再调派生类构造

五、继承和友元

友元关系是不能继承的,就像你爸的兄弟 你不能直接喊他爸。

可以看到基类的友元不能访问派生类的保护和私有成员的

六、继承与静态成员

静态成员在整个继承体系中只有一个这样的成员。无论派生出多少个子类,也就只有一个static成员实例

七、菱形继承和菱形虚拟继承

在说菱形继承前先了解单继承,单继承为Person,Student inherited Person,Postgraduate inherited Student

而多继承:一个子类有两个或者两个以上直接父类
比如Assistant学校助教,既是学生,亦是老师。 而学生和老师对象的基类是Person。这就出现了一个问题,Assistant这样的继承为菱形继承,有数据冗余和二义性问题,在Assistant对象中Person成员会有两份

如何解决?
虚拟继承,在Student和Teacherd继承Person时使用虚拟继承即可

class Person 
	
public:
	void Printf() 
		cout << "Name: " << _name << endl;
	
	string _name;

;

class Student: virtual public Person
protected:
	int _id;
;

class Teacher :virtual public Person 
protected:
	int _wkId;
;

class Assistant :public Student, public Teacher 
protected:
	string whichCourse;
;

void Test() 
	Assistant a;
	a._name = "henry";
	a.Printf();



int main() 
	Test();

虚拟继承的原理,我们通过一段代码来说明:

class A

public:
	int _a;
;
// class B : public A
class B : virtual public A

public:
	int _b;
;
// class C : public A
class C : virtual public A

public:
	int _c;
;
class D : public B, public C

public:
	int _d;
;
int main()

	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;


这里我们可以看到虚拟继承的内存对象成员模型,D对象中将A放到了对象组的最下面,A同时属于B和C。B和C对象是如何找到A的呢?实际上是通过B和C的两个指针指向一张表。B和C的两个指针叫做虚基表指针。虚基表中存的偏移量,通过偏移量可以找到A

最后总结一下多继承的存在,可能就会引发上述问题,一般不建议设计出多继承。否则在复杂度及性能上都有问题,后来JAVA就看到了这个缺陷后直接没有了多继承。

继承和组合:

一般优先使用组合而不是类继承
原因如下:

  1. 继承允许你根据基类实现来定义派生类的实现。这种通过派生类的复用被称为白箱复用。为什么称为白箱呢?因为基类的内部细节对子类可见,这在一定程度上破坏了基类的封装,基类的改变对派生类有很大影响。派生和基类的依赖关系强,耦合高
  2. 对象组合是类继承之外的另一种复用方式,对象组合要求被组合的对象具有良好的定义接口。这种称为黑箱复用,因为对象内部细节不可见,组合间没有很强的依赖关系耦合度低。优先使用黑箱复用有助于保持每个类被封装
  3. 如果要实现多态还得用继承,视情况而定

Question:

  1. 什么是菱形继承?菱形继承的问题是什么?

菱形继承是多继承的一种,B和C两个子类继承了同一个父类,B和C子类同时又被D继承了,这种特殊情况我们称为菱形继承

  1. 什么是菱形虚拟继承?如何解决数据冗余和二义性的

    B和C继承A时,为了解决数据冗余和二义性,我们在B和C加上virtual关键字
    这样就解决了D对象中存储了两份A类成员问题。我们通过B和C对象中的虚基表指针,指向虚基表,虚基表里放的是找到A的偏移量。
  1. 继承和组合的区别?什么时候用继承?什么时候用组合?

继承又称为白箱子复用,每个派生类对象都是一个基类对象,是is的关系,
组合又称为黑箱复用,假如B组合了A,每个B对象中都有一个A对象,是has的关系
如果类之间是is的关系如student is a person用继承,如果类之间是has的关系如The earth has live用组合,视情况而定不过一般优先使用组合,因为依赖关系弱,耦合度低。

以上是关于C++继承的主要内容,如果未能解决你的问题,请参考以下文章

C++继承之C++中不同的继承体系

C++继承之C++中不同的继承体系

C++继承汇总(单继承多继承虚继承菱形继承)

c++继承汇总(单继承多继承虚继承菱形继承)

继承(C++)

C++之继承详解