C++Primer——《第九章1 》“ 顺序容器概述、容器定义和初始化”

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34536551/article/details/84789072

目录

顺序容器概述

确定使用哪种顺序容器

容器操作

迭代器范围

迭代器的特性

迭代器的类型

类型别名

begin 和 end 成员

容器定义和初始化

将一个容器初始化为另一个容器的拷贝

列表初始化 (C++11)

与顺序容器相关的构造函数

标准库 array 具有固定大小

赋值与swap

使用 assign(关联容器和array除外、只有顺序容器可以使用)

使用 swap

关系运算符

容器的关系运算符使用元素的关系运算符完成比较


顺序容器概述


● 所有顺序容器都提供了快速顺序访问元素的能力。但是,这些容器在以下方面都有不同的性能折中:

向容器添加或从容器中删除元素的代价

非顺序访问容器中元素的代价

● 在 deque两端添加或删除元素与 list 或 forward_list 添加删除的速度相当。该容器支持快速的随机访问。

● 与内置数组相比,array 是一种更加安全、更容易使用的数组类型,array对象的大小是固定的,array不支持添加删除元素以及改变容器大小的操作。 array 也不像其他容器那样支持高效、灵活的内存管理。

● 注意: forward list的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此,forward_list 没有 size 操作,因为保存或计算其大小就会比手写链表多出额外的开销。forward_list 还不支持“反向容器的成员”。 forward_list 迭代器不支持递减运算符

● forward_list 和 list 不支持 元素的随机访问,为了访问一个元素 ,我们只能遍历整个容器,vector, deque 和 array相比,这两个容器的额外内存开销也很大。

● 与 vector 和 deque 不同, list的迭代器不支持 < 运算, 只支持 递增、递减、以及 != 运算 。

●  标准库容器中所有的迭代器都定义了递增运算符,第当前元素移到下一个运算。

● 注意: forward_list 支持 empty、max_size,但不支持size。

● forward_list 不支持  reverse_iterator、const_reverse_iterator、c.rbegin(), c.rend ()、c.crbegin(), c.crend ()  这些反向容器的成员。

● forward_list不支持 push_back、emplace_back、 pop_back、insert、emplace、erase操作。

● vector 和 string 不支持 push_front、 和 emplace_front、 pop_front.

● shrink_to_fit()函数只适用于 vector 和 string。

vector <string> svec;

svec.insert(svec.begin(),"Hello!"); //我们可以使用insert 。将元素插入到第一个元素之前,但是可能很耗时。

● 除了array和 forward_list 之外, 每个顺序容器(包括string类型)都支持push_back。

● 向一个vector、string 或 deque 插入元素会使所有指向容器的迭代器、引用和指针失效。

● 向一个vector或string添加元素可能引起整个对象存储空间的重新分配。 重新分配一个对象的存储空间需要分配新的内存, 并将元素从旧的空间移动到新的空间中。

● list、forward_list、deque这些容器还支持名为push_front 的类似操作。

● vector 、list、deque、string都支持insert成员,forward_list 支持特殊版本的insert成员。

● 包括array在内的每个顺序容器都有一个front成员函数, 而除了forward_list之外的所有顺序容器都有一个back成员函数。 分别返回首元素和尾元素的引用。

● array 不支持 resize操作来改变容器大小。


确定使用哪种顺序容器


以下是选择容器的基本原则:

  • 除非你有很好的理由选择其他容器, 否则应使用vector。
  • 如果你的程序有很多小的元素,且空间的额外开销很重要,则不要使用list或forward_list.
  • 如果程序要求随机访问元素,应使用vector或deque.
  • 如果程序要求在容器的中间插入或删除元素,应使用 list 或 forward-list.
  • 如果程序需要在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用deque.

如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则:

  • 首先,确定是否真的需要在容器中间位置添加元素。当处理输入数据时,通常可以很容易地向vector追加数据,然后再调用标准库的sort函数(我们将在10.2.3节介绍sort (第343页))来重排容器中的元素,从而避免在中间位置添加元素。
  • 如果必须在中间位置插入元素,考虑在输入阶段使用list,一旦输入完成,将 list中的内容拷贝到一个vector中。

如果程序既需要随机访问元素,又需要在容器中间位置插入元素,那该怎么办?

答案取决于在list或forwardlist中访问元素与vector或deque中插入/删除元素的相对性能。一般来说,应用中占主导地位的操作(执行的访问操作更多还是插入/删除更多)决定了容器类型的选择。在此情况下,对两种容器分别测试应用的性能可能就是必要的了。

注意 : 如果你不确定应该使用哪种容器,那么可以在程序中只使用vector和list公共的操作:使用迭代器,不使用下标操作,避免随机访问。这样,在必要时选择使用vector或list都很方便。


容器操作



迭代器范围


容器迭代器支持的所有操作

● 注意:forward_list  不支持递减运算符。 

迭代器支持的算术运算

● 注意:只有“string、 vector 、deque 、array、”这些顺序容器类型能够使用迭代器支持的算术运算, 我们不能将它们用于其他任何容器类型的迭代器。


