c++中的继承

今天我们谈c++的继承,其实继承大家都不陌生了,但是c++对于继承与java还是有很大的区别的。

继承的概念

继承其实就是设计层次上的复用。类比与模板,模板只能用于类型,而且继承属于变量层次上的。
假如说现在我需要管理学生、老师、宿管、保安等等人,那么你还能用模板解决吗?不行,因为模板只能是类型,对于变量我们可以用继承的方式,继承是is-a的关系。
我们把被继承者是父类或者基类,继承者是子类或者派生类
在这里插入图片描述

继承的格式

格式: class 子类 :(继承方式)访问限定符 父类
在这里插入图片描述

继承关系和访问限定符

在这里插入图片描述
图上其实是每本教科书中最常见的部分。

总结

①.基类的私有成员在派生类不可见。父类的私有成员子类不能直接使用,但是可以间接使用:get/set方法、调用父类中的公有/保护方法。
②.子类继承父类后,访问方式:在访限定符和继承方式中,取权限小的。例如,我的访问权限是公有,但是你私有继承,取权限小的就是私有。
③.保护和私有的区别:保护也可以在子类中使用,其他地方不可以。私有是子类和其他地方都不能使用。例如:下面代码我的父类成员name就是一个保护,但是子类继承后可以通过函数访问,注意:类内通过它自己的实例对象访问

class person
{
public:
	person(string name="zhangsan",string sex="男")
		:_name(name)
		,_sex(sex)
	{}
	void Print()
	{
		cout << _name << endl;
	}

protected:
	string _name;
private:
	string _sex;
};
class  student :public person
{
public:
	 student(int idnum=1)
		 :_idnum(idnum)
	 {}
	 void func()
	 {
		 _name = "lisi";
		 cout << _name << endl;
	 }
private:
	int _idnum;
};
int main()
{
	student s;
	s.func();
	return 0;
}

④.常用的访问限定符:成员公有保护,继承一般都是公有继承。
⑤.struct 也可以继承,且继承方式可以不写,struct默认继承方式是公有,class默认继承方式是私有。但是一定要写清楚!

基类和派生类对象赋值转换

我们知道类型相似的变量可以相互赋值。例如整型和字符。不同类型进行赋值:相近类型,意义相似的可以进行转换。隐式类型转化,产生临时变量。那么子类可以复制给父类吗?可以(注意必须是公有继承),并且不会产生临时变量

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

切片/切割的概念

其实我们之前也说过继承就是一个is-a的关系,每个子类都是一个特殊的父类。所以可以赋值很正常,但是会发生切割,也就是把子类内有父类的部分切割赋值给父类,并且存在赋值兼容,不会产生临时变量。
在这里插入图片描述
在这里插入图片描述

继承中的作用域

我们学了四种作用域有:全局域,局部域,类域,命名空间域。而继承中的属于类域的一种。类域可以细分成基类域和派生类域。基类和派生类是独立作用域,所以不同的域可以定义同名成员访问默认就近原则

问题:不同类,同名函数继承后还构成重载吗?
答:不,重载是同域。这种情况只会构成隐藏。若子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。

在这里插入图片描述

class person
{
public:
	person(string name="zhangsan",string sex="男")
		:_name(name)
		,_sex(sex)
	{}
	void Print()
	{
		cout << _name << endl;
	}
	void func()
	{
		_name = "wangwu";
		cout << _name << endl;
	}

protected:
	string _name;
private:
	string _sex;
};
class  student :public person
{
public:
	 student(int idnum=1)
		 :_idnum(idnum)
	 {}
	 void func()
	 {
		 _name = "lisi";
		 cout << _name << endl;
	 }
private:
	int _idnum;
};
int main()
{
	student s;
	s.func();
}

如果上述情况,我们就是要访问父类的func怎么办呢?就需要用访问鉴定符指定。
在这里插入图片描述
注意:我们一定要避免重名情况!

继承后派生类的默认成员函数

构造函数

我们之前说内置类型不做处理,自定义类型去调用它的构造函数。当派生类继承了基类,那么派生类成员就有三部分:父类的成员,子类的a.内置成员b.自定义成员
我们可以把父类是一个整体的自定义类型。父类会去调用它的默认构造。如果没有默认构造会报错

class person
{
public:
	person(string name,string sex)
		:_name(name)
		,_sex(sex)
	{
	//会报错,因为构造函数不是默认构造。
	}
	void Print()
	{
		cout << _name << endl;
	}
	void func()
	{
		_name = "wangwu";
		cout << _name << endl;
	}

protected:
	string _name;
private:
	string _sex;
};
class  student :public person
{
public:
	 student(int idnum=1)
		 :_idnum(idnum)
	 {}
	 void func()
	 {
		 _name = "lisi";
		 cout << _name << endl;
	 }
private:
	int _idnum;
};

我们可以显示调用。如图下,当父类没有默认构造,我们子类显示调用,从子类的初始化列表,利用匿名对象。在这里插入图片描述

