《C++程序設(shè)計(jì)》課件第11章_第1頁(yè)
《C++程序設(shè)計(jì)》課件第11章_第2頁(yè)
《C++程序設(shè)計(jì)》課件第11章_第3頁(yè)
《C++程序設(shè)計(jì)》課件第11章_第4頁(yè)
《C++程序設(shè)計(jì)》課件第11章_第5頁(yè)
已閱讀5頁(yè),還剩120頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

第11章模板11.1模板概述11.2模板函數(shù)

11.3模板類11.4模板的多態(tài)

11.5高級(jí)編程本章小結(jié)習(xí)題

11.1模板概述模板是C++在20世紀(jì)90年代引進(jìn)的一個(gè)新概念,原本是為了對(duì)容器類(containerclasses)的支持,但是現(xiàn)在模板產(chǎn)生的效果已經(jīng)遠(yuǎn)非當(dāng)初所想象。簡(jiǎn)單地講,模板就是一種參數(shù)化(parameterized)的類或函數(shù),也就是類的形態(tài)(成員、方法、布局等)或者函數(shù)的形態(tài)(參數(shù)、返回值等)可以被參數(shù)改變。這里所說的參數(shù),不只是我們傳統(tǒng)函數(shù)中所說的數(shù)值形式的參數(shù),還可以是一種類型(實(shí)際上我們更多地會(huì)注意到使用類型作為參數(shù),而往往忽略使用數(shù)值作為參數(shù)的情況)。下面舉例說明模板的概念。例如,在C語(yǔ)言中,如果我們要比較兩個(gè)數(shù)的大小,常常會(huì)定義兩個(gè)宏:

#definemin(a,b)((a)>(b)?(b):(a))

#definemax(a,b)((a)>(b)?(a):(b))這樣就可以在程序中通過宏展開得到所求的結(jié)果:

returnmin(10,4);或

returnmin(5.3,18.6);

這兩個(gè)宏非常好用,但是在C++中,它們并不像在C中那樣受歡迎。宏因?yàn)闆]有類型檢查而不安全。例如,如果將代碼寫為min(a++,b--);?,則結(jié)果非你所愿,因此,宏在C++中被inline函數(shù)替代。如果將min/max改為函數(shù),這個(gè)函數(shù)又通常不能處理形參的類型以外的其他類型的參數(shù)。例如min()聲明為:

intmin(inta,intb);則它顯然不能處理float類型的參數(shù),但是原來的宏卻可以實(shí)現(xiàn)。也可以通過重載不同類型的min()函數(shù)來實(shí)現(xiàn)。實(shí)際上,C++對(duì)于這類可以抽象的算法提供了更好的辦法,即模板。下面是一個(gè)模板函數(shù)的例子:

template<classT>constT&min(constT&t1,constT&t2){

returnt1>t2?t2:t1;

}有了模板之后,可以像在C語(yǔ)言中使用min宏一樣來使用這個(gè)模板。例如:

returnmin(10,4);

returnmin(5.3,18.6)

這樣就獲得了一個(gè)類型安全的而又可以支持任意類型的min函數(shù),它顯然比min宏更實(shí)用。

當(dāng)然,上面這個(gè)例子只涉及了模板的一個(gè)方面,模板的作用遠(yuǎn)不只是用來替代宏。實(shí)際上,模板是泛化編程(GenericProgramming)的基礎(chǔ)。所謂泛化編程,就是對(duì)抽象的算法的編程。泛化是指一段代碼可以廣泛地適用于不同的數(shù)據(jù)類型,例如上面提到的min算法。11.2模板函數(shù)11.2.1模板函數(shù)的重載和普通函數(shù)一樣,模板函數(shù)也可以進(jìn)行重載,即相同的模板函數(shù)名稱可以具有不同的函數(shù)定義。因此,當(dāng)使用函數(shù)名稱進(jìn)行函數(shù)調(diào)用時(shí),C++編譯器就必須決定究竟要調(diào)用哪個(gè)候選函數(shù)。本節(jié)主要討論有關(guān)模板的重載問題。程序11.1描述了如何重載模板函數(shù)。

【程序11.1】

//求兩個(gè)int類型值中的最小值

inlineintconst&min(intconst&a,intconst&b)

{

returna<b?a:b;

}

//求兩個(gè)任意類型值中的最小值

template<typenameT>

inlineTconst&min(Tconst&a,Tconst&b)

{

returna<b?a:b;

}

//求三個(gè)任意類型值中的最小值

template<typenameT>

inlineTconst&min(Tconst&a,Tconst&b,Tconst&c)

{

returnmin(min(a,b),c);

}

intmain()

{

min(2,13,45); //調(diào)用三個(gè)參數(shù)的模板函數(shù)

min(2.0,13.0); //調(diào)用min<double>(通過實(shí)參演繹)

min('a','b'); //調(diào)用min<char>(通過實(shí)參演繹)

min(2,13); //調(diào)用有兩個(gè)int類型參數(shù)的普通函數(shù)

min<>(2,13);//調(diào)用min<int>(通過實(shí)參演繹)

min<double>(2,13);//調(diào)用min<double>(沒有實(shí)參演繹)

min('a',13.5); //調(diào)用int重載的普通函數(shù)

return0;

}如程序11.1所示,一個(gè)模板函數(shù)可以和一個(gè)同名的普通函數(shù)同時(shí)存在,而且模板函數(shù)還可以被實(shí)例化為這個(gè)普通函數(shù)。對(duì)于模板函數(shù)和同名的普通函數(shù),如果其他條件都相同,重載解析時(shí)先搜索普通函數(shù),如果找到就解除查找,并匹配找到的函數(shù),而不會(huì)從該模板產(chǎn)生出一個(gè)實(shí)例。第4個(gè)調(diào)用就是這樣的:

min(2,13)//兩個(gè)int類型參數(shù),與普通函數(shù)很匹配如果模板可以產(chǎn)生一個(gè)具有更好匹配的函數(shù)實(shí)例,那么將選擇模板。第2次和第3次調(diào)用就是例子:

min(2.0,13.0) //調(diào)用min<double>(通過實(shí)參演繹)

min('a','b'); //調(diào)用min<char>(通過實(shí)參演繹)還可以顯式地指定一個(gè)空的模板實(shí)參列表,這個(gè)語(yǔ)法告訴編譯器:只有模板才能匹配這個(gè)調(diào)用,而且所有的模板參數(shù)都應(yīng)該根據(jù)調(diào)用實(shí)參演繹出來:

min<>(2,13) //調(diào)用min<int>(通過實(shí)參演繹)模板不允許自動(dòng)類型轉(zhuǎn)換,但普通函數(shù)可以進(jìn)行自動(dòng)類型轉(zhuǎn)換,所以最后一個(gè)調(diào)用將使用普通函數(shù)('a',13.5都被轉(zhuǎn)換為int類型):

