继承(C++)

目录

一.继承

1.定义:

2.语法

二.继承的作用

1.普通实现

2.继承实现(减少重复代码)

三.继承方式

1.公共继承

2.保护继承

3.私有继承

4.子类继承子类

四.继承中的对象模型

1.示例代码

2.开发人员命令提示符

五. 继承中构造和析构的顺序

六.继承中的同名问题

1.同名成员属性处理

2.同名成员函数处理

七.同名静态成员 

1.同名静态成员属性

2.同名静态成员函数

八.多继承

1.语法

2.代码案例

3.总结 

九.菱形继承

1.介绍

2.问题产生

3.解决办法

4.虚继承原理

虚基类表

虚基类表工作原理


一.继承

1.定义:

      继承,听起来好像是要继承遗产一样,但在编程里,它其实更像是一个“优点大派送”的过程。
      想象一下,有两位“宇宙超级无敌可爱的”老爸(也就是两个类),他们各自都有一堆超酷的技能和属性。然后,有一个“小机灵鬼”儿子(也就是子类)出现了,他想:“哇,这两位老爸的技能都太赞了,我能不能都要呢?”
      当然可以!于是,小机灵鬼使用了“继承”这个魔法,把两位老爸的所有优点都继承了过来。这样一来,他就可以同时拥有老爸A的篮球技巧和老爸B的音乐天赋了。
      但是,有时候两位老爸可能会有一些相同的“家产”(也就是属性或方法名),这可怎么办呢?没关系,小机灵鬼自有妙招。当他想要使用某个“家产”时,他会清楚地指明是“老爸A的”还是“老爸B的”。
      就这样,通过继承,小机灵鬼不仅拥有了两位老爸的优点,还成功地避免了“家产”冲突的问题。真是个聪明的家伙!
      所以,简单来说,继承就是“小机灵鬼”儿子把两位“宇宙超级无敌可爱的”老爸的优点都继承过来,同时还解决了可能出现的“家产”冲突问题。真是个一举两得的好方法!

2.语法

语法:class 子类:继承方式  父类
          子类 也称为 派生类 
          父类 也成为 基类

二.继承的作用

      当我们随便进入一个编程学习网页,引入眼帘的是各式各样的语言课程。今天我们简单学习一下,如何用C++去实现课程的排版的底层代码。 

1.普通实现

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;

//普通实现页面

//java页面

class Java
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}

	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}

	void left()
	{
		cout << "Java、Python、C++、...(公共分类列表)" << endl;
	}

	void content()
	{
		cout << "Java学科视频" << endl;
	}
};

//python
class Python
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}

	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}

	void left()
	{
		cout << "Java、Python、C++、...(公共分类列表)" << endl;
	}

	void content()
	{
		cout << "Python学科视频" << endl;
	}
};

void test01()
{
	cout << "Java下载视频页面如下:" << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "---------------------------" << endl;

	cout << "Python下载视频页面如下:" << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "---------------------------" << endl;
}

int main()
{
	test01();
	return 0;
}

2.继承实现(减少重复代码)

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;

//公共页面类

class BasePage
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}

	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}

	void left()
	{
		cout << "Java、Python、C++、...(公共分类列表)" << endl;
	}
};

//Java
class Java :public BasePage
{
public:
	void content()
	{
		cout << "Java学科视频" << endl;
	}
};
//Python
class Python :public BasePage
{
public:
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};
//C++
class CPP :public BasePage
{
public:
	void content()
	{
		cout << "CPP学科视频" << endl;
	}
};

void test01()
{
	cout << "Java下载视频页面如下:" << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "---------------------------" << endl;

	cout << "Python下载视频页面如下:" << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "---------------------------" << endl;

	cout << "CPP下载视频页面如下:" << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();
	cout << "---------------------------" << endl;
}

int main()
{
	test01();
	return 0;
}

三.继承方式

1.公共继承

class Base1
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son1 :public Base1
{
public:
	void func()
	{
		m_A = 10;//父类中的公共权限成员  到子类中依然是公共权限
		m_B = 10;//父类中的保护权限成员  到子类中依然是保护权限
		//m_C = 10;//父类中的私有成员    子类访问不到
	}
};

