STL標準模板庫學習--STL 很好很強大

從標準輸入設備(一般是鍵盤)讀入一些整型數據,然後對它們進行 排序,最終將結果輸出到標準輸出設備(一般是顯示器屏幕)。這是一種典型的處理方式,程序本身具備了一個系統所應該具有的幾乎所有的基本特徵:輸入 + 處理 + 輸出。你將會看到個不同版本的程序。第一個是沒有使用STL的普通C++程序,你將會看到完成這樣看似簡單的事情,需要花多大的力氣,而且還未必沒有一 點問題(真是吃力不討好)。第二個程序的主體部分使用了STL特性,此時在第一個程序中所遇到的問題就基本可以解決了。同時,你會發現採用了STL之後, 程序變得簡潔明快,清晰易讀。 

有幾點是需要說明的:

這個例程的目的,在於向你演示如何在C++程序中使用STL,同時希望通 過實踐,證明STL所帶給你的確確實實的好處。程序中用到的一些STL基本組件,比如:vector(一種容器)、sort(一種排序算法),你只需要有 一個大致的概念就可以了,這並不影響閱讀代碼和理解程序的含義。

第一版:史前時代--轉木取火

在STL還沒有降生的"黑暗時代",C++程序員要完成前面所提到的那些功能,需要做很多事情(不過這比起C程序來,似乎好一點),程序大致是如下這個樣子的:



            #include <stdlib.h>

            #include <iostream.h>

            

            int compare(const void *arg1, const void *arg2);

            

            void main(void)

            {

            const int max_size = 10;                // 數組允許元素的最大個數

            int num[max_size];                        // 整型數組

            

            // 從標準輸入設備讀入整數,同時累計輸入個數,

            // 直到輸入的是非整型數據爲止

            int n;

            for (n = 0; cin >> num[n]; n ++);

            

            // C標準庫中的快速排序(quick-sort)函數

            qsort(num, n, sizeof(int), compare);

            

            // 將排序結果輸出到標準輸出設備

            for (int i = 0; i < n; i ++)

            cout << num[i] << ""n";

            }

            

            // 比較兩個數的大小,

            // 如果*(int *)arg1比*(int *)arg2小,則返回-1

            // 如果*(int *)arg1比*(int *)arg2大,則返回1

            // 如果*(int *)arg1等於*(int *)arg2,則返回0

            int compare(const void *arg1, const void *arg2)

            {

            return        (*(int *)arg1 < *(int *)arg2) ? -1 :(*(int *)arg1 > *(int *)arg2) ? 1 : 0;

            }


             

這是一個和STL沒有絲毫關係的傳統風格的C++程序。因爲程序的注 釋已經很詳盡了,所以不需要我再做更多的解釋。總的說來,這個程序看起來並不十分複雜(本來就沒有太多功能)。只是,那個compare函數,看起來有點費勁。指向它的函數指針被作爲最後一個實參傳入qsort函數,qsort是C程序庫stdlib.h中的一個函數。以下是qsort的函數原型:

void qsort(void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
看起來有點令人作嘔,尤其是最後一個參數。大概的意思是,第一個參數 指明瞭要排序的數組(比如:程序中的num),第二個參數給出了數組的大小(qsort沒有足夠的智力預知你傳給它的數組的實際大小),第三個參數給出了數組中每個元素以字節爲單位的大小。最後那個長長的傢伙,給出了排序時比較元素的方式(還是因爲qsort的智商問題)。

以下是某次運行的結果:
輸入:0 9 2 1 5
輸出:0 1 2 5 9

有一個問題,這個程序並不像看起來那麼健壯(Robust)。如果我們輸入的數字個數超過max_size所規定的上限,就會出現數組越界問題。如果你在Visual C++的IDE環境下以控制檯方式運行這個程序時,會彈出非法內存訪問的錯誤對話框。這個問題很嚴重,嚴重到足以使你開始重新審視這個程序的代碼。爲了彌補程序中的這一缺陷。我們不得不考慮採用如下三種方案中的一種:

採用大容量的靜態數組分配。
限定輸入的數據個數。
採用動態內存分配。

第一種方案比較簡單,你所做的只是將max_size改大一點,比 如:1000或者10000。但是,嚴格講這並不能最終解決問題,隱患仍然存在。假如有人足夠耐心,還是可以使你的這個經過糾正後的程序崩潰的。此外,分配一個大數組,通常是在浪費空間,因爲大多數情況下,數組中的一部分空間並沒有被利用。再來看看第二種方案,通過在第一個for循環中加入一個限定條件,可 以使問題得到解決。比如:for (int n = 0; cin >> num[n] && n < max_size; n ++); 但是這個方案同樣不甚理想,儘管不會使程序崩潰,但失去了靈活性,你無法輸入更多的數。 看來只有選擇第三種方案了。是的,你可以利用指針,以及動態內存分配 妥善的解決上述問題,並且使程序具有良好的靈活性。這需要用到new,delete操作符,或者古老的malloc(),realloc()和 free()函數。但是爲此,你將犧牲程序的簡潔性,使程序代碼陡增,代碼的處理邏輯也不再像原先看起來那麼清晰了。一個compare函數或許就已經令 你不耐煩了,更何況要實現這些複雜的處理機制呢?很難保證你不會在處理這個問題的時候出錯,很多程序的bug往往就是這樣產生的。同時,你還應該感謝 stdlib.h,它爲你提供了qsort函數,否則,你還需要自己實現排序算法。如果你用的是冒泡法排序,那效率就不會很理想。……,問題真是越來越讓 人頭疼了! 關於第一個程序的討論就到此爲止,如果你對第三種方案感興趣的話,可以嘗試着自己編寫一個程序,作爲思考題。這裏就不準備再浪費筆墨去實現這樣一個讓人不甚愉快的程序了。


 第二版:工業時代--組件化大生產


我們應該慶幸自己所生活的年代。工業時代,科技的發展所帶來的巨大便利已經影響到了我們生活中的每個細節。如果你還在以原始人類的方式生活着,那我真該懷疑你是否屬於某個生活在非洲或者南美叢林裏的原始部落中的一員了,難道是瑪雅文明又重現了? STL便是這個時代的產物,正如其他科技成果一樣,C++程序員也應該努力使自己適應並充分利用這個"高科技成果"。讓我們重新審視第一版的那個破爛不堪的程序。試着使用一下STL,看看效果如何。

 

            #include <iostream>

            #include <vector>

            #include <algorithm>

            

            using namespace std;

            

            void main(void)

            {

            vector<int> num;                // STL中的vector容器

            int element;

            

            // 從標準輸入設備讀入整數,

            // 直到輸入的是非整型數據爲止

            while (cin >> element)

            num.push_back(element);

            

            // STL中的排序算法

            sort(num.begin(), num.end());

            

            // 將排序結果輸出到標準輸出設備

            for (int i = 0; i < num.size(); i ++)

            cout << num[i] << ""n";

            }


             

這個程序的主要部分改用了STL的部件,看起來要比第一個程序簡潔一 點,你已經找不到那個討厭的compare函數了。它真的能很好的運行嗎?你可以試試,因爲程序的運行結果和前面的大致差不多,所以在此略去。我可以向你 保證,這個程序是足夠健壯的。不過,可能你還沒有完全看明白程序的代碼,所以我需要爲你解釋一下。畢竟,這個戲法變得太快了,較之第一個程序,一眨眼的功 夫,那些老的C++程序員所熟悉的代碼都不見了,取而代之的是一些新鮮玩意兒。程序的前三行是包含的頭文件,它們提供了程序所要用到的所有C++特 性(包括輸入輸出處理,STL中的容器和算法)。不必在意那個.h,並不是我的疏忽,程序保證可以編譯通過,只要你的C++編譯器支持標準C++規範的相關部分。你只需要把它們看作是一些普通的C++頭文件就可以了。事實上,也正是如此,如果你對這個變化細節感興趣的化,可以留意一下你身旁的佐餐。同樣可以忽略第四行的存在。加入那個聲明只是爲了表明程序引用到了std這個標準名字空間(namespace),因爲STL中的那些玩意兒全都包含在那裏面。只有通過這行聲明,編譯器才能允許你使用那些有趣的特性。 程序中用到了vector,它是STL中的一個標準容器,可以用來存放一些元素。你可以把vector理 解爲int [?],一個整型的數組。之所以大小未知是因爲,vector是一個可以動態調整大小的容器,當容器已滿時,如果再放入元素則vector會悄悄擴大自己的容量。push_back是vector容器的一個類屬成員函數,用來在容器尾端插入一個元素。main函數中第一個while循環做的事情就是不斷向 vector容器尾端插入整型數據,同時自動維護容器空間的大小。sort是STL中的標準算法,用來對容器中的元素進行排序。它需要 兩個參數用來決定容器中哪個範圍內的元素可以用來排序。這裏用到了vector的另兩個類屬成員函數。begin()用以指向vector的首端,而end()則指向vector的末端。這裏有兩個問題,begin()和end()的返回值是什麼?這涉及到STL的另一個重要部件--迭代器 (Iterator),不過這裏並不需要對它做詳細瞭解。你只需要把它當作是一個指針就可以了,一個指向整型數據的指針。相應的sort函數聲明也可以看 作是void sort(int* first, int* last),儘管這實際上很不精確。另一個問題是和end()函數有關,儘管前面說它的返回值指向vector的末端,但這種說法不能算正確。事實上,它 的返回值所指向的是vector中最末端元素的後面一個位置,即所謂pass-the-end value。這聽起來有點費解,不過不必在意,這裏只是稍帶一提。總的來說,sort函數所做的事情是對那個準整型數組中的元素進行排序,一如第一個程序中的那個qsort,不過比起qsort來,sort似乎要簡單了許多。程序的最後是輸出部分,在這裏vector完全可以以假亂真了,它所提供的對元素的訪問方式簡直和普通的C++內建數組一模一樣。那個size函數用來返回vector中的元素個數,就相當於第一個程序中的變量n。這兩行代碼直觀的不用我再多解釋了。 我想我的耐心講解應該可以使你大致看懂上面的程序了,事實上STL的 運用使程序的邏輯更加清晰,使代碼更易於閱讀。試問,有誰會不明白begin、end、size這樣的字眼所表達的含義呢(除非他不懂英語)?試着運行一 下,看看效果。再試着多輸入幾個數,看看是否會發生數組越界現象。實踐證明,程序運行良好。是的,由於vector容器自行維護了自身的大小,C++程序 員就不用操心動態內存分配了,指針的錯誤使用畢竟會帶來很多麻煩,同時程序也會變得冗長無比。這正是前面第三種方案的缺點所在。 再仔細審視一下你的第一個STL版的C++程序,回顧一下第一章所提到的那些有關STL的優點:易於使用,具有工業強度……,再比較一下第一版的程序,我想你應該有所體會了吧!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章