min('a',13.5) //對(duì)于不同類型的參數(shù),只有普通函數(shù)允許轉(zhuǎn)換程序11.2是在指針和普通的C字符串重載上面求最小值的模板。

【程序11.2】

#include<iostream>

#include<cstring>

#include<string>

//求兩個(gè)任意類型值中的最小值

template<typenameT>

inlineTconst&min(Tconst&a,Tconst&b)

{

returna<b?a:b;

}

//求兩個(gè)指針?biāo)笇?duì)象中的最小值

template<typenameT>

inlineT*const&min(T*const&a,T*const&b)

{

return*a<*b?a:b;

}

//求兩個(gè)C字符串中的最小值

inlinecharconst*const&min(charconst*const&a,charconst*const&b)

{

returnstd::strcmp(a,b)<0?a:b;

}

intmain()

{

inta=2;

intb=13;

::min(a,b); //min()求兩個(gè)int類型中的最小值

std::strings="what’s";

std::stringt="up";

::min(s,t); //min()求兩個(gè)std::string類型中的最小值

int*p1=&b;

int*p2=&a;

::min(p1,p2);//min()求兩個(gè)指針?biāo)笇?duì)象中的最小值

charconst*s1="Lyle";

charconst*s2="Dansy";

::min(s1,s2);//min()求兩個(gè)C字符串中的最小值

return0;

}注意,在所有重載的實(shí)現(xiàn)里,我們都是通過引用來傳遞實(shí)參的。一般而言,在重載模板函數(shù)的時(shí)候,應(yīng)該把改變限制在下面兩種情況:改變參數(shù)的數(shù)目或者直接指定模板參數(shù)。否則就可能出現(xiàn)錯(cuò)誤的結(jié)果。例如,對(duì)于原來使用的傳引用的min模板,用C-string類型進(jìn)行重載;但對(duì)于現(xiàn)在(程序11.3)基于C-string的min函數(shù),是通過傳值來傳遞參數(shù),那么就不能使用三個(gè)參數(shù)的min版本來對(duì)三個(gè)C-string求最小值,如下例所示。

【程序11.3】

#include<iostream>

#include<cstring>

#include<string>

//求兩個(gè)任意類型值中的最小值(通過傳引用調(diào)用)

template<typenameT>

inlineTconst&min(Tconst&a,Tconst&b)

{

returna<b?a:b;

}

//求兩個(gè)C字符串中的最小值(通過傳值調(diào)用)

inlinecharconst*min(charconst*a,charconst*b)

{

returnstd::strcmp(a,b)<0?a:b;

}

//求三個(gè)任意類型值中的最小值(通過傳引用調(diào)用)

template<typenameT>

inlineTconst&min(Tconst&a,Tconst&b,Tconst&c)

{

returnmin(min(a,b),c); //如果min(a,b)使用傳值調(diào)用,則這里會(huì)產(chǎn)生錯(cuò)誤

}

intmain()

{

::min(2,13,45); //正確

constchar*s1="what";

constchar*s2="is";

constchar*s3="up";

::min(s1,s2,s3); //錯(cuò)誤

return0;

}為什么我們對(duì)三個(gè)C-strings調(diào)用min()函數(shù)就出現(xiàn)錯(cuò)誤了呢?這是因?yàn)檎Z(yǔ)句:

returnmin(min(a,b),c);產(chǎn)生了一個(gè)錯(cuò)誤。對(duì)于C-string而言,這里的min(a,b)產(chǎn)生了一個(gè)新的臨時(shí)局部變量,該變量有可能會(huì)被外面的min()函數(shù)以傳引用的方式返回,而這將導(dǎo)致傳回?zé)o效的引用。對(duì)于復(fù)雜的重載解析規(guī)則而言,這只是產(chǎn)生非預(yù)期行為的代碼中的例子之一。例如,當(dāng)調(diào)用重載函數(shù)時(shí),調(diào)用結(jié)果就可能與該重載函數(shù)在此時(shí)是否可見有關(guān),但也可能無(wú)關(guān)。事實(shí)上,定義一個(gè)具有三個(gè)參數(shù)的min()版本,而且直到該定義處還沒有看到一個(gè)具有兩個(gè)int參數(shù)的重載min()版本的聲明,那么這個(gè)具有三個(gè)int實(shí)參的max()調(diào)用將會(huì)使用具有兩個(gè)參數(shù)的模板,而不會(huì)使用基于int的重載版本min(),如下所示:

//求兩個(gè)任意類型值中的最小值

template<typenameT>

inlineTconst&min(Tconst&a,Tconst&b)

{

returna<b?a:b;

}

//求三個(gè)任意類型值中的最小值

template<typenameT>

inlineTconst&min(Tconst&a,Tconst&b,Tconst&c)

{

returnmin(min(a,b),c); //這里的min(a,b)調(diào)用上面模板產(chǎn)生的模板實(shí)例,

} //因?yàn)榛趇nt類型的重載函數(shù)聲明得太晚

//求兩個(gè)int類型值中的最小值

inlineintconst&min(intconst&a,intconst&b)

{

returna<b?a:b;

}應(yīng)該牢記這條規(guī)則:函數(shù)的所有重載版本的聲明都應(yīng)該位于該函數(shù)被調(diào)用的位置之前。11.2.2模板函數(shù)的語(yǔ)法本節(jié)簡(jiǎn)述聲明模板和定義模板的方法以及模板函數(shù)常見的語(yǔ)法方面的問題。template<>是模板的標(biāo)志,在<>中是模板的參數(shù)部分。參數(shù)可以是類型,也可以是數(shù)值。例如:

template<classT,Tt>

classTemp{

public:

...

voidprint(){cout<<t<<endl;}

private:

Tt_;

};在這個(gè)聲明中,第一個(gè)參數(shù)是一個(gè)類型,第二個(gè)參數(shù)是一個(gè)數(shù)值。這里的數(shù)值必須是一個(gè)常量。例如針對(duì)上面的聲明:

Temp<int,10>temp; //合法

inti=10;

Temp<int,i>temp; //不合法

constintj=10;

Temp<int,j>temp; //合法

參數(shù)也可以有默認(rèn)值:

template<classT,classC=char>...

默認(rèn)值的規(guī)則與函數(shù)的默認(rèn)值一樣,如果一個(gè)參數(shù)有默認(rèn)值,則其后的每個(gè)參數(shù)都必須有默認(rèn)值。

