你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

C++——继承与派生

[复制链接]
gaosmile 发布时间:2020-9-7 14:16

继承和派生的概念

01派生


通过特殊化已有的类来建立新类的过程,叫做“类的派生”, 原有的类叫做”基类”,新建立的类叫做“派生类”。


02继承


类的继承是指派生类继承基类的数据成员和成员函数。继承用来表示类属关系,不能将继承理解为构成关系。


03继承派生的作用

  • 增加新的成员(数据成员和成员函数)

  • 重新定义已有的成员函数

  • 改变基类成员的访问权限


单一继承

01一般形式

   代码格式:

  1. class 派生类名: 访问控制 基类名 {
  2.         private: 成员声明列表
  3.         protected: 成员声明列表
  4.         public: 成员声明列表
  5. }
复制代码

“冒号”表示新类是哪个基类的派生类;“访问控制”指继承方式。
三个方式:public、protected、private

02派生类的构造函数和析构函数

  1. // 基类
  2. class Point {
  3.     int x;
  4.     int y;
  5.    
  6.     public:
  7.     Point(int a, int b) {
  8.         x = a;
  9.         y = b;
  10.         cout << "init Point" << endl;
  11.     }
  12.     void showPoint() {
  13.         cout << "x = " << x << ", y = " << y << endl;
  14.     }
  15.     ~Point() {
  16.         cout << "delete Point" << endl;
  17.     }
  18. };

  19. // 派生类
  20. class Rect: public Point {
  21.     int w;
  22.     int h;
  23.    
  24.     public:
  25.     // 调用基类的构造函数对基类成员进行初始化
  26.     Rect(int a, int b, int c, int d):Point(a, b) {
  27.         w = c;
  28.         h = d;
  29.         cout << "init Rect" << endl;
  30.     }
  31.     void showRect() {
  32.         cout << "w = " << w << ", h = " << h << endl;
  33.     }
  34.     ~Rect() {
  35.         cout << "delete Rect" << endl;
  36.     }
  37. };

  38. int main() {
  39.     Rect r(3, 4, 5, 6);
  40.     r.showPoint();
  41.     r.showRect();
  42.    
  43.     /** 输出结果
  44.      init Point // 当定义一个派生类的对象时, 首先调用基类的构造函数, 完成对基类成员的初始化
  45.      init Rect // 然后执行派生类的构造函数, 完成对派生类成员的初始化
  46.      x = 3, y = 4 // 调用基类成员函数showPoint();
  47.      w = 5, h = 6 // 调用派生类成员函数showRect();
  48.      delete Rect // 构造函数的执行顺序和构造函数的执行顺序相反, 首先调用派生类的析构函数
  49.      delete Point // 其次调用基类的析构函数
  50.      */
  51. }
复制代码

03类的保护成员

如果希望Rect中的showRect()函数可以一次显示x、y、w、h。我们直接修改showRect()函数是不行的。

  1. void showRect() {
  2.   cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
  3. }
复制代码

报错 error: 'x' is a private member of‘Point' 'y' is a private member of‘Point'  

x, y为Point类的私有成员,公有派生时,在Rect类中是不可访问的。

我们还需要将基类Point中的两个成员声明为protected的属性。

像这样:

  1. // 基类
  2. class Point {
  3.     // 公有数据成员
  4.     protected:
  5.     int x;
  6.     int y;
  7.    
  8.     public:
  9.     Point(int a, int b) {
  10.         x = a;
  11.         y = b;
  12.         cout << "init Point" << endl;
  13.     }
  14.     void showPoint() {
  15.         cout << "x = " << x << ", y = " << y << endl;
  16.     }
  17. };

  18. // 派生类
  19. class Rect: public Point {
  20.     int w;
  21.     int h;
  22.    
  23.     public:
  24.     // 调用基类的构造函数对基类成员进行初始化
  25.     Rect(int a, int b, int c, int d):Point(a, b) {
  26.         w = c;
  27.         h = d;
  28.         cout << "init Rect" << endl;
  29.     }
  30.    
  31.     /** 公有派生, Point类中的受保护数据成员, 在Rect类中也是受保护的, 所以可以访问  // 而通过公有继承的基类私有的成员, 在派生类中是不可被访问的    void showRect() {
  32.         cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
  33.     }*/
  34. };

  35. int main() {
  36.     Rect r(3, 4, 5, 6);
  37.     r.showPoint();
  38.     r.showRect();
  39. }
复制代码

04访问权限和赋值兼容规则

在根类中,对于成员的访问级别有三种:public、protected、private

在派生类中,对于成员的访问级别有四种:public(公有)、protected(受保护)、private(私有)、inaccessible(不可访问)

(1)公有派生和赋值兼容规则

公有派生

基类成员的访问权限在派生类中基本保持不变。

  • 基类的公有成员在派生类中仍然是公有的

  • 基类的保护成员在派生类中仍然是受保护的

  • 基类的不可访问的成员在派生类中仍然是不可访问的

  • 基类的私有成员在派生类中变成了不可访问的


