栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++ 基础 --继承

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++ 基础 --继承

C++继承
  • 继承的概念及定义
    • 继承关系和访问限定符
    • 基类和派生类对象的赋值转换
    • 继承中的作用域
    • 派生类的默认成员函数
    • 继承与友元
    • 继承与静态变量
    • 复杂的菱形继承以及虚拟继承
    • 虚继承与菱形继承
  • 继承的总结
    • 继承和组合

继承的概念及定义

继承是面向对象程序设计的重要特性,其本质就是类的复用。


这里属于 is - a 的关系,我们的student 和 teacher 都继承了父类(基类) person 。
继承后父类的person的成员 都会变为子类的一部分,实现类的复用。


继承关系和访问限定符


总结来说,继承后的访问限定是 基类成员 和 继承方式权限小的那一个。其中基类中的private是不可见的。
这里的不可见解释:
private也被继承到了派生类中,但是语法限制派生类对象不论在类里还是类外都无法访问它。

如果基类成员不想在类外被访问,但要被派生类访问,我们使用protected限定,我们可以看出,protected限定符是为了继承而出现的。

还有一个小细节是,class 默认继承方式是private ,而struct 是public ,我们一般不会省略继承方式。

实际运用中,一般都是使用public继承,通过控制基类的成员限定符来达到目的。


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

派生类对象 可以给基类的对象、 基类的指针、基类的引用,进行赋值。也叫做赋值兼容规则。

从图中我们可以很轻易的看出我们的子类给父类赋值的时候,仅会将子类中父类的部分进行赋值,我们可以形象的理解为切片。这就是派生类给基类赋值。



代码中的第三点我们先挖一个坑,之后再来填。

继承中的作用域

我们要知道,基类和派生类属于独立的作用域,此时就会产生问题,如果基类和派生类中有同名成员,子类对象将会自动屏蔽掉父类继承下来的同名成员,我们称此情况为隐藏,也叫做重定义。当然我们可以使用访问限定符的方式来指定基类中的同名成员。
函数名如果相同 直接构成了 隐藏/重定义。在此我们可以对比一下函数重载,他们的区别是:是否在同一作用域。重载一定是在同一作用域下的函数名相同。

基类::同名成员 (显式访问)



我们有一个派生类对象s,很容易通过监视窗口看出我们继承的结构
派生类成员在下,基类在上,且同名成员有不同的值。

想分别访问这两个不同的num,我们可以通过访问限定符的方式,默认为当前作用域。

派生类的默认成员函数

默认成员函数是指:我们不写编译器自动生成的函数。
其中有两种情况
1.我们真的没写
2.我们写了,但是是无参的或是全缺省的,我们都叫编译器自动生成
默认构造函数在类中只能有一个

接下来 ,我们来谈派生类中的默认构造函数
派生类的构造函数我们要构造两类东西,一个是自己的成员,还要构造父类的成员。
所以在构造自身的成员时,需要调用自己的构造函数,在构造父类的成员时,需要调用父类的成员函数。

class Person
{
public:
	Person(const char* name)
		: _name(name)
	{
		cout << "Person()" << endl;
	}
}



class Student : public Person
{
public:
	Student(const char* name, int id)
		: Person(name)
		,_id(id)
	{
		 调用父类构造函数初始化继承的父类部分
		 再初始化自己的成员
		cout << "Student()" << endl;
	}
}

当然如果基类没有默认构造函数,如上代码情况,我们需要在派生类构造函数初始化列表中显式调用。
派生类对象初始化先调用基类构造在调用派生类构造。

拷贝构造也是同理,我们需要通过基类的拷贝构造完成基类的拷贝初始化,因为拷贝构造就是构造的一种重载。

Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

Student(const Student& s)
		:Person(s)   ->s传递给Person& s 是一个切片行为
		, _id(s._id)
	{
		 类似构造函数
		cout << "Student(const Student& s)" << endl;
	}