參數(shù)的名字在整個(gè)模板的作用域內(nèi)有效,類型參數(shù)可以作為作用域內(nèi)變量的類型(例如上例中的Tt_),數(shù)值型參數(shù)可以參與計(jì)算,就像使用一個(gè)普通常數(shù)一樣(例如上例中的cout<<t<<endl)。模板有個(gè)值得注意的地方,就是它的聲明方式。與普通類一樣,有聲明為inline的,或者雖然沒有聲明為inline,但是函數(shù)體在類聲明中的才是inline函數(shù)。包含了模板類的頭文件中除了類接口之外,到處充斥著實(shí)現(xiàn)代碼,對(duì)用戶來說是不可讀的。為了能像傳統(tǒng)頭文件一樣,讓用戶盡量只看到接口,而不用看到實(shí)現(xiàn)方法,一般會(huì)將所有的方法實(shí)現(xiàn)部分放在一個(gè)后綴為?.i或者?.inl的文件中,然后在模板類的頭文件中包含這個(gè)?.i或者?.inl文件。例如:

//temp.h開始

template<classT>classTemp

{

public:

voidprint();

};

#include“temp.inl”

//temp.h結(jié)束

//temp.in1開始

template<classT>voidTemp<T>::print()

{

...

}

//temp.in1結(jié)束

通過這樣的變通,既滿足了語(yǔ)法的要求,也讓頭文件更加易讀。模板函數(shù)也是一樣。普通的類中,也可以有模板方法,例如:

classA

{

public:

template<classT>voidprint(constT&t){...}

voiddummy();

};

對(duì)于模板方法的要求與模板類的方法一樣,也需要與類聲明出現(xiàn)在一起。而這個(gè)類的其他方法,例如dummy()則沒有這樣的要求。對(duì)模板的語(yǔ)法檢查有一部分被延遲到使用時(shí)刻(類被定義,或者函數(shù)被調(diào)用),而不是像普通的類或者函數(shù)在被編譯器讀到的時(shí)候就會(huì)進(jìn)行語(yǔ)法檢查。因此,如果一個(gè)模板沒有被使用,那么即使它包含了語(yǔ)法的錯(cuò)誤,也會(huì)被編譯器忽略。與語(yǔ)法檢查相關(guān)的另一個(gè)問題是可以在模板中做一些假設(shè)。例如:

template<classT>classTemp

{

public:

Temp(constT&t):t(t){}

voidprint(){t.print();}

private:

Tt_;

};

在這個(gè)模板中,假設(shè)類型T是一個(gè)類,并且有一個(gè)print()方法(t.print())。在11.1節(jié)中的min模板其實(shí)也作了同樣的假設(shè),即假設(shè)T重載了操作符?'>'。因?yàn)檎Z(yǔ)法檢查被延遲,編譯器看到這個(gè)模板的時(shí)候,并不去關(guān)心類型T是否有print()方法,這些假設(shè)在模板被使用的時(shí)候才被編譯器檢查。只要定義中給出的類型滿足假設(shè),就可以通過編譯。11.2.3模板函數(shù)的使用

下面是一個(gè)返回兩個(gè)值中最大者的函數(shù)模板:

template<typenameT>

inlineTconst&max(Tconst&a,Tconst&b)

{

//如果a<b,返回b,否則返回a

returna<b?b:a;

}

這個(gè)模板定義了一個(gè)“返回兩個(gè)值中最大者”的函數(shù)族,這兩個(gè)值是通過函數(shù)參數(shù)a和b傳遞給函數(shù)模板的;而參數(shù)的類型還沒有確定,用模板參數(shù)T來代替。如例子中所示,模板參數(shù)必須用如下形式的語(yǔ)法來聲明:

template<comma-separated-list-of-parameters>程序11.4展示了如何使用max()函數(shù)模板。

【程序11.4】

#include<iostream>

#include<string>

#include"max.h"

usingstd::cout;

usingstd::endl;

usingstd::string;

intmain()

{

inti=42;

cout<<"max(7,i):"<<::max(7,i)<<endl;

oublef1=3.4;

doublef2=-6.7;

cout<<"max(f1,f2):"<<::max(f1,f2)<<endl;

strings1="mathematics";

strings2="math";

cout<<"max(s1,s2):"<<::max(s1,s2)<<endl;

return0;

}在上面的程序里,max()被調(diào)用了3次,而且調(diào)用實(shí)參每次都不相同:一次用兩個(gè)int,一次用兩個(gè)double,一次用兩個(gè)std::string。每一次都計(jì)算出兩個(gè)實(shí)參的最大值,而調(diào)用結(jié)果是產(chǎn)生如下的程序輸出:可以看到max()模板每次調(diào)用的前面都有域限定符?::,這是為了確認(rèn)調(diào)用的是全局名字空間中的max()。由于標(biāo)準(zhǔn)庫(kù)中也有一個(gè)std::max()模板,在某些情況下也可以被使用,因此有時(shí)還會(huì)產(chǎn)生二義性。通常而言,并不是把模板編譯成一個(gè)可以處理任何類型的單一實(shí)體,而是對(duì)于實(shí)例化模板參數(shù)的每種類型,都從模板產(chǎn)生出一個(gè)不同的實(shí)體。因此,針對(duì)三種類型中的每一種,max()都被編譯了一次。例如,max()的第一次調(diào)用:

inti=42;

…max(7,i)…使用了以int作為模板參數(shù)T的函數(shù)模板。因此,它具有調(diào)用如下代碼的語(yǔ)義:

inlineintconst&max(intconst&a,intconst&b)

{

//如果a<b返回b,否則返回a

returna<b?b:a;

}這種用具體類型代替模板參數(shù)的過程叫做實(shí)例化(instantiation)。它產(chǎn)生了一個(gè)模板的實(shí)例。在面向?qū)ο蟮某绦蛟O(shè)計(jì)里,實(shí)例和實(shí)例化這兩個(gè)概念通常會(huì)被用于不同的場(chǎng)合——但都是針對(duì)一個(gè)類的具體對(duì)象。由于本書敘述的是關(guān)于模板的內(nèi)容,因此在未做特別指定的情況下,我們所說的實(shí)例都指的是模板的實(shí)例。只要使用函數(shù)模板,(編譯器)就會(huì)自動(dòng)地引發(fā)這樣一個(gè)實(shí)例化過程。因此程序員并不需要額外請(qǐng)求模板的實(shí)例化。類似地,max()的其他調(diào)用也將為double和std::string實(shí)例化max模板,就像具有如下單獨(dú)聲明和實(shí)現(xiàn)一樣:

constdouble&max(doubleconst&,doubleconst&);

conststd::string&max(std::stringconst&,std::stringconst&);如果試圖基于一個(gè)不支持模板內(nèi)部所使用操作的類型實(shí)例化一個(gè)模板,那么將會(huì)導(dǎo)致一個(gè)編譯期錯(cuò)誤,例如:

std::complex<float>c1,c2; //不支持operator<

max(c1,c2); //編譯時(shí)出錯(cuò)于是,可以得出一個(gè)結(jié)論:模板被編譯了兩次,分別發(fā)生在實(shí)例化之前和實(shí)例化期間。在實(shí)例化之前,先檢查模板代碼本身,查看語(yǔ)法是否正確,在這里會(huì)發(fā)現(xiàn)錯(cuò)誤的語(yǔ)法,如遺漏分號(hào)等;在實(shí)例化期間,檢查模板代碼,查看是否所有的調(diào)用都有效,在這里會(huì)發(fā)現(xiàn)無(wú)效的調(diào)用,如該實(shí)例化類型不支持某些函數(shù)調(diào)用等。這給實(shí)際的模板處理帶來了一個(gè)很重要的問題:當(dāng)使用函數(shù)模板,并且引發(fā)模板實(shí)例化的時(shí)候,編譯器(在某個(gè)時(shí)刻)需要查看模板的定義。這就不同于普通函數(shù)中編譯和鏈接之間的區(qū)別,因?yàn)閷?duì)于普通函數(shù)而言,只需要該函數(shù)的聲明(即不需要定義),就可以順利地通過編譯。11.3模板類11.3.1模板類的創(chuàng)建及使用模板能直接支持通用型程序設(shè)計(jì),即采用類型作為參數(shù)的程序設(shè)計(jì)。C++中的模板機(jī)制能在定義函數(shù)和類時(shí)以類型作為參數(shù)。容器類就是一個(gè)具有這種特性的典型例子。它通常被用于管理某種特定類型的元素,使用模板類即可實(shí)現(xiàn)容器類,而不需要確定容器中元素的類型。本節(jié)介紹一下簡(jiǎn)單地使用Stack作為模板類的例子。與函數(shù)模板一樣,在同一個(gè)頭文件中聲明和定義模板類Stack<>如下:

#include<list>

#include<stdexcept>

#include<iostream>

usingnamespacestd;

template<classT>

classStack

{

private:

list<T>container; //對(duì)象集合

public:

voidpush(Tconst&); //壓棧

voidpop();

//出棧

Ttop()const; //返回棧頂對(duì)象

boolempty()const; //判斷棧是否為空

};

template<typenameT>

voidStack<T>::push(Tconst&obj)

{

container.push_back(obj); //把obj追加到容器末尾

}

template<typenameT>

voidStack<T>::pop()

{

if(container.empty())

{

throwout_of_range("Stack<>::pop():emptystack");

}

container.pop_back(); //刪除容器末尾對(duì)象

}

template<typenameT>

TStack<T>::top()const

{

if(elems.empty()) {

throwstd::out_of_range("Stack<>::top():emptystack");

}

returncontainer.back(); //返回容器末尾對(duì)象

}

template<typenameT>

voidStack<T>::empty()

{

container.empty(); //判斷容器是否為空

}上面的程序中,我們使用標(biāo)準(zhǔn)庫(kù)的模板類list<>來實(shí)現(xiàn)模板類Stack<>。模板類的聲明和函數(shù)模板的聲明很相似:在聲明之前,先聲明作為類型參數(shù)的標(biāo)識(shí)符,然后繼續(xù)使用T作為類型參數(shù)的標(biāo)識(shí)符。如下所示:

template<typenameT>

classStack

{

};這里,我們可以使用關(guān)鍵字typename來代替class:

template<typenameT>

classStack

{

};在模板類的內(nèi)部,T可以像其他任何類型一樣,用于聲明成員變量和成員函數(shù)的返回值類型。下面的例子中,聲明list的元素類型為T類型的,聲明push()是一個(gè)接收常量T引用類型參數(shù)的成員函數(shù),聲明top()是返回類型為T類型的成員函數(shù):

template<classT>

classStack

{

private:

list<T>container; //容器

public:

Stack();

//構(gòu)造函數(shù)

voidpush(Tconst&);

//壓棧

voidpop(); //出棧

Ttop()const; //返回棧頂對(duì)象

};這個(gè)模板類的類型是Stack<T>,其中T是模板參數(shù)。因此,當(dāng)在聲明中需要使用該類的類型時(shí),必須使用Stack<T>。例如,如果要聲明自己實(shí)現(xiàn)的拷貝構(gòu)造函數(shù)和復(fù)制運(yùn)算符,則應(yīng)這樣編寫:

template<classT>

classStack

{

Stack(Stack<T>const&); //拷貝構(gòu)造函數(shù)

Stack<T>&operator=(Stack<T>const&); //賦值運(yùn)算符

};但是,要使用類名而不是類的類型時(shí),就應(yīng)該只用Stack。譬如,當(dāng)指定類的名稱﹑類的構(gòu)造函數(shù)﹑析構(gòu)函數(shù)時(shí),就應(yīng)該使用Stack。為了定義模板類的成員函數(shù),必須指定該成員函數(shù)是一個(gè)函數(shù)模板,而且還需要使用這個(gè)模板類的完整類型限定符。因此,類型Stack<T>的成員函數(shù)push()的實(shí)現(xiàn)如下:

template<classT>

voidStack<T>::push(Tconst&obj)

{

container.push_back(obj); //把對(duì)象obj追加到容器末尾

}在上面的例子中,調(diào)用了對(duì)應(yīng)list的push_back()方法,它把傳入對(duì)象附加到該list的末端。為了使用模板類對(duì)象,必須顯式地指定模板實(shí)參。下面的例子展示了如何使用模板類Stack<>:

#include<iostream>

#include<string>

#include<cstdlib>

#include"stack1.h"

usingnamespacestd;

intmain()

