5 c++核心——类和对象(二)

发布于:2025-07-04 ⋅ 阅读:(17) ⋅ 点赞:(0)

目录

1 运算符重载

1.1 加号运算符重载

1.2 左移运算符重载

1.3 递增运算符重载

1.4 赋值运算符重载

1.5 关系运算符重载

1.6 函数调用运算符重载

2 继承

2.1 继承的基本语法

2.2 继承方式

2.3 继承中的对象模型

2.4 继承中构造和析构顺序

2.5 继承同名成员处理方式

2.6 继承同名静态成员处理方式

2.7 多继承语法

2.8 菱形继承

3 多态

3.1 多态的基本概念

3.2 多态案例一——计算器类

3.3 纯虚函数和抽象类

3.4 多态案例二——制作饮品

3.5 虚析构和纯虚析构

3.6 多态案例三——电脑组装


1 运算符重载

概念:

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

注意:对于内置的数据类型的表达式的运算符是不可能改变的。

1.1 加号运算符重载

示例:

class Person
{
public:
    //1. 成员函数实现+号运算符重载,本质为Person p3 = p1.operator+(p2);
    Person operator+(const Person &p)
    {
        Person temp;
        temp.m_A = this->m_A + p.m_A;
        temp.m_B = this->m_B + p.m_B;
        return temp;
    }
    int m_A;
    int m_B;
};

//2. 全局函数实现+号运算符重载,本质为Person p3 = operator+(p1, p2);
Person operator+(Person &p1, Person &p2)
{
    Person temp;
    temp.m_A = this->m_A + p.m_A;
    temp.m_B = this->m_B + p.m_B;
    return temp;
}

void test()
{
    Person p1;
    p1.m_A = 10;
    p1.m_B = 10;

    Person p2;
    p2.m_A = 10;
    p2.m_B = 10;

    //两个函数选其一
    Person p3 = p1 + p2;
    cout << "p3.m_A = " << p3.m_A << endl;
    cout << "p3.m_B = " << p3.m_B << endl;
}

int main()
{
    test();

    return 0;
}

 注意:运算符重载函数也可以发生函数重载

//可以实现Person p = p1 + 10;类似操作
Person operator+(Person &p1, int num)
{
    Person temp;
    temp.m_A = this->m_A + num;
    temp.m_B = this->m_B + num;
    return temp;
}

 

1.2 左移运算符重载

作用:

可以输出自定义的数据类型

示例:

class Person
{
    friend ostream& operator<<(ostream &out, Person &p);
public:

    //不会用成员函数重载<<运算符,因为无法实现cout在左侧

    Person() {}
    Person(int a, int b)
    {
        m_A = a;
        m_B = b;
    }
private:
    int m_A;
    int m_B;
};

//全局函数实现<<运算符重载
//因为全局只能有一个cout,因为要用&;若用值传递会创建新副本。
ostream &operator<<(ostream &cout, Person &p)
{
    cout << "p.m_A = " << p.m_A << " p.m_B = " << p.m_B;
    return cout;
}

void test()
{
    Person p(10, 10);

    //链式编程
    cout << p << " hello world" << endl;
}

int main()
{
    test();

    return 0;
}

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

1.3 递增运算符重载

作用:

通过重载递增运算符,实现自己的整型数据。

示例:

class MyInteger
{
    friend ostream &operator<<(ostream &cout, Person &p);
public:
    MyInteger()
    {
        m_Num = 0;
    }

    //前置++运算符,返回引用是为了一直对一个数据进行递增操作
    MyInteger &operator++()
    {
        m_Num++;
        return *this;
    }

    /* 
     * 后置++运算符,返回引用是为了一直对一个数据进行递增操作
     * int代表占位参数,可以用于区分前置和后置递增
     * 使用值传递而不是引用传递,因为不能返回局部变量的引用
     */
    MyInteger operator++(int)
    {
        //先 记录当前结果
        MyInteger temp = *this;

        //后 递增
        m_Num++;

        //将记录的结果返回
        return temp;
    }

private:
    int m_Num;
}

//左移运算符重载
ostream &operator<<(ostream &cout, MyInteger &m)
{
    cout << m.m_Num;
    return cout;
}