● 一个迭代器范围由一对迭代器表示, 两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置。 通常被称为 begin 和 end 或者是 first 和 last (指的是最后一个元素的下一个位置), 它们标记了容器中元素的一个范围。

● 注意: begin 和 end 必须指向相同的容器。 end 可以与begin 指向相同的位置, 但不能指向begin 之前的位置

这里写图片描述


迭代器的特性


 假定begin和end构成一个合法的迭代器范围,则:

  • 如果begin与end相等,则范围为空
  • 如果begin与end不等,则范围至少包含一个元素, 且begin指向该范围中的第一个元素
  • 我们可以对begin递增若干次,使得begin=end
#include<vector>
using std::vector;
int main()
{
	int val = 20;
	vector<int> vec{ 0,1,2,3,4,5 };
	vector<int>::iterator iter1 = vec.begin();
	vector<int>::iterator iter2 = vec.end();
	while (iter1 != iter2)
	{
		*iter1 = val;
		++iter1;
	}
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

迭代器的类型


●  迭代器的类型 有:  size_type iterator 和 const_iterator。

大多数容器还提供反向迭代器。简单地说, 反向迭代器就是一种反向遍历容器的迭代器,与正向迭代器相比,各种操作的含义也都发生了颠倒。例如,对一个反向迭代器执行++操作,会得到上一个元素。


类型别名


● 通过类型别名, 我们可以在不了解容器中元素类型的情况下使用它。 如果需要元素类型, 可以使用容器的value_type。 如果需要元素类型的一个引用,可以使用reference 或 const_reference 。为了使用这些类型, 我们必须显式使用类名。


begin 和 end 成员


	vector<string> vec{ "huang","cheng","tao" };
	auto it1 = vec.begin();  //普通迭代器
	auto it2 = vec.rbegin(); //反向迭代器
	auto  it3 = vec.cbegin();  //const 迭代器
	auto it4 = vec.crbegin(); //反向const 迭代器

● 注意: 不以c 开头的都是重载过的函数,当我们对一个非常量对象调用这些成员时, 得到的是返回 iterator的版本。只有在对一个const对象调用这些函数时, 才会得到一个const版本。与const指针和引用类似,可以将一个普通的iterator转换为对应的const-iterator, 但反之不行。

        vector<string> vec{ "huang","cheng","tao" };
	vector<string>::iterator it5 = vec.begin();
	vector<string>::const_iterator it6 = vec.begin();

	// 是iterator还是const_iterator 依赖于 vec 的类型
	auto it7 = vec.begin(); //仅当 vec是const时, it7是 const_iterator
	auto it6 = vec.cbegin(); //it8是 const_iterator

● 当 auto 与 begin 或 end 结合使用时, 使用 begin 时 依赖于容器类型, 如果容器不是const的, 那么返回的是 iterator , 如果是const 类型, 那么返回的就是 const_iterator,  使用 cbegin 不管容器类型是const的 还是 非const的,  都返回 const_iterator .


容器定义和初始化


●  每个容器类型都定义了一个默认构造函数,除array之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。
 


将一个容器初始化为另一个容器的拷贝


将一个新容器创建为另一个容器的拷贝的方法有两种:

  •    可以直接拷贝整个容器  //两个容器类型和元素类型必须匹配
  •   或者(除array外)拷贝由一个迭代器对指定的元素范围进行拷贝。    // 不要求两个容器类型和元素类型必须匹配,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。
int main()
{
	list<string> authors = { "huang","cheng","tao" };
	vector<const char *>articles = { "a","an","the" };

	list<string> list2(authors); // 正确,容器类型、元素类型都匹配
	//deque<string> authList(authors); //错误,容器类型不匹配
	//list<int> words(authors);  //元素类型不匹配,错误

	forward_list<string> ft(articles.begin(), articles.end()); //正确:可以将const char*元素转换为string

	auto it1 = authors.cbegin();
	auto it2 = authors.cend();
	while (it1 != it2)
	{
		cout << *it1++ << " ";
	}
	cout << endl;

	auto it3 = ft.cbegin();
	auto it4 = ft.cend();
	while (it3 != it4)
	{
		cout << *it3++ << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}


输出结果为:

huang cheng tao
a an the

● 接受两个迭代器参数的构造函数用这两个迭代器表示我们想要拷贝的一个元素范围 : 新容器的大小与范围中元素的数目相同。 新容器中的每个元素都用范围中对应元素的值进行初始化 .

● 由于两个迭代器表示一个范围, 因此可以使用这种构造函数来拷贝一个容器中的子序列:

int main()
{
	vector<string> authors = { "Milton","Shakespeare","Austen","huang","chengt"};
	auto it = authors.begin() + 2;// 现在 it 指向第三个位置
	deque<string> authVector(authors.begin(), it); //拷贝元素,直到但不包括it指向的元素

	auto it1 = authVector.cbegin();
	auto it2 = authVector.cend();
	while (it1 != it2)
	{
		cout << *it1++ << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

Milton Shakespeare

列表初始化 (C++11)


● 在C++11新标准中允许我们对一个容器进行列表初始化, 除了array之外的容器类型,初始化列表隐含地指定容器的大小: 容器将包含与初始值一样多的元素个数。

vector<string> authors = { "Milton","Shakespeare","Austen","huang","chengt"}; //列表初始化

当这样做时,我们就显式地指定了容器中每个元素的值。对于除array之外的容器类型, 初始化列表还隐含地指定了容器的大小:容器将包含与初始值一样多的元素。


与顺序容器相关的构造函数


● 除了与关联容器相同的构造函数外, 顺序容器(array除外)  还提供另一个构造函数,  它接受一个 容器大小和一个(可选的)元素初始值。如果我们不提供元素初始值, 则标准库会值初始化每个元素的值:

vector<int> ivec (10, -1);
list<string> svec(10, "hi!");
forward list<int> ivec (10);
deque<string> svec (10);

● 如果元素类型是内置类型或者是具有默认构造函数的类类型,可以只为构造函数提供一个容器大小参数。

如果元素类型没有默认构造函数,除了大小参数外, 还必须指定一个显式的元素初始值

● 注意: 只有顺序容器的构造函数才接受大小参数,关联容器并不支持。


标准库 array 具有固定大小


● 与内置数组一样,标准库array的大小也是类型的一部分。 当定义一个array时,必须指定元素类型和容器大小。

array<int, 10> arr; //指定一个标准库 array, 类型为int, 数组个数为10

● 由于大小是array 类型的一部分,array 不支持普通的容器构造函数。

● 与其他容器不同的是, 一个默认构造的array 是非空的: 它包含了与其大小一样多的元素。 这些元素都被默认初始化。如果我们对array 进行列表初始化, 初始值的数目必须等于或小于array的大小。如果初始值数目小于array的大小,则它们被用来初始化array 靠前的元素,所有剩余元素都会进行值初始化。  在这两种情况下,如果元素类型是一个类类型, 那么该类必须有一个默认构造函数, 以使值初始化能够进行。

● 注意: 虽然我们不能对内置数组进行拷贝或者对象赋值操作,但array并无此限制

	int digs[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int cpy[10] = digs; //错误
	array<int, 10>digits = { 0,1,2,3,4,5,6,7,8,9 };
	array<int, 10> copy = digits; //正确

与其他容器一样, array也要求初始值的类型必须与要创建的容器类型相同。此外, array还要求元素类型和大小也都一样,因为大小是array类型的一部分。


赋值与swap


这里写图片描述
赋值相关的运算符可以用于所有容器

● 注意:  由于右边运算对象的大小可能与左边运算对象的大小不同, 因此array类型不支持assign。


使用 assign(关联容器和array除外、只有顺序容器可以使用)


● assign 成员: 允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。assign 操作用参数所指定的元素替换(拷贝)左边容器中的所有元素。 assign的参数决定了容器中将有多少个元素以及它们的值都是什么。

int main()
{
	
	list<string> authors = { "huang","cheng","tao" };
	vector<const char *>articles = { "a","an","the" };

	list<string> list2(authors); // 正确,容器类型、元素类型都匹配
	authors.assign(articles.cbegin(), articles.cend()); //正确:可以将const char*元素转换为string

	auto it1 = articles.cbegin();
	auto it2 = articles.cend();
	while (it1 != it2)
	{
		cout << *it1++ << " ";
	}
	system("pause");
	return 0;
}

输出结果为:

a an the 

● 注意 : 由于旧元素被替换,因此传递给assign 的迭代器不能指向调用assign的容器
 


使用 swap


● 注意: 除array 外,交换两个容器中的内容的操作保证会很快——元素本身并未交换,swap只是交换了两个容器的内部数据结构。

● 注意: 除array外, swap 不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。

● 元素不会被移动的事实意味着, 使用 swap, 除了array外,指向容器的迭代器、引用和指针在swap之后,都不会失效。 它们仍指向swap 操作之前所指向的那些元素。但是,在swap之后,这些元素已经属于不同的容器了。

注意: 与其它容器不同的是,对一个string 调用swap 会导致迭代器、引用和指针失效。

● 注意: 与其它容器不同, swap 两个array会真正交换它们的元素。 交换两个array所需的时间与array 中元素的数目成正比。

对于array,在swap操作之后 ,指针、引用和迭代器所绑定的元素保持不变, 但元素值已经与另一个array中对应元素的值进行了交换。

● 每个容器类型都有三个与大小相关的操作: size、empty、max_size。

注意: forward_list 支持 empty、max_size,但不支持size。


关系运算符


● 每个容器类型都支持相等运算符( == 和 != ); 除了无序关联容器外的所有容器都支持关系运算符——关系运算符左右两边的运算对象必须是容器类型相同和元素类型相同。

● 比较两个容器实际上是进行元素的逐对比较。


容器的关系运算符使用元素的关系运算符完成比较


● 注意: 只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符比较两个容器。

● 容器的相等运算符是使用元素的“ == ”运算符实现比较的, 而其他关系运算符是使用元素的“ < ” 运算符实现比较的。

如果元素类型不支持所需运算符, 那么保存这种元素的容器就不能使用相应的关系运算符。

猜你喜欢

转载自blog.csdn.net/qq_34536551/article/details/84789072