{

try {

Stack<int>intStack; //容納int類型對(duì)象的棧

Stack<string>stringStack;//容納string類型對(duì)象的棧

//使用int類型的棧

intStack.push(7);

cout<<intStack.top()<<std::endl;

//使用string類型的棧

stringStack.push("hello");

cout<<stringStack.top()<<endl;

stringStack.pop();

stringStack.pop();

}

catch(std::exceptionconst&ex)

{

cerr<<"Exception:"<<ex.what()<<endl;

eturnEXIT_FAILURE;

//產(chǎn)生異常時(shí),返回錯(cuò)誤的程序狀態(tài)

}

}通過聲明類型Stack<int>,在模板類內(nèi)部就可以用int實(shí)例化T。因此,intStack是一個(gè)創(chuàng)建自Stack<int>的對(duì)象,它的元素存儲(chǔ)于list,且類型為int,對(duì)于所有被調(diào)用的成員函數(shù),都會(huì)實(shí)例化出基于int類型的函數(shù)代碼。類似地,如果聲明和使用Stack<std?::?string>,將會(huì)創(chuàng)建一個(gè)Stack<string>對(duì)象,它的元素存儲(chǔ)于list,且類型為string;而對(duì)于所有被調(diào)用的成員函數(shù),也會(huì)實(shí)例化出基于string的函數(shù)代碼。在上面的例子中,缺省構(gòu)造函數(shù)﹑push()和top()都被實(shí)例化了一個(gè)int版本和一個(gè)string版本,而pop()僅被實(shí)例化了一個(gè)string版本。另一方面,如果模板類中含有靜態(tài)成員,那么用來實(shí)例化的每種類型都會(huì)實(shí)例化這些靜態(tài)成員??梢耘c其他任何類型一樣地使用實(shí)例化后的模板類類型,只要它支持所調(diào)用的操作即可。模板與類繼承都可以讓代碼重用,都是對(duì)具體問題的抽象過程。但是它們抽象的側(cè)重點(diǎn)不同,模板側(cè)重于對(duì)算法的抽象,也就是說如果在解決一個(gè)問題的時(shí)候,需要固定的step1、step2…,就可以抽象為模板。而如果一個(gè)問題域中有很多相同的操作,但是這些操作并不能組成一個(gè)固定的序列,就可以用類繼承來解決問題。模板類的運(yùn)用方式,更多情況是直接使用,而不是作為基類。例如在使用STL提供的模板時(shí),通常直接使用,而不需要從模板庫(kù)中提供的模板再派生自己的類。但這不是絕對(duì)的,也是模板與類繼承之間的一點(diǎn)區(qū)別。模板雖然也是抽象的,但是它往往不需要通過派生來具體化。11.3.2迭代器的創(chuàng)建及使用

迭代器提供對(duì)一個(gè)容器中的對(duì)象的訪問方法,并且定義了容器中對(duì)象的范圍。迭代器就如同一個(gè)指針。事實(shí)上,C++?的指針也是一種迭代器。但是,迭代器不僅僅是指針,因此不能認(rèn)為它們一定具有地址值。例如,一個(gè)數(shù)組索引也可以認(rèn)為是一種迭代器。迭代器有各種不同的創(chuàng)建方法。程序可能把迭代器作為一個(gè)變量創(chuàng)建。一個(gè)STL容器類可能為了使用一個(gè)特定類型的數(shù)據(jù)而創(chuàng)建一個(gè)迭代器。作為指針,必須能夠使用?*?操作符類獲取數(shù)據(jù)。還可以使用其他數(shù)學(xué)操作符如?++。典型的?++?操作符用來遞增迭代器,以訪問容器中的下一個(gè)對(duì)象。如果迭代器到達(dá)了容器中最后一個(gè)元素的后面,則迭代器將變成past-the-end值。使用一個(gè)past-the-end值的指針來訪問對(duì)象是非法的,就好像使用NULL作為初始化的指針一樣。提示:STL不保證從一個(gè)迭代器可以抵達(dá)另一個(gè)迭代器。例如,當(dāng)對(duì)一個(gè)集合中的對(duì)象排序時(shí),如果在不同的結(jié)構(gòu)中指定了兩個(gè)迭代器,而第二個(gè)迭代器無(wú)法從第一個(gè)迭代器抵達(dá),此時(shí)程序注定要失敗。這是STL靈活性的一個(gè)代價(jià)。STL不保證檢測(cè)毫無(wú)道理的錯(cuò)誤。

1.迭代器的類型對(duì)于STL數(shù)據(jù)結(jié)構(gòu)和算法,可以使用以下五種迭代器:

(1)?InputIterators(輸入迭代器):提供對(duì)數(shù)據(jù)的只讀訪問。

(2)?OutputIterators(輸出迭代器):提供對(duì)數(shù)據(jù)的只寫訪問。

(3)?ForwardIterators(前向迭代器):提供讀/寫操作,并能向前推進(jìn)迭代器。

(4)?Bidirectionaliterators(雙向迭代器):提供讀/寫操作,并能向前和向后操作。

(5)RandomAccessIterators(隨機(jī)訪問迭代器):提供讀/寫操作,并能在數(shù)據(jù)中隨機(jī)移動(dòng)。盡管各種不同的STL實(shí)現(xiàn)細(xì)節(jié)方面有所不同,但仍可以將上面的迭代器想象為一種類繼承關(guān)系。從這個(gè)意義上說,下面的迭代是繼承自上面的迭代器。由于這種繼承關(guān)系,可以將一個(gè)Forward迭代器作為一個(gè)Output或Input迭代器使用。同樣,如果一個(gè)算法要求是一個(gè)Bidirectional迭代器,那么只能使用該種類型和隨機(jī)訪問迭代器。

2.指針迭代器正如下面的小程序顯示的,一個(gè)指針也是一種迭代器。該程序同樣顯示了STL的一個(gè)主要特性——它不只是能夠用于它自己的類類型,而且也能用于任何C或C++類型。Listing1.iterdemo.cpp顯示了如何把指針作為迭代器用于STL的find()算法來搜索普通的數(shù)組。

Listing1.iterdemo.cpp

#include<iostream.h>

#include<algorithm>

usingnamespacestd;

#defineSIZE100

intiarray[SIZE];

intmain()

{ iarray[20]=50; int*ip=find(iarray,iarray+SIZE,50); if(ip==iarray+SIZE)

cout<<"50notfoundinarray"<<endl;

else cout<<*ip<<"foundinarray"<<endl; return0;

}在引用了I/O流庫(kù)和STL算法頭文件(注意沒有?.h后綴)后,該程序告訴編譯器使用std名字空間。使用std名字空間的這行是可選的,因?yàn)閯h除該行對(duì)于這個(gè)小程序來說不會(huì)導(dǎo)致名字沖突。程序中定義了尺寸為SIZE的全局?jǐn)?shù)組。由于是全局變量,因此運(yùn)行時(shí)數(shù)組自動(dòng)初始化為零。下面的語(yǔ)句將在索引20位置處的元素設(shè)置為50,并使用find()算法來搜索值50:

iarray[20]=50;

int*ip=find(iarray,iarray+SIZE,50);

find()函數(shù)接受三個(gè)參數(shù),前兩個(gè)定義了搜索的范圍。由于C和C++數(shù)組等同于指針,因此表達(dá)式iarray指向數(shù)組的第一個(gè)元素;第二個(gè)參數(shù)iarray+SIZE等同于past-the-end值,也就是數(shù)組中最后一個(gè)元素的后面位置;第三個(gè)參數(shù)是待定位的值,也就是50。find()函數(shù)返回和前兩個(gè)參數(shù)相同類型的迭代器,這里是一個(gè)指向整數(shù)的指針ip。提示:必須記住STL使用模板。因此,STL函數(shù)自動(dòng)根據(jù)它們使用的數(shù)據(jù)類型來構(gòu)造。為了判斷find()是否成功,例子中測(cè)試ip和past-the-end值是否相等:

if(ip==iarray+SIZE)...如果表達(dá)式為真,則表示在搜索的范圍內(nèi)沒有指定的值;否則就是指向一個(gè)合法對(duì)象的指針,這時(shí)可以用下面的語(yǔ)句顯示:

cout<<*ip<<"foundinarray"<<endl;測(cè)試函數(shù)返回值和NULL是否相等是不正確的。不要像下面這樣使用:

int*ip=find(iarray,iarray+SIZE,50);

if(ip!=NULL)... //錯(cuò)誤當(dāng)使用STL函數(shù)時(shí),只能測(cè)試ip是否和past-the-end值相等。盡管在本例中ip是一個(gè)C++指針,但其用法也必須符合STL迭代器的規(guī)則。3.容器迭代器盡管C++指針也是迭代器,但用得更多的是容器迭代器。容器迭代器的用法和iterdemo.cpp一樣,但和將迭代器申明為指針變量不同的是,可以使用容器類方法來獲取迭代器對(duì)象。兩個(gè)典型的容器類方法是begin()和end(),它們?cè)诖蠖鄶?shù)容器中表示整個(gè)容器范圍。其他一些容器還使用rbegin()和rend()方法提供反向迭代器,以按反向順序指定對(duì)象范圍。下面的程序創(chuàng)建了一個(gè)矢量容器(STL的和數(shù)組等價(jià)的對(duì)象),并使用迭代器在其中搜索。

Listing2.vectdemo.cpp

#include<iostream.h>

#include<algorithm>

#include<vector>

usingnamespacestd;

vector<int>intVector(100);

voidmain()

{

intVector[20]=50;

vector<int>::iteratorintIter= find(intVector.begin(),intVector.end(),50);

if(intIter!=intVector.end())

cout<<"Vectorcontainsvalue"<<*intIter<<endl;

else cout<<"Vectordoesnotcontain50"<<endl;

}可用下面的方法顯示搜索到的數(shù)據(jù):

cout<<"Vectorcontainsvalue"<<*intIter<<endl;

4.常量迭代器和指針一樣,可以給一個(gè)迭代器賦值。例如,首先申明一個(gè)迭代器:

vector<int>::iteratorfirst;該語(yǔ)句創(chuàng)建了一個(gè)vector<int>類的迭代器。下面的語(yǔ)句將該迭代器設(shè)置到intVector的第一個(gè)對(duì)象,并將它指向的對(duì)象值設(shè)置為123:

first=intVector.begin();*first=123;這種賦值對(duì)于大多數(shù)容器類都是允許的,除了只讀變量。為了防止錯(cuò)誤賦值,可以申明迭代器為:

constvector<int>::iteratorresult;

result=find(intVector.begin(),intVector.end(),value);

if(result!=intVector.end())

*result=123;11.4模板的多態(tài)11.4.1模板類的繼承模板類與普通類一樣有基類,也同樣可以有派生類。它的基類和派生類既可以是模板類,也可以不是模板類。模板類也具備所有與繼承相關(guān)的特點(diǎn),但有一些值得注意的地方。

假設(shè)有如下類關(guān)系:

template<classT>classA{...};

A<int>aint;

A<double>adouble;則aint和adouble并非A的派生類,甚至可以說根本不存在A這個(gè)類,只有A<int>和A<double>這兩個(gè)類。這兩個(gè)類沒有共同的基類,因此不能通過類A來實(shí)現(xiàn)多態(tài)。如果希望對(duì)這兩個(gè)類實(shí)現(xiàn)多態(tài),正確的類層次應(yīng)該是:

classAbase{...};

template<classT>classA:publicAbase{...};

A<int>aint;

A<double>adouble;也就是說,在模板類之上增加了一個(gè)抽象的基類。注意,這個(gè)抽象基類是一個(gè)普通類,而非模板。再來看下面的類關(guān)系:

template<inti>classA{...};

A<10>a10;

A<5>a5;在這種情況下,模板參數(shù)是一個(gè)數(shù)值,而不是一個(gè)類型。盡管如此,a10和a5仍然沒有共同的基類。這與用類型作模板參數(shù)是一樣的。11.4.2模板類多態(tài)用C++?實(shí)現(xiàn)多態(tài)的常用方法是通過繼承和虛函數(shù),但是使用模板同樣可以實(shí)現(xiàn)多態(tài)。使用繼承和虛函數(shù)來實(shí)現(xiàn)多態(tài)(動(dòng)態(tài)的多態(tài))存在以下幾個(gè)設(shè)計(jì)上的問題:

(1)增加了復(fù)雜度;

(2)增加了代碼大小以及程序運(yùn)行時(shí)間;

(3)降低了程序的靈活性。使用模板來實(shí)現(xiàn)多態(tài)(靜態(tài)的多態(tài))則可以解決上述設(shè)計(jì)問題。

1.使用繼承和虛函數(shù)來實(shí)現(xiàn)多態(tài)使用繼承和虛函數(shù)實(shí)現(xiàn)多態(tài)的過程如下:

(1)識(shí)別抽象概念。

(2)在抽象基類中將共有方法聲明為虛函數(shù)。

(3)在各個(gè)派生類中實(shí)現(xiàn)這些共有方法。例如:

classFile

{

public: virtualvoidOpen()=0;

virtualvoidSave()=0; virtualvoidSaveAs(String&)=0; virtualvoidClose()=0;

};

classTextFile:publicFile

{

public: voidOpen(); voidSave(); voidSaveAs(String&); voidClose();

};

classImageFile:publicFile

{

public: voidClose(); voidSave(); voidSaveAs(String&); voidClose();

};

//通過菜單項(xiàng)打開文件

voidmenu_open_file(File*f_ptr)

{

f_ptr->Open();

...

}可以如下運(yùn)用上述代碼:

File*file_ptr=newTextFile();

menu_open_file(file_ptr); //打開文本文件

...

file_ptr=newImageFile();

menu_open_file(file_ptr);

//打開圖片文件總結(jié):

(1)在上面這個(gè)實(shí)例中,F(xiàn)ile就是我們識(shí)別的抽象概念,在此將其定義為一個(gè)抽象基類。

(2)?Open、Save、SaveAs、Close是共有方法,并在File類中被聲明為純虛函數(shù)。

(3)?File抽象概念的具體實(shí)現(xiàn)是imagefile和textfile,在此為其提供了具體的實(shí)現(xiàn)。

(4)?Open、Save、SaveAs、Close等菜單操作將會(huì)調(diào)用這里的具體實(shí)現(xiàn)。