void test1()
{
    MyInteger m;

    cout << ++(++m) << endl;    //2
    cout << m << endl;          //2
}

void test2()
{
    MyInteger m;

    cout << m++ << endl; //0
    cout << m << endl;   //1
}

int main()
{
    test1();
    test2();

    return 0;
}

总结:前置递增返回引用,后置递增返回值 

 

1.4 赋值运算符重载

c++编译器至少给一个类添加4个函数:

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝
  4. 赋值运算符Operator=,对属性进行值拷贝

 如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题。

示例:

class Person
{
public:
    Person(int age)
    {
        m_Age = new int(age);
    }

    ~Person()
    {
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }
    }

    Person &operator=(const Person &p)
    {
        //先判断是否已有属性在堆区,如果有先释放干净,然后再深拷贝
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }

        //深拷贝
        m_Age = new int(&p.m_Age);

        return *this;
    }

    int *m_Age;
}

void test()
{
    Person p1(18);
    Person p2(20);

    cout << "p1的年龄为:" << *p1.m_Age << endl; //18
    cout << "p2的年龄为:" << *p2.m_Age << endl; //20

    //赋值操作
    p2 = p1;

    cout << "p1的年龄为:" << *p1.m_Age << endl; //18
    cout << "p2的年龄为:" << *p2.m_Age << endl; //18

    Person p3(20);
    p3 = p2 = p1;

    cout << "p1的年龄为:" << *p1.m_Age << endl; //18
    cout << "p2的年龄为:" << *p2.m_Age << endl; //18
    cout << "p3的年龄为:" << *p1.m_Age << endl; //18
}

int main()
{
    test();

    return 0;
}

1.5 关系运算符重载

作用:

重载关系运算符,可以让两个自定义类型对象进行对比操作

示例:

class Person
{
public:
    Person(string m_Name, int age)
    {
        this->m_Name = name;
        this->m_Age = age;
    }

    bool operator==(const Person &p)
    {
        if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
        {
            return true;
        }
        return false;
    }

    int m_Age;
    string m_Name;
}

void test()
{
    Person p1("张三", 18);
    Person p2("李四", 18);

    if (p1 == p2)
    {
        cout << "p1和p2是相等的" << endl;
    }
    else
    {
        cout << "p1和p2是不相等的" << endl;
    }
}

int main()
{
    test();

    return 0;
}

 

1.6 函数调用运算符重载

  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此成为仿函数
  • 仿函数没有固定写法,非常灵活

示例:

class MyPrint
{
public:
    void operator()(string text)
    {
        cout << text << endl;
    }
};

class MyAdd
{
public:
    int operator()(int num1, int num2)
    {
        cout << "num1 + num2" << num1 + num2 << endl;
        return num1 + num2;
    }
};

void MyPrint2(string text)
{
    cout << text << endl;
}

void test()
{
    MyPrint MyPrint;
    MyAdd myadd;

    //仿函数
    MyPrint("hello world");

    //函数调用
    MyPrint2("hello world");

    //非常灵活,没有固定写法
    myadd(100, 100);

    //匿名函数对象
    MyAdd()(100, 100);
}

int main()
{
    test();

    return 0;
}

2 继承

继承是面向对象三大特性之一。

有些类与类之间存在特殊的关系,例如如下图:

定义这些类时,夏季别的成员除了拥有上一级的共性,还有自己的特性。

这个时候就可以考虑利用继承的技术,减少重复代码。

2.1 继承的基本语法

语法:

class 子类: 继承方式 父类

  • 子类也称为派生类
  • 父类也称为基类

示例:很多网站中都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同。接下来我们分别利用普通写法和继承写法来实现网页中的内容,看一下继承存在的意义以及好处。

普通实现:

class Java
{
public:
    void header()
    {
        cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    }
    void footer()
    {
        cout << "帮助中心、交流合作...(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java、Python、C++...(左侧公共列表)" << endl;
    }
    void content()
    {
        cout << "Java学科视频" << endl;
    }
};


class Python
{
public:
    void header()
    {
        cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    }
    void footer()
    {
        cout << "帮助中心、交流合作...(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java、Python、C++...(左侧公共列表)" << endl;
    }
    void content()
    {
        cout << "Python学科视频" << endl;
    }
};

