学习C++——顺序容器(2)

3、顺序容器操作

3.1、向顺序容器添加元素

除了array外,所有的标准库容器都提供了灵活的内存管理。在运行时可以动态添加或删除元素来改变容器的大小。
  • 使用push_back
push_back将一个元素追加到一个vector的尾部。除array和forward_list之外,每个顺序容器( list,vector,deque,string)都支持push_back.
	string word;
	while(cin >> word)
		container.push_back(word);//container的类型可以是list,vecvtor和deque


	void pluralize(size_t cnt, string &word)
	{
		if(cnt > 1)
			word.push_back('s');//等价于word += 's';
	}
当我们用一个对象来初始化容器时,或将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝,而不是对象本身。
  • 使用push_front
此操作将元素插入到容器头部。 list,forward_list,deque支持此操作。
	list<int> ilist;
	for(size_t ix = 0; ix != 4; ++ix)
		ilist.push_front(ix);//将ix添加到ilist的头部
  • 在容器中的特定位置添加元素
insert提供了更一般的功能,允许在容器中任意位置插入0个或多个元素。 vector,deque,list和string都支持insert成员。forward_list提供了特殊的insert成员。
insert函数接受一个迭代器作为其第一个参数,函数将元素插入到迭代器所指定的位置之前。
slist.insert(iter, "Hello");//iter是一个迭代器,将"Hello"添加到iter之前的位置。
备注:虽然vector不支持push_front,但是它支持insert
  • 插入范围内元素
insert还接受一个元素数目和一个值,它将指定数量的元素添加到指定位置之前,这些元素都按给定值初始化。
	svec.insert(svec.end(), 10, "Hello");
	//将10个元素插入到svec的末尾,并将所有元素都初始化为"Hello"

	vector<string>  v = {"yy", "xx", "kk"};
	//将v中的最后两个元素添加到slist中
	slist.insert(slist.begin(), slist.end() - 2, v.end());
	
	slist.insert(slist.end(), {"yy", "xx", "kk"});
  • 使用insert的返回值
通过使用insert的返回值,可以在容器中一个特定位置反复插入元素。

	list<string>  lst;
	auto iter = lst.begin();
	while(cin >> word)
		iter = lst.insert(iter, word);//等价于调用push_front
  • 使用emplace操作(C++11)
新标准引入了三个新成员——emplace_front,emplace,和emplace_back。这些操作是构造元素,而不是拷贝元素。 这些操作分别对应push_front,insert,和push_back,允许我们将元素放置在容器头部,一个指定位置和容器末尾。

当我们使用push或insert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。但是当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素。

	//在C的末尾添加一个Sales对象
	c.emplace_back("yao",12,23);//使用三个参数的Sales的构造函数
	c.push_back("yao",12,23);//错误,没有接收三个参数的push_back版本
	c.push_back(Sales("yao",12,23));//正确,创建一个临时的Sales对象传递给push_back
备注:在使用emplace_back的调用和第二个push_back调用都会创建新的Sales对象。在emplace_back时,会在容器管理的内存空间中直接创建对象。而调用push_back则会创建一个局部临时对象,并将其压入容器中。
备注:emplace函数在容器中直接构造元素,传递给emplace函数的参数必须与元素类型的构造函数相匹配。

3.2、访问元素

在顺序容器中访问元素的操作
c.back()返回c中尾元素的引用。若c为空,函数行为未定义
c.front()返回c中首元素的引用。若c为空,函数行为未定义
c[n]返回c中下标为n的元素的引用,n是一个无符号整数。若n>=c.size(),则函数行为未定义
c.at(n)返回下标为n的元素的引用。如果下标越界,则抛出out_ofo_range异常。
备注:
  1. at和下标操作只适用于string,vector,deque和array
  2. back不适用于forward_list
  3. 对一个空容器调用front和back,就像使用一个越界的下标一样,是一个错误。
  • 访问成员函数返回的是引用
在容器中访问元素的成员函数,返回的是引用。如果容器时一个const对象,则返回值是const的引用。如果容器不是const的,则返回值是普通引用,我们可以用来改变元素的值。
  • 下标操作和安全的随机访问
提供快速随机访问的容器,也都提供下标运算符。下标运算符接受一个小标参数,返回容器中该位置的元素的引用。
如何保证下标的有效性?(也就是不越界)