(5)如果用戶選擇了打開文件菜單項(xiàng),該菜單項(xiàng)的實(shí)現(xiàn)函數(shù)將會(huì)根據(jù)所需打開文件的不同類型調(diào)用不同的實(shí)現(xiàn)。

2.使用模板來實(shí)現(xiàn)多態(tài)如果使用模板來實(shí)現(xiàn)多態(tài),則不用在基類中聲明共有的方法,但是需要在程序中隱式聲明它們。下面使用模板來實(shí)現(xiàn)上面的實(shí)例:

classTextFile

{

voidOpen();

};

classImageFile

{

voidOpen();

};

//使用菜單項(xiàng)來打開文件

template<typenameT>voidmenu_open_file(Tfile)

{

file.open();

}

對(duì)上述代碼的運(yùn)用為:

TextFiletxt_file;

ImageFileimg_file;

menu_open_file(txt_file);

//打開文本文件

menu_open_file(img_file);

//打開圖片文件總結(jié):

(1)只需定義具體的類,如textfile和imagefile,而不用定義抽象類。

(2)將使用這些類的函數(shù)定義為模板,如menu_open_file。

(3)在模板中,不必使用指針及引用。

3.使用動(dòng)態(tài)的多態(tài)(利用繼承和虛函數(shù))存在的問題

(1)時(shí)間及內(nèi)存使用上的開銷較大。使用動(dòng)態(tài)的多態(tài)不能很好地實(shí)現(xiàn)容器類,因?yàn)檫@種方法既耗時(shí)間又耗內(nèi)存。利用模板則不會(huì)有這個(gè)問題。C++的標(biāo)準(zhǔn)模板庫(kù)就是一個(gè)很好的例子。下面這個(gè)容器類是用繼承來實(shí)現(xiàn)的。在C++中加入模板之前,這種方法是最常用的。

classcontainer

{

public: virtualvoidadd(); virtualvoidremove(); virtualvoidprint();

};

classlist:container

{ ...

};

classvector:container

{

...

};

voidSort(container&con)

{

...

}

voidSearch(container&con)

{

...

}

(2)使用繼承來實(shí)現(xiàn)多態(tài)會(huì)降低程序的靈活性。對(duì)基類接口中的共有方法進(jìn)行增、刪、改是一件既費(fèi)事又容易出錯(cuò)的事情,有時(shí)還會(huì)引起一系列其他的問題。如果想在接口上有更大的靈活性,更好的方法是使用模板。例如:

classFile

{

public: virtualvoidOpen()=0; virtualvoidSave()=0; virtualvoidSaveAs(String&file_name)=0;

};

classTextFile:publicFile

{

public: voidOpen(); voidSave(); voidSaveAs(String&file_name);

};

classImageFile:publicFile

{

public: voidOpen();

voidSave(); voidSaveAs(String&file_name);

};

classBinaryFile:publicFile

{

public: voidOpen(); voidSave(); voidSaveAs(String&file_name);

};假定想向上面這個(gè)文件抽象中添加打印功能,但這里的問題是不能向一個(gè)二進(jìn)制文件中添加打印功能,因?yàn)椴辉试S打印一個(gè)二進(jìn)制文件。一種解決方法是使用RTTI(運(yùn)行時(shí)類型信息)。但這種方法也不好,因?yàn)樗枰獙?duì)現(xiàn)有的代碼做出很多改變。否則,必須將這些類分為兩類:可打印的和不可打印的。此時(shí),可使用模板作為解決之道,如下所示:

classTextFile

{

public: voidOpen(); voidSave();

voidSaveAs(String&file_name); voidPrint();

};

classImageFile

{

public: voidOpen(); voidSave(); voidSaveAs(String&file_name); voidPrint();

};

classBinaryFile

{

public: voidOpen(); voidSave(); voidSaveAs(String&file_name); //這里沒有提供Print函數(shù)

};

template<typenameT>

voidOn_Open(Tfile)

{ file.Open();

}

template<typenameT>

voidOn_Save(Tfile)

{

file.Save();

}

template<typenameT>

voidOn_Print(Tfile)

{

file.Print();

}

TextFiletxt_file;

ImageFileimg_file;

BinaryFilebin_file;

On_Print(txt_file);

On_Print(img_file);

On_Print(bin_file);

//這里編譯時(shí)將會(huì)出錯(cuò)

//對(duì)二進(jìn)制文件的打印將會(huì)在編譯時(shí)出現(xiàn)錯(cuò)誤

4.動(dòng)態(tài)的多態(tài)與靜態(tài)的多態(tài)的比較動(dòng)態(tài)的多態(tài)(基于虛函數(shù)的方法)相對(duì)于靜態(tài)的多態(tài)(基于模板的方法)有以下優(yōu)勢(shì):

(1)可執(zhí)行程序的代碼相對(duì)較小。

(2)不需公布源碼。靜態(tài)的多態(tài)(基于模板的方法)相對(duì)于動(dòng)態(tài)的多態(tài)(基于虛函數(shù)的方法)又有以下優(yōu)勢(shì):

(1)類型安全。

(2)執(zhí)行速度更快。

(3)不必在基類中聲明公共接口。

(4)低耦合,因此提高了重用性??傊?,這兩種方法都各有優(yōu)劣,總體上用基于模板的方法來實(shí)現(xiàn)多態(tài)效果更好。通常,可根據(jù)可重用性、靈活性以及所需的性能等目標(biāo)來選擇具體的方法。11.5高級(jí)編程11.5.1動(dòng)多態(tài)設(shè)計(jì)(DynamicPolymorphism)起初,C++只是通過繼承機(jī)制與虛擬函數(shù)的結(jié)合運(yùn)用來支持多態(tài)。這種背景下的多型設(shè)計(jì)藝術(shù)是:在彼此相關(guān)的objecttypes之間確認(rèn)一套共通能力,并將其聲明為某共通基礎(chǔ)類別(CommonBaseClass)的一套虛擬函數(shù)接口。這種設(shè)計(jì)最具代表性的例子就是一個(gè)管理若干幾何形狀的程序,這些幾何形狀可以以某種方式著色(例如在屏幕上著色)。在這樣的程序中,我們可以定義一個(gè)所謂的抽象基礎(chǔ)類別(AbstractBaseClass,ABC)GeoObj,在其中聲明適用于幾何對(duì)象的一些共通操作(operations)和共通屬性(properties),每一個(gè)針對(duì)特定幾何對(duì)象而設(shè)計(jì)的具象類別(ConcreteClass)都衍生自GeoObj(見圖11.1)。圖11.1多型(polymorphism)通過繼承(inheritance)來實(shí)現(xiàn)程序如下:

#include"coord.h"

//針對(duì)幾何對(duì)象而設(shè)計(jì)的共通抽象基礎(chǔ)類別(CommonAbstractBaseClass)GeoObj