class Cpp
{
public:
    void header()
    {
        cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    }
    void footer()
    {
        cout << "帮助中心、交流合作...(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java、Python、C++...(左侧公共列表)" << endl;
    }
    void content()
    {
        cout << "C++学科视频" << endl;
    }
};

void test()
{
    Java java;
    Python python;
    Cpp cpp;

    cout << "Java下载视频页面如下:" << endl;
    java.header();
    java.footer();
    java.left();
    java.content();

    cout << "Python下载视频页面如下:" << endl;
    python.header();
    python.footer();
    python.left();
    python.content();

    cout << "Cpp下载视频页面如下:" << endl;
    cpp.header();
    cpp.footer();
    cpp.left();
    cpp.content();
}

int main()
{
    test();

    return 0;
}

继承实现:

class BasePage
{
public:
    void header()
    {
        cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    }
    void footer()
    {
        cout << "帮助中心、交流合作...(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java、Python、C++...(左侧公共列表)" << endl;
    }
};

class Java:public BasePage
{
public:
    void content()
    {
        cout << "Java学科视频" << endl;
    }
};

class Python:public BasePage
{
public:
    void content()
    {
        cout << "Python学科视频" << endl;
    }
};

class Cpp:public BasePage
{
public:
    void content()
    {
        cout << "C++学科视频" << endl;
    }
};

void test()
{
    Java java;
    Python python;
    Cpp cpp;

    cout << "Java下载视频页面如下:" << endl;
    java.header();
    java.footer();
    java.left();
    java.content();

    cout << "Python下载视频页面如下:" << endl;
    python.header();
    python.footer();
    python.left();
    python.content();

    cout << "Cpp下载视频页面如下:" << endl;
    cpp.header();
    cpp.footer();
    cpp.left();
    cpp.content();
}

int main()
{
    test();

    return 0;
}

 

2.2 继承方式

语法:class 子类: 继承方式 父类

继承方式一共有三种:

  • 公共继承
  • 保护继承
  • 私有继承

 示例:公共继承

class Base
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};

class Son: public Base
{
public:
    void func()
    {
        m_A = 10;//父类中的公共权限成员 到子类中依然是公共权限
        m_B = 10;//父类中的保护权限成员 到子类中依然是保护权限
        //m_C = 10;//父类中的私有权限成员 子类访问不到
    }
};

void test()
{
    Son s;

    s.m_A = 100;
    //s.m_B = 100;//类外访问不到保护权限成员
}

int main()
{
    test();

    return 0;
}

示例:保护继承

class Base
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};

class Son: protected Base
{
public:
    void func()
    {
        m_A = 10;//父类中的公共权限成员 到子类中变成了保护权限
        m_B = 10;//父类中的保护权限成员 到子类中依然是保护权限
        //m_C = 10;//父类中的私有权限成员 子类访问不到
    }
};

void test()
{
    Son s;

    //s.m_A = 100;//错误,保护权限成员类外访问不到
    //s.m_B = 100;//错误,保护权限成员类外访问不到
}

int main()
{
    test();

    return 0;
}

示例:私有继承

class Base
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};

class Son: private Base
{
public:
    void func()
    {
        m_A = 10;//父类中的公共权限成员 到子类中变成了私有权限
        m_B = 10;//父类中的保护权限成员 到子类中变成了私有权限
        //m_C = 10;//父类中的私有权限成员 子类访问不到
    }
};

class GrandSon: public Son
{
public:
    void func()
    {
        //m_A = 100;//父类中的私有权限成员
        //m_B = 100;//父类中的私有权限成员
    }
};

void test()
{
    Son s;

    //s.m_A = 100;//错误
    //s.m_B = 100;//错误
}

int main()
{
    test();

    return 0;
}

 

2.3 继承中的对象模型

父类中所有非静态成员属性都会被子类继承,私有成员属性是被编译器隐藏了,因此访问不到,但是确实继承下去了。

示例:

class Base
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};

class Son: public Base
{
public:
    int m_D;
};

void test()
{
    cout << "sizeof Son = " << sizeof(Son) << endl;  //16
}

int main()
{
    test();

    return 0;
}

