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

下載本文檔

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

文檔簡介

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

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

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

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

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

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

returnmin(10,4);或

returnmin(5.3,18.6);

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

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

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

returnt1>t2?t2:t1;

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

returnmin(10,4);

returnmin(5.3,18.6)

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

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

【程序11.1】

//求兩個int類型值中的最小值

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

{

returna<b?a:b;

}

//求兩個任意類型值中的最小值

template<typenameT>

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

{

returna<b?a:b;

}

//求三個任意類型值中的最小值

template<typenameT>

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

{

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

}

intmain()

{

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

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

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

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

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

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

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

return0;

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

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

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

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

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

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

【程序11.2】

#include<iostream>

#include<cstring>

#include<string>

//求兩個任意類型值中的最小值

template<typenameT>

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

{

returna<b?a:b;

}

//求兩個指針所指對象中的最小值

template<typenameT>

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

{

return*a<*b?a:b;

}

//求兩個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()求兩個int類型中的最小值

std::strings="what’s";

std::stringt="up";

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

int*p1=&b;

int*p2=&a;

::min(p1,p2);//min()求兩個指針所指對象中的最小值

charconst*s1="Lyle";

charconst*s2="Dansy";

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

return0;

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

【程序11.3】

#include<iostream>

#include<cstring>

#include<string>

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

template<typenameT>

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

{

returna<b?a:b;

}

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

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

{

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

}

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

template<typenameT>

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

{

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

}

intmain()

{

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

constchar*s1="what";

constchar*s2="is";

constchar*s3="up";

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

return0;

}為什么我們對三個C-strings調(diào)用min()函數(shù)就出現(xiàn)錯誤了呢?這是因為語句:

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

//求兩個任意類型值中的最小值

template<typenameT>

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

{

returna<b?a:b;

}

//求三個任意類型值中的最小值

template<typenameT>

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

{

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

} //因為基于int類型的重載函數(shù)聲明得太晚

//求兩個int類型值中的最小值

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

{

returna<b?a:b;

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

template<classT,Tt>

classTemp{

public:

...

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

private:

Tt_;

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

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

inti=10;

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

constintj=10;

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

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

template<classT,classC=char>...

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

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

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

classA

{

public:

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

voiddummy();

};

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

template<classT>classTemp

{

public:

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

voidprint(){t.print();}

private:

Tt_;

};

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

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

template<typenameT>

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

{

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

returna<b?b:a;

}

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

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

inti=42;

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

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

{

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

returna<b?b:a;

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

constdouble&max(doubleconst&,doubleconst&);

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

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

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

#include<list>

#include<stdexcept>

#include<iostream>

usingnamespacestd;

template<classT>

classStack

{

private:

list<T>container; //對象集合

public:

voidpush(Tconst&); //壓棧

voidpop();

//出棧

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

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(); //刪除容器末尾對象

}

template<typenameT>

TStack<T>::top()const

{

if(elems.empty()) {

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

}

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

}

template<typenameT>

voidStack<T>::empty()

{

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

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

template<typenameT>

classStack

{

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

template<typenameT>

classStack

{

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

template<classT>

classStack

{

private:

list<T>container; //容器

public:

Stack();

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

voidpush(Tconst&);

//壓棧

voidpop(); //出棧

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

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

template<classT>

classStack

{

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

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

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

template<classT>

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

{

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

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

#include<iostream>

#include<string>

#include<cstdlib>

#include"stack1.h"

usingnamespacestd;

intmain()

{

try {

Stack<int>intStack; //容納int類型對象的棧

Stack<string>stringStack;//容納string類型對象的棧

//使用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)生異常時,返回錯誤的程序狀態(tài)

}

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

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

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

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

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

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

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

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

2.指針迭代器正如下面的小程序顯示的,一個指針也是一種迭代器。該程序同樣顯示了STL的一個主要特性——它不只是能夠用于它自己的類類型,而且也能用于任何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流庫和STL算法頭文件(注意沒有?.h后綴)后,該程序告訴編譯器使用std名字空間。使用std名字空間的這行是可選的,因為刪除該行對于這個小程序來說不會導致名字沖突。程序中定義了尺寸為SIZE的全局數(shù)組。由于是全局變量,因此運行時數(shù)組自動初始化為零。下面的語句將在索引20位置處的元素設(shè)置為50,并使用find()算法來搜索值50:

iarray[20]=50;

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

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

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

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

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

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

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.常量迭代器和指針一樣,可以給一個迭代器賦值。例如,首先申明一個迭代器:

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

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

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)的特點,但有一些值得注意的地方。

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

template<classT>classA{...};

A<int>aint;

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

classAbase{...};

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

A<int>aint;

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

template<inti>classA{...};

A<10>a10;

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

(1)增加了復雜度;

(2)增加了代碼大小以及程序運行時間;

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

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

(1)識別抽象概念。

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

(3)在各個派生類中實現(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();

};

//通過菜單項打開文件

voidmenu_open_file(File*f_ptr)

{

f_ptr->Open();

...

}可以如下運用上述代碼:

File*file_ptr=newTextFile();

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

...

file_ptr=newImageFile();

menu_open_file(file_ptr);

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

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

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

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

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

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

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

classTextFile

{

voidOpen();

};

classImageFile

{

voidOpen();

};

//使用菜單項來打開文件

template<typenameT>voidmenu_open_file(Tfile)

{

file.open();

}

對上述代碼的運用為:

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.使用動態(tài)的多態(tài)(利用繼承和虛函數(shù))存在的問題

(1)時間及內(nèi)存使用上的開銷較大。使用動態(tài)的多態(tài)不能很好地實現(xiàn)容器類,因為這種方法既耗時間又耗內(nèi)存。利用模板則不會有這個問題。C++的標準模板庫就是一個很好的例子。下面這個容器類是用繼承來實現(xiàn)的。在C++中加入模板之前,這種方法是最常用的。

classcontainer

{

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

};

classlist:container

{ ...

};

classvector:container

{

...

};

voidSort(container&con)

{

...

}

voidSearch(container&con)

{

...

}

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

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);

};假定想向上面這個文件抽象中添加打印功能,但這里的問題是不能向一個二進制文件中添加打印功能,因為不允許打印一個二進制文件。一種解決方法是使用RTTI(運行時類型信息)。但這種方法也不好,因為它需要對現(xiàn)有的代碼做出很多改變。否則,必須將這些類分為兩類:可打印的和不可打印的。此時,可使用模板作為解決之道,如下所示:

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);

//這里編譯時將會出錯

//對二進制文件的打印將會在編譯時出現(xiàn)錯誤

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

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

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

(1)類型安全。

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

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

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

#include"coord.h"

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

classGeoObj

{

public: //繪制幾何對象

virtualvoiddraw()const=0; //傳回幾何對象的重心(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; //...

};

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

#include"dynahier.h"

#include<vector>

//繪制任何GeoObj

voidmyDraw(GeoObjconst&obj)

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

}

//處理兩個GeoObjs重心之間的距離

Coorddistance(GeoObjconst&x1,GeoObjconst&x2)

{ Coordc=x1.center_of_gravity()-

x2.center_of_gravity(); returnc.abs(); //傳回坐標絕對值

}

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

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

{

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

{ elems[i]->draw(); //根據(jù)對象的類型調(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); //插入一個line coll.push_back(&c); //插入一個circle drawElems(coll); //繪出不同的種類

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

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

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

{

obj.draw();

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

template<typenameGeoObj>

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

{

obj.draw();

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

#include"coord.h“

//具象的幾何類別Circle

//不衍生自任何類別

classCircle

{

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

};

//具象的幾何類別Line

//不衍生自任何類別

classLine

{

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

};

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

#include"statichier.h"

#include<vector>

//繪出任何給定的GeoObj

template<typenameGeoObj>

voidmyDraw(GeoObjconst&obj)

{ obj.draw();

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

}

//處理兩個GeoObjs重心之間的距離

template<typenameGeoObj1,typenameGeoObj2>

Coorddistance(GeoObj

溫馨提示

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

評論

0/150

提交評論