总结:在公有派生的情况下,通过派生类自己的成员函数可以访问继承过来的公有和保护成员, 但是不能访问继承来的私有成员, 因为继承过程的私有成员,变成了第四个级别,不可访问的。

赋值兼容规则:

在公有派生的情况下, 一个派生类的对象可以作为基类的对象来使用的情况。

像这样:

  1. // 基类
  2. class Point {
  3.     // 这里声明成员属性为受保护的
  4.     protected:
  5.     int x;
  6.     int y;
  7.    
  8.     public:
  9.     Point(int a, int b) {
  10.         x = a;
  11.         y = b;
  12.     }
  13.    
  14.     void show() {
  15.         cout << "x = " << x << ", y = " << y << endl;
  16.     }
  17. };

  18. // 派生类
  19. class Rect: public Point {
  20.     int w;
  21.     int h;
  22.    
  23.     public:
  24.     // 调用基类的构造函数对基类成员进行初始化
  25.     Rect(int a, int b, int c, int d):Point(a, b) {
  26.         w = c;
  27.         h = d;
  28.     }
  29.    
  30.     void show() {
  31.         cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
  32.     }
  33. };

  34. int main() {
  35.     Point a(1, 2);
  36.     Rect b(3, 4, 5, 6);
  37.     a.show();
  38.     b.show();
  39.    
  40.     Point & pa = b; // 派生类对象初始化基类的引用
  41.     pa.show(); // 实际调用基类的show()函数
  42.    
  43.     Point * p = &b; // 派生类对象的地址赋值给指向基类的指针
  44.     p -> show(); // 实际也是调用基类的show()函数
  45.    
  46.     Rect * pb = &b; // 派生类指针
  47.     pb -> show(); // 调用派生类的show()函数
  48.    
  49.     a = b; // 派生类对象的属性值, 更新基类对象的属性值
  50.     a.show(); // 调用基类的show()函数
  51.     /**
  52.      x = 1, y = 2
  53.      x = 3, y = 4, w = 5, h = 6
  54.      x = 3, y = 4
  55.      x = 3, y = 4
  56.      x = 3, y = 4, w = 5, h = 6
  57.      x = 3, y = 4
  58.      */
  59. }
复制代码

(2)“isa”和”has-a“的区别

继承和派生 isa

比如一个Person类,派生出一个Student类,我们可以说Student就是Person,也就是 Student isa Person,而反过来则不行。

一个类用另一个类的对象作为自己的数据成员或者成员函数的参数 has-a。

像这样:

  1. // 地址类
  2. class Address {};
  3. class PhoneNumber {};

  4. // 职工类
  5. class Worker {
  6.     String name;
  7.     Address address;
  8.     PhoneNumber voiceNumber;
  9. };
复制代码

表示一个Worker对象有一个名字,一个地址,一个电话号码,has-a的关系,包含的关系。

(3)私有派生

通过私有派生,基类的私有和不可访问成员在派生类中是不可访问的,而公有和保护成员这里就成了派生类的私有成员。

  1. // 基类
  2. class Point {
  3.     int x;
  4.     int y;
  5.    
  6.     public:
  7.     Point(int a, int b) {
  8.         x = a;
  9.         y = b;
  10.     }

  11.     void show() {
  12.         cout << "x = " << x << ", y = " << y << endl;
  13.     }
  14. };

  15. // 派生类
  16. class Rect: private Point {
  17.     int w;
  18.     int h;
  19.    
  20.     public:
  21.     Rect(int a, int b, int c, int d):Point(a, b) {
  22.         w = c;
  23.         h = d;
  24.     }
  25.    
  26.     void show() {
  27.         Point::show(); // 通过私有继承, Point类中的公有成员show(), 在Rect中为私有
  28.         cout << "w = " << w << ", h = " << h << endl;
  29.     }
  30. };

  31. class Test: public Rect {
  32.    
  33.     public:
  34.     Test(int a, int b, int c, int d):Rect(a, b, c, d) {
  35.         
  36.     }
  37.     void show() {
  38.         Rect::show();
  39.         //Point::show();
  40.         /** error: 'Point' is a private member of ‘Point’
  41.          标明: 不可访问基类Point中的成员
  42.          Rect类私有继承自Point类, 所以Point中的私有成员x, 私有成员y, 在Rect类中为不可访问: Point类中公有成员show(), 在Rect中变私有
  43.          Test类公有继承自Rect类, 所以在Rect中成员x, 成员y, 仍然是不可访问, Rect::show()还是public, 但是Point::show()不可访问 */
  44.     }
  45. };
复制代码

因为私有派生不利于进一步派生, 因而实际中私有派生用得并不多。

(4)保护派生保护派生使原来的权限都降一级使用

即private变为不可访问,protected变为private,public变为protected。

限制了数据成员和成员函数的访问权限,因此在实际中保护派生用得也不多。

比如:我们在上个例子中,Rect类保护派生于Point,则在Test类中Point::show();就可以使用啦!

多重继承

01一个类从多个基类派生

代码格式:

  1. class 派生类名: 访问控制 基类名1, 访问控制 基类名2, … {
  2.     //定义派生类自己的成员
  3. }
