【学习篇】【C++】【类和对象】【继承】

下级别的成员除了拥有上一级别的共性,还有自己的特性。
用继承的技术可以减少重复的代码。


三种继承方式:
公共继承、保护继承、私有继承

权限名称类内、类外访问情况继承访问情况
public公共权限成员 类内可以访问、类外也可以访问
protected保护权限成员 类内可以访问、类外不能访问继承中子类可以访问父类的保护内容
private私有权限成员 类内可以访问、类外不能访问继承中子类不能访问父类的保护内容

一、继承的基本语法和方式

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


子类中的成员包含两大部分:

  1. 一部分是从基类继承过来的,一部分是自己增加的成员。
  2. 从基类继承过来的表现其共性,增加的成员表现其个性。
class Base
{
public:
	int m_a;
protected:
	int m_b;
private:
	int m_c;
};

1.公共继承

父类中的被继承后子类
公共权限成员依然是公共权限
保护权限成员依然是保护权限
私有权限成员访问不到
//1.公共继承
class Son1 : public Base
{
public:
	void func()
	{
		m_a = 10; //父类中的 公共权限成员 到子类中依然是公共权限
		m_b = 20; //父类中的 保护权限成员 到子类中依然是保护权限
		//m_c = 30; //父类中的 私有权限成员 子类访问不到
	}
};

2.保护继承

父类中的被继承后子类
公共权限成员变为了保护权限
保护权限成员依然是保护权限
私有权限成员访问不到
//2.保护继承
class Son2 : protected Base
{
public:
	void func()
	{
		m_a = 10; //父类中的 公共权限成员 到子类中变为了保护权限
		m_b = 20; //父类中的 保护权限成员 到子类中依然是保护权限
		//m_c = 30; //父类中的 私有权限成员 子类访问不到
	}
};

3.私有继承

父类中的被继承后子类
公共权限成员变为了私有权限
保护权限成员变为了私有权限
私有权限成员访问不到
//3.私有继承
class Son3 : private Base
{
public:
	void func()
	{
		m_a = 10; //父类中的 公共权限成员 到子类中变为了私有权限
		m_b = 20; //父类中的 保护权限成员 到子类中变为了私有权限
		//m_c = 30; //父类中的 私有权限成员 子类访问不到
	}
};

//3.1 验证Son3的成员在类外访问不到的原因是私有继承,而不是保护继承。
class Grandson3 : public Son3
{
public:
	void func()
	{
		//m_b = 100;
		//m_a = 100; 
		//Base的成员m_a和m_b到了Son3中已经变为了私有成员,所以Grandson3即使作为Son3的儿子也是访问不到的。
	}
};

测试编译结果

void test01()
{
	Son1 s1;
	s1.m_a = 100; //说明在Son1中m_a是公共权限,类外依然可以访问
	//s1.m_b = 100; //说明在Son1中m_b是保护权限,类外访问不到

	Son2 s2;
	//s2.m_a = 100; //说明在Son2中m_a是保护权限,类外访问不到
	//s2.m_b = 100; //说明在Son2中m_b是保护权限,类外访问不到

	Son3 s3;
}

二、继承的对象模型

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;
}

由此可见一个Son类对象所占的内存空间是16,意味着父类所有非静态成员属性都会被子类继承下去。父类中私有成员属性是被编译器给隐藏了,因此访问不到,但确实被子类继承了。


Tips of 利用开发人员命令提示工具查看对象模型
跳转盘符 F:
跳转文件路径 cd 具体路径
查看命令 cl /d1 reportSingleClassLayout查看的类名 所属文件名


三、继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数
那么父类和子类的构造析构顺序是什么?

class Base
{
public:
	Base()
	{
		cout << "父类的构造函数" << endl;
	}
	~Base()
	{
		cout << "父类的析构函数" << endl;
	}
};

class Son : public Base
{
public:
	Son()
	{
		cout << "子类的构造函数" << endl;
	}
	~Son()
	{
		cout << "子类的析构函数" << endl;
	}
};

void test01()
{
	Son s1;
}

在这里插入图片描述
结合编译结果,可以看出结论是:先构造父类,再构造子类;先析构子类,再析构父类。


四、继承中同名成员处理

当子类与父类出现同名的成员,

  1. 访问子类同名成员 直接访问即可
  2. 访问父类同名成员 需要加作用域

