第9章 顺序容器

1、STL组件:容器、迭代器、算法。

STL组件之间的合作

2、容器用来管理一大群元素。为了适应不同应用,STL提供了不同的容器。STL容器种类

总的来说,容器可分为三大类:

  • 顺序容器(sequence container),这是一种有序(ordered)集合,其内每个元素均有确凿的位置–取决于插入时机和地点,与元素值无关。他们的排序次序将和置入次序一致。STL提供了5个定义好的顺序容器:array、vector、deque、list和forward_list。
  • 关联式容器(associative container),这是一种已排序(sorted)集合,元素位置取决于其value(或可key)和给定的某个排序准则。他们的值决定他们的次序,和插入次序无关。STL提供了4个关联式容器:set、multiset、map和multimap。
  • 无序容器(unordered container),这是一种无序(unorderd)集合,其内每个元素的位置无关紧要,唯一重要的某特定元素是否位于此集合内。元素值、安插顺序,都不影响元素的位置,而且元素的位置有可能在容器生命中被改变。

3、顺序容器定义和初始化

C c; 默认构造函数
C c1(c2) 拷贝初始化
C c1 = c2 拷贝初始化
C c{a,b,c…} 初始值列表初始化
C c = {a,b,c…} 初始值列表初始化
C c(beg,end) 迭代器初始化(array不适用)

只有顺序容器(不包括array)的构造函数才能接受大小参数

C seq(n) seq包含n个元素,这些元素进行了值初始化
C seq(n,t) seq包含n个初始化为值t的元素
  • array具有固定大小,所以迭代器初始化、接受大小参数的初始化不适用。
  • 不能对内置数组类型进行拷贝或对象赋值操作,但array并无此限制。

4、容器赋值运算

c1 = c2 拷贝
c = {a,b,c…} 初始化列表拷贝
swap(c1,c2) 交换c1和c2中的元素
c1.swap(c2) 交换c1和c2中的元素,swap通常必从c2向c1拷贝元素快得多

assign操作不适合用于关联容器和array

seq.assign(beg,end) 迭代器替换
seq.assign(initlist) 初始值列表替换
seq.assign(n,t) 替换为n个值为t的元素
  • array<T,N> 具有单独的赋值函数,c.fill(val)
  • 除array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。
  • 元素不会被移动的事实意味着,除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效,他们仍指向swap操作之前所指向的那些元素。但是,swap之后,这些元素已经属于不同的容器了。
  8 int main(){
  9     vector<string> s1 = {"a"};
 10     vector<string> s2 = {"b"};
 11 
 12     auto pos = s1.begin();
 13     s1.swap(s2);
 14 
 15     cout << s1[0] << endl;
 16     cout << *pos << endl;
 17 
 18 
 19     return 0;
 20 }

运行结果

b
a
  • 对string调用swap会导致迭代器、引用和指针失效。
  • 对于array,在swap操作之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经与另一个array中对应元素的值进行了交换。
  8 int main(){
  9     array<string,1> s1 = {"a"};
 10     array<string,1> s2 = {"b"};
 11 
 12     auto pos = s1.begin();
 13     s1.swap(s2);
 14 
 15     cout << s1[0] << endl;
 16     cout << *pos << endl;
 17 
 18 
 19     return 0;
 20 }

运行结果

b
b

5、容器大小操作

c.empty() 返回容器是否为空
c.size() 返回目前的元素个数,forward_list不支持
c.max_size() 返回元素个数的最大可能量
  • c++ standard :我们希望forward_list和你自己手写的C-style singly linked list相较之下没有任何空间或时间上的额外开销。任何性质如果与这个目标相抵触,我们就放弃该性质。所以不提供size()函数。

6、关系运算符

  • 每个容器类型都支持相等运算符(==和!=);
  • 除了无序关联容器(unordered container)外的所有容器都支持关系运算符(>、 >=、 <、 <=)。

7、添加元素

  • 这些操作会改变容器的大小;array不支持这些操作。
  • forward_list有自己专有版本的insert和emplace。
  • vector和string不支持push_front和emplace_front。
  • forward_list没有指向最末元素的锚点(anchor)。基于这个原因,forward_list不提供“用以处理最末元素”的成员函数如back()、push_back()和pop_back()。
c.push_back(t) 在c的尾部创建一个值为t或由args创建的元素,返回void
c.emplace_back(args)
c.push_front(t) 在c的头部创建一个值为t或由args创建的元素,返回void
c.emplace_front(args)
c.insert(p,t) 在迭代器p指向的元素之前创建一个值为t或由args创建的元素,返回指向新添加的元素的迭代器
c.emplace(p,args)
c.insert(p,n,t) 在迭代器p指向的元素之前插入n个值为t的元素,返回指向新添加的第一个元素的迭代器
c.insert(p,beg,end) 将迭代器beg和end指定的范围内的元素插入到迭代器p指向的元素之前,返回指向新添加的第一个元素的迭代器
c.insert(p,initlist) 将初始值列表插入到迭代器p指向的元素之前,返回指向新添加的第一个元素的迭代器
  • 当调用push或insert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。而当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素。
  • emplace函数在容器中直接构造元素。传递给emplace函数的参数必须与元素类型的构造函数相匹配。
//在c的末尾构造一个Sales_data对象
//使用三个参数的Sales_data构造函数
c.emplace_back("978-0590353403", 25, 15.99);
c.push_back("978-0590353403", 25, 15.99);   			 //错误:没有接受三个参数的push_back版本
c.push_back(Sales_data("978-0590353403", 25, 15.99)); 	 //正确:创建一个临时的Sales_data对象传递给push_back 

8、访问元素

  • at和下标操作只适合用于string、vector、deque和array。
  • forward_list不支持back()。
