关联容器——map

一、介绍

map中的元素是一些关键字-值(key-value)对:关键字起到索引的作用,值则表示与索引相关联的数据。

定义在头文件map中,即使用时需要#include <map>

map<string, size_t> word_count_map;		// 空map

map<string, string> authors = {
    {“Joyce”, “James”},
    {“Austen”, “Jane”}
}

注意:当初始化一个map时,必须提供关键字类型和值类型。我们将关键字-值包围在花括号中{key, value}来指出它们一起构成了map中的一个元素。

在每个花括号中,关键字是第一个元素,值是第二个。

map中的元素类型是pair,其中pair的第一个元素是const的。

关键字是const这一特性意味着不能将关联容器传递给修改或者重排容器的算法函数,因为这类算法函数需要向元素写入值。

// 统计每个单词在输入中出现的次数
#include<map>
#include<string>
#include<iostream>
using namespace std;

int main(){
    // 注意: map string等数据类型是在std标准命名空间中的,不要漏写了
    map<string, size_t> word2count_map;  // string到size_t的空map
    string word;
    while (cin >> word){
        ++ word2count_map[word];          // 提取word的计数器并将其加1
        // 循环退出条件:输入字符q
        if(word == "q"){
            break;
        }
    }
    // 对map中的每个元素
    for(const auto &w:word2count_map){
        // 打印结果
        cout << w.first << " occurs " << w.second
            << ((w.second > 1) ? " times" : " time") << endl;
    }
}
map遍历
#include<map>
#include<string>
#include<iostream>
using namespace std;

int main(){
    map<string, string> book_author_map = {
        {"《书剑恩仇录》", "金庸"},
        {"《在细雨中呐喊》", "余华"},
        {"《穆斯林的葬礼》", "霍达"}
    };
    // 获得一个指向首元素的迭代器
    auto info_iter = book_author_map.cbegin();
    // 比较当前迭代器和尾后迭代器:说明此时还未到尾后元素
    while(info_iter != book_author_map.cend()){
        // 解引用迭代器,打印输出map中的每个元素
        auto book = info_iter->first;
        auto author = info_iter->second;
        cout << "book->author: " << book << "-> " << author << endl;
        // 注意这里的迭代器自增1,移动到下一个迭代器
        ++info_iter;
    }
}

// 输出结果:
book->author: 《书剑恩仇录》-> 金庸
book->author: 《在细雨中呐喊》-> 余华
book->author: 《穆斯林的葬礼》-> 霍达
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类型值的范围
c.insert(il) il是这种值的花括号列表,函数返回void。对于map和set,只插入关键字不在c中的元素。对于multimap和multiset,则会插入范围中的每个元素。
c.insert(p, v) 类似insert(v)(或emplace(args)),但将迭代器p作为一个提示,指出从哪里开始搜索新元素的应该存储的位置。返回一个迭代器,指向具有给定关键字的元素。
c.emplace(p, args) 同c.insert(p, v)

insert成员函数向容器中添加一个元素或一个元素范围。

由于map不包含重复的关键字,因此插入一个已经存在的元素没有任何影响。

对于一个map进行insert操作时,必须记住元素类型是pair

通常,对于想要插入的数据,并没有一个现成的pair对象,可以在insert的参数列表中创建一个pair

// 向word_count插入 word 的4种方法
word_count.insert({word, 1});
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中。

检测insert的返回值

insert(或emplace)返回的值依赖于容器类型和参数。对于不包含重复关键字的容器,添加单一元素的insertemplace版本返回一个pair,告诉我们插入操作是否成功。

pairfirst成员是一个迭代器,指向具有给定关键字的元素;second成员是一个bool值,指出元素是插入成功还是已经存在于容器中。

如果关键字已在容器中,则insert什么事情也不做,且返回值中的bool部分为false。如果关键字不存在,元素被插入容器中,且bool值为true

示例代码:

#include<map>
#include<string>
#include<iostream>
using namespace std;

