c++標準庫簡介


      C++標準庫非常大。在C++標準中,關於標準庫的規格說明佔了密密麻麻300多頁,這還不包括標準C庫,後者只是 "作爲參考"包含在C++庫中。
       當然,並非總是越大越好,但在現在的情況下,確實越大越好,因爲大的庫會包含大量的功能。標準庫中的功能越多,開發自己的應用程序時能借助的功能就越多。C++庫並非提供了一切(沒有提供併發和圖形用戶接口的支持),但確實提供了很多。幾乎任何事都可以求助於它。
       因爲標準庫中東西如此之多,你所選擇的類名或函數名就很有可能和標準庫中的某個名字相同。爲了避免這種情況所造成的名字衝突,實際上標準庫中的一切都被放在名字空間std中。但這帶來了一個新問題。無數現有的C++代碼都依賴於使用了多年的僞標準庫中的功能,例如,聲明在<iostream.h>,<complex.h>,<limits.h>等頭文件中的功能。現有軟件沒有針對使用名字空間而進行設計,如果用std來包裝標準庫導致現有代碼不能用,將是一種可恥行爲。
      懾於被激怒的程序員會產生的破壞力,標準委員會決定爲包裝了std的那部分標準庫構件創建新的頭文件名。生成新頭文件的方法僅僅是將現有C++頭文件名中的 .h 去掉,方法本身不重要,正如最後產生的結果不一致也並不重要一樣。所以<iostream.h>變成了<iostream>,<complex.h>變成了<complex>,等等。對於C頭文件,採用同樣的方法,但在每個名字前還要添加一個c。所以C的<string.h>變成了<cstring>,<stdio.h>變成了<cstdio>,等等。最後一點是,舊的C++頭文件是官方所反對使用的(即明確列出不再支持),但舊的C頭文件則沒有(以保持對C的兼容性)。實際上,編譯器製造商不會停止對客戶現有軟件提供支持,所以可以預計,舊的C++頭文件在未來幾年內還是會被支持。
所以,實際來說,下面是C++頭文件的現狀:
       · 舊的C++頭文件名如<iostream.h>將會繼續被支持,儘管它們不在官方標準中。這些頭文件的內容不在名字空間std中。
      · 新的C++頭文件如<iostream>包含的基本功能和對應的舊頭文件相同,但頭文件的內容在名字空間std中。(在標準化的過程中,庫中有些部分的細節被修改了,所以舊頭文件和新頭文件中的實體不一定完全對應。)
      · 標準C頭文件如<stdio.h>繼續被支持。頭文件的內容不在std中。
      · 具有C庫功能的新C++頭文件具有如<cstdio>這樣的名字。它們提供的內容和相應的舊C頭文件相同,只是內容在std中。

       所有這些初看有點怪,但不難習慣它。最大的挑戰是把字符串頭文件理清楚:<string.h>是舊的C頭文件,對應的是基於char*的字符串處理函數;<string>是包裝了std的C++頭文件,對應的是新的string類;<cstring>是對應於舊C頭文件的std版本。如果能掌握這些,其餘的也就容易了。
       關於標準庫,需要知道的第二點是,庫中的一切幾乎都是模板。iostream幫助你操作字符流,但什麼是字符?是char嗎?是wchar_t?是 Unicode字符?一些其它的多字節字符?沒有明顯正確的答案,所以標準庫讓你去選。所有的流類(stream class)實際上是類模板,在實例化流類的時候指定字符類型。例如,標準庫將cout類型定義爲ostream,但ostream實際上是一個 basic_ostream<char>類型定義(typedef )。
類似的考慮適用於標準庫中其它大部分類。string不是類,它是類模板:類型參數限定了每個string類中的字符類型。complex不是類,它是類模板:類型參數限定了每個complex類中實數部分和虛數部分的類型。vector不是類,它是類模板。如此不停地進行下去。
       在標準庫中無法避開模板,但如果只是習慣於和char類型的流和字符串打交道,通常可以忽略它們。這是因爲,對這些組件的char實例,標準庫都爲它們定義了typedef,這樣就可以在編程時繼續使用cin,cout,cerr等對象,以及istream,ostream,string等類型,不必擔心 cin的真實類型是basic_istream<char>以及string的真實類型是 basic_string<char>。
      標準庫中很多組件的模板化和上面所建議的大不相同。再看看那個概念上似乎很直觀的string。當然,可以基於 "它所包含的字符類型" 確定它的參數,但不同的字符集在細節上有不同,例如,特殊的文件結束字符,拷貝它們的數組的最有效方式,等等。這些特徵在標準中被稱爲traits,它們在string實例中通過另外一個模板參數指定。此外,string對象要執行動態內存分配和釋放,但完成這一任務有很多不同的方法。哪一個最好?選擇:string模板有一個Allocator參數,Allocator類型的對象被用來分配和釋放string對象所使用的內存。
