C++ Primer 第九章 9.3 顺序容器的操作 练习和总结

9.3 顺序容器的操作

如题,这一章讲的全是顺序容器如何操作。

9.3.1 向顺序容器中添加元素

顺序容器添加元素的操作如下:
在这里插入图片描述
因为array的大小是固定了,所以这些操作是和array无关的。

需要注意的点:

1.如果容器有push_back,push_front这样的函数,表示这个容器在头部或者尾部添加元素时,效率较高。比如vector有push_back,所以在尾部添加元素效率很高,但是如果在其他位置添加元素就需要移动容器元素,效率相对较低。(插入元素时,可能会创建新的存储空间,然后将元素拷贝过去)。

2.对于insert,它需要一个目标容器的迭代器,插入的元素都会在该迭代器之前添加。insert支持直接插入、使用迭代器范围、指定数量和元素值、初始值列表同样使用迭代器范围的时候,迭代器不能是目标容器的迭代器。

3.insert函数,在新标准中,执行insert之后,会返回插入的元素的迭代器,这样就可以实现连续的插入值。如果插入的是一个迭代器范围,那么返回是迭代器范围的第一个元素的迭代器,如果迭代器范围为空,则返回传入的第一个参数,即自己指定的那个迭代器

4.对于insert和push_back,push_front,添加的元素都是拷贝的,即将添加进来的元素复制一份,再添加,所以原来的元素可以随意的改变,不会影响容器的内部的元素。

5.和insert、push_back、push_front不一样,emplace是直接调用元素的构造函数来添加元素到容器中,相比之下,少了一个拷贝操作。

class A{
	public:
	A(){};
	A(int _a):a(_a){};
	int a;
};

vector<A> vec;
vec.emplace_back(1);//首先调用类A的构造函数,生成一个对象,将这个对象插入到元素中,少了一个拷贝操作
vec.push_back(A(1));//本质是一样的,但是这样多了一个拷贝操作,A(1)需要创建一次A对象,然后再将对象拷贝一份,添加到vec中

6.为什么vector,string,deque在插入了新元素之后,之前的迭代器,指针和引用会失效?这和三个容器的物理存储方式有关,他们三者存储结构都是连续的,添加新元素之后,有可能当前的存储空间不足,所以需要创建一个新的存储空间,再将之前的值拷贝进去。如果一来,对象的地址就发生了变换,而迭代器,指针本质还是指向对象的地址的,地址都发生了变化,自然就会失效了。

添加于 2020/2/12 通过对后续的内容学习了解到,vector,string,deque在插入新元素之后迭代器只是可能会失效,但是为了安全起见,我们在使用时都默认添加和删除元素会导致它们的迭代器失效

而list和forward_list,学过数据结构的都知道,他们的物理存储不一定是连续的,所以插入新的的元素,就不会导致原来的迭代器,指针,引用失效。

有一个点忘记说了,一个迭代器范围属于左闭合区间,也就是说,第二个迭代器是不在迭代器范围内的。

练习

9.18

string str;
	deque<string> dq;
	while (cin>>str) {
		dq.push_back(str);
	}
	for (const auto & item: dq) {
		cout<<item<<endl;
	}

9.19

string str;
	list<string> lst;
	//deque<string> dq;
	while (cin>>str) {
		lst.push_back(str);
	}
	for (const auto & item: lst) {
		cout<<item<<endl;
	}

9.20

list<int> lst = {1,2,3,4,5,6,7,8,9,0};
	deque<int> dq1, dq2;
	for (const auto& item:lst) {
		if (item%2==0) {
			dq1.push_back(item);
		}
		else {
			dq2.push_back(item);
		}
	}
	for (const auto& data:dq1) {
		cout<<data<<endl;
	}

9.21

因为insert会返回所插入的元素的迭代器,所以循环会一直向vector的头部插入新元素

9.22

