版權(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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025Ha居間合同求盤
- 2025原材料買賣合同
- 2025合資經(jīng)營(yíng)企業(yè)合作合同
- 課題申報(bào)參考:馬克思恩格斯對(duì)“慈善資本化”的本質(zhì)批判及其當(dāng)代價(jià)值研究
- 科技驅(qū)動(dòng)下的創(chuàng)業(yè)與職業(yè)發(fā)展新模式
- 2024年電子式金屬、非金屬試驗(yàn)機(jī)項(xiàng)目資金申請(qǐng)報(bào)告代可行性研究報(bào)告
- 數(shù)學(xué)課堂中的師生互動(dòng)與思維能力培養(yǎng)
- 節(jié)能環(huán)保洗浴中心裝修技術(shù)解析
- (2020年編輯)新版GSP零售藥店質(zhì)量管理手冊(cè)
- 2025年滬科版選擇性必修3化學(xué)上冊(cè)階段測(cè)試試卷含答案
- 電纜擠塑操作手冊(cè)
- 浙江寧波鄞州區(qū)市級(jí)名校2025屆中考生物全真模擬試卷含解析
- 2024-2025學(xué)年廣東省深圳市南山區(qū)監(jiān)測(cè)數(shù)學(xué)三年級(jí)第一學(xué)期期末學(xué)業(yè)水平測(cè)試試題含解析
- IATF16949基礎(chǔ)知識(shí)培訓(xùn)教材
- 【MOOC】大學(xué)生創(chuàng)新創(chuàng)業(yè)知能訓(xùn)練與指導(dǎo)-西北農(nóng)林科技大學(xué) 中國(guó)大學(xué)慕課MOOC答案
- 勞務(wù)派遣公司員工考核方案
- 基礎(chǔ)生態(tài)學(xué)-7種內(nèi)種間關(guān)系
- 2024年光伏農(nóng)田出租合同范本
- 《阻燃材料與技術(shù)》課件 第3講 阻燃基本理論
- 2024-2030年中國(guó)黃鱔市市場(chǎng)供需現(xiàn)狀與營(yíng)銷渠道分析報(bào)告
- 新人教版九年級(jí)化學(xué)第三單元復(fù)習(xí)課件
評(píng)論
0/150
提交評(píng)論