• 程序设计与算法(三)C++面向对象程序设计笔记 第五周 继承


    笔记按照中国大学MOOC上北京大学郭炜老师主讲的程序设计与算法(三)C++面向对象程序设计所作,B站上也有资源。原课程链接如下:

    程序设计与算法(三)C++面向对象程序设计

    其他各章节链接如下:

    程序设计与算法(三)C++面向对象程序设计笔记 第一周 从C到C++

    程序设计与算法(三)C++面向对象程序设计笔记 第二周 类和对象基础

    程序设计与算法(三)C++面向对象程序设计笔记 第三周 类和对象提高

    程序设计与算法(三)C++面向对象程序设计笔记 第四周 运算符重载

    程序设计与算法(三)C++面向对象程序设计笔记 第五周 继承

    程序设计与算法(三)C++面向对象程序设计笔记 第六周 多态

    程序设计与算法(三)C++面向对象程序设计笔记 第七周 输入输出和模板

    程序设计与算法(三)C++面向对象程序设计笔记 第八周 标准模板库STL(一)

    程序设计与算法(三)C++面向对象程序设计笔记 第九周 标准模板库STL(二)

    程序设计与算法(三)C++面向对象程序设计笔记 第十周 C++11新特性和C++高级主题

    其他各科笔记汇总

    继承

    继承和派生

    继承和派生的概念

    继承:在定义一个新的类 B 时,如果该类与某个已有的类 A 相似(指的是 B 拥有 A 的全部特点),那么就可以把 A 作为一个基类,而把 B 作为基类的一个派生类(也称子类)

    • 派生类是通过对基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数

    • 派生类一经定义后,可以独立使用,不依赖于基类

     

    派生类拥有基类的全部成员函数和成员变量,不论是 private , protected , public

    • 在派生类的各个成员函数中,不能访问基类中的 private 成员
    需要继承的例子

    所有的学生都有的共同属性:姓名、学号、性别、成绩

    所有的学生都有的共同方法(成员函数):是否该留级、是否该奖励

    而不同的学生,又有各自不同的属性和方法:研究生(导师、系)、大学生(系)、中学生(竞赛特长加分)

    如果为每类学生都从头编写一个类,显然会有不少重复的代码,浪费

    比较好的做法是编写一个“学生”类,概括了各种学生的共同特点,然后从“学生”类派生出“大学生”类,“中学生”类,“研究生类”

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xn5wStFM-1666237667479)(C++ 面向对象程序设计.assets/image-20221012222302566.png)]

    派生类的写法
    class 派生类名: public 基类名
    {
        
    };
    
    • 1
    • 2
    • 3
    • 4

     

    class CStudent {
             private:
                     string sName;
                     int nAge;
             public:
                     bool IsThreeGood() { };
                     void SetName( const string & name ) { sName = name; }
                     //......
    };
    
    class CUndergraduateStudent: public CStudent { 
             private:
                     int nDepartment;
             public:
                     bool IsThreeGood() { ...... }; //覆盖
                     bool CanBaoYan() { .... };
    }; // 派生类的写法是:类名:public 基类名 
    
    class CGraduatedStudent:public CStudent {
           private:
                   int nDepartment;
                   char szMentorName[20];
           public:
                   int CountSalary() { ... };
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    派生类对象的内存空间

    派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VavCIlcx-1666237667482)(C++ 面向对象程序设计.assets/image-20221012222814548.png)]

    继承实例程序:学籍管理
    #include 
    #include 
    using namespace std;
    class CStudent {
        private:
            string name;
            string id; //学号
            char gender; //性别,'F'代表女,'M'代表男
            int age;
        public:
            void PrintInfo();
            void SetInfo( const string & name_,const string & id_,
                 int age_, char gender_ );
            string GetName() { return name; }
    };
    
    class CUndergraduateStudent:public CStudent
    {//本科生类,继承了CStudent类
        private:
            string department; //学生所属的系的名称
        public:
            void QualifiedForBaoyan() { //给予保研资格
                cout << “qualified for baoyan” << endl;
            }
            void PrintInfo() {
                CStudent::PrintInfo(); //调用基类的PrintInfo
                cout << “Department:<< department <<endl;
            }
            void SetInfo( const string & name_,const string & id_,
               int age_,char gender_ ,const string & department_) {
                CStudent::SetInfo(name_,id_,age_,gender_); //调用基类的SetInfo
                department = department_;
            }
    };
    
    void CStudent::PrintInfo()
    {
            cout << "Name: " << name << endl;
            cout << "ID: " << id << endl;
            cout << "Age: " << age << endl;
            cout << "Gender: " << gender << endl;
    }
    
    void CStudent::SetInfo( const string & name_,const string & id_,
                            int age_,char gender_ )
    {
            name = name_;
            id = id_;
            age = age_;
            gender = gender_;
    }
    
    int main()
    {
        CUndergraduateStudent s2;
        s2.SetInfo(“Harry Potter ”,118829212,19,‘M’,“Computer Science”);
        cout << s2.GetName() << “ ” ;
        s2.QualifiedForBaoyan ();
        s2.PrintInfo ();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    输出结果:
    Harry Potter qualified for baoyan
    Name: Harry Potter
    ID: 118829212
    Age: 19
    Gender: M
    Department: Computer Science

    继承关系和复合关系

    类之间的两种关系

    继承:“”关系

    • 基类 A,B 是基类 A 的派生类
    • 逻辑上要求:“一个 B 对象也一个 A 对象“

    复合:“”关系

    • 类 C 中“”成员变量 k ,k 是类 D 的对象,则 C 和 D 是复合关系
    • 一般逻辑上要求:“ D 对象是 C 对象的固有属性或组成部分”
    继承关系的使用

    写了一个 CMan 类代表男人

    后来又发现需要一个 CWoman 类来代表女人

    CWoman 类和 CMan 类有共同之处

    就让 CWoman 类从 CMan 类派生而来,是不合适的。因为“一个女人也是一个男人”从逻辑上不成立

    好的做法是概括男人和女人共同特点,写一个 CHuman 类,代表“人”,然后 CMan 和 CWoman 都从
    CHuman 派生

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-phjUXc5V-1666237667483)(C++ 面向对象程序设计.assets/image-20221012223440514.png)]

    复合关系的使用

    几何形体程序中,需要写“点”类,也需要写“圆”类,两者的关系就是复合关系 —— 每一个“圆”对象里都包含()一个“点”对象,这个“点”对象就是圆心

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jUiVsJgk-1666237667484)(C++ 面向对象程序设计.assets/image-20221012223612864.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZpTq3yc6-1666237667485)(C++ 面向对象程序设计.assets/image-20221012223636320.png)]

     

     

    如果要写一个小区养狗管理程序,需要写一个**“业主”类,还需要写一个“狗”**类。

    而狗是有 “主人” 的,主人当然是业主(假定狗只有一个主人,但一个业主可以有最多 10 条狗)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-td5lrUwt-1666237667486)(C++ 面向对象程序设计.assets/image-20221012223742680.png)]

    循环定义,无法计算一个 CMaster 类和 CDog 类的对象占多少字节,编译时无法通过

     

     

    另一种写法:为**“狗”类设一个“业主”类的成员对象;为“业主”**类设一个“狗”类的对象指针数组

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KNyyqHi8-1666237667488)(C++ 面向对象程序设计.assets/image-20221012225313194.png)]

    这样就避免了循环定义

    两条狗的主人有可能是一个人,比如修改了一条狗的主人信息,另外一条狗的主人和这条狗是相同的,另外一条狗里面的主人信息也要进行相应的修改,要维护相同主人的多条狗里面包含的多个主人对象的信息一致性是一件很啰嗦的事情

     

     

    凑合的写法:为**“狗”类设一个“业主”类的对象指针;为“业主”**类设一个“狗”类的对象数组

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ykLQETm-1666237667490)(C++ 面向对象程序设计.assets/image-20221012225418220.png)]

    从逻辑上讲一般要求复合关系成员对象是所属类的组成部分或固有属性,狗不是主人的一部分,也不是主人的固有属性

    此外,所有”狗“的对象都被包含在一个或多个”业主“对象里,要对”狗“对象进行操作就要通过它的主人来进行

     

     

    正确的写法:为“狗”类设一个“业主”类的对象指针;为“业主”类设一个“狗”类的对象指针数组

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n8ZYrDvh-1666237667492)(C++ 面向对象程序设计.assets/image-20221013155349367.png)]

    派生类覆盖基类成员

    覆盖

    派生类可以定义一个和基类成员同名的成员,这叫覆盖。在派生类中访问这类成员时,缺省的情况是
    访问派生类中定义的成员。要在派生类中访问由基类定义的同名成员时,要使用作用域符号 ::

    基类和派生类有同名成员的情况
    class base {
           int j;
           public:
                  int i;
                  void func();
    };
    
    class derived :public base{
          public:
                 int i;
                 void access();
                 void func();
    };
    
    void derived::access() {
              j = 5; //error
              i = 5; //引用的是派生类的 i 
              base::i = 5; //引用的是基类的 i
              func(); //派生类的
              base::func(); //基类的
    }
    
    derived obj;
    obj.i = 1;
    obj.base::i = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    派生类不能访问从基类继承的私有成员,公有成员可以

    一般来说,基类和派生类不定义同名成员变量

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q6fKg8aB-1666237667494)(C++ 面向对象程序设计.assets/image-20221013160353783.png)]

    类的保护成员

    实际上用的不是很多

    另一种存取权限说明符:protected
    • 基类的 private 成员:可以被下列函数访问
      • 基类的成员函数
      • 基类的友元函数
    • 基类的 public 成员:可以被下列函数访问
      • 基类的成员函数
      • 基类的友元函数
      • 派生类的成员函数
      • 派生类的友元函数
      • 其他的函数
    • 基类的 protected 成员:可以被下列函数访问
      • 基类的成员函数
      • 基类的友元函数
      • 派生类的成员函数可以访问当前对象的基类的保护成员
    保护成员
    class Father {
        private: 
                int nPrivate;   //私有成员
        public: 
                int nPublic;    //公有成员
        protected: 
                int nProtected; //保护成员
    };
    
    class Son :public Father{
        void AccessFather () {
            nPublic = 1;      // ok
            nPrivate = 1;     // wrong
            nProtected = 1;   // OK,访问从基类继承的protected成员
            Son f;
            f.nProtected = 1; // wrong ,f不是当前对象
        }
    };
    
    int main()
    {
          Father f;
          Son s;
          f.nPublic = 1;    // Ok
          s.nPublic = 1;    // Ok
          f.nProtected = 1; // error
          f.nPrivate = 1;   // error
          s.nProtected = 1; // error
          s.nPrivate = 1;   // error
          return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    派生类的构造函数

    派生类的构造函数

    派生类对象的构造函数怎么初始化从基类继承的私有成员?

    class Bug {
           private :
                  int nLegs; int nColor;
           public:
                  int nType;
                  Bug (int legs, int color);
                  void PrintBug (){ };
    };
    
    class FlyBug: public Bug // FlyBug是Bug的派生类
    {
           int nWings;
           public:
                  FlyBug(int legs,int color, int wings);
    };
    
    Bug::Bug(int legs, int color)
    {
         nLegs = legs;
         nColor = color;
    }
    
    //错误的FlyBug构造函数
    FlyBug::FlyBug ( int legs,int color, int wings)
    {
         nLegs = legs;   // 不能访问
         nColor = color; // 不能访问
         nType = 1; // ok
         nWings = wings;
    }
    
    //正确的FlyBug构造函数:
    FlyBug::FlyBug (int legs, int color, int wings):Bug(legs, color)
    {
         nWings = wings;
    }
    
    int main() {
        FlyBug fb(2,3,4);
        fb.PrintBug();
        fb.nType = 1;
        fb.nLegs = 2;  // error. nLegs is private
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

     

    一个派生类对象里面包含一个基类对象,派生类对象初始化时它里面的基类对象也必须要调用基类的构造函数初始化

    在创建派生类的对象时,需要调用基类的构造函数:初始化派生类对象中从基类继承的成员。在执行一个派生类的构造函数之前,总是先执行基类的构造函数,因为在派生类的构造函数里面可能要访问从基类继承的成员变量

    调用基类构造函数的两种方式:

    • 显式方式 —— 在派生类的构造函数中,为基类的构造函数提供参数,如:

      derived::derived(arg_derived-list):base(arg_base-list)
      
      • 1
    • 隐式方式 —— 在派生类的构造函数中,省略基类构造函数时,派生类的构造函数则自动调用基类的无参构造函数,如果基类没有无参构造函数编译出错

    派生类的析构函数被执行时,执行完派生类的析构函数后,自动调用基类的析构函数

     

    class Base {
           public:
                  int n;
                  Base(int i):n(i)
                  { cout << "Base " << n << " constructed" << endl;}
                  ~Base()
                  { cout << "Base " << n << " destructed" << endl; }
    };
    
    class Derived:public Base {
           public:
                  Derived(int i):Base(i)
                  { cout << "Derived constructed" << endl; }
                  ~Derived()
                  { cout << "Derived destructed" << endl;}
    };
    int main() { Derived Obj(3); return 0; }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    输出结果:
    Base 3 constructed
    Derived constructed
    Derived destructed
    Base 3 destructed

    包含成员对象的派生类的构造函数写法
    class Bug {
             private :
                      int nLegs; 
                      int nColor;
             public:
                      int nType;
                      Bug ( int legs, int color);
                      void PrintBug (){ };
    };
    
    class Skill {
             public:
                      Skill(int n) { }
    };
    
    class FlyBug: public Bug {
             int nWings;
             Skill sk1, sk2;
             public:
                      FlyBug( int legs, int color, int wings);
    };
    
    FlyBug::FlyBug( int legs, int color, int wings):
            Bug(legs,color),sk1(5),sk2(color) ,nWings(wings) {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    封闭派生类对象的构造函数的执行顺序

    在创建派生类的对象时:

    1. 先执行基类的构造函数,用以初始化派生类对象中从基类继承的成员

    2. 再执行成员对象类的构造函数,用以初始化派生类对象中成员对象

    3. 最后执行派生类自己的构造函数

    封闭派生类对象消亡时析构函数的执行顺序

    在派生类对象消亡时:

    1. 先执行派生类自己的析构函数

    2. 再依次执行各成员对象类的析构函数

    3. 最后执行基类的析构函数

    析构函数的调用顺序与构造函数的调用顺序相反

    public 继承的赋值兼容规则

    派生类写 public 是公有派生,也可以写 private、protected,只不过很少用

    public 继承的赋值兼容规则
    class base { };
    class derived : public base { };
    base b;
    derived d;
    
    • 1
    • 2
    • 3
    • 4

    1)派生类的对象可以赋值给基类对象

    b = d;
    
    • 1

    把 d 里面所包含的基类对象的内容拷贝到 b 里面

    2)派生类对象可以初始化基类引用

    base & br = d;
    
    • 1

    可以认为这个基类的引用实际上引用了派生类对象里面包含的基类对象

    3.派生类对象的地址可以赋值给基类指针

    base * pb = & d;
    
    • 1

    可以认为这个指针指向了派生类对象里面包含的基类对象,这个基类对象放在派生类对象存储空间的最前面,基类对象的起始地址就是整个派生类对象的起始地址

    如果派生方式是 private 或 protected ,则上述三条不可行

    直接基类和间接基类

    类A派生类B,类B派生类C,类C派生类D,则:

    • 类A是类B的直接基类
    • 类B是类C的直接基类,类A是类C的间接基类
    • 类C是类D的直接基类,类A、B是类D的间接基类

     

    在声明派生类时,只需要列出它的直接基类

    • 派生类沿着类的层次自动向上继承它的间接基类
    • 派生类的成员包括:
      • 派生类自己定义的成员
      • 直接基类中的所有成员
      • 所有间接基类的全部成员

     

    构造函数的初始化列表里面只需要指明直接基类如何初始化,间接基类如何初始化是由直接基类的构造函数决定的

    #include 
    using namespace std;
    class Base {
           public:
                  int n;
                  Base(int i):n(i) {
                         cout << "Base " << n << " constructed" << endl;
                  }
                  ~Base() {
                         cout << "Base " << n << " destructed" << endl;
                  }
    };
    
    class Derived:public Base
    {
           public:
                  Derived(int i):Base(i) {
                         cout << "Derived constructed" << endl;
                  }
                  ~Derived() {
                         cout << "Derived destructed" << endl;
                  }
    };
    
    class MoreDerived:public Derived {
           public:
                  MoreDerived():Derived(4) {
                         cout << "More Derived constructed" << endl;
                  }
                  ~MoreDerived() {
                         cout << "More Derived destructed" << endl;
                  }
    };
    
    int main()
    {
           MoreDerived Obj;
           return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    输出结果:
    Base 4 constructed
    Derived constructed
    More Derived constructed
    More Derived destructed
    Derived destructed
    Base 4 destructed

  • 相关阅读:
    LCP 61.气温变化趋势
    碎片笔记 | 大模型攻防简报
    Docker与低代码跨平台开发:实现高效跨平台开发的新范式
    正则表达式
    Springboot实验室自主预约系统毕业设计源码111953
    RabbitMQ3.10.7高级特性
    【Milvus的安装和使用】
    Django部署vue项目报错:Not Found The requested resource was not found on this server.
    动态规划的算法题以及其python实现
    GET和POST请求的区别
  • 原文地址:https://blog.csdn.net/zimuzi2019/article/details/127424256