顺序容器的相关操作

  • 顺序容器迭代器(forward_list迭代器部分不支持)

iterator                           //容器的正序迭代器类型

reverse_iterator             //容器的逆序迭代器类型

const_iterator                //可以读取元素,但不能修改元素的迭代器类型

const_reverse_iterator  //不能修改元素的逆序迭代器类型

c.begin()  c.end()          //返回指向c的首元素和尾元素之后位置的迭代器

c.cbegin()  c.cend()      //返回const_iterator

c.rbegin()  c.rend()       //返回指向c的尾元素和首元素之前位置的迭代器

c.crbegin()  c.crend()   //返回const_reverse_iterator

	vector<int> v{ 1,2,3,4 };
	for (auto iter = v.begin(); iter != v.end(); iter++)
		cout << *iter << endl;   //1 2 3 4
	for (auto r_iter = v.rbegin(); r_iter != v.rend(); r_iter++)
		cout << *r_iter << endl; //4 3 2 1
  • 容器定义和初始化

每个容器类型都定义了一个默认构造函数,除array之外,其他容器的默认构造函数都会创建一个指定类型的空容器

默认构造函数:
vector<int> v1;     //如果是vector等其他顺序容器,则v1为空
array<int,10> a1;   //如果是array,则a1中元素按默认方式初始化,初始化为一些不确定的值
array<int,10> a2{1};  //初始化为一个1和9个0 

拷贝构造:
vector<int> v2(v1);  //要求v1和v2:1.容器类型必须相同  2.容器中元素类型必须相同
vector<int> v2=v1;
array<int,10> a2(a1);   //如果是array,则俩者还必须具有相同的大小

列表初始化:
vector<int> v3{1,2,3,4};  //v3初始化为初始化列表中元素的拷贝,列表中元素的类型必须与v3的元素类型相容
array<int,5> a3{1,2,3,4};  //如果是array,则列表中元素数目必须等于或小于array的大小

范围初始化(支持不同容器类型,但容器中元素类型需要相容):
vector<int> v4(v3.begin(),v3.end());  //v4初始化为v3的迭代器所指范围中的元素的拷贝,范围中元素类型必须与v4的元素类型相容,array不使用此初始化方法
vector<const char*> v4{"a","an","the"};
forward_list<string> l1(v4.begin(),v4.end());  //俩种类型相容,const char*可以转换为string

参数值初始化(只有顺序容器且不包括array的构造函数才能接受大小参数):
vector<int> v5(10);   //v5包含了10个元素,这些元素进行了值初始化,此构造函数是explicit的(string不适用)
vector<int> v5(10,1);  //v5包含10个初始化为值是1的元素

  • 赋值、swap、assign

赋值运算符将其左边容器中的全部元素替换为右边容器中元素的拷贝,如果俩个容器原来大小不同,赋值运算后俩者的大小都与右边容器的原大小相同(array除外,array特殊处理)

容器对象赋值:
vector<int> v1{1,2,3};
vector<int> v2{1};
v1=v2;      //v1和v2必须具有相同的类型,经过运算后,原本大小为3的v1,大小变成了1
array<int,10> a1={0};
array<int,10> a2={1,2,3,4};
a1=a2;     //array类型赋值时,俩者的大小也需要相同,大小指的是array的总容量,总容量相等则可以相互赋值

列表赋值:
vector<int> v1;
v1={1,2,3,4,5};
array<int,10> a1;
a1={1,2,3,4,5};     //此时a1中元素为{1,2,3,4,5,0,0,0,0,0}

