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

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个回答

所属标签

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