欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解C++模板如何實現(xiàn)多態(tài)思想

 更新時間:2023年03月17日 14:22:50   作者:余識-  
這篇文章主要為大家詳細介紹了C++模板如何實現(xiàn)多態(tài)的相關(guān)資料,文中的示例代碼講解詳細,對我們深入了解C++有一定的幫助,感興趣的可以了解一下

一、模板與多態(tài)基礎(chǔ)

再進一步了解如何用模板來實現(xiàn)多態(tài)前,我們還是來看一看這兩個概念的基礎(chǔ)理解

1.模板

首先是模板,其主要用途在于讓我們程序員少寫代碼

比如像下面兩個函數(shù)類似的一系列函數(shù):

int add(int a, int b) {
    return a + b;
}
double add(double a, double b) {
    return a + b;
}

就可以用模板簡寫為:

template<class T>
T add(T a, T b) {
    return a + b;
}

使用的方式如下:

    add<int>(1,3);
    add<double>(1.1, 3.4);
    add<char>('s','a');

但由于C++編譯器可以自動推斷參數(shù)類型,所以中間的<int>是可以省略的

這里要注意一個非常重要的問題,雖然我們只寫了一個模板函數(shù),但實際上并不止有一個函數(shù)

比如這里我們用了三種類型的add函數(shù),那么編譯器就會為我們分別生成三個函數(shù)

也就是說,這是編譯器根據(jù)我們寫的模板。幫我們自動生成的函數(shù)

進一步來說,模板是完全給編譯器看的,并不會參與到最終的可執(zhí)行文件中

上面這一點便是模板的精髓!

為了更加直觀的理解,我們來看一下最終生成的三個函數(shù)的內(nèi)存地址:

#include<iostream>
template<class T>
T add(T a, T b) {
	return a + b;
}

int main() {
	printf("%p\n", add<int>);
	printf("%p\n", add<double>);
	printf("%p\n", add<char>);
}

這樣我們就能實際的看到最終確實是生成了三個函數(shù),因為三個函數(shù)的地址完全不同,分別就代表著三個版本的add函數(shù)

總結(jié)來說就是,模板并沒有減少最終的代碼量,它僅僅只是減少了我們程序員需要寫的代碼量

并且這個過程是在編譯期間就完成了的,這一點很重要!

之所以要用模板來實現(xiàn)多態(tài),就是看重了它是在編譯期間就完成的,而不會去影響最終的可執(zhí)行文件的執(zhí)行時間、大小

2.多態(tài)

然后便是多態(tài)了,多態(tài)是類中一個很重要的概念,其主要用途就是使得函數(shù)接口統(tǒng)一化

比如下面這段代碼:

#include <iostream> 
using namespace std;
class A {
public:
    virtual void area() {
        cout << "這是基類A" << endl;
    }
};
class B : public A {
public:
    void area()
    {
        cout << "這是子類B" << endl;
    }
};
class C : public A {
public:
    void area()
    {
        cout << "這是子類C" << endl;
    }
};
// 程序的主函數(shù)
int main()
{
    A* a;
    a = new B();
    a->area();
    a = new C();
    a->area();
    return 0;
}

邏輯并不復雜,就是B,C兩個類都繼承于A,

并且在基類中我們用到了關(guān)鍵字virtual 定義area為虛函數(shù),還在兩個子類里面都分別重寫了這個函數(shù)

因為B,C類都繼承于A類,所以我們可以用A類指針來接收B,C對象

從占用內(nèi)存上考慮,子類是繼承父類的,所以子類所占用的內(nèi)存量肯定大于或等于父類占用內(nèi)存,那么子類申請一塊內(nèi)存,賦值給父類的指針,父類就不可能會內(nèi)存訪問越界,而反過來,如果用子類指針存儲父類對象,由于子類訪問的內(nèi)存大于等于父類,就可能造成內(nèi)存訪問越界,因此一般禁止這樣使用

此時我們發(fā)現(xiàn),我們只用了一個A調(diào)用同一個函數(shù)area,卻可以完成兩個類的調(diào)用!

所以很多時候,當我們使用別人的提供給我們的類時,只要知道了它的父類有哪些函數(shù),那么其子類就必然有對應的函數(shù)

這可以極大方便類的管理、升級以及使用

雖然它的好處很多,但同樣也有壞處,那就是它是動態(tài)綁定函數(shù)的,依靠了一個叫做虛函數(shù)表的東西,導致其內(nèi)存占用更大,運行時間更長

比如上面的代碼我們就可以在調(diào)試窗口中看到其虛函數(shù)表:

就是這個名為 _vfptr的變量名稱,他就是指向虛函數(shù)表的函數(shù)指針,而虛函數(shù)表中就存有我們的虛函數(shù)

父類指針想要正確使用子類重寫的函數(shù),就必須要在這個虛函數(shù)表中進行遍歷查詢對應的函數(shù)地址

所以一旦你的類中有虛函數(shù),那么你的類就肯定會多出一個指針大小的內(nèi)存用于存儲虛函數(shù)表的地址,并且最終生成的可執(zhí)行文件也會變大很多字節(jié)

