STL_5

 

 

2.2.3 第三版:唯美主義的傑作

 

  事態的發展有時候總會趨向極端,這在那些唯美主義者當中猶是如此。首先聲明,我並不是一個唯美主義者,提供第二版程序的改進版,完全是爲了讓你更深刻的感受到STL的魅力所在。在看完第三版之後,你會強烈感受到這一點。或許你也會變成一個唯美主義者了,至少在STL方面。這應該不是我的錯,因爲決定權在你手裏。下面我們來看看這個絕版的C++程序。

 

// name:example2_3.cpp
// alias:aesthetic version

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

using namespace std;

void main(void)
{
	typedef vector<int>				int_vector;
	typedef istream_iterator<int>				istream_itr;
	typedef ostream_iterator<int>				ostream_itr;
	typedef back_insert_iterator< int_vector >	back_ins_itr;

	// STL中的vector容器
	int_vector num;

	// 從標準輸入設備讀入整數, 
	// 直到輸入的是非整型數據爲止
	copy(istream_itr(cin), istream_itr(), back_ins_itr(num));

	// STL中的排序算法
	sort(num.begin(), num.end());

	// 將排序結果輸出到標準輸出設備
	copy(num.begin(), num.end(), ostream_itr(cout, "/n"));
}
     

 

  在這個程序裏幾乎每行代碼都是和STL有關的(除了main和那對花括號,當然還有註釋),並且它包含了STL中幾乎所有的各大部件(容器container,迭代器iterator, 算法algorithm, 適配器adaptor),唯一的遺憾是少了函數對象(functor)的身影。

  還記得開頭提到的一個典型系統所具有的基本特徵嗎?--輸入+處理+輸出。所有這些功能,在上面的程序裏,僅僅是通過三行語句來實現的,其中每一行語句對應一種操作。對於數據的操作被高度的抽象化了,而算法和容器之間的組合,就像搭積木一樣輕鬆自如,系統的耦合度被降到了極低點。這就是閃耀着泛型之光的STL的偉大力量。如此簡潔,如此巧妙,如此神奇!就像魔術一般,以至於再一次讓你摸不着頭腦。怎麼實現的?爲什麼在看第二版程序的時候如此清晰的你,又墜入了五里霧中(竊喜)。

  請留意此處的標題(唯美主義的傑作),在實際環境中,你未必要做到這樣完美。畢竟美好願望的破滅,在生活中時常會發生。過於理想化,並不是一件好事,至少我是這麼認爲的。正如前面提到的,這個程序只是爲了展示STL的獨特魅力,你不得不爲它的出色表現所折服,也許只有深諳STL之道的人才會想出這樣的玩意兒來。如果你只是一般性的使用STL,做到第二版這樣的程度也就可以了。

  實在是因爲這個程序太過"簡單",以至於我無法肯定,在你還沒有完全掌握STL之前,通過我的講解,是否能夠領會這區區三行代碼,我將盡我的最大努力。

  前面提到的迭代器可以對容器內的任意元素進行定位和訪問。在STL裏,這種特性被加以推廣了。一個cin代表了來自輸入設備的一段數據流,從概念上講它對數據流的訪問功能類似於一般意義上的迭代器,但是C++中的cin在很多地方操作起來並不像是一個迭代器,原因就在於其接口和迭代器的接口不一致(比如:不能對cin進行++運算,也不能對之進行取值運算--即*運算)。爲了解決這個矛盾,就需要引入適配器的概念。istream_iterator便是一個適配器,它將cin進行包裝,使之看起來像是一個普通的迭代器,這樣我們就可以將之作爲實參傳給一些算法了(比如這裏的copy算法)。因爲算法只認得迭代器,而不會接受cin。對於上面程序中的第一個copy函數而言,其第一個參數展開後的形式是:istream_iterator(cin),其第二個參數展開後的形式是:istream_iterator()(如果你對typedef的語法不清楚,可以參考有關的c++語言書籍)。其效果是產生兩個迭代器的臨時對象,前一個指向整型輸入數據流的開始,後一個則指向"pass-the-end value"。這個函數的作用就是將整型輸入數據流從頭至尾逐一"拷貝"到vector這個準整型數組裏,第一個迭代器從開始位置每次累進,最後到達第二個迭代器所指向的位置。或許你要問,如果那個copy函數的行爲真如我所說的那樣,爲什麼不寫成如下這個樣子呢?

 