可以通过开发人员命令提示工具查看到继承下来的私有成员属性

  1. 跳转盘符,如:F:
  2. 跳转文件路径,cd 具体路径
  3. 查看命令:cl /d1 reportSingleClassLayout类名 文件名

例如:

 

2.4 继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数。

顺序为:

先构造父类,再构造子类;析构的顺序与构造顺序相反。

示例:

class Base
{
public:
    Base()
    {
        cout << "Base构造函数" << endl;
    }
    ~Base()
    {
        cout << "Base析构函数" << endl;
    }
};

class Son: public Base
{
public:
    Base()
    {
        cout << "Son构造函数" << endl;
    }
    ~Base()
    {
        cout << "Son析构函数" << endl;
    }
};

void test()
{
    Son s;//Base构造->Son构造->Son析构->Base析构
}

int main()
{
    test();

    return 0;
}

 

2.5 继承同名成员处理方式

  • 子类对象可以直接访问子类同名成员
  • 子类对象加作用域可以访问父类同名成员
  • 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

示例:

class Base
{
public:
    Base()
    {
        m_A = 100;
    }
    void func()
    {
        cout << "Base - func()调用" << endl;
    }
    void func(int a)
    {
        cout << "Base - func(int a)调用" << endl;
    }

    int m_A;
};

class Son: public Base
{
public:
    Son()
    {
        m_A = 100;
    }
    void func()
    {
        cout << "Son - func()调用" << endl;
    }

    int m_A;
};

//同名成员
void test1()
{
    Son s;

    cout << "Son中m_A = " << s.m_A << endl;//200
    cout << "Base中m_A = " << s.Base::m_A << endl;//100
}

//同名函数
void test2()
{
    Son s;

    s.func();       //调用子类func()
    s.Base::func(); //调用父类func()

    //s.func(100);     //错误,因为依然调用子类,但是子类中没有这个函数
    s.Base::func(100); //正确
}

int main()
{
    test1();
    test2();

    return 0;
}

 

2.6 继承同名静态成员处理方式

同名静态成员处理方式和非静态处理方式一致。与6.5章节不同的是,有两种访问方式。

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域
  • 当子类与父类拥有同名的静态成员函数,子类会隐藏父类中同名静态成员函数,加作用域可以访问到父类中静态同名函数

 示例:

class Base
{
public:
    static void func()
    {
        cout << "Base - static void func()" << endl;
    }
    static void func(int a)
    {
        cout << "Base - func(int a)调用" << endl;
    }
    static int m_A;
};
//静态成员类内声明,类外初始化
int Base::m_A = 100;

class Son: public Base
{
public:
    static void func()
    {
        cout << "Son - static void func()" << endl;
    }
    static int m_A;
};
int Son::m_A = 200;

//同名静态成员
void test1()
{
    Son s;

    //方式一:通过对象访问
    cout << "Son中m_A = " << s.m_A << endl;         //200
    cout << "Base中m_A = " << s.Base::m_A << endl;  //100

    //方式二:通过类名访问
    cout << "Son中m_A = " << Son::m_A << endl;         //200
    cout << "Base中m_A = " << Son::Base::m_A << endl;  //100
}

//同名静态函数
void test1()
{
    Son s;

    //方式一:通过对象访问
    s.func();       //调用子类func()
    s.Base::func(); //调用父类func()

    //方式二:通过类名访问
    Son::func();
    Son::Base::func(100);

    //Son::func(100);     //错误,因为依然调用子类,但是子类中没有这个函数
    Son::Base::func(100); //正确
}

int main()
{
    test1();
    test2();

    return 0;
}

 

2.7 多继承语法

c++允许一个类继承多个类。

语法:class 子类: 继承方式 父类1, 继承方式 父类2

  • 多继承可能会引发父类中有同名成员出现,需要加作用域区分
  • C++实际开发中不建议用多继承

示例:

//父类1
class Base1
{
public:
    Base1()
    {
        m_A = 100;
    }
    int m_A;
};

//父类2
class Base2
{
public:
    Base2()
    {
        m_A = 200;
    }
    int m_A;
};

//多继承
class Son:public Base1, public Base2
{
public:
    Son()
    {
        m_B = 300;
        m_C = 400;
    }
    int m_B;
    int m_C;
};

