继承

继承

继承语法

class 子类名:继承方式1 基类1,继承方式2 基类2…{
      …
};

继承方式

公有继承:public
保护继承:protected
私有继承:private

继承方式

基类public成员

基类protected成员

基类private成员

public

public

protected

private

protected

protected

protected

private

private

private

private

private

这张表格里面公有继承,基类的访问控制权限不变; 保护继承,基类的访问控制权限最低为protected; 私有继承,基类的访问控制权限全部为private.

公有继承特点
  • 子类可以直接访问基类的所有公有和保护成员,其效果如同它们是在子类中声明一样
  • 对于基类的私有成员,在子类中存在但不能访问
  • 在子类中定义基类中同名的公有成员或保护成员,子类中的成员会隐藏基类同名成员,想访问被隐藏的成员,可以借助作用域限定符“::”
  • 子类对象任何时候都可以被当成基类类型对象(皆然性 ISA)
保护继承特点
  • 使基类公有成员和保护成员进行保护化,只禁止外部通过该子类访问
  • 子类指针或引用不能隐式转换成基类类型指针或引用
私有继承特点
  • 将基类公有和保护成员私有化,禁止外部通过该子类访问,也禁止该子类的子类访问
  • 子类指针或引用不能隐式转换成基类类型指针或引用

访问控制权限

访问控制限定符

访问控制属性

基类

子类

外部

友元

public

公有成员

OK

OK

OK

OK

protected

保护成员

OK

OK

NO

OK

private

私有成员

OK

NO

NO

OK

  • 不写继承方式,默认私有继承
  • 继承方式是用来指明基类成员在派生类中的最高访问权限的
  • 使用 using关键字可以改变基类公有和保护成员在派生类中的访问权限

继承演示

#include <string>
#include <iostream>
using namespace std;
class Human
{
public:
	Human()
	{
		cout << "Human  构造:" << this << endl;
	}
	void sleep(){
		cout << "基类 Sleep" << endl;
	}
private:
	int m_age;
protected:
	string m_name;
public:
	bool m_gender;  //0 女  1 男
	int m_show;
};  //基类 父类

class Student : public Human
{
public:
	Student() : m_id(0)
	{
		/*
		*	1.派生类可以直接访问基类的所有公有和保护成员,其效果如同它们是在派生类中声明一样
		*/

		//基类中的保护成员在派生类中可以访问
		m_name = "梦凡";
		//基类中的公有成员在派生类中可以访问
		m_gender = 1;

		/*
		*	2.对于基类的私有成员,在派生类中存在但不能访问
		*/
		//m_age = 10;
	}
	void show()
	{
		//隐藏基类中的m_show变量		基类中的m_show存在,用作用域限定符访问
		cout << "派生类中的 m_show:" << m_show <<" "<< "基类中的m_show:" << Human::m_show << endl;
	}
private:
	int m_id;
public:
	//	3.同名隐藏  派生类中定义基类中同名的公有成员或保护成员(成员包括成员变量和成员函数)
	int m_show;		//隐藏基类中的m_show变量		基类中的m_show存在,用作用域限定符访问
	int sleep;		//隐藏基类中的sleep()函数
};	//派生类 子类

int main()
{
	Student s;
	/*
	*	3.同名隐藏测试
	*/
	//s.sleep("水果");	//会报错 因为派生类中的sleep变量将父类中的sleep函数隐藏了
	s.m_show = 10;			//m_show为派生类中的m_show
	s.Human::m_show = 20;	//通过作用域访问基类中的m_show
	s.show();

	/*
	*	4.公有继承	派生类对象任何时候都可以被当成基类类型对象
	*				基类对象不能隐式转换成派生类对象
	*/
	//缩小访问范围 安全
	Human h = s;
	h.sleep();		//不报错,因为派生类对象隐式转换成基类对象

	Human* ph = &s;	//基类指针指向派生类对象	ph隐式指向基类部分
	Human& rh = s;	//基类引用指向派生类对象	rh隐式指向基类部分

	cin.get();
	return 0;
}

阻断继承

将基类构造函数设为私有,子类永远无法被实例化对象,实现阻断继承

