C++ STL顺序容器详解(二)

C++ STL顺序容器详解(一)

[目录]
顺序容器的操作
特殊的forward_list操作
改变容器的大小
容器操作的注意点
vector增长原理

顺序容器的操作

向顺序容器添加元素

下面表格有中

  1. 插入操作会改变array的大小,所以都不支持
  2. forwar_list有自己的insert和emplace
  3. forward_list不支持push_back和emplace_back
  4. vector与string不支持push_front和emplace_front
元素操作 说明
c.push_back(t) 向c的尾部添加元素t,返回void
c.emplace_back(args) 向c的尾部添加元素args,返回void
c.push_front(t) 向c的首部添加元素t,返回void
c.emplace_front(args) 向c的首部添加元素args,返回void
c.insert(p,t) 向迭代器p前面的位置插入元素t,返回新添加元素的迭代器
c.emplace(p,args) 向迭代器p前面的位置插入元素args,返回新添加元素的迭代器
c…insert(p,n,t) 向迭代器p前面的位置插入n个t元素,返回插入元素的第一个元素的迭代器,若n为0则返回p
c.insert(p,b,e) 向迭代器p前面的位置插入迭代器b,e之间的元素,但是b,e之间的迭代器不能指向自身c,返回插入元素的第一个元素的迭代器
c.insert(p,li) li是一个花括号的额列表元素,将这些元素插入到迭代器p前面的位置,返回第一个元素的迭代器,如果列表为空,则返回p

需要注意的是,vector和string,deque容器插入元素都会让指向该容器的迭代器,指针,引用失效

使用push_back

使用push_back,将元素插入到容器的末尾,但是array和forward_list不支持这个操作。
需要注意的是,我们插入的元素是插入对象的拷贝,并不是插入对象本身。

	vector<int> l;
	for (int u = 0; u < 10; u++)
		l.push_back(u); // 插入数据
	for (auto y : l)
		cout << y << endl;

使用push_front(仅支持forward_list,list,deque)

push_front在元素的前面插入一个元素。

list<int> l{1,2,3,4,5,6,7,8,9};
	l.push_front(100);
	for (auto i : l)
		cout << i << endl;

在特定的位置插入元素

我们不管使用push_back,或者是push_front,都只能插入到容器首尾,但是有的时候我们需要将元素插入到容器的中间。
insert可以插入到容器的任意一个位置,并且可以一次性插入0个以上的元素,vector,string,list,deque都支持insert,forward_list有自己的insert版本,不支持此版本。后续会介绍。
insert可以插入到容器的任意位置,所以也解决的某些容器不能使用push_front()的问题

vector<int> i{ 1,2,3,4,5,6,7,8,9 };
	auto x = i.begin();
	int u = 0;
	while (u < 5)
	{
		x++; // 迭代器移动5个位置
		u++;
	}
	i.insert(x, 666); // 在 i[5]插入666
	for (auto p : i)
		cout << p << "  ";
	
	//输出结果:1  2  3  4  5  666  6  7  8  9

我们通过循环的方式来移动迭代器,移动到我们想要插入的位置后传给insert。实现任意位置插入元素。

插入范围内元素

insert不仅可以接受一个参数,还可以接受更多的参数。

//在i的末尾插入10个0
i.insert(i.end(),10,0); 

同时也能够接受两个迭代器之间的范围,以及一个列表

vector<int> s{1,2,3,4,5,6,7,8,9,10};
i.insert(i.end(),s.begin(),s.end()-2);
//在i的末尾插入s开头到倒数第二个元素
i.insert(i.end(),{1,2,3,4,5});
//在i的末尾插入列表的元素

插入范围元素的时候需要注意的是,我们插入的范围不能自身的迭代器。
例如下面的错误示范

i.insert(i.end(),i.begin(),i.end()-2);

使用insert的返回值