void test()
{
    Son s;

    cout << "sizeof Son = " << sizeof(s) << endl;   //16

    //当父类中出现同名成员,子类使用时需要加作用域区分
    cout << "Base1 m_A = " << s.Base1::m_A << endl; //100
    cout << "Base2 m_A = " << s.Base2::m_A << endl; //200
}

int main()
{
    test();

    return 0;
}

 

2.8 菱形继承

菱形继承概念:

  • 两个子类继承同一个父类
  • 又有某个类同时继承两个子类
  • 这种继承被称为菱形继承,或者钻石继承

 图例:

菱形继承问题:

羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。羊驼继承自动物的数据有两份,但其实我们知道这份数据我们只需要一份。

示例:

//动物类
class Animal
{
public:
    int m_Age;
};

//羊类
class Sheep: public Animal {};

//驼类
class Tuo: public Animal {};

//羊驼类
class SheepTuo: public Sheep, public Tuo {};

void test()
{
    SheepTuo st;

    st.Sheep::m_Age = 18;
    st.Tuo::m_Age = 28;

    //当菱形继承时,两个父类拥有相同数据,需要加以作用域区分
    cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl; //18
    cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;     //28
}

int main()
{
    test();

    return 0;
}

解决:

//动物类
class Animal
{
public:
    int m_Age;
};

//羊类:
//利用虚继承(关键字virtual)可以解决菱形继承的问题
class Sheep: virtual public Animal {};

//驼类:
//利用虚继承(关键字virtual)可以解决菱形继承的问题
class Tuo: virtual public Animal {};

//羊驼类
class SheepTuo: public Sheep, public Tuo {};

void test()
{
    SheepTuo st;

    st.Sheep::m_Age = 18;
    st.Tuo::m_Age = 28;

    cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;//28
    cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;    //28
    cout << "st.m_Age = " << st.m_Age << endl;              //28
}

int main()
{
    test();

    return 0;
}

 vbptr(virtual base pointer):虚基类指针,指向vbtable

总结:

  • 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
  • 利用虚继承可以解决菱形继承问题

3 多态

多态是C++面向对象三大特性之一。

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护

3.1 多态的基本概念

多态分为两类:

  • 静态多态:函数重载和运算符重载都属于静态多态,复用函数名
  • 动态多态:子类和虚函数实现运行时多态

 静态多态和动态多态区别:

  • 静态多态的函数地址早绑定——编译阶段确定函数地址
  • 动态多态的函数地址晚绑定——运行阶段确定函数地址

示例:

class Animal
{
public:
    void speak()
    {
        cout << "动物在说话" << endl;
    }
};

class Cat: public Animal
{
    void speak()
    {
        cout << "小猫在说话" << endl;
    }
};

//如果想让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定(晚绑定)
void doSpeak(Animal &animal)
{
    animal.speak();
}

void test()
{
    Cat cat;

    //父类的引用指向子类的对象 is ok,因为c++允许父子之间的类型转换
    doSpeak(cat); //输出动物在说话,因为这是地址早绑定,在编译阶段确定函数地址
}

int main()
{
    test();
    return 0;
}

解决后代码:

class Animal
{
public:
    //虚函数
    virtual void speak()
    {
        cout << "动物在说话" << endl;
    }
};

class Cat: public Animal
{
    void speak()
    {
        cout << "小猫在说话" << endl;
    }
};

void doSpeak(Animal &animal)
{
    animal.speak();
}

void test()
{
    Cat cat;

    doSpeak(cat); //输出小猫在说话
}

int main()
{
    test();
    return 0;
}

动态多态满足条件:

  1. 继承关系
  2. 子类重写父类的虚函数(函数返回值类型、函数名、参数列表完全相同)

动态多态使用:

        父类的指针或引用指向子类对象,即示例中doSpeak()

深度剖析多态示例:

class Animal
{
public:
    void speak()
    {
        cout << "动物在说话" << endl;
    }
};

class Cat: public Animal
{
    void speak()
    {
        cout << "小猫在说话" << endl;
    }
};

void doSpeak(Animal &animal)
{
    animal.speak();
}

void test1()
{
    Cat cat;

    doSpeak(cat);
}

