C++中的顺序容器(长文,全)

顺序容器

分段写法

C++ 顺序容器的初始化

顺序容器的操作——添加元素(含emplace)

C++容器的迭代器失效

C++ 顺序容器的赋值和swap

顺序容器的元素排列次序与元素值无关,而是由元素添加到容器里的次序决定。

标准库定义了三种顺序容器vector list deque,还提供了三种容器适配器,适配器是根据原始的容器类型所提供的操作。

顺序容器功能
vector支持快速随机访问
list支持快速输入、删除
deque双端队列
顺序容器适配器功能
stack
queue队列
priority优先级管理的队列

顺序容器的初始化

以下伪代码的注释

C 容器类型名
c、 c1、c2… 容器名
b(begin) 、e(end) 迭代器b和e
n 整型

C<T> c; 	//创建一个名为c的空容器
C c(c2); 	//创建容器c2的副本c,c和c2必须要有同样的容器类型
C c(b,e); 	//创建c,其元素是迭代器b和e所表示的范围内元素的副本。
C c(n,t);	//用n个值为t的元素创建容器c,其中值t必须是容器类型c的元素类型的值 只适用顺序容器
C c(n);		//创建由n个值初始化元素的容器c 只适用于顺序容器
1、将一个容器初始化为另一容器的副本

要求:容器类型和元素类型相同

vector<int> ivec;
vector<int> ivec2(ivec); 	//ok
list<int> ilist(ivec); 		//error 容器类型不同
vector<double> dvec(ivec) 	//error 元素类型不同
2、初始化为一段元素的副本

要求: 不要求容器类型相同,元素类型也可以不同,只要能够相互兼容,能够将要复制的元素转换为所构架的容器元素的元素类型,就可以实现复制。

// svec的容器类型为 vector<string>
list<string> slist(svec.begin(), svec.end());
vector<string>::iterator mid = svec.begin() + svec.size()/2;
deque<string> front(svec.begin(), mid);
deque<string> back(mid, svec.end());
3、分配和初始化指定数目的元素

创建顺序容器时,可显式地指定容器大小和一个(可选的)元素初始化式。

容器大小可以是常量或者非常量表达式。元素初始化必须是可用于初始化其元素类型的对象的值。

const list<int>::size_type list_size = 64;
list<string> slist(list_size, "eh?"); //64个strings, 每个都是"eh?"
4、 容器内元素的约束

要做容器元素类型,要满足下面的要求

  • 元素类型必须支持赋值运算
  • 元素类型的对象必须可以复制

大多数类型满足上述的元素类型要求。除了引用类型外,因为引用类型不支持赋值运算。

还有就是IO库类型,IO库类型不支持复制或赋值。因此不能存放IO对象的容器。

5、 容器的容器

现在判断一下下面哪个语句有问题。

vector< vector<string> > lines;							//acc
vector<vector<string>> lines;							//error

必须要记住要隔开> >,如果连在一起会被以为是右移操作符。

顺序容器支持的操作

begin 和 end成员

符号功能
c.begin()返回一个迭代器,它指向容器c的第一个元素
c.end()返回一个迭代器,指向容器c的最后一个元素的下一个位置
c.rbegin()返回一个逆序迭代器,指向容器c的最后一个元素
c.rend()返回一个逆序迭代器,它指向容器c的第一个元素前面的位置

添加元素

  • push_back()
操作功能
c.push_back(t)在容器c的尾部添加值为t的元素。返回void类型
c.push_front(t)在容器c的前端添加值为t的元素。返回void类型 只适用于list和deque容器类型
c.insert(p,t)在迭代器p所指向的元素前面插入值为t的新元素。返回指向新添加元素的迭代器
c.insert(p,n,t)在迭代器p所指向的元素前面插入n个值为t的新元素。返回void类型
c.insert(p,b,e)在迭代器p所指向的元素前面插入值为由迭代器b和e标记的范围内的元素。返回void 类型

关键概念:容器元素都是副本

在容器内添加元素时,系统时将元素值复制到容器里。类似,使用一段元素初始化新容器时,新容器存放的都是原始元素的副本。

被复制的原始值和容器中的元素互不相关。

新C++11标准 emplace

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

//现在有一个类
class Person {
private:
	string name;
	int id;
public:
	Person(string name, int id) {
		this->name = name;
		this->id = id;
	}
};
//然后看康emplace和push_back的区别

int main() {
	vector<Person> pvec;
	pvec.push_back(Person("me", 10));
	pvec.emplace_back("mea", 10);
	pvec.push_back("me", 10)  //error

}

push_back是可以直接传入一个对象,Person("me",10),而使用emplace是可以直接传入其参数,然后交给其类型的构造函数去构建一个对象放入vector<Person>

insert操作 由于我们直到insert(p,t)返回的时新添加元素的迭代器

通过这个我们便可以实现重复插入元素

list<string> lst
list<string>::iterator iter = lst.begin();
while(cin >> word){
	iter = lst.insert(iter.word);
}
关系操作符

所有容器类型都支持用关系操作符来实现两个容器的比较。比较的容器必须具有相同的容器类型,其元素类型也必须相同

  • 若两个容器具有相同的长度且所有元素都相等,则两个容器相等
  • 长度不同,但较短的容器中所有元素都等于较长容器中对应的元素,则称较短的容器小于另一个容器。
  • 如果两个容器都不是对方的初始子序列,则它们的比较结果取决于所比较的第一个不相等的元素。

访问元素的操作

at和下标操作只适用于string,vector,deque和array

