C++ Primer 学习笔记 第十一章 关联容器

2023-11-08

关联容器中的元素是按关键字来保存和访问的,与之相对的顺序容器是按元素在容器中的顺序来保存和访问的。

关联容器支持高效的关键字查找和访问,两个主要的关联容器类型是map和set。map中的元素是一些键(关键字)值对,键起到索引的作用,值则表示与索引相关的数据。set中每个元素只包含一个关键字(关键字即值),set支持高效的关键字查询操作。

标准库提供8个关联容器,它们的不同体现在三个不同维度上:
1.或者是一个map,或者是一个set。
2.或者要求不重复的关键字,或者允许重复的关键字。
3.按顺序保存元素或无序保存。
在这里插入图片描述
允许重复关键字的容器名字中都包含单词multi,不保持关键字按顺序存储的容器名都以unordered开头,因此unordered_multi_set是一个允许重复关键字、元素无序保存的集合,而set是一个要求不重复关键字、有序存储的集合。

无序容器使用哈希函数来组织元素。

类型map和multimap定义在头文件map中,set和multiset定义在头文件set中。无序容器则定义在头文件unordered_map和unordered_set中。

map可以看成将关键字映射到值。map类型通常被称为关联数组,它的特殊之处在于其下标可以不是整数,可以是关键字,如给定一个名字到电话号码的map,我们可以使用人名作为下标来获取此人的电话号码。

set就是关键字的简单集合。当只是想知道一个值是否存在时,set是最有用的。set的去重是库函数实现的,编写简单,并且查找的效率高,set是红黑树实现的,查找效率与元素个数成对数关系。

map给单词计数:

map<string, int> word_count;
string word;
while (cin >> word) {    
    ++word_count[word];    // 如果word未在map中,会创建一个新元素,其关键字为word,值为0
}

for (const auto &w : word_count) {
    cout << w.first << " occurs " << w.second << ((w.second > 1) ? "times" : "time") << endl;
}

以上for循环中,当从map中提取一个元素时,会得到一个pair类型的对象,它是一个模板类型,保存两个名为first和second的公有数据成员,first用来保存关键字,而second保存对应的值。

类似于顺序容器,关联容器也是模板,为定义一个map,我们需要指定关键字和值的类型。

上例的扩展:忽略某些的单词,我们可以使用set保存想忽略的单词,只对不出现在集合中的单词统计出现次数:

map<string, int> word_count;
set<string> exclude = { "The", "the" };
string word;
while (cin >> word) {    
    if (exclude.find(word) == exclude.end()) {    // find成员返回一个迭代器,如给定关键字在set中,则迭代器指向该数字,否则,迭代器指向尾后元素
        ++word_count[word];    // 如果word未在map中,会创建一个新元素,其关键字为word,值为0
    }
}

for (const auto &w : word_count) {
    cout << w.first << " occurs " << w.second << ((w.second > 1) ? "times" : "time") << endl;
}

set也是一个模板,定义set必须指定其元素类型。可以用列表初始化方式初始化set。

关联容器都支持普通容器操作,但不支持顺序容器相关的操作,如push_back、push_front,原因在于关联容器中元素是根据关键字存储的。关联容器也不支持像构造函数或插入操作这些接受一个元素值和一个数量值的操作。

关联容器的迭代器都是双向迭代器。

如上例,C++11中可以使用列表值初始化关联容器:

map<string, string> authors = { { "Joyce", "James" },
                                { "Austen", "Jane" } };    // 两个元素的map容器,与以往一样,初始化器必须能将输入元素类型转换为容器中元素的类型

每个关联容器都定义了一个默认构造函数,它创建一个指定类型的空容器。我们也可以将关联容器初始化为另一个同类型容器的拷贝。

一个map或set中的关键字必须是唯一的。但容器multimap和multiset没有此限制:

int main() {
    // 创建一个含有0~9的vector,每个元素重复出现两次
    vector<int> ivec;
    for (int i = 0; i < 10; ++i) {
        ivec.push_back(i);
        ivec.push_back(i);
    }

    set<int> iset(ivec.begin(), ivec.end());
    multiset<int> miset(ivec.begin(), ivec.end());

    cout << ivec.size() << endl;    // 输出20
    cout << iset.size() << endl;    // 输出10
    cout << miset.size() << endl;    // 输出20
}

上例中,即使我们使用ivec初始化iset,它也只含不重复的元素,而multiset允许重复元素。