copy(istream_iterator<int>(cin), istream_iterator<int>(), num.begin());


  你確實可以這麼做,但是有一個小小的麻煩。還記得第一版程序裏的那個數組越界問題嗎?如果你這麼寫的話,就會遇到類似的麻煩。原因在於copy函數在"拷貝"數據的時候,如果輸入的數據個數超過了vector容器的範圍時,數據將會拷貝到容器的外面。此時,容器不會自動增長容量,因爲這只是簡單地拷貝,並不是從末端插入。爲了解決這個問題,另一個適配器back_insert_iterator登場了,它的作用就是引導copy算法每次在容器末端插入一個數據。程序中的那個back_ins_itr(num)展開後就是:back_insert_iterator(num),其效果是生成一個這樣的迭待器對象。

  終於將講完了三分之一(真不容易!),好在第二句和前一版程序沒有差別,這裏就略過了。至於第三句,ostream_itr(cout, "/n")展開後的形式是:ostream_iterator(cout, "/n"),其效果是產生一個處理輸出數據流的迭待器對象,其位置指向數據流的起始處,並且以"/n"作爲分割符。第二個copy函數將會從頭至尾將vector中的內容"拷貝"到輸出設備,第一個參數所代表的迭代器將會從開始位置每次累進,最後到達第二個參數所代表的迭代器所指向的位置。

  這就是全部的內容。

 

  

  歷史的車輪總是滾滾向前的,工業時代的文明較之史前時代,當然是先進並且發達的。回顧那兩個時代的C++程序,你會真切的感受到這種差別。簡潔易用,具有工業強度,較好的可移植性,高效率,加之第三個令人目眩的絕版程序所體現出來的高度抽象性,高度靈活性和組件化特性,使你對STL背後所蘊含的泛型化思想都有了些微的感受。

  真幸運,你可以橫跨兩個時代,有機會目睹這種"文明"的差異。同時,這也應該使你越加堅定信念,使自己順應時代的潮流。

 

  

 

  事態的發展有時候總會趨向極端,這在那些唯美主義者當中猶是如此。首先聲明,我並不是一個唯美主義者,提供第二版程序的改進版,完全是爲了讓你更深刻的感受到STL的魅力所在。在看完第三版之後,你會強烈感受到這一點。或許你也會變成一個唯美主義者了,至少在STL方面。這應該不是我的錯,因爲決定權在你手裏。下面我們來看看這個絕版的C++程序。

 

// name:example2_3.cpp
// alias:aesthetic version

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

using namespace std;