//阻断继承 将构造函数声明为私有
class parent
{
private:
	parent() {}
};
class son :public parent
{
};
int main()
{
    //son s;	无法实例化对象
}

构造析构顺序

子类构造
  • 子类构造函数会调用基类构造函数,构造子类对象中的基类子对象
  • 子类构造函数没有显示指明基类构造方式,会选择基类的缺省构造函数(无参构造)
  • 子类显示调用基类构造函数可以在初始化表中显示指明构造方式。
  • 构造过程:构造基类子对象-》构造成员变量-》执行构造代码
子类析构
  • 子类析构会调用基类析构
  • 通过基类指针析构子类对象,只会析构子类对象中的基类子对象。可能造成内存泄漏
  • 析构过程:执行析构代码-》析构成员变量-》析构基类子对象
演示
#include <string>
#include <iostream>
using namespace std;
class Human
{
public:
	Human()
	{
		cout << "Human构造 this:" << this << endl;
	}
	Human(int age)
	{
		cout << "Human单参 this:" << this << endl;
	}
private:
	int m_age;
	string m_name;
	int m_gender;
};  //基类

class Student : public Human
{
public:
	//默认调用基类无参构造
	//Student() :m_no(0) {
	//	cout << "Student构造 this:" << this << endl;
	//}

	/*
	*	2.通过初始化列表选择基类的构造方式
	*/
	Student(int age) :Human(age), m_no(0)
	{
		std::cout << "Student单参构造 this:" << this << std::endl;
	}
private:
	int m_no;
};

int main()
{
	/*
	*	1.构造顺序	在构造析构里面分析过
	*	为整个对象分配内存
	*	构造基类部分(如果存在基类)
	*	构造成员变量
	*	执行构造函数代码
	*/
	//无参构造
	//Student s1;
	//单参构造
	Student s1(18);
	cin.get();
	return 0;
}

多继承

一个类可以从多个基类继承

多重继承内存布局

子类对象中的基类子对象按照继承表顺序依次构造

#include <iostream>
using namespace std;
class Telephone
{
public:
	Telephone()
	{
		cout << "Telephone构造" << endl;
	}
	void call()
	{
		cout << "打电话" << endl;
	}
	int m_t;
};

class Camera
{
public:
	Camera()
	{
		cout << "Camera构造" << endl;
	}
	void photo()
	{
		cout << "照相" << endl;
	}
	int m_c;
};

class Ipod
{
public:
	Ipod()
	{
		cout << "Ipod构造" << endl;
	}
	void music()
	{
		cout << "听歌" << endl;
	}
	int m_i;
};

//子类对象中的基类子对象按照继承表顺序依次构造
class IphoneXs :public Telephone, public Camera, public Ipod
{
public:
	IphoneXs()
	{
		cout << "IphoneXs构造" << endl;
	}
	int i;
};

int main()
{
	IphoneXs ipx;   //构造一个子类对象
	ipx.call();
	ipx.photo();
	ipx.music();
	cout << "------------------" << endl;

	//基类指针指向子类对象
	Telephone* t = &ipx;  //公有继承 皆然性   子类IS A父类
	Camera* c = &ipx;      //公有继承 皆然性   子类IS A父类
	Ipod* i = &ipx;		  //公有继承 皆然性   子类IS A父类

	cout << t << " " << c << " " << i << endl;
	cin.get();
	return 0;
}
Telephone构造
Camera构造
Ipod构造
IphoneXs构造
打电话
照相
听歌
------------------
006FF754 006FF758 006FF75C

棱形继承

中间子类有公共基类,导致最终子类存在多个公共基类,导致调用歧义通过使用::明确调用

#include <iostream>
using namespace std;
class A
{
public:
	A()
	{
		cout << "A构造" << this << endl;
	}
	int m_a;
	void foo()
	{
		m_a = 10;
		cout << m_a << endl;
	}
};	

class X :public A
{
public:
	X()
	{
		cout << "X构造" << this << endl;
	}
};

class Y :public A
{
public:
	Y()
	{
		cout << "Y构造" << this << endl;
	}
};

class Z :public X, public Y
{
public:
	Z()
	{
		cout << "Z构造" << this << endl;
	}
};

