多态、多态的原理剖析、纯虚函数
内容纲要

多态*

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

多态分为两类:

  1. 静态多态:函数重载,运算符重载 属于静态多态,复用函数名
  2. 动态多态: 派生类 和 虚函数实现运行时多态

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

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

方法的重写和重载的比较:

  • 来自JAVA 笔记

file

file

多态基本语法

#include <iostream >
#include "string"
using namespace std;
// 多态 最最最基本
class Animal {
public:
    virtual void speak() {
        cout << "动物会说话" << endl;
    }
};

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

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

void test01() {
    Animal animal;
    Cat cat;
    doSpeak(cat); //如果上面的Animal 类中的speak() 函数前不加virtual,则这里就会返回Animal 的speak。如果加了virtual 则在运行时绑定函数地址。
}

void main() {
    test01();
}

file

多态的原理剖析

sizeof(Animal); 在没有加virtual的时候,Animal类虽然有一个非静态函数,

  • 未加virtual 关键字,时候Animal 类占用的内存大小

file

  • 加了virtual关键字,这时Animal 类占用的内存大小(只有1个类继承它,哪怕2个类继承也一样占8字节) X64 编译运行环境。占 8字节。。

file

  • 加了virtual关键字,这时Animal 类占用的内存大小 (也只有1个类继承它,2个类也一样占用4个字节) X86 编译运行环境。。占 4字节。。

file

vfptr ( virtual function pointer 虚函数(表)指针 )

  • 重磅!!!!!!

file

代码详见上一节也就是这篇文章开始时 "多态基本语法" 的代码

  • 当使用虚函数Animal 的 speak函数 加了virtual 的时候

file

  • 当Animal 的speak 函数 未加virtual时。

file

案例:计算器类。体现多态的好处。

#include <iostream>
#include "string"
using namespace std;
class  AbstractCalculate {
public:
    virtual int getResult() {
        return 0;
    };
    int Num1;
    int Num2;
};

class AddCalculate : public AbstractCalculate {
public:
    int getResult() {
        return Num1 + Num2;
    }
};

class SubCalculate : public  AbstractCalculate {
public:
    int getResult() {
        return Num1 - Num2;
    }
};

void test01() {

    AbstractCalculate * ac = new AddCalculate;
    ac->Num1 = 10;
    ac->Num2 = 20;
    cout << "AddCalculate 算出的结果是:" << ac->getResult() << endl;
    delete ac;
    ac = NULL;
    AbstractCalculate* abc = new SubCalculate;
    abc->Num1 = 10;
    abc->Num2 = 20;
    cout << "SubCalculate 算出的结果是:" << abc->getResult() << endl;
    delete abc;
    abc = NULL;
}
void main() {
    test01();
}

纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容。如下,无意义的实现。

file

因此可以将虚函数写为 纯虚函数!!

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

  • 当类中有了纯虚函数,这个类也就是 抽象类!!!

抽象类的特点:

  1. 无法实例化对象
  2. 子类必须重写抽象类的纯虚函数,否则也属于抽象类。
#include<iostream> 
#include"string"
using namespace std;

class Person {
public:
    virtual int getMyAge() = 0;
    int m_Age;
};

class Car :public Person {
public:
    void getCar() {
        cout << "I have a beautifulllll car !" << endl;
    }
    int getMyAge() {  //父类是抽象类(有纯虚函数),子类必须重写它的纯虚函数
        return m_Age;
    }
};

void test01()
{
    Car c;
    Car* newCar = new Car;
    c.m_Age = 100;
    cout << c.getMyAge();
    newCar->getCar();
}

void main() {
    test01();
}

多态案例二 - 制作饮品 (烧水,冲泡茶叶/咖啡、倒入杯中、加糖/柠檬)体现抽象类的作用

#include <iostream>
#include "string"
using namespace std;

class AbstractDrinking {
public:
     void Boil()
    {
        cout << "烧开水" << endl;
    }

    virtual void Brew() = 0; //冲泡

    virtual void PourInCup() = 0;

