2 牛刀小試:且看一個簡單常式
2.1 引子
如果你是一個純粹的實用主義者,也許一開始就可以從這裡開始看起,因為此處提供了一個樣本程式,它可以帶給你有關使用STL的最直接的感受。是的,與其紙上談兵,不如單刀直入,實際操作一番。但是,需要提醒的是,假如你在興緻昂然地細細品味本章內容的時候,能夠同時結合前面章節作為佐餐,那將是再好不過的。你會發現,前面所提到的有關STL的那些優點,在此處得到了確切的應證。本章的後半部分,將為你示範在一些主流C++編譯器上,運行上述樣本程式的具體操作方法,和需要注意的事項。
2.2 常式實作
非常遺憾,我不得不捨棄"Hello World"這個經典的範例,儘管它不只一次的被各種介紹電腦語言的教科書所引用,幾乎成為了一個預設的“標準”。其原因在於它太過簡單了,以至於不具備代表性,無法展現STL的巨大魅力。我選用了一個稍稍複雜一點的例子,它的大致功能是:從標準輸入裝置(一般是鍵盤)讀入一些整型資料,然後對它們進行排序,最終將結果輸出到標準輸出裝置(一般是顯示器螢幕)。這是一種典型的處理方式,程式本身具備了一個系統所應該具有的幾乎所有的基本特徵:輸入 + 處理 + 輸出。你將會看到三個不同版本的程式。第一個是沒有使用STL的普通C++程式,你將會看到完成這樣看似簡單的事情,需要花多大的力氣,而且還未必沒有一點問題(真是吃力不討好)。第二個程式的主體部分使用了STL特性,此時在第一個程式中所遇到的問題就基本可以解決了。同時,你會發現採用了STL之後,程式變得簡潔明快,清晰易讀。第三個程式則將STL的功能發揮到了及至,你可以看到程式裡幾乎每一行代碼都是和STL相關的。這樣的機會並不總是隨處可見的,它展現了STL中的幾乎所有的基本組成部分,儘管這看起來似乎有點過分了。
有幾點是需要說明的:
這個常式的目的,在於向你示範如何在C++程式中使用STL,同時希望通過實踐,證明STL所帶給你的確確實實的好處。程式中用到的一些STL基本組件,比如:vector(一種容器)、sort(一種排序演算法),你只需要有一個大致的概念就可以了,這並不影響閱讀代碼和理解程式的含義。
很多人對GUI(圖形化使用者介面)的運行方式高度興趣,這也難怪,漂亮的介面總是會令人賞心悅目的。但是很可惜,在這裡沒有加入這些功能。這很容易解釋,對於所提供的這個簡單樣本程式而言,加入GUI特性,是有點本末倒置的。這將會使程式的代碼量驟然間急劇膨脹,而真正可以說明問題的核心部分確被淹沒在諸多無關緊要的代碼中間(你需要花去極大的精力來處理鍵盤或者滑鼠的訊息響應這些繁瑣而又較為規範的事情)。即使你有像Borland C++ Builder這樣的基於IDE(整合化開發環境)的工具,介面的處理變得較為簡單了(架構代碼是自動產生的)。請注意,我們這裡所談及的是屬於C++標準的一部分(STL的第一個字母說明了這一點),它不涉及具體的某個開發工具,它是幾乎在任何C++編譯器上都能編譯通過的代碼。畢竟,在Microsoft Visual C++和Borland C++ Builder裡,有關GUI的處理代碼是不一樣的。如果你想瞭解這些GUI的細節,這裡恐怕沒有你希望得到的答案,你可以尋找其它相關書籍。
2.2.1 第一版:史前時代--轉木取火
在STL還沒有降生的"黑暗時代",C++程式員要完成前面所提到的那些功能,需要做很多事情(不過這比起C程式來,似乎好一點),程式大致是如下這個樣子的:
// name:example2_1.cpp// alias:Rubish#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,則返回0int 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函數,否則,你還需要自己實現排序演算法。如果你用的是冒泡法排序,那效率就不會很理想。……,問題真是越來越讓人頭疼了!
關於第一個程式的討論就到此為止,如果你對第三種方案感興趣的話,可以嘗試著自己編寫一個程式,作為思考題。這裡就不準備再浪費筆墨去實現這樣一個讓人不甚愉快的程式了。
2.1 引子
如果你是一個純粹的實用主義者,也許一開始就可以從這裡開始看起,因為此處提供了一個樣本程式,它可以帶給你有關使用STL的最直接的感受。是的,與其紙上談兵,不如單刀直入,實際操作一番。但是,需要提醒的是,假如你在興緻昂然地細細品味本章內容的時候,能夠同時結合前面章節作為佐餐,那將是再好不過的。你會發現,前面所提到的有關STL的那些優點,在此處得到了確切的應證。本章的後半部分,將為你示範在一些主流C++編譯器上,運行上述樣本程式的具體操作方法,和需要注意的事項。
2.2 常式實作
非常遺憾,我不得不捨棄"Hello World"這個經典的範例,儘管它不只一次的被各種介紹電腦語言的教科書所引用,幾乎成為了一個預設的“標準”。其原因在於它太過簡單了,以至於不具備代表性,無法展現STL的巨大魅力。我選用了一個稍稍複雜一點的例子,它的大致功能是:從標準輸入裝置(一般是鍵盤)讀入一些整型資料,然後對它們進行排序,最終將結果輸出到標準輸出裝置(一般是顯示器螢幕)。這是一種典型的處理方式,程式本身具備了一個系統所應該具有的幾乎所有的基本特徵:輸入 + 處理 + 輸出。你將會看到三個不同版本的程式。第一個是沒有使用STL的普通C++程式,你將會看到完成這樣看似簡單的事情,需要花多大的力氣,而且還未必沒有一點問題(真是吃力不討好)。第二個程式的主體部分使用了STL特性,此時在第一個程式中所遇到的問題就基本可以解決了。同時,你會發現採用了STL之後,程式變得簡潔明快,清晰易讀。第三個程式則將STL的功能發揮到了及至,你可以看到程式裡幾乎每一行代碼都是和STL相關的。這樣的機會並不總是隨處可見的,它展現了STL中的幾乎所有的基本組成部分,儘管這看起來似乎有點過分了。
有幾點是需要說明的:
這個常式的目的,在於向你示範如何在C++程式中使用STL,同時希望通過實踐,證明STL所帶給你的確確實實的好處。程式中用到的一些STL基本組件,比如:vector(一種容器)、sort(一種排序演算法),你只需要有一個大致的概念就可以了,這並不影響閱讀代碼和理解程式的含義。
很多人對GUI(圖形化使用者介面)的運行方式高度興趣,這也難怪,漂亮的介面總是會令人賞心悅目的。但是很可惜,在這裡沒有加入這些功能。這很容易解釋,對於所提供的這個簡單樣本程式而言,加入GUI特性,是有點本末倒置的。這將會使程式的代碼量驟然間急劇膨脹,而真正可以說明問題的核心部分確被淹沒在諸多無關緊要的代碼中間(你需要花去極大的精力來處理鍵盤或者滑鼠的訊息響應這些繁瑣而又較為規範的事情)。即使你有像Borland C++ Builder這樣的基於IDE(整合化開發環境)的工具,介面的處理變得較為簡單了(架構代碼是自動產生的)。請注意,我們這裡所談及的是屬於C++標準的一部分(STL的第一個字母說明了這一點),它不涉及具體的某個開發工具,它是幾乎在任何C++編譯器上都能編譯通過的代碼。畢竟,在Microsoft Visual C++和Borland C++ Builder裡,有關GUI的處理代碼是不一樣的。如果你想瞭解這些GUI的細節,這裡恐怕沒有你希望得到的答案,你可以尋找其它相關書籍。
2.2.1 第一版:史前時代--轉木取火
在STL還沒有降生的"黑暗時代",C++程式員要完成前面所提到的那些功能,需要做很多事情(不過這比起C程式來,似乎好一點),程式大致是如下這個樣子的:
// name:example2_1.cpp// alias:Rubish#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,則返回0int 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的最直接的感受。是的,與其紙上談兵,不如單刀直入,實際操作一番。但是,需要提醒的是,假如你在興緻昂然地細細品味本章內容的時候,能夠同時結合前面章節作為佐餐,那將是再好不過的。你會發現,前面所提到的有關STL的那些優點,在此處得到了確切的應證。本章的後半部分,將為你示範在一些主流C++編譯器上,運行上述樣本程式的具體操作方法,和需要注意的事項。
2.2 常式實作
非常遺憾,我不得不捨棄"Hello World"這個經典的範例,儘管它不只一次的被各種介紹電腦語言的教科書所引用,幾乎成為了一個預設的“標準”。其原因在於它太過簡單了,以至於不具備代表性,無法展現STL的巨大魅力。我選用了一個稍稍複雜一點的例子,它的大致功能是:從標準輸入裝置(一般是鍵盤)讀入一些整型資料,然後對它們進行排序,最終將結果輸出到標準輸出裝置(一般是顯示器螢幕)。這是一種典型的處理方式,程式本身具備了一個系統所應該具有的幾乎所有的基本特徵:輸入 + 處理 + 輸出。你將會看到三個不同版本的程式。第一個是沒有使用STL的普通C++程式,你將會看到完成這樣看似簡單的事情,需要花多大的力氣,而且還未必沒有一點問題(真是吃力不討好)。第二個程式的主體部分使用了STL特性,此時在第一個程式中所遇到的問題就基本可以解決了。同時,你會發現採用了STL之後,程式變得簡潔明快,清晰易讀。第三個程式則將STL的功能發揮到了及至,你可以看到程式裡幾乎每一行代碼都是和STL相關的。這樣的機會並不總是隨處可見的,它展現了STL中的幾乎所有的基本組成部分,儘管這看起來似乎有點過分了。
有幾點是需要說明的:
這個常式的目的,在於向你示範如何在C++程式中使用STL,同時希望通過實踐,證明STL所帶給你的確確實實的好處。程式中用到的一些STL基本組件,比如:vector(一種容器)、sort(一種排序演算法),你只需要有一個大致的概念就可以了,這並不影響閱讀代碼和理解程式的含義。
很多人對GUI(圖形化使用者介面)的運行方式高度興趣,這也難怪,漂亮的介面總是會令人賞心悅目的。但是很可惜,在這裡沒有加入這些功能。這很容易解釋,對於所提供的這個簡單樣本程式而言,加入GUI特性,是有點本末倒置的。這將會使程式的代碼量驟然間急劇膨脹,而真正可以說明問題的核心部分確被淹沒在諸多無關緊要的代碼中間(你需要花去極大的精力來處理鍵盤或者滑鼠的訊息響應這些繁瑣而又較為規範的事情)。即使你有像Borland C++ Builder這樣的基於IDE(整合化開發環境)的工具,介面的處理變得較為簡單了(架構代碼是自動產生的)。請注意,我們這裡所談及的是屬於C++標準的一部分(STL的第一個字母說明了這一點),它不涉及具體的某個開發工具,它是幾乎在任何C++編譯器上都能編譯通過的代碼。畢竟,在Microsoft Visual C++和Borland C++ Builder裡,有關GUI的處理代碼是不一樣的。如果你想瞭解這些GUI的細節,這裡恐怕沒有你希望得到的答案,你可以尋找其它相關書籍。
2.2.1 第一版:史前時代--轉木取火
在STL還沒有降生的"黑暗時代",C++程式員要完成前面所提到的那些功能,需要做很多事情(不過這比起C程式來,似乎好一點),程式大致是如下這個樣子的:
// name:example2_1.cpp// alias:Rubish#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,則返回0int 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函數,否則,你還需要自己實現排序演算法。如果你用的是冒泡法排序,那效率就不會很理想。……,問題真是越來越讓人頭疼了!
關於第一個程式的討論就到此為止,如果你對第三種方案感興趣的話,可以嘗試著自己編寫一個程式,作為思考題。這裡就不準備再浪費筆墨去實現這樣一個讓人不甚愉快的程式了。
非常遺憾,我不得不捨棄"Hello World"這個經典的範例,儘管它不只一次的被各種介紹電腦語言的教科書所引用,幾乎成為了一個預設的“標準”。其原因在於它太過簡單了,以至於不具備代表性,無法展現STL的巨大魅力。我選用了一個稍稍複雜一點的例子,它的大致功能是:從標準輸入裝置(一般是鍵盤)讀入一些整型資料,然後對它們進行排序,最終將結果輸出到標準輸出裝置(一般是顯示器螢幕)。這是一種典型的處理方式,程式本身具備了一個系統所應該具有的幾乎所有的基本特徵:輸入 + 處理 + 輸出。你將會看到三個不同版本的程式。第一個是沒有使用STL的普通C++程式,你將會看到完成這樣看似簡單的事情,需要花多大的力氣,而且還未必沒有一點問題(真是吃力不討好)。第二個程式的主體部分使用了STL特性,此時在第一個程式中所遇到的問題就基本可以解決了。同時,你會發現採用了STL之後,程式變得簡潔明快,清晰易讀。第三個程式則將STL的功能發揮到了及至,你可以看到程式裡幾乎每一行代碼都是和STL相關的。這樣的機會並不總是隨處可見的,它展現了STL中的幾乎所有的基本組成部分,儘管這看起來似乎有點過分了。
有幾點是需要說明的:
這個常式的目的,在於向你示範如何在C++程式中使用STL,同時希望通過實踐,證明STL所帶給你的確確實實的好處。程式中用到的一些STL基本組件,比如:vector(一種容器)、sort(一種排序演算法),你只需要有一個大致的概念就可以了,這並不影響閱讀代碼和理解程式的含義。
很多人對GUI(圖形化使用者介面)的運行方式高度興趣,這也難怪,漂亮的介面總是會令人賞心悅目的。但是很可惜,在這裡沒有加入這些功能。這很容易解釋,對於所提供的這個簡單樣本程式而言,加入GUI特性,是有點本末倒置的。這將會使程式的代碼量驟然間急劇膨脹,而真正可以說明問題的核心部分確被淹沒在諸多無關緊要的代碼中間(你需要花去極大的精力來處理鍵盤或者滑鼠的訊息響應這些繁瑣而又較為規範的事情)。即使你有像Borland C++ Builder這樣的基於IDE(整合化開發環境)的工具,介面的處理變得較為簡單了(架構代碼是自動產生的)。請注意,我們這裡所談及的是屬於C++標準的一部分(STL的第一個字母說明了這一點),它不涉及具體的某個開發工具,它是幾乎在任何C++編譯器上都能編譯通過的代碼。畢竟,在Microsoft Visual C++和Borland C++ Builder裡,有關GUI的處理代碼是不一樣的。如果你想瞭解這些GUI的細節,這裡恐怕沒有你希望得到的答案,你可以尋找其它相關書籍。
2.2.1 第一版:史前時代--轉木取火
在STL還沒有降生的"黑暗時代",C++程式員要完成前面所提到的那些功能,需要做很多事情(不過這比起C程式來,似乎好一點),程式大致是如下這個樣子的:
// name:example2_1.cpp// alias:Rubish#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,則返回0int 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++程式員要完成前面所提到的那些功能,需要做很多事情(不過這比起C程式來,似乎好一點),程式大致是如下這個樣子的:
// name:example2_1.cpp// alias:Rubish#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,則返回0int 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函數,否則,你還需要自己實現排序演算法。如果你用的是冒泡法排序,那效率就不會很理想。……,問題真是越來越讓人頭疼了!
關於第一個程式的討論就到此為止,如果你對第三種方案感興趣的話,可以嘗試著自己編寫一個程式,作為思考題。這裡就不準備再浪費筆墨去實現這樣一個讓人不甚愉快的程式了。