浅记C++学习之路 基础篇----继承和多态(接上篇 完)

C++基础


函数的提高


函数默认参数

  • 语法

    返回值类型 函数名(参数 = 默认值)

  • 特性

    若传入参数,则优先使用传入的参数,否则使用默认参数

  • 注意

    如果形参列表中某个参数有默认值,则其后面的所有参数都必须要有默认值

函数占位参数

  • 语法

    返回值类型 函数名(数据类型)

  • 特性

    占位参数可以有默认值

函数重载

  • 作用

    函数名称相同,提高函数名的复用性

  • 条件

    • 函数名相同
    • 函数在同一作用域
    • 函数参数的 类型不同个数不同顺序不同
  • 注意

    • 函数返回值不可以作为函数重载的条件

    • 引用作为重载条件时,要注意加const和不加const的区别

    • 当函数重载遇到默认参数时,要避免二义性的出现

类和对象


面向对象的特性

  • 万事万物皆为对象,对象有其属性行为

    • 封装

    • 继承

    • 多态

封装

  • 语法

    • class 类名{访问权限:属性/行为};
  • 意义

    • 将属性和行为作为一个整体,表现生活中的事物

    • 将属性和行为加以权限控制

  • 示例1----设计一个圆类求出圆的周长和面积

    #include <iostream>
    
    using namespace std;
    
    #define PI 3.14
    
    //需求:设计一个圆类,求圆的周长和面积
    
    //class代表设计一个类,后面紧跟着的就是类的名称
    class Circle
    {
        //公共访问权限
    public:
        //行为
        //设置半径
        void setRadius(int radius);
        //求周长
        double getCircumference(void);
        //求面积
        double getArea(void);
    
        //私有访问权限
    private:
        //属性
        //半径
        int radius;
    };
    
    int main()
    {
        //通过圆类实例化一个圆对象
        Circle c1;
    
        //设置圆的半径
        c1.setRadius(10);
    
        //求圆的周长和面积并打印
        cout << "圆的周长:" << c1.getCircumference() << endl << "圆的面积:" << c1.getArea() << endl;
    
        return 0;
    }
    
    //设置圆的半径
    void Circle::setRadius(int radius)
    {
        this->radius = radius;
    }
    
    //计算圆的周长
    double Circle::getCircumference(void)
    {
        return 2 * PI * this->radius;
    }
    
    //计算圆的面积
    double Circle::getArea(void)
    {
        return PI * this->radius * this->radius;
    }
    
    
  • 成员访问权限

    • public(公有权限)
      • 类内可以访问,类外可以访问
    • protected(保护权限)
      • 类内可以访问 ,类外不可以访问
      • 子类可以访问
    • private(私有权限)
      • 类内可以访问 ,类外不可以访问
      • 子类不可以访问
      • 一般都把成员属性设置为私有权限,然后在公有部分提供访问接口(成员函数)
        • 优点
          1. 可以自己控制读写权限
          2. 对于写,可以检测数据的有效性
  • 示例2----设计一个立方体类求出立方体的面积和体积

    #include <iostream>
    
    using namespace std;
    
    //需求:设计一个立方体类,求出立方体的面积和体积
    
    //创建一个立方体类
    class Cube
    {
    public:     //公有权限
        void setLength(int length); //设置长
        int getLength(void);        //获取长
    
        void setWidth(int length);  //设置宽
        int getWidth(void);         //获取宽
    
        void setHeight(int length); //设置高
        int getHeight(void);        //获取高
    
        int getArea(void);          //获取面积
        int getVolume(void);        //获取体积
    protected:  //保护权限
    
    private:    //私有权限
        int length; //长
        int width;  //宽
        int height; //高
    };
    
    int main()
    {
        //实例化一个立方体对象
        Cube c1;
    
        //设置立方体的长宽高
        c1.setLength(20);
        c1.setWidth(10);
        c1.setHeight(30);
    
        //打印立方体的长宽高
        cout << "长:" << c1.getLength() << endl << "宽:" << c1.getWidth() << endl << "高:" << c1.getHeight() << endl;
    
        //计算立方体的面积和体积并打印
        cout << "面积:" << c1.getArea() << endl << "体积:" << c1.getVolume() << endl;
    
        return 0;
    }
    
    //设置长
    void Cube::setLength(int length)
    {
        this->length = length;
    }
    //获取长
    int Cube::getLength(void)
    {
        return this->length;
    }
    
    //设置宽
    void Cube::setWidth(int width)
    {
        this->width = width;
    }
    //获取宽
    int Cube::getWidth(void)
    {
        return this->width;
    }
    
    //设置高
    void Cube::setHeight(int height)
    {
        this->height = height;
    }
    //获取高
    int Cube::getHeight(void)
    {
        return this->height;
    }
    
    //获取面积
    int Cube::getArea(void)
    {
        return (this->length*this->width + this->length*this->height + this->width*this->height) * 2;
    }
    //获取体积
    int Cube::getVolume(void)
    {
        return this->length * this->width * this->height;
    }
    
    