如果我们希望确保下标不越界,是合法的,可以使用at成员函数。at成员函数类似于下标运算符,但是如果下标越界,at会抛出一个out_of_range异常。

	vector<string>  svec;//空vector
	cout << svec[0];//运行时错误:svec中没有元素
	cout << svec.at(0);//抛出一个out_of_range异常

3.3、删除元素

顺序容器的删除操作
c.pop_back()删除c中尾元素。若c为空,则函数行为未定义。函数返回void.
c.pop_front()删除c中首元素。若c为空,则函数行为未定义。函数返回void.
c.erase(p)删除迭代器p所指定的元素,返回一个指向被删除元素之后元素的迭代器。
c.erase(b,e)删除迭代器b和e所指定范围内的元素。返回一个指向最后一个被删元素之后元素的迭代器。
c.clear()删除c中所有元素。返回void

备注:
  1. array不适用于这些操作。
  2. 删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。指向vector或string中删除点之后位置的迭代器、引用和指针都会失效。
  3. 删除元素的成员函数并不检查其参数,所以要确保它们是存在的。
  • pop_front和pop_back成员函数
像vector和string不支持push_front一样,这些类型也不支持pop_front。
forward_list也不支持pop_back。这些操作返回void。

  • 从容器内部删除一个元素
	//删除一个List中的所有奇数元素
	list<int> lst = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
	auto it = lst.begin();
	while(it != lst.end())
		if(*it % 2)				//所元素为奇数
			it = lst.erase(it); //删除此元素
		else
			++it;

  • 删除多个元素

3.4、特殊的forward_list操作

 
forward_list中插入或删除元素的操作
lst.before_begin()
lst.cbefore_begin()
返回指向链表首元素之前不存在的元素的迭代器。此迭代器不能解引用。cbefore_begin()返回一个const_iterator
lst.insert_after(p, t)
lst.insert_after(p, n, t)
lst.insert_after(p, b, e)
lst.insert_after(p, il)
在迭代器p之后的位置插入元素。t是一个对象,n是数量,b和e是表示范围的一对迭代器(b和e不能指向lst内),
il是一个花括号列表。返回一个指向最后一个插入元素的迭代器。
如果范围为空,则返回p;
emplace_after(p, args)使用args在p指定位置之后创建一个元素。返回一个指向这个新元素的迭代器。
如果p为尾后迭代器,则函数行为未定义。
lst.erase_after(p)
lst.erase_after(b, e)
删除p指向的位置之后的元素,或删除从b到e之间的元素(但是不包括e)。
返回一个指向被删除元素之后元素的迭代器,若不存在这个迭代器,则返回尾后迭代器。

	//从forward_list中删除奇数元素
	forward_list<int> flst = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
	auto prev = flst.before_begin();//表示flst的“首前元素”
	auto curr = flst.begin();//表示flst中的第一个元素
	while(curr != flst.end())//扔有元素要处理
	{
		if(*curr % 2)//若元素为奇数
			curr = flst.erase_after(prev);//删除prev指向元素之后的元素,并移动curr
		else
		{
			prev = curr;//移动迭代器curr,指向下一个元素,prev指向curr之前的元素
			++curr;
		}	
	}

3.5、改变容器的大小

顺序容器大小操作
c.resize(n)调整c的大小为n个元素。
若n<c.size(),则多出的元素被丢弃。
若必须添加新元素,则对新元素进行值初始化。
c.reszie(n, t)调整c的大小为n个元素。任何新添加的元素都初始化为值t

	list<int>  ilist(10, 42);//10个int,每个值都是42
	ilist.resize(15);//将5个值为0的元素添加到ilist的末尾。
	ilist.resize(25, -1);//将10个值为-1的元素添加到ilist的末尾。
	ilist.resize(5);//从ilist末尾删除20个元素

3.6、容器操作可能使迭代器失效

在向容器添加元素之后:
  • 如果容器时vector或string,且存储空间重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间没有重新分配,指向插入位置之前的元素的迭代器、指针和引用仍然有效,但指向插入位置之后元素的迭代器、指针和引用都将失效。
  • 对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但是指向存在的元素的引用和指针不会失效。
  • 对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。
在删除容器中一个元素后:
  • 对于list和forward_list,指向容器其它位置的迭代器、指针和引用仍然有效。
  • 对于deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、指针和引用也会失效。
  • 对于vector和string,指向被删除元素之前元素的迭代器、指针和引用仍有效。
  • 当我们删除元素时,尾后迭代器总是会失效。