void test2()
{
    /* 
     * 原本为1个字节,因为class Animal相当于空类,只占1字节
     * 非静态成员函数分开存放
     *
     * 在Animal.speak()前面加上virtual后为4字节
     * 因为是一个指针,vfptr(virtual function pointer)指向vftable
     * 可见下方示例图(1)(2)
     */
    cout << "sizeof Animal = " << sizeof(Animal) << endl;
}

int main()
{
    test1();
    test2();

    return 0;
}

多态前: 多态后:

 

3.2 多态案例一——计算器类

分别利用普通写法和多态技术实现计算器,实现两个操作数进行运算的计算器类。

普通写法示例:

class Calculator
{
public:
    int getResult(string oper)
    {
        if (oper == "+")
        {
            return m_Num1 + m_Num2;
        }
        else if (oper == "-")
        {
            return m_Num1 - m_Num2;
        }
        else if (oper == "*")
        {
            return m_Num1 * m_Num2;
        }
        //如果想扩展新功能,需要修改源码
        //在真实开发中,提倡开闭原则
        //即,对扩展进行开放,对修改进行关闭
    }

    int m_Num1; //操作数1
    int m_Num2; //操作数2
};

void test()
{
    Calculator c;
    c.m_Num1 = 10;
    c.m_Num2 = 10;

    cout << c.m_Num1 << "+" << c.m+Num2 << " = " << c.getResult("+") << endl;
    cout << c.m_Num1 << "-" << c.m+Num2 << " = " << c.getResult("-") << endl;
    cout << c.m_Num1 << "*" << c.m+Num2 << " = " << c.getResult("*") << endl;
}

int main()
{
    test();
    return 0;
}

多态技术示例: c++开发提倡多用多态技术!

//实现计算器抽象类
class AbstractCalculator
{
public:
    virtual int getResult()
    {
        return 0;
    }

    int m_Num1; //操作数1
    int m_Num2; //操作数2
};

//加法计算器类
class AddCalculator: public AbstractCalculator
{
public:
    int getResult()
    {
        return m_Num1 + m_Num2;
    }
};

//减法计算器类
class SubCalculator: public AbstractCalculator
{
public:
    int getResult()
    {
        return m_Num1 - m_Num2;
    }
};

//乘法计算器类
class MulCalculator: public AbstractCalculator
{
public:
    int getResult()
    {
        return m_Num1 * m_Num2;
    }
};

void test()
{
    //多态使用条件:父类指针或者引用指向子类对象
    AbstractCalculator *abc = new AddCalculator;
    abc->m_Num1 = 10;
    abc->m_Num2 = 10;
    cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
    delete abc;

    AbstractCalculator *abc = new SubCalculator;
    abc->m_Num1 = 10;
    abc->m_Num2 = 10;
    cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
    delete abc;

    AbstractCalculator *abc = new MulCalculator;
    abc->m_Num1 = 10;
    abc->m_Num2 = 10;
    cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getResult() << endl;
    delete abc;
}

int main()
{
    test();
    return 0;
}

 

3.3 纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名{参数列表} = 0;

当类中有了纯虚函数,这个类也成为抽象类

抽象类特点:

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

示例:

class Base
{
public:
    /*
     * func()为纯虚函数,只要有一个纯虚函数,这个类就称为抽象类
     * Base为抽象类
     * 抽象类特点:1. 无法实例化对象
     * 2. 子类必须重写父类的虚函数
     */
    virtual void func() = 0;//在虚函数的基础上才能赋值为0!!
};

class Son1: public Base
{
public:
};

class Son2: public Base
{
public:
    virtual void func() {};
};

class Son3: public Base
{
public:
    virtual void func()
    {
        cout << "func函数调用" << endl;
    }
};

void test()
{
    //失败,因为子类Son1没重写纯虚函数
    //Son1 s1;
    
    //成功,因为重写了,哪怕一排代码都没写......
    Son2 s2;

    Base *base = new Son3;
    base->func();

    delete base;
}

int main()
{
    test();
    return 0;
}

 

3.4 多态案例二——制作饮品

制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中- 加入辅料

利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶。

示例:

class AbstractDrink
{
public:
    //煮水
    virtual void boil() = 0;
    
    //冲泡    
    virtual void Brew() = 0;