int main(){
    // 统计每个单词在输入中出现次数的一种更繁琐的方式
    map<string, size_t> word_count;  // 从string到size_t的空map
    string word;
    while(cin >> word){
        if(word == "q"){
            break;
        }
        // 插入一个元素,关键字等于word, 值为1
        // 若word已在word_count中, insert什么也不做
        auto ret = word_count.insert({word, 1});
        if(!ret.second){                // word已在word_count中
            ++ret.first->second;        // 递增计数器
        }
    }
}
// GDB断点调试结果:
(gdb) p ret.first
$1 = {
  first = "io", 
  second = 1
}
(gdb) p ret
$2 = {
  first = {
    first = "io", 
    second = 1
  }, 
  second = true
}
(gdb) p ret.first
$3 = {
  first = "io", 
  second = 1
}
(gdb) p ret.first.first
There is no member or method named first.
(gdb) p ret.first->second
$4 = 1
(gdb) p ret.first->first
$5 = "io"
删除元素
操作 说明
c.erase(k) 从c中删除每个关键字为k的元素。返回一个size_type值,指出删除的元素的数量
c.erase(p) 从c中删除迭代器p指定的元素。p必须指向c中的一个真实元素,不能等于c.end()。返回一个指向p之后的元素的迭代器,若p指向c中的尾元素,则返回c.end()
c.erase(b, e) 删除迭代器对b和e所表示的范围中的元素。返回e

关联容器提供一个erase操作,它接受一个key_type参数。此版本删除所有匹配给定关键字的元素(如果存在的话),返回实际删除的元素的数量。示例代码如下:

#include<map>
#include<string>
#include<iostream>
using namespace std;

int main(){
    map<string, string> book_author_map = {
        {"《书剑恩仇录》", "金庸"},
        {"《在细雨中呐喊》", "余华"},
        {"《穆斯林的葬礼》", "霍达"}
    };
    // 删除一个关键字,返回删除的元素数量
    if(book_author_map.erase("《书剑恩仇录》")){
        cout << "OK: " << "《书剑恩仇录》" << " removed\n";
    }
    else{
        cout << "oops: " << "《书剑恩仇录》" << " not found! \n";
    }
}
  
// GDB调试结果:
bzl@bzl ~/cppcode/Temp o gdb a.out -q
Reading symbols from a.out...done.
(gdb) start
Temporary breakpoint 1 at 0x400fe6: file example.cpp, line 6.
Starting program: /home/bzl/cppcode/Temp/a.out 

