服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C++类和对象之多态详解

C++类和对象之多态详解

2022-07-22 10:35烬烬今天学习了吗 C/C++

大家好,本篇文章主要讲的是C++类和对象之多态详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览

多态基本概念和原理剖析

多态:多态是C++面向对象的三大特性之一。多态分为静态多态和动态多态。
静态多态:函数重载和运算符重载属于静态多态,复用函数名。
动态多态:派生类和虚函数实现运行时多态。

区别:
静态多态的函数地址早绑定,编译阶段确定函数地址。
动态多态的函数地址晚绑定,运行阶段确定函数地址。

?
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
#include <iostream>
using namespace std;
//动态多态满足条件
//1、有继承关系
//2、子类重写父类的虚函数 重写是指返回值 函数名 参数完全一样    重载是指函数名相同
//动态多态使用
//父类的指针或引用,指向子类对象
 
 
//当子类重写父类的虚函数,子类中的虚函数表内部会替换成子类的虚函数地址,将&Animal::speak替换成&Cat::speak
 
//动物类
class Animal {
public:
    //虚函数
    virtual void speak(){
        cout << "动物在说话" << endl;
    }
};
//猫类
class Cat :public Animal {
public:
    void speak() {
        cout << "小猫在说话" << endl;
    }
};
//狗类
class Dog :public Animal {
public:
    void speak() {
        cout << "小狗在说话" << endl;
    }
};
 
//执行说话的函数
//地址早绑定,在编译阶段就确定了函数的地址
//虽然想让猫说话,但是这里输出了动物在说话   因为地址早绑定
//如果想让猫说话,这个函数的地址就不能提前绑定,需要在运行阶段进行绑定,也即地址晚绑定
void DoSpeak(Animal &animal) {
    animal.speak();
}
void test01() {
    Cat cat;
    DoSpeak(cat);     //虽然想让猫说话,但是这里输出了动物在说话,因为地址早绑定,若父类改为虚函数地址晚绑定,就是猫在说话
    Dog dog;
    DoSpeak(dog);
}
 
void test02() {
    cout << "size of Animal = " << sizeof(Animal) << endl;
}
int main() {
    //test01();
    test02();
    system("pause");
    return 0;
}

多态案例1 计算器类

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

多态优点
1、代码组织结构清晰
2、可读性强
3、便于前期和后期的扩展与维护

?
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include<iostream>
using namespace std;
 
//普通写法
class Calculate {
public:
    int GetResult(string oper) {
        if (oper == "+") {
            return num1 + num2;
        }
        else if (oper == "-") {
            return num1 - num2;
        }
        else if (oper == "*") {
            return num1 * num2;
        }
        //如果想扩展新的功能,需要修改原码,在实际开发中,提倡开闭原则:对扩展进行开发,对修改进行关闭
    }
    int num1;
    int num2;
};
void test01() {
    //创建计算器对象
    Calculate c;
    c.num1 = 10;
    c.num2 = 10;
    cout << c.num1 << " + "<< c.num2 << " = " <<c.GetResult("+") << endl;
    cout << c.num1 << " - "<< c.num2 << " = " <<c.GetResult("-") << endl;
    cout << c.num1 << " * "<< c.num2 << " = " <<c.GetResult("*") << endl;
}
 
 
//利用多态实现计算器
//多态的优点
//1、组织结构清晰
//2、可读性强
//3、对于前期扩展和后期维护性能高
 
