C++关联容器操作
专栏简介:本专栏主要面向C++初学者,解释C++的一些基本概念和基础语言特性,涉及C++标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级程序设计技术。希望对读者有帮助!
目录
- 11.3 关联容器操作
- set的迭代器是const的
- 遍历关联容器
- 关联容器和算法
- 添加元素
- 向map添加元素
- 检测insert的返回值
- 展开递增语句
- 向multiset或multimap添加元素
- 删除元素
- map的下标操作
- 使用下标操作的返回值
- 访问元素
- 对map使用find代替下标操作
- 在multimap或multiset中查找元素
- 一种不同的,面向迭代器的解决方法
- equal_range函数
- 一个单词转换的map
- 建立转换映射
- 生成转换文本
11.3 关联容器操作
表11.3:关联容器额外的类型别名
key_type | 此容器类型的关键守类型 |
mapped_type | 每个关键字关联的类型;只适用于map |
value_type | 对于set,与key_type相同 |
对于map,为pair<eonstkey_type,mapped_type> |
对于set类型,key_type和value_type是一样的;set中保存的值就是关键宇。在一个map中,元素是关键字-值对。即,每个元素是一个pair对象,包含一个关键字和一个关联的值。由于我们不能改变一个元素的关键字,因此这些pair的关键字部分是const的:
set<string>::value_type v1;//v1是一个string
set<string>::key_type v2;//v2是一个string
map<string,int>::value_type v3;//v3是一个pair<const string,int>
map<string,int>::key_type v4;//v4是一个string
map<string,int>::mapped_type v5;//v5是一个int
与顺序容器一样,我们使用作用域运算符来提取一个类型的成员一一例如,map<string,int>::key_type。
只有map类型(unordered_map、unordered_multimap、multimap和map)才定义了mapped_type。
11.3.1关联容器迭代器
当解引用一个关联容器迭代器时,我们会得到一个类型为容器的value_type的值的引用。对map而言,value_type是一个pair类型,其first成员保存const的关键字,second成员保存值:
//获得指向word_count中一个元素的选代器
auto map_it= word_count.begin();
//*map_it是指向一个pair<const string,size_t>对象的引用
cout << map_tt->first;//打印此元素的关键守
cout << " "<< map_it->second;//打印此元素的值
map_it->first = "new key";//锦误:关键守是const的
++map_it->second;//正确:我们可以通过选代器改变元素
必须记住,一个map的value_type是一个pair,我们可以改变pair的值,但不能改变关键守成员的值。
set的迭代器是const的
虽然set类型同时定义了iterator和const_iterator类型,但两种类型都只允许只读访问set中的元素。与不能改变一个map元素的关键字一样,一个set中的关键字也是const的。可以用一个set迭代器来读取元素的值,但不能修改:
set<int>iset = {0,1,2,3,4,5,6,7,8,9};
set<int>::iterator set_it = set.begin();
if(set_it!=iset.end()){
*set_it=42;//错误:set中的关键字是只读的
cout<<*set_it<<endl;//正确:可以读关键字
}
遍历关联容器
map和set类型都支持表中的begin和end操作。与往常一样,我们可以用这些函数获取迭代器,然后用迭代器来遍历容器。例如,我们可以编写一个循环来打单词计数程序的结果,如下所示:
//获得一个指向首元素的选代器
auto map_it=word_count.cbegin();
//比较当前迭代器和尾后选代嚣、
while(map_it!=word_count.cend()){
//解引用选代器,打印关键字-值对
cout<<map_it->first<<"occurs"
<<map_it->second<<" times"<<endl;
++map_it;//递增选代器,移动到下一个元素
}
while的循环条件和循环中的选代器递增操作看起来很像我们之前编写的打印一个vector或一个string的程序。我们首先初始化迭代器map_it,让它指向word_count中的首元素。只要迭代器不等于end,就打印当前元素并递增迭代器。输出语句解引用map_it来获得pair的成员,否则与我们之前的程序一样。
本程序的输出是按字典序排列的。当使用一个选代器遂历一个map、multimap、set或multiset时,迭代器按关键字升序遍历元素。
关联容器和算法
我们通常不对关联容器使用泛型算法。关键字是const这一特性意味着不能将关联容器传递给修改或重排容器元素的算法,因为这类算法需要向元素写入值,而set类型中的元素是const的,map中的元素是pair,其第一个成员是const的。
关联容器可用于只读取元素的算法。但是,很多这类算法都要搜索序列。由于关联容器中的元素不能通过它们的关键字进行(快速)查找,因此对其使用泛型搜索算法几乎总是个坏主意。例如,关联容器定义了一个名为find的成员,它通过一个给定的关键字直接获取元素。我们可以用泛型find算法来查找一个元素,但此算法会进行顺序搜索。使用关联容器定义的专用的find成员会比调用泛型find快得多。
在实际编程中,如果我们真要对一个关联容器使用算法,要么是将它当作一个源序列,要么当作一个目的位置。例如,可以用泛型copy算法将元素从一个关联容器拷贝到另一个序列。类似的,可以调用inserter将一个插入器绑定到一个关联容器。通过使用inserter,我们可以将关联容器当作一个目的位置来调用另-个算法。
添加元素
关联容器的insert成员向容器中添加一个元素或一个元素范围。由于map和set(以及对应的无序类型)包含不重复的关键字,因此插入一个已存在的元素对容器没有任何影响:
vector<int>ivec = {2,4,6,8,2,4,6,8};//ivec有8个元素
set<int> set2;//set集合
set2.insert(ivec.cbegin(),ivec.cend());//set有4个元素
set2.insert({1,3,5,7,1,3,5,7});//set现在有8个元素
insert有两个版本,分别接受一对迭代器,或是一个初始化器列表,这两个版本的行为类似对应的构造函数一一对于一个给定的关键字,只有第一个带此关键字的元素才被插入到容器中。
向map添加元素
对一个map进行insert操作时,必须记住元素类型是pair。通常,对于想要插入的数据,并没有一个现成的pair对象。可以在insert的参数列表中创建一个pair:
//向word_count插入word的4种方法
word_count.insert(tword,i1);
word_count.insert(make_pair(word,1));
word_count.insert(pair<string,size_t>(word,1))
word_count.insert(map<string,size_t>::value_type(word,1));
如我们所见,在新标准下,创建一个pair最简单的方法是在参数列表中使用花括号初始化。也可以调用make_pair或显式构造pair。最后一个insert调用中的参数:
map<string,size_t>::value_type(s,1)
构造一个恰当的pair类型,并构造该类型的一个新对象,插入到map中。
表11.4:关联容器insert操作
c.insert(v) | v是value_type类型的对象;args用来构造一个元素 |
c.emplace(args) | 对于map和set,只有当元素的关键字不在c中时才插入(或构造)元素。函数返回一个pair,包含一个迭代器,指向具有指定关键字的元素,以及一个指示插入是否成功的bool值。对于multimap和multiset,总会插入(或构造)给定元素,并返回一个指向新元素的迭代器 |
c.insert(b,e) | b和e是迭代器,表示一个c::value_type类型值的范围;i1是 |
c.insert(i1) | 这种值的花括号列表。函数返回void对于map和set,只捍入关键字不在c中的元素。对于multimap和multiset,则会插入范围中的每个元素 |
c.insert(p,v) | 类似insert(v)(或emplace(args)),但将迭代器p作为一个提 |
c.emplace(p,args) | 示,指出从哪里开始搜索新元素应该存储的位置。返回一个迭代器,指向具有给定关键字的元素 |
检测insert的返回值
insert(或emplace)返回的值依赖于容器类型和参数。对于不包含重复关键守的容器,添加单一元素的insert和emplace版本返回一个pair,告诉我们插入操作是否成功。pair的first成员是一个迭代器,指向具有给定关键字的元素;second成员是一个boo1值,指出元素是插入成功还是已经存在于容器中。如果关键字已在容器中,则insert什么事情也不做,目返回值中的boo1部分为false。如果关键字不存在,元素被插入容器中,且boo1值为true。
作为一个例子,我们用insert重写单词计数程序:
//统计每个单词在输入中出现次数的一种更烦琐的方法
map<string,size_t>word_count;//从string到size_t的空map
string word;
while(cin>>word){
//插入一个元素,关键字等于word,值为1;
//若word已在word_count中,insert什么也不做
auto ret = word_count.insert({word,1});
if(!ret.second)//word已在word_count中
++ret.first->second;//递增计数器
}
对于每个word,我们尝试将其插入到容器中,对应的值为1。若word已在map中,则什么都不做,特别是与word相关联的计数器的值不变。若word还未在map中,则此string对象被添加到map中,日其计数器的值被置为1。
if语句检查返回值的bool部分,若为false,则表明插入操作未发生。在此情况下,word已存在于word_count中,因此必须递增此元素所关联的计数器。
展开递增语句
在这个版本的单词计数程序中,递增计数器的语句很难理解。通过添加一些括号来反映出运算符的优先级,会使表达式更容易理解一些:
++((ret.first)->second);//等价的表达式
下面我们一步一步来解释此表达式:
ret保存insert返回的值,是一个pair。
ret.first是pair的第一个成员,是一个map迭代器,指向具有给定关键字的元素。
ret.first->解引用此迭代器,提取map中的元素,元素也是一个pair。
ret.first->second map中元素的值部分。
++ret.first->second逐增此值。
再回到原来完整的递增语句,它提取匹配关键字word的元素的迭代器,并递增与我们试图插入的关键字相关联的计数器。
如果读者使用的是旧版本的编译器,或者是在阅读新标准推出之前编写的代码,ret的声明和初始化可能复杂些:
pair<map<string,sitze_t>::iterator,bool>ret =
word_count.insert(make_pair(word,1));
应该容易看出这条语句定义了一个pair,其第二个类型为bool类型。第一个类型理解起来有点儿困难,它是一个在map<string,size_t>类型上定义的iterator类型。
向multiset或multimap添加元素
我们的单词计数程序依赖于这样一个事实:一个给定的关键字只能出现一次。这样,任意给定的单词只有一个关联的计数器。我们有时希望能添加具有相同关键字的多个元素。例如,可能想建立作者到他所著书籍题目的映射。在此情况下,每个作者可能有多个条目,因此我们应该使用multimap而不是map。由于一个multi容器中的关键字不必唯一,在这些类型上调用insert总会插入一个元素:
multimap<string,string>authors;
//插入第一个元素,关键字为Barth,John
authors.insert({"Barth,John","Sot-Weed Factor");
//正确:添加第二个元素,关键守也是Barth,John
authors.insert({"Barth,John","Lost in the Funhouse"});
对允许重复关键字的容器,接受单个元素的insert操作返回一个指向新元素的迭代器。这里无须返回一个boo1值,因为insert总是向这类容器中加入一个新元素。
删除元素
关联容器定义了三个版本的erase,如表11.5所示。与顺序容器一样,我们可以通过传递给erase一个迭代器或一个迭代器对来删除一个元素或者一个元素范围。这两个版本的erase与对应的顺序容器的操作非常相似:指定的元素被删除,函数返回void。
关联容器提供一个额外的erase操作,它接受一个key_type参数。此版本删除所有匹配给定关键字的元素(如果存在的话),返回实际删除的元素的数量。我们可以用此版本在打印结果之前从word_count中删除一个特定的单词:
//删除一个关键字,返回删除的元素数量
if(word_count.erase(remoyal_word))
cout<<"ok:"<<remoyal_word<<"remoyed\n";
else cout<<"oops:"<<removal_word<<"not found \n";
对于保存不重复关键字的容器,erase的返回值总是0或1。若返回值为0,则表明想要删除的元素并不在容器中
对允许重复关键字的容器,删除元素的数量可能大于1:
auto cnt=authors.erase("Barth, John");
如果authors是我们创建的maltimap,则cnt的值为2。
表11.5:从关联容器删除元素
c.erase(k) | 从c中删除每个关键守为k的元素。返回一个size_type值,指出别除的元素的数量 |
c.erase§ | 从c中删除迭代器p指定的元素。p必须指向c中一个真实元素,不能等于c.end()。返回一个指向p之后元素的迭代器,若p指向c中的尾元素,则返回c.end() |
c.erase(b,e) | 删除迭代器对b和e所表示的范围中的元素。返回e |
map的下标操作
map和unordered_map容器提供了下标运算符和一个对应的at函数,如表11.6所示。set类型不支持下标,因为set中没有与关键字相关联的“值“。元素本身就是关键字,因此“获取与一个关键字相关联的值“的操作就没有意义了。我们不能对一个multimap或一个unordered_multimap进行下标操作,因为这些容器中可能有多个值与一个关键字相关联。
类似我们用过的其他下标运算符,map下标运算符接受一个索引(即,一个关键守),获取与此关键字相关联的值。但是,与其他下标运算符不同的是,如果关键字并不在map中,会为它创建一个元素并插入到map中,关联值将进行值初始化。
例如,如果我们编写如下代码
map<string,size_t>word_count;//empty map
//插入一个关键守为Anna的元素,关联值进行值初始化;然后将1赋予它
word_count["Anna"]=1;
将会执行如下操作:
- 在word_count中搜索关键字为Anna的元素,未找到。
- 将一个新的关键字-值对插入到word_count中。关键字是一个const string,保存Ahnna。值进行值初始化,在本例中意昧着值为0。
- 提取出新插入的元素,并将值1赋予它。
由于下标运算符可能插入一个新元素,我们只可以对非const的map使用下标操作。
对一个map使用下标操作,其行为与数组或yector上的下标操作很不相同:
使用一个不在容器中的关键守作为下标,会添加一个具有此关键词的元素到map中。
表11.6:map和unordered_map的下标操作
c[k] | 返回关键字为k的元素;如果k不在c中,添加一个关键守为x的元素,对其进行值初始化 |
c.at(K) | 访问关键字为k的元素,带参数检查;若x不在c中,抛出一个out_of_range异常 |
使用下标操作的返回值
map的下标运算符与我们用过的其他下标运算符的另一个不同之处是其返回类型。通常情况下,解引用一个迭代器所返回的类型与下标运算符返回的类型是一样的。但对map则不然:当对一个map进行下标操作时,会获得一个mapped_type对象;但当解引用一个map迭代器时,会得到一个value_type对象与其他下标运算符相同的是,map的下标运算符返回一个左值由于返回的是一个左值,所以我们既可以读也可以写元素:
cout<<word_count["Anna"];//用Anna作为下标提取元素;会打印出1
++word_count["Anna"];//提取元素,将其增1
cout<<word_count["Anna"];//提取元素并打印它;会打印出2
与vector与string不同,map的下标运算符返回的类型与解引用map迭代器得到的类型不同。
如果关键字还未在map中,下标运算符会添加一个新元素,这一特性允许我们编写出异常简洁的程序,例如单词计数程序中的循环,另一方面,有时只是想知道一个元素是否已在map中,但在不存在时并不想添加元素。在这种情况下,就不能使用下标运算符。
访问元素
关联容器提供多种查找一个指定元素的方法,如表11.7所示。应该使用哪个操作依赖于我们要解决什么问题。如果我们所关心的只不过是一个特定元素是否已在容器中,可能find是最佳选择。对于不允许重复关键字的容器,可能使用find还是count没什么区别。但对于允许重复关键字的容器,count还会做更多的工作:如果元素在容器中,它还会统计有多少个元素有相同的关键字。如果不需要计数,最好使用find:
set<int>iset={0,1,2,3,4,5,6,7,8,9};
iset.find(1);//返回一个追代器,指向key==1的元素
iset.find(1);//返回一个选代器,其值等于iset.end()
iset.count(1);// 返回1
iset.count(1);//返回0
表11.7:在一个关联容器中查找元素的操作
lower_bound和upper_bound不适用于无序容器. | |
下标和at操作只适用于非const的map和hnordered_map | |
c.find(k) | 返回一个迭代器,指向第一个关键字为k的元素,若k不在容器中,则返回尾后迭代器 |
c.count(k) | 返回关键守等于k的元素的数量。对于不允许重复关键字的容器,返回值永远是0或1 |
c.lower_bound(k) | 返回一个迭代器,指向第一个关键守不小于x的元素 |
c.upper_bound(k) | 返回一个迭代器,指向第一个关键字大于x的元素 |
c.equal_range(K)返回一个迭代器paiz,表示关键字等于k的元素的范围。若k不存在,pair的两个成员均等于c.end() |
对map使用find代替下标操作
对map和unordered_map类型,下标运算符提供了最简单的提取元素的方法。但是,如我们所见,使用下标操作有一个严重的副作用:如果关键字还未在map中,下标操作会插入一个具有给定关键字的元素。这种行为是否正确完全依赖于我们的预期是什么。例如,单词计数程序依赖于这样一个特性,使用一个不存在的关键字作为下标,会插入一个新元素,其关键字为给定关键字,其值为0。也就是说,下标操作的行为符合我们的预期。
但有时,我们只是想知道一个给定关键字是否在map中,而不想改变map。这样就不能使用下标运算符来检查一个元素是否存在,因为如果关键字不存在的话,下标运算符会插入一个新元素。在这种情况下,应该使用find:
if(word_count.find("foobar")==word_count.end())
cout<<"foobar is not in the map"<<endl;
在multimap或multiset中查找元素
在一个不允许重复关键字的关联容器中查找一个元素是一件很简单的事情一一元素要么在容器中,要么不在。但对于允许重复关键字的容器来说,过程就更为复杂:在容器中可能有很多元素具有给定的关键字。如果一个multimap或multiset中有多个元素具有给定关键字,则这些元素在容器中会相邻存储。
例如,给定一个从作者到著作题目的映射,我们可能想打印一个特定作者的所有著作。可以用三种不同方法来解决这个问题。最直观的方法是使用find和count:
string search_item("Alain de Botton");//要查找的作者
auto entries=authors.count(search_item);//元素的数量
auto iter = authors.find(search_item);//此作者的第一本书
//用一个循环查找此作者的所有著作
while (entries){
cout<<iter->second<<endl;//打印每个题目
++iter;//前进到下一本书
--entries;//记录已经打印了多少本书
}
首先调用count确定此作者共有多少本著作,并调用find获得一个迢代器,指向第一个关键字为此作者的元素。for循环的迭代次数依赖于count的返回值。特别是,如果count返回0,则循环一次也不执行。
当我们遍历一个multimap或multiset时,保证可以得到序列中所有具有给定关键字的元素。
一种不同的,面向迭代器的解决方法
我们还可以用lower_bound和upper_bound来解决此问题。这两个操作都接受一个关键字,返回一个迭代器。如果关键字在容器中,lower_bound返回的迭代器将指向第一个具有给定关键字的元素,而upper_bound返回的迭代器则指向最后一个匹配给定关键字的元素之后的位置。如果元素不在multimap中,则lower_bound和upper_bound会返回相等的迭代器一一指向一个不影响排序的关键宇插入位置。因此,用相同的关键字调用lower_bound和upper_bound会得到一个迭代器范围,表示所有具有该关键守的元素的范围。
当然,这两个操作返回的迭代器可能是容器的尾后迭代器。如果我们查找的元素具有容器中最大的关键字,则此关键字的upper_bound返回尾后追代器。如果关键字不存在,且大于容器中任何关键字,则lower_bound返回的也是尾后迭代器。
lower_bound返回的逄代器可能指向一个具有给定关键守的元素,但也可能不指向。如果兰键字不在容器中,则lower_bound会返回关键字的第一个安全点插入-一不影响容器中元素顺序的插入位置。
使用这两个操作,我们可以重写前面的程序:
//authors 和 search_item的定义,与前面的程序一样
//beg和end表示对应此作者的元素的范图
for(auto beg=authors.lower_bound(search_item),
end=authors.upper_bound(search_item);
beg!=end;++beg)
cout<<beg->second<<endl;//打印每个题目
此程序与使用count和find的版本完成相同的工作,但更直接。对lower_bound的调用将beg定位到第一个与search_item匹配的元素(如果存在的话)。如果容器中没有这样的元素,beg将指向第一个关键字大于search_item的元素,有可能是尾后迢代器。upper_bound调用将end指向最后一个匹配指定关键字的元素之后的元素。这两个操作并不报告关键字是否存在,重要的是它们的返回值可作为一个迭代器范围。
如果没有元素与给定关键字匹配,则lower_bound和upper_bound会返回相等的迭代器一一都指向给定关键字的插入点,能保持容器中元素顺序的插入位置。
假定有多个元素与给定关键字匹配,beg将指向其中第一个元素。我们可以通过递增beg来遍历这些元素。end中的迭代器会指出何时完成遍历一一当beg等于end时,就表明已经道历了所有匹配给定关键字的元素了。
由于这两个迭代器构成一个范围,我们可以用一个for循环米遍历这个范围。循环可能执行零次,如果存在给定作者的话,就会执行多次,打印出该作者的所有项。如果给定作者不存在,beg和end相等,循环就一次也不会执行。否则,我们知道递增beg最终会使它到达end,在此过程中我们就会打印出与此作者关联的每条记录。
如果lower_bound和 upper_bound返回相同的迭代器,则给定关键字不在容器中。
equal_range函数
解决此问题的最后一种方法是三种方法中最直接的,不必再调用upper_bound和lower_bound,直接调用equal_range即可。此函数接受一个关键字,返回一个迭代器pair。若关键字存在,则第一个迭代器指向第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素之后的位置。若未找到匹配元素,则两个迭代器都指向关键宇可以插入的位置。
可以用equal_range来再次修改我们的程序:
//authors和search_item的定义,与前面的程序一样
//pos保存选代器对,表示与关键守匹配的元素范图
for(auto pos=authors.equal_range(Search_item}
pos.first!=pos.second;++pos.first)
cout<<pos.first->second<<endl;//打印每个题目
此程序本质上与前一个使用upper_bound和lower_bound的程序是一样的。不同之处就是,没有用局部变量beg和end来保存元素范围,而是使用了equal_range返回的pair。此pair的first成员保存的迭代器与lower_bound返回的迭代器是一样的,second保存的迭代器与upper_bound的返回值是一样的。因此,在此程序中,pos.first等价于beg,pos.second等价于end。
一个单词转换的map
我们将以一个程序结束本节的内容,它将展示map的创建、搜索以及遍历。这个程序的功能是这样的:给定一个string,将它转换为另一个string。程序的输入是两个文件。第一个文件保存的是一些规则,用来转换第二个文件中的文本。每条规则由两部分组成:一个可能出现在输入文件中的单词和一个用来替换它的短语。表达的含义是,每当第一个单词出现在输入中时,我们就将它替换为对应的短语。第二个输入文件包含要转换的文本。
如果单词转换文件的内容如下所示:
brb be right back
k okay?
y why
r are
u you
pic picture
thk thanks!
18r later
我们希望转换的文本为
where r u
y dont u send me a pic
k thk 18r
则程序应该生成这样的输出:
where are you
why dont you send me a pictuze
okay? thanks! later
单词转换程序
我们的程序将使用三个函数。函数word_transform管理整个过程。它接受两个ifstream参数:第一个参数应绑定到单词转换文件,第二个参数应绑定到我们要转换的文本文件。函数buildMap会读取转换规则文件,并创建一个map,用于保存每个单词到其转换内容的映射。函数transform接受一个string,如果存在转换规则,返回转换后的内容。
我们首先定义word_transform函数。最重要的部分是调用buildMap和 transform:
void word_transform(ifstream &map_file,ifstream &input) {
auto trans_map=buildMap(map_file);//保存转换规则
string text;//保存输入中的每一行
while(getline(input,text)){//读取一行输入
istringstream stream(text);//读取每个单词
string word;
bool firstword=true;//控制是否打印空格
while(stream>>word){
if(firstword)
firstword=false;
else
cout<<"";//在单词间打印一个空格
//transform返回它的第一个参数战其转换之后的形式
cout<<transform(word,trans_map);//打印输出
}
cout<<endl;//完成一行的转接
}
}
函数首先调用baildMap来生成单词转换map,我们将它保存在trans_map中。函数的剩余部分处理输入文件。while循环用getline一行一行地读取输入文件。这样做的目的是使得输出中的换行位置能和输入文件中一样。为了从每行中获取单词,我们使用了一个嵌套的while循环,它用一个istringstream来处理当前行中的每个单词。
在输出过程中,内层while循环使用一个boo1变量firstword来确定是否打印一个空格。它通过调用transform来获得要打印的单词。transform的返回值或者是word中原来的string,或者是trans_map中指出的对应的转换内容。
建立转换映射
函数buildMap读入给定文件,建立起转换映射。
map<string,string>buildMap(ifstream&map_file)
{
map<string,string>trans_map;//保存转换规则
string key; //要转换的单词
string value;//晴换后的内容
//读取第一个单词存入key中,行中剩余内容存入value
while(map_file>>key && getltne(map_file,value))
if(value.size()>1)//检查是否有转换规则
trans_map[key]=value.substr(1);//跳过前导空格
else
throw runtime_error("no rule for"+key);
return trans_map;
}
map_file中的每一行对应一条规则。每条规则由一个单词和一个短语组成,短语可能包含多个单词。我们用>>读取要转换的单词,存入key中,并调用getline读取这一行中的剩余内容存入value。由于getline不会跳过前导空格,需要我们来跳过单词和它的转换内容之间的宇格。在保存转换规则之前,检查是否获得了一个以上的字符。如果是,调用substr来跳过分隔单词及其转换短语之间的前导空格,并将得到的子字符串存入trans_map。
注意,我们使用下标运算符来添加关键字-值对。我们隐含地忽略了一个单词在转换文件中出现多次的情况。如果真的有单词出现多次,循环会将最后一个对应短语存入trans_map。当while循环结束后,trans_map中将保存着用来转换输入文本的规则。
生成转换文本
函数transform进行实际的转换工作。其参数是需要转换的string的引用和转换规则map。如果给定string在map中,transform返回相应的短语.否则,transform直接返回原string:
const string& transform (const string&s,const map<string,string>&mi)
{
//实际的转换工作;此部分是程序的核心
auto map_it = m.find(s);
//如果单词在转拷规则map中
if(map_it!=m.cend())
return map_it->second;//使用替换短语
else
return s;//否则返回原string
}
函数首先调用find来确定给定string是否在map中。如果存在,则find返回一个指向对应元素的迭代器。否则,find返回尾后迭代器。如果元素存在,我们解引用迭代器,获得一个保存关键字和值的pair,然后返回成员second,即用来替代s的内容。