析构函数

我们刚刚显示调用了构造函数,那我们是不是也可以显示调用析构函数呢?会发现我们多调用了一次,就会导致二次析构。
在这里插入图片描述

问题:为什么子类的析构也会隐藏父类?
析构函数可以显示调用。子类的析构也会隐藏父类。因为多态的需求,析构的函数名会被统一为destructor。都叫destructor就是同名了,所以构成了隐藏。
问题2:为什么我显示调用析构,会出现父类析构两次?
答:析构一定要保证先父后子,原因是,父类先析构,但是子类还有,就会导致野调用,所以先子后父。

构造的顺序是先父后子。析构保证先子后父,否则会多调用一次,出现析构两次报错

拷贝构造

(1).内置类型值拷贝,自定义类型去调用它的拷贝构造。父类会去调用他自己的拷贝构造。
(2).如果需要深拷贝需要写拷贝构造。
(3).对父类的拷贝构造,子类对象可以赋值兼容。
在这里插入图片描述

赋值重载

内置类型值拷贝,自定义类型去调用它的拷贝构造。父类会去调用他自己的赋值重载。父类调用父类的赋值。
那么深拷贝怎么办呢?我显示的写怎么办?显示调用赋值重载。但是图下有问题,存在隐藏问题,会导致栈溢出,所以要制定作用域。
在这里插入图片描述

继承和友元(了解)

你是父类的友元,继承后,耶不是子类的友元。友元关系不能继承,如果也想访问子类,可以在子类加友元,一个函数可以多个友元

继承和静态成员

静态成员只有一份,父类静态成员属于当前类也属于当前类的所有派生类。父子类共用一份
题目:统计person的派生类有多少个?
解:可以利用静态变量,在构造函数中自增。

单继承和多继承

单继承

我们刚刚说的继承都是单继承。一个子类只继承了一个父类。

多继承

一个子类继承了两个及以上父类。
在这里插入图片描述

class A {
public:
	void funv()
	{
		cout << _a << endl;
	}
private:
	int _a = 0;
};

class B
{
public:
	void funv()
	{
		cout << _b<< endl;
	}
private:
	int _b = 0;
};
class C:public A,public B
{
public:
	void funv()
	{
		cout << _c << endl;
	}
private:
	int _c = 0;
};

为什么存在多继承?

因为有些事物单继承是无法解决的,比如说西红柿即使蔬菜又是水果,鸭嘴兽即使哺乳动物又是卵生动物。所以多继承存在就很合理。

菱形继承

既然多继承那么好用,肯定是有弊端的。多继承很合理,但是有多继承就会有菱形继承
在这里插入图片描述
为什么菱形继承有弊端?因为他会存在数据冗余和二义性

二义性

如上图,学生你可能叫张三,但是在老师中你叫张老师,那么我如果要访问你的名字,会存在不确定性,你到底叫什么?

数据冗余

当我创建Assistant对象,那么我这个对象是不是会有两份person里的数据,就会有数据冗余。

解决方案:虚拟继承

虚拟继承可以解决代码冗余,让你继承父类中的一份,并且间接解决了二义性。注意:virtual–>加载菱形继承的腰部位置,菱形继承在继承基类的时候加virtual
在这里插入图片描述

//虚拟继承
class A {
public:
	void funv()
	{
		cout << _a << endl;
	}

	int _a = 1;
};

class B :virtual public A
{
public:
	void funv()
	{
		cout << _b<< endl;
	}

	int _b = 2;
};
class C:virtual public A
{
public:
	void funv()
	{
		cout << _c << endl;
	}
private:
	int _c = 3;
};
class D : public B,  public C
{
public:
	void funv()
	{
		cout << _d << endl;
	}

	int _d =4 ;
};

会发现虚拟继承之后,a只有一份在D中,并且按照继承的顺序,BC也依次存储在D中。实践中可以实现多继承,但是不要涉及菱形继承,很麻烦容易出错。
在这里插入图片描述

组合与继承

组合是什么? 如果B想用A里面的成员,可以创建对象。组合和继承都是一种复用。一个类可以复用另一个类。

  • 继承和组合的区别:
    ①.继承是一个is -a的关系,每个派生类都是一个基类。而组合是一个has-a的关系,就是B组合A,每个B对象中都有一个A。
    ②.优先使用对象组合,而不是继承。
    继承是一个白箱复用,组合是一个黑箱复用。
    黑箱复用:内部细节看不到。—耦合度低:类和类之间,模块和模块之间关系不那么紧密,关联度不高。
    白箱复用:内部细节一目了然。–耦合度高:类和类之间,模块和模块之间关系紧密,关联度高。
    ③继承会导致所有成员派生类都可用,我可能会修改/删除等。
  • 使用:
    适合is-a关系,就用继承;适合has-a用组合;is-a和has-a都可以,就用组合。
  • 27
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值