初识顺序容器

一、顺序容器类型

顺序容器提供了元素存储。

顺序容器也提供了对元素的顺序访问,但不一定提供直接访问特定元素的能力(除了std::array和std::vector,它们提供了基于索引的直接访问)。

以下是C++ STL中主要的顺序容器类型:

  1. std::vector:

    • 动态数组,可以动态地增长和缩小。
    • 提供基于索引的快速直接访问
    • 插入和删除元素(尤其是在末尾之外的位置)可能涉及移动多个元素,速度会很慢
  2. std::deque:

    • 双端队列,支持在两端快速插入和删除元素

    • 提供基于索引的直接访问,但提供了在两端进行操作的成员函数。

    • 内部实现可能涉及多个固定大小的数组,因此可以高效地处理在两端的操作。

    • C++11及以后的版本,std::deque提供了operator[],它允许你通过索引访问元素,就像std::vector一样。但是,与std::vector不同,std::deque的operator[]不提供对容器大小的检查,所以如果你使用一个超出容器范围的索引,它可能会导致未定义的行为。

  3. std::list:

    • 双向链表,允许在任何位置快速插入和删除元素。
    • 不提供基于索引的直接访问,但提供了基于迭代器的遍历。
    • 在任何位置插入和删除元素的成本都是常数时间。
  4. std::forward_list:

    • 单向链表,类似于std::list但只提供前向迭代。
    • 由于其单向性质,它通常比std::list更节省空间。
    • 插入和删除操作与std::list相似。
  5. std::array:

    • 固定大小的数组,提供了基于索引的直接访问。
    • 由于其大小是固定的,因此不支持动态增长或缩小。
    • 通常用于需要固定大小数组的场景,并且希望获得与原生数组类似的性能。
  6. std::string:

    • 用于存储字符序列的专用容器。
    • 在很多方面与std::vector类似,但针对-字符串操作进行了优化。
    • 提供了许多方便的字符串处理成员函数。
  7. std::queue 和 std::stack:

    • 这些实际上是容器适配器,它们使用底层容器(如std::deque或std::list)来提供队列或栈的语义。
    • 它们不是独立的容器类型,但提供了队列和栈的接口。

二、如何选取容器

通常情况下,使用vector是最好的选择。

  1. 程序有很多小元素,且空间的额外开销很重要,不要使用list和forward_list。
  2. 要求随机访问元素,使用vector和deque。
  3. 从中间插入或者删除,使用list或forward_list。
  4. 头尾位置插入和删除,但不在中间进行操作,使用deque。
  5. 既要随机访问,又要在中间位置插入删除,需要综合考虑。

三、容器库

每个容器都定义在一个头文件中,文件名与类型名相同。
顺序容器几乎可以保存任意类型的元素

vector<vector<string>> lines;   //vector的vector
//lines是vector,元素的类型是string的vector。

1、容器操作:

alt text

alt text

alt text

alt text

2、迭代器

一个迭代器范围是由一组迭代器表示的。
[begin,end) 左闭右开区间,begin指向开始的第一个元素,end指向最后的元素的下一个元素。

1、构成一个迭代器范围的要求:

  • 指向同一个容器中的元素,或者是容器最后一个元素之后的位置。

  • begin通过反复递增可以达到end,end不在begin之前。

  • begin == end,范围为空

  • begin != end,范围内至少包含一个元素

2、begin和end有多个版本:

实际上有两个begin成员,一个是const成员,返回容器const_iterator类型。一个是非const成员,返回iterator类型。
rbegin、end、rend也是类似情况。

3、容器定义和初始化

alt text

3.1、将一个容器初始化为另一个容器的拷贝

  1. 直接拷贝整个容器(容器类型和元素类型都必须相等)
  2. 拷贝迭代器对指定的范围(容器类型和元素类型可以不等,只要元素类型能相互转化即可)
vector<const char*> vec = {"hello","hi"};
//list<string> list1(vec);    //容器类型不匹配
//vector<int> vec2(vec);      //元素类型不匹配

//forward_list<int> word(vec.begin(),vec.end());
//int不能转化为string类型

//const char*可以转换为string类型
forward_list<string> word2(vec.begin(),vec.end());

//string可以以转化为const char*类型
vector<string> vec2 = {"h","j","k"};
list<const char*> list2(vec2.begin(),vec2.end());

还有一种用法:

//假定迭代器it表示vec中的一个元素
//拷贝元素,直到(但是不包括)it指向的元素
deque<string> list(vec.begin(), it);

3.2、列表初始化

list<string> list1 = {"hello", "hi"};

3.3、构造函数初始化

vector<int> vec1(10, -1);   //10个int元素,都为-1

list<string> list1(10, "hello"); //初始化10个hello

forward_list<int> flist1(20);   //初始化10个0 

deque<string> vec2(10); //初始化10个空string

注意:仅顺序容器的构造函数才接受参数大小,关联容器不接受。

3.4、array类型

array类型不只是需要指定类型,还要指定大小。

//array<int> a1;        //错误:参数太少
array<int,20> a2;

array进行列表初始化,初始值的数目必须等于或者小于array的大小。