//实现计算器抽象类
class AbstractCalculate {
public:
    virtual int Result() {
        return 0;
    }
    int m_num1;
    int m_num2;
};
//加法计算器类
class AddCalculate : public AbstractCalculate{
public:
    int Result() {
        return m_num1 + m_num2;
    }
};
//减法计算器类
class SubCalculate : public AbstractCalculate {
public:
    int Result() {
        return m_num1 - m_num2;
    }
};
//乘法计算器类
class MulCalculate : public AbstractCalculate {
public:
    int Result() {
        return m_num1 * m_num2;
    }
};
void test02() {
    //多态使用指针
    //父类指针或者引用指向子类对象
 
    //加法运算
    AbstractCalculate* p = new AddCalculate;
    p->m_num1 = 100;
    p->m_num2 = 100;
    cout << p->m_num1 << " + " << p->m_num2 << " = " << p->Result() <<endl;
    //用完后释放新开辟的堆区数据
    delete p;
 
    //减法运算
    p = new SubCalculate;
    p->m_num1 = 100;
    p->m_num2 = 100;
    cout << p->m_num1 << " - " << p->m_num2 << " = " << p->Result() << endl;
    delete p;
 
    //乘法运算
    p = new MulCalculate;
    p->m_num1 = 100;
    p->m_num2 = 100;
    cout << p->m_num1 << " * " << p->m_num2 << " = " << p->Result() << endl;
    delete p;
}
int main() {
    //test01();
    test02();
    system("pause");
    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
#include<iostream>
using namespace std;
//纯虚函数语法 virtual 返回值 函数名(参数列表) = 0;
//当类中有了纯虚函数,这个类也称为抽象类,抽象类不能实例化对象,子类必须重写抽象类里的纯虚函数,不然子类也属于抽象类
class Base {
public:
    //纯虚函数 只要有纯虚函数这个类就是抽象类
    virtual void func() = 0;
};
class Son :public Base {
public:
    void func() {
        cout << "Son类下的func函数调用" << endl;
    }
};
void test01() {
    /*抽象类是无法实例化对象的
    Base b;
    new Base;*/
    Son s;
    s.func();
    //多态方式调用 父类的指针new一个子类
    Base* p = new Son;
    p->func();
    delete p;
}
 
int main() {
    test01();
    system("pause");
    return 0;
}

多态案例2 制作饮品

利用多态技术实现制作饮品的流程,提供抽象制作饮品基类,提供子类制作咖啡和茶。

这里使用指针多态而不是引用的原因在于,引用需先实例化对象,但是有纯虚函数的抽象类无法实例化对象。

?
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include<iostream>
using namespace std;
class AbstractDrinking {
public:
    //煮水
    virtual void Boil() = 0;
    //冲泡
    virtual void Brew() = 0;
    //倒入杯中
    virtual void DropInCup() = 0;
    //加入辅料
    virtual void AddSomething() = 0;
 
    //制作饮品
    void MakeDrink() {
        Boil();
        Brew();
        DropInCup();
        AddSomething();
    }
};
 
//制作咖啡流程
class Coffee : public AbstractDrinking {
public:
    //煮水
    virtual void Boil() {
        cout << "煮一壶沸水" << endl;
    }
    //冲泡
    virtual void Brew() {
        cout << "冲泡咖啡粉" << endl;
    }
    //倒入杯中
    virtual void DropInCup() {
        cout << "将咖啡倒入杯中" << endl;
    }
    //加入辅料
    virtual void AddSomething() {
        cout << "将牛奶和白糖加入咖啡中" << endl;
    }
};
 
 
//制作茶水流程
class Tea : public AbstractDrinking {
public:
    //煮水
    virtual void Boil() {
        cout << "煮一壶沸水" << endl;
    }
    //冲泡
    virtual void Brew() {
        cout << "冲泡西湖龙井" << endl;
    }
    //倒入杯中
    virtual void DropInCup() {
        cout << "将茶水倒入杯中" << endl;
    }
    //加入辅料
    virtual void AddSomething() {
        cout << "将枸杞加入茶水中" << endl;
    }
};
 
//制作饮品函数
void DoWork(AbstractDrinking *p) {   //这里相当于 AbstractDrinking *p = new Coffee;
                                    //若这里不使用指针,改为引用,test01的函数就不能使用
                                   //因为引用是先实例化对象再调用对象,抽象类无法实例化对象
    p->MakeDrink();
    delete p;  //防止内存泄露,使用后记得释放内存
}
void test01() {
    //想喝一杯咖啡
    DoWork(new Coffee);
    cout << "-----------------------------------" << endl;
    //想喝一杯西湖龙井茶
    DoWork(new Tea);
}
 
int main() {
    test01();
    system("pause");
    return 0;
}

虚析构和纯虚析构

使用多态技术时,如果有子类开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。解决该问题的方法是将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性
1、可以解决父类指针释放子类对象
2、都需要有具体的函数实现
虚析构和纯虚析构区别
如果是纯虚析构,该类属于抽象类,无法实例化对象。

?
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
#include<iostream>
#include<string>
using namespace std;
//动物类
class Animal {
public:
    Animal() {
        cout << "这是Animal的构造函数调用" << endl;
    }
    //纯虚函数
    virtual void Speak() = 0;
 
    //为了走子类的虚构函数,需要将父类的虚构函数改为虚析构
    /*virtual ~Animal() {
        cout << "这是Animal的析构函数调用" << endl;
    }*/
    //纯虚析构函数  父类的堆区可能也被开辟需要释放,因此父类的析构也必须需要声明,也需要实现
    //有纯虚析构后这个类也属于抽象类,无法实例化对象
    virtual ~Animal() = 0;
};
Animal:: ~Animal() {
    cout << "这是Animal类的写在外部的纯虚析构函数调用" << endl;
}
 
//猫类
class Cat : public Animal{
public:
    //Cat类的构造函数
    Cat(string name) {
        cout << "这是Cat类的构造函数" << endl;
        m_name = new string(name);
    }
    void Speak() {
        cout << *m_name<<"小猫的叫声" << endl;
    }
    ~Cat() {
        if (m_name != NULL) {
            //父类指针在析构的时候,不会调用子类中的析构函数,导致子类中有堆区开辟的数据没有被释放干净导致内存泄露
            cout << "这是Cat类的析构函数" << endl;
            delete m_name;
            m_name = NULL;
        }
    }
    string *m_name;
    
};
 
void test01() {
    Animal* p = new Cat("Tom");             //如果子类中没有堆区数据,可以不写虚析构或纯虚析构函数
    p->Speak();
    delete p;     //堆区开辟以后,释放指针
}
 
int main() {
    test01();
    system("pause");
    return 0;
}

多态案例3 电脑组装

电脑主要部件为CPU,显卡和内存条。将每个零件封装出抽象基类,并提供不同的厂商生产不同的零件。创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口。测试时组装三台不同的电脑进行工作。

?
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include<iostream>
using namespace std;
//每个零件都封装出抽象的基类
//CPU类
class Cpu {
public:
    //抽象的计算函数
    virtual void Calculate() = 0;
    //virtual ~Cpu() = 0;
};
//Cpu::~Cpu() {
//  cout << "Cpu类写在外部的纯虚析构函数调用" << endl;
//}
//显卡类
class VideoCard {
public:
    //抽象的显示函数
    virtual void Display() = 0;
};
//内存条类
class Memory {
public:
    //抽象的存储函数
    virtual void Storage() = 0;
};
 
 
//电脑类
class Computer {
public:
    Computer(Cpu* cpu, VideoCard* videocard, Memory* memory) {
        m_cpu = cpu;
        m_videocard = videocard;
        m_memory = memory;
    }
    //工作函数:让零件调用起来的工作接口  父类指针调用接口时就已经是多态技术了
    void DoWork() {
        //Cpu进行计算操作
        m_cpu->Calculate();
        //显卡进行显示操作
        m_videocard->Display();
        //内存条进行存储操作
        m_memory->Storage();
    }
    //提供析构函数释放电脑的三个零件
    ~Computer() {
        if (m_cpu != NULL) {
            delete m_cpu;
            m_cpu = NULL;
        }
        if (m_videocard != NULL) {
            delete m_videocard;
            m_videocard = NULL;
        }
        if (m_memory != NULL) {
            delete m_memory;
            m_memory = NULL;
        }
    }
private:
    Cpu *m_cpu;
    VideoCard *m_videocard;
    Memory *m_memory;
};
 
 
//具体厂商///
//因特尔厂商
class IntelCpu :public Cpu{
public:
    //子类重写父类的虚函数
    virtual void Calculate() {
        cout << "Intel的Cpu开始计算了!" << endl;
    }
    /*~IntelCpu() {
        cout << "这是子类IntelCpu的析构函数调用" << endl;
    }*/
};
class IntelVideoCard :public VideoCard {
public:
    //子类重写父类的虚函数
    virtual void Display() {
        cout << "Intel的显卡开始显示屏幕了!" << endl;
    }
};
class IntelMemory :public Memory {
public:
    //子类重写父类的虚函数
    virtual void Storage() {
        cout << "Intel的内存条开始存储数据了!" << endl;
    }
};
 
//Lenovo厂商
class LenovoCpu :public Cpu {
public:
    //子类重写父类的虚函数
    virtual void Calculate() {
        cout << "Lenovo的Cpu开始计算了!" << endl;
    }
};
class LenovoVideoCard :public VideoCard {
public:
    //子类重写父类的虚函数
    virtual void Display() {
        cout << "Lenovo的显卡开始显示屏幕了!" << endl;
    }
};
class LenovoMemory :public Memory {
public:
    //子类重写父类的虚函数
    virtual void Storage() {
        cout << "Lenovo的内存条开始存储数据了!" << endl;
    }
};
 
 
//开始测试,组装不同的电脑
void test01() {
    //第一台电脑的零件
    //父类指针指向了子类对象,利用了多态技术
    Cpu* intelcpu = new IntelCpu;
    VideoCard* intelvideocard = new IntelVideoCard;
    Memory* intelmemory = new IntelMemory;
    //创建第一台电脑
    cout << "组装好的第一台电脑配置如下:" << endl;
    Computer* computer1 = new Computer(intelcpu,intelvideocard,intelmemory);
    computer1->DoWork();
    delete computer1;
 
    //创建第二台电脑
    cout << "组装好的第二台电脑配置如下:" << endl;
    Computer* computer2 = new Computer(new LenovoCpu, new LenovoVideoCard, new LenovoMemory);
    computer2->DoWork();
    delete computer2;
 
    //创建第三台电脑
    cout << "组装好的第三台电脑配置如下:" << endl;
    Computer* computer3 = new Computer(new LenovoCpu, new IntelVideoCard, new LenovoMemory);
    computer3->DoWork();
    delete computer3;
 
}
 
int main() {
    test01();
    system("pause");
    return 0;
}

到此这篇关于C++类和对象之多态详解的文章就介绍到这了,更多相关C++多态内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/weixin_44884304/article/details/122066349

延伸 · 阅读

精彩推荐