【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;
}

上述代码的示意图如下

相关文章
|
1月前
|
编译器 C++ 开发者
【C++】继承
C++中的继承是面向对象编程的核心特性之一,允许派生类继承基类的属性和方法,实现代码复用和类的层次结构。继承有三种类型:公有、私有和受保护继承,每种类型决定了派生类如何访问基类成员。此外,继承还涉及构造函数、析构函数、拷贝构造函数和赋值运算符的调用规则,以及解决多继承带来的二义性和数据冗余问题的虚拟继承。在设计类时,应谨慎选择继承和组合,以降低耦合度并提高代码的可维护性。
33 1
【C++】继承
|
2月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
92 11
|
2月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
67 1
|
2月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
49 1
|
2月前
|
安全 编译器 程序员
C++的忠实粉丝-继承的热情(1)
C++的忠实粉丝-继承的热情(1)
22 0
|
2月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
41 0
|
2月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
44 0
|
3月前
|
C++
C++(二十)继承
本文介绍了C++中的继承特性,包括公有、保护和私有继承,并解释了虚继承的作用。通过示例展示了派生类如何从基类继承属性和方法,并保持自身的独特性。此外,还详细说明了派生类构造函数的语法格式及构造顺序,提供了具体的代码示例帮助理解。
|
3月前
|
C++
c++继承层次结构实践
这篇文章通过多个示例代码,讲解了C++中继承层次结构的实践应用,包括多态、抽象类引用、基类调用派生类函数,以及基类指针引用派生类对象的情况,并提供了相关的参考链接。
|
4月前
|
安全 Java 编译器