C++中的继承

1.继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
我们中国有句古话:龙生龙,凤生凤,老鼠的儿子会打洞,由此可以看出,儿子继承了父亲的某些行为或特征。
而在面向对象思想中也存在类的继承关系。也就是子类继承了父类的属性和行为。
继承是指一个对象直接使用另一对象的属性和方法。
例如,若把汽车看成一个实体,它可以分成多个子实体,如:卡车、公共汽车等。这些子实体都具有汽车的特性,因此,汽车是它们的"父亲",而这些子实体则是汽车的"孩子"。如果一个类A继承自另一个类B,就把这个A称为"B的子类",而把B称为"A的父类"。继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码。在令子类继承父类的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。

2. 继承定义:
2.1 定义格式
class person
{
public:
	void print()
	{
		cout << _name << endl;
		cout << _age << endl;
	}
protected:
	string _name;
	int _age;
};
class student : public person //公有继承方式(public)
//public这儿为继承方式
{
protected:
	int _stuid;//学号
};

我们把person类称为父类,也叫基类。student称子类,也叫派生类。

2.2 继承关系和访问限定符

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

2.3 继承基类成员访问方式的变化

继承方式publicprotectedprivate
基类public成员派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员派生类的protected成员派生类的protected成员派生类的private成员
基类的private成员在派生类中不可见在派生类中不可见在派生类中不可见

注:
1.基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,最好显示的写出继承方式。

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

在公有继承方式下

  • 派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。
  • 基类对象不能赋值给派生类对象。
  • 基类的指针可以通过强制类型转换赋值给派生类的指针。但必须是基类的指针是指向派生类对象时才是安全的。

对象模型:
在这里插入图片描述
可以看出,student 继承了父类的姓名,性别,年龄,并且重新定义了学号。
由图可知在派生类的上半部分是继承基类的那一部分,所以派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用,直接将上半部分赋值给基类就好。
如果将基类对象赋值给派生类对象,那派生类自己的属性放到哪呢?编译器也不知道,所以就会出错。

4.继承中的作用域

1.在继承体系中,基类和派生类都有独立的作用域。
2.子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问(通过子类对象调用函数或变量),这种情况叫隐藏,可以使用 基类::基类成员显示访问。
3.如果是成员函数的隐藏,只需函数名相同即可,与参数列表,返回值等无关。
4.如果是成员变量同名,与成员变量的类型无关。
5.在实际的继承体系中,最好不要定义同名的函数。

class A
{
public:
  void fun()
  {
  cout << _a <<endl;
  }
private:
	int _a;
};
class B : public A
{
public:
	void fun(int)
	{
		cout << _a << endl;
	}
private:
	int _a;
};

上述代码,成员fun函数和成员变量都构成重写。

5.派生类的默认成员函数

1.派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。换句话说:如果基类具有无参的构造函数时,派生类必须显示定义自己的构造函数,并且必须在初始化列表的位置显示调用基类的构造函数完成派生类对象中基类成员的初始化。
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。即:如果基类没有显示定义自己的拷贝构造函数,派生类拷贝构造函数可写可不写;如果基类显示定义自己的拷贝构造函数,派生类需要在其拷贝构造函数初始化列表位置显示调用基类成员的初始化。
3. 派生类的operator=必须要调用基类的operator=完成基类的复制。但如果涉及到资源管理问题,派生类需将自己的赋值运算符重载显示定义。
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
5. 派生类对象初始化先调用基类构造再调派生类构造。
6. 派生类对象析构清理先调用派生类析构再调基类的析构。

6.继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
举个列子:就像你的父亲有一个特别好的朋友,你把他叫王叔叔,可他的财产却不会让你继承,只能让他的孩子继承。

7.继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
举个列子:就像你的家里有一个传家宝,只会一代代传下来,并且只有一个。

8.实现一个不能被继承的类
class A
{
public:
	static A GetInstance(int a)
	{
		return A();
	}
private:
	A() //构造改为私有
	{}
};
//C++11 中给出关键字final禁止继承
/*
class A final
{};
*/
int main()
{
	A a = A::GetInstance(2);
	return 0;
}

1.如果类被final修饰,那么此类不可以被继承。
2.如果类中只有private的构造方法,那么此类不可以被继承。
其原因在于:
(1)一个类一定会有构造函数,如果不写,那就是默认的无参构造函数,如果写,就只有所写的构造函数。
(2)子类的构造函数一定会调用父类的构造函数,但是如果父类中只有私有的构造方法,那么子类就无法调用父类,就会有问题。

9.菱形继承

<1>单继承:一个类只能有一个父类。

class A
{};
class B : public A
{};
class C: public A
{};

B类和C类都继承自A类,但都只有A一个父类,所以为单继承。
<2>多继承:一个类有多个父类。

class A
{};
class B
{};
class C: public A,public B
{};

C类的父类为B类和A类,所以为多继承。

9.2菱形继承
class B
{
	int _b;
};
class C1 : public B
{
	int _c1;
};
class C2 : public B
{
	int _c2;
};
class D : public C1, public C2
{
	int _d;
};

菱形继承问题:菱形继承有数据冗余和二义性问题。
对象模型:
在这里插入图片描述
由图可知:C1继承自B,所以C1中存在变量_b;C2继承自B,C2中也有变量_b;所以存在数据冗余问题。
当我们创建D的对象后,通过D中的对象调用变量_b时,编译器就不知道调用C1还是C2的_b,因为存在两个_b变量。(解决此问题的方法,可以通过类名::类成员 显示访问),此为二义性问题。

菱形虚拟继承
class B
{
	int _b;
};
class C1 : virtual public B
{
	int _c1;
};
class C2 : virtual public B
{
	int _c2;
};
class D : public C1, public C2
{
	int _d;
};

菱形虚拟继承:上述代码就为菱形虚拟继承。
先看C1的对象模型:
在这里插入图片描述
在构造对象时,将对象的前4个字节中的地址,放在最前面,我们称它为虚基表指针。
虚基表指针指向的空间我们称为偏移量表格,偏移量表格上面存放派生类相对于自己的偏移量,下面存放的是基类相对于派生类的偏移量。
那如何访问呢?
通过对象的前四个字节中的地址,找到偏移量表格,如果访问基类的成员,拿到基类相对于派生类的偏移量,再去访问。如果访问派生类类的成员,拿到派生类相对于派生类的偏移量,再去访问。

看看菱形虚拟继承的对象模型
在这里插入图片描述
由上图可知,将基类的成员放最后,如果要访问基类的成员,可以通过偏移量找到要访问的成员。只存一份,解决了数据冗余问题,只继承了一份,同样也解决了二义性问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值