這裏有一個basic_string模板的完整聲明,以及建立在它之上的string類型定義(typedef);可以在<string>頭文件中找到它(或與之相當的什麼東西):
namespace std {
   template<class charT,
            class traits = char_traits<charT>,
            class Allocator = allocator<charT> >
      class basic_string;
   typedef basic_string<char> string;
}
注意,basic_string的traits和Allocator參數有缺省值。這在標準庫中是很典型的做法。它爲使用者提供了靈活性,但對於這種靈活性所帶來的複雜性,那些只想做 "正常" 操作的"典型" 用戶卻又可以避開。換句話說,如果只想使用象C字符串那樣的字符串對象,就可以使用string對象,而不用在意實際上是在用 basic_string<char, char_traits<char>, allocator<char> >類型的對象。
通常可以這麼做,但有時還是得稍稍看看底層。例如,聲明一個類而不提供定義具有優點;它還指出,下面是一種聲明string類型的錯誤方法:
class string;                    // 會通過編譯,
先不要考慮名字空間,這裏真正的問題在於:string不是一個類,而是一個typedef。如果可以通過下面的方法解決問題就太好了:
typedef basic_string<char> string;
但這又不能通過編譯。"所說的basic_string是什麼東西?" 編譯器會奇怪 ---- 當然,它可能會用不同的語句來問你。所以,爲了聲明string,首先得聲明它所依賴的所有模板。如果可以這麼做的話,就會象下面這樣:
template<class charT> struct char_traits;
template<class T> class allocator;
template<class charT, class traits = char_traits<charT>,
class Allocator = allocator<charT> >
class basic_string;
typedef basic_string<char> string;
然而,你不能聲明string。至少不應該。這是因爲,標準庫的實現者聲明的stirng(或std名字空間中任何其它東西)可以和標準中所指定的有所不同,只要最終提供的行爲符合標準就行。例如,basic_string的實現可以增加第四個模板參數,但這個參數的缺省值所產生的代碼的行爲要和標準中所說的原始的basic_string一致。那到底該怎麼辦?不要手工聲明string(或標準庫中其它任何部分)。相反,只用包含一個適當的頭文件,如<string>。
有了頭文件和模板的這些知識,現在可以看看標準C++庫中有哪些主要組件:
      · 標準C庫。它還在,還可以用它。雖然有些地方有點小的修修補補,但無論怎麼說,還是那個用了多年的C庫。
      · Iostream。和 "傳統" Iostream的實現相比,它已經被模板化了,繼承層次結構也做了修改,增強了拋出異常的能力,可以支持string(通過stringstream 類)和國際化(通過locales)。當然,你期望Iostream庫所具有的東西幾乎全都繼續存在。也就是說,它還是支持流緩衝區,格式化標識符,操作子和文件,還有cin,cout,cerr和clog對象。這意味着可以把string和文件當做流,還可以對流的行爲進行更廣泛的控制,包括緩衝和格式化。
      · String。string對象在大多數應用中被用來消除對char*指針的使用。它們支持你所期望的那些操作(例如,字符串連接,通過 operator[]對單個字符進行常量時間級的訪問,等等),它們可以轉換成char*,以保持和現有代碼的兼容性,它們還自動處理內存管理。一些 string的實現採用了引用計數,這會帶來比基於char*的字符串更佳的性能(時間和空間上)。
     · 容器。標準庫提供了高效的容器實現:vector(就象動態可擴充的數組),list(雙鏈表),queue, stack,deque,map,set和bitset。但多少可以作爲補償的一點是, string是容器。這很重要,因爲它意味着對容器所做的任何操作對string也適用。
標準庫規定了每個類的接口,而且每條接口規範中的一部分是一套性能保證。所以,舉例來說,無論vector是如何實現的,僅僅提供對它的元素的訪問是不夠的,還必須提供 "常量時間" 內的訪問。如果不這樣,就不是一個有效的vector實現。
很多C++程序中,動態分配字符串和數組導致大量使用new和delete,new/delete錯誤 ---- 尤其是沒有delete掉new出來的內存而導致的泄漏 ---- 時常發生。如果使用string和vector對象(二者都執行自身的內存管理)而不使用char*和動態分配的數組的指針,很多new和delete就可以免於使用,使用它們所帶來的問題也會隨之消失。
     · 算法。標準庫就提供了大量簡易的方法(即,預定義函數,官方稱爲算法(algorithm) ---- 實際上是函數模板),其中的大多數適用於庫中所有的容器 ---- 以及內建數組(built-in arrays)。
