第十章 关联容器
10.1 引言:pair类型
pair类型是标准库类型,该类型在utility头文件中定义,pair是一种模板类型,pair包含两个公有数据成员,各自对应类型名字,这两个类型不必相同,创建pair对象时不提供 初始化式,则调用 默认构造函数 对其成员采取值初始化,除了构造函数,标准库还定义了一个make_pair函数,由传递给它的两个实参生成一个新的pair对象
10.2 关联容器
关联容器 共享大部分顺序容器的操作,但 不提供front、 push_front、 pop_front、 back、 push_back、 pop_back操作,关联容器 与 顺序容器 公共的操作 :三种构造函数,关系运算,begin、end、rbegin和rend操作,类型别名typedef (对于map容器,value_type并非元素的类型(Note that for map, the value_type is not the same as the element type),而是描述键及其关联值类型的pair类型),swap和赋值操作,clear和erease操作,容器大小的操作(resize函数不能用于关联容器 )
关联容器元素根据键的次序排列,在迭代遍历关联容器时,按键的顺序访问元素,与元素在容器中的存放位置完全无关
10.3 map类型
map是键值对的集合,map类型通常可理解为关联数组:可使用键作为下标来获取一个值,正如内置数组类型一样,而关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置来获取
10.3.1 map对象的定义
在定义map对象时,必须分别指明键和值得类型,键类型的约束:在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数,默认情况下,标准库使用键类型定义的 < 操作符来实现键的比较,所用的比较函数必须在键类型上定义严格弱排序,可理解为键类型数据上的小于关系
10.3.2 map定义的类型
map定义的类型:map<K,V >:: key_type(在map容器中,用做索引的键的类型)、map<K,V>::mapped_type(键所关联的类型)、map<K,V>::value_type(一个pair类型,它的first元素具有const map<K,V>::key_type类型,second元素则为map<K,V>::mapped_type类型
对map迭代器进行解引用,将获得一个引用,指向容器中一个value_type类型的值,对于map容器,其value_type是pair类型
#include <iostream> #include <string> #include <map> using namespace std; int main() { map<string,int> m; m.insert(make_pair("Hello",5) ); map<string,int>::const_iterator ci = m.begin(); map<string,int>::value_type vt = *ci; //迭代器解引用,获得一个引用,指向map容器中一个value_type类型的值,value_type是pair类型 map<string,int>::key_type kt = vt.first;// key_type是map容器中用做索引的键的类型 map<string,int>::mapped_type mt = vt.second;//mapped_type是map容器中,键所关联的值得类型 cout << kt << "," << mt << endl; return 0; }
10.3.3 给map添加元素
在map容器中添加键值元素对,可使用insert成员,或者,先用下标操作符获取元素,然后给获取的元素赋值
10.3.4 使用下标访问map对象
用下标访问不存在的元素将导致在map容器中添加一个新的元素,它的键即为该下标值(map的下标也使用索引,如果该键已在容器中,返回该键所关联的值),下标操作符返回左值,下标操作符返回一个mapped_type类型的值
10.3.5 map::insert的使用
m.insert(e ) //e是一个用在m上的value_type类型的值,该函数返回一个pair类型对象,包含指向键的e.first的元素的map迭代器,以及一个bool类型的对象,表示是否插入了该元素,如果该键已在容器中,则其关联的值保持不变,返回的bool值为false
10.3.6 查找并读取map中的元素
使用下标操作符 读取一个值,如果该键不在map容器中,那么下标操作符会插入一个具有该键的新元素,map容器提供了两个操作:count 和 find,用于检查某个键是否存在而不会插入该键,如果希望元素存在就使用它,应该用find操作,find操作返回指向元素的迭代器,如果元素不存在,返回end迭代器
10.3.7 从map对象中删除元素
10.3.8 map对象的迭代遍历
10.3.9 “单词转换”map对象
10.4 set类型
当只想知道一个值是否存在时,使用set容器是最合适的,set容器支持 大部分的map操作:所有通用的容器操作、构造函数、insert操作、count、find操作、erase操作,set不支持下标操作,而且没有定义mapped_type类型,在set容器中,value_type指的是set中存储的元素类型,set容器存储的键是唯一的且不能修改
10.4.1 set容器的定义和使用
insert操作在set中添加元素:带一个键参数的insert版本 返回pair类型对象,包含一个迭代器和一个bool值,迭代器指向拥有该键的元素,而bool值表明是否添加了元素,使用迭代器对的insert版本 返回void类型,使用 find运算 从set中获取元素,返回迭代器,使用 count运算,返回set中该键对象的元素个数,只能是1或0,set中的键为const,只能做读操作,不能做写操作
10.4.2 创建“单词排除”集
函数读取传递进来的文件,该文件列出了所有被排除的单词,读入这些单词并 存储在一个名为excluded的set容器中,输入单词,检查该单词是否出现在排除集中,如果没有出现过,则将它插入到map容器对象word_count中,并在插入元素后将它关联的值初始化为0,然后不管是否插入了新元素,相应元素的值都加1
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]; } }
10.5 nultmap和multset类型
10.5.1 元素的添加和删除
10.5.2 在multmap和multset中查找元素
10.6 容器的综合应用:文本查询程序
程序将读取用户指定的任意文本文件,然后允许用户从该文件中查找单词,查询的结果是该单词出现的次数,并列出每次出现所在的行,如果某单词在同一行中多次出现,程序将只显示该行一次,行号按升序显示
设计程序的一个良好的习惯是首先将程序所涉及的操作列出来,明确需要提供的操作有助于建立需要的数据结构和实现这些行为
操作:接口成员函数:read_file,形参为一个ifstream&类型对象,run_query,形参为一个string类型对象,返回一个set对象,text_line,形参为一个行号,返回输入文本中该行号对应的文本行;private函数(为实现read_file功能):store_file函数读入文件,并将文件内容存储在vector容器对象中,build_map,将每一行分解为各个单词,创建map容器对象,同时记录每个单词出现的行号
数据结构:存储输入文件的vector对象,一个map容器对象,该对象关联每个输入的单词以及记录该单词所在行号的set容器对象
#include <iostream> #include <fstream> //ifstream #include <sstream> //istringstream #include <vector> #include <set> #include <map> #include <string> class TextQuery{ public: typedef std::vector<std::string>::size_type line_no; void read_file(std::ifstream &is){ //std::ifstream & store_file(is); build_map(); } std::set<line_no> run_query(const std::string & )const; std::string text_line(line_no )const; private: void store_file(std::ifstream & ); //std::ifstream & void build_map(); std::vector<std::string> lines_of_text; std::map< std::string, std::set<line_no> > word_map; }; void TextQuery::store_file(std::ifstream &is ) { std::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 ) { std::istringstream line(lines_of_text[line_num] );//从std::vector读到 std::istringstream std::string word; while(line >> word ){ word_map[word].insert(line_num ); } } } std::set<TextQuery::line_no> TextQuery::run_query(const std::string &query_word )const { std::map<std::string,std::set<line_no> > ::const_iterator loc = word_map.find(query_word ); //std::map里查找行std::set if(loc == word_map.end() ) return std::set<line_no>(); //没找到返回set空对象 else return loc->second; } std::string TextQuery::text_line(line_no line ) const { if(line <lines_of_text.size() ) return lines_of_text[line ]; throw std::out_of_range("line number out of range"); } std::ifstream& open_file(std::ifstream &in, const std::string &file ); //open_file void print_results(const std::set<TextQuery::line_no > &locs, const std::string &sought, const TextQuery &file ); //print_results std::string make_plural(std::size_t ctr, const std::string &word, const std::string &ending );//make_plural //单词复数形式 int main(int argc, char**argv ) { std::ifstream infile; if(argc<2 ||!open_file(infile,argv[1] ) ) { std::cerr << "No input file!" << std::endl; return EXIT_FAILURE; } TextQuery tq; tq.read_file(infile ); while(true){ std::cout << "enter word to look for, or q to quit: "; std::string s; std::cin >> s; if(!std::cin || s == "q" ) break; //退出循环条件 std::set<TextQuery::line_no > locs = tq.run_query(s ); print_results(locs, s, tq ); } return 0; } std::ifstream& open_file(std::ifstream &in, const std::string &file ) { in.close(); //如果已经打开 in.clear(); in.open(file.c_str() ); //打开文件 return in; } void print_results(const std::set<TextQuery::line_no > &locs, const std::string &sought, const TextQuery &file ) { typedef std::set<TextQuery::line_no > line_nums; line_nums::size_type size = locs.size(); std::cout << "\n" << sought << " occurs " << size << " " << make_plural(size, "time", "s" ) << std::endl; line_nums::const_iterator it = locs.begin(); for( ; it != locs.end(); ++it ){ std::cout << "\t(line " << (*it) + 1 << ")" << file.text_line(*it ) << std::endl; } } std::string make_plural(std::size_t ctr, const std::string &word, const std::string &ending ) //make_plural //单词复数形式 { return (ctr == 1 ) ? word : word + ending; } /* Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, like a fiery bird in flight. A beautiful fiery bird, he tells her, magical but untamed. "Daddy, shush, there is no such thing," she tells him, at the same time wanting him to tell her more. Shyly, she asks, "I mean, Daddy, is there?" */
map 与 set 组合使用
#include <iostream> #include <string> #include <map> #include <set> using namespace std; /* 单词"Hello" 所在的文本的行号 */ int main() { map<string,set<int> > m; //1. pair< map<string,set<int> >::iterator, bool > p_mb = m.insert( make_pair( "Hello", set<int>() ) ); //const_iterator不可以 //插入键为 "Hello" 空的set<int>对象,insert() 返回pair cout << p_mb.first->first; //map插入返回类型为pair,first为map类型,second为bool类型 ((p_mb.first )->second ).insert(120 ); //map的pair类型的second,是set<int>类型,键的值是set<int>对象 // 向键"Hello"关联的值set<int>对象插入值 //2. pair<set<int>::iterator,bool > p_sb = (m["Hello"].insert(130) ); //set的insert() 返回pair //cout << *(p_sb.first); //遍历 /* set<int>::iterator it_b = (p_mb.first->second).begin(); set<int>::iterator it_e = (p_mb.first->second).end(); */ map<string,set<int> >::iterator it = m.find("Hello" ); //map的find函数,返回map类型的迭代器 set<int>::iterator it_b = (it->second).begin(); //通过map迭代器获取pair类型的second,该pair的second是set<int>类型 set<int>::iterator it_e = (it->second).end(); while(it_b!=it_e ){ cout << *it_b++; } return 0; }