C++继承

目录

1.继承方式的区别总结

2.赋值兼容性规则

3.隐藏---变量&&函数

4.派生类的默认成员函数(精华)

(1)构造函数&&析构函数执行顺序

(2)析构函数

(3)拷贝构造函数

(4)重载赋值构造函数

(5)析构函数

(6)全部代码


1.继承方式的区别总结

(1)继承的方式有三种,分别是公有继承,私有继承,以及受保护的继承;

(2)基类的私有成员无论是是使用何种方式继承,在派生类里面和外面都是不可以进行访问的;但是如果像这个基类的公有成员被私有继承,这个时候基类的公有成员在派生类里面就是私有的,这个私有的成员在派生类里面是可以进行访问的,但是在派生类的外面是不可以进行访问的;

(3)我们的这个继承方式的组合尽管是多种多样的,但是这个经常使用的就是public继承,其他的这个继承组合我们了解即可,一般也是不会使用到的;

(4)继承里面就发挥了保护成员的作用,我们之前在学习这个类和对象的时候还看不出来这个保护成员和私有成员的区别,这个时候我们就可以发现,基类的私有成员在派生类里面是不可以直接访问的,但是如果我们想要这个成员在派生类里面被访问,但是在累的外面不可以访问,这个时候我们就可以把这个成员定义为受保护的成员;

(5)我们在写这个派生类的时候,例如基类是teacher类,子类是student类,这个时候,我们想要这个子类去公有继承父类,我们的写法就是class student:public teacher这个就是大部分教材的写法,我们需要知道的就是这个public继承方式是可以省略的,但是如果我们不写出来,这个继承方式就会被默认为是私有的,但是如果是使用的struct表示的,这个默认方式就是公有继承,这个也是class和struct之间的一个区别;

此外我们再进行这个类的定义的时候,我们知道,我们使用class定义的时候,这个里面的成员会被默认为是私有成员,但是如果我们使用struct去定义类的时候,我们里面的这个成员就会被默认为是公有成员;

即使如此,我们再进行这个继承类的书写的时候,还是建议把这个继承的方式标注出来;

2.赋值兼容性规则

(1)什么是赋值兼容性规则,这个规则是指的父子之间的兼容性规则,我们也称之为切割或者是切片,后面你就会明白为什么会这样起名字;

(2)上面的图片里面展示了三组例子,第一个就是double和int之间的相互兼容,这个是可以进行相互之间的赋值的,但是如果是引用的话,这个时候就会生成一个临时变量,临时变量具有常属性,我们需要加上const进行修饰,这个时候是生成了临时变量的;

第二组是使用的自定义类型的string类型的数据,这个当引用赋值的时候也是会生成一个临时变量的,我们依然需要使用const进行修饰,但是当两个class进行复制引用的时候,就不需要加上const进行修饰了;

(3)这个原因是什么,需要注意的是在这个例子里面我们是使用的public继承,而且这个person是父类,student是子类,我们是把这个子类赋值给父类对象,这个才是复制兼容性规则的使用条件,因为子类有的,父类不一定有,我们把这个需要复制的切割下来进行这个赋值,因此有人叫做切割或者是切片;

如果是父类给子类,这个父类有的子类都有,就不是切割了,所以父类不可以赋值给子类,这个在语法上面是会报错的;

(4)我们认为,子类对象赋值给父类对象,父类指针,父类引用,我们认为这个过程是天然的,这个就是兼容性规则 ;

3.隐藏---变量&&函数

(1)下面的就是一个变量的隐藏,这个是允许存在的,因为父类和子类的变量的作用于是不一样的,我们在子类里面去打印这个变量的值,不进行说明就会打印子类的,进行这个说明就可以打印这个父类的;

(2)当子类和父类里面有两个同名函数的时候,例如下面的这个情况:
这个时候,两个函数的参数的个数不一样,但是这个时候不会构成函数的重载,因为函数的重载要求函数的作用域是相同的,这两个函数一个是父类的,一个是子类的,作用域不一样,不是函数重载;

这个时候,两个func函数构成隐藏,我们无法在子类里面去调用父类的无参数的func函数,必须指明这个作用域,使用预作用限定符进行标明,否则就会报错;

(3)因此我们在编写程序的时候,尽量不要使用同名函数;

4.派生类的默认成员函数(精华)

(1)构造函数&&析构函数执行顺序