关联容器对其关键字类型有一些限制,对有序容器(map、multimap、set、multiset)来说,关键字类型必须定义元素比较的方法,默认情况下,使用<运算符来比较两个关键字。在set中,关键字类型就是元素类型;在map中,关键字类型是元素的第一部分的类型。以上要求就像传递给排序算法的元素类型一样。

我们可以向一个算法提供我们自己定义的比较操作,类似地,也可以提供自己定义的操作来代替关键字上的<运算符。所提供的操作必须在关键字类型上定义一个严格弱序:
1.两个关键字不能同时小于等于对方:如k1小于k2,则k2不能小于k1。
2.传递性,若k1<=k2,k2<=k3,则k1<=k3。
3.若存在两个关键字,任何一个都不小于等于另一个,那么称这两个关键字是等价的。若k1等价于k2,且k2等价于k3,则k1等价于k3。

如果两个关键字是等价的,那么容器将它们视作相等来处理,当这两个关键字用作map的关键字时,只能有一个元素与这两个关键字关联,可以用两者中任意一个来访问对应的值:

bool compStringLen(string s1, string s2) {
    return s1.length() < s2.length();
}

int main() {
    map<string, int, bool(*) (string, string)> m(compStringLen);
    m.insert({ "sss", 5 });
    m.insert({ "ddd", 2 });
    cout << m["sss"] << endl;
    cout << m["ddd"] << endl;
    cout << m["ooo"] << endl;    // 甚至也能返回5,说明插入键相同的元素进入时,新插入的元素会被完全舍弃
}

运行它:
在这里插入图片描述

使用关键字类型的比较函数:如我们不能直接定义一个Sales_data的multiset,因为Sales_data没有<运算符,但我们可以用compareIsbn函数定义一个multiset,此函数就是关键字类型Sales_data的比较函数,此函数在Sales_data对象的ISBN成员上定义了一个严格弱序:

bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) {
    return lhs.isbn() < rhs.isbn();
}

为使用自己定义的操作,在定义multiset时必须提供两个类型,一个是关键字类型Sales_data;另一个是比较操作的类型,它是能指向compareIsbn函数的指针类型:

multiset<Sales_data, decltype(compareIsbn) *> bookStore(compareIsbn);
multiset<Sales_data, bool(*) (const Sales_data&, const Sales_data&)> bookStore(compareIsbn);    // 与上句代码含义相同

如上,我们使用decltype来获取一个函数指针类型时,必须加上一个*来指出我们要使用的是函数类型的指针。之后用compareIsbn来初始化bookStore表示当我们向bookStore添加元素时,调用compareIsbn来为这些元素排序。我们可以使用compareIsbn来代替&compareIsbn作为构造函数的参数,它会自动转化为一个指针,当然,使用&compareIsbn也是一样的。

定义一个map,将单词与行号的list关联,list中保存的是单词所出现的行号:

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <list>
#include <map>
using namespace std;

int main() {
	map<string, list<int>> words;
	ifstream fin(文件路径);
	string line, word;
	int lineCount = 0;
	while (getline(fin, line)) {
		++lineCount;
		istringstream sin(line);
		while (sin >> word) {
			words[word].push_back(lineCount);
		}
	}

	for (auto& p : words) {
		cout << p.first << " : ";
		list<int>::iterator b = p.second.begin(), e = p.second.end();
		while (b != e) {
			cout << *b << " ";
			++b;
		}
		cout << endl;
	}
}

可以定义一个vector<int>::iterator为键的map,但不能定义list<int>::iterator为键的map,因为有序关联容器要求关键字必须支持<运算符,而list的迭代器是双向迭代器而不是随机访问迭代器,不支持<操作。

pair类型定义在头文件utility中,一个pair保存两个数据成员,类似容器,pair是一个生成特定类型的模板,当创建一个pair时,我们必须提供两个类型名,pair的数据成员将具有对应的类型,这两个类型可以不同。

pair的默认构造函数对数据成员进行值初始化:

pair<string, int> p;    // 包含一个空串和一个0

也可以为每个成员提供初始化器:

pair<string, string> author{"James", "Joyce"};    // 包含两个给定值的串

pair的数据成员是public的,两个成员分别命名为first和second,我们用成员访问符来访问它们。

map的元素是pair。

pair上的操作:
在这里插入图片描述
C++11新标准下,若一个函数需要返回一个pair,我们可以对返回值进行列表初始化:

pair<string, int> process(vector<string> &v) {
    if (!v.empty()) {
        return { v.back(), v.back().size() };    // 列表初始化,用v中最后一个string和最后一个string的大小初始化
    } else {
        return pair<string, int>();    // 隐式构造返回值
    }
}

上例中,较老的C++版本不允许花括号包围的初始化列表来返回pair的对象,必须显式构造返回值:

return pair<string, int>(v.back(), v.back().size());

我们还可以使用make_pair来生成pair对象:

return make_pair(v.back(), v.back().size());

关联容器额外的类型别名:
在这里插入图片描述
对于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类型(unordered_map、unordered_multimap、multimap、map)才有mapped_type。

当解引用一个关联容器迭代器时,我们会得到一个类型为容器的value_type的值的引用,对map而言,value_type是一个pair类型,其first成员保存const的关键字,second成员保存值。

auto map_it = word_count.begin();    // 获得指向word_count中一个元素的迭代器
map<string, list<int>>::iterator map_it = word_count.begin();    // 含义与上句代码相同

cout << map_it->first;    // 输出此元素的关键字
cout << " " << map_it->second;    // 输出此元素的值

map_it->first = "new key";    // 错误,元素的关键字是const的
++map_it->second;    // 正确,元素值不是const的

一个map的value_type是一个pair,我们可以改变pair的值,但不能改变关键字成员的值。

虽然set类型同时定义了iterator和const_iterator类型,但两种类型都只允许访问set中的元素,即都是const的:

set<int> iset = { 0, 1, 2, 3, 4 };
set<int>::iterator set_it = iset.begin();
if (set_it != iset.end()) {
    *set_it = 42;    // 错误,set中的关键字是只读的
    cout << *set_it << endl;    // 正确,可以访问关键字
}

遍历关联容器:

auto map_it = word_count.cbegin();
while (map_it != word_count.cend()) {
    cout << map_it->first << " occurs " << map_it->second << ((map_it->second > 1) ? "times" : "time") << endl;
    ++map_it;
}

以上程序中word_count中元素是按关键字的字典顺序排列的,当使用一个迭代器遍历一个map、multimap、set或multiset时,迭代器按关键字升序遍历元素。

通常我们不对关联容器使用泛型算法,关键字是const的这一特性意味着不能将关联容器传递给修改或重排容器元素的算法,因为这类算法需要向元素写入值。

关联容器可用于只读取元素的算法,但是,很多这类算法需要搜索序列,由于关联容器中的元素通过它们的关键字能很快地进行查找,因此对其使用泛型搜索算法几乎总是个坏主意。关联容器定义了一个名为find的成员,它通过一个给定的关键字直接获取元素。泛型find会顺序搜索容器,而关联容器定义的find成员会快得多。

实际编程中,如果我们真要对一个关联容器使用算法,要么将它当作一个源序列,要么当作一个目的位置。如,可以使用泛型copy算法将元素从一个关联容器拷贝到另一个序列。类似地,可以调用inserter将一个插入器绑定到一个关联容器,就可以将这个插入器用于一个算法。

	multiset<string> smset = { "aaa", "bbb", "ccc" };
	vector<string> svec = { "ddd", "eee", "fff" };
	copy(svec.begin(), svec.end(), inserter(smset, smset.end()));    // 正确
    copy(svec.begin(), svec.end(), back_inserter(smset));    // 错误,报错push_back不是multiset的成员
    copy(smset.begin(), smset.end(), inserter(svec, svec.end()));    // 正确
    copy(smset.begin(), smset.end(), back_inserter(svec));    // 正确

关联容器的insert成员向容器插入一个元素或一个元素范围。由于map和set(及其对应的无序类型)不包含重复的关键字,因此插入一个已存在的元素对容器没有任何影响:

vector<int> ivec = { 2, 4, 6, 2, 4, 6 };
set<int> set2;    // 空集合
set2.insert(ivec.cbegin(), ivec.cend());    // set2有3个元素
set2.insert({ 1, 3, 5, 1, 3, 5 });    // set2现在有6个元素

insert有两个版本,一个版本接受一对迭代器,另一个版本接受一个初始化列表。

对一个map进行inset操作时,元素类型是pair,通常,对于想要插入的数据,可以在insert参数列表中创建一个pair:

word_count.insert({ word, 1 });    // C++11新标准
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, int>(word, 1));
word_count.insert(map<string, int>::value_type(word, 1));

关联容器的insert操作:
在这里插入图片描述
insert或emplace返回的值依赖于容器类型和参数。对于不包含重复关键字的容器,添加单一元素的insert和emplace版本返回一个pair,它的first成员是一个迭代器,指向具有给定关键字的pair,second成员是一个bool值,指出元素是插入成功还是已经存在于容器。如果关键字已在容器中,则insert什么事情也不做,且返回值中的bool部分为false。如果关键字不存在,元素被插入容器中,且bool值为true。但我在测试时发现map的insert,如果要插入的值存在了,则新插入的pair的键和旧键同时都能访问容器中的原值,而忽略新插入的值:

bool compStringLen(string s1, string s2) {
    return s1.length() < s2.length();
}

int main() {
    map<string, int, bool(*) (string, string)> m(compStringLen);
    m.insert({ "sss", 5 });
    auto res = m.insert({ "ddd", 2 });
    cout << (*res.first).first << endl;    // insert返回指向容器中键相同的元素的迭代器
    cout << res.second << endl;
    cout << m["sss"] << endl;
    cout << m["ooo"] << endl;
}

运行它:
在这里插入图片描述
下面是一个更繁琐的统计单词输入次数的例子:

map<string, int> word_count;
string word;
while (cin >> word) {
    pair<map<string, int>::iterator, bool> ret = word_count.insert({word, 1});    // 若使用C++11版本,ret的类型可以使用auto自动推断
    if (!ret.second) {
        ++ret.first->second;
    }
}

其中,递增计数器语句的优先级等价于:

++((ret.first)->second);

使用multimap:

multimap<string, string> authors;
authors.insert({ "Barth, John", "Sot-Weed Factor" });
authors.insert({ "Barth, John", "Lost in the Funhouse" });    // 正确,添加了第二个关键字相同的元素

对于允许重复关键字的容器,接受单个元素的insert操作返回一个指向新元素的迭代器,这里无须返回bool值,因为insert总是向这类容器中加入一个新元素。

新的给单词计数的例子:

while (cin >> word) {
    ++word_count.insert({ word, 0 }).first->second;
}

其中insert返回的pair中的first成员要么是已经存在的关键字相同的元素的迭代器,要么是新添加的值为0的元素的迭代器,再对迭代器指向对象的值增加1。

关联容器定义了三个版本的erase:
在这里插入图片描述
对于保存不重复关键字的容器,erase的返回值总是0或1,0表示想要删除的元素并不在容器中;对于允许重复关键字的容器,删除元素的数量可能大于1。

map和unordered_map的下标操作:
在这里插入图片描述

map和unordered_map容器都提供了下标运算符和一个对应的at函数,但set类型不支持下标,因为set中没有与关键字相关联的值。我们不能对一个multimap或unordered_multimap进行下标操作,因为这些容器中可能一个下标对应多个结果。

对map使用下标运算符时,若下标运算符接受的关键字值不在map中,会以该键值创建一个元素并插入map,这个关键字关联的值会被值初始化:

map<string, size_t> word_count;    // empty map
word_count["Anna"] = 1;

上例会:
1.在word_count中查找关键字Anna,未找到。
2.将一个新的关键字-值对插入到word_count中,关键字是一个const string,保存Anna,并进行值初始化为0.
3.提取出新插入的元素,并将值1赋给它。

由于下标运算符可能会插入一个新元素,我们只能对非const的map容器使用下标操作。

map的下标运算符与我们用过的其他下标运算符不同之处在于返回类型,通常,解引用一个迭代器返回的类型与下标运算符返回的类型是一样的,但对map进行下标操作时,会获得一个mapped_type类型的对象,而当解引用一个map迭代器时会得到一个value_type类型的对象。

而与其他下标运算符相同的是,map的下标运算符返回的是一个左值。

在一个关联容器中查找元素的操作:
在这里插入图片描述
在这里插入图片描述
上图中有一个错误,c.equal_range(k),若k不存在,返回的pair的两个迭代器成员指向一个安全位置,在此位置插入关键字不破坏c的顺序:

    multimap<string, int> mmap = { { "c", 2 }, { "c", 2 } };
    auto p = mmap.equal_range("a");
    if (p.first == mmap.begin()) {
        cout << "若找不到时,指向安全位置。" << endl;    // 会输出
    }