void main(void)
{
	typedef vector<int>				int_vector;
	typedef istream_iterator<int>				istream_itr;
	typedef ostream_iterator<int>				ostream_itr;
	typedef back_insert_iterator< int_vector >	back_ins_itr;

	// STL中的vector容器
	int_vector num;

	// 從標準輸入設備讀入整數, 
	// 直到輸入的是非整型數據爲止
	copy(istream_itr(cin), istream_itr(), back_ins_itr(num));

	// STL中的排序算法
	sort(num.begin(), num.end());

	// 將排序結果輸出到標準輸出設備
	copy(num.begin(), num.end(), ostream_itr(cout, "/n"));
}
     

 

  在這個程序裏幾乎每行代碼都是和STL有關的(除了main和那對花括號,當然還有註釋),並且它包含了STL中幾乎所有的各大部件(容器container,迭代器iterator, 算法algorithm, 適配器adaptor),唯一的遺憾是少了函數對象(functor)的身影。

  還記得開頭提到的一個典型系統所具有的基本特徵嗎?--輸入+處理+輸出。所有這些功能,在上面的程序裏,僅僅是通過三行語句來實現的,其中每一行語句對應一種操作。對於數據的操作被高度的抽象化了,而算法和容器之間的組合,就像搭積木一樣輕鬆自如,系統的耦合度被降到了極低點。這就是閃耀着泛型之光的STL的偉大力量。如此簡潔,如此巧妙,如此神奇!就像魔術一般,以至於再一次讓你摸不着頭腦。怎麼實現的?爲什麼在看第二版程序的時候如此清晰的你,又墜入了五里霧中(竊喜)。

  請留意此處的標題(唯美主義的傑作),在實際環境中,你未必要做到這樣完美。畢竟美好願望的破滅,在生活中時常會發生。過於理想化,並不是一件好事,至少我是這麼認爲的。正如前面提到的,這個程序只是爲了展示STL的獨特魅力,你不得不爲它的出色表現所折服,也許只有深諳STL之道的人才會想出這樣的玩意兒來。如果你只是一般性的使用STL,做到第二版這樣的程度也就可以了。

  實在是因爲這個程序太過"簡單",以至於我無法肯定,在你還沒有完全掌握STL之前,通過我的講解,是否能夠領會這區區三行代碼,我將盡我的最大努力。

  前面提到的迭代器可以對容器內的任意元素進行定位和訪問。在STL裏,這種特性被加以推廣了。一個cin代表了來自輸入設備的一段數據流,從概念上講它對數據流的訪問功能類似於一般意義上的迭代器,但是C++中的cin在很多地方操作起來並不像是一個迭代器,原因就在於其接口和迭代器的接口不一致(比如:不能對cin進行++運算,也不能對之進行取值運算--即*運算)。爲了解決這個矛盾,就需要引入適配器的概念。istream_iterator便是一個適配器,它將cin進行包裝,使之看起來像是一個普通的迭代器,這樣我們就可以將之作爲實參傳給一些算法了(比如這裏的copy算法)。因爲算法只認得迭代器,而不會接受cin。對於上面程序中的第一個copy函數而言,其第一個參數展開後的形式是:istream_iterator(cin),其第二個參數展開後的形式是:istream_iterator()(如果你對typedef的語法不清楚,可以參考有關的c++語言書籍)。其效果是產生兩個迭代器的臨時對象,前一個指向整型輸入數據流的開始,後一個則指向"pass-the-end value"。這個函數的作用就是將整型輸入數據流從頭至尾逐一"拷貝"到vector這個準整型數組裏,第一個迭代器從開始位置每次累進,最後到達第二個迭代器所指向的位置。或許你要問,如果那個copy函數的行爲真如我所說的那樣,爲什麼不寫成如下這個樣子呢?

 

copy(istream_iterator<int>(cin), istream_iterator<int>(), num.begin());


  你確實可以這麼做,但是有一個小小的麻煩。還記得第一版程序裏的那個數組越界問題嗎?如果你這麼寫的話,就會遇到類似的麻煩。原因在於copy函數在"拷貝"數據的時候,如果輸入的數據個數超過了vector容器的範圍時,數據將會拷貝到容器的外面。此時,容器不會自動增長容量,因爲這只是簡單地拷貝,並不是從末端插入。爲了解決這個問題,另一個適配器back_insert_iterator登場了,它的作用就是引導copy算法每次在容器末端插入一個數據。程序中的那個back_ins_itr(num)展開後就是:back_insert_iterator(num),其效果是生成一個這樣的迭待器對象。

  終於將講完了三分之一(真不容易!),好在第二句和前一版程序沒有差別,這裏就略過了。至於第三句,ostream_itr(cout, "/n")展開後的形式是:ostream_iterator(cout, "/n"),其效果是產生一個處理輸出數據流的迭待器對象,其位置指向數據流的起始處,並且以"/n"作爲分割符。第二個copy函數將會從頭至尾將vector中的內容"拷貝"到輸出設備,第一個參數所代表的迭代器將會從開始位置每次累進,最後到達第二個參數所代表的迭代器所指向的位置。

  這就是全部的內容。

 

  

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章