这个地方仅仅是作为一个铺垫,我们知道这个子类继承了父类的相关的成员变量和成员函数,所以构造的时候是先构造父类,再构造子类,否则子类是没有办法去继承父类的成员函数和成员变量的,而我们更清楚的就是,析构函数和构造函数的执行顺序是相反的;

如果构造的时候,先去构造父类,那么析构函数执行的时候,我们就会先去析构掉子类,但是析构函数为什么要按照这个顺序执行,这个背后是有自己的原因的,就是子类里面继承了父类的成员,因此我们可以理解为子类有父类的成员,如果我们先去析构掉父类,这个时候再去析构子类的时候,子类里面继承父类的成员已经消失了,这个时候就会出现问题,因此,我们先析构子类,再去析构父类,父类成员析构的时候就不会受到影响;

这个地方只是简单的介绍一下,后面我们会使用到这个知识;

接下来,我们会通过构造,拷贝构造和复制构造,以及析构函数,介绍这些构造函数在子类和派生类之间使用时候的注意事项,下面的就是这个父类的相关成员变量和成员函数,我们接下来的演示都会在这个基础上面展开;

(2)析构函数

下面的这个写法,就是很多初学者对于这个子类的构造函数的写法,实际上这个写法是错误的,因为语法上面要求,我们需要把这个父类的成员变量当做一个整体进行初始化;

正确的初始化的方法:也就是说我们对于这个子类写构造函数的时候,只需要管好自己的成员变量就可以了,对于父类的,我们调用父类的构造函数让他自己去初始化;

(3)拷贝构造函数

这个时候拷贝构造函数应该如何去写,我们的这个形参是s对象,如何把这个s对象里面属于父类的那一部分成员变量给抠出来,我们上面的构造函数,因为是name,因此这个当时可以直接构造,但是这个里面让父类执行自己的拷贝构造,我们需要把这个s对象属于父类的扣除出来,这个时候我们就可以使用上面介绍的复制兼容性规则,这个时候把子类的对象赋值给父类的时候,就会使用切片把这个子类里面继承父类的给传递过去,为什么标题上写这个是精华,因为这个把我们上面介绍的语法应用了起来;

(4)重载赋值构造函数

这个写法看上去好像没有问题,实际上这个写法是错误的,因为这个地方,我们应该去调用的是父类的operator=(s),但是这个里面因为成员函数同名,构成了函数的隐藏,子类的成员函数会把父类的隐藏掉,因此这个里面不会去调用父类的,而是去自己调用自己,就会出现栈溢出的情况;

正确的写法就是把这个函数前面加上父类和域作用限定符:

(5)析构函数

这个就比较特殊了,我们直接去调用这个person即父类的析构函数,我们是调用不动的,因为父子类的析构函数构成隐藏,即使这两个函数名是不一样的,但是这个地方依然会构成隐藏,我们需要加上域作用限定符;

但是即使这样,打印的结果里面会显示每一个对象都调用了两次构造函数,这个原因就是我们析构的时候,子类的构造函数结束之后,这个系统会自动调用父类的构造函数,保证析构的安全,因此我们自己写了一次,最后系统再去调用一次,所以每一个对象就调用了2次析构函数,我们只需要把自己写的析构父类给注释掉就可以了;

(6)全部代码

以下部分就是上面介绍的所有情况的测试代码,综合在一起了,读者下去可以自行的进行实操验证,了解背后的机理原因;

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

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

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

		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	Student(const char* name, int id)
		:_id(id)
		, Person(name)
	{
		cout << "Student(const char* name, int id)" << endl;
	}

	Student(const Student& s)
		:Person(s)
		,_id(s._id)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator=(const Student& s)
	{
		if (&s != this)
		{
			Person::operator=(s);
			_id = s._id;
		}

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

		return *this;
	}

	// 由于多态的原因,析构函数统一会被处理成destructor
	// 父子类的析构函数构成隐藏
	// 为了保证析构安全,先子后父
	// 父类析构函数不需要显示调用,子类析构函数结束时会自动调用父类析构
	// 保证先子后父

	~Student()
	{
		//Person::~Person();
		cout << "~Student()" << endl;
	}

protected:
	int _id;
};

int main()
{
	Student s1("张三", 18);
	Student s2(s1);

	Student s3("李四", 19);
	s1 = s3;


	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值