对map和unordered_map,下标运算符提供了最简单的提取元素的方法,但它有一个副作用,当关键字还未在map中时,会插入一个具有给定关键字的新元素。但有时,我们只想知道一个给定的关键字是否在map中,而不想改变map,这种情况下应该使用find:

if (word_count.find("foobar") == word_count.end()) {
    cout << "foobar is not in the map" << endl;
}

对于允许重复关键字的关联容器,这些重复关键字的元素会在容器中相邻存储。

若我们在作者和著作的映射中,想找到一个作者的所有著作:
方法一:

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

方法二:lower_bound返回的迭代器将指向第一个具有给定关键字的元素,而upper_bound会返回最后一个匹配的关键字元素之后的元素的迭代器。如果元素不在multimap中,lower_bond和upper_bound会返回相等的迭代器,即指向一个安全插入点,在此处插入指定关键字的元素不影响容器中元素的顺序。即这两个函数会返回一个迭代器范围,表示具有该关键字的元素的范围。当然,这两个操作返回的迭代器可能是容器的尾后迭代器,如果我们查找的元素具有容器中最大的关键字,则此关键字的upper_bound返回尾后迭代器,如果关键字不存在,且大于容器中任何迭代器,则lower_bound返回的也是尾后迭代器:

auto beg = authors.lower_bound(search_item), end = authors.upper_bound(search_item);
for ( ; beg != end; ++beg) {
    cout << beg->second << endl;
}

上例中,如果没有以search_item为关键字的元素,beg和end都将指向第一个关键字大于search_item的元素,有可能是尾后迭代器。

方法三:使用equal_range函数,此函数接受一个关键字,返回一个迭代器pair,若关键字存在,则第一个迭代器返回第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素之后的位置。若未找到匹配元素,则两个迭代器都指向关键字可以插入的安全位置:

for (auto pos = authors.equal_range(search_item); pos.first != pos.second; ++pos.first) {
    cout << pos.first->second << endl;
}

multimap中的内容按序排列:

multimap<string, int> mmap = { { "c", 2 }, { "c", 2 }, { "d", 5 }, { "a", 1} };
for (pair<const string, int> &s : mmap) {
    cout << s.first << endl;    // 输出accd
}

单词转换程序:读取两个文件,一个文件保存的是一些转换规则,另一个文件保存的是一个文本,单词转换文件内容:
在这里插入图片描述
输入文件内容:
在这里插入图片描述
程序代码:

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

map<string, string> BuildMap(ifstream& mapFile) {    // 返回从缩写到完全体的map
    map<string, string> trans_map;
    string key, value;
    while (mapFile >> key && getline(mapFile, value)) {    // 从流中获取一个string遇到空格时,空格不会被读取到流中
        if (value.size() > 1) {    // 如果有值(键和值之间有空格,有值时value的size比1大)
            trans_map[key] = value.substr(1);    // 去掉键和值之间的空格并保存到trans_map,隐含着如果一个单词在转换文件中出现多次,则以最后一次出现的为准
        } else {
            throw runtime_error("no rule for " + key);
        }
    }
    return trans_map;
}

string Transform(map<string, string> &trans_map, string &s) {    // 复杂类型用引用可以提高性能
    map<string, string>::iterator it = trans_map.find(s);

    if (it != trans_map.end()) {
        return it->second;
    } else {
        return s;
    }
}

void Word_transform(ifstream& mapFile, ifstream& input) {
    map<string, string> trans_map = BuildMap(mapFile);
    string line;
    while (getline(input, line)) {
        istringstream sin(line);
        string word;
        bool isFirst = true;
        while (sin >> word) {
            if (isFirst) {
                isFirst = false;
            } else {
                cout << " ";    // 在每个单词前打印一个空格,但每行第一个单词就不必了
            }
            cout << Transform(trans_map, word);
        }
        cout << endl;
    }
}

int main() {
    ifstream map_file("文件路径"), input("文件路径");
    
    if (!map_file) {
        cout << "map_file文件打开失败。" << endl;
    }
    if (!input) {
        cout << "input文件打开失败。" << endl;
    }
    
    if (map_file && input) {
        Word_transform(map_file, input);
        
        map_file.close();
        input.close();
    }
}

在以上代码中,将trans_map[key] = value.substr(1);改为trans_map.insert({key, value.substr(1)});效果为,在改之前,每次在map_file中找到关键字重复的条目时,会将关键字与最新(在文件中是最下面)的值关联,而修改后关键字会一直等于第一次插入时的值。

