之前寫過一篇關於「避免重複代碼——知曉你的庫」的文章。今天心血來潮,趁著窗外雨聲淅瀝,繼續來聊聊這個話題。
上週某公司來校招聘,C++ 筆試題中有一道要求對包含自定義結構體的 vector 進行排序。結構體包含兩個欄位:int num 和 string name;排序規則為先按 num 升序,若相同則按 name 升序。原題結構體並未重載 < 或 == 運算符,且 vector 存放的是實例而非指標。
聽說考完試的同學們在機房討論該如何手寫排序演算法。但這是 C++ 呀,標準庫 std::sort 早已解決此類問題,何必重複造輪子?
最直接的做法是為 Person 特化 std::less,作為比較函子傳給 std::sort:
#include <algorithm>
#include <functional>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
struct Person {
int num;
string name;
Person(int num, string name) : num(num), name(name) {}
};
namespace std {
template<>
struct less<Person> {
bool operator()(const Person& p1, const Person& p2) {
if (p1.num < p2.num) return true;
if (p1.num == p2.num && p1.name < p2.name) return true;
return false;
}
};
}
struct printElement {
void operator()(const Person& p) {
cout << "Person: " << p.num << ", " << p.name << endl;
}
};
int main() {
vector<Person> v;
v.push_back(Person(2, "smith"));
v.push_back(Person(1, "john"));
v.push_back(Person(2, "micheal"));
v.push_back(Person(1, "micheal"));
v.push_back(Person(3, "albert"));
sort(v.begin(), v.end(), less<Person>());
for_each(v.begin(), v.end(), printElement());
return 0;
}
輸出結果:
Person: 1, john Person: 1, micheal Person: 2, micheal Person: 2, smith Person: 3, albert
這樣就能利用 STL 高效、穩定的排序實現,避免手寫演算法帶來的 Bug 風險與維護成本。當然,程序員仍需掌握排序原理,但在生產環境中,優先使用成熟庫才是明智之舉。
隨著 C++11 標準發布,語法更加簡潔。使用 Lambda 表達式內聯比較邏輯,搭配範圍基礎的 for 迴圈遍歷:
#include <algorithm>
#include <functional>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
struct Person {
int num;
string name;
Person(int num, string name) : num(num), name(name) {}
};
int main() {
vector<Person> v;
v.push_back(Person(2, "smith"));
v.push_back(Person(1, "john"));
v.push_back(Person(2, "micheal"));
v.push_back(Person(1, "micheal"));
v.push_back(Person(3, "albert"));
sort(v.begin(), v.end(), [](const Person& p1, const Person& p2) {
if (p1.num < p2.num) return true;
if (p1.num == p2.num && p1.name < p2.name) return true;
return false;
});
for (const Person& p : v) {
cout << "Person: " << p.num << ", " << p.name << endl;
}
return 0;
}
註:VS2010 CTP 已支援部分 C++11 特性(如 Lambda),但範圍 for 因依賴 Concepts 機制(當時未定案)而暫不支援。
若結構體有公認的自然順序,直接重載 operator< 即可使用兩參數版 sort()。但題目限制不得修改結構體,故採用上述方式。
順便對比一下函數式語言與腳本語言的表達力:
let l = [(2, "smith"); (1, "john"); (2, "micheal"); (1, "micheal"); (3, "albert")] List.sort compare l // val it : (int * string) list = [(1, "john"); (1, "micheal"); (2, "micheal"); (2, "smith"); (3, "albert")]
l = [[2, "smith"], [1, "john"], [2, "micheal"], [1, "micheal"], [3, "albert"]] l.sort #=> [[1, "john"], [1, "micheal"], [2, "micheal"], [2, "smith"], [3, "albert"]]
腳本語言若連這點便利性都沒有,存在的意義何在?
牢騷到此結束,核心觀點不變:知曉你的庫,避免重複造輪子。