    virtual void PutSomething() = 0;

    void makeDrink() {
        Boil();
        Brew();
        PourInCup();
        PutSomething();
    }
};

//茶叶制作
class Tea : public AbstractDrinking {
public:
    void Brew() {
        cout << "冲泡茶叶" << endl;
    }
    void PourInCup() {
        cout << "倒入杯中" << endl;
    }
    void PutSomething() {
        cout << "放入柠檬" << endl;
    }
};

class Coffee : public AbstractDrinking {
public:
    void Brew() {
        cout << "冲泡咖啡" << endl;
    }
    void PourInCup() {
        cout << "倒入杯中" << endl;
    }
    void PutSomething() {
        cout << "加入牛奶红糖" << endl;
    }
};

void doMake(AbstractDrinking* ad) {
    ad->makeDrink();
    delete ad;
}
void test() {
    Tea * tea = new Tea;
    doMake(tea);
    cout << "====================" << endl;
    doMake(new Coffee);
}

void main() {
    test();
}

虚析构和纯虚析构

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

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

虚析构和纯虚析构共性:

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

虚析构和纯虚析构区别:

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

虚析构语法:

virtual ~类名() {}

纯虚析构语法:

virtual ~类名() = 0;

Summary:

  1. 虚析构或者纯虚析构就是用来解决通过父类指针释放子类对象的问题
  2. 如果子类中没有堆中的数据,不需要释放,则可以不写纯虚析构/虚析构
  3. 拥有纯虚析构函数的类也属于抽象类
#include <iostream>
#include "string"
using namespace std;
class Animal {
public:
    Animal() {
        cout << "Animal的构造函数被调用" << endl;
    }
    int m_Age;
    string* m_Name;
    virtual void speak() = 0;
    //~Animal() {
    //  cout << "Animal的析构函数被调用" << endl;
    //}

    //virtual ~Animal() {
    //  cout << "Animal的虚析构函数被调用" << endl;
    //}
    virtual ~Animal() = 0; //必须实现,不然运行的时候会报错
};
Animal::~Animal() {
    cout << "Animal的纯虚析构被调用" << endl;
}

class Cat : public Animal {
public:
    Cat() {
        cout << "Cat类的构造函数被调用!!" << endl;
        m_Name = new string;
        cout << "&m_Name = " <<  &m_Name << endl;
    }
    void speak() {
        cout << "猫哥可以说话" << endl;
    }

    ~Cat() {
        cout << "Cat类的析构函数被调用!!" << endl;
        if (m_Name != NULL) {
            delete m_Name;
            m_Name = NULL;
        }
    }
};

void test() {
    Animal* animal = new Cat;
    animal->m_Age = 11;
    animal->m_Name = new string("Tom");
    cout << "animal->m_Name = " << &animal->m_Name << endl;
    delete animal;
}

void main() {

    test();
}

注意:我在类中构造器用成员变量在堆中开辟了新的空间,而在类外也开辟了一次,如果使用取址符号,那么他们就是从堆中拿到的地址。则都是一样的。。如果不用取址符,那么他们显示的是成员在栈中的地址。

file

file

  • 如果不用虚析构函数或者纯虚析构函数,则子类的析构调用不了

file

  • 用了虚析构的情况

    file

  • 纯虚析构!!!! 需要实现纯虚析构,不然要裂开。

file

电脑装配的demo,体现多态的各类特性

#include<iostream>
#include "string"
using namespace std;

class CPU {
public:

    virtual void cpuWork() = 0;

    CPU* cpu_Industry; //使用一个指针,来指向下游制造商。

};

class GPU {
public:
    virtual void gpuWork() = 0;
};

class Memory {
public:
    virtual void memoryWork() = 0;

};
class Test {
public:
    void test() {
        cout << "this is Test class working" << endl;
    }

};

class Samsung :public Memory {
public:

    void memoryWork() {
        cout << "三星内存条开始工作" << endl;
    }

};