我们使用insert的时候,会返回一个迭代器,这个迭代器指向新插入的元素,这样我们就可以在容器的同一个位置不断的进行插入操作

vector<string> i;
	string word;
	auto iter = i.begin();
	int u = 0;
	while (cin >> word && u < 10)
	{
		iter = i.insert(iter, word);
		u++;
	}
	for (auto p : i)
		cout << p << " ";

在这个循环中,不停的将迭代器一直停留在这个位置,并且进行插入操作。

插入的位置是iter指向的元素之前的位置,比如迭代器指向5的位置,那么插入的位置在6的位置

使用emplace操作

C++11的标准中加入了emplace_back,emplace_front,emplace的操作,分别是插入尾元素,插入首元素,插入任意位置元素。
与前面插入操作不同的是,emplace可以操作插入对象的构造,比如一个容器是类类型的,他只有我们给定的构造函数,但是没有默认构造,那么这个时候我们可以传入这个类的构造参数

struct person
{
	int age, hight;
	string name;
	person(int age, int hight, string name) :age(age), hight(hight), name(name)
	{
		cout << "name:" << name << "\n" << "age:" << age
			<< "\n" << "hight:" << hight << endl;
	}
};
int main()
{
	vector<person> p;
	p.push_back(1, 1, "张三"); // 错误,不能传入太多的参数,不支持构造
	p.emplace_back(18, 160, "张三"); // 正确,直接调用构造函数
}

访问元素

除了array之外的每一个容器都有front成员,除了forward_list之外的每一个容器都有back成员。
front和back成员返回的都是首元素或尾元素的引用

vector<int> p{1,2,3,4,5,6,7,8,9,10};
cout << "首元素:" << p.front() << "尾元素:" << p.back() << endl;

下面表格中:

  • at和下标操作只适用于string,vector,deque,array
  • back操作不适用于forward_list
容器中访问元素的操作 说明
c.back() 返回尾元素的引用,若c为空,函数位定义
c.front() 返回元素的首元素,若c为空,函数未定义
c[n] 返回c下标对应的元素的引用,n是一个无字符型(size_t)
c.at(n) 返回下标为n的元素引用,如果下标越界,抛出异常

需要注意的是,如果容器是一个const类型的,那么返回的是一个const引用,不可修改c中的值。如果是普通引用则可以修改

删除元素

除了array之外,其他容器有多种删除方式。
以下容器会改变容器的大小,所以不适用于array
forward_list有自己的erase
forwar_list不支持pop_back
vector和string不支持pop_front

顺序容器的删除操作 说明
c.pop_back() 删除尾元素,若c为空,则函数位定义,返回void
c.pop_front 删除首元素,若c为空,则函数未定义,返回void
c.erase( p ) 删除迭代器p指定的元素,返回一个指向被删除元素之后元素的迭代器,如果迭代器是尾后迭代器,则位定义
c.erase(b,e) 删除迭代器b到迭代器之间的元素,返回一个指向删除的最后一个元素的下一个元素的迭代器,如果e是尾后迭代器,则返回一个尾后迭代器
c.clear() 清空c中所有的元素,返回void

特殊的forward_list操作

因为forward_list是一个单链表的缘故,删除或者添加一个元素都会改变下一个元素链表的指向,所以需要特殊的插入和删除的操作。
链表图示
我们删除一个元素后,访问的是删除元素的前驱,删除元素前序的指针指向删除元素的下一个元素。
所以我们不再使用erase,insert和emplace,而是改成insert_after,emplace_after,erase_after.