操作功能
c.back()返回c中尾元素的引用。
c.back()返回c中首元素的引用。
c.back()返回c中下标为n的元素引用。n是一个无符号整数,n>=c.size(),则函数行为未定义
c.back()返回c中下标为n的元素的引用。下标越界,抛出out_of_range异常

返回的是引用

if(!c.empty()){
	c.front() = 43;
	auto &v = c.back();	//获得最后一个元素的引用
	v = 1024;
	auto v2 = c.back();	//不是引用,是c.back()的拷贝
	v2 = 0;

容器大小的操作

顺序容器大小的操作功能
c.size()返回容器c的大小
c.max_size()返回容器最多能够的存储的元素数目
c.empty()返回容器大小是否为0的bool值
c.resize(n)如果c.size() < n,那么采用值初始化的新元素,否则,删除掉多出来的元素(后部的元素)
c.resize(n, t)调整容器的大小,多出来的元素用t来初始化

注意:resize可能会使迭代器失效。在vector或deque容器上做resize操作由可能使所有的迭代器失效

如果resize操作进行了压缩,那么被删除的元素的迭代器失效。

删除元素

删除顺序容器内元素的操作功能
c.erase( p )删除迭代器p所指向的元素,返回指向被删除元素的下一个元素
c.erase(b,e)删除迭代器b和e所标记的范围内的所有元素,返回一个被删除段后面的元素。
c.clear()删除c内所有的元素。返回void
c.pop_back()删除容器c内的最后一个元素,返回void
c.pop_front()删除c内的第一个元素,返回void。只适用于list和deque

迭代器失效

添加/删除元素可能会使迭代器失效

insert 或 push操作都可能导致迭代器 失效。

  • 对于vector和string,添加/删除元素位置之后的迭代器失效。
  • 对于deque,插入到除了首尾位置之后元素的迭代器、指针、引用都会失效。
  • 对于list、forward_list不会失效,因为加入了新元素之后,迭代器会进行更新。

当编写循环将元素插入到vector或者deque容器时,程序必须保证迭代器在每次循环后都得到更新。

重定位迭代器

在进行了添加或者删除之后,程序员需要自己进行迭代器的更新。

// 要求:若是奇数元素,则复制,否则,删除偶数元素
vector<int> vi = {1,2,3,4,5,6,7,8,9};
auto iter = vi.begin();
while(iter != vi.end()){
	if(*iter % 2 ==1){
		iter = vi.insert(iter, *iter);
		iter += 2;
	}
	else{
		iter = vi.erase(iter);//iter指向我们删除之后的元素
	}
}

在这个代码里面,添加了元素之后,end()肯定会变化,而last = v.end(),last这个迭代器却没有变化,导致了存储在last中的迭代器失效。last不再指向的v的最后一个元素了。

不要保存end的迭代器
vector<int> v;
auto begin = v.begin(), end = v.end();
while (begin != end) {
	++begin;
	begin = v.insert(begin, 42);
	++begin;
}

当加了insert之后,end这个迭代器就失效了,它不是指向v中的任何元素,而是指向了v中尾元素之后的位置。因此这个代码会陷入死循环。

所以正确做法是

while(begin != v.end()){
	++begin;
	begin = v.insert(begin, 42);
	++begin;
}

顺序容器的赋值和swap

现在有两个容器c1和c2,想要把c2的全部元素赋给c1

c1.erase(c1.begin(), c1.end());
c1.insert(c1.begin(), c2.begin(), c2.end());
顺序容器的赋值操作功能
c1=c2删除c1的所有元素,将c2的元素复制给c1。c1和c2的类型必须相同
c={a,b,c…}将c1中的元素替换为初始化列表中元素的拷贝(array不适用)
swap(c1,c2)交换c1和c2中的元素。c1和c2必须具有相同的类型。swap通常比从c2向c1拷贝元素快得多
c1.swap(c2)交换内容:调用完该函数后,c1中存放的是c2原来的元素,c2中存放的则是c1原来的元素。c1和c2的类型必须相同。该函数的执行速度通常要比将c2的元素复制到c1的操作快
assign操作不适用关联容器和array
c.assign(b,e)重新设置c的元素:将迭代器b和e标记的范围内所有的元素复制到c中。b和e必须不是指向c中元素的迭代器
c.assign(il)将容器c中的元素替换为初始化列表il中的元素
c.assign(n,t)将容器c重新设置为存储n个值为t的元素

使用assign
如果容器类型相同,元素类型相同,就可以直接使用赋值操作符(=)将一个容器赋给另一个容器。

如果在不同(相同)类型的容器内,元素类型不相同但是相互兼容,则赋值运算符必须使用assign函数。如:通过assign操作实现将vector容器中一段char*类型的元素赋给string类型的list容器。

list<string> names;
vector<const char*> oldstyle;
names = oldstyle; //错误,类型不匹配
names = oldstyle.assign(oldstyle.cbegin(), oldstyle.cend());

使用swap

swap交换两个相同类型容器的内容。调用swap之后,两个容器的元素会交换

vector<string> svec1(10);
vector<string> svec2(24);
swap(svec1, svec2);

调用swap之后,svec1会右24个元素,svec1有10个元素。swap没有交换元素本身,而是交换了两个数据结构,所以操作会很快。

  • 并且swap不对任何元素进行拷贝、删除或插入操作,除了array数组外。因此能在常数级时间内完成。
  • 迭代器不会失效, 假设现在有一个iter指向了svec1[3],在交换之后iter指向了svec2[3],变换了容器而已。
  • 虽然说可以调用成员函数的swap, s1.swap(s2),但是在泛型编程中,经常调用的是非成员函数的swap。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值