這取決于你的虛函數(shù)個數(shù),每多一個虛函數(shù),那么虛函數(shù)表就需要多一個指針大小的內(nèi)存來存儲

如果依舊不太懂的,可以自行在瀏覽器中搜索一下,有很多優(yōu)秀的文章對此有解釋

總結(jié)來說就是:使用傳統(tǒng)類的多態(tài)特性,會導致程序效率變低,最終生成的可執(zhí)行文件體積變大

原因就是它生成了虛函數(shù)表、虛函數(shù)指針,在程序運行過程中執(zhí)行查詢函數(shù)的操作

MFC就是因為大量使用的這種多態(tài),公共控件都繼承于基本窗口類,一般都有數(shù)十上百個虛函數(shù),所以這就導致即使你什么都沒干,一個MFC程序都至少有數(shù)兆大小,并且運行效率還較低

二、模板實現(xiàn)多態(tài)

了解了上面所說的兩個基本概念的優(yōu)缺點之后,現(xiàn)在我們就可以來到如何使用模板來實現(xiàn)多態(tài)了

因為模板就是編譯期間就完成的操作,如果讓模板來實現(xiàn)多態(tài),那么就不存在運行期間去遍歷虛函數(shù)表來找對應的函數(shù),也不需要開辟一個虛函數(shù)表來存儲虛函數(shù)地址

既能節(jié)約內(nèi)存,又能提高程序運行效率,是不是非常的完美!

下面我們就來看一看模板實現(xiàn)多態(tài)的基本流程

#include <iostream> 
using namespace std;
template<class T>
class A {
public:
    void Show() {
        T* p=static_cast<T*>(this);
        p->area();
    }
    void area() {
        cout << "這是基類A" << endl;
    }
};
class B : public A<B> {
public:
    void area()
    {
        cout << "這是子類B" << endl;
    }
};
class C : public A<C> {
public:
    void area()
    {
        cout << "這是子類C" << endl;
    }
};
// 程序的主函數(shù)
int main()
{
    B b;
    b.Show();
    C c;
    c.Show();
    return 0;
}

這里同樣是B,C兩個類都繼承自A類,但不同點就在于A類帶了一個模板變量

所以B,C類在繼承A的時候,就需要將自己這個類型傳遞進去

此時三個類都寫了area函數(shù),但只有基類寫了show方法對吧

但由于B,C類都是繼承自A類,所以它們其實也已經(jīng)含有了show方法

然后便是最重要的一步,在基類的show方法中,我將this指針轉(zhuǎn)化為T類型指針

static_cast與強制轉(zhuǎn)化基本等價,唯一很大一點的區(qū)別就是,強制轉(zhuǎn)換可以任意使用,比如B沒有繼承自A類,強制轉(zhuǎn)換仍然可以將兩者指針進行轉(zhuǎn)換,而static_cast無法轉(zhuǎn)換兩個毫不相干的東西,這樣就保證了傳入的類型是繼承自基類的,否則編譯會直接報錯

此時這里的p指針

T* p=static_cast<T*>(this);

實際就轉(zhuǎn)化為了調(diào)用者的指針,以B舉例子:

B b;
b.Show(); //調(diào)用Show方法后,完成了指針的轉(zhuǎn)換,指代的B,那么B調(diào)用area函數(shù),也就是調(diào)用自己重寫的area函數(shù)

如果現(xiàn)在再多出一個子類D繼承于A,但里面什么都沒有:

class D : A<D>{
}

那么當你使用D時:

D d;
d.Show(); //將指針轉(zhuǎn)換為D類型,由于D類型沒有重寫area方法,所以將調(diào)用繼承下來的基類area方法

同樣是一個show函數(shù),能夠卻能根據(jù)情況選擇出不同的函數(shù)調(diào)用,而且還是在編譯期間就完成了的

這便是模板實現(xiàn)多態(tài)的基本原理

三、實際應用

由于上面說的都是實現(xiàn)原理,例子比較奇怪,下面我們來直接看一看ATL中的代碼:

WTL是基于ATL之上開發(fā)的,而ATL庫則是vs開發(fā)環(huán)境中自帶,WTL庫需要自己去下載

可以輸入以下代碼

#include<atlwin.h>
class MyWindow :public CWindowImpl<MyWindow> 
{

};

然后右鍵速覽CWindowImpl類,接著在跳出的文件中搜索static_cast:

就能看到很多像上圖這樣的調(diào)用

1.先將this指針還原為子類

2.然后再調(diào)用對應的函數(shù)

3.如果子類重寫了這個函數(shù),那么就調(diào)用子類的函數(shù),否則就調(diào)用父類的函數(shù)

當然這并不完全如此,比如上圖中的那一出,是將其轉(zhuǎn)化為子類后,傳給某個函數(shù)進行處理

不過總體邏輯是一致的:在父類中操作子類,以實現(xiàn)靜態(tài)多態(tài)的目的

到此這篇關(guān)于深入理解C++模板如何實現(xiàn)多態(tài)思想的文章就介紹到這了,更多相關(guān)C++模板 多態(tài)思想內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論