五、虛函數(shù)與多態(tài)性
1、多態(tài)性的概念
(1)一個面向?qū)ο蟮南到y(tǒng)常常要求一組具有相同基本語義的方法能在同一接口下為不同的對象服務(wù),這就是多態(tài)性。
(2)在C++中,多態(tài)性可分為編譯時的多態(tài)性(靜態(tài)多態(tài))和運行時的多態(tài)性(動態(tài)多態(tài)),編譯時的多態(tài)性是通過函數(shù)重載和模板體現(xiàn)的,運行時的多態(tài)性是通過虛函數(shù)體現(xiàn)的。
(3)靜態(tài)多態(tài)和動態(tài)多態(tài)的區(qū)別:
①靜態(tài)多態(tài)的函數(shù)地址早綁定——編譯階段確定函數(shù)地址。
②動態(tài)多態(tài)的函數(shù)地址晚綁定——運行階段確定函數(shù)地址。
2、虛函數(shù)
(1)在非靜態(tài)成員函數(shù)聲明的前面加上virtual修飾符,即把該函數(shù)聲明為虛函數(shù)。
(2)在派生類中可以重寫從基類繼承下來的虛函數(shù),從而提供該函數(shù)的適用于派生類的專門版本,如果不重寫虛函數(shù),那么繼承下來的虛函數(shù)仍然保持其在基類中的定義,即派生類和基類使用同一函數(shù)版本。(除少數(shù)特殊情況外,在派生類中重寫虛函數(shù)時,函數(shù)名、形參和返回值類型必須保持不變)
(3)虛函數(shù)在派生類中被重寫后,重寫的函數(shù)仍然是虛函數(shù),可以在其派生類中再次被重寫。對于虛函數(shù)的重寫函數(shù),無論是否使用virtual修飾符都是虛函數(shù)(建議加上,避免遺忘它是虛函數(shù))。
(4)對虛函數(shù)的調(diào)用有非多態(tài)調(diào)用和多態(tài)調(diào)用兩種方式:
①非多態(tài)調(diào)用是指不借助指針或引用的直接調(diào)用,它建立在靜態(tài)綁定機制基礎(chǔ)之上,不具備多態(tài)特征)。
②多態(tài)調(diào)用是指借助指向基類的指針或引用的調(diào)用,一個基類指針(或引用)可以指向它的派生類對象,而且通過這樣的指針(或引用)調(diào)用虛函數(shù)時,調(diào)用的是該指針(或引用)實際所指向的對象所在類的那個重寫版本。
(5)基類中的實函數(shù)也可以在派生類中改寫,但改寫的函數(shù)仍然是實函數(shù),調(diào)用實函數(shù)時,通過基類指針(或引用)所調(diào)用的也只能是基類的函數(shù)版本,無法調(diào)用到派生類中的改寫函數(shù)。(換句話說,對實函數(shù)的任何形式的調(diào)用都是非多態(tài)的)
(6)舉例:
①例1:文章來源:http://www.zghlxwxcb.cn/news/detail-832076.html
#include<iostream>
using namespace std;
class Animal
{
public:
int m_Age;
/*void speak()
{
cout << "動物在說話" << endl;
}*/
virtual void speak() //虛函數(shù)
{
cout << "動物在說話" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小貓在說話" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在說話" << endl;
}
};
//地址早綁定 在編譯階段確定函數(shù)地址
//如果想讓貓說話,那么這個函數(shù)地址就不能提前綁定,需要在運行階段綁定(地址晚綁定)
void doSpeak(Animal &animal) //Animal & animal = cat/dog;父類的指針或者引用執(zhí)行與子類對象
{
animal.speak(); //動態(tài)多態(tài)滿足條件:有繼承關(guān)系;子類重寫父類的虛函數(shù)
//重寫時,函數(shù)的返回值類型、函數(shù)名、參數(shù)列表都要完全相同
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
②例2:
#include<iostream>
using namespace std;
class Animal
{
public:
virtual void speak()
{
cout << "動物在說話" << endl;
}
};
class Cat :public Animal
{
public:
/*void speak()
{
cout << "小貓在說話" << endl;
}*/
};
class Dog :public Animal
{
public:
virtual void speak()
{
cout << "小狗在說話" << endl;
}
};
void doSpeak(Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
void test02()
{
cout << "sizeof Animal=" << sizeof(Animal) << endl; //虛函數(shù)帶指針vfptr,所以對象占4個字節(jié)
cout << "sizeof Animal=" << sizeof(Cat) << endl;
cout << "sizeof Animal=" << sizeof(Dog) << endl;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
③例3:
#include<iostream>
using namespace std;
#include<string>
class Calculator //普通寫法
{
public:
int m_Num1;
int m_Num2;
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;
}
}
};
class AbstractCalculator //多態(tài)技術(shù)
{
public:
int m_Num1;
int m_Num2;
virtual int getResult()
{
return 0;
}
};
class AddCalculator :public AbstractCalculator
{
int getResult()
{
return m_Num1 + m_Num2;
}
};
class SubCalculator :public AbstractCalculator
{
int getResult()
{
return m_Num1 - m_Num2;
}
};
class MulCalculator :public AbstractCalculator
{
int getResult()
{
return m_Num1 * m_Num2;
}
};
//開閉原則:對擴展進行開發(fā),對修改進行關(guān)閉
void test01()
{
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;
}
void test02()
{
AbstractCalculator* abc = new AddCalculator;
abc->m_Num1 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc; //用完計算器后記得銷毀
abc = new SubCalculator;
abc->m_Num1 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc; //用完計算機后記得銷毀
abc = new MulCalculator;
abc->m_Num1 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getResult() << endl;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
3、純虛函數(shù)和抽象類
(1)為了將一個虛函數(shù)聲明為純虛函數(shù),需要在虛函數(shù)原型的語句結(jié)束符“;”之前加上“=0”。
(2)擁有純虛函數(shù)的類稱為抽象類,抽象類不能用來定義對象。如果一個抽象類的派生類沒有重寫來自其基類的某個純虛函數(shù),則該函數(shù)在派生類中仍然是純虛函數(shù),這就使得該派生類也稱為抽象類,也就是說,一個派生類可以把重寫純虛函數(shù)的任務(wù)進一步轉(zhuǎn)交給它自己的派生類。
(3)可以在將一個函數(shù)聲明為純虛函數(shù)的同時為該函數(shù)提供實現(xiàn)版本,一個函數(shù)是否為純虛函數(shù),取決于其原型的尾部是否為“=0;”,與實現(xiàn)版本的有無沒有關(guān)系。純虛函數(shù)的實現(xiàn)版本通常是不完善的版本,但包含了一些共有操作供各個派生類在重寫函數(shù)中調(diào)用。擁有實現(xiàn)版本的純虛函數(shù)仍然有賴于派生類提供重寫版本。
(4)派生類在重寫一個純虛函數(shù)時可以繼續(xù)將之聲明為純虛函數(shù)。另外,純虛函數(shù)不得聲明為內(nèi)聯(lián)函數(shù)。
(5)舉例:
①例1:
#include<iostream>
using namespace std;
class Base
{
public:
int m_Num1;
int m_Num2;
virtual int func() = 0; //純虛函數(shù)
};
class Son :public Base
{
public:
int func() //子類必須重寫父類中的純虛函數(shù),否則無法實例化對象
{
cout << "func()函數(shù)調(diào)用" << endl;
}
};
void test01()
{
//Base b; 對于抽象類,無法實例化對象,即使是在堆區(qū)開辟也不行
Base * base = new Son;
base->func();
}
int main() {
test01();
system("pause");
return 0;
}
②例2:
#include<iostream>
using namespace std;
class AbstractDrinking
{
public:
virtual void zhushui() = 0;
virtual void chongpao() = 0;
virtual void daorubeizhong() = 0;
virtual void jiarufuliao() = 0;
void makeDrink()
{
zhushui();
chongpao();
daorubeizhong();
jiarufuliao();
}
};
class Coffee :public AbstractDrinking
{
public:
void zhushui()
{
cout << "煮水" << endl;
}
virtual void chongpao() //這里virtual 加不加都行
{
cout << "沖泡咖啡" << endl;
}
void daorubeizhong()
{
cout << "倒入杯中" << endl;
}
void jiarufuliao()
{
cout << "加糖和牛奶" << endl;
}
};
class Tea :public AbstractDrinking
{
public:
void zhushui()
{
cout << "煮水" << endl;
}
void chongpao()
{
cout << "沖泡茶葉" << endl;
}
void daorubeizhong()
{
cout << "倒入杯中" << endl;
}
void jiarufuliao()
{
cout << "加檸檬" << endl;
}
};
void doWork(AbstractDrinking * abs)
{
abs->makeDrink() ;
delete abs; //做完drink后,釋放new在堆區(qū)開辟出的數(shù)據(jù)
}
void test01()
{
doWork(new Coffee);
cout << endl;
doWork(new Tea);
}
int main() {
test01();
system("pause");
return 0;
}
4、虛析構(gòu)函數(shù)和純虛析構(gòu)函數(shù)
(1)析構(gòu)函數(shù)也可通過virtual修飾而聲明為虛函數(shù)。
(2)只要虛基類的析構(gòu)函數(shù)聲明為虛函數(shù),由它派生的所有派生類的析構(gòu)函數(shù)也一定是虛函數(shù)。
(3)在使用多態(tài)時,如果子類中有屬性開辟到堆區(qū),那么父類指針在釋放時無法調(diào)用到子類的析構(gòu)代碼,為了解決這個問題,需要將父類中的析構(gòu)函數(shù)改為虛析構(gòu)函數(shù)或者純虛析構(gòu)函數(shù)。
①虛析構(gòu)函數(shù)或純虛析構(gòu)函數(shù)可達到通過父類指針釋放子類對象的目的。
②如果子類中沒有堆區(qū)數(shù)據(jù),可以不將析構(gòu)函數(shù)寫為虛析構(gòu)函數(shù)或純虛析構(gòu)函數(shù)。
③擁有純虛析構(gòu)函數(shù)(不是虛析構(gòu)函數(shù))的類也屬于抽象類,無法實例化對象。
(4)虛析構(gòu)函數(shù)和純虛析構(gòu)函數(shù)的語法:
virtual ~<類名>( ) ????//虛析構(gòu)函數(shù)的定義(類內(nèi))
{
????????<虛析構(gòu)函數(shù)體>
}
virtual ~<類名>( ) = 0; ?//純虛析構(gòu)函數(shù)的聲明(類內(nèi))
<類名>::~<類名>( ) ???//純虛析構(gòu)函數(shù)的定義(類外)
{
????????<純虛析構(gòu)函數(shù)體>
}
(5)舉例:
①例1:
#include<iostream>
using namespace std;
#include<string>
class Animal
{
public:
virtual void speak() = 0; //純虛函數(shù)
Animal()
{
cout << "Animal構(gòu)造函數(shù)調(diào)用" << endl;
}
virtual ~Animal() //父類指針在析構(gòu)的時候不會調(diào)用子類中的析構(gòu)函數(shù),子類可能會發(fā)生內(nèi)存泄漏,所以要給它改成虛析構(gòu)
{
cout << "Animal析構(gòu)函數(shù)調(diào)用" << endl;
}
//virtual ~Animal() = 0; 純虛析構(gòu)(需要有聲明以及具體實現(xiàn))
};
/*Animal::~Animal()
{
cout << "Animal純虛析構(gòu)函數(shù)調(diào)用" << endl;
}*/
class Cat :public Animal
{
public:
void speak()
{
cout << *m_Name << "小貓在說話" << endl;
}
Cat(string name)
{
cout << "Cat構(gòu)造函數(shù)調(diào)用" << endl;
m_Name = new string(name);
}
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat析構(gòu)函數(shù)調(diào)用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;
};
void test01()
{
Animal * animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
②例2:文章來源地址http://www.zghlxwxcb.cn/news/detail-832076.html
#include<iostream>
using namespace std;
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 * card, Memory * memory)
{
m_cpu = cpu;
m_card = card;
m_memory = memory;
}
void work()
{
m_cpu->calculate();
m_card->display();
m_memory->storage();
}
~Computer() //提供析構(gòu)函數(shù)釋放3個電腦零件
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_card != NULL)
{
delete m_card;
m_card = NULL;
}
if (m_memory != NULL)
{
delete m_memory;
m_memory = NULL;
}
}
private:
CPU * m_cpu;
VideoCard * m_card;
Memory * m_memory;
};
class IntelCPU :public CPU
{
public:
void calculate()
{
cout << "Inter的CPU開始計算了" << endl;
}
};
class IntelVideoCard :public VideoCard
{
public:
void display()
{
cout << "Inter的顯卡開始顯示了" << endl;
}
};
class IntelMemory :public Memory
{
public:
void storage()
{
cout << "Inter的內(nèi)存條開始存儲了" << endl;
}
};
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的內(nèi)存條開始存儲了" << endl;
}
};
void test01()
{
//第一臺電腦零件
CPU * intelCPU = new IntelCPU;
VideoCard * intelCard = new IntelVideoCard;
Memory * intelMemory = new IntelMemory;
//第一臺電腦
Computer * c1 = new Computer(intelCPU, intelCard, intelMemory);
c1->work();
delete c1;
cout << endl;
//第二臺電腦
Computer * c2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);
c2->work();
delete c2;
cout << endl;
//第三臺電腦
Computer * c3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);
c3->work();
delete c3;
cout << endl;
}
int main() {
test01();
system("pause");
return 0;
}
到了這里,關(guān)于C++從入門到精通 第九章(繼承和多態(tài))【下】的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!