继承复习笔记

1.基本概念

子类可以把父类的东西当作自己的东西来用(个人理解)

子类继承父类,相当于将父类的成员包含到自己的类里,所以定义子类对象所占用的空间大小除了
子类自身的成员还包括父类的成员。成员在内存空间分布为:先父类成员后子类成员,而每个类中
的成员分布与在类中定义的顺序一致。

继承的优点:我们可以将类中的一些功能相近、相似的共同的方法,抽离出来放到单独的一个类
中,并让其继承这个类,那么抽离出来的类就是父类,将来其他类在增加公共的方法时,我只需要
在父类添加一份即可。提高了代码的复用性、扩展性。

#include<iostream>
using namespace std;

class Person//基类
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18; // 年龄
};

class Student : public Person//子类
{
protected:
	int _stuid; // 学号
};
class Teacher : public Person//子类
{
protected:
	int _jobid; // 工号
};
int main()
{
	Student s;
	Teacher t;
	s.Print();
	t.Print();
	return 0;
}

 上述代码基类Person有姓名,年龄等属性, 这是派生类学生和老师共有的性质,所以可以统一写到基类当中,派生类老师和学生分别有自己的工号和学号,但也可以使用基类的姓名和年龄等属性。

继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和Teacher复用了Person的成员。

2.继承的方式 

使用private继承,父类的protected和public属性在子类中变为private
使用protected继承,父类的protected和public属性在子类中变为protected
使用public继承,父类中的protected和public属性不发生改变

基类的private ,子类咋的死活都不能通

protected子类可以访问,但是除了在父类和子类的外面,不可访问

public随便访问

3.构造-析构执行顺序

class CSTepFather {
public:
	int m_a;
	CSTepFather() {
		m_a = 10;
	}
	CSTepFather(int a) {
		m_a = a;
	}
};

class CSon {
public:
	CSTepFather m_stepFa;   //包含另一个类的对象
	int m_son;
	CSon()/*:m_stepFa()*/ {    // 当包含另一个类对象,编译器会自动调用无参的构造进行初始化
		m_son = 20;
	}
	CSon(int a):m_stepFa(a) {    // 如果像调用带参数的构造函数进行初始化,需要手动显式指定
		m_son = 20;
	}
};

当一个类包含另一个类的对象时编译器会自动调用无参的构造函数

类比继承 派生类编译器默认自动调用继承的父类的无参构造

定义子类对象时执行顺序:父构造 ‐> 子构造 ‐> 孙构造 ...| ... 孙析构 ‐> 子析构 ‐> 父析构。
构造顺序说明:在子类创建对象的时候,执行子类的构造函数(注意这里并不是直接先执行父类的
构造函数),但要先执行子类的构造的初始化列表, 在初始化列表中会默认调用父类的无参构造初
始化父类成员 ,如果父类只有带参数的构造,那么需要在子类的初始化参数列表显示的指定父类的
初始化。这有点像之前说的组合关系形式。
析构顺序说明:子类对象的生命周期结束后,因为是子类所以自动调用子类析构,当析构执行完
了,才会回收对象分配的空间,当然这个空间包含创建的父类的成员,那么回收父类成员前,自动
调用父类的析构。如果是 new 出来的子类对象,同理。

4.隐藏

对于函数重载而言,我们调用的时候,可以根据参数类型、参数个数,编译器自动区分该具体调用
哪个函数。同样如果在一个类中存在两个同名函数(参数列表不同),那么也可以根据调用者传递
的参数自动的区分执行哪个函数,因为也是一个函数重载的关系。
那对于父类和子类中,如果有同名的函数但是参数列表不同,则不能够自动区分,因为他们之间的
关系并不是函数重载的关系,作用域不同,必须使用 类名 :: 去区分到底该调用哪个函数。子类中
的重名函数我们称之为 隐藏
在继承关系下,允许父类的指针指向子类的对象,但是反过来却不行,这么做的好处是:父类的指
针可以统一多个类的类型,提高代码的复用性、扩展性。

函数重载条件:

  1. 同一作用域
  2. 函数名相同,函数参数不同

而隐藏是不同作用域下的(一个作用域是基类,另一个是子类)

在继承体系中基类派生类都有独立的作用域。而如果子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用基类::基类成员显示访问)

5.赋值转换

子类对象可以赋值给 父类的对象 / 父类的指针 / 父类的引用。这里有个形象的说法叫切片或者切割。寓意把子类中父类那部分切来赋值过去。并且这个过程中没有类型转换。

class Person
{
protected:
	string _name; 
	string _sex;  
public:
	int	_age;	 
};
class Student :public Person
{
public:
	int _No;
};
int main()
{
	Person p;
	Student s;
	//中间不存在类型转换
	p = s;
    //引用赋值
	Person& rp = s;
    rp._age = 1;
    //指针赋值
	Person* ptr = &s;
	ptr->_age = 2;
	return 0;
}

 6.设计一个无法被继承的类

方式一:将基类的构造函数设置成private权限

#include<iostream>
using namespace std;

class A
{
	private:
	A()
	{
		cout << "A()" << endl;
	}

	protected:
	int _a;
};

class B :public A
{
public:
	B()//无法继承了,会报错
	{
		cout << "B()" << endl;
	}
	void func()
	{
		cout << _b << endl;
		cout << _a << endl;
	}


protected:
	int _b;
};


int main()
{
	B b;
	b.func();
	return 0;
}

​ 我们已经知道基类中的private成员在派生类中是不可见(不可访问)

​ 上述代码中,B继承了A,那么在B的构造函数中是会去自动调用A的构造函数的,而A的构造函数时不可见(不可被B访问),所以是会报错的。这是一个很巧的办法。

方式2:加上关键词final

​ 当我们在基类设计时,在后面加上final关键字,那么此类就无法被继承。

#include<iostream>
using namespace std;

class A final
{
public:
	A()
	{
		cout << "A()" << endl;
	}

	protected:
	int _a;
};

class B :public A
{
public:
	B()
	{
		cout << "B()" << endl;
	}
	void func()
	{
		cout << _b << endl;
		cout << _a << endl;
	}


protected:
	int _b;
};


int main()
{
	B b;
	b.func();
	return 0;
}

 7.菱形继承

菱形继承概念:

两个派生类继承同一个基类

又有某个类同时继承者两个派生类

这种继承被称为菱形继承,或者钻石继承。

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。

  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

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 = 100;
	st.Tuo::m_Age = 200;

	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
	cout << "st.m_Age = " << st.m_Age << endl;
}


int main() {

	test01();

	system("pause");

	return 0;
}

动物类为基类,羊继承了动物类,驼也继承了动物类,羊驼类继承了羊类和驼类,那么羊驼则继承了两份动物,也就是两份m_Age.

这样就形成了菱形继承,也就会产生数据冗余–m_Age有两份,二义性––m_Age同名,访问不明确

若是硬要访问需要加上类名作用域用来区分,到底访问的是哪个age:

	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;

 解决办法是虚继承,加上virtual:

class Sheep : virtual public Animal {};
class Tuo   : virtual public Animal {};

这样一来两份age就变成了唯一的一份,数据共享,最终age的值以最后一次修改的值为标准

底层原理:

原来羊和驼类继承了两份age,virtual之后,改为继承了vbptr指针,指针指向的就是虚基表,虚基表里面有虚基类的偏移量最终指向同一个age

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值