在這篇教程中我們將會使用教程上篇中的一些巨集和預定義型別。
利用 map 建立 vector
正如你所知,map實際上包含的是元素對。因此你可以這樣寫:
1 2 3 4 |
map<string, int> M; // ... vector< pair<string, int> > V(all(M)); // remember all(c) stands for (c).begin(),(c).end() |
現在vector中包含著和map 中相同的元素。當然,和map一樣,向量也是有序的。在你既不想改變map中的元素,又想以map所不允許的方式使用元素索引時,這個特性就派上用場了。
容器間拷貝資料
讓我們看一下 copy(…) 演算法。演算法的原型如下:
1 |
copy(from_begin, from_end, to_begin); |
這個演算法從第一個區間向第二個區間拷貝元素。第二個區間中應該有足夠的可用空間。請看下面的程式碼:
1 2 3 4 5 6 7 8 9 10 |
vector<int> v1; vector<int> v2; // ... // Now copy v2 to the end of v1 v1.resize(v1.size() + v2.size()); // 確保 v1 有足夠空間 copy(all(v2), v1.end() - v2.size()); // Copy v2 elements right after v1 ones |
譯者注:教程上篇中有巨集定義:#define all(c) c.begin(), c.end()
copy 還有另一個用於連線的好特性是inserters。由於篇幅限制,不多加贅述。請看下面的程式碼:
1 2 3 4 5 |
vector<int> v; // ... set<int> s; // add some elements to set copy(all(v), inserter(s)); |
最後一行程式碼等價於:
1 2 3 4 |
tr(v, it) { // remember traversing macros from Part I s.insert(*it); } |
既然已經有了標準函式,那麼我們還有什麼理由要使用自定義的巨集(這些巨集定義只能夠在 GCC 環境下執行)呢?使用諸如 copy 的標準演算法是 STL 的一個有效應用,因為可以使別人更容易理解你的程式碼。
push_back 使用 back_inserter 向 Vector 中插入元素 ,或者使用f ront_inserter 向 deque 容器中插入元素。在某些情況下,需要知道,不只 begin/end 可以作為 copy 的前兩個引數,rbegin/ren d也可以。使用 rbegin/rend,將會逆序拷貝元素。
歸併 list
歸併佇列是對有序 list 的另一個常見操作。假設你有兩個有序 list,分別是 A 和 B。你想將這兩個 list 歸併成一個新列表。通常會有四種方式:
- ‘union’ the lists, R = A+B
- intersect the lists, R = A*B
- set difference, R = A*(~B) or R = A-B
- set symmetric difference, R = A XOR B
STL為這類任務提供了四種演算法:set_union(…)、set_intersection(…)、set_difference(…) 和 set_symmetric_difference(…)。它們的呼叫方式相同,因此我們以 set_intersection 為例。一個常用原型如下:
1 |
end_result = set_intersection(begin1, end1, begin2, end2, begin_result); |
[begin1,end1) 和 [begin2,end2) 是輸入的兩個list。 ‘begin_result’ 是隻是輸出結果起點的迭代器。但是輸出結果list的大小是未知的。所以這個函式返回輸出結果終點的迭代器(這決定了在輸出結果中有多少個元素)。 關於使用細節,請看下面的例子:
1 2 3 4 5 6 7 8 9 |
int data1[] = { 1, 2, 5, 6, 8, 9, 10 }; int data2[] = { 0, 2, 3, 4, 7, 8, 10 }; vector<int> v1(data1, data1+sizeof(data1)/sizeof(data1[0])); vector<int> v2(data2, data2+sizeof(data2)/sizeof(data2[0])); vector<int> tmp(max(v1.size(), v2.size()); vector<int> res = vector<int> (tmp.begin(), set_intersection(all(v1), all(v2), tmp.begin()); |
最後一行,我們建立了一個新向量 res。它通過區間建構函式建立。區間以 tmp 的起點作為起點,以 set_intersection 演算法結果作為結尾。這個演算法會取 v1 和 v2 的交集,並將交集寫到輸出迭代器,從’tmp.begin()’開始寫入。set_intersection 演算法的返回值是結果資料集終點。
補充說明一點可能會幫助你深入地理解:如果你只是想得到交集中元素的數量,則使用 int cnt = set_intersection(all(v1), all(v2), tmp.begin()) – tmp.begin(); 即可。
實際上,我不會使用“vector<int> tmp”這種結構。我認為每呼叫一次“set_***”演算法都開闢一次記憶體是不明智的。相反,我會定義一個型別合適並且空間充足的全域性或者靜態變數。請看下面的程式碼:
1 2 3 4 5 6 7 8 9 10 |
set<int> s1, s2; for(int i = 0; i < 500; i++) { s1.insert(i*(i+1) % 1000); s2.insert(i*i*i % 1000); } static int temp[5000]; // greater than we need vector<int> res = vi(temp, set_symmetric_difference(all(s1), all(s2), temp)); int cnt = set_symmetric_difference(all(s1), all(s2), temp) – temp; |
‘res’ 中包含兩個輸入資料集中存在差異的元素。
注意,使用這些演算法,輸入的資料集必須是有序的。因此,例外一點也需要牢記,由於set是有序的,我們可以使用set(或者不覺得pair麻煩的話,也可以使用map)作為這些演算法的引數。
這類演算法從一端開始排序,演算法複雜度是 O(N1+N2),N1 和 N2 是輸入資料集的大小。
算術演算法
另外一個有趣的演算法是 accumulate(…)。如果我對一個int型的vector呼叫,並且將第三個引數設為0,accumulate(…) 會返回 vector 中元素之和。
1 2 3 |
vector<int> v; // ... int sum = accumulate(all(v), 0); |
accumulate()的返回值型別與第三個引數的型別一致。所以,當你不確定元素之和是否可以採用int型時,直接指定第三個引數的型別就可以了。
1 2 3 |
vector<int> v; // ... long long sum = accumulate(all(v), (long long)0); |
Accumulate也可以用來計算乘積。第四個引數標明瞭計算方法。如果你想獲得乘積,則使用如下程式碼:
1 2 3 4 |
vector<int> v; // ... double product = accumulate(all(v), double(1), multiplies<double>()); // don’t forget to start with 1 ! |
另外一個有趣的演算法是inner_product(…),它用來計算兩個向量的數量積。例如:
1 2 3 4 5 6 7 |
vector<int> v1; vector<int> v2; for(int i = 0; i < 3; i++) { v1.push_back(10-i); v2.push_back(i+1); } int r = inner_product(all(v1), v2.begin(), 0); |
‘r’是這樣的計算得來的:(v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]),或者說是(10*1+9*2+8*3),最終計算結果是52.
和“accumulate”演算法一樣,inner_product的返回值型別是由最後一個引數指定的。最後一個引數是返回結果的初始值。因此inner_product可以用於多維空間的超平面物件,這樣呼叫就可以了:
1 |
inner_product(all(normal), point.begin(), -shift) |
現在你應該明白,inner_product只需要對迭代器進行遞增,因此queue或者set也可以用作引數。用於計算特殊中間值的卷積濾波器可以這樣來實現:
1 2 3 4 5 6 7 |
set<int> values_ordered_data(all(data)); int n = sz(data); // int n = int(data.size()); vector<int> convolution_kernel(n); for(int i = 0; i < n; i++) { convolution_kernel[i] = (i+1)*(n-i); } double result = double(inner_product(all(ordered_data), convolution_kernel. |
當然,這些程式碼只是一個例子。老實說,將值拷貝到另一個vector然後排序會更快一些。
這樣用也是可以的:
1 2 3 |
vector<int> v; // ... int r = inner_product(all(v), v.rbegin(), 0); |
上面的程式碼將會計算 V[0]*V[N-1] + V[1]+V[N-2] + … + V[N-1]*V[0],其中N是‘v’中元素的個數。
Nontrivial Sorting重要的排序
實際上,sort(…)採用了與STL相同的技術。
- 所有的比較都基於運算子‘<’
這意味著你只需過載’operator <‘即可。示例程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
struct fraction { int n, d; // (n/d) // ... bool operator < (const fraction& f) const { if(false) { return (double(n)/d) < (double(f.n)/f.d); // Try to avoid this, you're the TopCoder! } else { return n*f.d < f.n*d; } } }; // ... vector<fraction> v; // ... sort(all(v)); |
為了以防萬一,你的物件應該有預設建構函式和拷貝建構函式(或許,還要過載賦值運算子——這條補充說明並非對TopCoders而言的)。
一定要牢記操作符 ‘ <’ 的原型:返回值是bool型別,有const修飾符,引數是const型別的引用。
另一個實現比較的方法是建立比較函式。特定的比較方式作為sort(…)演算法的第三個引數傳入。例如:按照極角大小對點排序(點的結構是pair<double,double>)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
typedef pair<double, double> dd; const double epsilon = 1e-6; struct sort_by_polar_angle { dd center; // Constuctor of any type // Just find and store the center template<typename T> sort_by_polar_angle(T b, T e) { int count = 0; center = dd(0,0); while(b != e) { center.first += b->first; center.second += b->second; b++; count++; } double k = count ? (1.0/count) : 0; center.first *= k; center.second *= k; } // Compare two points, return true if the first one is earlier // than the second one looking by polar angle // Remember, that when writing comparator, you should // override not ‘operator <’ but ‘operator ()’ bool operator () (const dd& a, const dd& b) const { double p1 = atan2(a.second-center.second, a.first-center.first); double p2 = atan2(b.second-center.second, b.first-center.first); return p1 + epsilon < p2; } }; // ... vector<dd> points; // ... sort(all(points), sort_by_polar_angle(all(points))); |
這段程式碼非常複雜,但是證明了STL的強大功能。應當指出,在這個例子中,所有的程式碼在編譯的時候都是內建(inline)的,實際上執行起來是很快的。
也要注意對於兩個相等的物件,操作符 ‘ <’ 會返回FALSE。這是非常重要的,接下來會解釋為什麼如此重要。
在map和set中使用自定義物件
set 和 map 中的元素是有序的。這是一個總體規則。所以,如果想在set或者map中使用你的物件,那麼這些物件應該是可比的。你已經瞭解了STL中的比較規則:
- 所有的比較都基於運算子 ‘<’
也就是,你應該這樣理解:要實現set或者map中元素有序,我只需要實現操作符“<”
假設我們要對point結構體(或者point類)進行操作。我們想讓一些線段相交,並且取得這些焦點的集合(有點耳熟?)由於計算機精度有限,當一些點的座標差別不大時,這些點將會是相同的。我們應該這樣編碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const double epsilon = 1e-7; struct point { double x, y; // ... // Declare operator < taking precision into account bool operator < (const point& p) const { if(x < p.x - epsilon) return true; if(x > p.x + epsilon) return false; if(y < p.y - epsilon) return true; if(y > p.y + epsilon) return false; return false; } }; |
現在,你可以使用set<point>或者map<point, string>,例如,查詢某些點是否已經在交集中存在。更進一步,使用map<point, vector<int> >,獲得相交在一點的所有線段索引的列表。
在STL中相等並不意味著相同,這是一個有趣的概念,但是此處我們不予深究。
Vector中的記憶體管理
據說vector不會在每次push_back()的時候都重新開闢記憶體。實際上,呼叫push_back()的時候,vector開闢了多於當前所需的記憶體空間。當呼叫push_back()的時候,vector的大部分STL實現都開闢了雙倍空間,並不需要每次都開闢分配記憶體。這或許在實際運用中並不好,因為你的程式佔用了雙倍的記憶體空間。有兩種簡易方法和一種複雜方法來處理這個問題。
第一種方法是使用vector的成員函式reserve()。這個函式使vector開闢多餘的記憶體。在未達到reserve()指定的大小之前,vector不會再次呼叫push_back()時開闢記憶體。
考慮一下接下來的例子。你有一個1000個元素的向量,它開闢了1024大小的空間。你打算向vector中追加50個元素。如果你呼叫50次push_back(),vector開闢的記憶體控制元件將會是2048.但是如果在呼叫push_back()之前加上這句程式碼:
1 |
v.reserve(1050); |
Vector開闢的記憶體空間恰好容納1050個元素.
如果你經常使用push_back,那麼reserve()會使你受益匪淺。
順便說一句,對於vector來說,在copy(…, back_inserter(v)) 之後使用v.reserve()是一種很好的模式。
另外一種情況:你希望某些操作之後,vector佔用的記憶體不會增加。該如何擺脫潛在的記憶體追加呢?解決方案如下:
1 2 3 |
vector<int> v; // ... vector<int>(all(v)).swap(v); |
這段程式碼的含義是:建立一個與 ‘v’ 內容相同的臨時向量,然後這個臨時向量與 ‘v’ 互換。互換之後v中的多餘記憶體將會相會小時。在SRMs中這個方案很少用到。
恰當但複雜的解決方案是為vector開發自定義的分配符,但這很明顯不是本教程該討論的內容。
用STL實現真正的演算法
帶著STL知識,我們繼續這篇文章中最有意思的部分:如何實現真正高效的演算法?
深度優先檢索(DFS)
這裡不再贅述DFS的原理——可以閱讀 gladius 所著《Introduction to Graphs and Data Structures》教程中的 這一章——但是我將會展示STL如何有助於實現DFS。
首先,假設有一個無向圖。在STL中,儲存這個無向圖最簡單的方法是儲存每個節點的相鄰節點。最終生成結構體vector< vector<int> > W ,其中W[i] 是到節點 i 的相鄰節點列表。接下來證明一下我們是按照DFS儲存的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* Reminder from Part 1: typedef vector<int> vi; typedef vector<vi> vvi; */ int N; // number of vertices vvi W; // graph vi V; // V is a visited flag void dfs(int i) { if(!V[i]) { V[i] = true; for_each(all(W[i]), dfs); } } bool check_graph_connected_dfs() { int start_vertex = 0; V = vi(N, false); dfs(start_vertex); return (find(all(V), 0) == V.end()); } |
這樣就證明完了。STL演算法”for_each”為V中的每一個元素呼叫指定的函式”dfs”。在check_graph_connected()函式中我們首先建立一個訪問標記陣列(陣列大小合適並且以0填充)。DFS呼叫完成之後,通過檢查V中是否有值為0的元素——只需呼叫一下find()函式就可以實現——就可以確認我們是否訪問到了所有結點,。
注意一下for_each:這個函式的最後一個引數,幾乎可以是任何具有函式功能的值。不僅可以是全域性函式,還可以是函式配接器,標準演算法甚至是成員函式。如果是成員函式的話,則需要成員函式或者是成員函式引用的配接器,在此我們不討論這個問題。
注:不建議使用vector<bool>。儘管在這個特定案例中這樣使用沒有問題,但最好還是避免這種做法。使用預定義的 ‘vi’ (vector<int>)。將“true”或者“false”作為int型別賦值給vi是沒有問題的。儘管這樣需要的記憶體是使用bool型的 8*sizeof(int)=8*4=32 倍,但是可以適應大多數情況並且在TopCoder上執行很快。
關於其他型別容器及其使用方法的簡要介紹
Vector由於是最簡單的陣列容器,因此非常受歡迎。在大多數情況下,你只用到vector的陣列功能。但是,有時你可能需要一個更高階的容器。
在 SRM(Single Round Match) 熱期間,研究某個STL容器的全部功能並不是一個好的做法。如果不清楚需要使用什麼容器,那麼你最好使用vector、map或者set。例如,stack可以通過vector實現,並且如果你忘記了stack容器的符號,這種方式可以執行的更快一些。
STL提供了以下容器:list、stack、queue、deque、priority_queue。我發現在SRM中,list和deque很少用到(除了在某些特殊的基於這些容器的任務中會用到)。但是,queue和priority_queue仍然有必要介紹一下。
Queue
Queue是一種具有三類操作的資料型別,所有操作的平均時間複雜度都是O(1):在頭部追加一個元素,在尾部移除一個元素,獲取第一個無法訪問的元素(“tail”)。換言之,queue是一個先進先出(FIFO)的緩衝區。
廣度優先檢索(BFS)
再次說明,如果你不熟悉BFS演算法,請首先參照一下這篇Topcoder教程(連結)。在廣度優先演算法(BFS)中使用queue是非常便捷的,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/* Graph is considered to be stored as adjacent vertices list. Also we considered graph undirected. vvi is vector< vector<int> > W[v] is the list of vertices adjacent to v */ int N; // number of vertices vvi W; // lists of adjacent vertices bool check_graph_connected_bfs() { int start_vertex = 0; vi V(N, false); queue<int> Q; Q.push(start_vertex); V[start_vertex] = true; while(!Q.empty()) { int i = Q.front(); // get the tail element from queue Q.pop(); tr(W[i], it) { if(!V[*it]) { V[*it] = true; Q.push(*it); } } } return (find(all(V), 0) == V.end()); } 注:#define tr(c,i) for(typeof((c).begin() i = (c).begin(); i != (c).end(); i++) |
更確切地說,queue 支援 front()、back()、 push()(==push_back())和 pop()( ==pop_front())操作。如果你會用到push_front()和pop_back(),就使用dequeue。Dequeue提供時間複雜度為O(1)的所有演算法。
queue和map有一個有趣的應用,用於在一幅複雜的圖中,通過BFS演算法實現最短路徑的檢索。假設我們有一幅圖,圖中的節點代表著某些複雜的東西。如:
1 2 3 4 |
pair< pair<int,int>, pair< string, vector< pair<int, int> > > > (this case is quite usual: complex data structure may define the position in some game, Rubik’s cube situation, etc…) |
假設已知我們要查詢的路徑很短,並且路徑上的位置節點很少。如果圖中所有邊的長度都為1,那麼我們可以使用BFS在這幅圖中檢索最短路徑。一段虛擬碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// Some very hard data structure typedef pair< pair<int,int>, pair< string, vector< pair<int, int> > > > POS; // ... int find_shortest_path_length(POS start, POS finish) { map<POS, int> D; // shortest path length to this position queue<POS> Q; D[start] = 0; // start from here Q.push(start); while(!Q.empty()) { POS current = Q.front(); // Peek the front element Q.pop(); // remove it from queue int current_length = D[current]; if(current == finish) { return D[current]; // shortest path is found, return its length } tr(all possible paths from 'current', it) { if(!D.count(*it)) { // same as if(D.find(*it) == D.end), see Part I // This location was not visited yet D[*it] = current_length + 1; } } } // Path was not found return -1; } // ... |
然而,如果圖中邊長不相等,那麼BFS演算法就無效了。這時我們應該使用Dijkstra演算法代替。通過 priority_queue可以實現這樣一個Dijkstra演算法,請繼續看後面的內容。
Priority_Queue
Priority_Queue是一個二進位制堆。它是一個可以執行以下操作的資料結構:
- 壓入任意元素
- 顯示頭部元素
- 彈出頭部元素
STL中priority_queue的應用請看SRM307中TrainRobber問題。
Dijkstra
在本文的最後一節,介紹一下如何利用STL容器實現稀疏圖中的Dijkstra演算法。請讀這篇教程瞭解一下Dijkstra演算法。
假設我們有一幅帶比重的有向圖,這幅有向圖是以vector<vector<pair<int,int>>>G 儲存的,在G中
- G.size() 代表有向圖中的節點數量
- G[i].size() 是從索引為i的節點直接可達的節點數量
- G[i][j].first 是從索引為i的節點直接可達的第j個節點的索引
- G[i][j].second 是連線索引為i的節點和索引為 G[i][j].first 節點的邊長
我們假設在如下兩個程式碼段中這樣定義:
1 2 3 |
typedef pair<int,int> ii; typedef vector<ii> vii; typedef vector<vii> vvii; |
通過 priority_queue 實現 Dijstra 演算法
非常感謝 misof 抽出時間給我解釋為什麼這個演算法的時間複雜度很好,儘管沒有從queue中移除獨立的元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
vi D(N, 987654321); // distance from start vertex to each vertex priority_queue<ii,vector<ii>, greater<ii> > Q; // priority_queue with reverse comparison operator, // so top() will return the least distance // initialize the start vertex, suppose it’s zero D[0] = 0; Q.push(ii(0,0)); // iterate while queue is not empty while(!Q.empty()) { // fetch the nearest element ii top = Q.top(); Q.pop(); // v is vertex index, d is the distance int v = top.second, d = top.first; // this check is very important // we analyze each vertex only once // the other occurrences of it on queue (added earlier) // will have greater distance if(d <= D[v]) { // iterate through all outcoming edges from v tr(G[v], it) { int v2 = it->first, cost = it->second; if(D[v2] > D[v] + cost) { // update distance if possible D[v2] = D[v] + cost; // add the vertex to queue Q.push(ii(D[v2], v2)); } } } } |
本文中我不想點評演算法本身,但是你應該注意到priority_queue物件的定義。一般而言,priority_queue<ii>是可以用的,但是成員函式top()將會返回佇列中最大的元素,而不是最小的。我常用的簡單解決方案之一是在pair的第一個元素不儲存偏移量而是儲存偏移量的負值。如果你想以合適的方法實現佇列的反轉,你需要實現priority_queue的反轉。priority_queue的第二個模板引數是容器的儲存型別,第三個模板引數則是比較函式的指標。
通過 set 實現 Dijkstra
在向Petr請教C#中Dijkstra的有效實現的時候,他給我講述了這個方法。在Dijkstra演算法的實現中,我們使用priority_queue向“已經分析過的結點”佇列中追加元素,平均時間複雜度和最差時間複雜度都是O(log N)。但是,除了priority_queue還有一個容器為我們提供這個功能——就是set。經過大量的實踐,我得出:基於priority_queue和基於set的Dijkstra演算法是一樣的效果。
基於set的程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
vi D(N, 987654321); // start vertex set<ii> Q; D[0] = 0; Q.insert(ii(0,0)); while(!Q.empty()) { // again, fetch the closest to start element // from “queue” organized via set ii top = *Q.begin(); Q.erase(Q.begin()); int v = top.second, d = top.first; // here we do not need to check whether the distance // is perfect, because new vertices will always // add up in proper way in this implementation tr(G[v], it) { int v2 = it->first, cost = it->second; if(D[v2] > D[v] + cost) { // this operation can not be done with priority_queue, // because it does not support DECREASE_KEY if(D[v2] != 987654321) { Q.erase(Q.find(ii(D[v2],v2))); } D[v2] = D[v] + cost; Q.insert(ii(D[v2], v2)); } } } |
另外重要的一點:STL中的priority_queue不支援DECREASE_KEY 操作,如果你需要這個操作,你最好使用於set。
我曾經花費了大量的時間來弄明白為什麼從queue(還有set)中移除元素和移除第一個元素一樣快。
這兩個實現有著同樣的複雜度並且花費一樣的時間。而且,我進行了實驗,兩種實現方式的效果幾乎相同(時間差大約是%0.1)
對我而言,我更傾向於利用set實現Dijkstra演算法,因為從邏輯上更容易理解,並且不需要記住greater<int>預示著重寫。
STL 之外的一些東西
讀到這裡,我希望你已經明白了STL是一個非常強大的工具,尤其對TopCoder SRMs來說。但是,在你使用STL之前,請記住哪些沒有包含在STL中。
首先,STL沒有BigInteger。如果SRM中的一個任務需要大量的運算,尤其是乘除運算,你有三種選擇:
- 使用預先寫好的模板
- 使用JAVA,如果你很熟練的話
- 說“啊,這真的不是我能解決的SRM任務”
我建議第一個選項。
在幾何庫中有一個幾乎相同的問題。STL不支援幾何學,所以你再次面臨著上面的三個選項。
最後一件事情——有時很煩人的事情——是STL沒有內部的字串分割函式。如果這個ExampleBuilder外掛的預設C++模板中包含這個函式,就更麻煩了。但是,我發現在一般的案例中使用istringstream(s),在複雜的案例中使用sscanf(s.c_str(), …)就足夠了。
通過這些說明,希望你能夠認識到這篇文章的價值,也希望你能發現STL是C++中一個非常有用的附加項。祝你在競賽中好運。
作者注:在本教程的兩部分中,我都建議使用模板來減少實現某些功能的時間。這個建議一直適用於程式設計師。暫且不談在SRM中使用模板是不是一個好的策略,在日常生活中,模板對於想理解程式碼的人來說是一件煩人的事情。我有時會依賴於模板,最終我決定不再使用。我鼓勵你權衡使用模板類的利弊,然後做出自己的決定。