抱著本厚厚的《C++標准庫》讀了幾天,想想也該寫點關於用法的總結,一來怕今後容易忘記,二來將書上的事例重新敲一遍,鞏固對程序庫相關知識的了解。今天開第一篇,以後不固定更新。當然,筆者所讀為該書為基於C++98的第一版,已有一定的年代感,不過雖然C++11的推出已有一定的時日,但是在普及上還需要一定的時間,因而,這本中文譯本還是有一定的可讀性的。這本書更新的版本為英文第二版,很遺憾還未出現其中文譯本。
由於是開篇,本文所講都很基礎,但這些基礎內容對後面的學習是非常重要的。
1 C++標准
C++的標准化是一個漫長的過程。為何要標准化?作為一個語言,如果不設立相應的標准規格那是萬萬不能的。世界上有很多C++程序員,他們都有各自的編程風格,如果不做統一的話,那麼這門語言將會演變出相當多個版本,這也會讓C++進入分崩離析的狀態,其中的一個嚴重後果就是兼容性問題,因而,標准化是必不可少的,標准規格的建立,是C++的一個重要裡程碑。
標准程序庫是C++標准規格的一部分,提供一系列核心組件,用以支持I/O、字符串(string)、容器(數據結構)、算法(排序、搜索、合並等等)、數值計算、國別(例如不同字符集,character set)等主題。
2 C++語言特性
C++語言核心和C++程序庫是同時被標准化的。C++每5年會有一個新版本。如果你沒有緊跟其發展,可能會對某些新的語言特性大感驚訝。接下來的幾個小節,將描述與C++標准程序庫有關的幾個最重要的語言特性。
2.1 template(模板)
程序庫中幾乎所有東西都被設計為template形式。不支持template,就不能使用標准程序庫。
所謂template,是針對“一個或多個尚未明確的類型”所撰寫的函數或類別。使用templete時,可以顯式地或隱式地將類型當做參數來傳遞。下面的一個典型例子,傳回兩數中的較大數:
template <class T> const T& max( const T& a, const T& b) { return a < b ? b : a; }
在這裡,第一行將T定義為任意數據類型,在函數被調用時由調用者指定。任何合法標示符都可以拿來做參數名,但通常以T表示,這差不多成了一個“准”標准。這個類別用關鍵字class引導,但類型本身不一定得是class-------任何數據類型只要提供template定義式內所用到的操作,都可適用於此template。
遵循同樣原則,你可以將class參數化,並以任意類型作為實際參數。這一點對容器類別非常有用。你可以實現出“有能力操控任意類型元素”的容器。C++標准程序庫提供了許多模板容器類。
欲實現C++標准程序庫的完整功能,編譯器不僅要提供一般的template支持,還需要很多新的template標准特性,以下分別探討。
2.1.1 Nontype Templates參數(非類型模板參數)
類型(type)可以作為模板(template)參數,非類型(nontype)也可以作為模板(template)參數。因而後者可被看為前者的一部分。例如,可以把標准類型bitset<>的bits數量以template參數指定。以下定義了兩個由bits構成的容器,分別為32個bits空間和50個bits空間:
bitset<32> flags32; //32個bits空間的bitset bitset<50> flags50; //50個bits空間的bitset
這些bitsets由於使用不同的template參數,所以有不同的類型,不能互相復制(assign)或比較(除非提供了相應的類型轉換機制)。
2.1.2 Default Template Parameters(缺省模板參數)
template classes可以缺省參數。例如一下聲明,允許你使用一個或兩個template參數來聲明MyClass對象:
template <class T, class container = vector<T> > //注意兩個>之間必須有一個空格,如果沒寫空格“>>”會被解讀為移位運算子,導致語法錯誤。 class MyClass;
如果只傳給它一個參數,那麼缺省參數可作為第二個參數使用:
//以下兩條語句等價 MyClass<int> x1; MyClass<int, vector<int> > x1;
注意:template缺省參數可根據前一個(或前一些)參數定義。
2.1.3 關鍵字typename
關鍵字typename被用來作為類型之前的標識符號。考慮下面例子:
template <class T> class MyClass { typename T::SubType* ptr; //To do something... };
這裡,typename指出SubType是class T中定義的一個類型,因此ptr是一個指向T::SubType類型的指針。如果沒有關鍵字typename,Subtype會被當成一個static成員,於是:
T::Subtype * ptr 會被解釋為類型T內的數值Subtype與ptr的乘積。
Subtype成為一個類型的條件是,任何一個用來取代T的類型,其內都必須提供一個內部類型(inner type)Subtype的定義。例如,將類型Q當做template參數:
MyClass<Q> x;
必要條件是類型Q有如下的內部類型定義:
class Q { typedef int SubType; //To do something... };
注意,如果要把一個template中的某個標識符號指定為一種型別,就算意圖顯而易見,關鍵字typename也不可或缺,因此C++的一般規則是,除了以typename修飾之外,template內的任何標識符號都被視為一個值(value)而非一個類型。
typename還可以在template聲明中用來替換關鍵字class:
template <typename T> class MyClass;
2.1.4 Member Template(成員模板)
類成員函數可以是個template(模板),但是這樣的成員模板既不能是virtual,也不能缺省參數。例如:
class MyClass { //To do something... template<class T> void f(T); //To do something... };
在這裡,MyClass::f() 聲明了一個成員函數集,適用任何類型參數。只要某個類型提供有f()用到的所有操作,它就可以被當做參數傳遞進去。這個特性通常用來為模板類中的成員提供自動類型轉換。例如以下定義式中,assign()的參數x,其類型必須和調用端所提供的對象的類型完全吻合。
template <class T> class MyClass { private: T value; public: void assign(const MyClass<T>& x) { //x必須和*this有同樣的T value = x.value; } //To do something... };
即使兩個類型之間可以自動轉換,如果我們對assign()使用不同的template類型,也會出錯:
void f() { MyClass<double> d; MyClass<int> i; d.assign(d); //OK a.assign(i); //錯誤,i是MyClass<int>,但這裡需要MyClass<double> }
如果C++允許我們為成員函數提供不同的template類型,就可以放寬“必須精確吻合”這條規則:只要類型可被賦值,就可以被當做上述成員模板函數的參數。
template <class T> class MyClass { private: T value; public: template <class X> //成員模板 void assign(const MyClass<X>& x) { //允許不同的模板類型 value.x.getValue(); } T getValue() const { return value; } //To do something... }; void f() { MyClass<double> d; MyClass<int> i; d.assign(d); //OK a.assign(i); //OK,int對於double是可賦值的。 }
請注意,現在,assign()的參數x和*this的類型並不相同,所以你不能夠直接存取MyClass<>的private成員和protected成員,取而代之的是,此例中你必須使用類似getValue()之類的東西。
模板構造函數(templeta constructor)是成員模板的一種特殊形式,它通常用於“在復制對象時實現隱式類型轉換”。注意,模板構造函數並不遮蔽隱式的復制構造函數。如果類型完全吻合,隱式復制構造函數就會被產生出來並被調用。舉個例子:
template <class T> class MyClass { public: //帶隱式類型轉換的復制構造函數並不遮蔽隱式的復制構造函數 template <class U> MyClass(const &MyClass<U>& x); }; void f() { MyClass<double> xd; MyClass<double> xd2(xd); //調用內建復制構造函數(隱式) MyClass<int> xi(xd); //調用模板構造函數 //To do something... }
在這裡,xd2和xd的類型完全一致,所以它被內建的復制構造函數初始化,xi和xd的類型不同,所以它使用模板構造韓式進行初始化。因此,撰寫模板復制構造函數時,如果default輔助構造函數不符合你的要求,別忘了自己提供一個復制構造函數。
2.1.5 嵌套模板類
嵌套類本身也可以是個template:
template <class T> class MyClass { //To do something... template <class T2> //嵌套類 class NestedClass; //... };
2.2 基本類型的顯式初始化
如果采用不含參數的、明確的構造函數調用語法,基本類型會被初始化為零:
int i1; //未定義的值 int i2 = int(); //初始化為0
這個特性可以確保我們在撰寫template程序代碼時,任何型別都有一個確切的初值。例如下面的這個函數中,x保證被初始化為零。
template <class T> void f() { T x = T(); //... }
2.3 異常處理
通過異常處理,C++標准程序庫可以在不污染函數接口(亦即參數和返回值)的情況下處理異常。要注意的是這種概念叫做“異常處理”,而不是“錯誤處理”,兩者未必相同。舉個例子,許多時候用戶的無效輸入並非一種異常;這時候最好是在區域范圍內采用一般的錯誤處理技術來處理。
C++標准庫提供了一些通用的異常處理特性,例如標准異常類別(standard exception classes)和auto_ptr類別。
2.4 Namespaces(命名空間)
越來越多的軟件由程序庫、模塊和組件拼湊而成。各種不同事物的結合,可能導致一場名稱大沖突。Namespaces正是用來解決此問題的。
namespaces將不同的標識符號集合在一個具名作用域(names scope)內。如果你在namespace之內定義所有標識符號,則namespace本身就成了唯一可能與其他全局符號沖突的標識符號。你必須在標識符號前加上namespace名字,才能援引namespace內的符號,這和class處理方式雷同。namespace的名字和標識符號之間以::分割開來(這個符號及其意義和class與其成員之間的聯系有點類似)。
//在命名空間josuttis中定義標識符號 namespace josuttis { class File; void myGlobalFunc(); //... } //使用命名空間中的標識符 josuttis::File obj; josuttis::myGlobalFunc();
不同於class的是,namespace是開放的,你可以在不同模塊中定義和擴展namespace。因此你可以使用namespace來定義模塊、程序庫或組件,甚至在多個文件之間完成。namespace定義的是邏輯模塊,而非實質模塊。請注意,在UML及其他建模表示法中,模塊又被成為package。
如果某個函數的一個或多個參數類型,定義於函數所處的namespace中,那麼你可以不必為該函數指定namespace。這個規則成為Koenig lookup。例如:
//在命名空間josuttis中定義標識符號 namespace josuttis { class File; void myGlobalFunc(File& ); //... } //使用命名空間中的標識符 josuttis::File obj; myGlobalFunc(obj);
通過using聲明(using declaration),我們可以避免一再寫出冗長的namespace名稱,例如一下聲明:
using josuttis::File; //using declaration
會使File成為當前作用域內代表josuttis::File的一個同義字。
Using directive會使namespace內的所有名字曝光,它等於將這些名字聲明於namespace之外。但這麼一來,名稱沖突問題可能死灰復燃。例如:
using namespace josuttis; //using directive
會使File和myGlobalFunc()在當前作用域內完全曝光。如果全局范圍內已存在同名的File和myGlobalFunc(),而且使用者不加任何資格飾詞地使用這兩個名字,編譯器將東西難辨。
注意,如果場合(contex)不甚清楚(例如不清楚是在頭文件、模塊裡還是在程序庫裡),你不應該使用using directive。這個指令可能會改變namespace的作用域,從而使程序代碼被包含或使用於另一模塊中,導致意外的行為發生。事實上在頭文件中使用using directive相當不明智。
C++標准程序庫在namespace std中定義了它的所有標識符號。
2.5 Bool類型
為了支持布爾值(真假),C++增加了bool類型。Bool可增加程序可讀性,並允許你對bool值實現重載動作。兩個常數true和false同時亦被引入C++。此外C++還提供bool值與整數值之間的自動轉換。0值相當於false,非0值相當於true。
2.6 關鍵字explicit
通過關鍵字explicit的作用,我們可以禁止“單參數構造函數”被用於自動類型轉換。典型的例子便是群集類型。你可以將初始長度作為參數傳給構造函數,例如你可以聲明一個構造函數,以stack的初始大小為參數:
class Stack { explicit Stack(int size); //用初始大小size構造Stack //... };
在這裡,explicit的應用非常重要。如果沒有explicit,這個構造函數有能力將一個Int自動轉型為Stack。一旦發生這種情況,你甚至可以給Stack指派一個整數值而不會引起任何問題:
Stack s; s = 40; //糟糕,創建了一個有40個元素的Stack,並把它賦值給了s
自動類型轉換動作會把40轉換為有40個元素的Stack,並指派給s,這幾乎肯定不是我們所要的結果。如果我們將構造函數聲明為explicit,上述賦值操作就會導致編譯錯誤(那很好)。
注意,explicit同樣也能阻絕“以賦值語法進行帶有轉型操作的初始化”:
Stack s1(40); //OK Stack s2 = 40; //Error
這是因為一下兩組操作:
X x; Y y(x); //顯式轉換
和
X x; Y y = x; //隱式轉換
存在一個小差異。前者透過顯式轉換,根據類型X產生了一個類型Y的新對象;後者通過隱式轉換,產生了一類型Y的新對象。
2.7 新的類型轉換操作符
為了讓你對“參數的顯式類型轉換”了解更加透徹,C++引入一下4個新的操作符:
2.7.1 static_cast
將一個值以符合邏輯的形式轉換。這可看做是“利用原值重建一個新對象,並在設立初值時使用類型轉換”。唯有當上述的類型轉換有所定義,真個轉換才會成功。所謂“有所定義”,可以是語言內建規則,也可以是程序員自定的轉換動作。例如:
float x; cout << static_cast<int>(x); //打印int型的x f(static_cast<string>("hello")); //調用f(),參數為string類型,而不是char*
2.7.2 dynamic_cast
將多態類型向下轉型為其實際靜態類型。這是唯一在執行期進行檢驗的轉型動作。你可以用它來檢驗多個多態對象的類型,例如:
class Car; //抽象的基類,至少用於一個虛函數 class Cabriolet : public Car { //... }; class Limousine : public Car { //... }; void f(Car* cp) { Cabriolet* p = dynamic_cast<Cabriolet*>(cp); if(p == NULL) { //... } }
在這個例子中,面對實際靜態類型為Cabriolet的對象,f()有特殊應對行為。當參數是個reference,而且類型轉換失敗時,dynamic_cast會丟出一個bad_cast異常。注意,從設計者角度而言,你應該在運用多態技術的程序中,避免這種“程序行為取決於具體類型”的寫法。
2.7.3 const_cast
設定或去除類型的常數性,亦可去除volatile飾詞。除此之外不允許任何轉換。
2.7.4 reinterpret_cast
此操作符的行為由實際編譯器定義。可能重新解釋bits意義,但也不一定如此,使用此一轉型動作通常帶來不可移植性。
這些操作符取代了以往小圓括號所代表的舊式轉型,能夠清楚闡明轉型的目的。小圓括號轉型可替換dynamic_cast之外的其他三種轉型,也因此當你運用它時,你無法明確顯示使用它的確切理由。這些新式轉型操作給了編譯器更多信息,讓編譯器清楚知道轉型的理由,並在轉型失敗時釋出一份錯誤報告。
注意,這些操作符只接受一個參數。試看下面例子:
static_cast<Fraction>(15, 100) //糟糕,創建了Fraction(100)
在這個例子中你得不到你想要的結果。它只用一個數值100,將Fraction暫時對象設定初值,而非設定分子15、分母100。逗號在這裡並不起分隔作用,而是形成一個comma操作符,將兩個表達式組合為一個表達式,並傳回第二個表達式作為最終結果。將15和100“轉換”為分數的正確做法是:
Fraction(15, 100);
2.8 常數靜態成員的初始化
如今,我們終於能夠在class聲明中對“整數型常數靜態成員”直接賦予初值。初始化後,這個常數便可用於class之中,例如:
class MyClass { static const int num = 100; int elems[num]; };
注意,你還必須為class之中聲明的常數靜態成員,定義一個空間:
const int MyClass::num; //不用初始化
2.9 main()的定義式
在C++中有一個重要而又常被誤解的問題,那就是正確而可移植的main()的唯一寫法。根據C++標准,只有兩種main()是可移植的:
int main() { //... }
和
int main(int argc, char* argv[]) { //... }
這裡argv(命令行參數數組)也可以定義為char**。請注意,由於不允許“不言而喻”的返回類型int,所以返回類型必須明白寫為int。你可以使用return語句來結束main(),但不必一定如此。這一點和C不同,換句話說,C++在main()的末尾定義了一個隱式地:
return 0;
這意味著如果你不采用return語句離開main(),實際上就表示成功退出(傳回任何一個非0值都代表某種失敗)。處於這個原因,本范例在main()尾端都沒有return語句。有些編譯器可能會對此發出警告,那正是標准制定前的黑暗日子。
3 總結
第一篇到此結束,關於復雜度的問題我在此省略,相信讀者能理解。
第一章 集合與函數概念
一、集合有關概念
1、集合的含義:某些指定的對象集在一起就成為一個集合,其中每一個對象叫元素。
2、集合的中元素的三個特性:
1.元素的確定性; 2.元素的互異性; 3.元素的無序性
說明:(1)對於一個給定的集合,集合中的元素是確定的,任何一個對象或者是或者不是這個給定的集合的元素。
(2)任何一個給定的集合中,任何兩個元素都是不同的對象,相同的對象歸入一個集合時,僅算一個元素。
(3)集合中的元素是平等的,沒有先後順序,因此判定兩個集合是否一樣,僅需比較它們的元素是否一樣,不需考查排列順序是否一樣。
(4)集合元素的三個特性使集合本身具有了確定性和整體性。
3、集合的表示:{ … } 如{我校的籃球隊員},{太平洋,大西洋,印度洋,北冰洋}
1. 用拉丁字母表示集合:A={我校的籃球隊員},B={1,2,3,4,5}
2.集合的表示方法:列舉法與描述法。
注意啊:常用數集及其記法:
非負整數集(即自然數集) 記作:N
正整數集 N*或 N+ 整數集Z 有理數集Q 實數集R
關於“屬於”的概念
集合的元素通常用小寫的拉丁字母表示,如:a是集合A的元素,就說a屬於集合A 記作 a∈A ,相反,a不屬於集合A 記作 aA
列舉法:把集合中的元素一一列舉出來,然後用一個大括號括上。
描述法:將集合中的元素的公共屬性描述出來,寫在大括號內表示集合的方法。用確定的條件表示某些對象是否屬於這個集合的方法。
①語言描述法:例:{不是直角三角形的三角形}
②數學式子描述法:例:不等式x-3>2的解集是{xR| x-3>2}或{x| x-3>2}
4、集合的分類:
1.有限集 含有有限個元素的集合
2.無限集 含有無限個元素的集合
3.空集 不含任何元素的集合 例:{x|x2=-5}
二、集合間的基本關系
1.“包含”關系—子集
注意: 有兩種可能(1)A是B的一部分,;(2)A與B是同一集合。
反之: 集合A不包含於集合B,或集合B不包含集合A,記作A B或B A
2.“相等”關系(5≥5,且5≤5,則5=5)
實例:設 A={x|x2-1=0} B={-1,1} “元素相同”
結論:對於兩個集合A與B,如果集合A的任何一個元素都是集合B的元素,同時,集合B的任何一個元素都是集合A的元素,我們就說集合A等於集合B,即:A=B
① 任何一個集合是它本身的子集。AA
②真子集:如果AB,且A B那就說集合A是集合B的真子集,記作A B(或B A)
③如果 AB, BC ,那麼 AC
④ 如果AB 同時 BA 那麼A=B
3. 不含任何元素的集合叫做空集,記為Φ
規定: 空集是任何集合的子集, 空集是任何非空集合的真子集。
三、集合的運算
1.交集的定義:一般地,由所有屬於A且屬於B的元素所組成的集合,叫做A,B的交集.
記作A∩B(讀作”A交B”),即A∩B={x|x∈A,且x∈B}.
2、並集的定義:一般地,由所有屬於集合A或屬於集合B的元素所組成的集合,叫做A,B的並集。記作:A∪B(讀作”A並B”),即A∪B={x|x∈A,或x∈B}.
3、交集與並集的性質:A∩A = A, A∩φ= φ, A∩B......余下全文>>
氯氣為黃綠色,具有很強的氧化性;方程式:Fe+Cl2===FeCl3
與氫氣反應有蒼白色火焰
正確。氯氣可以與變價金屬反應使之生成最高價化合物,也可以與非金屬化合。