复制代码

像这样:

  1. // 基类A, 也叫根类
  2. class A {
  3.     int a;
  4.    
  5.     public:
  6.     void setA(int x) {
  7.         a = x;
  8.     }
  9.    
  10.     void showA() {
  11.         cout << "a = " << a << endl;
  12.     }
  13. };

  14. // 基类B, 也叫根类
  15. class B {
  16.     int b;
  17.    
  18.     public:
  19.     void setB(int x) {
  20.         b = x;
  21.     }
  22.    
  23.     void showB() {
  24.         cout << "b = " << b << endl;
  25.     }
  26. };

  27. // 多重继承, 公有继承自类A, 私有继承自类B
  28. class C: public A, private B {
  29.     int c;
  30.    
  31.     public:
  32.     void setC(int x, int y) {
  33.         c = x;
  34.         setB(y);
  35.     }
  36.    
  37.     void showC() {
  38.         showB();
  39.         cout << "c = " << c << endl;
  40.     }
  41. };

  42. int main() {
  43.     C c;
  44.     c.setA(53); // 调用基类setA()函数
  45.     c.showA(); // 调用基类showA()函数
  46.    
  47.     c.setC(55, 58); // 调用派生类C的setC()函数
  48.     c.showC(); // 调用派生类C的showC()函数
  49.    
  50.     // 派生类C私有继承自基类B, 所以基类B中私有成员b, 在派生类C中不可访问, 基类B中公有成员setB(), showB()在派生类C中变私有. 在main()函数中不可访问
  51.     // c.setB(60); // error: 'setB' is a private member of 'B'
  52.     // c.showB(); // 'showB' is a private member of 'B'
  53.     /**
  54.      a = 53
  55.      b = 58
  56.      c = 55
  57.      */
  58. }
复制代码

二义性及其支配规则

对基类成员的访问必须是无二义性的,如果一个表达式的含义可以解释为可以访问多个基类中的成员,则这种对基类成员的访问就是不确定的,称这种访问具有二义性。

01作用域分辨符和成员名限定

代码格式:

  1. 类名::标识符
复制代码

:: 为作用域分辨符,"类名"可以是任一基类或派生类名,“标识符”是该类中声明的任一成员名,

像这样:

  1. // 基类A, 也叫根类
  2. class A {
  3.     public:
  4.     void func() {
  5.         cout << "A func" << endl;
  6.     }
  7. };

  8. // 基类B, 也叫根类
  9. class B {
  10.     public:
  11.     void func() {
  12.         cout << "B func" << endl;
  13.     }
  14.    
  15.     void gunc() {
  16.         cout << "B gunc" << endl;
  17.     }
  18. };

  19. // 多重继承
  20. class C: public A, public B {
  21.     public:
  22.     void gunc() {
  23.         cout << "C gunc" << endl;
  24.     }
  25.    
  26.     void hunc() {
  27.         /**
  28.          这里就具有二义性, 它即可以访问A类中的func(), 也可以访问类B中的func()
  29.          */
  30.         //func(); // error: Member 'func' found in multiple base classes of different types
  31.     }
  32.    
  33.     void hunc1() {
  34.         A::func();
  35.     }
  36.    
  37.     void hunc2() {
  38.         B::func();
  39.     }
  40. };

  41. int main() {
  42.     C c;
  43.     //c.func(); //具有二义性
  44.     c.A::func();
  45.     c.B::func();
  46.     c.B::gunc();
  47.     c.C::gunc();
  48.    
  49.     c.gunc();
  50.     c.hunc1();
  51.     c.hunc2();
  52.    
  53.     /** 输出结果
  54.      A func
  55.      B func
  56.      B gunc
  57.      C gunc
  58.      
  59.      C gunc // 如果基类中的名字在派生类中再次声明, 则基类中的名字就被隐藏. 如果我们想要访问被隐藏的基类中的成员则使用作用域分辨符B::gunc();
  60.      A func
  61.      B func
  62.      */
  63. }
复制代码

02派生类支配基类的同名函数

如果派生类定义了一个同基类成员函数同名的新成员函数(具有相同参数表的成员函数),派生类的新成员函数就覆盖了基类的同名成员函数。

在这里,直接使用成员名只能访问派生类中的成员函数,使用作用域运算符,才能访问基类的同名成员函数。

派生类中的成员函数名支配基类中的同名的成员函数名,这称为名字支配规则。

如果一个名字支配另一个名字,则二者之间不存在二义性,当选择该名字时,使用支配者的名字。

例如上个例子中

  1. c.gunc() // 输出”C gunc”, 基类B中的gunc成员函数被支配了
  2. c.B::gunc(); // 加上作用域分辨符, 来使用被支配的成员
复制代码

总结

C++中的多重继承可能更灵活, 并且支持三种派生方式。
我们在学习一门语言的时候, 更应该把精力放在它的特性上面, 不应该用什么语言, 都用自己所擅长语言的思考方式, 实现方式等, 要学会发挥该语言的优势所在。

收藏 评论0 发布时间:2020-9-7 14:16

举报

0个回答

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版