C++继承

目录

概念及定义

继承的概念

继承的使用

继承方式

隐藏/重定义

调用被隐藏的函数

 派生类的基本成员函数

构造函数

拷贝构造

赋值重载

析构函数

特殊成员的继承

友元继承

静态成员的继承

多继承

概念

菱形继承

虚拟继承


概念及定义

继承是面向对象编程的三大特性之一,所以继承的概念和使用细节是C++学好必不可少的一步。

继承的概念

继承概念:继承实际上就是复用,在一个类的基础上增加一些功能,形成新的类,这个类称为派生类(子类),原有类称为基类(父类)。

继承的使用

继承的实现包含三个部分:派生类,继承方式,基类。

派生类就是新形成的类,基类是派生类的来源。

class Person
{
public:

protected:
	char name[20];  //姓名
	char tele[12];  //电话
};

    //派生类  继承方式  基类
class Student :public   Preson
{

public:

protected:
	int major;  //专业
};

可以看到以上是一个简单的继承,因为Student也是Person,可以将Person类继承给Student,让Student也具有Person的属性。


继承方式

关于继承方式一共有三种,但是当继承方式与基类的访问限定符组合的时候就有了9种方式。但是总结下来也就两点要求。

以下表格:横向代表继承方式,纵向代表基类的访问权限。表格内是作为派生类成员的访问权限。

基类/继承方式publicprotectedprivate
publicpublicprotectedprivate
protectedprotectedprotectedprivate
private派生类不可见派生类不可见派生类不可见

以上表格总结:1)基类中访权限是privated的成员,派生类不可见;

2)非privated成员,派生类中访问限定符是min(基类成员权限,继承方式),即基类成员权限和继承方式中权限小的那一个。privated<protected<public。

先解释什么是派生类不可见:对于基类中private成员,派生类中是继承的有,但是派生类中不能访问,类外更不能访问,相当于有但是不能使用。

基类的private在派生类的内外都不可见,那如果想要让派生类内部可以访问,但是派生类外部不能访问怎么办???此处引出了新的访问限定符:protected。

protected在非继承的类中的使用和privated是相同的,但是对于继承类来说就不一样了;protected继承后派生类内部可以访问,但是派生类外不能访问。

当没有写继承方式时,class类默认继承时privated,而struct默认是public。

实际上一般在使用继承的时候,基本上都会用public的继承方式,基类一般也不会只用privated成员。


隐藏/重定义

在继承中,当子类有与父类同名的函数时,就会出现对父类函数的隐藏/重定义。只需要函数名相同就可以构成隐藏。

思考:当派生类和基类的函数名,函数参数,函数返回值都相同的时候,会不会报错或者换个说法隐藏是不是函数重载???既不会报错也不会出现函数重载,因为派生类和基类位于两个作用域中。

调用被隐藏的函数

函数调用的顺序是:局部>派生类>基类>全局。

class A
{
public:
	void test_01(int a=1)
	{
		cout << a;
	}
	void test_02(int b=2)
	{
		cout << b;
	}
};

class B :public A
{
public:
	void test_02(int c = 3)
	{
		cout << c;
	}
};

int main()
{
	B _b;
	_b.test_01();     //派生类中没有函数,去基类中找,调用基类中的
	_b.test_02();     //派生类中有,基类的被隐藏了,调派生类的
	_b.A::test_02();  //指定调用基类的

	return 0;
}

可以看到以上代码:在基类的函数被隐藏后,默认调用派生类的函数,只有显示访问的时候才会调用基类被隐藏的函数。

在实际使用继承的时候最好不要定义同名函数。


 派生类的基本成员函数

派生类也是类,也有基本成员函数:构造函数;拷贝构造函数;析构函数;赋值重载函数;那派生类在使用这些函数的时候是如何处理基类部分的呢???

构造函数

在派生类的初始化列表中,如果没有显示调用基类的构造函数就会默认调用基类的无参构造来初始化基类成员。当要通过传参进行基类成员的构造的时候,就需要显示调用基类的构造函数

class A
{
public:
	A()
		:_a(1)
	{}

	A(const int& a)
		:_a(a)
	{}
	int _a;
};

class B :public A
{
public:
	B(const int& b,const int& a)
		:_b(b)
		,A(a)   //显示调用A的构造函数
	{}
	int _b;
};

以上代码,对B进行构造的时候,显示调用A的构造函数并提供参数。当然A的构造也可以使用缺省值,此处仅仅为了演示,代码无任何意义。

需要注意的时候:初始化列表的初始化顺序是依靠成员声明的顺序进行的,而基类成员声明是要比派生类早的,所以会先去初始化基类部分,再去初始化派生类部分。


拷贝构造