今天的C++就学到这里叭,又是学习的一天~

----2023/08/30

对象的初始化和清理

  • 构造函数和析构函数

    • 构造函数
      • 作用
        • 创建对象时为对象的成员属性赋值
      • 语法
        • 类名(){}
      • 规则
        • 函数名与类名相同
        • 没有返回值,可以有参数
        • 可以发生重载
    • 析构函数
      • 作用
        • 对象销毁前执行一些清理工作,如将对象申请的堆区内存释放
      • 语法
        • ~类名(){}
      • 规则
        • 函数名与类名相同,但是前面要加一个~
        • 没有返回值,不可以有参数
        • 不可以发生重载
    • 特性
      • 由编译器自动调用,不需要手动调用,且每个对象只调用一次
      • 构造函数和析构函数都是必须要有的实现,如果我们不提供,编译器会自动生成,其函数内部是空实现
  • 构造函数的分类和调用方法

    • 分类

      • 按参数分类
        • 有参构造
        • 无参构造(不需要传递参数的构造也称默认构造
      • 按类型分类
        • 普通构造
        • 拷贝构造
    • 调用方法

      • 括号法

        //括号法
        Person p1;      //默认构造的调用,注意:不能加(),否则编译器会认为这是一个函数的声明
        Person p2(10);  //有参构造的调用
        Person p3(p2);  //拷贝构造的调用
        
      • 显式法

        //显式法
        Person p4;              //默认构造的调用,注意:不能加(),否则编译器会认为这是一个函数的声明
        Person p5 = Person(10); //有参构造的调用
        Person p6 = Person(p5); //拷贝构造的调用
        
        Person(10); //单独写就是匿名对象,语句结束后系统会立即回收匿名对象
        
        • 通过匿名对象显式的告诉编译器要调用哪种构造
        • 注意
          • 匿名对象的语句结束后系统会立即回收匿名对象
          • 不能利用拷贝构造函数初始化一个匿名对象,否则编译器会认为是对象的声明,导致变量重定义报错
      • 隐式转换法

        //隐式转换法
        Person p7;      //默认构造的调用,注意:不能加(),否则编译器会认为这是一个函数的声明
        Person p8 = 10; //相当于写了 Person p8 = Person(10); //有参构造
        Person p9 = p8; //拷贝构造
        
      • 注意

        • 调用默认构造函数时不要加(),否则编译器会认为这是一个函数的声明
    • 完整示例代码

      #include <iostream>
      
      using namespace std;
      
      class Person
      {
      public:
          //无参构造(只要不需要传递参数的构造都称为默认构造)
          Person()
          {
              cout << "Person 的无参构造函数调用" << endl;
          }
      
          //有参构造
          Person(int age)
          {
              cout << "Person 的有参构造函数调用" << endl;
          }
      
          //拷贝构造
          Person(const Person& p) //构造函数不能修改传进来的参数,必须要用引用传递,如果使用值传递会造成无限递归导致程序崩溃
          {
              this->age = p.age;
              cout << "Person 的拷贝构造函数调用" << endl;
          }
      
          //析构
          ~Person()
          {
              cout << "Person 的析构函数调用" << endl;
          }
      protected:
      
      private:
          int age;
      };
      
      int main()
      {
          //构造函数的调用方法
          //括号法(常用)
          Person p1;      //默认构造的调用,注意:不能加(),否则编译器会认为这是一个函数的声明
          Person p2(10);  //有参构造的调用
          Person p3(p2);  //拷贝构造的调用
      
          //显式法
          Person p4;              //默认构造的调用,注意:不能加(),否则编译器会认为这是一个函数的声明
          Person p5 = Person(10); //有参构造的调用
          Person p6 = Person(p5); //拷贝构造的调用
      
          Person(10); //单独写就是匿名对象,语句结束后系统会立即回收匿名对象
      
          //隐式转换法
          Person p7;      //默认构造的调用,注意:不能加(),否则编译器会认为这是一个函数的声明
          Person p8 = 10; //相当于写了 Person p8 = Person(10); //有参构造
          Person p9 = p8; //拷贝构造
      
          return 0;
      }
      
      
  • 拷贝构造函数的调用时机

    • 使用一个已经创建完毕的对象来初始化另一个对象
    • 值传递的方式给函数参数传值
    • 以值的方式返回局部对象
    #include <iostream>
    
    using namespace std;
    
    class Person
    {
    public:
        Person()
        {
            cout << "Person 的默认构造函数调用" << endl;
        }
    
        Person(int age)
        {
            this->age = age;
            cout << "Person 的有参构造函数调用" << endl;
        }
    
        Person(const Person& p)
        {
            this->age = p.age;
            cout << "Person 的拷贝构造函数调用" << endl;
        }
    
        ~Person()
        {
            cout << "Person 的析构函数调用" << endl;
        }
    protected:
    
    private:
        int age;
    };
    
    void test01(Person p)
    {
    
    }
    
    Person test02(void)
    {
        Person p;
    
        return p;
    }
    
    int main()
    {
        //拷贝构造函数的调用时机
        //使用一个已经创建完毕的对象来初始化另一个对象
        Person p1(20);
        Person p2(p1);
    
        //值传递的方式给函数参数传值
        Person p3;
        test01(p3);
    
        //以值的方式返回局部对象
        Person p4 = test02();
    
        return 0;
    }
    
    
  • 构造函数的调用规则

    • 默认情况下,编译器至少给一个类添加四个函数
      • 默认构造函数(无参,函数体为空)
      • 默认析构函数(无参,函数体为空)
      • 默认拷贝构造函数(对属性进行值拷贝)
      • 赋值运算符重载operator=(对属性进行值拷贝,详见运算符重载章节)
    • 如果自定义了有参构造,则编译器不再提供默认无参构造,但是会提供默认拷贝构造
    • 如果自定义了拷贝构造,则编译器不再提供任何构造
  • 深拷贝与浅拷贝

    • 浅拷贝

      • 简单的赋值操作,拷贝成员属性中的内容
      • 编译器提供的拷贝构造函数属于浅拷贝
    • 深拷贝

      • 如果一个类的成员属性存在堆区的操作,则在拷贝时重新开辟一块内存提供新对象使用,此时一定要自定义析构函数进行堆区内存的释放操作
      • 其目的是避免堆区内存的重复释放
  • 初始化列表

    • 语法
      • 构造函数(形参列表):属性1(初值1),属性2(初值2)…{}
  • 类对象作为类成员

    • 注意
      • 在构造时,先构造成员对象,再构造自身
      • 在析构时,先析构自身,再析构成员对象(先构造的后析构)
  • 静态成员

    • 静态成员变量

      • 所有对象共享同一个变量
      • 在编译阶段分配内存
      • 在类内声明,在类外初始化
      • 可以通过对象来访问,也可以通过类名访问(也要看访问权限)
      • 受访问权限的控制
    • 静态成员函数

      • 所有对象共享同一个函数
      • 只能访问静态成员变量
      • 可以通过对象来访问,也可以通过类名访问(也要看访问权限)
      • 受访问权限的控制

不迈出第一步,你永远不知道你能走到哪里,加油!

2023/08/31

C++对象模型和this指针

  • 成员变量和成员函数分开存储
    • 只有非静态成员变量才属于类的对象上
    • C++编译器会给每个空对象分配1个字节空间,是为了区分空对象占用的内存位置(每个空对象都应该有独一无二的地址)
  • this指针
    • 特性
      • 指向调用成员函数的对象
      • 隐含于非静态成员函数内的一种指针
      • 不需要定义,可以直接在非静态成员函数中使用
    • 用途
      • 当形参和成员变量同名时,可用this指针区分
      • 在类的非静态成员函数中返回对象本身,可使用return *this
  • 空指针访问成员函数
    • C++中允许空指针调用成员函数
    • 在调用时如果有用到this指针,要进行判断增加代码的健壮性
  • const修饰成员函数
    • 常函数
      • 成员函数后加const修饰,这个函数就变成了常函数(加const的本质是修饰this指针指向的值)
      • 常函数内不可以修改成员属性
      • 成员属性声明时加关键字mutable后,在常函数中依然可以修改
    • 常对象
      • 声明对象时加const,这个对象就变成了常对象
      • 常对象只能调用常函数
      • 成员属性声明时加关键字mutable后,在常对象中依然可以修改

友元

  • 目的

    • 让一个函数或者一个类访问另一个类中的私有成员
  • 三种实现

    • 全局函数做友元

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      class Building
      {
          //声明全局函数为友元后,该函数可以访问类中的私有成员
          friend void goodGay(Building *building);
      public:
          Building()
          {
              keTing = "客厅";
              woShi = "卧室";
          }
      
          string keTing;
      private:
          string woShi;
      };
      
      void goodGay(Building *building)
      {
          cout << "好基友全局函数正在访问 " << building->keTing << endl; //访问公有成员
          cout << "好基友全局函数正在访问 " << building->woShi << endl;  //访问私有成员
      }
      
      
      void test01(void)
      {
          Building building;
          goodGay(&building);
      }
      
      int main()
      {
          test01();
      
          return 0;
      }
      
    • 类做友元

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      class Building;
      
      class GoodGay
      {
      public:
          GoodGay();
      
          void visit(void);   //参观函数 访问Building中的属性
      
          Building *building;
      };
      
      class Building
      {
          //声明类为友元后,该类可以访问本类中的私有成员
          friend class GoodGay;
      public:
          Building();
      
          string keTing;
      private:
          string woShi;
      };
      
      GoodGay::GoodGay()
      {
          this->building = new Building;
      }
      
      Building::Building()
      {
          keTing = "客厅";
          woShi = "卧室";
      }
      
      void GoodGay::visit(void)
      {
          cout << "好基友类正在访问 " << building->keTing << endl;    //访问公有成员
          cout << "好基友类正在访问 " << building->woShi << endl;     //访问私有成员
      }
      
      void test01(void)
      {
          GoodGay gg;
          gg.visit();
      }
      
      int main()
      {
          test01();
      
          return 0;
      }
      
    • 成员函数做友元

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      class Building;
      
      class GoodGay
      {
      public:
          GoodGay();
      
          void visit(void);   //让visit函数可以访问Building中的私有成员
          void visit2(void);   //让visit2函数不可以访问Building中的私有成员
      
          Building *building;
      };
      
      class Building
      {
          //声明类下的成员函数为友元后,该类的成员函数可以访问本类中的私有成员
          friend void GoodGay::visit(void);
      public:
          Building();
      
          string keTing;
      private:
          string woShi;
      };
      
      GoodGay::GoodGay()
      {
          this->building = new Building;
      }
      
      Building::Building()
      {
          keTing = "客厅";
          woShi = "卧室";
      }
      
      void GoodGay::visit(void)
      {
          cout << "visit函数正在访问 " << building->keTing << endl;     //访问公有成员
          cout << "visit函数正在访问 " << building->woShi << endl;      //访问私有成员,通过
      }
      
      void GoodGay::visit2(void)
      {
          cout << "visit2函数正在访问 " << building->keTing << endl;    //访问公有成员
          //cout << "visit2函数正在访问 " << building->woShi << endl;     //访问私有成员,报错
      
      }
      
      void test01(void)
      {
          GoodGay gg;
          gg.visit();
          gg.visit2();
      }
      
      int main()
      {
          test01();
      
          return 0;
      }
      

运算符重载

  • 概念

    • 对已有的运算符重新定义,赋予其另一种功能,以适应不同的数据类型
  • 作用

    • 实现两个自定义数据类型的运算
    • 运算符重载也可以发生函数重载
  • 加号运算符重载operator+

    • 通过成员函数重载 或 通过全局函数重载

    • 对于内置数据类型不可以重载(比如1+1=2,我们想重载为1+1=0,不允许这样操作)

    • 不能滥用重载(比如重载+运算符,却在函数体里实现减法运算)

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      //通过成员函数重载 +
      class Person
      {
      public:
          //实现自定义类型和整形型相加
          Person operator+(int num)
          {
              Person temp;
      
              temp.a = this->a + num;
              temp.b = this->b + num;
      
              return temp;
          }
      
          int a;
          int b;
      };
      
      //通过全局函数重载 +
      //实现自定义类型和自定义类型相加
      Person operator+(Person &p1, Person &p2)
      {
          Person temp;
      
          temp.a = p1.a + p2.a;
          temp.b = p1.b + p2.b;
      
          return temp;
      }
      
      void test01(void)
      {
          Person p1;
          p1.a = 10;
          p1.b = 10;
      
          Person p2;
          p2.a = 10;
          p2.b = 10;
      
          Person p3 = p1 + p2;
          Person p4 = p1 + 100;
      
          cout << "p3.a = " << p3.a << endl << "p3.b = " << p3.b << endl;
          cout << "p4.a = " << p4.a << endl << "p4.b = " << p4.b << endl;
      }
      
      int main()
      {
          test01();
      
          return 0;
      }
      
  • 左移运算符重载operator<<

    • 只能通过全局函数重载

    • 重载左移运算符配合友元可以实现输出自定义数据类型

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      //通过成员函数重载
      class Person
      {
      public:
          //不会利用成员函数来重载<<运算符,因为无法实现cout等标准流在左侧
      //    void operator<<()
      //    {
      
      //    }
      
          int a;
          int b;
      };
      
      //只能通过全局函数重载 << (返回cout可以实现链式编程)
      ostream &operator<<(ostream &cout, Person &p)
      {
          cout << "p.a = " << p.a << endl << "p.b = " << p.b;
      
          return cout;
      }
      
      void test01(void)
      {
          Person p1;
          p1.a = 10;
          p1.b = 10;
      
          cout << p1 << endl;
      }
      
      int main()
      {
          test01();
      
          return 0;
      }
      
  • 递增运算符重载operator++

    • 前置递增返回引用,后置递增返回值

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      class MyInt
      {
          friend ostream &operator<<(ostream &cout, MyInt myint);
      public:
          MyInt():a(0){}
      
          //重载前置++运算符
          MyInt &operator++(void)
          {
              ++this->a;
      
              return *this;
          }
      
          //重载后置++运算符 //这个int代表的是占位参数
          MyInt operator++(int)
          {
              MyInt temp = *this;
      
              ++this->a;
      
              return temp;
          }
      
      private:
          int a;
      };
      
      //重载<<运算符
      ostream &operator<<(ostream &cout, MyInt myint)
      {
          cout << myint.a;
      
          return cout;
      }
      
      //前置++
      void test01(void)
      {
          MyInt myint;
      
          cout << ++(++myint) << endl;
          cout << myint << endl;
      }
      
      //后置++
      void test02(void)
      {
          MyInt myint;
      
          cout << myint++ << endl;
          cout << myint << endl;
      }
      
      int main()
      {
          test01();
          test02();
      
          return 0;
      }
      
  • 赋值运算符重载operator=

    • 编译器提供的默认赋值运算符重载是浅拷贝的操作,同样面临着深浅拷贝的问题

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      class Person
      {
      public:
          Person(int age)
          {
              this->age = new int(age);
          }
      
          ~Person()
          {
              if(this->age != NULL)
                  delete this->age;
          }
      
          Person &operator=(Person &p)
          {
              //先判断是否有属性在堆区,如果有,先释放干净,再深拷贝
              if(this->age != NULL)
              {
                  delete this->age;
                  this->age = NULL;
              }
      
              this->age = new int(*p.age);
      
              return *this;
          }
      
          int *age;
      };
      
      void test01(void)
      {
          Person p1(18);
          Person p2(20);
          Person p3(0);
      
          cout <<"p1的年龄为 " << *p1.age << endl;
          cout <<"p2的年龄为 " << *p2.age << endl;
      
          p3 = p2 = p1;
      
          cout <<"p1的年龄为 " << *p1.age << endl;
          cout <<"p2的年龄为 " << *p2.age << endl;
          cout <<"p3的年龄为 " << *p3.age << endl;
      }
      
      int main()
      {
          test01();
      
          return 0;
      }
      
  • 关系运算符重载operator==和operator!=

    • 重载==和!=

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      class Person
      {
      public:
          Person(string name, int age)
          {
              this->name = name;
              this->age = age;
          }
      
          //重载==号
          bool operator==(Person &p)
          {
              if(this->name == p.name && this->age == p.age)
                  return true;
              return false;
          }
      
          //重载!=号
          bool operator!=(Person &p)
          {
              if(this->name == p.name && this->age == p.age)
                  return false;
              return true;
          }
      
          ~Person()
          {
      
          }
      
          string name;
          int age;
      };
      
      void test01(void)
      {
          Person p1("Tom", 18);
          Person p2("Tom", 18);
      
          if(p1 == p2)
          {
              cout << "p1和p2是相等的" << endl;
          }
          else
          {
              cout << "p1和p2不是相等的" << endl;
          }
      
          if(p1 != p2)
          {
              cout << "p1和p2不是相等的" << endl;
          }
          else
          {
              cout << "p1和p2是相等的" << endl;
          }
      }
      
      int main()
      {
          test01();
      
          return 0;
      }
      
  • 函数调用运算符重载operator()

    • 由于重载后的使用方式非常像函数的调用,因此称为仿函数

    • 仿函数没有固定写法,非常灵活

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      //打印输出类
      class MyPrint
      {
      public:
          //重载函数调用运算符
          void operator()(string test)
          {
              cout << test << endl;
          }
      };
      
      //仿函数非常灵活,没有固定的写法
      //加法类
      class MyAdd
      {
      public:
          int operator()(int a, int b)
          {
              return a + b;
          }
      };
      
      void test01(void)
      {
          MyPrint myPrint;
      
          myPrint("Hello world");
      }
      
      void test02(void)
      {
          MyAdd myAdd;
      
          int result1 = myAdd(100, 200);
      
          //匿名函数对象
          int result2 = MyAdd()(200, 300);
      
          cout << "result1 = " << result1 << endl;
          cout << "result2 = " << result2 << endl;
      }
      
      int main()
      {
          test01();
          test02();
      
          return 0;
      }
      

继承

  • 继承的基本语法

    • 语法
      • class 子类 : 继承方式 父类
      • 子类也称为派生类,父类也称为基类
    • 总结
      • 继承的好处就是可以减少重复的代码
  • 继承方式

    • 公有继承
      • 父类中private权限的成员子类不可以访问
      • 父类中public权限的成员在子类中也是public权限
      • 父类中protected权限的成员在子类中也是protected权限
    • 保护继承
      • 父类中private权限的成员子类不可以访问
      • 父类中public权限和protected权限的成员在子类中都是protected权限
    • 私有继承
      • 父类中private权限的成员子类不可以访问
      • 父类中public权限和protected权限的成员在子类中都是private权限
  • 继承中的对象模型

    • 父类中所有的非静态成员属性都会被子类继承
    • 父类中的私有成员在子类中是被编译器隐藏了,因此访问不到,但是确实是被继承下去了
  • 继承中构造和析构顺序

    • 先构造父类,再构造子类
    • 先析构子类,再析构父类
  • 继承中同名成员的处理方式

    • 子类对象可以直接访问到子类中的同名成员
    • 子类对象加作用域可以访问到父类中的同名成员
    • 如果子类中出现了和父类中同名的成员函数,那么子类中会隐藏所有父类中的同名成员函数,如果想要访问隐藏的成员函数,需要加作用域
  • 继承同名静态成员的处理方式

    • 与非静态成员出现同名的处理方式一致
  • 多继承语法

    • C++允许一个类继承多个类

    • 多继承可能会引发父类中同名成员出现,需要加作用域区分

    • 在实际开发中,不建议使用多继承

    • 语法

      • class 子类 : 继承方式 父类1, 继承方式 父类2…
      #include <iostream>
      #include <string>
      
      using namespace std;
      
      //父类1
      class Base1
      {
      public:
          Base1()
          {
              this->a = 100;
          }
      
          int a;
      };
      
      //父类2
      class Base2
      {
      public:
          Base2()
          {
              this->a = 200;
          }
      
          int a;
      };
      
      //子类  继承父类1和父类2
      class Son : public Base1, public Base2
      {
      public:
          Son()
          {
              this->c = 300;
              this->d = 400;
          }
      
          int c;
          int d;
      };
      
      void test01(void)
      {
          Son s;
      
          cout << "sizeof Son = " << sizeof(Son) << endl;
          //当父类中出现同名成员,需要加作用域区分
          cout << "Base1 a = " << s.Base1::a << endl;
          cout << "Base2 a = " << s.Base2::a << endl;
      }
      
      int main()
      {
          test01();
      
          return 0;
      }
      
  • 菱形继承

    • 概念

      • 两个派生类继承同一个基类
      • 又有某个类同时继承这两个派生类
      • 这种继承被称为菱形继承或者钻石继承
    • 菱形继承问题

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      //动物类
      class Animal
      {
      public:
          int age;
      };
      
      //利用虚继承 解决菱形继承的问题
      //在继承之前加上关键字virtual 变为虚继承
      //Animal类称为虚基类
      
      //羊类
      class Sheep : virtual public Animal
      {
      
      };
      
      //驼类
      class Tuo : virtual public Animal
      {
      
      };
      
      //羊驼类
      class SheepTuo : public Sheep, public Tuo
      {
      
      };
      
      void test01(void)
      {
          SheepTuo st;
      
          st.Sheep::age = 18;
          st.Tuo::age = 28;
      
          //菱形继承时,两个父类拥有相同数据,需要加以作用域区分
          cout << "st.Sheep::age = " << st.Sheep::age << endl;
          cout << "st.Tuo::age = " << st.Tuo::age << endl;
      
          //这个数据我们知道只要有一个就可以了,菱形继承导致数据有两个,导致资源浪费
          //解决方法:利用虚继承
          cout << "st.age = " << st.age << endl;  //虚继承之后数据只有一个,可以直接访问
      }
      
      int main()
      {
          test01();
      
          return 0;
      }
      

多态

  • 多态的基本概念

    • 分类

      • 静态多态
        • 函数重载 和 运算符重载都属于静态多态,复用函数名
        • 函数地址早绑定----编译阶段确定函数地址
      • 动态多态
        • 派生类和虚函数实现运行时多态
        • 函数地址晚绑定----运行阶段确定函数地址
    • 动态多态的满足条件

      • 有继承关系

      • 子类重写父类的虚函数

    • 动态多态的使用

      • 父类的指针或者引用 指向 子类对象

    • 多态的好处

      • 组织结构清晰

      • 可读性强

      • 方便前期和后期的扩展和维护

        #include <iostream>
        #include <string>
        
        using namespace std;
        
        //普通写法和多态技术实现计算器
        
        //普通写法
        //class Calculator
        //{
        //public:
        //    int getResult(string oper)
        //    {
        //        if(oper == "+")
        //            return this->num1 + this->num2;
        //        else if(oper == "-")
        //            return this->num1 - this->num2;
        //        else if(oper == "*")
        //            return this->num1 * this->num2;
        
        //        //如果想扩展新的功能,需要修改源码
        //        //在真实的开发中 提倡 开闭原则----对扩展进行开放,对修改进行关闭
        //    }
        
        //    int num1;
        //    int num2;
        //};
        
        //利用多态实现计算器
        //实现计算器的抽象类
        class AbstractCalculator
        {
        public:
            virtual int getResult(void)
            {
                return 0;
            }
        
            int num1;
            int num2;
        };
        
        //加法计算器类
        class AddCalculator : public AbstractCalculator
        {
        public:
            int getResult(void)
            {
                return this->num1 + this->num2;
            }
        };
        
        //减法计算器类
        class SubCalculator : public AbstractCalculator
        {
        public:
            int getResult(void)
            {
                return this->num1 - this->num2;
            }
        };
        
        //乘法计算器类
        class MulCalculator : public AbstractCalculator
        {
        public:
            int getResult(void)
            {
                return this->num1 * this->num2;
            }
        };
        
        //void test01(void)
        //{
        
        //    c.num1 = 10;
        //    c.num2 = 20;
        
        //    cout << c.num1 << "+" << c.num2 << "=" << c.getResult("+") << endl;
        //    cout << c.num1 << "-" << c.num2 << "=" << c.getResult("-") << endl;
        //    cout << c.num1 << "*" << c.num2 << "=" << c.getResult("*") << endl;
        //}
        
        void test02(void)
        {
            //多态的使用条件
            //父类的指针或者引用指向子类对象
            AbstractCalculator *abc = new AddCalculator;
        
            abc->num1 = 10;
            abc->num2 = 20;
        
            cout << abc->num1 << "+" << abc->num2 << "=" << abc->getResult() << endl;
            //用完后记得销毁
            delete abc;
        
            abc = new SubCalculator;
        
            abc->num1 = 10;
            abc->num2 = 20;
        
            cout << abc->num1 << "-" << abc->num2 << "=" << abc->getResult() << endl;
            //用完后记得销毁
            delete abc;
        
            abc = new MulCalculator;
        
            abc->num1 = 10;
            abc->num2 = 20;
        
            cout << abc->num1 << "*" << abc->num2 << "=" << abc->getResult() << endl;
            //用完后记得销毁
            delete abc;
        }
        
        int main()
        {
            //test01();
        
            test02();
        
            return 0;
        }
        
  • 纯虚函数和抽象类

    • 语法

      • virtual 返回值类型 函数名(参数列表) = 0;
      • 当类中有了纯虚函数,这个类就称为抽象类
    • 抽象类的特点

      • 无法实例化对象
      • 抽象类的子类必须重写抽象类中的纯虚函数,否则也属于抽象类
    • 案例

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      //制作饮品
      class AbstractDrink
      {
      public:
          //煮水
          virtual void boil(void) = 0;
      
          //冲泡
          virtual void brew(void) = 0;
      
          //倒入杯中
          virtual void pourIncup(void) = 0;
      
          //加入辅料
          virtual void putSomething(void) = 0;
      
          //制作饮品
          void makeDrink(void)
          {
              this->boil();
              this->brew();
              this->pourIncup();
              this->putSomething();
          }
      };
      
      //制作咖啡
      class Coffee : public AbstractDrink
      {
      public:
          //煮水
          virtual void boil(void)
          {
              cout << "煮农夫山泉" << endl;
          }
      
          //冲泡
          virtual void brew(void)
          {
              cout << "冲泡咖啡" << endl;
          }
      
          //倒入杯中
          virtual void pourIncup(void)
          {
              cout << "倒入咖啡" << endl;
          }
      
          //加入辅料
          virtual void putSomething(void)
          {
              cout << "加入糖和牛奶" << endl;
          }
      };
      
      //制作茶叶
      class Tea : public AbstractDrink
      {
      public:
          //煮水
          virtual void boil(void)
          {
              cout << "煮矿泉水" << endl;
          }
      
          //冲泡
          virtual void brew(void)
          {
              cout << "冲泡茶叶" << endl;
          }
      
          //倒入杯中
          virtual void pourIncup(void)
          {
              cout << "倒入茶叶" << endl;
          }
      
          //加入辅料
          virtual void putSomething(void)
          {
              cout << "加入枸杞" << endl;
          }
      };
      
      //制作函数
      void doWork(AbstractDrink *abs)
      {
          abs->makeDrink();
          delete abs;
      
      }
      
      void test01(void)
      {
          doWork(new Coffee);
          doWork(new Tea);
      }
      
      int main()
      {
          test01();
      
          return 0;
      }
      
  • 虚析构和纯虚析构

    • 场景:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

      • 解决方法
        • 将父类中的析构函数改为虚析构或者纯虚析构
    • 虚析构和纯虚析构的共性

      • 都可以解决父类指针无法调用子类析构的问题
      • 都需要具体的函数实现
    • 虚析构和纯虚析构的区别

      • 如果是纯虚析构,那么该类属于抽象类,无法实例化对象
    • 语法

      • 虚析构
        • virtual ~类名(){}
      • 纯虚析构
        • virtual ~类名() = 0;
        • 类名::~类名(){}
    • 案例

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      //虚析构和纯虚析构
      class Animal
      {
      public:
          Animal()
          {
              cout << "Animal 构造" << endl;
          }
      
          //纯虚函数
          virtual void speak(void) = 0;
      
          //利用虚析构可以解决父类指针释放子类对象时不干净的问题
      //    virtual ~Animal()
      //    {
      //        cout << "Animal 析构" << endl;
      //    }
      
          //纯虚析构 需要声明也需要实现
          //有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
          virtual ~Animal() = 0;
      };
      
      Animal::~Animal()
      {
          cout << "Animal 纯虚析构" << endl;
      }
      
      //小猫类
      class Cat : public Animal
      {
      public:
          Cat(string name)
          {
              this->name = new string(name);
      
              cout << "Cat 构造" << endl;
          }
      
          void speak(void)
          {
              cout << *this->name << "小猫在说话" << endl;
          }
      
          ~Cat()
          {
              if(this->name != NULL)
              {
                  delete this->name;
                  this->name = NULL;
      
                  cout << "Cat 析构" << endl;
              }
          }
      
          string *name;
      };
      
      void test01(void)
      {
          Animal *animal = new Cat("Tom");
          animal->speak();
      
          //父类的指针在析构的时候不会调用子类的析构函数,导致子类如果有堆区属性,会存在内存泄漏的情况
          delete animal;
      }
      
      int main()
      {
          test01();
      
          return 0;
      }
      
    • 总结

      • 虚析构和纯虚析构都是用来解决通过父类指针释放子类对象的问题
      • 如果子类中没有堆区数据,可以不写虚析构或者纯虚析构
      • 拥有纯虚析构的类也属于抽象类
    • 案例 ---- 组装电脑

      #include <iostream>
      #include <string>
      
      using namespace std;
      
      //抽象不同零件类
      //抽象CPU类
      class Cpu
      {
      public:
          //抽象的计算函数
          virtual void calculate(void) = 0;
      };
      
      //抽象显卡类
      class VideoCard
      {
      public:
          //抽象的显示函数
          virtual void display(void) = 0;
      };
      
      //抽象内存条类
      class Memory
      {
      public:
          //抽象的存储函数
          virtual void storage(void) = 0;
      };
      
      //电脑类
      class Computer
      {
      public:
          Computer(Cpu *cpu, VideoCard *vc, Memory *mem)
          {
              this->cpu = cpu;
              this->vc = vc;
              this->mem = mem;
          }
      
          //提供工作的函数
          void work(void)
          {
              //让零件工作起来
              this->cpu->calculate();
              this->vc->display();
              this->mem->storage();
          }
      
          //提供析构函数释放电脑零件
          ~Computer()
          {
              if(this->cpu != NULL)
              {
                  delete this->cpu;
                  this->cpu = NULL;
              }
      
              if(this->vc != NULL)
              {
                  delete this->vc;
                  this->vc = NULL;
              }
      
              if(this->mem != NULL)
              {
                  delete this->mem;
                  this->mem = NULL;
              }
          }
      
      private:
          Cpu *cpu;       //CPU零件指针
          VideoCard *vc;  //显卡零件指针
          Memory *mem;    //内存条零件指针
      };
      
      //具体的厂商
      //Intel
      class IntelCpu : public Cpu
      {
      public:
          void calculate(void)
          {
              cout << "Intel CPU 开始计算" << endl;
          }
      };
      
      class IntelVideoCard : public VideoCard
      {
      public:
          void display(void)
          {
              cout << "Intel 显卡 开始显示" << endl;
          }
      };
      
      class IntelMemory : public Memory
      {
      public:
          void storage(void)
          {
              cout << "Intel 内存条 开始存储" << endl;
          }
      };
      
      //Lenovo
      class LenovoCpu : public Cpu
      {
      public:
          void calculate(void)
          {
              cout << "Lenovo CPU 开始计算" << endl;
          }
      };
      
      class LenovoVideoCard : public VideoCard
      {
      public:
          void display(void)
          {
              cout << "Lenovo 显卡 开始显示" << endl;
          }
      };
      
      class LenovoMemory : public Memory
      {
      public:
          void storage(void)
          {
              cout << "Lenovo 内存条 开始存储" << endl;
          }
      };
      
      void test01(void)
      {
          //第一台电脑零件
          Cpu *intelCpu1 = new IntelCpu;
          VideoCard *intelCard1 = new IntelVideoCard;
          Memory *intelMem1 = new IntelMemory;
      
          //创建第一台电脑
          Computer *computer1 = new Computer(intelCpu1, intelCard1, intelMem1);
          computer1->work();
          delete computer1;
      
          //第二台电脑零件
          Cpu *lenovoCpu2 = new LenovoCpu;
          VideoCard *lenovoCard2 = new LenovoVideoCard;
          Memory *lenovoMem2 = new LenovoMemory;
      
          //创建第二台电脑
          Computer *computer2 = new Computer(lenovoCpu2, lenovoCard2, lenovoMem2);
          computer2->work();
          delete computer2;
      
          //第三台电脑零件
          Cpu *intelCpu3 = new IntelCpu;
          VideoCard *lenovoCard3 = new LenovoVideoCard;
          Memory *lenovoMem3 = new LenovoMemory;
      
          //创建第三台电脑
          Computer *computer3 = new Computer(intelCpu3, lenovoCard3, lenovoMem3);
          computer3->work();
          delete computer3;
      }
      
      int main()
      {
          test01();
      
          return 0;
      }
      
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值