这道题,我不太明白程序意图是什么,很明显while将会是一个死循环,因为iter没有任何移动的步骤,同时向vector中添加元素,将会导致之前的iterator失效。也就是iter和mid都会失效。

我觉得这题目就有些问题。

9.3.2 访问元素

我们可以使用front(),back().下标运算符,at()来访问顺序容器的元素。

在这里插入图片描述
如果容器中的size()为空的话,使用front(),back().将会导致程序崩溃。如果使用下标运算符,访问的范围超过了容器的大小,程序也会崩溃,但是使用at不会,at会发出数组越界的异常。

我们在访问容器元素的时候,最好先判断一下,容器是否为空。

对于front(),back(),下标运算符,at()返回的类型都是容器中元素的引用类型,所以我们可以通过他们修改容器内部的值。

但是我们定义一个auto变量的时候,我们需要显式的表示,我们需要引用类型。

auto &item  = vec.back()

这在之前讲auto进行变量声明的时候就讲过了,顶层const赋值可以被忽略。

注意下标访问符和at只适用于string,vector,deque,array

练习

9.23
四个值都是一样的。

9.24

vector<int> vec;
	try
	{
		//vec.back(); 程序崩溃
		//vec.at(1);  捕获异常
		//vec[1];   程序崩溃
		//vec.front();  程序崩溃

	}
	catch (const std::exception&)
	{
		cout<<"下标越界"<<endl;
	}

9.3.3 删除元素

删除元素有很多的方式
在这里插入图片描述
1.和之前一样访问元素的front(),back()一样,pop_back(),pop_front(),这些成员函数并不会检查容器是否为空,所以如果容器为空,将产生未定义的行为,在VS2017下,表现形式是程序崩溃。,所以在使用pop_back(),pop_front()时,要记得检查容器是否为空。

2.erase()用来删除迭代器所指向的元素,或者迭代器范围包含的元素。insert函数,返回的是插入的元素(或者迭代器范围第一个元素元素)的迭代器,而erase函数,返回的是删除的迭代器之后一位的(或者返回表示迭代器范围的第二个迭代器)迭代器。

对于单迭代器的erase函数,如果传入了尾迭代器,则行为未定义,在VS2017下,表现为程序崩溃。这也很容易理解,因为传入单迭代器就是要删除元素呀,但是end()所指的内容,并不是容器内部,所以会报错。

erase传入迭代器范围的之后,返回的就是第二个迭代器,因为迭代器范围属于左闭合区间,表示范围的第二迭代器并不属于范围内部,所以返回的就是它了

3.虽然erase会返回删除的元素之后迭代器,但是pop_front,pop_back返回的void,所以如果在使用pop_front,pop_back,还要使用删除的元素话,需要提前保存。

练习

9.25

elem1和elem2相等并不会发生什么,即容器不会删除任何元素。

如果elem1和elem2都是尾后迭代器,那么erase返回的还是尾后迭代器,这在之前的表中都写了的。

9.26

int ia[] = {0,1,1,2,3,5,8,13,21,55,89};
	//不要想当然的讲数组传入容器中
	//vector<int> vec(ia);
	list<int> lst;
	vector<int> vec;
	for (const auto& item:ia) {
		vec.push_back(item);
		lst.push_back(item);
	}
	auto iter = lst.begin();
	while (iter!=lst.end()) {
		if (*iter%2==1) {
			iter = lst.erase(iter);
		}
		else {
			++iter;
		}
	}
	for (const auto& item:lst) {
		cout << item << endl;
	}

	auto iter1 = vec.begin();
	while (iter1!=vec.end()) {
		if (*iter1%2==0) {
			iter1 = vec.erase(iter1);
		}
		else {
			++iter1;
		}
	}
	for (const auto& item:vec) {
		cout << item << endl;
	}

9.3.4 特殊的forward_list操作

forward_list是单项链表,不支持insert,erase,empalce操作。这是因为它本身的结构的原因。

