11.3 关联容器操作
关联容器定义的额外的类型。
注意map的value_type是一个pair类型,而且pair的first成员是const类型,这是因为我们从map中得到的pair返回的是引用,这意味着我们可以修改pair中的数据成员,而first成员是map容器的关键字,关键字是不允许改变的,为了防止我们修改了关键字,所以将first设置为const。
mapped_type表示的是map容器key关联的value的类型。
11.3.1 关联容器迭代器
关联容器同样有迭代器,map迭代器解引用返回的是pair,且par的first是常量类型。
set迭代器返回的是常量类型的迭代器,因为set的关键字就是值,而关键字是不允许改变的。
因为set的迭代器为const,而map的pair的fist是const,所以有些泛型算法对于关联容器是没法使用的,因为这些算法需要写入值。所以当我们对关联容器使用算法时,关联容器当做输入范围时,只能使用只读算法,但是当做目标位置时,可是使用inserter()进行插入。
如果泛型算法和关联容器都有某一个算法,那么优先使用关联容器提供的算法。
对于关联容器,我们同样可以使用beign()和end()来遍历元素,遍历的元素是按照关键字的字典序升序输出的。
练习
11.15
从之前的内容可以知道。
mappped_type为vector<int>
key_type:int
value_type:pair<const int,vector<int>>
11.16
使用迭代器来将关键字3的值,改为three
map<size_t, string> my_map = { {1,"one"},{2,"two"},{3,"four"} };
auto iter = my_map.begin();
++iter;
++iter;
(*iter).second = "three";
cout<<my_map[3]<<endl;
11.17
只有2是不合法的
首先因为set的迭代器是const类型,所以无法修改迭代器指向的元素的值,但是copy算法是只读算法,所以不会修改multiset的内容。
其次是关联容器的迭代器可以作为目标位置的参数,进行insert,这是所有容器都有的成员函数,但是back_iterator()调用push_back(),set容器没有。
//std::copy(v.begin(),v.end(),std::inserter(c,c.end()));
//std::copy(v.begin(),v.end(),std::back_inserter(c));
//std::copy(c.begin(),c.end(),std::inserter(v,v.end()));
std::copy(c.begin(), c.end(), std::back_inserter(v));
11.18
map<string, int> word_count;
map<string, int>::const_iterator map_it = word_count.cbegin();
while (map_it!=word_count.cend()) {
//todo
++map_it;
}
11.19
之前写的Sale_data懒得copy过来了,使用string代替一下。
//自定义比较操作
bool compare_str_len(const string& str1,const string& str2) {
return str1.size() < str2.size();
}
//创建容器
multiset<string, bool (*)(const string& str1, const string& str2) > bookstore(compare_str_len);
//创建迭代器
multiset<string, bool(*)(const string& str1, const string& str2) >::const_iterator iter = bookstore.cbegin();
11.3.2 添加元素
在set和map添加元素时,如果关键字已经存在,则插入失败。
向map中插入元素,需要插入pair类型的数据。添加元素的方法和之前创建pair的方式一样。
可以使用列表初始化,make_pair以及显式的创建一个pair。
关联容器有如下的插入操作。
当我们向关联容器中添加一个元素时,容器会返回一个pair。pair的first是一个迭代器指向具有指定关键字的元素。second是一个bool值,表示插入是否成功。
注意,无论是set和map,插入元素时都会返回一个pair。不要把map的元素为pair类型和返回pair弄混了。
因为multiset和multimap允许重复关键字,所以向multiset和multimap插入新元素时,返回的只是指向新元素的迭代器。没有bool,因为不管关键字是否存在,插入总是成功的。
练习
11.20
开放题
我觉得使用下标更加的容易理解。
因为在map中如果关键字不存在,那么就会创建一个pair,并为关联的值执行值初始化。我们在编码的时候只需要一句话,++words[word].
而使用insert的话,首先需要检测插入是否成功,如果插入失败,需要在insert返回的pair上进行相关的操作。
这样的代码可读性是很差的。
++(result_pair.first->second)
string detect(const string& str,const set<char> & ingore) {
//string temp_str = "";
std::ostringstream output_str("");
for (const auto & item:str) {
if (ingore.find(item) == ingore.end()) {
output_str << static_cast<char>(tolower(item));
}
}
output_str.flush();
return output_str.str();
}
map<string, size_t> words;
set<string> ingore_word_set = {"the","an","a","of","from","and"};
set<char> ingore_char_set = {',','.',';'};
string word;
while (cin>>word) {
////11.1
//if (ingore_word_set.find(word)== ingore_word_set.end()) {
// word = detect(word,ingore_char_set);
// ++words[word];
//}
//11.20
if (ingore_word_set.find(word)== ingore_word_set.end()) {
word = detect(word,ingore_char_set);
auto result_pair = words.insert({word,1});
if (!result_pair.second) {
++(result_pair.first->second);
}
}
}
for (const auto& item:words) {
cout << item.first << " :" << item.second << endl;
}
11.21
作用:为单词计数
相对于我11.20写的代码,要简单一些。
调用insert会返回pair,无论是否插入成功,都会返回指向关键字-值对的迭代器。通过first访问该迭代器,然后通过箭头运算符,访问该迭代器指向的元素的值,并为值+1.
11.22
map的insert会返回一个pair,这个pair的first是指向容器中插入(或已存在)的关键字所在的键值对的迭代器。第二个参数为是否插入成功
map<string, vector<int>> m;
std::pair<map<string,vector<int>>::iterator,bool> result = m.insert({ "a",{1,2,3,4} });
11.23
multimap是沒有下标运算符的
而且如果分两次插入同一个关键字,那么在multimap中有两个pair。
//11.23
multimap<string, vector<string>> family = {
{"A",{"aa1","a2"}},
{"B",{"bb1","bb2","bb3"}}
};
vector<string> new_item = { "c1","cc2" };
family.insert({ "C",new_item });
family.insert(std::make_pair("A", new_item));
for (const auto& item : family) {
cout << item.first << ":" << std::ends;
for (const auto& member : item.second) {
cout << member<< std::ends;
}
cout << endl;
}
11.3.3 删除元素
关联容器提供三个版本的erase。
其中erase()可以传入关键字,关联容器将删除所有和此关键字匹配的元素并返回删除的元素的数量,对于关键字不能重复的容器,该值为0或者1,而对于关键字可以重复的容器,该值>=0.
对于传入迭代器的erase,关联容器将返回删除之后的元素的下一个迭代器。
对于传入输入范围的erase,关联容器将返回第二个迭代器。
11.3.4 map的下标操作
只有map和unordered_map才有下标操作,set没有下标操作,multimap也没有。
map的下标操作分为两个
一个是下标运算符,一个是at
如果使用下标运算符来访问map中的元素,如果该关键字不存在,则会创建一个关键字-值对插入到map中,并为值执行值初始化。
具体步骤为:
1.在map中搜索关键字
2.如果没有找到则创建一个关键字-值对,插入到map中
3.对值进行值初始化
因为使用下标运算符可能会添加新的元素,所以只有非const的map才可以使用下标运算符。
只有使用下标运算符,如果关键字不存在会报错,但是使用at不会,所以at,对于const的map也是可以用的。
而且at是会进行类型检查,当元素不存在时会报错。
下标操作的返回值
和vector,string类型不一样,map的下标操作得到的值类型和使用迭代器得到的值类型是不一样的。
map的下标操作得到的值类型为mapped_value,而使用迭代器解引用得到的值类型为value_type。
下标操作返回的是值得引用,所以我们可以通过下标操作来修改值。
练习
11.24
1.创建一个关键字和值类型都为int得map,并定义一个遍历m
2.在m中寻找关键字0
3.没有找到关键字0,所以添加一个关键字-值对,(0,0)
4.将关键字为0对应的值赋值为1
11.25
1.创建一个存储int值得vector类型,并定义一个变量v
2.访问v中下标为0的元素,由于v为空,所以报错
11.26
根据前面学习到的,如果一个类型定义了<运算符,那么这个类型就可以作为关键字,那么只要类型定义了<运算符,都可以对map进行下标操作。
下标操作返回的类型为map的值类型。
map<string, int> m;
m["1"]=123;
11.3.5 访问元素
C++ Primer书上说下标运算符和at()都只能在非const的map和unordered_map上使用,但是我在VS2017上,发现只有at()运算符是可以使用的,不知道这是微软对编译器做了优化还是怎么的
关联容器上访问元素的方式有很多,这取决于我们想要的操作。
1.find,用来查找给定的定关键字是否在容器中,如果在则返回指向元素的迭代器,如果不在则返回尾后迭代器
2.cout,用来统计给定关键字在容器中的数量,在不允许重复的关联容器中值为0或者1,在multi类的容器中值>=0.
3.lower_bound()用来返回第一个关键字不小于k的迭代器,也就是第一个>=k的迭代器,有什么用呢?如果我们使用默认<操作符来存放元素,那么存入的元素的关键字顺序是符合字典序的,那么我们使用lower_bound()得到的第一个>=k的迭代器可以作为迭代器范围的第一个参数,如果k不存在,返回尾后迭代器
4.upper_bound()得到是是第一个大于k的迭代器,可以用来作为迭代器范围的第二个元素。如果不存在,返回尾后迭代器,所以如果k不存在,那么lower_bound()==upper_bound();
5.equal_range(k)则是一次性找到等于k的一个范围,它返回一个pair,first指向第一个匹配的元素,second指向最后一个匹配的元素的下一个元素。如果没有匹配到,则first和second都是end(),所以equal_range()可以同时做到4和5的事情
对于multimap和multiset插入的相同关键字的元素是相邻存储的。
所以我们可以使用cout(k)找到关键字在容器中的个数。使用find(k)找到第一个匹配的关键字,然后使用循环遍历出相同关键字的元素。
multimap<int, string> m;
m.insert({1,"1"});
m.insert({1,"one"});
m.insert({1,"一"});
auto size = m.count(1);
auto iter = m.find(1);
while(size){
cout << iter->second << endl;
--size;
++iter;
}
也可以使用lower_bound和uppper_bound(),以及equal_range()来完成。
multimap<int, string> m;
m.insert({1,"1"});
m.insert({1,"one"});
m.insert({1,"一"});
for (auto iter = m.lower_bound(1); iter != m.upper_bound(1);++iter) {
cout << iter->second << endl;
}
auto two_iter = m.equal_range(1);
for (auto iter = two_iter.first; iter != two_iter.second;++iter) {
cout << iter->second << endl;
}
//这种方法更加的简洁
for (auto pos = m.equal_range(1); pos.first != pos.second;++pos.first) {
cout << pos.first->second << endl;
}
练习
11.27
如果我仅仅需要知道某个关键字是否在元素中,那么我会使用count。但是如果还需要对这个关键字进行访问,修改等操作,那么我使用find
11.28
map<string, vector<int>> m;
map<string, vector<int>>::iterator iter= m.find("123");
11.29
都返回都是容器的尾后迭代器,也就是不影响元素顺序的插入位置。 后半句话,书上只写提了这么一下,具体怎么理解,也没有说。= =
11.30
如果key存在,那么pos的first返回的是第一个和key匹配的元素的迭代器,因为对迭代器解引用得到的是一个pair类,所以为了访问迭代器所指向元素的值,我们需要pos.first->second;
11.31
multimap<string, string> books = { {"1","1"} ,{"1","one"},{"1","一"} ,{"3","3"} };
auto iter = books.find("1");
/*if (iter != books.end()) {
books.erase(iter);
}*/
//如果使用key_value的话,可以一次全部删除,但是使用迭代器是不行的
while (iter!=books.end()) {
books.erase(iter);
//在循环中更新迭代器
iter = books.find("1");
}
for (const auto& item:books) {
cout <<item.first<<":"<< item.second << endl;
}
11.32
上一个代码已经打印出来了。
11.3.6
如果使用下标操作重复的为关联容器添加相同关键字的容器,那么关键字-值对中的值,为最后一个赋值的值。
但是insert,如果关键则已经存在于容器内,不会指向插入。
map<string, string> books = { {"1","1"} ,{"1","one"},{"1","一"} ,{"3","3"} };
books["1"] = "344";
for (const auto& item:books) {
cout <<item.first<<":"<< item.second << endl;
}
输出为
1:344
3:3
如果没有那条赋值语句,则输出
1:1
3:3
练习
11.33
map<string, string> buildMap(ifstream& map_file) {
map<string, string> m;
string word;
string target_word;
while (map_file>>word&&std::getline(map_file,target_word)) {
//去除空格
target_word = target_word.substr(1);
if (target_word.size()>0) {
m[word] = target_word;
}
else {
throw std::exception("target word is null");
}
}
return m;
}
void word_transform(ifstream& map_file,ifstream &input) {
auto trans_map = buildMap(map_file);
string text;
while (std::getline(input,text)) {
std::istringstream stream(text);
string word;
while (stream>>word) {
auto iter = trans_map.find(word);
if (iter!=trans_map.end()) {
cout <<iter->second << std::ends;
}
else {
cout << word << std::ends;
}
}
cout << endl;
}
}
11.34
原本在trans_map中没有出现的关键字会被添加到trans_map中,trans_map[word],返回空字符串,程序编译报错。
11.35
不会起作用,因为关键字已经存在于map中,所以相同关键字的元素会插入失败
11.36
因为添加了异常检测,所以程序会报错。