# 第?13?章?容器
### 目錄
* [13.1 概述](containers.html#containers_general)
* [13.2 Boost.Array](containers.html#containers_array)
* [13.3 Boost.Unordered](containers.html#containers_unordered)
* [13.4 Boost.MultiIndex](containers.html#containers_multiindex)
* [13.5 Boost.Bimap](containers.html#containers_bimap)
* [13.6 練習](containers.html#containers_exercises)
[](http://creativecommons.org/licenses/by-nc-nd/3.0/de/deed.zh) 該書采用 [Creative Commons License](http://creativecommons.org/licenses/by-nc-nd/3.0/de/deed.zh) 授權
## 13.1.?概述
這一章將會介紹許多我們在 C++ 標準中已經很熟悉的容器的 Boost 版本。 在這一章里, 我們會對 Boost.Unordered 的用法有一定的了解 (這個容器已經在 TR1 里被加入到了 C++ 標準); 我們將會學習如何定義一個 Boost.MultiIndex; 我們還會了解何時應該使用 MuitiIndex 的一個特殊的擴展 —— Boost.Bimap。 接下來, 我們會向你介紹第一個容器 —— Boost.Array, 通過使用它, 你可以把 C++ 標準里普通的數組以容器的形式實現。
## 13.2.?Boost.Array
庫 [Boost.Array](http://www.boost.org/libs/array/) 在 `boost/array.hpp` 中定義了一個模板類 `boost::array` 。 通過使用這個類, 你可以創建一個跟 C++ 里傳統的數組有著相同屬性的容器。 而且, `boost::array` 還滿足了 C++ 中容器的一切需求, 因此, 你可以像操作容器一樣方便的操作這個 array。 基本上, 你可以把 `boost::array` 當成 `std::vector` 來使用, 只不過 `boost::array` 是定長的。
```
#include <boost/array.hpp>
#include <iostream>
#include <string>
#include <algorithm>
int main()
{
typedef boost::array<std::string, 3> array;
array a;
a[0] = "Boris";
a.at(1) = "Anton";
*a.rbegin() = "Caesar";
std::sort(a.begin(), a.end());
for (array::const_iterator it = a.begin(); it != a.end(); ++it)
std::cout << *it << std::endl;
std::cout << a.size() << std::endl;
std::cout << a.max_size() << std::endl;
}
```
* [下載源代碼](src/13.2.1/main.cpp)
就像我們在上面的例子看到的那樣, `boost::array` 簡直就是簡單的不需要任何多余的解釋, 因為所有操作都跟 `std::vector` 是一樣的。
在下面的例子里, 我們會見識到 Boost.Array 的一個特性。
```
#include <boost/array.hpp>
#include <string>
int main()
{
typedef boost::array<std::string, 3> array;
array a = { "Boris", "Anton", "Caesar" };
}
```
* [下載源代碼](src/13.2.2/main.cpp)
一個 `boost::array` 類型的數組可以使用傳統 C++ 數組的初始化方式來初始化。
既然這個容器已經在 TR1 中加入到了 C++ 標準, 它同樣可以通過 `std::array` 來訪問到。 他被定義在頭文件 `array` 中, 使用它的前提是你正在使用一個支持 TR1 的 C++ 標準的庫。
## 13.3.?Boost.Unordered
[Boost.Unordered](http://www.boost.org/libs/unordered/) 在 C++ 標準容器 `std::set`, `std::multiset`, `std::map` 和 `std::multimap` 的基礎上多實現了四個容器: `boost::unordered_set`, `boost::unordered_multiset`, `boost::unordered_map` 和 `boost::unordered_multimap`。 那些名字很相似的容器之間并沒有什么不同, 甚至還提供了相同的接口。 在很多情況下, 替換這兩種容器 (std 和 boost) 對你的應用不會造成任何影響。
Boost.Unordered 和 C++ 標準里的容器的不同之處在于—— Boost.Unordered 不要求其中的元素是可排序的, 因為它不會做出排序操作。 在排序操作無足輕重時(或是根本不需要), Boost.Unordered 就很合適了。
為了能夠快速的查找元素, 我們需要使用 Hash 值。 Hash 值是一些可以唯一標識容器中元素的數字, 它在比較時比起類似 String 的數據類型會更加有效率。 為了計算 Hash 值, 容器中的所有元素都必須支持對他們自己唯一 ID 的計算。 比如 `std::set` 要求其中的元素都要是可比較的, 而 `boost::unordered_set` 要求其中的元素都要可計算 Hash 值。 盡管如此, 在對排序沒有需求時, 你還是應該傾向使用 Boost.Unordered 。
下面的例子展示了 `boost::unordered_set` 的用法。
```
#include <boost/unordered_set.hpp>
#include <iostream>
#include <string>
int main()
{
typedef boost::unordered_set<std::string> unordered_set;
unordered_set set;
set.insert("Boris");
set.insert("Anton");
set.insert("Caesar");
for (unordered_set::iterator it = set.begin(); it != set.end(); ++it)
std::cout << *it << std::endl;
std::cout << set.size() << std::endl;
std::cout << set.max_size() << std::endl;
std::cout << (set.find("David") != set.end()) << std::endl;
std::cout << set.count("Boris") << std::endl;
}
```
* [下載源代碼](src/13.3.1/main.cpp)
`boost::unordered_set` 提供了與 `std::set` 相似的函數。 當然, 這個例子不需要多大改進就可以用 `std::set` 來實現。
下面的例子展示了如何用 `boost::unordered_map` 來存儲每一個的 person 的 name 和 age。
```
#include <boost/unordered_map.hpp>
#include <iostream>
#include <string>
int main()
{
typedef boost::unordered_map<std::string, int> unordered_map;
unordered_map map;
map.insert(unordered_map::value_type("Boris", 31));
map.insert(unordered_map::value_type("Anton", 35));
map.insert(unordered_map::value_type("Caesar", 25));
for (unordered_map::iterator it = map.begin(); it != map.end(); ++it)
std::cout << it->first << ", " << it->second << std::endl;
std::cout << map.size() << std::endl;
std::cout << map.max_size() << std::endl;
std::cout << (map.find("David") != map.end()) << std::endl;
std::cout << map.count("Boris") << std::endl;
}
```
* [下載源代碼](src/13.3.2/main.cpp)
就像我們看到的, `boost::unordered_map` 和 `std::map` 之間并沒多大區別。 同樣地, 你可以很方便的用 `std::map` 來重新實現這個例子。
就像上面提到過的, Boost.Unordered 需要其中的元素可計算 Hash 值。 一些類似于 `std::string` 的數據類型“天生”就支持 Hash 值的計算。 對于那些自定義的類型, 你需要手動的定義 Hash 函數。
```
#include <boost/unordered_set.hpp>
#include <string>
struct person
{
std::string name;
int age;
person(const std::string &n, int a)
: name(n), age(a)
{
}
bool operator==(const person &p) const
{
return name == p.name && age == p.age;
}
};
std::size_t hash_value(person const &p)
{
std::size_t seed = 0;
boost::hash_combine(seed, p.name);
boost::hash_combine(seed, p.age);
return seed;
}
int main()
{
typedef boost::unordered_set<person> unordered_set;
unordered_set set;
set.insert(person("Boris", 31));
set.insert(person("Anton", 35));
set.insert(person("Caesar", 25));
}
```
* [下載源代碼](src/13.3.3/main.cpp)
在代碼中, `person` 類型的元素被存到了 `boost::unordered_set` 中。 因為 `boost::unordered_set` 中的 Hash 函數不能識別 `person` 類型, Hash 值就變得無法計算了。 若果沒有定義另一個 Hash 函數, 你的代碼將不會通過編譯。
Hash 函數的簽名必須是: `hash_value()`。 它接受唯一的一個參數來指明需要計算 Hash 值的對象的類型。 因為 Hash 值是單純的數字, 所以函數的返回值為: `std::size_t`。
每當一個對象需要計算它的 Hash 值時, `hash_value()` 都會自動被調用。 Boost C++ 庫已經為一些數據類型定義好了 Hash 函數, 比如: `std::string`。 但對于像 `person` 這樣的自定義類型, 你就需要自己手工定義了。
`hash_value()` 的實現往往都很簡單: 你只需要按順序對其中的每個屬性都調用 Boost 在 `boost/functional/hash.hpp` 中提供的 `boost::hash_combine()` 函數就行了。 當你使用 Boost.Unordered 時, 這個頭文件已經自動被包含了。
除了自定義 `hash_value()` 函數, 自定義的類型還需要支持通過 `==` 運算符的比較操作。 因此, `person` 就重載了相應的 `operator==()` 操作符。
## 13.4.?Boost.MultiIndex
[Boost.MultiIndex](http://www.boost.org/libs/multi_index/) 比我們之前介紹的任何庫都要復雜。 不像 Boost.Array 和 Boost.Unordered 為我們提供了可以直接使用的容器, Boost.MultiIndex 讓我們可以自定義新的容器。 跟 C++ 標準中的容器不同的是: 一個用戶自定義的容器可以對其中的數據提供多組訪問接口。 舉例來說, 你可以定義一個類似于 `std::map` 的容器, 但它可以通過 value 值來查詢。 如果不用 Boost.MultiIndex, 你就需要自己整合兩個 `std::map` 類型的容器, 還要自己處理一些同步操作來確保數據的完整性。
下面這個例子就用 Boost.MultiIndex 定義了一個新容器來存儲每個人的 name 和 age, 不像 `std::map`, 這個容器可以分別通過 name 和 age 來查詢(std::map 只能用一個值)。
```
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <iostream>
#include <string>
struct person
{
std::string name;
int age;
person(const std::string &n, int a)
: name(n), age(a)
{
}
};
typedef boost::multi_index::multi_index_container<
person,
boost::multi_index::indexed_by<
boost::multi_index::hashed_non_unique<
boost::multi_index::member<
person, std::string, &person::name
>
>,
boost::multi_index::hashed_non_unique<
boost::multi_index::member<
person, int, &person::age
>
>
>
> person_multi;
int main()
{
person_multi persons;
persons.insert(person("Boris", 31));
persons.insert(person("Anton", 35));
persons.insert(person("Caesar", 25));
std::cout << persons.count("Boris") << std::endl;
const person_multi::nth_index<1>::type &age_index = persons.get<1>();
std::cout << age_index.count(25) << std::endl;
}
```
* [下載源代碼](src/13.4.1/main.cpp)
就像上面提到的, Boost.MultiIndex 并沒有提供任何特定的容器而是一些類來方便我們定義新的容器。 典型的做法是: 你需要用到 `typedef` 來為你的新容器提供對 Boost.MultiIndex 中類的方便的訪問。
每個容器定義都需要的類 `boost::multi_index::multi_index_container` 被定義在了 `boost/multi_index_container.hpp` 里。 因為他是一個模板類, 你需要為它傳遞兩個模板參數。 第一個參數是容器中儲存的元素類型, 在例子中是 `person`; 而第二個參數指明了容器所提供的所有索引類型。
基于 Boost.MultiIndex 的容器最大的優勢在于: 他對一組同樣的數據提供了多組訪問接口。 訪問接口的具體細節都可以在定義容器時被指定。 因為例子中的 person 為 age 和 name 都提供了查詢功能, 我們必須要定義兩組接口。
接口的定義必須借由模板類 `boost::multi_index::indexed_by` 來實現。 每一個接口都作為參數傳遞給它。 例子中定義了兩個 `boost::multi_index::hashed_non_unique` 類型的接口,(定義在頭文件 `boost/multi_index/hashed_index.hpp` 中) 如果你希望容器像 Boost.Unordered 一樣存儲一些可以計算 Hash 值的元素, 你就可以使用這個接口。
`boost::multi_index::hashed_non_unique` 是一個模板類, 他需要一個可計算 Hash 值的類型作為它的參數。 因為接口需要訪問 person 中的 name 和 age, 所以 name 和 age 都要是可計算 Hash 值的。
Boost.MultiIndex 提供了一個輔助模板類: `boost::multi_index::member` (定義在 `boost/multi_index/member.hpp` 中) 來訪問類中的屬性。 就像我們在例子中所看到的, 我們指定了好幾個參數來讓 `boost::multi_index::member` 明白可以訪問 `person` 中的哪些屬性以及這些屬性的類型。
不得不說 `person_multi` 的定義第一眼看起來相當復雜, 但這個類本身跟 Boost.Unordered 中的 `boost::unordered_map` 并沒有什么不同, 他也可以分別通過其中的兩個屬性 name 和 age 來查詢容器。
為了訪問 MultiIndex 容器, 你必須要定義至少一個接口。 如果用 `insert()` 或者 `count()` 來直接訪問 `persons` 對象, 第一個接口會被隱式的調用 —— 在例子中是 `name` 屬性的 Hash 容器。 如果你想用其他接口, 你必須要顯示的指定它。
接口都是用從0開始的索引值來編號的。 想要訪問第二個接口, 你需要調用 `get()` 函數并且傳入想要訪問的接口的索引值。
函數 `get()` 的返回值看起來也有點復雜: 他是一個用來訪問 MultiIndex 容器的類 `nth_index` , 同樣的, 你也需要指定需要訪問的接口的索引值。 當然, 這個值肯定跟 `get()` 函數指定的模板參數是一樣的。 最后一步: 用 `::` 來得到 `nth_index` 的 `type`, 也就是接口的真正的`type`。
雖然我們并不知道細節就用 `nth_index` 和 `type` 得到了接口, 我們還是需要明白這到底是什么接口。 通過傳給 `get()` 和 `nth_index` 的索引值, 我們就可以很容易得知所使用的哪一個接口了。 例子中的 `age_index` 就是一個通過 age 來訪問的 Hash 容器。
既然 MultiIndex 容器中的 name 和 key 作為了接口訪問的鍵值, 他們都不能再被更改了。 比如一個 person 的 age 在通過 name 搜索以后被改變了, 使用 age 作為鍵值的接口卻意識不到這種更改, 因此, 你需要重新計算 Hash 值才行。
就像 `std::map` 一樣, MultiIndex 容器中的值也不允許被修改。 嚴格的說, 所有存儲在 MultiIndex 中的元素都該是常量。 為了避免刪除或修改其中元素真正的值, Boost.MultiIndex 提供了一些常用函數來操作其中的元素。 使用這些函數來操作 MultiIndex 容器中的值并不會引起那些元素所指向的真正的對象改變, 所以更新動作是安全的。 而且所有接口都會被通知這種改變, 然后去重新計算新的 Hash 值等。
```
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <iostream>
#include <string>
struct person
{
std::string name;
int age;
person(const std::string &n, int a)
: name(n), age(a)
{
}
};
typedef boost::multi_index::multi_index_container<
person,
boost::multi_index::indexed_by<
boost::multi_index::hashed_non_unique<
boost::multi_index::member<
person, std::string, &person::name
>
>,
boost::multi_index::hashed_non_unique<
boost::multi_index::member<
person, int, &person::age
>
>
>
> person_multi;
void set_age(person &p)
{
p.age = 32;
}
int main()
{
person_multi persons;
persons.insert(person("Boris", 31));
persons.insert(person("Anton", 35));
persons.insert(person("Caesar", 25));
person_multi::iterator it = persons.find("Boris");
persons.modify(it, set_age);
const person_multi::nth_index<1>::type &age_index = persons.get<1>();
std::cout << age_index.count(32) << std::endl;
}
```
* [下載源代碼](src/13.4.2/main.cpp)
每個 Boost.MultiIndex 中的接口都支持 `modify()` 函數來提供直接對容器本身的操作。 它的第一個參數是一個需要更改對象的迭代器; 第二參數則是一個對該對象進行操作的函數。 在例子中, 對應的兩個參數則是: `person` 和 `set_age()` 。
至此, 我們都還只介紹了一個接口: `boost::multi_index::hashed_non_unique` , 他會計算其中元素的 Hash 值, 但并不要求是唯一的。 為了確保容器中存儲的值是唯一的, 你可以使用 `boost::multi_index::hashed_unique` 接口。 請注意: 所有要被存入容器中的值都必須滿足它的接口的限定。 只要一個接口限定了容器中的值必須是唯一的, 那其他接口都不會對該限定造成影響。
```
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <iostream>
#include <string>
struct person
{
std::string name;
int age;
person(const std::string &n, int a)
: name(n), age(a)
{
}
};
typedef boost::multi_index::multi_index_container<
person,
boost::multi_index::indexed_by<
boost::multi_index::hashed_non_unique<
boost::multi_index::member<
person, std::string, &person::name
>
>,
boost::multi_index::hashed_unique<
boost::multi_index::member<
person, int, &person::age
>
>
>
> person_multi;
int main()
{
person_multi persons;
persons.insert(person("Boris", 31));
persons.insert(person("Anton", 31));
persons.insert(person("Caesar", 25));
const person_multi::nth_index<1>::type &age_index = persons.get<1>();
std::cout << age_index.count(31) << std::endl;
}
```
* [下載源代碼](src/13.4.3/main.cpp)
上例中的容器現在使用了 `boost::multi_index::hashed_unique` 來作為他的第二個接口, 因此他不允許其中有兩個同 age 的 person 存在。
上面的代碼嘗試存儲一個與 Boris 同 age 的 Anton, 因為這個動作違反了容器第二個接口的限定, 它(Anton)將不會被存入到容器中。 因此, 程序將會輸出: `1` 而不是2。
接下來的例子向我們展示了 Boost.MultiIndex 中剩下的三個接口: `boost::multi_index::sequenced`, `boost::multi_index::ordered_non_unique` 和 `boost::multi_index::random_access`。
```
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/member.hpp>
#include <iostream>
#include <string>
struct person
{
std::string name;
int age;
person(const std::string &n, int a)
: name(n), age(a)
{
}
};
typedef boost::multi_index::multi_index_container<
person,
boost::multi_index::indexed_by<
boost::multi_index::sequenced<>,
boost::multi_index::ordered_non_unique<
boost::multi_index::member<
person, int, &person::age
>
>,
boost::multi_index::random_access<>
>
> person_multi;
int main()
{
person_multi persons;
persons.push_back(person("Boris", 31));
persons.push_back(person("Anton", 31));
persons.push_back(person("Caesar", 25));
const person_multi::nth_index<1>::type &ordered_index = persons.get<1>();
person_multi::nth_index<1>::type::iterator lower = ordered_index.lower_bound(30);
person_multi::nth_index<1>::type::iterator upper = ordered_index.upper_bound(40);
for (; lower != upper; ++lower)
std::cout << lower->name << std::endl;
const person_multi::nth_index<2>::type &random_access_index = persons.get<2>();
std::cout << random_access_index[2].name << std::endl;
}
```
* [下載源代碼](src/13.4.4/main.cpp)
`boost::multi_index::sequenced` 接口讓我們可以像使用 `std::list` 一樣的使用 MultiIndex。 這個接口定義起來十分容易: 你不用為它傳遞任何模板參數。 `person` 類型的對象在容器中就是像 list 一樣按照加入的順序來排列的。
而通過使用 `boost::multi_index::ordered_non_unique` 接口, 容器中的對象會自動的排序。 你在定義容器時就必須指定接口的排序規則。 示例中的對象 `person` 就是以 age 來排序的, 它借助了輔助類 `boost::multi_index::member` 來實現這一功能。
`boost::multi_index::ordered_non_unique` 為我們提供了一些特別的函數來查找特定范圍的數據。 通過使用 `lower_bound()` 和 `upper_bound()`, 示例實現了對所有 30 歲至 40 歲的 person 的查詢。 要注意因為容器中的數據是有序的, 所以才提供了這些函數, 其他接口中并不提供這些函數。
最后一個接口是: `boost::multi_index::random_access`, 他讓我們可以像使用 `std::vector` 一樣使用 MultiIndex 容器。 你又可以使用你熟悉的 `operator[]()` 和 `at()` 操作了。
請注意 `boost::multi_index::random_access` 已經被完整的包含在了 `boost::multi_index::sequenced` 接口中。 所以當你使用 `boost::multi_index::random_access` 的時候, 你也可以使用 `boost::multi_index::sequenced` 接口中的所有函數。
在介紹完 Boost.MultiIndex 剩下的4個接口后, 本章剩下的部分將向你介紹所謂的“鍵值提取器”(key extractors)。 目前為止, 我們已經見過一個在 `boost/multi_index/member.hpp` 定義的鍵值提取器了—— `boost::multi_index::member` 。 這個輔助函數的得名源自它可以顯示的聲明類中的哪些屬性會作為接口中的鍵值使用。
接下來的例子介紹了另外兩個鍵值提取器。
```
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <iostream>
#include <string>
class person
{
public:
person(const std::string &n, int a)
: name(n), age(a)
{
}
bool operator<(const person &p) const
{
return age < p.age;
}
std::string get_name() const
{
return name;
}
private:
std::string name;
int age;
};
typedef boost::multi_index::multi_index_container<
person,
boost::multi_index::indexed_by<
boost::multi_index::ordered_unique<
boost::multi_index::identity<person>
>,
boost::multi_index::hashed_unique<
boost::multi_index::const_mem_fun<
person, std::string, &person::get_name
>
>
>
> person_multi;
int main()
{
person_multi persons;
persons.insert(person("Boris", 31));
persons.insert(person("Anton", 31));
persons.insert(person("Caesar", 25));
std::cout << persons.begin()->get_name() << std::endl;
const person_multi::nth_index<1>::type &hashed_index = persons.get<1>();
std::cout << hashed_index.count("Boris") << std::endl;
}
```
* [下載源代碼](src/13.4.5/main.cpp)
鍵值提取器`boost::multi_index::identity`(定義在 `boost/multi_index/identity.hpp` 中) 可以使用容器中的數據類型作為鍵值。 示例中, 就需要 `person` 類是可排序的, 因為它已經作為了接口 `boost::multi_index::ordered_unique` 的鍵值。 在示例里, 它是通過重載 `operator<()` 操作符來實現的。
頭文件 `boost/multi_index/mem_fun.hpp` 定義了兩個可以把函數返回值作為鍵值的鍵值提取器: `boost::multi_index::const_mem_fun` 和 `boost::multi_index::mem_fun` 。 在示例程序中, 就是用到了 `get_name()` 的返回值作為鍵值。 顯而易見的, `boost::multi_index::const_mem_fun` 適用于返回常量的函數, 而 `boost::multi_index::mem_fun` 適用于返回非常量的函數。
Boost.MultiIndex 還提供了兩個鍵值提取器: `boost::multi_index::global_fun` 和 `boost::multi_index::composite_key`。 前一個適用于獨立的函數或者靜態函數, 后一個允許你將幾個鍵值提取器組合成一個新的的鍵值提取器。
## 13.5.?Boost.Bimap
[Boost.Bimap](http://www.boost.org/libs/bimap/) 庫提供了一個建立在 Boost.MultiIndex 之上但不需要預先定義就可以使用的容器。 這個容器十分類似于 `std::map`, 但他不僅可以通過 key 搜索, 還可以用 value 來搜索。
```
#include <boost/bimap.hpp>
#include <iostream>
#include <string>
int main()
{
typedef boost::bimap<std::string, int> bimap;
bimap persons;
persons.insert(bimap::value_type("Boris", 31));
persons.insert(bimap::value_type("Anton", 31));
persons.insert(bimap::value_type("Caesar", 25));
std::cout << persons.left.count("Boris") << std::endl;
std::cout << persons.right.count(31) << std::endl;
}
```
* [下載源代碼](src/13.5.1/main.cpp)
在 `boost/bimap.hpp` 中定義的 `boost::bimap` 為我們提供了兩個屬性: `left` 和 `right` 來訪問在 `boost::bimap` 統一的兩個 `std::map` 類型的容器。 在例子中, `left` 用 `std::string` 類型的 key 來訪問容器, 而 `right` 用到了 `int` 類型的 key。
除了支持用 left 和 right 對容器中的記錄進行單獨的訪問, `boost::bimap` 還允許像下面的例子一樣展示記錄間的關聯關系。
```
#include <boost/bimap.hpp>
#include <iostream>
#include <string>
int main()
{
typedef boost::bimap<std::string, int> bimap;
bimap persons;
persons.insert(bimap::value_type("Boris", 31));
persons.insert(bimap::value_type("Anton", 31));
persons.insert(bimap::value_type("Caesar", 25));
for (bimap::iterator it = persons.begin(); it != persons.end(); ++it)
std::cout << it->left << " is " << it->right << " years old." << std::endl;
}
```
* [下載源代碼](src/13.5.2/main.cpp)
對一個記錄訪問時, `left` 和 `right` 并不是必須的。 你也可以使用迭代器來訪問每個記錄中的 left 和 right 容器。
`std::map` 和 `std::multimap` 組合讓你覺得似乎可以存儲多個具有相同 key 值的記錄, 但 `boost::bimap` 并沒有這樣做。 但這并不代表在 `boost::bimap` 存儲兩個具有相同 key 值的記錄是不可能的。 嚴格來說, 那兩個模板參數并不會對 `left` 和 `right` 的容器類型做出具體的規定。 如果像例子中那樣并沒有指定容器類型時, `boost::bimaps::set_of` 類型會缺省的使用。 跟 `std::map` 一樣, 它要求記錄有唯一的 key 值。
第一個 `boost::bimap` 例子也可以像下面這樣寫。
```
#include <boost/bimap.hpp>
#include <iostream>
#include <string>
int main()
{
typedef boost::bimap<boost::bimaps::set_of<std::string>, boost::bimaps::set_of<int>> bimap;
bimap persons;
persons.insert(bimap::value_type("Boris", 31));
persons.insert(bimap::value_type("Anton", 31));
persons.insert(bimap::value_type("Caesar", 25));
std::cout << persons.left.count("Boris") << std::endl;
std::cout << persons.right.count(31) << std::endl;
}
```
* [下載源代碼](src/13.5.3/main.cpp)
除了 `boost::bimaps::set_of`, 你還可以用一些其他的容器類型來定制你的 `boost::bimap`。
```
#include <boost/bimap.hpp>
#include <boost/bimap/multiset_of.hpp>
#include <iostream>
#include <string>
int main()
{
typedef boost::bimap<boost::bimaps::set_of<std::string>, boost::bimaps::multiset_of<int>> bimap;
bimap persons;
persons.insert(bimap::value_type("Boris", 31));
persons.insert(bimap::value_type("Anton", 31));
persons.insert(bimap::value_type("Caesar", 25));
std::cout << persons.left.count("Boris") << std::endl;
std::cout << persons.right.count(31) << std::endl;
}
```
* [下載源代碼](src/13.5.4/main.cpp)
代碼中的容器使用了定義在 `boost/bimap/multiset_of.hpp` 中的 `boost::bimaps::multiset_of`。 這個容器的操作和 `boost::bimaps::set_of` 差不了多少, 只是它不再要求 key 值是唯一的。 因此, 上面的例子將會在計算 age 為 31 的 person 數時輸出: `2`。
既然 `boost::bimaps::set_of` 會在定義 `boost::bimap` 被缺省的使用, 你沒必要再顯示的包含頭文件: `boost/bimap/set_of.hpp`。 但在使用其它類型的容器時, 你就必須要顯示的包含一些相應的頭文件了。
Boost.Bimap 還提供了類: `boost::bimaps::unordered_set_of`, `boost::bimaps::unordered_multiset_of`, `boost::bimaps::list_of`,`boost::bimaps::vector_of` 和 `boost::bimaps::unconstrainted_set_of` 以供使用。 除了 `boost::bimaps::unconstrainted_set_of`, 剩下的所有容器類型的使用方法都和他們在 C++ 標準里的版本一樣。
```
#include <boost/bimap.hpp>
#include <boost/bimap/unconstrained_set_of.hpp>
#include <boost/bimap/support/lambda.hpp>
#include <iostream>
#include <string>
int main()
{
typedef boost::bimap<std::string, boost::bimaps::unconstrained_set_of<int>> bimap;
bimap persons;
persons.insert(bimap::value_type("Boris", 31));
persons.insert(bimap::value_type("Anton", 31));
persons.insert(bimap::value_type("Caesar", 25));
bimap::left_map::iterator it = persons.left.find("Boris");
persons.left.modify_key(it, boost::bimaps::_key = "Doris");
std::cout << it->first << std::endl;
}
```
* [下載源代碼](src/13.5.5/main.cpp)
`boost::bimaps::unconstrainted_set_of` 可以使 `boost::bimap` 的 `right` (也就是 age)值無法用來查找 person。 在這種特定的情況下, `boost::bimap` 可以被視為是一個 `std::map` 類型的容器。
雖然如此, 例子還是向我們展示了 `boost::bimap` 對于 `std::map` 的優越性。 因為 Boost.Bimap 是基于 Boost.MultiIndex 的, 你當然可以使用 Boost.MultiIndex 提供的所有函數。 例子中就用 `modify_key()` 修改了 key 值, 這在 `std::map` 中是不可能的。
請注意修改 key 值的以下細節: key 通過 `boost::bimaps::_key` 函數賦予了新值, 而 `boost::bimaps::_key` 是一個定義在 `boost/bimap/support/lambda.hpp` 中的 lambda 函數。 有關 lambda 函數, 詳見:[第?3?章 _函數對象_](functionobjects.html "第?3?章?函數對象")。
`boost/bimap/support/lambda.hpp` 還定義了 `boost::bimaps::_data`。 函數 `modify_data()` 可以用來修改 `boost::bimap` 中的 value 值。
## 13.6.?練習
You can buy [solutions to all exercises](http://en.highscore.de/shop/index.php?p=boost-solution) in this book as a ZIP file.
1. 創建你的應用: 它支持指派員工到一家公司不同的部門。 存入一些示例記錄, 然后通過指定員工來得到他所在的部門, 再通過指定部門來得到該部門的員工數。
2. 擴展你的應用: 加入員工的 ID 號。 ID 號必須是唯一的, 保證在員工同名的情況下依然可以唯一的標識員工。 通過指定 ID 號來得到某個員工的信息并把它輸出以驗證正確性。