c++面向对象特性之继承(2)

c++面向对象特性之继承(2)

继承共分为三种 :
public 公有继承
protected 保护继承
private 私有继承

于此同时,每个类中的成员也具有public,protected,private这三种特性,每种特性下的成员在每种继承方式下都有不同的结果,总结如下:

公有继承:

继承方式本来的属性转换后的属性
publicpublicpublic
publicprotectedprotected
publicprivate无法访问

使用两个类来表示,Person和Worker
Worker公有继承Person类,那么Person类中的public 中的成员函数与变量,在Worker中都可以直接使用,而Person中的protected中的成员函数与变量,则相当于转移到了Worker中的Protected中,此时,Protected中的成员函数和成员变量,只能由类中的成员函数来调用。而private中的,则直接被隐藏了起来,继承后的Worker无法调用其中的变量和函数。

保护继承:

继承方式本来的属性转换后的属性
protectedpublicprotected
protectedprotectedprotected
protectedprivate无法访问

保护继承,则是在公有继承上更进一步,将public和protected全部转移到派生类中的protected中去,此时,也只有成员函数可以调用他们,而private依然不能使用。
私有继承:

继承方式本来的属性转换后的属性
privatepublicprivate
privateprotectedprivate
privateprivate无法访问

私有继承,则是和protected类似,将变量全部转移到派生类中的private中,而本身为private的则无法调用。

隐藏

当你定义了一个类A,类中包含一个成员函数void ABC();然后类A被类B继承,但恰巧类B中也有一个自己独有的成员函数void ABC();这样就有可能引起不必要的麻烦,于是需要隐藏的这个功能,名字为隐藏,但是代码并没有被隐藏,这两个ABC()函数都存在,只是在调用时需要注意一些语句的使用,下面用代码来实现:

继承方式 :class worker:public Person
m_strName 是 Person类中的protected变量
//Person::play()
void Person::play()
{
	m_strName = "james";
	cout<<m_strName<<endl;
}
//worker.play()
void worker::play()
{
	m_strName = "jim";
	cout<< m_strName<<endl;
}
	

结果如下:
在这里插入图片描述
此时两个play()函数都是没有变量的,如果修改worker 的play()函数,将其修改为void worker::play(int x),那么此时两个play()函数就不同了,还会隐藏吗?
我们将worker的play()修改后,取消Person::语句,如下:

	worker workerman;
	workerman.play(7);
	workerman.play();

计算机会报错
在这里插入图片描述
第二行不接受0个参数,说明此时计算机依然会调用worker中的play()函数,所以无论参数是否相同,都需要在被隐藏的函数前加上类名::,才可以调用。派生类只能调用自己的同名成员函数play(),不能调用继承来的同名函数play().也就是说,他们之间无法形成重载,只能形成隐藏。
变量也同理,当派生类中和基类中都有相同的类型,相同名称的变量时,在子类中直接调用只会修改子类中的该变量,而想修改父类中的该变量,依然要在赋值语句中加上类名::变量名,并且此时父类中的该变量也确实被改变,下次直接在父类中调用时,也是更改过的值。

Is-a

定义类的时候,分为基类和派生类,那么在c++中,还有一种基类和派生类之间的关系,就是Is-a,我们用慕课网中的一张图来理解。
慕课网c++远征之继承篇 作者:jamesyuan
图中最上方是定义的一个Person类,代表人类,而下方左边是一个Worker类,右下方是一个Soldier类,可以看出Worker和Soldier都是Person类,这是名义上的从属关系,也是继承的实现,那么Is-a体现在哪里呢?是这样的,我们可以把Soldier和Worker叫为士兵,工人,也可以把他们叫做人类,但反过来就不行,这在c++代码中可以得到体现

int main(void)
{
	Soldier s1;//实例化一个士兵s1
	Person p1=s1;//实例化一个人p1,并且他初始化的值是士兵s1,我们可以说一个士兵同时也是人类,所以这样是合法的
	Person *p2 = &s1;//同样的表示方法有不同的实现,让指针p2指向士兵s1的首地址,也可以实现让一个士兵给一个人类赋值
	s1 = p1; //非法,你实例化一个人出来,但你不能说他就是士兵
	Soldier *s2 = &p1;//也是错误的
	return 0}

所以,Is-a体现在派生类可以给基类用来初始化,基类的指针也可以指向派生类的首地址。但反之则会语法错误。

使用过程

void fun1(Person *p)
{
	...
}
void fun2(Person &p)
{
	...
}
int main()
{
	void fun1(&p1);//传入p1的首地址
	void fun1(&s2);//传入s2的首地址
	void fun2(p1);//p1的引用
	void fun2(s2);//s2的引用
}

两者之间的存储结构也存在以下知识点:
在这里插入图片描述
在这里插入图片描述
当用子类赋值给父类时,父类对象只可以调用他原本有的变量,而子类特有的成员变量使用父类调用则会丢失,使用父类指针指向子类也是,只能指向其本身所拥有的变量。

虚析构函数

我们使用基类的指针去指向一个派生类的对象时,记过一系列操作后,需要delete释放指针指向的空间,但我们知道is-a关系下,基类指针是无法访问到某些派生类的变量的,这就会导致不调用派生类的析构函数,只调用基类的,就有可能造成内存泄漏。

解决方法便是使用virtual关键字,加在基类析构函数前,如之前的Person类和Soldier类,virtual ~Person,virtual会自动继承给Soldier类,即此时的Soldier的析构函数也是virtual ~Soldier,这样就会调用两者的析构函数,所以,使用基类指针去new一个派生类对象,需要释放内存时,就需要用到virtual关键字,这就是虚析构函数。

多继承与多重继承

多继承:

class Worker
{};
class Farmer
{};
class MigrantWorker:public Worker,public Farmer
{};

注意,多继承时如果不写继承模式,则默认为private。

多重继承:

class Person
{};
class Soldier:public Person
{};
class Infantryman:public Solider
{};

要牢记之前继承时的三种继承模式 public,protected,private

虚继承

关键字 virtual
用到虚继承就得了解一下菱形继承(图片来源 慕课网c++远征之继承篇)
在这里插入图片描述
可以看到 A->B->D,A->C->D,是两个多重继承,而B,C同时继承给D又是多继承,这就是菱形继承,存在的问题是,A中将含有两个完全一样的数据,这对于内存来说是一种冗余,所以要消除这种冗余,就需要使用虚继承来实现了,而实现的关键字就是virtual

宏定义

使用宏定义来避免重定义的方法

#ifndef (if not defined) 文件名要大写 如Person.h 要写成PERSON_H
#define 上面没定义的
。。。
#endif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值