【C++】继承知识点大总结

一、继承的概念及定义

1.1 继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。举个例子,更好地理解什么是继承:
在这里插入图片描述
现在有学生、老师、助教三类人。他们有各自的成员变量和成员函数,实现这三个类需要写三份代码,然而是不是显得有点冗余?因为他们之中有相同的功能(红色框)。再看下图:
在这里插入图片描述
这样看的话是不是能好一点,就相当于下面三部分继承了上面的,重复的部分写一遍就行了。

1.2 继承的定义

1.2.1 定义格式

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类:
在这里插入图片描述

1.2.2 继承关系和访问限定符

在这里插入图片描述

1.2.3 继承基类成员访问方式的变化

在这里插入图片描述
总结:

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它;
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的;
  3. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public;
  4. . 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承。
//class Student : public Person
//class Student : protected Person
//class Student : private Person

二、基类和派生类对象赋值转换

  1. 派生类对象 可以赋值给基类的对象 / 基类的指针 / 基类的引用;基类对象不能赋值给派生类对象。
  2. 可以使用基类指针或者引用指向派生类的对象,反之则不行

三、继承中的作用域

在继承体系中基类和派生类都有独立的作用域。
同名隐藏:在子类和基类中具有相同名称的成员(成员函数或者成员变量),需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。通过一段代码来展示一下:

// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
    
    
public:
	void fun()
	{
    
    
		cout << "func()" << endl;
	}
};
class B : public A
{
    
    
public:
	void fun(int i)
	{
    
    
		A::func();
		cout << "func(int i)->" << i << endl;
	}
};
void Test()
{
    
    
	B b;
	b.fun(10);
};

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

6个默认成员函数,“默认”的意思就是指我们不写,编译器自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用;
class Base
{
    
    
public:
	Base(int b)
		: _b(b)
	{
    
    
		cout << "Base()" << endl;
	}
protected:
	int _b;
};
class Derived : public Base
{
    
    
public:
	Derived(int b, int d)
		: Base(b)// 初始化基类部分继承下来的成员
		, _d(d)
	{
    
    
		cout << "Derived()" << endl;
	}
protected:
	int _d;
};
  1. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化;
class Base
{
    
    
public:
	Base(int b)
		: _b(b)
	{
    
    
		cout << "Base()" << endl;
	}
	Base(const Base& b)
		: _b(b._b)
	{
    
    }
protected:
	int _b;
};
class Derived : public Base
{
    
    
public:
	Derived(int b,int d)
		: Base(b)
		, _d(d)
	{
    
    }
	Derived(const Derived& d)
		: Base(d)
		, _d(d._d)
	{
    
    }
protected:
	int _d;
};
  1. . 派生类的operator=必须要调用基类的operator=完成基类的复制;
class Base
{
    
    
public:
	Base(int b)
		: _b(b)
	{
    
    
		cout << "Base()" << endl;
	}
	Base& operator=(const Base& d)
	{
    
    
		if (this != &d)
		{
    
    
			_b = d._b;
		}

		return *this;
	}
protected:
	int _b;
};
class Derived : public Base
{
    
    
public:
	Derived(int b,int d)
		: Base(b)
		, _d(d)
	{
    
    }
	Derived& operator=(const Derived& d)
	{
    
    
		if (this != &d)
		{
    
    
			__super::operator=(d);
			_d = d._d;
		}
		return *this;
	}
protected:
	int _d;
};
  1. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序;
  2. 派生类对象初始化先调用基类构造再调派生类构造;
  3. 派生类对象析构清理先调用派生类析构再调基类的析构。

五、继承与友元、静态函数

  1. 友元关系是不能继承的,因为友元不是类的成员;
  2. 普通成员变量、函数可以继承到子类中;
  3. 静态成员变量、函数也可以被子类继承

六、复杂的菱形继承及菱形虚拟继承

6.1 单继承

单继承:子类只有一个基类。
在这里插入图片描述

6.2 多继承

多继承:子类至少有两个基类

class B1;
class B2;
class D : public B1, public B2;
//多个基类前的访问限定符不能省略,如果没有写则是默认的访问权限

在这里插入图片描述
注意:如果是多继承,基类中的成员在子类中的排列次序与继承列表中的基类的先后次序一致。

6.3 菱形继承

在这里插入图片描述
派生类D中包含有两个_b,一个是从C1基类中继承的,另一个是从C2基类中继承的。但是:

D d;
d._b = 1;//编译器不知道放到从哪个基类中继承下来的_b,因此产生二义性

菱形继承存在的二义性问题可以体现在成员变量上,也可以体现在成员函数上。那么该如何解决呢?有两种方法:

  1. 让访问明确化,只需要在产生二义性成员前加上基类的名称。该种方式并没有从本质上解决问题,因为最顶层基类中的成员在最下层子类中仍旧有两份。
  2. 让最顶层基类中成员变量在最底层子类中只存储一份。因此引出了菱形虚拟继承的概念

虚拟继承:在继承权限前加上virtual关键字。
我们知道,普通继承是基类加上子类新增;而虚拟继承多出了一个虚基表指针,这个指针指向了偏移量表格,也叫虚基表。
在这里插入图片描述
如果通过派生类对象访问基类成员,先从对象前4个字节取出地址(虚表指针的地址),再在该地址向后偏移4个字节,取出该空间的内容(偏移量),从而让对象地址往后偏移对应偏移量(8)个字节,就拿到了子类对象中从基类中继承下来的成员变量。

虚拟继承唯一的作用:就是用在菱形继承中,解决菱形继承的二义性问题。菱形虚拟继承:
在这里插入图片描述

七、继承的总结和反思

很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。

继承和组合:
public继承是一种is-a的关系,也就是说每个派生类对象都是一个基类对象;组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

// Car和BMW Car和Benz构成is-a的关系
class Car
{
    
    
protected:
	string _colour = "白色"; // 颜色
	string _num = "陕ABCDEF"; // 车牌号
};

class BMW : public Car
{
    
    
public:
	void Drive() {
    
    cout << "好开-操控" << endl;}
};

class Benz : public Car
{
    
    
public:
	void Drive() {
    
    cout << "好坐-舒适" << endl;}
};

 // Tire和Car构成has-a的关系
class Tire
{
    
    
protected:
	string _brand = "Michelin"; // 品牌
	size_t _size = 17; // 尺寸
};

class Car
{
    
    
protected:
	string _colour = "白色"; // 颜色
	string _num = "陕ABCDEF"; // 车牌号
	Tire _t; // 轮胎
};

猜你喜欢

转载自blog.csdn.net/zhao_leilei/article/details/110948754