4、vector对象是如何增长的

容器大小管理操作
c.shrink_to_fit()请将capacity()减少为与size()相同大小
c.capacity()不重新分配内存空间的话,c可以保存多少元素
c.reserve(n)分配至少能容纳n个元素的内存空间

备注:
shrink_to_fit只适用于 vector,string和deque。
capacity和reserve只适用于vector和string。
reserve并不改变容器中元素的数量,但影响vector预先分配多大的内存空间。

调用reserve不会减少容器占用的内存空间。
resize成员函数只是改变容器中元素的数目,而不是容器的容量。

  • capacity和size
容器的size是指它已经保存的元素的数目。
capacity则是在不分配新的内存空间的前提下它最多可以保存多少元素。
#include <iostream>
#include <vector>

int main()
{
	using namespace std;

	vector<int> ivec;
	cout << "ivec: size = " << ivec.size();//容器中元素的个数
	cout << "  capacity = " << ivec.capacity() << endl;//容器的大小

	for(vector<int>::size_type ix = 0; ix != 24; ++ix)//向容器中添加24个元素
		ivec.push_back(ix);

	cout << "ivec: size = " << ivec.size();//现在容器中元素的个数
	cout << " capacity = " << ivec.capacity() << endl;//现在容器的大小(比元素个数要大)

	ivec.reserve(50);//将capacity设置为至少是50
	cout << "ivec: size = " << ivec.size();//容器中元素的个数还是24,没有变化
	cout << " capacity = " << ivec.capacity() << endl;//容器大小为50

	//将这些预留空间用完
	while(ivec.size() != ivec.capacity())
		ivec.push_back(0);
	cout << "ivec: size = " << ivec.size();//容器中元素的个数
	cout << " capacity = " << ivec.capacity() << endl;//容器大小不变
	//没有预留空间之后再添加一个元素
	ivec.push_back(12);
	cout << "ivec: size = " << ivec.size();//容器中元素的个数为51
	cout << " capacity = " << ivec.capacity() << endl;//容器大小改变,具体多少由vector内存分配机制决定
	//退回多余的内存
	ivec.shrink_to_fit();
	cout << "ivec: size = " << ivec.size();//容器中元素的个数为51
	cout << " capacity = " << ivec.capacity() << endl;//容器大小变为51,将多余的内存归还
	cin.get();
	return 0;
}




5、额外的string操作

构造string的其它方法
string s(cp, n)s是cp指向的数组中前n个字符的拷贝。此数组至少包含n个字符
string s(s2, pos2)s是string s2从下标pos2开始的字符的拷贝。若pos2>s.size(),函数行为未定义。
string s(s2, pos2, len2)s是string s2从下标pos2开始len2个字符的拷贝。


5.1、substr操作

substr操作返回一个string,它是原始string的一部分或全部的拷贝。可以传递给substr一个可选的开始位置和计数值:
s.substr(pos, n)返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0。n的默认值为s.size()-pos,即拷贝从pos开始的所有字符。

5.2、改变string的其它方法

修改string的操作
s.insert(pos, args)在pos之前插入args指定的字符。pos可以是一个下标或者是一个迭代器。
接受下标的版本返回一个指向s的引用。
接受迭代器的版本返回指向第一个插入字符的迭代器。
s.erase(pos, len)删除从位置pos开始的len个字符。
如果len被省略,则删除从pos开始直至末尾的所有字符。
返回一个指向s的引用。
s.assign(args)将s中的字符替换为args指定的字符。返回一个指向s的引用。
s.append(args)将args追加到s。返回一个指向s的引用。
s.replace(range, args)删除s中范围range内的字符,替换为args指定的字符。
range是一个下标和一个长度,或者是一对指向s的迭代器。返回一个指向s的引用。


6、容器适配器

除了顺序容器外,标准库还定义了三个顺序容器适配器:stack, queue和priority_queue。
一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。比如:stack适配器接受一个顺序容器(除array和forward_list),并使其操作起来像一个stack一样。
  • 定义一个适配器

每个适配器都定义了两个构造函数:默认构造函数创建一个空对象。接受一个容器的构造函数拷贝该容器来初始化适配器。
deque<int>  deq;//
stack<int>  stk(deq);//用deq来初始化一个新的stack。从deq拷贝元素到stk。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值