【注意】
当子类与父类拥有同名的成员函数时,子类会隐藏父类中同名成员函数,加作用域后才可以访问到父类中的同名成员函数。

class Father
{
public:
	int m_a;
public:
	Father()
	{
		m_a = 100;
	}
	void func()
	{
		cout << "Father的func函数调用" << endl;
	}
	void func(int a)
	{
		cout << "Father的func重载函数调用" << endl;
	}
};

class Son : public Father
{
public:
	int m_a;
public:
	Son()
	{
		m_a = 200;
	}
	void func()
	{
		cout << "Son的func函数调用" << endl;
	}
};

1.同名成员属性

//同名成员属性
void test01()
{
	Son s1;
	cout << "Son的m_a = " << s1.m_a << endl;
	cout << "Father的m_a = " << s1.Father::m_a << endl;
	cout << "*********************" << endl;
}

2.同名成员函数

//同名成员函数
void test02()
{
	Son s2;
	s2.func();
	s2.Father::func();
	cout << "*********************" << endl;

	//s2.func(100); //无法访问到父类中重载后的成员函数func(int a)
	s2.Father::func(100);
	cout << "*********************" << endl;

五、继承中同名静态成员处理

静态成员属性在类外进行了初始化

class Father
{
public:
	static int m_a;
public:
	static void func()
	{
		cout << "Father的静态成员函数func调用" << endl;
	}
};
int Father::m_a = 100;

class Son : public Father
{
public:
	static int m_a;
public:
	static void func()
	{
		cout << "Son的静态成员函数func调用" << endl;
	}
};
int Son::m_a = 200;

1.同名静态成员属性

//同名静态成员属性
void test01()
{
	//1.通过对象访问
	cout << "通过对象访问: " << endl;
	Son s1;
	cout << "Son的m_a = " << s1.m_a << endl;
	cout << "Father的m_a = " << s1.Father::m_a << endl;

	//2.通过类名访问
	cout << "通过类名访问: " << endl;
	cout << "Son的m_a = " << Son::m_a << endl;
	cout << "Father的m_a = " << Father::m_a << endl;
	
	//第一个::是“通过类名方式访问”。第二个::是“访问父类作用域”
	cout << "Father的m_a = " << Son::Father::m_a << endl;
}

特别注意最后一行的Son::Father::m_a,这里的第一个::是通过类名方式访问。第二个::是访问父类作用域

2.同名静态成员函数

//同名静态成员函数
void test02()
{
	//1.通过对象访问
	cout << "通过对象访问: " << endl;
	Son s2;
	s2.func();
	s2.Father::func();

	//2.通过类名访问
	cout << "通过类名访问: " << endl;
	Son::func();
	Son::Father::func();
}

六、多继承语法

cpp允许一个类继承多个类
语法: class 子类 : 继承方式 父类1, 继承方式 父类2 …
!!!多继承可能会引发父类中有同名成员出现,需要加作用域进行区分
!!!在实际开发中不建议使用多继承

举例:
下面创建3个父类Father 1 2 3

class Father1
{
public:
	int m_a;
public:
	Father1()
	{
		m_a = 100;
		cout << "Father1调用" << endl;
	}
};
class Father2
{
public:
	int m_a;
public:
	Father2()
	{
		m_a = 200;
		cout << "Father2调用" << endl;
	}
};
class Father3
{
public:
	int m_a;
public:
	Father3()
	{
		m_a = 300;
		cout << "Father3调用" << endl;
	}
};
class Son : public Father1, public Father2, public Father3
{
public:
	int m_c;
	int m_d;
public:
	Son()
	{
		m_c = 10;
		m_d = 10;
	}
};

void test01()
{
	Son s;
	cout << sizeof(s) << endl;
}

在这里插入图片描述
由编译结果可见,Son类继承了三个类Father1Father2Father3。三个类的m_a加上Son自身的m_cm_d,一共就是5个int型的数据,因此共占了20字节的内存空间。


七、菱形继承问题

两个派生类继承同一个基类,又有某个类同时继承了两个派生类。又称为钻石继承。

问题:菱形继承导致基类的数据有两份,造成资源浪费。
解决方法:利用虚继承,在继承前加上关键字virtual

class Animal
{
public:
	int m_age;
};

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;
	cout << st.m_age << endl;
}

在这里插入图片描述
在这里插入图片描述

由此可知继承的不是两份数据,而是两份指针,两个指针会通过偏移量找到唯一的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值