拿删除举例,如果我们
在这里插入图片描述
如果要删除elem3,elem2中指向后续元素的指针也需要改变,即我们删除elem3的时候,需要改变elem2的内容。

所以在forward_list中,进行插入,删除和emplace操作时,传入的迭代器是要操纵的对象之前的“元素”的迭代器

比如我们要在头部插入一个元素,我们需要

//指向begin之前的一个不存在的“元素”
forward_list<int>::before_begin;

forward_list中的insert,erase和emplace版本是

insert_after()
erase_after()
emplace_after()

在这里插入图片描述
这些操作和之前顺序容器的通用操作都是一样的,除了传入的迭代器不是目标元素的迭代器而是目标元素迭代器之前的“元素”的迭代器之外。

对单项链表的操作,一般需要两个迭代器,一个迭代器是目标对象的前驱,一个迭代器指向目标元素自己。

对于单链表的题目,这一点需要记住。

练习

9.27

forward_list<int> flst = {1,2,3,4,5,6,7,8,9,0};
	// 单项链表需要操纵两个指针,一个前驱,一个指向要操纵的元素
	auto pre_iter = flst.cbefore_begin();
	auto cur_iter = flst.cbegin();
	while (cur_iter!=flst.cend()) {
		if (*cur_iter%2==1) {
			//传入前驱,返回的是删除元素之后的元素的迭代器
			cur_iter =  flst.erase_after(pre_iter);
		}
		else {
			//如果不是奇数,则移动前驱和当前元素
			pre_iter = cur_iter;
			++cur_iter;
		}
	}

9.28

void func_1(forward_list<string>& flst,const string& str1,const string& str2) {
	//因为不存在插入到最前面的情况,所以不需要使用前置
	auto cur_iter = flst.cbegin();
	bool str_in_container = false;
	while(cur_iter!=flst.cend()){
		if (*cur_iter==str1) {
			flst.insert_after(cur_iter,str2);
			str_in_container = true;
			break;
		}
		else {
			++cur_iter;
		}
	}
	//插入到容器最后
	if (!str_in_container) {
		//单项链表,不能直接--
		//寻找最后一个元素之前的元素
		auto pre_last_iter = flst.cbefore_begin();
		auto last_iter = flst.cbegin();
		while (last_iter!=flst.cend()) {
			pre_last_iter = last_iter;
			++last_iter;
		}
		flst.insert_after(pre_last_iter,str2);
	}

	for (const auto& item:flst) {
		cout << item << endl;
	}
}

9.3.5 改变容器大小

我们可以通过resize来增大或者缩小容器,array的大小是固定的,所以不允许使用resize().
在这里插入图片描述
如果resize之后,容器变小了,则按照顺序删除元素。
如果resize之后,容器变大了,则新元素执行值初始化,如果元素类型为类类型,对于只传入一个实参的resize,则该类类型必须要有默认构造函数。

对于需要传入两个实参的resize,如果容器的类型是类类型的话,赋予的初值t,在类类型中要有与之对应的构造函数。

class A {
public:
	explicit A(int _a): a(_a){

	}
	int a;
};

测试用例
//vector<A> vec(10,1);

练习

9.29
1.如果vec.resize(100),则会添加75个新元素,这些新元素执行值初始化,如果新元素是类类型,则执行默认构造函数

如果vec.resize(10),则删除掉后面的90个元素

9.30
如果元素类型是类类型,则要求元素类型要有默认构造函数

9.3.6 容器操作可能使迭代器失效

之前一直再说迭代器会失效,这一节专门介绍了迭代器失效。

首先容器的迭代器、指针或者引用会因为添加或者删除的操作导致迭代器失效,使用失效的迭代器将产生未定义的行为

1.对于list和forward_list因为它们的存储空间不一定是连续的,所以插入和删除都不会让他们的迭代器、指针、引用失效。