Temporary breakpoint 1, main () at example.cpp:6
6	int main(){
(gdb) n
11	    };
(gdb) n
13	    if(book_author_map.erase("《书剑恩仇录》")){
(gdb) n
14	        cout << "OK: " << "《书剑恩仇录》" << " removed\n";
(gdb) p book_author_map
$1 = std::map with 2 elements = {
  ["《在细雨中呐喊》"] = "余华",
  ["《穆斯林的葬礼》"] = "霍达"
}
// 可以看到当前的 book_author_map 中已经删除了 《书剑恩仇录》 这个key-value键值对

对于保存不重复关键字的容器,erase的返回值总是0或1。若返回值为0,则表明想要删除的元素并不在容器中。

map的下标操作

map容器提供了下标运算符和一个对应的at函数。

map下标运算符接受一个索引(即,一个关键字),获取与此关键字相关联的值。但是,与其他下标运算符不同的是,如果关键字并不在map中,会为它创建一个元素并插入到map中,关联值将进行初始化:

#include<map>
#include<string>
#include<iostream>
using namespace std;

int main(){
    map<string, string> book_author_map = {
        {"《书剑恩仇录》", "金庸"},
        {"《在细雨中呐喊》", "余华"},
        {"《穆斯林的葬礼》", "霍达"}
    };
    // 插入一个关键字为 《黄金时代》
    book_author_map["《黄金时代》"] = "王小波";
    for(auto info_obj:book_author_map){
        cout << info_obj.first << "\n";
        cout << info_obj.second << "\n";
    }
    // at的使用
    auto author = book_author_map.at("《剑恩仇录》");
    cout << author << "\n";
}

由于下标运算符可能插入一个新元素,我们只可以对非constmap使用下标操作。(只能对可变对象执行此操作)

使用一个不在容器中的关键字作为下标,会添加一个具有此关键字的元素到map中。

操作 说明
c[k] 返回关键字为k的元素:如果k不在c中,添加一个关键字为k的元素,对其进行值初始化
c.at(k) 访问关键字为k的元素,带参数检查:如果k不在c中,抛出一个out_of_range异常

解引用一个迭代器返回的类型与下标运算符返回的类型是一样的。

对一个map进行下标操作时,会获得一个mapped_type对象;但当解引用一个map迭代器时,会得到一个value_type对象。

与其他下标运算符相同的是,map的下标运算符返回一个左值。由于返回的是一个左值,所以我们既可以读也可以写元素

vectorstring不同,map的下标运算符返回的类型与解引用map迭代器得到的类型不同。

访问元素

下标和at操作只适用于非constmapunordered_map

操作 说明
c.find(k) 返回一个迭代器,指向第一个关键字为k的元素,若k不在容器中,则返回尾后迭代器
c.count(k) 返回关键字等于k的元素的数量。对于不允许重复关键字的容器,返回值永远是0或1
c.lower_bound(k) 返回一个迭代器,指向第一个关键字不小于k的元素
c.upper_bound(k) 返回一个迭代器,指向第一个大于k的元素
c.equal_range(k) 返回一个迭代器pair, 表示关键字等于k的元素的范围。若k不存在,pair的两个成员均等于c.end()
使用find代替下标操作

下标运算符提供了最简单的提取元素的方法。

但是,如我们所见,使用下标操作有一个严重的副作用:如果关键字还未在map中,下标操作会插入一个具有给定关键字的元素。这种行为是否正确完全依赖于我们的预期是什么。例如,单词计数程序有这样 一个特性:使用一个不存在的关键字作为下标,会插入一个新元素,其关键字为给定关键字,其值为0。也就是说,下标操作的行为符合我们的预期。

但有时,我们只是向知道一个给定关键字是否在map中,而不想改变map。这样就不能使用下标运算符来检查一个元素是否存在,因为如果关键字不存在的话,下标运算符会插入一个新元素。在这种情况下,应该使用find

#include<map>
#include<string>
#include<iostream>
using namespace std;

int main(){
    map<string, string> book_author_map = {
        {"《书剑恩仇录》", "金庸"},
        {"《在细雨中呐喊》", "余华"},
        {"《穆斯林的葬礼》", "霍达"}
    };
    // 使用find检查一个元素是否存在
    // 注意:下面的判断条件: 若不在该容器中,find()返回值是尾后迭代器,所以下面的判断逻辑是不存在的时候
    if(book_author_map.find("《书剑恩仇录》") == book_author_map.end()){
        cout << "《雪山飞狐》不存在当前集合中" << endl;
    }
    else{
        cout << "《书剑恩仇录》存在当前集合中" << endl;
    }
}
一个单词转换的map示例

功能:给定一个string,将它转换为另一个string。程序的输入是两个文件。第一个文件保存的是一些规则,用来转换第二个文件中的文本。每条规则由两部分组成。

规则:一个可能出现在输入文件中的单词和一个用来替换它的短语。表达的含义是:每当第一个单词出现在输入中时,我们就将它替换为对应的短语。第二个输入文件包含要转换的文本。

#include<map>
#include<string>
#include<fstream>
#include<iostream>

using namespace std;

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;
    }
}

map<string, string> buildMap(ifstream, &map_file){
    map<string, string> trans_map;  // 保存转换规则
    string key;                     // 要转换的单词
    string value;                   // 替换后的内容
    // 读取第一个单词存入key中, 行中剩余内容存入value
    while(map_file >> key && getline(map_file, value)){
        if(value.size() > 1){       // 检查是否有转换规则
            trans_map[key] = value.substr(1);  // 跳过前导空格
        }  
        else{
            throw runtime_error("no rule for " + key);
        }
    }
    return trans_map;
}

const string &transform(const string &s, const map<string, string> &m){
    // 实际的转换工作,此部分是程序的核心
    auto map_item = m.find(s);
    // 如果单词在转换规则中
    if(map_item != m.cend()){
        return map_item->second;  // 使用替换短语
    }
    else{
        return s;                 // 否则返回原string
    }
}

int main(){
    string map_file = "brb be right back
        k okay?
        y why
        r are
        u you
        pic picture
        thk thans!
        18r later
        ";
     // 这个先暂且停一下
     word_transform(map_file, )
}
posted @ 2021-09-20 11:17  砚台是黑的  阅读(86)  评论(0编辑  收藏  举报