【C/C++】详解C++三大特性之一:继承

一、继承的概念和定义

1.1 继承的概念:

  1. 实现代码复用,实现多态的必要条件
  2. 保持原有类特性的基础上进行扩展,增加新功能
  3. 呈现了面向对象设计的层次结构

继承之后:成员函数和成员变量都被继承了,sizeof(子类) = sizeof(父类) + 子类新增

1.2 继承权限

在这里插入图片描述
小结:

  1. 基类 private 修饰的成员在子类中是不可见的,不可见:尽管继承到了子类中,但是语法规定不能访问
  2. 基类中 protect 修饰的成员,在子类中可以访问,但是类外不能访问,保护成员限定符是因继承才出现的
  3. 这个继承权限是针对类外的访问,派生类中还是可以访问基类中public 和 protected 成员的

C++中 calss 和 struct 的区别:
1.默认访问权限
2.默认继承权限
3.定义一个模板 template< class T >

二、继承规则

2.1 继承中的对象模型

class A
{
public:
	int a = 1;
};

class B : public A
{
public:
	int b = 2;
};

对象模型(子类对象的内存分布):
在这里插入图片描述

赋值兼容规则:

  1. 基类对象可以用子类对象赋值
  2. 基类的指针或者引用,可以指向子类对象
  3. 子类指针可以指向,通过强转的基类对象,但使用时可能造成越界,因为指针是按子类解析的

2.2 继承中的作用域

前题:

  1. 在继承体系中基类和派生类的作用域是独立的
  2. 基类和派生中同名的成员,会发生同名隐藏,也就是基类的成员不会被访问到。注意:这里并不是函数重载,因为作用域都不同
  3. 可以通过 基类:基类成员 或者 子类对象.基类::基类成员 访问基类对象

2.3 派生类的默认成员函数

派生的构造,析构,拷贝构造函数,赋值重载的规则:

  1. 基类没有显示定义构造函数 或者说 重载函数是无参或全缺省的:则派生类也不用显示定义构造函数,但编译器会生成一份默认的构造
  2. 基类的构造带有参数(不是全缺省):则派生的构造必须显示定义出来,并且要在初始化列表的位置调用基类的构造函数
  3. 派生的拷贝构造:必须调用基类的拷贝构造
  4. 派生的赋值运算符重载:必须调用基类的赋值重载
  5. 派生的析构:在调用完派生的析构函数之后,编译器会自动调用基类的析构函数
class A
{
public:
	int a_;
	A(int a)
		:a_(a)
	{}
	A(const A& temp)
		:a_(temp.a_)
	{}
	A& operator=(const A& temp)
	{
		if (this != &temp)
		{
			a_ = temp.a_;
		}
		return *this;
	}
	~A()
	{
		cout << "A析构" << endl;
	}
};

class B : public A
{
public:
	int b_;
	// 派生的构造必须显示定义,并且在初始化列表中调用基类的构造
	B(int a, int b)
		:A(a)
		,b_(b)
	{}
	// 拷贝构造必须在初始化列表调用基类拷贝构造
	B(const B& temp)
		:A(temp)
		,b_(temp.b_)
	{}
	// 赋值运算符重载
	B& operator=(const B& temp)
	{
		if (this != &temp)
		{
			A::operator=(temp);
			b_ = temp.b_;
		}
		return *this;
	}
	~B()
	{
		cout << "B析构" << endl;
	}
};

其他性质:

  1. 友元特性不能被继承:基类的友元函数无法调用派生类的成员
  2. static 修饰的成员在基类和派生都表示同一地址,也就是说静态成员在整个继承体系中只有一份

三、多继承和菱形继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承
在这里插入图片描述

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
在这里插入图片描述

菱形继承:菱形继承是多继承的一种特殊情况

3.1 多继承对象模型

在这里插入图片描述

class A
{
public:
	int a_ = 1;
};
class B
{
public:
	int b_ = 2;
};
class C :public A, public B
{
public:
	int c_ = 3;
};

多继承规则:

  1. 多继承下,每个基类名前都要加权限,否则编译器默认给private 权限
  2. 继承的顺序决定了,不同基类的成员在派生中的位置,也就是对象模型中的位置

3.2 菱形继承

在这里插入图片描述
这样有两个问题:

  1. 数据冗余,基类A中的成员有两份
  2. 二义性,派生类C的对象调用 a 时会产生二义性,例 c.a_ = 10;过不了编译

虚拟继承可以解决菱形继承的二义性和数据冗余的问题,但需要注意的是,虚拟继承不要在其他的地方使用

虚拟继承:
在这里插入图片描述
汇编中的显示:
在这里插入图片描述
这里是通过了 B1 和 B2 的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量,通过偏移量可以找到下面的A。

class A
{
public:
	A()
		:a_(1)
	{}
	int a_;
};
class B1 : virtual public A
{
public:
	B1()
		:b1_(2)
	{}
	int b1_;
};
class B2 : virtual public A
{
public:
	B2()
		:b2_(3)
	{}
	int b2_;
};
class C : public B1, public B2
{
public:
	C()
		:c_(1)
	{}
	int c_;
};

虚拟继承下的赋值方式:

  1. 先给基类成员赋值:根据虚基表中相对于子类对象起始位置的偏移量找到基类成员地址,并赋值
  2. 给B1类成员赋值,根据继承的顺序决定赋值顺序
  3. 给B2类成员赋值
  4. 最后给基类成员赋值

注意:编译器会给派生类C生成一份构造函数,如果用户定义出了构造函数,则会对构造函数进行改造,这样做的目的是:给前四个字节填充数据

四、继承和组合

  • public 继承是一种 is-a 的关系,也就是每个派生对象都是基类对象
  • 组合是一种 has-a 的关系,假设B组合了A,则每个对象B中都有一个对象A

继承被称为:白箱复用,也就是基类内部细节对子类可见,在一定程度上破坏了封装性,并且基类和派生的耦合度很高
对象组合被称为:黑箱复用,优先使用对象组合有助于类的封装,并且耦合度低,代码维护性好

面试题:
什么是菱形继承?菱形继承存在什么问题?
什么是菱形虚拟继承?如何解决数据冗余和二义性?
继承和组合的区别?什么时候用继承?什么时候用组合?

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值