1. 概论:
关联容器和顺序容器的本质差别在于:关联容器通过键(key)存储和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素。
关联容器类型
map |
Associative array; elements stored and retrieved by key 关联数组:元素通过键来存储和读取 |
set |
Variable-sized collection with fast retrieval by key 大小可变的集合,支持通过键实现的快速读取 |
multimap |
map in which a key can appear multiple times 支持同一个键多次出现的 map 类型 |
multiset |
set in which a key can appear multiple times 支持同一个键多次出现的 set 类型 |
2.pair类型
在 utility 头文件中定义。
pairs 类型提供的操作
pair<T1, T2> p1; |
Create an empty pair with two elements of types T1 and T2. The elements are value-initialized 创建一个空的 pair 对象,它的两个元素分别是 T1 和 T2 类型,采用值初始化 |
|
pair<T1, T2> p1(v1, v2); |
|
|
|
Create a pair with types T1 and T2 initializing the first member from v1 and the second from v2. 创建一个 pair 对象,它的两个元素分别是 T1 和 T2 ,其中 first 成员初始化为 v1,而 second 成员初始化为 v2 |
|
make_pair(v1, v2) |
Creates a new pair from the values v1 and v2. The type of the pair is inferred from the types of v1 and v2. 以 v1 和 v2 值创建一个新 pair 对象,其元素类型分别是 v1 和 v2 的类型 |
|
p1 < p2 |
Less than between two pair objects. Less than is defined as dictionary ordering: Returns true if p1.first < p2.first or if !(p2.first < p1.first) && p1.second < p2.second. 两个 pair 对象之间的小于运算,其定义遵循字典次序:如果 p1.first < p2.first 或者 !(p2.first < p1.first) && p1.second < p2.second,则返回 true |
|
p1 == p2 |
Two pairs are equal if their first and second members are respectively equal. Uses the underlying element == operator. 如果两个 pair 对象的 first 和 second 成员依次相等,则这两个对象相等。该运算使用其元素的 == 操作符 |
|
p.first |
Returns the (public) data member of p named first. 返回 p 中名为 first 的(公有)数据成员 |
|
p.second |
Returns the (public) data member of p named second. 返回 p 的名为 second 的(公有)数据成员 |
使用typedef简化声明:
typedef pair<string, string> Author;
Author proust("Marcel", "Proust");
与其他标准库类型不同,对于 pair 类,可以直接访问其数据成员:其成员都是仅有的,分别命名为 first 和 second。只需使用普通的点操作符——成员访问标志即可访问其成员:
string firstBook;
// access and test the data members of the pair
if (author.first == "James" && author.second == "Joyce")
firstBook = "Stephen Hero";
除了构造函数,标准库还定义了一个 make_pair 函数,由传递给它的两个实参生成一个新的 pair 对象。可如下使用该函数创建新的 pair 对象,并赋给已存在的 pair 对象:
pair<string, string> next_auth;
string first, last;
while (cin >> first >> last) {
// generate a pair from first and last
next_auth = make_pair(first, last);
// process next_auth...
}
3. 关联容器:
关联容器共享大部分——但并非全部——的顺序容器操作。关联容器不提供 front、push_front、 pop_front、back、push_back 以及 pop_back 操作。
“容器元素根据键的次序排列”这一事实就是一个重要的结论:在迭代遍历关联容器时,我们可确保按键的顺序的访问元素,而与元素在容器中的存放位置完全无关。
4. map类型
map 是键-值对的集合。map 类型通常可理解为关联数组(associative array):可使用键作为下标来获取一个值,正如内置数组类型一样。而关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置来获取。
使用map必须包含 map 头文件。在定义 map 对象时,必须分别指明键和值的类型(value type)
// count number of times each word occurs in the input
map<string, int> word_count; // empty map from string to int
map 的构造函数
map<k, v> m; |
Create an empty map named m with key and value types k and v. 创建一个名为 m 的空 map 对象,其键和值的类型分别为 k 和 v |
map<k, v> m(m2); |
Create m as a copy of m2; m and m2 must have the same key and value types. 创建 m2 的副本 m,m 与 m2 必须有相同的键类型和值类型 |
map<k, v> m(b, e); |
Create m as a copy of the elements from the range denoted by iterators b and e. Elements must have a type that can be converted to pair<const k, v>. 创建 map 类型的对象 m,存储迭代器 b 和 e 标记的范围内所有元素的副本。元素的类型必须能转换为 pair<const k, v> |
在实际应用中,键类型必须定义 < 操作符,而且该操作符应能“正确地工作”,这一点很重要。
map 对象的元素是键-值对,也即每个元素包含两个部分:键以及由键关联的值。map 的 value_type 是存储元素的键以及值的 pair 类型,而且键为 const。例如,word_count 数组的 value_type 为 pair<const string, int> 类型。
map 类定义的类型
map<K, V>::key_type |
The type of the keys used to index the map. 在 map 容器中,用做索引的键的类型 |
map<K, V>::mapped_type |
The type of the values associated with the keys in the map. 在 map 容器中,键所关联的值的类型 |
map<K, V>::value_type |
A pair whose first element has type const map<K, V>::key_type and second has type map<K, V>::mapped_type. 一个 pair 类型,它的 first 元素具有 const map<K, V>::key_type 类型,而 second 元素则为 map<K, V>::mapped_type 类型 |
对迭代器进行解引用时,将获得一个引用,指向容器中一个 value_type 类型的值。对于 map 容器,其 value_type 是 pair 类型:
// get an iterator to an element in word_count
map<string, int>::iterator map_it = word_count.begin();
对迭代器进行解引用将获得一个 pair 对象,它的 first 成员存放键,为 const,而 second 成员则存放值。
谨记 value_type 是 pair 类型,它的值成员可以修改,但键成员不能修改。可以使用作用域操作符(scope operator)来获取类型成员,如 map<string, int>::key_type。
使用下标访问 map 对象
如下编写程序时:
map <string, int> word_count; // empty map
// insert default initialzed element with key Anna; then assign 1 to its value
word_count["Anna"] = 1;
将发生以下事情:
(1) 在 word_count 中查找键为 Anna 的元素,没有找到。
(2)将一个新的键-值对插入到 word_count 中。它的键是 const string 类型的对象,保存 Anna。而它的值则采用值初始化,这就意味着在本例中值为 0。
(3)将这个新的键-值对插入到 word_count 中。
(4)读取新插入的元素,并将它的值赋为 1。
使用下标访问 map 与使用下标访问数组或 vector 的行为截然不同:用下标访问不存在的元素将导致在 map 容器中添加一个新元素,它的键即为该下标值。
注意一点:
通常来说,下标操作符返回左值。它返回的左值是特定键所关联的值。可如下读或写元素:
cout << word_count["Anna"]; // fetch element indexed by Anna; prints 1
++word_count["Anna"]; // fetch the element and add one to it
cout << word_count["Anna"]; // fetch the element and print it; prints 2
有别于 vector 或 string 类型,map 下标操作符返回的类型与对 map 迭代器进行解引用获得的类型不相同。
map 迭代器返回 value_type 类型的值——包含 const key_type 和 mapped_type 类型成员的 pair 对象;下标操作符则返回一个 mapped_type 类型的值。
统计单词次数的小程序:
map<string, int> word_count; // empty map from string to int
string word;
while (cin >> word)
++word_count[word];
除了下标法,插入元素的另一个方法是:直接使用 insert 成员,其语法更紧凑:
// if Anna not already in word_count, inserts new element with value 1
word_count.insert(map<string, int>::value_type("Anna", 1));
使用 insert 成员可避免使用下标操作符所带来的副作用:不必要的初始化。
以上可用两种方法简化:使用 make_pair:
word_count.insert(make_pair("Anna", 1));
或使用 typedef
typedef map<string,int>::value_type valType;
word_count.insert(valType("Anna", 1));
map 容器提供的 insert 操作
m.insert(e) |
e is a value of the value_type for m. If the key(e.first) is not in m, inserts a new element with value e.second. If the key is in m, then m is unchanged. Returns a pair containing a map iterator referring to the element with key e.first and a bool indicating whether the element was inserted or not. e 是一个用在 m 上的 value_type 类型的值。如果键(e.first)不在 m 中,则插入一个值为 e.second 的新元素;如果该键在 m 中已存在,则保持 m 不变。该函数返回一个 pair 类型对象,包含指向键为 e.first 的元素的 map 迭代器,以及一个 bool 类型的对象,表示是否插入了该元素 |
m.insert(beg, end) |
beg and end are iterators that denote a range of values that are keyvalue pairs with the same type as m's value_type. For each element in the range, if the given key is not already in m, it inserts the key and its associated value into m. Returns void. beg 和 end 是标记元素范围的迭代器,其中的元素必须为 m.value_type 类型的键-值对。对于该范围内的所有元素,如果它的键在 m 中不存在,则将该键及其关联的值插入到 m。返回 void 类型 |
m.insert(iter, e) |
e is a value of the value_type for m. If the key(e.first) is not in m, inserts the new element using the iterator iter as a hint for where to begin the search for where the new element should be stored. Returns an iterator that refers to the element in m with given key. e 是一个用在 m 上的 value_type 类型的值。如果键(e.first)不在 m 中,则创建新元素,并以迭代器 iter 为起点搜索新元素存储的位置。返回一个迭代器,指向 m 中具有给定键的元素 |
带有一个键-值 pair 形参的 insert 版本将返回一个值:包含一个迭代器和一个 bool 值的 pair 对象,其中迭代器指向 map 中具有相应键的元素,而 bool 值则表示是否插入了该元素。如果该键已在容器中,则其关联的值保持不变,返回的 bool 值为 falsefalse,如果该键不存在,则插入新元素,且bool值为true。在这两种情况下,迭代器都将指向具有给定键的元素。下面是使用 insert 重写的单词统计程序:
// count number of times each word occurs in the input
map<string, int> word_count; // empty map from string to int
string word;
while (cin >> word) {
// inserts element with key equal to word and value 1;
// if word already in word_count, insert does nothing
pair<map<string, int>::iterator, bool> ret =
word_count.insert(make_pair(word, 1));
if (!ret.second) // word already in word_count
++ret.first->second; // increment counter
(注意:*p.first等价于p->first)
查找并读取 map 中的元素
下标操作符给出了读取一个值的最简单方法:
map<string,int> word_count;
int occurs = word_count["foobar"];
但是,使用下标存在一个很危险的副作用:如果该键不在 map 容器中,那么下标操作会插入一个具有该键的新元素。
map 容器提供了两个操作:count 和 find,用于检查某个键是否存在而不会插入该键。
不修改 map 对象的查询操作
m.count(k) |
Returns the number of occurrences of k within m. 返回 m 中 k 的出现次数 |
m.find(k) |
Returns an iterator to the element indexed by k, if there is one, or returns an off-the-end iterator if the key is not present. 如果 m 容器中存在按 k 索引的元素,则返回指向该元素的迭代器。如果不存在,则返回超出末端迭代器 |
从map删除元素
从 map 容器中删除元素的 erase 操作有三种变化形式。与顺序容器一样,可向 erase 传递一个或一对迭代器,来删除单个元素或一段范围内的元素。其删除功能类似于顺序容器,但有一点不同:map 容器的 erase 操作返回 void,而顺序容器的 erase 操作则返回一个迭代器,指向被删除元素后面的元素
除此之外,map 类型还提供了一种额外的 erase 操作,其参数是 key_type 类型的值,如果拥有该键的元素存在,则删除该元素。对于单词统计程序,可使用这个版本的 erase 函数来删除 word_count 中指定的单词,然后输出被删除的单词:
if (word_count.erase(removal_word))
cout << "ok: " << removal_word << " removed/n";
else cout << "oops: " << removal_word << " not found!/n";
erase 函数返回被删除元素的个数。对于 map 容器,该值必然是 0 或 1。如果返回 0,则表示欲删除的元素在 map 不存在。
从 map 对象中删除元素
m.erase(k) |
Removes the element with key k from m. Returns size_type indicating the number of elements removed. 删除 m 中键为 k 的元素。返回 size_type 类型的值,表示删除的元素个数 |
m.erase(p) |
Removes element referred to by the iterator p from m. p must refer to an actual element in m; it must not be equal to m.end(). Returns void. 从 m 中删除迭代器 p 所指向的元素。p 必须指向 m 中确实存在的元素,而且不能等于 m.end()。返回 void |
m.erase(b, e) |
Removes the elements in the range denoted by the iterator pair b, e. b and e must be a valid range of elements in m: b and e must refer to elements in m or one past the last element in m. b and e must either be equalin which case the range is emptyor the element to which b refers must occur before the element referred to by e. Returns void. 从 m 中删除一段范围内的元素,该范围由迭代器对 b 和 e 标记。b 和 e 必须标记 m 中的一段有效范围:即 b 和 e 都必须指向 m 中的元素或最后一个元素的下一个位置。而且,b 和 e 要么相等(此时删除的范围为空),要么 b 所指向的元素必须出现在 e 所指向的元素之前。返回 void 类型 |
5.set类型:
set 容器只是单纯的键的集合。set 不支持下标操作符,而且没有定义 mapped_type 类型。在 set 容器中,value_type 不是 pair 类型,而是与 key_type 相同的类型。它们指的都是 set 中存储的元素类型。这一差别也体现了 set 存储的元素仅仅是键,而没有所关联的值。与 map 一样,set 容器存储的键也必须唯一,而且不能修改。
为了使用 set 容器,必须包含 set 头文件。
在set中添加元素:
可使用 insert 操作在 set 中添加元素:
set<string> set1; // empty set
set1.insert("the"); // set1 now has one element
set1.insert("and"); // set1 now has two elements
另一种用法是,调用 insert 函数时,提供一对迭代器实参,插入其标记范围内所有的元素。该版本的 insert 函数类似于形参为一对迭代器的构造函数——对于一个键,仅插入一个元素:
set<int> iset2; // empty set
vector<int> ivec;
for (vector<int>::size_type i = 0; i != 10; ++i) {
ivec.push_back(i);
ivec.push_back(i); // duplicate copies of each number
}
iset2.insert(ivec.begin(), ivec.end()); // iset2 has 10 elements
与 map 容器的操作一样,带有一个键参数的 insert 版本返回 pair 类型对象,包含一个迭代器和一个 bool 值,迭代器指向拥有该键的元素,而 bool 值表明是否添加了元素。使用迭代器对的 insert 版本返回 void 类型。
创建“单词排除”集:
void restricted_wc(ifstream &remove_file,
map<string,int> &word_count)
{
set<string>excluded;
string remove_word;
while(remove_file>>remove_word)
excluded.insert(remove_word);
string word;
while(cin>>word)
if(!excluded.count(word))
++word_count[word];
}
6.multiset和multimap
map 和 set 容器中,一个键只能对应一个实例。而 multiset 和 multimap 类型则允许一个键对应多个实例。multimap 和 multiset 类型与相应的单元素版本具有相同的头文件定义:分别是 map 和 set 头文件。
multimap 和 multiset 所支持的操作分别与 map 和 set 的操作相同,只有一个例外:multimap 不支持下标运算。
带有一个键参数的 erase 版本将删除拥有该键的所有元素,并返回删除元素的个数。而带有一个或一对迭代器参数的版本只删除指定的元素,并返回 void 类型:
multimap<string, string>::size_type cnt = authors.erase(search_item);
返回迭代器的关联容器操作
m.lower_bound(k) |
Returns an iterator to the first element with key not less than k. 返回一个迭代器,指向键不小于 k 的第一个元素 |
m.upper_bound(k) |
Returns an iterator to the first element with key greater than k. 返回一个迭代器,指向键大于 k 的第一个元素 |
m.equal_range(k) |
Returns a pair of iterators. 返回一个迭代器的 pair 对象 The first member is equivalent to m.lower_bound(k) and second is equivalent to m.upper_bound(k). 它的 first 成员等价于 m.lower_bound(k)。而 second 成员则等价于 m.upper_bound(k) |
运用如下:
typedef multimap<string, string>::iterator authors_it;
authors_it beg = authors.lower_bound(search_item),
end = authors.upper_bound(search_item);
// loop through the number of entries there are for this author
while (beg != end) {
cout << beg->second << endl; // print each title
++beg;
}
使用 equal_range 函数修改程序:
// definitions of authors and search_item as above
// pos holds iterators that denote range of elements for this key
pair<authors_it, authors_it>
pos = authors.equal_range(search_item);
// loop through the number of entries there are for this author
while (pos.first != pos.second) {
cout << pos.first->second << endl; // print each title
++pos.first;
}
一个文本查询程序:
//头文件定义类
class TextQuery {
public:
typedef std::vector<std::string>::size_type line_no;
void read_file(std::ifstream &is)
{store_file(is);build_map();}
std::set_new_handler<line_no> run_query(const std::string&) const;
std::string text_line(line_no) const;
private:
void store_file(std::ifstream&);
void build_map();
std::vector<std::string> lines_of_text;
std::map<std::string,std::set_new_handler<line_no> >word_map;
};
注:有一种情况下,必须总是使用完全限定的标准库名字:在头文件中。理由是头文件的内容会被预处理器复制到程序中。用 #include 包含文件时,相当于头文件中的文本将成为我们编写的文件的一部分。如果在头文件中放置 using 声明,就相当于在包含该头文件 using 的每个程序中都放置了同一 using,不论该程序是否需要 using 声明。
通常,头文件中应该只定义确实必要的东西。请养成这个好习惯。
int main(int argc,char **argv)
{
ifstream infile;
if(argc<2||!open_file(infile,argv[1])) {
cerr<<"no input file!"<<endl;
return EXIT_FAILURE;
}
TextQuery tq;
tq.read_file(infile);
while(true) {
cout<<"enter word to look for,or q to quit:";
string s;
cin>>s;
if(!cin||s=="q") break;
set<TextQuery::line_no> locs=tq.run_query(s);
print_results(locs,s,tq);
}
return 0;
}
void print_results(const set<TextQuery::line_no>& locs,
const string& sought,const TextQuery &file)
{
typedef set<TextQuery::line_no> line_nums;
line_nums::size_type size=locs.size();
cout<<"/n"<<sought<<"occurs"
<<size<<" "
<<make_plural(size,"time","s")<<endl;
line_nums::const_iterator it=locs.begin();
for(;it!=locs.end();++it) {
cout<<"/t(line"
<<(*it)+1<<")"
<<file.text_line(*it)<<endl;
}
}
void TextQuery::store_file(ifstream &is)
{
string textline;
while(getline(is,textline))
lines_of_text.push_back(textline);
}
void TextQuery::build_map()
{
for(line_no line_num=0;
line_num!=lines_of_text.size();
++line_num)
{
istringstream line(lines_of_text[line_num]);
string word;
while(line>>word)
word_map[word].insert(line_num);
}
}
set<TextQuery::line_no>
TextQuery::run_query(const string &query_word) const{
map<string,set<line_no> >::const_iterator
loc=word_map.find(query_word);
if(loc==word_map.end())
return set<line_no>();
else
return loc->second;
}
string TextQuery::text_line(line_no line) const
{
if(line<lines_of_text.size())
return lines_of_text[line];
throw std::out_range("line number out of range");
}