C++ 11定义了4个无序关联容器,这些容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字的==运算符。在关键字类型的元素没有明显的序关系的情况下,无序容器很有用,因为有时维护元素的序的代价非常高昂。

除了哈希管理外,无序容器还提供了与有序容器相同的操作,如find、insert等,并且无序容器也有允许重复值的版本,因此通常可以用无序容器替换对应的有序容器,反之亦然。但由于无序容器的元素未按顺序存储,一个使用无序容器的的程序的输出通常会与使用有序版本的不同。

无序容器在存储上组织为一组桶,每个桶保存0个或多个元素。无序容器使用一个哈希函数将元素映射到桶,为访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶,容器将具有一个特定哈希值的所有元素都保存在相同的桶中。如果元素允许重复关键字,所有具有相同关键字的元素也都会在一个桶中。因此,无序容器的性能依赖于哈希函数的质量和桶的数量和大小。

对于相同的参数,哈希函数必须总是产生相同的结果,理想情况下,哈希函数将每个特定值映射到唯一的桶,但将不同的元素映射到相同的桶也是允许的。当一个桶保存多个元素时,需要顺序搜索桶内元素,如果一个桶中保存了很多元素,那么查找速度会慢。

无序容器管理操作:
在这里插入图片描述
默认,无序容器使用关键字类型的==运算符来比较元素,无序容器还使用一个hash<key_type>类型的对象来生成每个元素的哈希值。标准库为内置类型、一些标准库类型如string、智能指针提供了hash模板,因此可以直接定义以上类型的无序容器。但我们不能直接定义关键字类型为自定义类类型的无序容器,我们必须提供自己的哈希函数版本才能使用。

为了能将Sales_data类型用作关键字,我们需要提供函数来替代==运算符和哈希值计算函数:

size_t hasher(const Sales_data &sd) {
    return hash<string>()(sd.isbn());
}

bool eqOp(const Sales_data &lhs, const Sales_data &rhs) {
    return lhs.isbn() == rhs.isbn();
}

我们的hasher函数的return后面定义了一个std::hash<std::string>临时变量(第一对圆括号),然后调用该临时变量的成员函数operator()(第二对圆括号,这里是运算符重载),并传sd.isbn()的返回值作为参数。

类似地,eqOp函数通过比较ISBN号来比较两个Sales_data。

定义一个unordered_multiset:

using SD_multiset = unordered_multiset<Sales_data, decltype(hasher)*, decltype(eqOp)*>;
SD_multiset bookstore(42, hasher, eqOp);    // 参数是桶大小、哈希函数指针、相等性判断运算符指针

