本文续:STL之关联式容器map(一)
emplace() 可以在适当的位置直接构造新元素,从而避免复制和移动操作。
当容器中现有元素的键与这个元素的键不同时,才会构造这个元素。
std::map<Name, size_t> people;
auto pr = people.emplace (Name { "Dan","Druff"},77);
成员函数 emplace() 和 insert() 返回的 pair 对象提供的指示相同。pair 的成员变量 first 是一个指向插入元素或阻止插入的元素的迭代器;成员变量 second 是个布尔值,如果元素插入成功,second 就为 true。
emplace_hint() 和 emplace() 生成元素的方式在本质上是一样的,除了必须为前者提供一个指示元素生成位置的迭代器,作为 emplace_hint() 的第一个参数。
std::map<Name, size_t> people;
auto pr = people.emplace(Name{"Dan","Druff"}, 77);
auto iter = people.emplace_hint (pr.first, Name {"Cal","Cutta"}, 62);
如果容器使用这个提示符,那么新元素会在这个指示符表示的位置之前生成,并尽可能靠近这个位置。
emplace_hint() 的返回值不是一个 pair 对象,如果新元素被插入,它返回的是指向新元素的迭代器;如果没有插入,返回的是和这个键匹配的现有元素的迭代器。
用 size() 成员函数来获取 map 中对应元素的数量来检查 map 元素增加的数量。
auto pr = people.emplace(Name{"Dan", "Druff"}, 77);
auto count = people.size ();
auto iter = people.emplace_hint (pr.first, Name {"Cal", "Cutta"}, 62);
if(count < people.size ())
std::cout <<"Success!\n";
获取 map 容器的开始和结束迭代器以及反向迭代器,它们都可以访问容器中的所有元素。
map 的成员函数 at() 返回的是参数键对应的对象。如果这个键不存在,就会拋出 out_of_range 异常
Name key;
try{
key = Name {"Dan”, ”Druff"};
auto value = people.at(key);
std:: cout << key << "is aged " << value << std:: endl;
key = Name {"Don", "Druff"};
value = people.at(key);
std::cout << key << " is aged " << value << std::endl;}
catch(const std::out_of_range& e){
std::cerr << e.what() << '\n'<< key << " was not found." <<std::endl;}
Dan Druff is aged 77
invalid map<K, T> key Don Druff was not found.
当 catch 代码块中的代码执行后,try 代码块中的所有变量会被销毁,因此不再可以访问。
元素默认的构造函数会用键和键所关联的对象生成一个新元素,如果键关联的对象是基本数据类型,它的值为 0。
auto value = people[Name {"Ned", "Kelly"}]; // Creates a new element if the key is not there
因为容器中不存在这个键,所以用它生成了新元素。关联对象的值是 0,并会返回这个值。修改已存在的元素:
people[Name {"Ned", "Kelly”}] = 39; // Sets the value associated with the key to 39
综合应用:
假设通过人名来保存并检索名人名言。显然,一个名人会有很多名言,因此需要通过单个键来保存多个名言。不能在 map 容器中保存重复的键,但是可以将键关联到封装了多个名言的对象上。
#ifndef QUOTATIONS_H
#define QUOTATIONS_H
#include <vector>
#include <string>
#include <exception>
class Quotations
{
private:
std::vector<std::string> quotes;
public:
// 这里用 << 运算符来添加名言是合理的,它可以在其他一些场景下使用,例如输入流
//第一个版本接收一个字符串常量参数,然后把它传给 vector 的成员函数 emplace_back(),
//emplace__back() 会调用 string 的构造函数以在适当的位置生成元素
Quotations& operator<<(const char* quote)
{
quotes.emplace_back(quote);
return *this;
}
// 第二个版本只有一个参数,它是 string 对象的引用,这个参数会被传给 vector 的成员函数 push_back()
Quotations& operator<<(const std::string& quote)
{
quotes.push_back(quote);
return *this;
}
// 第三个版本有一个右值引用参数。当在函数体中通过名称使用右值引用时,它会变成左值,因此必须使用 move()
//函数将它变为右值,然后把它传给 vector 的成员函数 push_back()。这会保证对象总是移动传值,而不是复制传值。
Quotations& operator<<(std::string&& quote)
{
quotes.push_back(std::move(quote));
return *this;
}
// 类的成员函数 []() 可以通过索引来访问成员元素。当索引不在范围内时,这个函数将抛出一个异常,
//这种情况不应该发生;如果真的发生,这会是程序中的一个 bug。
std::string& operator[](size_t index)
{
if(index < quotes.size())
return quotes[index];
else
throw std::out_of_range {"Invalid index to quotations."};
}
size_t size() const// Returns the number of quotations
{
return quotes.size();
}
// Returns the begin iterator for the quotations
std::vector<std::string>::iterator begin()
{
return std::begin(quotes);
}
// Returns the const begin iterator for the quotations
std::vector<std::string>::const_iterator begin() const
{
return std::begin(quotes);
}
// Returns the end iterator for the quotations
std::vector<std::string>::iterator end()
{
return std::end(quotes);
}
// Returns the const end iterator for the quotations
std::vector<std::string>::const_iterator end() const
{
return std::end(quotes);
}
};
#endif
如果没有定义 const 版的 begin() 和 end() 函数,就不能在 for 循环中使用 const 类型的循环变量:
for (const auto& pr : quotations)//Requires const iterators ...
#include <iostream>
#include <cctype> // For toupper()
#include <map>
#include <string>
#include "Quotations.h"
#include "name.h"
using std::string;
// 第一个用来从 cin 读入 name:
inline Name get_name()
{
Name name {};
std::cout << "Enter first name and second name: ";
//std::ws 消除空格
std::cin >> std::ws >> name;
return name;
}
// 第二个辅助函数用来读取名言:
inline string get_quote(const Name& name)
{
std::cout << "Enter the quotation for " << name
<< ". Enter * to end:\n";
string quote;
std::getline(std::cin >> std::ws, quote, '*');
return quote;
}
int main()
{
std::map<Name, Quotations> quotations; //类,类
std::cout << "Enter 'A' to add a quote."
"\nEnter 'L' to list all quotes."
"\nEnter 'G' to get a quote."
"\nEnter 'Q' to end.\n";
Name name {}; // Stores a name
string quote {}; // Stores a quotation
char command {}; // Stores a command
while(command != 'Q')
{
std::cout << "\nEnter command: ";
std::cin >> command;
command = static_cast<char>(std::toupper(command));
switch(command)
{
case 'Q':
break;
case 'A':
name = get_name();
quote = get_quote(name);
//<< 左边的操作数等同于 quotations.operator[](name),它返回一个和 name 关联的 Quotations 对象
//quotations.operator[](name).operator<<(quote);
quotations[name] << quote;
break;
case 'G':
{
name = get_name();
const auto& quotes = quotations[name];
size_t count = quotes.size();
if(!count)
{
std::cout << "There are no quotes recorded for "<< name << std::endl;
continue;
}
size_t index {};
if(count > 1)
{
std::cout << "There are " << count << " quotes for " << name << ".\n"<< "Enter an index from 0 to " << count - 1 << ": ";
std::cin >> index;
}
//利用表达式 quotations[name][index] 来得到一条名言,
//它等价于 quotations.operator[](name).operator[](index)
std::cout << quotations[name][index] << std::endl;
}
break;
case 'L':
if(quotations.empty())
{
std::cout << "\nNo quotations recorded for anyone." << std::endl;
}
// List all quotations
for(const auto& pr : quotations)
{
std::cout << '\n' << pr.first << std::endl;
for(const auto& quote : pr.second)
{
std::cout << " " << quote << std::endl;
}
}
break;
default:
std::cout << " Command must be 'A', 'G', 'L', or 'Q'. Try again.\n";
continue;
break;
}
}
}
结果显示:
5查找元素
map 容器的成员函数 find() 可以返回一个元素的迭代器,这个元素的键值和参数匹配。
std::map<std::string, size_t> people {{"Fred", 45}, {"Joan", 33}, {"Jill", 22}};
std::string name{"Joan"};
auto iter = people.find(name);
if(iter == std::end(people))
std:: cout <<"Not found.\n";
else
std:: cout << name << " is ""<< iter->second << std::endl;
如果没有和参数匹配的元素,find()函数会返回容器的结束迭代器,因此在使用这个迭代器之前,必须先对它进行检查。
map 的成员函数 erase() 可以移除键和参数匹配的元素,然后返回所移除元素的个数。
std::map<std::string, size_t> people {{ "Fred", 45}, {"Joan", 33},{"Jill", 22}};
std::string name{"Joan"};
if(people.erase(name))
std::cout << name << " was removed." << std::endl;
else
std::cout << name << " was not found" << std::endl;
map 容器的返回值只可能是 0 或 1,0 表明元素不在容器中。
也可以用指向删除元素的迭代器作为 erase() 的参数。这种情况下,返回的迭代器指向被删除元素的下一个位置。这个参数必须是容器中的有效迭代器,不能是结束迭代器。如果迭代器参数指向的是容器的最后一个元素,那么会返回结束迭代器。
auto iter = people.erase(std::begin(people));
if(iter == std::end(people))
std::cout << "The last element was removed."<< std::endl;
else
std::cout << "The element preceding " << iter->first << "was removed." << std::endl;
高级版本的 erase(),它可以移除两个迭代器参数所定义范围内的元素。
auto iter = people.erase(++std::begin(people), --std::end(people));//Erase all except 1st & last
返回的迭代器指向这段元素中最后一个被删除的元素。
如果想删除容器中的所有元素,可以调用成员函数 clear()。