【C++】详解C++的继承

简介: 【C++】详解C++的继承

概念

继承类似于函数的复用,它是类设计层次上的复用

减少代码量的同时,还能在原有类的基础上扩展新的功能

被继承的叫基类父类,得到继承的类叫派生类子类

子类可以继承父类的数据方法

因为类的方法都存放再代码段,所以继承的概念在内存层面是数据之间的继承

定义

语法如下

class a
{
//......
};
 
class b : public a
{
//......
}

public是继承方式,继承方式有如下几种

共有继承:public

保护继承:protected

私有继承:private

继承方式和访问限定符的关系如下

类成员 / 继承方式

public 继承

protected 继承

private 继承

父类的 public 成员

子类是public成员

子类是protected

成员

子类是private

成员

父类的protected

成员

子类是protected

成员

子类是protected

成员

子类是private

成员

父类的private

在子类中不可见

在子类中不可见

在子类中不可见

不可见是指父类的私有成员还是被继承到了子类对象中,但是语法上限制子类对象不管在类里面还是类外面都不能去访问它

如果父类的成员 想被子类访问,不想被其他类访问,父类的成员就 加上protected(保护)限定符

继承方式和访问限定符取权限小的,权限关系如下:

public(共有) > protected(保护) > private(私有)

使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public

赋值

子类对象可以给父类对象赋值,父类对象不能给子类对象赋值

子类对象可以赋值给父类的对象 / 父类的指针 / 父类的引用

子类是继承父类的数据,父类有的子类一定有,子类有的父类不一定有

形象示意图

这种赋值更形象化的说法叫——切片

作用域

在继承体系中父类子类都有独立的作用域

子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏

也叫重定义在子类成员函数中,可以使用 基类::基类成员 显示访问

需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏

如下是测试代码,

有兴趣的可以粘到编译器上运行一下

// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected :
 string _name = "小李子"; // 姓名
 int _num = 111;   // 身份证号
};
class Student : public Person
{
public:
 void Print()
 {
 cout<<" 姓名:"<<_name<< endl;
 cout<<" 身份证号:"<<Person::_num<< endl;
 cout<<" 学号:"<<_num<<endl;
 }
protected:
 int _num = 999; // 学号
};
void Test()
{
 Student s1;
 s1.Print();
};
 
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
 void fun()
 {
 cout << "func()" << endl;
 }
};
class B : public A
{
public:
 void fun(int i)
 {
 A::fun();
 cout << "func(int i)->" <<i<<endl;
 }
};
void Test()
{
 B b;
 b.fun(10);
};

子类的默认成员函数

关于常用的默认成员函数可参考如下两篇文章


子类不能显式的定义只能调用父类的构造,析构等默认成员函数

构造函数:如果父类不提供默认构造,需要在子类的初始化列表显式调用

拷贝构造:子类必须显式调用符类的拷贝构造

operator=:子类必须显式调用父类的operator=

析构:子类不能显式的调用父类的析构函数,不然会报错

子类对象初始化先调用父类构造再调子类构造

子类对象析构清理先调用子类析构再调父类的析构

继承与友元

父类与其他类或函数的友元关系不能传递给子类

继承与静态成员

父类定义了static静态成员,则 整个继承体系 里面只有 一个 这样的成员。无论派生出多少个子

类,都只有一个静态成员的实例

菱形继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

多继承的语法:

class  父类类名 :继承方式 子类类名, 继承方式 子类类名...... 中间用逗号分隔

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

菱形继承是把同一个父类的数据继承了多次,这会造成数据冗余和二义性的问题

菱形虚拟继承

为了解决菱形继承的问题,C++用菱形虚拟继承来解决数据冗余和二义性的问题

语法:

class 父类 : virtual public 子类

virtual是关键字,说明该继承是虚拟继承

虚拟继承的位置(小编把情况搞复杂让大家理解清楚):

红线的继承需要虚拟继承,下面是虚拟继承可以出现的位置

总结:

如果一个父类要被继承两次以上,一定要用虚拟继承

虚拟继承可以传递

多次虚拟继承不影响最后的结果

原理

虚拟继承是怎么解决数据冗余和二义性的呢

问题:

子类可以继承父类的数据和方法,由于类的方法都存放在代码段,在内存层面是数据之间的继承。菱形继承导致的问题是因为父类的数据被继承了多次,在内存中一份数据就被储存了多次,同名的变量会造成二义性。

解决:

重复的父类会被子类存放在最下面,父类把数据继承给子类地同时,还会再让子类存一个指针,指针指向一张表。这个指针叫虚基表指针,这个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面本应该重复的父类。

下面是内存形象化示意图

虚拟继承就是通过上述方法让多个父类和一个子类管理一份数据而不是同名的多份数据。

小编用如下代码调试,带大家感受一下

class A
{
public:
  int _a;
};
// class B : public A
class B : virtual public A
{
public:
  int _b;
};
// class C : public A
class C : virtual public A
{
public:
  int _c;
};
class D : public B, public C
{
public:
  int _d;
};
 
int main()
{
  D d;
  d.B::_a = 1;
  d.C::_a = 2;
  d._b = 3;
  d._c = 4;
  d._d = 5;
  return 0;
}

上述代码的示意图如下

相关文章
|
2月前
|
C++
C++中的封装、继承与多态:深入理解与应用
C++中的封装、继承与多态:深入理解与应用
32 1
|
20天前
|
编译器 数据安全/隐私保护 C++
c++primer plus 6 读书笔记 第十三章 类继承
c++primer plus 6 读书笔记 第十三章 类继承
|
7天前
|
C++
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
10 0
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
|
7天前
|
C++
【C++】学习笔记——继承_2
【C++】学习笔记——继承_2
12 1
|
9天前
|
编译器 C++
C++中的继承
C++中的继承
17 1
|
12天前
|
C++
C++一分钟之-继承与多态概念
【6月更文挑战第21天】**C++的继承与多态概述:** - 继承允许类从基类复用代码,增强代码结构和重用性。 - 多态通过虚函数实现,使不同类对象能以同一类型处理。 - 关键点包括访问权限、构造/析构、菱形问题、虚函数与动态绑定。 - 示例代码展示如何创建派生类和调用虚函数。 - 注意构造函数初始化、空指针检查和避免切片问题。 - 应用这些概念能提升程序设计和维护效率。
21 2
|
14天前
|
C++
C++:继承性
C++:继承性
17 2
|
14天前
|
编译器 C++
C++:继承性_程序
C++:继承性_程序
11 1
|
18天前
|
存储 编译器 程序员
【C++高阶】C++继承学习手册:全面解析继承的各个方面
【C++高阶】C++继承学习手册:全面解析继承的各个方面
18 1
|
6天前
|
程序员 编译器 C++
【c++】继承
【c++】继承
7 0