unordered_multiset<Sales_data, decltype(hasher)*> bookstore(42, hasher);    // 如果我们的类Sales_data定义了==运算符,就不需要相等性判断运算符指针了
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ Primer 学习笔记 第十一章 关联容器 的相关文章

  • Android基础教程(非常详细)从零基础入门到精通,看完这一篇就够了

    文章目录 一 开发环境搭建 二 简单控件 1 文本显示 2 按钮 3 常用布局 1 线性布局LinearLayout 3 相对布局RelativeLayout 3 网格布局GridLayout 4 图像显示 1 图像的缩放问题 2 图像按钮
  • Ubuntu 18.04下使用Apache搭建一个web服务器

    Ubuntu 18 04下使用Apache搭建一个web服务器 几个必要的概念 web服务器 以我之见 web服务器就是运行在计算机上的一个向整个网络或者是web客户端 浏览器提供文档的一个程序 我们通过http请求便可以获取到存储到web
  • 阿里巴巴管理三板斧 - 阿里巴巴专栏

    阿里巴巴管理三板斧 阿里巴巴专栏 阿里巴巴管理三板斧 阿里巴巴专栏 posted on 2016 07 16 00 44 lexus 阅读 评论 编辑 收藏 转载于 https www cnblogs com lexus p 5675125
  • 基于51单片机的智能大棚浇花系统设计 花盆浇水灌溉补光散热方案原理图程序

    硬件电路的设计 附文件 3 1系统的功能分析及体系结构设计 3 1 1系统功能分析 本设计由STC89C52单片机电路 LCD1602液晶显示电路 光照检测电路 土壤湿度传感器电路 A D采样PCF8591电路 风扇控制电路 继电器控制电路
  • windows7最简单最快速解决“此windows副本不是正版”(“This copy of Windows is not genuine”)方法

    如果出现这个问题的话 windows的桌面就会变成全黑 并且右下角出现 其实解决这个问题的方法有很多种 有很多工具都可以解决这个问题 下面介绍下最简单快速的解决方法 步骤1 在开始的输入框中输入cmd 步骤2 右击出现的cmd 以管理员ad
  • ubuntu2.0安装postgresql

    1 更新系统软件包 首先 通过在终端中运行以下以下apt命令 确保所有系统软件包都是最新的 sudo apt update sudo apt upgrade 2 安装 使用以下apt命令软件包 apt get install postgre
  • 智能算法和人工智能算法,人工智能算法概念股票

    人工智能股票有哪些 1 苏州科达 苏州科达科技股份有限公司是领先的视讯与安防产品及解决方案提供商 致力于以视频会议 视频监控以及丰富的视频应用解决方案帮助各类政府及企业客户解决可视化沟通与管理难题 2012年 公司整体改制为股份有限公司 2
  • python之数据驱动DDT安装

    黑窗口一行指令即可 pip install ddt
  • Mybatis与Spring的集成

    目录 一 Mybatis与spring的集成 Mybatis与spring集成的步骤 1 导入pom依赖 2 利用mybatis逆向工程生成模型底层代码 3 编写appolication mybatis xml 4 Spring Test
  • 让GPT成为您的科研加速器丨GPT引领前沿与应用突破之GPT4科研实践技术与AI绘图

    GPT对于每个科研人员已经成为不可或缺的辅助工具 不同的研究领域和项目具有不同的需求 如在科研编程 绘图领域 1 编程建议和示例代码 无论你使用的编程语言是Python R MATLAB还是其他语言 都可以为你提供相关的代码示例 2 数据可
  • 5-软件实现

    程序设计语言 数据成分 运算成分 控制成分 传输成分 结构化程序设计编码 结构化程序设计的特点 自顶向下 逐步求情 单入口和单出口的控制结构 结构化程序设计步骤 提出和分析问题 确定数学模型 设计算法 模块化编程 编译 运行程序 模块设计和
  • DID基础介绍

    1 介绍 DID Decentralized Identity 去中心化身份标识 它的本质是基于去中心化体系下的中心化信任模型 2 相关名词解释 DID标识符 did example 1232423143215jlgaglgak 前缀必然是

