IE盒子

搜索
查看: 113|回复: 1

第008问:C++中继承概念及其原理

[复制链接]

2

主题

6

帖子

10

积分

新手上路

Rank: 1

积分
10
发表于 2022-12-22 20:09:24 | 显示全部楼层 |阅读模式
继承与派生

在第006问中介绍到C++的三大特性包括封装、继承、多态;

  • 继承是面向对象程序设计中代码复用和软件抽象的重要表现形式
  • 继承允许在类的基础上定义新的类,被继承的类叫基类, 新的类称之为子类/派生类(is-a)
  • 子类/派生类拥有所有基类的特性,并且可以给自身增加特性
  • 子类的定义与基类的定义没有区别,除了增加一个类继承列表,如例1
// 例1
class Base {
public:
  Base(int a) : a_(a) {}
  int GetA() {return a_;}

private:
  int a_;
};

class Derived : public Base {
public:
  Derived(int a, int b) : Base(a), b_(b) {}
  int GetB() {return b_;}

private:
  int b_;
};
继承中类的访问控制


  • 类声明本身就是有访问控制的,包括public、protected、private,具体含义如表1
访问控制publicprotectedprivate
类本身YesYesYes
类对象YesNoNo
子类YesYesNo
protected:主要可以控制类对象对于数据成员的访问,类的实例化对象不可访问,但子类是可以获得访问权;
其他两个好理解不解释

  • 不同继承方式影响子类对于基类成员的访问权限,具体的继承方式和访问权限关系如表2
继承控制publicprotectprivate
父类public成员publicprotectprivate
父类protect成员protectprotectprivate
父类private成员NoNoNo


  • 友元关系不能被继承
  • 可以通过作用域限定符,显式指明访问基类成员;如例2
// 例2
#include <iostream>
class Base1 {
public:
    Base1(int a) : a_(a) {}
    int a_;
};


class Derived : public Base1 {
public:
    Derived(int a, int d) : Base1(a), a_(d) {}
    int a_;
};

int main() {
    Derived tmp(1, 4);
    std::cout << "dericed a: " << tmp.a_ << ", base a: " << tmp.Base1::a_ << std::endl;
    return 0;
}
派生类的构造与析构


  • 默认情况下子类需要定义自己的构造函数,不会继承基类的构造函数
  • 可使用using关键字继承基类构造函数,但只能初始化基类成员,子类新增成员需要自己完成初始化,如例3
// 如例3
clase Base {
public:
  Base(int a) : a_(a) {}
  int a_;
};

class Derived : public Base {
public:
  using Base::Base; // 继承基类构造函数
};

  • 子类构造函数执行顺序为:

  • 基类构造函数
  • 子类初始化列表中的其余项
  • 构造函数体


  • 若基类构造函数有参或无默认参数,需要显示调用;若无参数或者有默认参数可不显示调用基类构造函数
  • 基类的析构函数不能被继承,子类如需要析构函数,可自行定义析构函数;为什么基类的析构函数不能被继承, 因为这样就会被释放两次
  • 子类析构函数中不需要显示调用基类的析构函数,编译器会隐式调用
  • 子类对象析构时,先执行子类析构在执行基类析构
多重继承与虚继承


  • 子类继承自多个基类,多个基类又有公共的基类,那么子类对象中就会存在多份公共基类成员,存在冗余
// 例4
#include <iostream>
class Base1 {
public:
    Base1(int a) : a_(a) {}
    int a_;
};

class Base2 : public Base1 {
public:
    Base2(int a, int b) : Base1(a), b_(b) {}
    int b_;
};

class Base3 : public Base1 {
public:
    Base3(int a, int c) : Base1(a), c_(c) {}
    int c_;
};

class Derived : public Base2, public Base3 {
public:
    Derived(int a, int b, int c, int d) : Base2(a, b), Base3(a, c), d_(d) {}
    int d_;
};

int main() {
    Derived tmp(1, 2, 3, 4);
    std::cout << "a: " << tmp.a_ << ", b: " << tmp.b_ << ", c: " << tmp.c_ << std::endl;
    return 0;
}
output:
error: request for member 'a_' is ambiguous
     std::cout << "a: " << tmp.a_ << ", b: " << tmp.b_ << ", c: " << tmp.c_ << std::endl;
                               ^~
继承示意图如图1



图 1

多重继承下内存分布如图2



图 2

如上如所示,子类对象就保存了两份公共基类的成员,存在冗余。
多继承中的二义性

如例4所示,由于子类存在两份公共基类成员,访问公共基类成员产生二义性,无法通过编译,需要引入虚基类机制消除二义性。

  • 继承公共基类时,加virtual关键字,表示Base1为虚基类;
  • 派生类Derived构造函数时,不仅要对直接基类进行初始化,还要负责虚基类的初始化;
// 例5
#include <iostream>
class Base1 {
public:
    Base1(int a) : a_(a) {}
    int a_;
};

class Base2 : virtual public Base1 {
public:
    Base2(int a, int b) : Base1(a), b_(b) {}
    int b_;
};

class Base3 : virtual public Base1 {
public:
    Base3(int a, int c) : Base1(a), c_(c) {}
    int c_;
};

class Derived : public Base2, public Base3 {
public:
    Derived(int a, int b, int c, int d) : Base1(a), Base2(a, b), Base3(a, c), d_(d) {}
    int d_;
};

int main() {
    Derived tmp(1, 2, 3, 4);
    std::cout << "a: " << tmp.a_ << ", b: " << tmp.b_ << ", c: " << tmp.c_ << std::endl;
    return 0;
}
output:
a: 1, b: 2, c: 3
此时内存模型示意如图



图 3

总结


  • 理解类的访问控制和继承控制
  • 理解类继承过程中构造和析构的顺序
  • 理解类多重继承(菱形继承)中的二义性问题,以及解决方案
参考


  • C++ Inheritance
  • C++之访问控制与继承 - 哆来咪的文章 - 知乎
回复

使用道具 举报

3

主题

8

帖子

16

积分

新手上路

Rank: 1

积分
16
发表于 昨天 15:35 | 显示全部楼层
高手云集 果断围观
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表