C++ primer 第十一章

1.关联容器

关联容器中的元素是按关键字来保存和访问的,反映了关键字的作用。

关联容器支持高效的关键字查找和访问,主要关联容器类型是map和set。

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

map<key,value>

set中的每个元素只包含一个关键字,支持高效的关键字查询操作。

set<key>

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

关联容器类型
map关联数组,保存关键字_值对
set关键字即是值,只保存关键字的容器
multimap关键字可重复出现的map
multiset关键字可重复出现的set
unordered_map用哈希函数组织的map
unordered_set用哈希函数组织的set
unordered_multimap关键字可重复的哈希map
unordered_multiset关键字可重复的哈希set

2.使用关联容器 

set<string> exclude = {"but","or","end","and"}; 

 map类型通常被称为关联数组,与数组不同之处在于map的下标不一定是整数。

set是关键字的简单集合,用于查找某个值是否存在。

map<string,size_t> words;

类似于顺序容器,关联容器也是模板,定义map时必须指定关键字和值的类型。

从map中提取一个元素时,会得到一个pair类型的对象。

pair是一个模板类型,保存两个名为first和second的数据成员。

first成员保存关键字,second成员保存对应的值。

set容器起到类似于黑名单、白名单的作用。

与顺序容器相似,我们可以对一个关联容器的元素进行列表初始化。

set<string> exclude = {"the","end","and"};

3.关联容器概述 

由于关联容器中元素是根据关键字存储的,因此关联容器不支持顺序容器的位置相关的操作。

每个关联容器都定义了一个默认构造函数,它创建一个指定类型的空容器。

map<string,size_t> words;  //空容器

使用列表初始化空容器map,要将每个关键字_值对包围在花括号中。

map<string,string> authors = {{"lisi","lizhenyuan"},{"zhangsan","zhangsan2"}};

一个map或set中的关键字必须是唯一的。 

容器multimap和multiset允许多个元素具有相同的关键字。

set容器会自动忽略具有相同的关键字的元素,不会报错。

3.1、关键字类型的要求 

 对于有序容器来说,关键字类型必须定义元素比较的方法。

默认情况下,标准库使用关键字类型的<运算符来比较两个关键字。

我们可以提供自己定义的比较函数来替代关键字上的<运算符,但该函数必须是行为正常的。

用来组织一个容器中元素的操作的类型也是该容器类型的一部分。

自定义的操作类型必须在类型列表中的元素类型后明确指出。

multiset<Sales_data,decltype(compare)*> bookstore;
//compare是比较函数,且使用decltype推导函数类型,加*表示函数指针

3.2、pair类型 

 标准库类型pair定义在头文件utility中。

pair是一个用来生成特定类型的模板,它的默认构造函数对数据成员进行值初始化。

pair<string,string> author{"james","joyce"}; //为每个成员提供初始化器

pair的数据成员是public的,分别命名为first和second。 

pair上的操作

pair<T1,T2> p(v1,v2)

pair<T1,T2> p = {v1,v2}

p是成员类型为T1和T2的pair

使用v1和v2初始化first、second

make_pair(v1,v2)

返回一个用v1和v2初始化的pair

pair的类型从v1、v2的类型推断出来

p.first      p.second

返回p的名为first或second的数据成员

p1 == p2

p1 != p2

当first和second的成员分别相等时,两个pair相等

 在新标准下,我们可以对返回值进行列表初始化。

pair<string,int> process(vector<string> &v)
{
    return {v.back(), v.back().size()}; //列表初始化pair
}

4.关联容器操作

关联容器的类型别名
key_type容器类型的关键字类型
mapped_type值的类型,只适用于map
value_type

对于set,与key_type相同

对于map,为pair<const key_type,mapped_type>

由于我们不能改变一个元素的关键字,因此关键字部分为const的。

使用作用域运算符来提取一个类型的成员。

map<string,int>::key_type v1;   //v1是一个string
map<string,int>::mapped_type v2; //v2是一个int
map<string,int>::value_type v3;  //v3是一个pair<const string, int>

 4.1、关联容器迭代器

当解引用一个关联容器迭代器时,会得到一个类型为容器的value_type的值的引用。

auto map_it = words.begin();
//*map_it是指向一个pair<const string,size_t>对象的引用

 set类型中的关键字是const的,该类型迭代器只允许访问元素,set类型迭代器是const的

 map和set类型都支持begin和end操作,迭代器按关键字升序来遍历元素。

由于关键字是const不能修改或重排容器元素,我们通常不对关联容器使用泛型算法。

4.2、添加元素 

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

对一个map进行insert操作时,元素类型为pair。

//向words插入元素的方法
words.insert({word,1});  
words.insert(make_pair(word,1));
words.insert(pair<string,size_t>(word,1));
words.insert(map<string,size_t>::value_type(word,1));
关联容器insert操作

c.insert(v)

c.emplace(args)

v是value_type类型的对象,args用来构造一个元素

只有当元素的关键字不在c中时才插入元素。(set和map)

c.insert(b,e)

c.insert(il)

b和e是迭代器,表示一个value_type类型值的范围

il是这种值的花括号列表,函数返回void

c.insert(p,v)

c.emplace(P,args)

迭代器p作为一个坐标,表示从哪里开始搜索新元素存储的位置

返回一个迭代器,指向具有给定关键字的元素

insert返回一个pair,该pair的first成员是迭代器,指向具有给定关键字的元素,second成员是bool值,表示插入是否成功。