随机推荐

  • fastDFS文件服务器的java客户端初始化方法ClientGlobal.init(fdfs_client.properties) 找不到配置文件路径异常的解决

    最近在使用fastDFS文件服务器的java客户端上传文件时 它的初始化方法ClientGlobal init String 出现找不到配置文件的异常 无论是写死fdfs client properties文件位置还是怎样 都找不到配置文件
  • 阅读element-ui源码之ResizeObserver使用

    1 ResizeObserver 阅读tabs标签页源码时 发现了这个api 于是 我查了下MDN 可以监听任意DOM元素内容区域的变化 这里的变化包括但不限于 1 某个节点的出现和隐藏 2 某个节点的大小变化 和resize api相比的
  • Mac上的oracle使用

    进入docker容器 sudo docker exec it docker ps grep oracle cut d f 1 bin bash 通过sqlplus进入Oracle sqlplus 输入用户名和密码进入 Oracle用户中的默
  • npm报错Failed at the electron-chromedriver@1.8.0 install script.

    问题描述 Electron vue 项目 npm install 报错Failed at the electron chromedriver 1 8 0 install script 解决方案 方法一 vue cli 脚手架的一个 bug
  • unity 删除依赖

    记录 Scene中有依赖废弃的资源 using System using System Collections using System Collections Generic using System IO using System Li
  • JavaScript初学 3.改变文本内容

    JavaScript改变html网页的文本内容 p JavaScript能改变html文本内容 p
  • 90个JavaScript资料免费下载【合集】

    为了方便大家学习 小弟最近整理了一批免积分下载的JavaScript 共90个 整理了这批资料的下载地址 大家可以根据自己的需要选择性下载 希望大家喜欢 JS刷新页面 源码 http down 51cto com data 452926 6
  • 【100%通过率 】【华为OD机试c++/python】回文字符串【2023 Q1考试题 A卷

    华为OD机试 题目列表 2023Q1 点这里 2023华为OD机试 刷题指南 点这里 题目描述 如果一个字符串正读和反渎都一样 大小写敏感 则称它为一个 回文串 例如 leVel是一个 回文串 因为它的正读和反读都是leVel 同理a也是
  • Visual Studio编译问题

    最近在用vs 跑下精简后的数学库 验证输出结果的 结果在其他ide上编译通过 在vs上不行 出现了一堆莫名其妙的错误 问题现象 if endif 不匹配 实际是匹配的 xxx变量未声明 实际是声明并定义的 等等诸如此类问题 解决处理 参考这
  • [1064]大数据概述

    文章目录 大数据时代的数据特点 大数据时代的关键技术 大数据时代的数据特点 一般认为 大数据主要具有 四方面的典型特征 规模性 Volume 多样性 Variety 高速性 Velocity 和价值性 Value 即所谓的 4V 1 规模性
  • Linux(Ubuntu、CentOS)命令行编辑文件后如何保存退出

    在 Ubuntu CentOS 命令行中编辑文件后 可以使用以下步骤保存并退出 按下键盘上的 Ctrl 键和 X 键组合 以退出编辑模式 如果文件已更改 你将看到提示 询问是否保存更改 按下 Y 键来确认保存更改 或按下 N 键取消保存 如
  • 遥感+python 1.3 大气校正

    遥感 python 1 3 大气校正 目录 遥感 python 1 3 大气校正 一 大气校正概念 1 吸收和散射改变大气中的电磁辐射 2 电磁能在大气中相互作用 二 大气校正的方法 1 基于辐射传输方程的大气校正 2 基于地面场数据或辅助
  • 生活之感悟

    有一些路 走下去 很累 可是 不走 会后悔 如果你的生活 处于低谷 那就大胆的向前走吧 因为不管你怎么走 你都是向上 即使没有人关注 也要努力成长 许多眼睛 都藏在你看不见的地方 压力不是有人比你努力 而是比你牛几倍的人 依然在努力 不是所
  • DNF单机版:注册失败的解决

    文章目录 注册的解决 DNF单击版本资源 linux下mysql配置文件my cnf最详细解释与目录 MyISAM 相关选项 INNODB 相关选项 安装启动 相关目录 全文约 9945 字 预计阅读时长 28分钟 注册的解决 个人遇到的问
  • 您的计算机已被.locked勒索病毒感染?恢复您的数据的方法在这里!

    导言 在数字化时代 威胁网络安全的恶意软件层出不穷 locked 勒索病毒作为其中的一员 给个人 企业和组织带来了严重的威胁 影响和心理压力 本文91数据恢复将深入分析 locked 勒索病毒的威胁性质 潜在影响以及由此产生的心理压力 一些
  • android 富文本编辑器_在vue中使用富文本编辑器vue-quill-editor

    一 前言 在一些博客 评论相关的位置 我们不会简单使用 HTML 中的 input 或者 textarea 等纯文本 需要用到富文本编辑器 即实现可以对文本进行加粗 变色 改变字体及大小等操作 本文借助的是vue quill editor
  • Linux怎么修改用户密码

    地址 linux修改用户密码 方法 步骤 首先 要用mobaxterm软件连接Linux系统 1 Linux怎么修改用户密码 首选 确认是用root用户登录系统的 输入命令 id 查看登录用户信息 2 Linux怎么修改用户密码 若修改ro
  • 【黑马-python进阶】---学习笔记(6)---系统性能监控+基于TCP的Web服务器

    1 系统性能定时监控 1 1 系统监控概述 用Python来编写脚本简化日常的运维工作是Python的一个重要用途 在Linux下 有许多系统命令可以时刻监控系统运行的状态 1 2 psutil psutil是python system a
  • IDEA Community(社区版)再谈之无奈之下还是去安装旗舰版

    不多说 直接上干货 前言 相信很多人 跟我一样 一开始 接触spark 肯定会首选IntelliJ IDEA的社区版Community IntelliJ IDEA号称当前Java开发效率最高的IDE工具 IntelliJ IDEA有两个版本
  • C++ Primer 学习笔记 第十一章 关联容器

    关联容器中的元素是按关键字来保存和访问的 与之相对的顺序容器是按元素在容器中的顺序来保存和访问的 关联容器支持高效的关键字查找和访问 两个主要的关联容器类型是map和set map中的元素是一些键 关键字 值对 键起到索引的作用 值则表示与