    //倒入杯中
    virtual void PourInCup() = 0;

    //加入辅料
    virtual void PutSomething() = 0;

    //制作饮品
    void makeDrink()
    {
        boil();
        Brew();
        PourInCup();
        PutSomething();
    }
};

class Coffee: public AbstractDrink
{
public:
    virtual void boil()
    {
        cout << "煮纯净水" << endl;
    } 
    virtual void Brew()
    {
        cout << "冲泡咖啡" << endl;
    }
    virtual void PourInCup()
    {
        cout << "倒入马克杯中" << endl;
    }
    virtual void PutSomething()
    {
        cout << "加入糖和牛奶" << endl;
    }
};

class Tea: public AbstractDrink
{
public:
    virtual void boil()
    {
        cout << "煮矿泉水" << endl;
    } 
    virtual void Brew()
    {
        cout << "冲泡茶叶" << endl;
    }
    virtual void PourInCup()
    {
        cout << "倒入茶杯中" << endl;
    }
    virtual void PutSomething()
    {
        cout << "加入枸杞" << endl;
    }
};

void doWork(AbstractDrink *a)
{
    a->makeDrink();
    delete a;//释放
}

void test()
{
    doWork(new Coffee);

    cout << "--------------" << endl;

    doWork(new Tea);
}

int main()
{
    test();
    return 0;
}

 

3.5 虚析构和纯虚析构

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

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构纯虚析构区别:

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

析构类语法

virtual ~类名() {}

纯虚析构类语法: 

virtual ~类名()  = 0;

类名::~类名() {}

问题代码示例:父类指针释放子类对象时不干净

#include<string>

class Animal
{
public:
    //纯虚函数
    virtual void speak() = 0;
    Animal()
    {
        cout << "Animal构造函数调用" << endl;
    }

    ~Animal()
    {
        cout << "Animal析构函数调用" << endl;
    }
    
};

class Cat: public Animal
{
public:
    Cat(string name)
    {
        cout << "Cat构造函数调用" << endl;
        m_Name = new string(name);
    }
    virtual void speak()
    {
        cout << *m_Name << "小猫在说话" << endl;
    }
    ~Cat()
    {
        if (m_Name!= NULL)
        {
            cout << "Cat析构函数调用" << endl;
            delete m_Name;
            m_Name = NULL;
        }
    }
    string *m_Name;
};

void test()
{
    Animal *animal = new Cat;

    animal->speak();

    /*
     * 不会调用Cat析构函数,因为:
     * 父类指针在析构时候,不会调用子类中析构函数
     * 导致如果子类有堆区属性,会造成堆区泄露情况
     */
    delete animal;
}

int main()
{
    test();
    return 0;
}

解决代码示例一:虚析构

#include<string>

class Animal
{
public:
    //纯虚函数
    virtual void speak() = 0;
    Animal()
    {
        cout << "Animal构造函数调用" << endl;
    }

    //这样可以解决父类指针释放子类对象时不干净的问题
    virtual ~Animal()
    {
        cout << "Animal析构函数调用" << endl;
    }
    
};

class Cat: public Animal
{
public:
    Cat(string name)
    {
        cout << "Cat构造函数调用" << endl;
        m_Name = new string(name);
    }
    virtual void speak()
    {
        cout << *m_Name << "小猫在说话" << endl;
    }
    ~Cat()
    {
        if (m_Name!= NULL)
        {
            cout << "Cat析构函数调用" << endl;
            delete m_Name;
            m_Name = NULL;
        }
    }
    string *m_Name;
};

void test()
{
    Animal *animal = new Cat;

    animal->speak();

    delete animal;//正常调用Cat析构函数
}