classGeoObj

{

public: //繪制幾何對(duì)象

virtualvoiddraw()const=0; //傳回幾何對(duì)象的重心(centerofgravity) virtualCoordcenter_of_gravity()const=0; //...

};

//具象幾何類別(concretegeometricclass)Circle

//衍生自GeoObj

classCircle:publicGeoObj

{

public: virtualvoiddraw()const; virtualCoordcenter_of_gravity()const; //...

};

//具象幾何類別Line

//衍生自GeoObj

classLine:publicGeoObj

{

public: virtualvoiddraw()const; virtualCoordcenter_of_gravity()const; //...

};

//...建立具象對(duì)象(concreteobjects)之后,客戶端程序代碼可以通過指向基礎(chǔ)類別的references或pointers來操縱這些對(duì)象,這會(huì)啟動(dòng)虛擬函數(shù)分派機(jī)制(virtualfunctiondispatchmechanism)。當(dāng)通過一個(gè)指向基礎(chǔ)類別之子對(duì)象(subobject)的references或pointers來調(diào)用某虛擬函數(shù)時(shí),會(huì)調(diào)用被指涉(refered)的那個(gè)特定具象物件的相應(yīng)成員函數(shù)。以本例而言,具體程序代碼大致描繪如下:

#include"dynahier.h"

#include<vector>

//繪制任何GeoObj

voidmyDraw(GeoObjconst&obj)

{ obj.draw(); //根據(jù)對(duì)象的類型調(diào)用draw()

}

//處理兩個(gè)GeoObjs重心之間的距離

Coorddistance(GeoObjconst&x1,GeoObjconst&x2)

{ Coordc=x1.center_of_gravity()-

x2.center_of_gravity(); returnc.abs(); //傳回坐標(biāo)絕對(duì)值

}

//繪出GeoObjs異質(zhì)群集(heterogeneouscollection)

voiddrawElems(std::vector<GeoObj*>const&elems)

{

for(unsignedi=0;i<elems.size();++i)

{ elems[i]->draw(); //根據(jù)對(duì)象的類型調(diào)用draw()

}

}

intmain()

{ Linel; Circlec,c1,c2;

myDraw(l);//myDraw(GeoObj&)=>Line::draw() myDraw(c);

//myDraw(GeoObj&)=>Circle::draw() distance(c1,c2);/distance(GeoObj&,GeoObj&) distance(l,c);/distance(GeoObj&,GeoObj&) std::vector<GeoObj*>coll; //異質(zhì)群集

coll.push_back(&l); //插入一個(gè)line coll.push_back(&c); //插入一個(gè)circle drawElems(coll); //繪出不同的種類

return0;}上述程序的關(guān)鍵性多型接口元素是draw()和center_of_gravity(),兩者都是虛函數(shù)。程序示范了它們?cè)趍yDraw()、distance()和drawElems()函數(shù)內(nèi)被使用的情況。由于這三個(gè)函數(shù)使用共通基礎(chǔ)類別GeoObj作為表達(dá)手段,因而無(wú)法在編譯期決定使用哪一個(gè)版本的draw()或center_of_gravity()。然而在執(zhí)行期,調(diào)用虛擬函數(shù)的那個(gè)對(duì)象的完整動(dòng)態(tài)類型會(huì)被取得,以便對(duì)調(diào)用語(yǔ)句進(jìn)行分派(dispatch)。于是,根據(jù)幾何對(duì)象的實(shí)際類型,程序得以完成適當(dāng)操作:如果對(duì)一個(gè)Line對(duì)象調(diào)用myDraw(),函數(shù)內(nèi)的obj.draw()就調(diào)用Line::draw();對(duì)Circle物件調(diào)用的則是Circle::draw()。同樣道理,對(duì)distance()而言,調(diào)用的將是與自變量物件相應(yīng)的那個(gè)center_of_gravity()。動(dòng)態(tài)多型最引人注目的特性是處理異質(zhì)對(duì)象群集(heterogeneouscollectionsofobjects)的能力。由drawElems()函數(shù)可以看出,elems[i]->draw()能根據(jù)目前正被處理的元素類型,調(diào)用不同的成員函數(shù)。11.5.2靜多態(tài)設(shè)計(jì)(StaticPolymorphism)

templates也可以用來實(shí)現(xiàn)多型,然而它們并不依賴分解及抽取baseclasses共通行為。在這里,共通性是指應(yīng)用程序所提供的不同幾何形狀,必須以共通語(yǔ)法支持其操作(也就是說,相關(guān)函數(shù)必須同名)。具象類別之間彼此獨(dú)立定義(見圖11.2)。一旦templates被具象類別實(shí)例化,便可獲得(被賦予)多型的威力。圖11.2多型(polymorphism)通過模板(templates)來實(shí)現(xiàn)例如前一節(jié)中的函數(shù)myDraw():

voidmyDraw(GeoObjconst&obj) //GeoObj是抽象基礎(chǔ)類別

{

obj.draw();

}可設(shè)想改寫如下:

template<typenameGeoObj>

voidmyDraw(GeoObjconst&obj) //GeoObj是模板參數(shù)

{

obj.draw();

}比較前后兩份myDraw()實(shí)作碼,可以得出一個(gè)結(jié)論:兩者的主要區(qū)別在于GeoObj是個(gè)模板參數(shù)而非一個(gè)共通基礎(chǔ)類(commonbaseclass)。然而在此表象背后,還有一些根本的區(qū)別。比方說,如果使用動(dòng)態(tài)多型,執(zhí)行期只會(huì)有一個(gè)myDraw()函數(shù),但如果使用template,則會(huì)有不同的函數(shù),如myDraw<Line>()和myDraw<Circle>()。下面使用靜態(tài)多型機(jī)制改寫前一節(jié)的例子。首先不再使用幾何類別階層體系,而是編寫若干個(gè)獨(dú)立的幾何類別:

#include"coord.h“

//具象的幾何類別Circle

//不衍生自任何類別

classCircle

{

public: voiddraw()const; Coordcenter_of_gravity()const; //...

};

//具象的幾何類別Line

//不衍生自任何類別

classLine

{

public: voiddraw()const; Coordcenter_of_gravity()const; //...

};

//...現(xiàn)在,這些classes的應(yīng)用程序看起來像這樣:

#include"statichier.h"

#include<vector>

//繪出任何給定的GeoObj

template<typenameGeoObj>

voidmyDraw(GeoObjconst&obj)

{ obj.draw();

//根據(jù)對(duì)象的類型調(diào)用draw()

}

//處理兩個(gè)GeoObjs重心之間的距離

template<typenameGeoObj1,typenameGeoObj2>

Coorddistance(GeoObj

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論