void test01()
{
	Son1 sa;
	sa.m_A = 100;
	//s1.m_B = 100; //到Son1中m_B是保护权限  类外访问不到
}

2.保护继承

class Son2 :protected Base1
{
public:
	void func()
	{
		m_A = 100;  //父类中公共成员,到子类中变为保护权限
		m_B = 100;  //父类中保护成员,到子类中变为保护权限
		//m_C = 100;//父类中私有成员  子类访问不到
	}
};

void test02()
{
	Son2 sa;
	//sa.m_A = 1000;//在Son2中m_A变为保护权限,因此类外访问不到
	//sa.m_B = 1000;//在son2中m_B保护权限  不可以访问
}

3.私有继承

class Son3 :private Base1
{
public:
	void func()
	{
		m_A = 100;//父类中公共成员 到子类中变为 私有成员
		m_B = 100;//父类中保护成员 到子类中变为 私有成员
		//m_C = 100;//父类中私有成员,子类访问不到
	}
};

void test03()
{
	Son3 sa;
	//sa.m_A = 1000;//到Song3中 变为 私有成员 类外访问不到
	//sa.m_B = 1000;//到Song3中 变为 私有成员 类外访问不到
}

4.子类继承子类

class GrandSon3 :public Son3
{
public:
	void func()
	{
		//m_A = 1000;//到了Son3中 m_A 变为私有,即使是儿子,也是不可访问
		//m_B = 1000;//到了Son3中 m_A 变为私有,即使是儿子,也是不可访问
	}
};

四.继承中的对象模型

1.示例代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;

//继承中的对象模型

class Base
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son :public Base
{
public:
	int m_D;
};

void test01()
{
	cout << "size of Son = " << sizeof(Son) << endl;
}

int main()
{
	test01();
	return 0;
}

      父类成员m_C在子类中无法访问,如何得知m_C是否被成功继承呢?我们通过计算子类的大小,得知父类中所有非静态成员属性都会被子类继承下去,父类中私有成员属性 是被编译器给隐藏了,因此是访问不到的,但确实是被继承下去了。接下来,教大家如何直观的观察子类中继承的私有成员。

2.开发人员命令提示符

      首先,找到代码文件的路径。右击代码文件名,选择打开所在文件夹,复制路径。

      其次,找到并打开vs命令提示符(X64_X86)。输入命令,跳转到文件所在的文件盘里(如在D盘,输入D:即可) 。cd 后加文件路径,dir 查询该文件夹下所有的文件。

      然后,输入命令cl /d1 reportSingleClassLayout+类名  +文件名。借由此工具,我们可以清晰看到Son类中的成员。 

五. 继承中构造和析构的顺序

      继承中的构造函数和析构顺序如下:先构造父类,再构造子类,析构的顺序与构造的顺序相反

class Base
{
public:
	Base()
	{
		cout << "Base构造函数" << endl;
	}

	~Base()
	{
		cout << "Base析构函数" << endl;
	}
};

class Son :public Base
{
public:
	Son()
	{
		cout << "Son构造函数" << endl;
	}

	~Son()
	{
		cout << "Son析构函数" << endl;
	}
};

void test01()
{

	Son a;
}

int main()
{
	test01();
	return 0;
}

六.继承中的同名问题

1.同名成员属性处理

      如果通过子类对象 访问到父类中的同名成员,需要加作用域。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;

class Base
{
public:
	Base()
	{
		m_A = 100;
	}
	void func()
	{
		cout << "Base - func()调用" << endl;
	}

	void func(int a)
	{
		cout << "Base - func(int a)调用" << endl;
	}
	int m_A;
};

class Son :public Base
{
public:
	Son()
	{
		m_A = 200;
	}

	void func()
	{
		cout << "Son - func()调用" << endl;
	}
	int m_A;
};

void test01()
{
	Son s;
	cout << "Son  下 m_A = " << s.m_A << endl;
	cout << "Base 下 m_A = " << s.Base::m_A << endl;
}

int main()
{
	test01();
	return 0;
}

2.同名成员函数处理


      a.直接调用 调用是子类中的同名成员;
      b.如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名函数;
      c.如果想访问到父类中被隐藏的同名成员函数,需要加作用域。

