【C++修行之路】面向对象三大特性之继承

前言

大家好久不见,今天我们来学习一下面向对象设计的三大特性之继承。

继承

概念

继承是一种面向对象的设计思想,和函数复用的目的一致,继承实现了代码的复用,是类设计层次的复用,允许程序员在保持原有类特性的基础上进行扩展,新产生的类被称为子类或派生类。

定义

//    派生类 继承方式  基类
class A  :  public   B
{
    
    }

其中涉及到继承方式和访问修饰限定符的问题,这里简单列举一下:

private: 私有,基类中私有的成员,派生类里无法访问
protect: 保护,基类中私有的成员,派生类里可以访问

1、基类private成员在派生类里无论以什么方式继承都是不可见的。语法上限制派生类对象类里类外都不能访问这些成员。
2、如果想在派生类中调用这些成员,就将基类的访问修饰限定符更改为protected,此操作符因为继承而出现。
3、基类的其他成员在派生类中的访问方式 = Min(成员在基类中的访问限定符,继承方式)[ public > protected > private ]
4、关键字class的默认继承方式是private、struct的默认继承方式是public
5、实际应用中尽量使用public继承,protected、private继承方式在维护拓展方面不强。

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

派生类对象 可以复制给 基类的对象、基类的指针、基类的引用。形象的说法叫做切片、切割,寓意把派生类中父类那部分赋值过去。

但是基类的对象不能赋值给派生类

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

继承中的作用域

1、继承体系里,基类和派生类都有自己独立的作用域
2、基类和派生类中有同名成员,子类成员屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义,因为两个类不在同一个类域,因此不构成函数重载,若想显示调用,可以使用 基类::基类成员 来显示访问
3、函数名相同就会构成隐藏,继承体系里不要定义同名成员

派生类的默认成员函数

我们知道c++类中六个默认成员函数,即使我们不写,编译器也会帮助我们生成,在派生类中,这几个函数怎么生成呢?

1、派生类的构造函数必须调用基类构造函数初始化基类的那一部分成员,如果基类没有默认构造,必须在派生类构造函数的初始化列表显示调用。
2、派生类的拷贝构造必须调用基类的拷贝构造完成基类的拷贝初始化。
3、派生类的赋值重载必须调用基类的赋值重载完成基类的复制。
4、派生类的析构函数会在被调用后调用基类的析构函数清理类成员,保证派生类对象先清理派生类成员,再清理基类成员的顺序
5、派生类的初始化要先调用基类构造再调用派生类构造
6、派生类的析构要先调用派生类析构,再去调用基类析构
7、编译器会对析构函数名特殊处理为destrutor(),如果父类析构不加virtual的情况下,子类析构和父类析构构成了隐藏

友元函数

我们知道,类里声明友元函数后,友元函数即可访问类内的成员,但这一友元关系不能继承,基类的友元不能访问子类的私有和保护成员:

class B;
class A
{
    
    
public:
	friend void print(const A& a,const B& b);

protected:
	int _a1 = 10;
};

class B : public A
{
    
    
public:
	~B()
	{
    
    
		cout << "~B()" << endl;
	}
protected:
	int _b1;
};

void print(const A& a,const B& b)
{
    
    
	cout << a._a1 << endl;
	cout << b._b1 << endl;//这里会报错,因为基类的友元无法继承到派生类
}

int main()
{
    
    
	A a;
	B b;

	return 0;
}

继承体系里的静态成员

基类中定义了静态成员,则继承体系里的静态成员只会有一个

class A
{
    
    
private:
	static int _a1;
};

int A::_a1 = 100;

极其复杂的多继承

多继承和单继承

只继承一个父类即为单继承,相反为多继承。多继承的设计思路是符合世界运行规则的,但这也让程序设计变得复杂,甚至会出现菱形继承一样的难题。

菱形继承

存在如图所示的继承关系:
在这里插入图片描述
代码如下,必须指定是哪个类域里的name,因为菱形继承会导致二义性冗余等问题。

class Person
{
    
    
protected:
	string _name;
};
class Teacher : public Person
{
    
    

};

class Student : public Person
{
    
    

};

class Assistant : public Teacher, public Student
{
    
    
public:
	Assistant()
	{
    
    
		Teacher::_name = "nhy";
		Student::_name = "niehaoyang";
	}
};

解决菱形继承二义性、数据冗余

采用虚继承的方式,即可解决这个问题,虚继承的格式如下:

class Person
{
    
    

};
class Student : virtual public Person
{
    
    

};
class Teacher : virtual public Person
{
    
    

};
class Assistant : public Student, public Teacher
{
    
    

};

通过这样的方式即可解决二义性和数据冗余。
在这里插入图片描述
可见原来两份数据被以指针的形式替换了,这两个指针被称为虚基表指针,两个表叫虚基表,虚基表中存有偏移量,即可定位到同一个成员,解决了冗余问题。

继承和组合

public继承是 is a 的关系,每个派生类都是一个基类对象
组合是一种has a的关系,B组合了A,则每个B对象都有一个A对象

一、优先使用对象组合,而不是类继承
二、继承允许我们根据基类的实现定义派生类,这种方式被称为白箱复用,基类的内部细节对子类可见,因此继承一定程度上破坏了基类的封装、基类如果改变,对派生类有很大的影响,派生类和基类耦合度高。
三、组合是另一种复用的选择,新的复杂功能可以组装多个类来得到。这种方式被称为黑箱复用,对象组合要求被组合的对象有良好定义的接口,组合类之间没有很强的依赖关系,耦合度低,代码维护性好。
四、因为组合的耦合度低,所以建议在实际中多使用组合,但继承并不是没什么用的,有些场景继承是更符合要求的,同时多态的实现也必须以来继承。

结语

以上就是本篇文章的全部内容了,如果你觉得有所收获,请给我一个三连,我们下次再见!

猜你喜欢

转载自blog.csdn.net/m0_73209194/article/details/129844107