算法將容器的內容當作序列(sequence),每個算法可以應用於一個容器中所有值所對應的序列,或者一個子序列(subsequence)。標準算法有 for_each(爲序列中的每個元素調用某個函數),find(在序列中查找包含某個值的第一個位置,count_if(計算序列中使得某個判定爲真的所有元素的數量),equal(確定兩個序列包含的元素的值是否完全相同),search(在一個序列中找出某個子序列的起始位置),copy(拷貝一個序列到另一個),unique(在序列中刪除重複值),rotate(旋轉序列中的值),sort(對序列中的值排序)。注意這裏只是抽取了所有算法中的幾個;標準庫中還包括其它很多算法。
和容器操作一樣,算法也有性能保證。例如,stable_sort算法執行時要求不超過0比較級(N log N) 。(stable_sort提供的性能必須和最高效的通用排序算法在同一個級別。)
     · 對國際化的支持。不同的文化以不同的方式行事。和C庫一樣,C++庫提供了很多特性有助於開發出國際化的軟件。但雖然從概念上來說和C類似,其實C++的方法還是有所不同。例如,C++爲支持國際化廣泛使用了模板,還利用了繼承和虛函數,這些一定不會讓你感到奇怪。
支持國際化最主要的構件是facets和locales。facets描述的是對一種文化要處理哪些特性,包括排序規則(即,某地區字符集中的字符應該如何排序),日期和時間應該如何表示,數字和貨幣值應該如何表示,怎樣將信息標識符映射成(自然的)明確的語言信息,等等。locales將多組facets 捆綁在一起。例如,一個關於美國的locale將包括很多facets,描述如何對美國英語字符串排序,如何以適合美國人的方式讀寫日期和時間,讀寫貨幣和數字值,等等。而對於一個關於法國的locales來說,它描述的是怎麼以法國人所習慣的方式完成這些任務。C++允許單個程序中同時存在多個 locales,所以一個應用中的不同部分可能採用的是不同的規範。
     · 對數字處理的支持。C++庫爲複數類(實數和虛數部分的精度可以是float,double或long double)和專門針對數值編程而設計的特殊數組提供了模板。例如,valarray類型的對象可用來保存可以任意混疊(aliasing)的元素。這使得編譯器可以更充分地進行優化,尤其是對矢量計算機來說。標準庫還對兩種不同類型的數組片提供了支持,並提供了算法計算內積(inner product),部分和(partial sum),臨差(adjacent difference)等。
      · 診斷支持。標準庫支持三種報錯方式:C的斷言,錯誤號,例外。爲了有助於爲例外類型提供某種結構,標準庫定義了下面的例外類(exception class)層次結構:
                                                    |---domain_error
                     |----- logic_error<---- |---invalid_argument
                     |                               |---length_error
                     |                               |---out_of_range
exception<--|
                     |                                |--- range_error
                     |-----runtime_error<--|---underflow_error
                                                     |---overflow_error
logic_error(或它的子類)類型的例外表示的是軟件中的邏輯錯誤。理論上來說,這樣的錯誤可以通過更仔細的程序設計來防止。runtime_error(或它的子類)類型的例外表示的是只有在運行時才能發現的錯誤。
可以就這樣使用它們,可以通過繼承它們來創建自己的例外類,或者可以不去管它。沒有人強迫你使用它。
上面列出的內容並沒有涵蓋標準庫中的一切。
標準庫中容器和算法這部分一般稱爲標準模板庫。STL中實際上還有第三個構件 ---- 迭代子(Iterator)。迭代子是指針似的對象,它讓STL算法和容器共同工作。
       STL 是標準庫中最具創新的部分,這並不是因爲它提供了容器和算法(雖然它們非常有用),而是因爲它的體系結構。簡單來說,它的體系結構具有擴展性:可以對 STL進行添加。當然,標準庫中的組件本身是固定的,但如果遵循STL構建的規範,可以寫出自己的容器,算法和迭代子,使它們可以和標準STL組件一起工作,就象標準組件自身之間相互工作一樣。還可以利用別人所寫的符合STL規範的容器,算法和迭代子,就象別人利用你的一樣。使得STL具有創新意義的原因在於它實際上不是軟件,而是一套規範(convention)。標準庫中的STL組件只是具體體現了遵循這種規範所能帶來的好處。
通過使用標準庫中的組件,通常可以避免從頭到尾來設計自己的IO流,string,容器,國際化,數值數據結構以及診斷等機制。這就有更多的時間和精力去關注軟件開發中真正重要的部分:實現軟件的其他功能。
來源:http://hi.baidu.com/why0813/blog/item/850ea46eecf658db80cb4a6a.html

發佈了34 篇原創文章 · 獲贊 1 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章