forwar_list的删除和插入操作 说明
l.before_begin() 返回指向链表首元素之前的元素迭代器,这个迭代器不能解引用
l.cbefor_begin() 返回指向链表首元素之前的元素迭代器,这个迭代器不能解引用,返回const_iterator
l.insert_after(p,t) 在迭代器p之后的位置插入元素t,返回一个插入元素迭代器
l.insert_after(p,n,t) 在迭代器p之后的位置插入 n 个元素 t,返回一个插入首元素迭代器
l.insert_after(p.b,e) 在迭代器p之后的位置插入b,e迭代器之间的元素,返回一个插入首元素迭代器
l.erase_after§ 删除p指向的位置之后的元素,返回一个被删除元素之后额迭代器。
l.erase(b,e) 删除b之后(不包含b)到c之间的元素,返回一个被删除元素之后额迭代器。

改变容器的大小

我们可以使用 resize 来控制容器的大小,由于array是一个固定大小的额容器,所以不支持该操作。
resize可以接受一个参数,只是控制容器大小的参数,也可以接受两个参数,一个是容器的大小,另外一个如果容器增大后,增大空间的元素初始值

vector<int> p{1,2,3,4,5,6,7,8,9,10};
	p.resize(5); // 缩小容器
	p.resize(20); // 扩大容器,但是元素初始值由系统默认
	p.resize(20, -1); // 扩大容器,扩大部分的初始值为-1

如果我们缩小容器,那么删除的时容器后部分的元素。

容器操作的注意点

我们向容器的插入元素或者删除元素的时候可能会改变原本容器中的迭代器,如我们一个正常的容器,在他的尾部添加上新元素的时候,尾后迭代器就会发生改变,这个时候我们如果将之前的迭代器赋值给其他变量,其他变量在使用这个迭代器的时候就不再时原来的迭代器了,这样可能会违背我们程序的初衷

添加元素后
如果是vector或者string添加元素后,内存空间发生变化,则原本的迭代器,指针,引用全部失效,如果空间没有重新分配,那么插入位置之前的迭代器,指针,引用仍然有效,但是插入位置之后的全部无效
对于deque,除了插入首元素之外的任何位置,都会导致迭代器指针,引用失效。如果在首元素插入,那么容器的迭代器失效,但是指向元素的指针和引用仍然有效。
对于list,forw_list,迭代器,指针,引用仍然有效

删除元素
对于list和forwar_list指向容器其他位置的迭代器,指针,引用仍然有效
对于deque,除了首位元素外的其他位置的元素被删除后,所有的迭代器,指针,引用全部失效,如果删除的是尾元素,那么尾迭代器失效,其他迭代器,指针,引用仍然有效;
对于vector,string,指向被删元素之前的元素的迭代器,指针,引用仍然有效。
当我们删除元素时,尾后迭代器总是失效

vector增长原理

vector增长原理:
vector容器之所以能够扩充内存,并且这块内存是连续的,这是因为系统先创建一定数量的空间,等到空间储存满了之后,如果在添加元素,就开辟另外一个空间,将旧空间中的元素全部拷贝到新的空间上,这就是为什么增加元素如果空间重新分配,迭代器,指针,引用失效的原因
但是如果插入的元素过于频繁,那么也需要频繁的开辟空间,这样就降低了效率。所以采用了另外一种方法。
在我们创建对象的时候就已经开辟了一定的空间,系统开辟空间的储存量大于我们存入元素的总量,只有当存入元素的总量等于容器总容量的时候,并且需要在存入元素,这个时候才开辟空间。

  • shrink_to_fit只适用于vector,string,deque
  • capacity,reserve只适用于 vector,string
容量大小管理 说明
c.shrnk_to_fit() 将capacity的大小减为size()的大小
c.capacity() 不重新分配内存的话,c的总大小
c.reserve(n) 分配至少能容纳n个元素的大小

我们在调用reszie的时候并不会改变capacity的大小,但是会改变size 的大小

vector<int> p{1};
	p.reserve(10); // 预留至少容纳去10个元素的空间
	cout << "capacity:" << p.capacity() << "\n"
		<< "size:" << p.size();
	//
	//输出结果:	capacity:10
	//			size : 1

C++ STL顺序容器详解(三)

你可能感兴趣的:(C++基础,c++,stl)