class Base
{
public:
	Base()
	{
		m_A = 100;
	}
	void func()
	{
		cout << "Base - func()调用" << endl;
	}

	void func(int a)
	{
		cout << "Base - func(int a)调用" << endl;
	}
	int m_A;
};

class Son :public Base
{
public:
	Son()
	{
		m_A = 200;
	}

	void func()
	{
		cout << "Son - func()调用" << endl;
	}
	int m_A;
};

void test02()
{
	Son s;
	s.func();//直接调用 调用是子类中的同名成员

	//如何调用到父类中的同名成员函数?
	s.Base::func();

	//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名函数
	//如果想访问到父类中被隐藏的同名成员函数,需要加作用域
	s.Base::func(100);
}

int main()
{
	test02();
	return 0;
}

七.同名静态成员 

1.同名静态成员属性

class Base
{
public:
	static int m_A;

	static void func()
	{
		cout << "Base - static void func()" << endl;
	}

	static void func(int a)
	{
		cout << "Base - static void func(int)" << endl;
	}
};
int Base::m_A = 100;

class Son :public Base
{
public:
	static int m_A;

	static void func()
	{
		cout << "Son - static void func()" << endl;
	}
};
//在C++中,静态成员变量必须在类外部进行初始化,
//这主要是因为静态成员变量不属于任何一个类的实例,而是属于类本身。
int Son::m_A = 200;

void test01()
{
	//通过对象访问
	cout << "通过对象访问" << endl;
	Son s;
	cout << "Son 下 m_A = " << s.m_A << endl;
	cout << "base 下 m_A = " << s.Base::m_A << endl;

	//通过类名访问
	cout << "通过类名访问" << endl;
	cout << "Son 下 m_A = " << Son::m_A << endl;
	//第一个::代表通过类名方式访问   第二个::代表访问父类作用
	cout << "base 下 m_A = " << Son::Base::m_A << endl;
}

int main()
{
	test01();
	return 0;
}

2.同名静态成员函数

class Base
{
public:
	static int m_A;

	static void func()
	{
		cout << "Base - static void func()" << endl;
	}

	static void func(int a)
	{
		cout << "Base - static void func(int)" << endl;
	}
};
int Base::m_A = 100;

class Son :public Base
{
public:
	static int m_A;

	static void func()
	{
		cout << "Son - static void func()" << endl;
	}
};
//在C++中,静态成员变量必须在类外部进行初始化,
//这主要是因为静态成员变量不属于任何一个类的实例,而是属于类本身。
int Son::m_A = 200;

void test02()
{
	Son s;
	//通过对象名访问
	cout << "通过对象访问" << endl;
	s.func();
	s.Base::func();

	//通过类名访问
	cout << "通过类名访问" << endl;
	Son::func();
	Son::Base::func();

	//子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
	//如果想访问父类中被隐藏同名成员,需要加作用域
	Son::Base::func(100);
}

int main()
{
	test02();
	return 0;
}

八.多继承

1.语法

         举例:子类  需要继承Base1和Base2
         语法:class 子类 :继承方式 父类 , 继承方式 父类,...

2.代码案例

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;

//多继承语法

class Base1
{
public:
	Base1()
	{
		m_A = 100;
	}
	int m_A;
};

class Base2
{
public:
	Base2()
	{
		m_A = 200;
	}
	int m_A;
};

//子类  需要继承Base1和Base2
//语法  class 子类 :继承方式 父类 , 继承方式 父类
class Son :public Base1, public Base2
{
public:
	Son()
	{
		m_C = 300;
		m_D = 400;
	}

	int m_C = 300;
	int m_D = 400;
};

void test01()
{
	Son s;

	cout << "sizeof Son = " << sizeof(s) << endl;
	//当父类中出现同名成员,需要加作用域区分
	cout << "Base1::m_A = " << s.Base1::m_A << endl;
	cout << "Base2::m_A = " << s.Base2::m_A << endl;
}


int main()
{
	test01();
	return 0;
}

3.总结 