swap:
除array外,交换俩个容器内容的操作保证会很快,因为元素本身并未交换,swap只是交换了俩个容器的内部数据结构,除array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成
vector<int> v1{1,2,3,4};  //容器的种类和容器中元素的种类都要相同,如果是array容器,则容器的大小也要相同
vector<int> v2{11,11,11,11,11};
v1.swap(v2);   //swap通常比从v2向v1拷贝元素快得多,swap后v1的capacity为5,v2的capacity为4
       元素不会被移动的事实意味着,除string外指向容器的迭代器、引用和指针在swap操作后都不会失效,它们仍指向swap操作之前所指向的那些元素,但是在swap之后,这些元素已经属于不同的容器了,例如iter在swap之前指向v1[3]的int,那么在swap之后它指向v2[3]的元素,与其他容器不同,对一个string调用swap会导致迭代器、引用和指针失效

	vector<int> v1{ 1,2,3 };
	vector<int> v2{ 11,11,11,11};
	auto iter_b_1 = v1.begin();
	auto iter_e_1 = v1.end();
	auto iter_b_2 = v2.begin();
	auto iter_e_2 = v2.end();
	v1.swap(v2);
	for (auto i = iter_b_1; i != iter_e_1; i++)
		cout << *i << endl;   //1,2,3
	for (auto i : v1)
		cout << i << endl;  //11,11,11,11

       与其他容器不容,swap俩个array会真正交换它们的元素,因此,交换俩个array所需要的时间与array中元素的数目成正比,因此对于array,在swap操作之后,指针、引用和迭代器所绑定的元素保存不变,但元素值已经与另一个array中对应元素的值进行了交换。

	array<int, 4> v1{ 1,2,3,4 };
	array<int, 4> v2{ 11,11,11,11 };
	auto iter_b_1 = v1.begin();
	auto iter_e_1 = v1.end();
	auto iter_b_2 = v2.begin();
	auto iter_e_2 = v2.end();
	v1.swap(v2);
	for (auto i = iter_b_1; i != iter_e_1; i++)
		cout << *i << endl;   //11,11,11,11
	for (auto i : v1)
		cout << i << endl;  //11,11,11,11

赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效,而swap操作将容器内容交换不会导致指向容器的迭代器、引用和指针失效(容器类型为array和string的情况除外)

新标准库中,容器既提供成员函数版本的swap,也提供非成员版本的swap,而早期标准库版本只提供成员函数版本的swap,非成员版本的swap在泛型编程中是非常重要的。统一使用非成员版本的swap是一个好习惯。

assign(此操作不适用于关联容器和array)
assign允许从一个不同但相容的类型赋值(可以跨容器)
vector<int> v1{1,2,3,4};
vector<int> v2{1,2,3,4,5,6};
v2.assign(v1.begin(),v1.end());     //将v2中元素替换为v1迭代器范围中的元素,迭代器不能指向v2中的元素,赋值后v2的capacity值还是6
v2.assign({11,11,11});    //将v2中的元素替换为初始化列表中的元素
v2.assign(10,2);  //将v2中元素替换为10个值为2的元素

array特性:
当定义一个array时,除了指定元素类型,还要指定容器大小,array的大小固定的特性也影响了它所定义的构造函数的行为,与其他容器不同,一个默认构造的array是非空的,它包含了与其大小一样多的元素,这些元素都被默认初始化(初始值不一定是什么)初始化array时,如果元素类型是一个类类型,那么该类必须有一个默认的构造函数,以使值初始化能够进行,值得注意的是,虽然我们不能对内置数组类型进行拷贝或对象赋值操作,但array并无此限制

  • 容器大小操作(size、empty、max_size)

除了forward_list外,每个容器类型都有三个与大小相关的操作,成员函数size返回容器中元素的数目,empty当size为0时返回布尔值true,否则返回false,max_size返回一个大于或等于该容器所能容纳的最大元素数的值,forward_list支持max_size和empty,但不支持size

  • 关系运算符

每个容器类型都支持相等运算符(==和!=),除了无序关联容器外的所有容器都支持关系运算符(>、>=、<、<=),关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素,比较俩个容器实际上是进行元素的逐对比较,这些运算符的工作方式与string运算符类似:
1.如果俩个容器具有相同大小且所有元素都俩俩对应相等,则这俩个容器相等,否则不等
2.如果俩个容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器
3.如果俩个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等的元素的比较结果

容器的关系运算符使用元素的关系运算符完成比较,如果元素类型不支持所需运算符,那么保存这种元素的容器就不能使用相应的关系运算符,只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较俩个容器

  • 链表容器(list、forward_list)特定算法(merge、remove、unique、reverse、splice)

       与其他容器不同,链表类型list和forward_list定义了几个成员函数形式的算法,特别是,它们定义了独有的sort、merge、remove、reverse和unique。通用版本的sort要求随机访问迭代器因此不能用于list和forward_list,因为这俩个类型分别提供双向迭代器和前向迭代器对于list和forward_list应该优先使用成员函数版本的算法而不是通用算法

merge:

lst.merge(lst2) 
lst.merge(lst2,comp)
//将来自lst2的元素合并入lst,lst和lst2都必须是有序的,否则会发生报错,元素将从lst2中删除,在合并之后,lst2变为空,默认版本使用<运算符,第二个版本使用给定的比较运算操作,重复元素不会覆盖,而是继续保存下来。