c.back() 返回c中尾元素的引用,若c为空,函数行为未定义
c.front() 返回c中首元素的引用,若c为空,函数行为未定义
c[n] 返回c中下标为n的元素的引用,n是一个无符号整数,若n越界,则函数行为未定义
c.at(n) 返回下标为n的元素的应用,如果下标越界,则抛出out_of_range异常
  • 在容器中访问元素的成员函数返回的都是引用。我们可以用来改变元素的值:
if (!c.empty(){
	c.front() = 42;
	auto &v = c.back();
	v = 1024;    //改变c中的元素
	auto v2 = c.back();
	v2 = 0;      //未改变c中的引用
}

9、删除元素

  • 这些操作会改变容器的大小,所以不适用于array。
  • forward_list有特殊版本的erase。
  • forward_list不支持pop_back;vector和string不支持pop_front。
c.pop_back() 删除c中尾元素,若c为空,函数行为未定义。函数返回void
c.pop_front() 删除c中首元素,若c为空,函数行为未定义。函数返回void
c.erase( p ) 删除迭代器p所指定的元素,返回一个指向被删元素之后元素的迭代器,若p指向尾元素,则返回尾后迭代器,若p是尾后迭代器,函数行为未定义
c.erase(beg,end) 删除迭代器beg和end所指定范围内的元素,返回一个指向最后一个被删元素之后元素的迭代器,若e本身就是尾后迭代器,则函数也返回尾后迭代器
c.clear() 删除c中所有的元素

10、特殊的forward_list操作

  • 当添加或删除一个元素时,删除或添加的元素之前的那个元素的后继会发生改变。但在单向链表中,没有简单的方法来获取一个元素的前驱。
  • forward_list并未定义insert、emplace和erase,而是定义了insert_after、emplace_after和erase_after的操作。
  • forward_list定了了首前(off-the-beginning)迭代器,这个迭代器允许我们在链表首元素之前并不存在的元素“之后”添加或删除元素。
lst.before_begin() 返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用
lst.cbefore_begin() 返回一个const_iterator
lst.insert_after(p,t) 在迭代器p之后的位置插入元素t
lst.insert_after(p,n,t) 在迭代器p之后的位置插入n个元素t
lst.insert_after(p,beg,end)
lst.insert_after(p,initlist)
emplace_after(p,args) 使用args在p指定的位置之后创建一个元素,返回一个指向这个新元素的迭代器,若p为尾后迭代器,则函数行为未定义
lst.erase_after( p ) 删除p指向的位置之后的元素
lst.erase_after(beg,end) 删除从beg之后直到end之前的元素
  • 当在forward_list中添加或删除元素时,我们必须关注两个迭代器–一个指向我们要处理的元素,另一个指向起前驱。
  5 int main(){
  6     forward_list<int> coll = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  7     auto pre = coll.before_begin();
  8     auto cur = coll.begin();
  9 
 10     while (cur != coll.end()){
 11         if (0 == *cur % 2){
 12             cur = coll.erase_after(pre);
 13         }
 14         else{
 15             pre = cur;
 16             ++cur;
 17         }
 18     }
 19 
 20     for (const auto &elem : coll){
 21         cout << elem << " ";
 22     }
 23     cout << endl;
 24 
 25     return 0;
 26 }

运行结果

1 3 5 7 9

11、改变容器大小

  • 可以用resize来增大或缩小容器,与往常一样,array不支持resize。
  • 如果当前大小小于所要求的大小,容器后部的元素会被删除,如果当前大小小于新大小,会将新元素添加到容器后部。
c.resize(n) 调整c的大小为n个元素
c.resize(n,t) 调整c的大小为n个元素

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

  • 向容器中添加元素和从容器中删除元素的 操作可能会使容器元素的指针、引用或迭代器失效。一个失效的指针、引用或迭代器将不再表示任何元素。
  • 使用失效的指针、引用或迭代器是一种严重的程序设计错误,很可能引起与未初始化指针一样的问题。
向容器添加元素后:
  • 如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效。
  • 对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
  • 对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。
删除元素后:
  • 指向删除元素的迭代器、指针和引用会失效。
  • 对于list和forward_list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍有效。
  • 对于deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、引用或指针也会失效。如果是删除deque的尾元素,则尾后迭代器也会失效,但其他迭代器、引用和指针不受影响;如果是删除首元素,这些也不会受影响。
  • 对于vector和string,指向被删元素之前元素的迭代器、引用和指针仍有效。
  • 当我们删除元素时,尾后迭代器总是会失效。
example 1
  //循环内,删除偶数元素,复制每个奇数元素
  5 int main(){
  6     vector<int> coll = {0,1,2,3,4,5,6,7,8,9};
  7 
  8     auto pos = coll.begin();
  9 
 10     while (pos != coll.end()){
 11         if (*pos % 2){
 12             pos = coll.insert(pos, *pos);
 13             pos += 2;
 14         }
 15         else{
 16             pos = coll.erase(pos);
 17         }
 18     }
 19 
 20     for (const auto &elem : coll){
 21         cout << elem << " ";
 22     }
 24	    cout << endl;
 23 
 24     return 0;
 25 }

运行结果

1 1 3 3 5 5 7 7 9 9 
example 2:不要保存end返回的迭代器
  5 //灾难:此循环的行为是未定义的
  6 auto begin = v.begin(),
  7      end = v.end();  //保存尾迭代器的值是一个坏主意
  8 
  9 while (begin != end){
 10     ++begin;  //向前移动begin,因为我们想在此元素之后插入元素
 11     begin = v.insert(begin, 32);  //插入新值
 12     ++begin;  //向前移动begin跳过我们刚刚加入的元素
 13 }   

猜你喜欢

转载自blog.csdn.net/weixin_42205011/article/details/87182638