尽管多继承在某些情况下可能看起来很有用,但它通常不被推荐使用,原因如下:

  1. 复杂性增加:当使用多继承时,类的层次结构变得更加复杂,这可能导致代码更难理解和维护。同时,多继承可能导致菱形继承(diamond inheritance)问题,即一个类从两个父类继承,而这两个父类又有一个共同的父类。这种情况下,可能会导致多重继承和歧义。
  2. 歧义性问题:在多重继承的情况下,如果一个方法在多个父类中都存在,那么子类在调用这个方法时就会产生歧义。子类需要明确指定要调用哪个父类的方法,这可能会导致代码难以阅读和维护。
  3. 增加错误的可能性:多继承可能导致命名冲突和隐藏继承问题。例如,子类可能无意中覆盖了父类的方法,或者父类的方法可能在子类中产生不可预见的行为。
  4. 破坏封装性:多继承可能会破坏对象的封装性。子类可能会访问和修改它不应该访问或修改的父类的属性和方法。
  5. 替代方案:在许多情况下,可以通过其他方式实现多继承的功能,如接口(interface)、组合(composition)或混合(mixin)等。这些替代方案通常更加灵活,易于理解和维护。

因此,尽管多继承在某些特定情况下可能有用,但通常建议避免使用它,而是寻找其他更简洁、更易于理解和维护的解决方案。 

九.菱形继承

1.介绍

       菱形继承(Diamond Inheritance)是面向对象编程中多继承的一种特殊情况,它涉及到一个类从两个不同的类继承,而这两个类又都继承自同一个基类。这种情况下的类层次结构图形成了一个菱形的形状,因此得名。
      具体来说,在菱形继承中:
                                             有一个顶层基类(Top Base Class)。
                                             有两个中间层类(Middle Classes),它们都继承自顶层基类。
                                             有一个底层派生类(Bottom Derived Class),它同时继承这两个中间层类。

2.问题产生


      底层派生类实际上间接地继承了顶层基类的两份拷贝。在某些编程语言中,这可能会导致问题,特别是如果顶层基类包含数据成员时。这是因为底层派生类可能会包含顶层基类数据成员的两个不同实例,这通常不是程序员所期望的。
这个问题在C++中尤为突出,因为C++支持多继承,并且默认情况下,每个基类在派生类中都有自己的实例。这可能会导致数据冗余和歧义,因为同一个基类的成员可能会在派生类中存在多个副本。

3.解决办法

      为了解决这个问题,C++提供了虚继承(Virtual Inheritance)的机制。通过使用虚继承,可以确保在菱形继承中顶层基类只被包含一次,从而避免了数据冗余和歧义。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;

//菱形继承

//动物类
class Animal
{
public:
	int m_Age;
};

//利用虚继承 解决菱形继承文本问题
//继承之前 加上关键字 virtual 变为虚继承
//Animal类称为 虚基类

//羊类
class Sheep :virtual public Animal
{

};

//驼类
class Tuo :virtual  public Animal
{

};

//羊驼类
class SheepTuo :public Sheep, public Tuo
{

};

void test01()
{
	SheepTuo st;

	st.Sheep::m_Age = 18;
	st.Tuo::m_Age = 28;
	//当菱形继承,两个父类拥有相同数据,需要加以作用域区分
	//这份数据我们知道,只有一份可以,菱形继承导致数据有两份
	
	//使用虚继承后,数据变为一份
	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
}

int main()
{
	test01();
	return 0;
}

4.虚继承原理

      使用虚继承后,SheepTuo会生成一个虚基表(运用vs命令提示符查询,本文第五点中的方法)如下图所示。

虚基类表

      C++中支持虚继承(Virtual Inheritance)机制的一部分。当使用虚继承时,编译器会生成一个虚基类表来帮助解决菱形继承(Diamond Inheritance)问题,确保基类在派生类中只被实例化一次。

虚基类表工作原理

      在C++中,当使用虚继承时,每个包含虚基类的对象都会包含一个指向虚基类表的指针。这个表存储了关于虚基类实例的位置信息。由于每个虚基类可能在对象内存布局中的位置不同,因此需要通过这个表来动态地找到虚基类的实例。

       在运行时,当通过派生类对象访问虚基类的成员时,编译器会首先查找虚基类表,以确定虚基类实例的位置,然后再访问相应的成员。

      继承的有关知识暂时分享到这里,感谢观看。

 看到这里,不妨点个攒,关注一下吧!

最后,谢谢你的观看 

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值