sort:

lst.sort()   //使用<比较操作来排序元素
lst.sort(comp)  //使用指定比较操作来完成排序

       这里需要注意的是,在merge使用之前的俩个list的顺序如果和merge函数的比较方法如果不一致则会导致崩溃,比如俩个list的从大到小的排序,在merge的时候使用的默认的从小到大的排序,则会反生崩溃,所以建议先进行指定顺序sort,然后再按照这个指定顺序进行merge

如下程序则会发生报错

//俩个list是从大到小的顺序
list<int> l1{ 99,88,77 };
list<int> l2{ 100,66,55 };
l1.merge(l2);  //merge用的是默认的从小到大的顺序
for (auto i : l1)
	cout << i << endl;

以下为正确使用:

//如果无序则必须进行排序
list<int> l1{ 11,77,33,44,66,22 };
l1.sort(cmp); //11 22 33 44 66 77

list<int> l2{ 88,99,100,21 };
l2.sort(cmp);  //21 88 99 100
l1.merge(l2,cmp);  //按照从大到小的方式
for (auto i : l1)
	cout << i << endl;  //100 99 88 77 66 44 33 22 21 11
cout << l2.size() << endl;  //0

remove:

lst.remove(val)      //调用erase删除掉与给定值相等(==)或令一元谓词为真的每个元素
lst.remove_if(pred)  

bool func(const int& i)
{
	return (i < 44);  //筛选掉小于44的数
}

int main()
{
	list<int> l1{ 11,77,33,44,66,22 };
	l1.remove(11);
	for (auto i : l1)
		cout << i << "	";  //77 33 44 66 22 
	cout << endl;
	l1.remove_if(func);
	for (auto i : l1)
		cout << i << "	";  //77 44 66 
	cout << endl;
}

unique:

lst.unique()       
lst.unique(pred)
//调用erase删除同一个值的连续拷贝,如果相同的版本之间隔着其他的元素,则不能删除,第一个版本使用==,第二个版本使用给定的二元谓词

bool func(const int& i1, const int& i2)
{
	return i1 > i2;
}

bool func2(const int& i1, const int& i2)
{
	return i1 >= i2;
}

int main()
{
	list<int> l1{ 11,11,22,11,22,44,44,44 };
	l1.unique();
	for (auto i : l1)
		cout << i << "	";  //11 22 11 22 44

	cout << endl;

	l1.unique(func);
	for (auto i : l1)
		cout << i << "	";  //11 22 22 44

	cout << endl;

	list<int> l2{ 11,22,11,22,44 };
	l2.unique(func2);
	for (auto i : l2)
		cout << i << "	";  //11 22 44
}

由程序中可以看出来unique函数的内部实现是按照list容器中元素的存储顺序依次俩俩进行比较,来判断是否满足条件,如果满足条件,则优先删除俩个比较数中右端的数据,然后在接着下一个数进行比较

reverse:

lst.reverse()   //反转lst中元素的顺序

list<int> l1{ 11,22,33,44 };
l1.reverse();
for (auto i : l1)
	cout << i << endl;  //44 33 22 11

splice(拼接):

此算法时链表数据结构所特有的,因此不需要通用版本

lst.splice(args)
flst.splice_after(args)

args:
(p,lst2) 
//p是一个指向lst中元素的迭代器,或一个指向flst首前位置的迭代器,函数将lst2的所有元素移动到lst中p之前的位置或是flst中p之后的位置,将元素从lst2中删除,lst2的类型必须与lst或flst相同,且不能是同一个链表

(p,lst2,p2)
//p2是一个指向lst2中位置的有效的迭代器,将p2指向的元素移动到lst中,或将p2之后的元素移动到flst中,lst2可以是与lst或flst相同的链表

(p,lst2,b,e)
//b和e必须表示lst2中的合法范围,将给定范围中的元素从lst2移动到lst或flst,lst2与lst可以是相同的链表,但p不能指向给定范围中的元素

链表特有的操作会改变底层容器:
        例如remove的链表版本会删除指定元素,unique的链表版本会删除满足条件的重复元素,merge和splice会销毁其参数,通用版本的merge将合并的序列写到一个给定的目的迭代器,俩个输入序列是不变的,而链表版本的merge函数会销毁给定的链表--元素从参数指定的链表中删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值