array<int, 10> a1;  //默认10个都为0
array<int, 10> a2 = {1,2,3,4,5,6,7,8,9,10};
//列表初始化
array<int, 10> a3 = {1};    //只有a3[0]是1,其他都为0

内置数组类型不能拷贝或者赋值,但是array可以

int a[10] = {0,1,2,3,4,5,6,7,8,9};
int b[10] = a;      //错误:内置数组不支持拷贝或者赋值

array<int, 10> a1 = {0,1,2,3,4,5,6,7,8,9};
array<int, 10> a2 = a1;
//正确:数组类型匹配即合法

3.5、容器赋值和swap

以下是一个表格,列出了上述几种容器操作的说明、示例代码以及使用场景:

c1 = c2 将c2覆盖c1。 c1和c2元素类型必须相同
c = 将c1中的元素替换为初始化列表中元素的拷贝 array不适用
swap(c1, c2) 交换c1和c2的元素。c1和c2必须具有相同的类型。 swap比拷贝速度快
c1.swap(c2) 同上 同上
assign操作不适用于关联容器和array

|seq.assign(b, e) | 将seq中的元素替换为迭代器b和e所表示的范围中的元素 |
|seq.assign(il) | 将seq中的元素替换为初始化列表il中的元素 |
| seq.assign(n, t) | 将seq中元素替换为n个t |

swap和赋值的区别
赋值操作会导致左边容器内部的迭代器、引用和指针都失效。swap操作将容器内容交换不会导致指向容器的迭代器、引用、指针失效。(array和string除外)

使用assign(仅限顺序容器)

assign允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。

第一个版本:
assign操作用参数指定的元素(的拷贝)替换左边容器中的元素。

list<string> list1;
vector<const char *> vec1;

list1 = vec1;       //error,类型不匹配
list1.assign(vec1.cbegin(), vec1.cend());   //可以将const char*转化为string类型

第二个版本:
assign接受一个整型值和元素值

list<string> list2(1);  //1个空string
list2.assign(10, "hello");  //10个hello

使用swap

swap交换两个相同类型容器的内容。

vector<string> vec1(10);
vector<string> vec2(20);

swap(vec1, vec2);       //交换vec1和vec2
  • 交换两个容器内容操作会很快,因为swap只是交换了两个容器内部的数据结构。
  • 元素不会被移动意味着,除了string之外,指向容器的迭代器、引用、指针在swap操作之后都不会失效。
  • swap两个array会真正的交换它们的元素,所有array使用swap的时间取决于元素的数目。
    对于array,在swap之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经和另一个array中对应元素的值进行了交换。

3.6、关系运算符

  • 两个容器大小相同,所有元素两两对应相等,则 ==。
  • 两个容器大小不同,较小容器中每个元素都等于较大容器中的对应元素,则 小容器 < 大容器。
  • 如果两个容器都不是另一个的前缀子序列,则它们比较结果取决于第一个不相等的元素的比较结果。
vector<int> v1 = {1,2,3,4,5};
vector<int> v2 = {1,2,3,4,5};
vector<int> v3 = {1,2};
vector<int> v4 = {5,4,32,6,7,4,4,32};

v1 == v2;    //容器大小相同,对应元素匹配。
v2 > v3;     //容器大小不同,v3是v2的子序列
v4 > v3;     //容器大小不同,v4的第一个不匹配的字符大于v3;

四、顺序容器操作

1、向容器中添加元素

除array之外,所有标准库容器都可以在运行时动态的删除或者添加元素来该表容器的大小。

c.push_back(t) 在c的尾部创建一个值为t或由args创建的元素。返回void
c.emplace_back(args) 在c的头部创建一个值为t或者由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的元素
c.insert(p,b,e) 将迭代器b和e指定的范围内的元素插入到迭代器p指向的元素之前。b和e不能指向c中的元素。返回指向新添加的第一个元素的迭代器。
c.insert(p,il) il是一个花括号包围的元素值列表。将这些给定值插入到迭代器p指向的元素之前。返回指向新添加的第一个元素的迭代器,否则返回空。

注意:向vector、string、array插入元素会使所有指向容器的迭代器、指针、引用失效。
在vector、string尾部之外插入,在deque的首尾之外插入元素,都需要移动元素。向一个vector或string添加元素可能引起整个对象存储空间的重新分配。

  1. 使用push_back
list<string> list1;
string word;
while(cin >> word)
{
    list.push_back(word);
}

list1的size增大了1,该元素的值为word的一个拷贝。

  1. push_front
    将元素插入到头部
list<int> list1;

for(size_t i = 0; i < 4; i++)
{
    list1.push_front(i);
}
  1. insert
    第一种insert:
list.insert(list1, "hello");
//将"hello"添加到list1之前的位置。

注意:在vector、string、deque中任何位置都是合法的,但是很耗费时间。

第二种insert:

list.insert(list1.end(), 10, "hello");
//将10个hello插入到list1的末尾的。

第三种insert:
接受一对迭代器或者一个初始化列表的insert版本

list<string> list1{"hello","hi"};
vector<string> vec1{"123", "345"};

list1.insert(list1.cend(), vec1.cbegin(),vec1.cend());

list1.insert()
posted @ 2024-05-27 02:12  baobaobashi  阅读(3)  评论(0编辑  收藏  举报