int main(
{
    test();
    return 0;
}

解决代码示例二:纯虚析构

#include<string>

class Animal
{
public:
    //纯虚函数
    virtual void speak() = 0;
    Animal()
    {
        cout << "Animal构造函数调用" << endl;
    }

    //纯虚析构函数也可以解决父类指针释放子类对象时不干净的问题
    //有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
    virtual ~Animal() = 0;
};

//必须要有具体的函数实现
Animal::~Animal()
{
    cout << "Animal纯虚析构函数调用" << endl;
}

class Cat: public Animal
{
public:
    Cat(string name)
    {
        cout << "Cat构造函数调用" << endl;
        m_Name = new string(name);
    }
    virtual void speak()
    {
        cout << *m_Name << "小猫在说话" << endl;
    }
    ~Cat()
    {
        if (m_Name!= NULL)
        {
            cout << "Cat析构函数调用" << endl;
            delete m_Name;
            m_Name = NULL;
        }
    }
    string *m_Name;
};

void test()
{
    Animal *animal = new Cat;

    animal->speak();

    delete animal;//也可以正常调用Cat析构函数
}

int main(
{
    test();
    return 0;
}

 

3.6 多态案例三——电脑组装

电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)。

将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商。

创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口,测试时组装三台不同的电脑进行工作。

示例:

//抽象的CPU类
class CPU
{
public:
    virtual void calculate() = 0;
};

//抽象的显卡类
class VideoCard
{
public:
    virtual void display() = 0;
};

//抽象的内存类
class Memory
{
public:
    virtual void storage() = 0;
};

//电脑类
class Computer
{
public:
    //构造函数中传入三个零件指针
    Computer(CPU *cpu, VideoCard *vc, Memory *mem)
    {
        m_cpu = cpu;
        m_vc = vc;
        m_mem = mem;
    }
    ~Computer(+)
    {
        if (m_cpu != NULL) 
        {
            delete m_cpu;
            m_cpu = NULL;
        }
        if (m_vc != NULL) 
        {
            delete m_vc;
            m_vc = NULL;
        }
        if (m_mem != NULL) 
        {
            delete m_mem;
            m_mem = NULL;
        }
    }

    //提供让电脑工作的函数,调用每个零件工作的接口
    void work()
    {
        m_cpu->calculate();
        m_vc->display();
        m_mem->storage();
    }
private:
    CPU *m_cpu;
    VideoCard *m_vc;
    Memory *m_mem;
};

//具体零件厂商:Intel
class IntelCPU: public CPU
{
public:
    void calculate()
    {
        cout << "Intel CPU开始工作了" << endl;
    }
};

class IntelVideoCard: public VideoCard
{
public:
    void display()
    {
        cout << "Intel 显卡开始工作了" << endl;
    }
};

class IntelMemory: public Memory
{
public:
    void storage()
    {
        cout << "Intel 内存条开始工作了" << endl;
    }
};

//具体零件厂商:Lenovo
class LenovoCPU: public CPU
{
public:
    void calculate()
    {
        cout << "Lenovo CPU开始工作了" << endl;
    }
};

class LenovoVideoCard: public VideoCard
{
public:
    void display()
    {
        cout << "Lenovo 显卡开始工作了" << endl;
    }
};

class LenovoMemory: public Memory
{
public:
    void storage()
    {
        cout << "Lenovo 内存条开始工作了" << endl;
    }
};

void test()
{
    //第一台电脑零件
    cout << "第一台电脑开始工作:" << endl;
    CPU *intelCPU = new IntelCPU;
    VideoCard *intelVideoCard = new IntelVideoCard;
    Memory *intelMem = new IntelMemory;
    Computer *computer1 = new Computer(intelCPU, intelVideoCard, intelMem);
    
    computer1->work();
    delete computer1;

    //第二台电脑零件
    cout << "--------------------" << endl;
    cout << "第二台电脑开始工作:" << endl;
    CPU *lenovoCPU = new LenovoCPU;
    VideoCard *lenovoVideoCard = new LenovoVideoCard;
    Memory *lenovoMem = new LenovoMemory;
    Computer *computer2 = new Computer(lenovoCPU, lenovoVideoCard, lenovoMem);
    
    computer2->work();
    delete computer2;

    //第三台电脑零件
    cout << "--------------------" << endl;
    cout << "第三台电脑开始工作:" << endl;
    CPU *lenovoCPU = new LenovoCPU;
    VideoCard *intelVideoCard = new LenovoVideoCard;
    Memory *lenovoMem = new LenovoMemory;
    Computer *computer3 = new Computer(lenovoCPU, intelVideoCard, lenovoMem);
    
    computer3->work();
    delete computer3;
}

int main()
{
    test();

    return 0;
}


网站公告

今日签到

点亮在社区的每一天
去签到