派生类的operator= 必须调用基类operator= 完成基类赋值

Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}


	Student& operator=(const Student& s)/	{
		// 同上
		if (this != &s)
		{
			 小心这里是隐藏
		     Person::operator=(s);
			_id = s._id;
		}

		cout << "Student& operator=(const Student& s)" << endl;


		return *this;
	}

派生类的析构函数会在被调用完成后自动调用基类的析构函数,我们不需要向上面三个一样主动去调用基类的成员函数。
因为这样才能保证派生类对象先清理,基类对象后清理。


继承与友元

友元是什么,友元就是那个破坏封装的特性,我们都不怎么用。
友元函数一般定义在类外,然后在类内用friend 进行声明,这样,类外的函数就是这个类的友元。这个函数可以访问类内任意成员。

友元和继承有什么关系呢?
友元函数是不能继承的。父类有一个友元函数,继承下来。这个友元函数可不能任意对派生类私有和保护成员动手动脚。


这个Display可不能访问_stuNum这个派生类的保护成员。

继承与静态变量

基类定义了一个static静态变量,那么整个继承体系中,它具有唯一性。
无论派生了多少个子类,实例出的基类的static始终只有一个,这与static变量在内存中存放的位置有关,并不在栈帧中。

复杂的菱形继承以及虚拟继承

我们以上谈到的继承都是单继承,也就是单向一对一的继承方式。

那一定也会存在多继承,一个子类有两个或以上直接父类

至此还没有什么太大的问题,但就在这时,人们用了多继承发现,有一种特殊的多继承 —菱形继承,产生了很严重的问题。

菱形继承的问题:数据冗余和二义性。
也就是说在Assistant对象中会有两份Person成员

二义性:这两个同名变量_name,当我们要改变它的时候,究竟改变的是谁的数据,它的两个父类会受到牵连吗?这就叫做二义性。

c++开发人员处理了这个问题,但是数据冗余的问题无法解决。

此时c++实在无法容忍这样的数据冗余和二义性的效率问题,所以产生了一个新的语法:虚继承 virtual

虚继承与菱形继承

虚继承体系:

我们先来看看一般的菱形继承究竟是怎么样的。


通过访问限定符指定B和C中的a,并对其赋值,其结构很好理解,我们再来看看虚继承的方式,其底层是怎么样的


首先我们发现,A已经不在B和C对象附近了,而是浓缩成了一份写入了D对象的最高位。
那么C类和C类中除了自己的成员变量 还有四个字节,我们可以发现这四个低字节就是我们的地址,是谁的地址呢?
我们通过内存窗口发现其存的为一个偏移量,这个偏移量指向了最高处的A。
也就是说,B通过这个地址找到虚基表,然后在虚基表中找到偏移量,经过计算得到它所继承下来的A。
当然虚基表中除了这个偏移量,还有四个字节是有关多态的,我们在此不详谈。

一般情况下,还是建议大家不要设计出菱形继承这样过于复杂的程序


继承的总结

C++语法复杂,其中一个点,就是多继承的存在,导致菱形继承,衍生出虚拟继承。导致底层的实现很复杂,我们使用、分析、调试起来,也很复杂。所以建议不要设计菱形继承这样的程序,在复杂度和性能上都不优秀。
你看隔壁java都没有多继承这种奇怪的东西。

继承和组合

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

我们优先使用对象组合而不是类继承

继承允许你根据基类的实现来定义派生类的实现,我们将这种生成派生类的复用称为白箱复用,因为基类的内部细节对子类是可见的。
对象组合则是通过组合对象来获得更加复杂的功能,我们称之为黑箱复用,因为组合对象的内部细节是不可见的。
这样就导致继承的耦合度高,组合的耦合度低,而我们程序设计就是要追求低的耦合度。
因为继承的共有和保护成员都可以被派生类调用,所以A的所有成员对B都是透明的,也就是说A的封装对于B来说效果是很低的。
而反观组合,只有被组合的对象的共有成员才会影响C类。所以这就意味着A对C的封装是相对良好的。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/317740.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号