4.3、删除元素 

删除元素操作
c.erase(k)从c中删除每个关键字为k的元素,返回一个size_type值,指出删除的元素数量
c.erase(p)

从c中删除迭代器p指定的元素,p必须指向c中一个真实元素

返回一个指向p之后元素的迭代器

c.erase(b,e)删除迭代器对b和e所表示的范围中的元素。返回e

 对于保存不重复关键字的容器,erase的返回值总是0或1。

4.4、map的下标操作

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

map下标运算符接受一个关键字,获得与此关键字相关联的值。

如果关键字并不在map中,运算符会为它创建一个元素并插入到map中。 

下标操作
c[k]

返回关键字为k的元素

如果k不在c中,添加一个关键字为k的元素,对其进行值初始化

c.at(k)

访问关键字为k的元素,带参数检查;

若k不在c中,抛出一个out_of_range异常

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

 4.5、访问元素

查找元素的操作
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()

若我们只想知道一个给定关键字是否在map中,而不想改变map,可以使用find。

if(words.find("football") == words.end())
  cout << "not in the map" << endl;

如果一个multimap或multiset中有多个元素具有给定关键字,则这些元素在容器中会相邻存储。 

若我们想遍历具有相同关键字的所有值,最直观的方法是使用find和count。

auto counts = words.count(word);
auto iter = words.find(word);
while(counts)
{
    cout << iter->second << endl;
    ++iter;
    --counts;
}

 还可以使用上下限函数来解决该问题。

for(auto beg = words.lower_bound(word),end = words.upper_bound(word);
    beg != end; ++beg)
{
    cout << beg->second << endl;
}
//若上下限函数返回相同的迭代器,则给定关键字不在容器中。

通过equal_range函数解决该问题:

for(auto pos = words.eual_range(word);pos.first != pos.second; ++pos.first)
{
    cout << pos.first->second << endl;
}
//pos是pair,成员都是保存迭代器,表示与关键字匹配的范围

4.6、一个单词转换的map 

std::map<string, string> buildmap(std::ifstream& map_file) //将转换规则从文件写入map中
{
	std::map<string, string> trans_map; //创建map
	string key;  //临时保存key
	string value;  //替换的内容
	while (map_file >> key && getline(map_file, value))  //读取第一个单词进入key中,剩余内容存入value
		if (value.size() > 1) //检查是否有转换规则
			trans_map[key] = value.substr(1); //跳过key与规则之间的空格,并存入value
		else
			throw std::runtime_error("no rule for " + key);  //若无规则,则抛出异常
	return trans_map; //返回转换规则map
}

const string& transform(const string& s, const std::map<string, string>& m)  //单词转换
{
	auto map_it = m.find(s); //在转换规则map寻找s
	if (map_it != m.cend())  
		return map_it->second;  //存在则使用替换短语
	else
		return s; 
}
void word_transform(std::ifstream& map_file, std::ifstream& input)
{
	auto trans_map = buildmap(map_file);  //建立并保存转换规则map
	string text;   //临时保存读取数据
	while (getline(input, text))   //读取一行输出
	{
		std::istringstream stream(text);  //string流,用于读取每个单词
		string word;  //临时保存读取单词数据
		bool firstword = true;  //用于控制是否打印空格
		while (stream >> word) 
		{
			if (firstword)
				firstword = false;  //修改状态
			else
				cout << " "; //打印空格
			cout << transform(word, trans_map); //转换
		}
		cout << endl;
	}
}

int main()
{
	std::ifstream in("file.txt");
	std::ifstream in2("input.txt");
	word_transform(in, in2);
	return 0;
}

5.无序容器 

 无序关联容器由哈希函数和关键字类型的==运算符来组织容器中的元素。

理论上哈希技术能获得更好的平均性能,使用无序容器通常会有更好的性能。

无序容器也有允许重复关键字的版本,通常可以用一个无序容器替换对应的有序容器。

无序容器在存储上组织为一组桶,每个桶保存零个或多个元素,每个关键字是一个桶。

无序容器的性能依赖于哈希函数的质量和桶的数量和大小。

理想情况下,每个特定的值映射到唯一的桶,但也允许将不同关键字的元素映射到相同的桶。

无序容器管理操作
c.bucket_count()正在使用的桶的数目
c.max_bucket_count()容器能容纳的最多的桶的数量
c.bucket_size(n)第n个桶有多少元素
c.bucket(k)关键字为k的元素在哪个桶中
local_iterator用来访问桶中元素的迭代器类型
const_local_iterator桶迭代器的const版本
c.begin(n),c.end(n)桶n的首元素迭代器和尾后迭代器
c.cbegin()  c.cend()返回const_local_iterator
c.load_factor()每个桶的平均元素数量,返回float值
c.max_load_factor()

c试图维护的平均桶大小

在需要时添加新桶,使得load_factor <= max_load

c.rehash(n)重组存储,使得桶的数目>= n
c.reserve(n)重组存储,但c可以保存n个元素且不必rehash

 默认情况下,无序容器使用关键字类型的==运算符来比较元素,hash<key_type>类型对象来生成每个元素的哈希值。

由于自定义类类型无hash模板和==运算符,因此不能直接定义关键字类型为自定义类类型的无序容器。

可以提供函数来代替==运算符和哈希值计算函数。

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

bool qual(const Sales_data &left,const Sales_data& right)
{
   return left.isbn() == right.isbn();
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值