派生类的拷贝构造必须显示调用基类的拷贝构造来完成基类部分的拷贝。

在继承中,支持将派生类的基类部分赋值给其他父类,称为赋值兼容转化(切割)。

此处拷贝构造,在将派生类的基类部分进行拷贝是就使用了赋值兼容转化。

class A
{
public:
	A(const A& aa)
	{
		_a = aa._a;
	}
	int _a;
};

class B :public A
{
public:
	B(const B& bb)
		:_b(bb._b)
		,A(bb)   //赋值兼容转化,向上转化
	{}
	int _b;
};

赋值重载

在派生类的赋值重载时,需要显示调用基类的赋值重载来实现基类部分的赋值。

class A
{
public:
	A& operator=(const A& aa)
	{
		_a = aa._a;
		return *this;
	}
	int _a;
};

class B :public A
{
public:
	B& operator=(const B& bb)
	{
		_b = bb._b;
		A::operator=(bb);  //显示调用基类的赋值重载函数
		return *this;
	}
	int _b;
};

析构函数

编译器对于派生类的析构时,会默认调用基类析构函数来对基类部分进行处理,所以不需要显示调用基类的析构函数,只对派生类成员进行处理就行了。

在对派生类进行构造时,先对基类部分进行构造再对派生类部分进行构造;

在对派生列进行析构时,先对派生类部分进行析构,再析构基类部分,因为在派生类的析构函数中可能还要访问基类成员。


特殊成员的继承

友元继承

在C++中友元函数是不支持继承的,父类的友元不是子类的友元,好比父亲的朋友不是我朋友。

除非在子类中显示声明其也是子类的友元。

静态成员的继承

在C++中,基类定义了一个static修饰静态成员,经继承后派生类中还是static静态成员,只不过是子类部分和派生类部分共用这一个静态成员。

class A1
{
public:
	static int aa;
	void test_01()
	{
		++aa;
		cout << aa;
	}
	void test_02()
	{
		++aa;
		cout << aa;
	}
};
int A1::aa = 1;

class B1 :public A1
{
public:
	void test_02(int c = 3)
	{
		++aa;
		cout << aa;
	}
};

思考:以上代码的输出结果是什么???答案:234。


多继承

概念

C++中支持一个派生类继承多个基类,即满足多继承,现实中也确实有许多情况需要多继承,比如一人他可能会有多种身份,这是就需要多继承这一概念了。

class Person
{
public:
	//....
};

class monitor   //班长
{
	//....
};

class Student :public Person ,public monitor
{
public:
	//....
};

多继承这么好用,为什么有些语言不支持呢,比如Java???因为多继承就会导致菱形继承出现,而对于菱形继承的处理是是很麻烦的。


菱形继承

当一个学生继承了两个身份:男朋友和班长,而这两个身份又继承了:人。

以下是对菱形继承代码的实现:

class Person
{
public:
	int _id;   //身份证号
};

class monitor:public Person   //班长
{
public:
	size_t work;   //工作量
};

class boyfriend :public Person
{
public:
	size_t height;  //身高
};

class Student :public boyfriend ,public monitor
{
public:
	//....
};

根据图形可以直观的看出,菱形继承会到导致数据冗余(只需要一个_id变量,这个类却定义了两个)和二义性(取哪一个_id是不明确的)。

在student这个派生类中一共有两个_id变量,所以在对student对象中_id进行访问的时候要如何访问呢???必须显示表明调用哪一个基类的_id变量,如以下代码。

Student s;
s.monitor::Person::_id = 10;
s.boyfriend::Person::_id = 12;

一个student对象有两个_id变量本来就浪费了空间,如果这两个身份证号不一样就会导致一些不可预料的结果。

所以为了解决菱形继承的问题引入了:虚拟继承。


虚拟继承

虚拟继承写法:在继承方式前面加上virtual关键字。

class Person
{
public:
	int _id;   //身份证号
};

class monitor:virtual public Person   //班长
{
public:
	size_t work;   //工作量
};

class boyfriend :virtual public Person
{
public:
	size_t height;  //身高
};

class Student :public boyfriend ,public monitor
{
public:
	//....
};

对于虚拟继承的派生类,其基类在空间中储存的数据与普通继承是不同的。

在调试信息中,其又增加了一个Person的类,这就说明其与普通继承是不同的,但是其结构是什么呢???

可以看到虚继承的思想方法是:将重复的基类Person放在一个位置,让基类(monitor,boyfriend)不再存放存放重复的基类Person,而是存放一个地址记录偏移量来找到重复的基类Person。关于虚函数表在多态中会详细讲解。

将重复基类换成地址,当公共积累比较大的时候可以节省菱形继承的空间。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半桔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值