int main()
{
	Z z;
	//z.foo();   调用不明确   z对象中有X和Y的对象,X和Y中有A的对象
	//不知道调用X->A.foo 还是调用Y->A.foo();
	z.X::foo();
	z.Y::foo();

	//z.A::foo();
	z.X::A::foo();
	return 0;
}

虚继承

在公共基类继承方式前加关键字virtual 在公共基类对象保存到公共位置,存储偏移值

虚继承是为了解决棱形继承中成员访问的二义性。在A B继承方式前加关键字virtual,编译器将Base的数据保存在一个公共位置,通过虚基表访问。

#include <iostream>
using namespace std;
class A
{
public:
	A()
	{
		cout << "A构造" << this << endl;
	}
	int m_a;
	int m_b;
	void foo()
	{
		m_a = 10;
		cout << m_a << endl;
	}
};
/*
*	解决方法二:虚继承基类A
*/
class X :virtual public A
{
public:
	X()
	{
		cout << "X构造" << this << endl;
	}
};

class Y :virtual public A
{
public:
	Y()
	{
		cout << "Y构造" << this << endl;
	}
};

class Z :public X, public Y
{
public:
	Z()
	{
		cout << "Z构造" << this << endl;
	}
};

int main()
{
    cout << "A:" << sizeof(A) << ",X:" << sizeof(X) << ",Y:" << sizeof(Y) << ",Z:" << sizeof(Z) << endl;
	Z z;
	//z.foo();	//对“foo”的访问不明确	
	/*
	*	解决方法一:作用域::这么调用哪个父类里面的foo函数
	*/
	//z.X::foo();
	//z.Y::foo();
	z.foo();	//虚继承解决棱形继承问题
	cin.get();
	return 0;
}
A:4,X:8,Y:8,Z:12
A构造00F6F994
X构造00F6F98C
Y构造00F6F990
Z构造00F6F98C
10

End

本文分享自微信公众号 - 编程学习基地(LearnBase),作者:DeRoy

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 飞机大战

    我知道我很懒,一直拖一直拖,拖到最后一刻才肯开始撰写推送,但这次真的不是故意的,本来这个链表的应用我是想给大家介绍下线程池的设计,可是线程池这个东西得牵扯到网络...

    DeROy
  • Qt项目DeskGirl开发

    在主Widget上拖一个Widge控件,然后设置Widget的窗体,以「qss」的形式设置窗体的背景图

    DeROy
  • 五子棋

    一个不是很好的五子棋项目,因为以前没写过五子棋,或者说对于没有人机对决的AI五子棋,感觉没什么好写的。当然,我对算法这块也不怎么强,上次有朋友留言要五子棋项目,...

    DeROy
  • 控制反转-Ioc之Unity

    本篇幅主要介绍控制反转的一些概念,和如何使用Unity实现Ioc。在介绍的时候,会尽量结合代码来讲解一些概念。

    少羽大怪兽
  • 服务器失效那些事

    服务器出现故障是大家都非常关心的,而服务器由CPU,内存,磁盘,主板,电源等多种部件组成,一定会有一定的失效率。本文介绍服务器失效的特性及一些部件的失效标准,探...

    腾讯技术工程官方号
  • Mybatis【入门】

    什么是MyBatis MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁...

    Java3y
  • 建造者模式

    主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变...

    杨小杰
  • Mybatis入门

    发现一个非常好的Mybatis详细教程,地址:https://www.w3cschool.cn/mybatis/

    SuperHeroes
  • 扒一扒奥迪未来工厂里的七大黑科技

    奥迪一直将科技作为产品的卖点,这一次奥迪将科技发挥到了极致,为我们描绘出一座未来汽车工厂——奥迪智能工厂。在这座工厂中,我们熟悉的生产线消失不见,零件运输由自动...

    机器人网
  • 读取Excel还用POI?试试这款开源工具

    Java 后端程序员应该会遇到读取 Excel 信息到 DB 等相关需求,脑海中可能突然间想起 Apache POI 这个技术解决方案,但是当 Excel 的数...

    用户4172423

扫码关注云+社区

领取腾讯云代金券