class Intel : public CPU, public GPU {  //Intel 同时继承GPU,在构造函数的时候分选择性地调用。
public:
    //Intel() {
    //  cpuWork();
    //}
    void gpuWork() {
        cout << "Intel GPU工作了" << endl;
    }

    void cpuWork() {
        cout << "Intel CPU工作了" << endl;
    }
};

class AMD : public CPU, public GPU {
public:
    void cpuWork() {
        cout << "AMD CPU工作" << endl;
    }
    void gpuWork() {
        cout << "AMD GPU工作" << endl;
    }

};

class Computer {
public:
    Computer(CPU* myCPU, GPU* myGPU, Memory* myMemory) {
        m_CPU = myCPU;
        m_GPU = myGPU;
        m_Memory = myMemory;
        /*test.test();*/
        //创建完成对象之后要进行析构。
    }

    void doWork() {
        m_CPU->cpuWork();
        m_Memory->memoryWork();
        m_GPU->gpuWork();
    }

    ~Computer() {
        if (m_CPU != NULL)
        {
            cout << "Computer析构的 m_CPU清理被调用" << endl;
            delete m_CPU;
            m_CPU = NULL;
        };

        if (m_GPU != NULL)
        {
            cout << "Computer析构的 m_GPU清理被调用" << endl;
            delete m_GPU;
            //m_GPU = NULL;

        };

        if (m_Memory != NULL)
        {
            cout << "computer析构的 m_memory清理被调用" << endl;
            delete m_Memory;
            m_Memory = NULL;
        }
    }

private:
    CPU* m_CPU;
    GPU* m_GPU;
    Memory* m_Memory;
};

void main() {
    CPU* myCPU1 = new AMD;
    GPU* myGPU = new Intel;
    Memory* myMemory = new Samsung;
    Test test;
    Computer* myComputer = new Computer(myCPU1, myGPU, myMemory);
    myComputer->doWork();
    delete myComputer;

}   //

上方代码有个地方有问题。

有一个小贴士:之前自己手撸代码出现的问题。。。代码和贴士如下:

#include<iostream>
#include "string"
using namespace std;
class CPU {
public:
    virtual void cpuWork() = 0;
    CPU * cpu_Industry; //使用一个指针,来指向下游制造商。

};
class GPU {
public:
    virtual void gpuWork() = 0;
};
class Memory {
public:
    virtual void memoryWork() = 0;
};
class Test {
public:
    void test() {
        cout << "this is Test class working" << endl;
    }
};

class Samsung :public Memory {
public:

    void memoryWork() {
        cout << "三星内存条开始工作" << endl;
    }
};

class Intel: public CPU ,public GPU{  //Intel 同时继承GPU,在构造函数的时候分选择性地调用。
public:
    //Intel() {
    //  cpuWork();
    //}
    void gpuWork() {
        cout << "Intel GPU工作了" << endl;
    }

    void cpuWork() {
        cout << "Intel CPU工作了" << endl;
    }
};

class AMD : public CPU,public GPU {
public:
    void cpuWork() {
        cout << "AMD CPU工作" << endl;
    }
    void gpuWork() {
        cout << "AMD GPU工作" << endl;
    }
};

class Computer {
public:
    Computer(CPU &myCPU,GPU &myGPU,Memory &myMemory) {
        myCPU.cpuWork();
        myGPU.gpuWork();
        myMemory.memoryWork();
        /*test.test();*/
        //创建完成对象之后要进行析构。
    }
    ~Computer() {
    }
private:
    string * m_CPU;
    string * m_Memory;
    string * m_GPU;
};

void main() {
    CPU * myCPU1 = new AMD;
    GPU * myGPU = new Intel;
    Memory * myMemory = new Samsung;
    Test test;
    Computer myComputer(*myCPU1,*myGPU,*myMemory); 
    //注意这里的问题点,这里要是直接传 myCPU的地址,因为myCPU存在 栈中,存放的地址是 堆中 开辟的AMD 对象的地址。
    //因此这里要使用解指针,直接传在堆中的对象地址就行了。不然编译器报“没有与参数列表匹配的构造函数”
}   //
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