对于添加元素

2.对于vector,string,添加元素有可能导致迭代器,指针,引用失效。为什么是有可能,因为添加元素,有可能导致存储空间重新分配,一旦重新分配那么之前的迭代器,指针,引用将会失效。

但是如果没有重新分配内存,那么插入位置之后的迭代器,指针,引用会失效,但是插入位置之前的不会。

3.对于deque,在其他位置插入元素会导致迭代器、指针,应用失效,但是在首尾插入元素,只会导致迭代器失效,指针和引用不会。

对于删除元素
1.对于vector和string,删除元素之前的迭代器、指针、引用还有效
2.对于deque,在首尾之外删除元素,都会导致迭代器、指针、引用失效,如果在头删除元素,则其他位置的迭代器、指针、引用不会受到影响,如果在尾部删除元素,效果也一样。

总结下来,基本上vector,string,deque在增加和删除的时候都有可能让迭代器失效。

使用失效的迭代器,指针、引用时非常危险了,所以在编码时,我们要避免使用失效的迭代器。

1.在编写循环的时候,循环内容器有增加和删除操作,在每次循环时,需要更新迭代器

2.我们经常使用end()来判断,遍历是否结束,因为end()也是迭代器,所以为了避免其失效,推荐直接使用container<T>::end()而不是保存end()…

练习

9.31

list


	list<int> vi = { 0,1,2,3,4,5,6,7,8,9 };
	auto iter = vi.begin();
	while (iter!=vi.end()) {
		if (*iter%2) {
			iter = vi.insert(iter, *iter);
			//list容器不支持迭代器+n,-n,iter1>iter2,iter1<iter2,iter1>=iter2,iter1<=iter2
			// 但是list容器本身支持lst1>lst2,lst1>=lst2....
			//vector使用这种
			//iter += 2;
			//list使用这种
			++iter;
			++iter;
		}
		else {
			iter = vi.erase(iter);
		}
	}

forward_list

forward_list<int> vi = { 0,1,2,3,4,5,6,7,8,9 };
	auto pre_iter = vi.before_begin();
	auto iter = vi.begin();
	while (iter!=vi.end()) {
		if (*iter%2) {
			//指向插入的元素
			iter = vi.insert_after(pre_iter, *iter);

			//++iter,指向原来的自己
			++iter;
			//将自己赋值给前驱
			pre_iter = iter;
			//自己向前移动
			++iter;
		}
		else {
			// 返回的是删除之后的元素后面元素的迭代器所以不需要iter++
			// pre_iter不会失效,所以不需要改变
			iter = vi.erase_after(pre_iter);
		}
	}
	for (const auto& item:vi) {
		cout << item << endl;
	}

9.32
不合法,会产生未定义的行为,因为++的优先级比*高,所以iter会先++,再解引用,所以可能导致iter++访问到end(),此时解引用会导致未定义的行为。

9.33
在VS2017下程序无法运行,可能的原因是,在插入新元素后,重新分配了存储空间,所以原来的迭代器失效了。

9.34
1.while语句没有花括号,所以在while语句下只有if语句,这里iter没有任何++操作,所以程序是一个死循环

2.如果我们为程序添加了花括号,程序依旧是一个死循环,因为插入元素之后,iter为新插入元素的迭代器,所以++iter,只会回到原来的iter。这样只要遇到一个奇数,while就会出现死循环。

修改后的代码格式为:

vector<int> vi = {1,2,3,4,5};
	auto iter = vi.begin();
	while (iter != vi.end()) {
		if (*iter % 2 == 1) {
			iter = vi.insert(iter, *iter);
			++iter;
		}
		++iter;
		//++iter;
	}
	for (const auto& item:vi) {
		cout << item << endl;
	}
发布了54 篇原创文章 · 获赞 6 · 访问量 3328

猜你喜欢

转载自blog.csdn.net/zengqi12138/article/details/104227235