C++STL——顺序容器

目录

顺序容器

一、顺序容器类型

二、顺序容器的一些特性

三、顺序容器的公有内容

一、迭代器

二、顺序容器的定义和初始化

默认构造函数*:

拷贝构造函数:

迭代器初始化* :

初始化列表:

三、赋值和swap

初始化列表赋值*: 

swap:

顺序容器assign*:

四、 容器大小操作

 五、关系运算符

六、容器的遍历

四、顺序容器的操作

一、向顺序容器添加元素*

一、push_back()

二、push_front()

三、insert()——特定位置添加元素

3.1.1迭代器失效问题:

四、emplace操作

二、访问元素

三、删除容器中的元素*

四、forward_list操作

五、改变容器大小*

五、vector容器的增长

六、部分泛型算法

一、find

二、accumulate

三、sort

四、reverse

X、与类相关

 Y、顺序容器的各异

一、string

二、forward_list

三、array 


顺序容器

都在std名字空间中,以下都省略std名字空间的导入等。

         “容器均定义为模板类,我们必须提供额外信息来生成特定的容器类型。顺序容器几乎可以保存任意类型的元素。所有容器类都共享公共的接口,不同容器按不同方式对其进行扩展。这个公共接口使容器的学习更加容易——我们基于某种容器所学习的内容也都适用于其他容器。每种容器都提供了不同的性能和功能的权衡。”

        “一个容器就是一些特定类型对象的集合。顺序容器( sequential container)为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。与之相对的,我们将在第11章介绍的有序和无序关联容器,则根据关键字的值来存储元素。”

042c51b737704ed0bdf8060d964e6af4.png

一、顺序容器类型

  • vector:可变大小数组。支持快速随机访问。尾部之外的位置插入或删除元素可能很慢。
  • deque:双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。
  • string:与vector相似的容器,但专门用于保存字符随机访问快。在尾部插入/删除速度快
  • list:双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。
  • forward_list:单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快
  • array:固定大小数组。支持快速随机访问。不能添加或删除元素。array当做普通数组来用就行,因为它比较特殊,很多其他容器共有的操作,它没有,比如大小参数初始化,列表赋值,迭代器初始化等。不如记忆它比普通数组多了什么,它可以直接被赋值和初始化(拷贝构造),可以使用swap。和普通数组一样有的,列表初始化,剩余部分初始化为0,随机访问等。只有array是固定数组的,因此只有它在可能导致赋值大小和被赋值大小不等时的操作不被允许。

        一些普通操作中,注意容器的区别,体会为什么有的容器有这样的操作,有的容器没有。但大体上是一致的。

stack、queue和priority_queue是顺序容器适配器,这里并不讨论他们。他们的操作与顺序容器有一些区别。比如它们是push()操作添加元素,而顺序容器是push_back()、push_front()等。

二、顺序容器的一些特性

  • string和vector 将元素保存在连续的内存空间中。这也是为什么在中间添加或删除元素非常耗时!
  • 在deque 的两端添加或删除元素都是很快的,与list 或 forward_list 添加删除元素的速度相当。
  • forward_list 的设计目标是 达到与最好的手写的单向链表数据结构相当的性能。因此,forward_list 没有 size 操作,因为保存或计算其大小就会比手写链表多出额外的开销。
  • 对其他容器而言,size 保证是一个快速的常量时间的操作。
  • 由于vector在中间添加元素非常耗时,如果必须在中间位置插入元素,考虑在输入阶段使用list,一旦输入完成,将 list中的内容拷贝到一个 vector 中。

三、顺序容器的公有内容

一、迭代器

        iterator是嵌套在容器类型中的迭代器类型,vector<int>::iterator是指向vector<int>中元素类型的迭代器类型,vector<int>::iterator it 是定义一个vector<int>的迭代器类型对象it。

2a6941b03e7c4061a6e2f46e10fd6b5a.png

iterator:正序迭代器,可读可写

const_iterator:常数正序迭代器,可读不可写

reverse_iterator:逆序迭代器,必须指向的是逆序迭代器。

const_reverse_iterator:乘数逆序迭代器,可读不可写。

迭代器和指针非常像,迭代器使用*来访问元素,用++来实现迭代器指向向后移动,--实现向前移动(前和后相对于正逆序不一样)但是确定迭代器的指向并不是取地址。但注意对于非顺序容器,比如forward_list、set和map,它们的迭代器不支持--操作,也不支持-操作,因为其迭代器是前向迭代器(Forward Iterator)而非双向迭代器(Bidirectional Iterator),只支持向前移动。

begin():指向正序迭代器首元素位置的迭代器

end():指向正序迭代器末尾元素位置的迭代器

rbegin():指向逆序迭代器首元素位置的迭代器

rend():指向逆序迭代器末尾元素位置的迭代器

cbegin():指向正序迭代器首元素位置的常值迭代器//不需要写时,养成好习惯用const

cend():指向正序迭代器末尾元素位置的常值迭代器

crbegin():指向逆序迭代器首元素位置的常值迭代器

crend():指向逆序迭代器末尾元素位置的常值迭代器

//所有容器均有的迭代器!这里使用vector举例
#include<vector>
vector<int> a(100,0);

//确定迭代器指向,并不是 &a[2] 而是使用首迭代器偏移:a.begin()+2

vector<int>::iterator v_it=a.begin()+2; 
while(v_it!=a.end()){
    *v_it=1;//非常数迭代器 可以修改值
    ++v_it;//正序迭代器++操作,往a.end()走
}

vector<int>::const_iterator v_it2=a.begin()+2; //常数迭代器不可修改值
while(v_it2!=a.end()) ++v_it2;//正序迭代器++操作,往a.end()走

vector<int>::reverse_iterator v_rit=a.rbegin()+3;//反序迭代器, 实际上是指向a.begin()+100-3
while(v_rit!=a.rend()) ++v_rit//反序遍历

auto it1=a.begin()+2;
auto it2=a.cbegin()+2;
auto it3=a.rbegin()+2;
auto it4=a.rend()+2;
//auto 会自动对应 对应类型的迭代器 的类型
#include<deque>
deque<int> q(10,0);
deque<int>::iterator it=q.rbegin();
while(it!=q.rend()){
    cout<<*it;
    ++it;
}

特殊一点的:

#include<map>
map<int,int> hmap;
map<int,int>::iterator it=hmap.begin();
while(it!=hmap.end()){
    it->second=1;
    (*it).second=1;
    ++it;
}
vector<int> a={12,412,3,123,3};
auto i=a.end();
i-=1;//顺序容器的迭代器支持 + 和 -、++、--操作
unordered_map<int,int> b;
b[1]=1;
auto it=b.begin();
++it;//无序迭代器仅支持++操作
//it+=1; 错的
//++it;错的
//auto it=b.end()-1;错的

df4b7368a00c438d97225e4110041146.png

二、顺序容器的定义和初始化
默认构造函数*:

300ee68154864fbabc06075ee20eae5e.png

        每个顺序容器类型都定义了一个默认构造函数。除array之外,其他顺序容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。

array则是在容器类型中指定容器大小的。默认构造函数的指定容器大小和元素初始值的参数对于string而言必须是同时存在的,不能只给定其中一个。

#include<bits/stdc++.h>
vector<int> a(50,0);
vector<int> b(50,1);
deque<int> q(100,2);
list<string> lst(100,"hi");
forward_list<int> f_lst(101,0x3f3f3f3f);
string s(10,'a');
//前者为容器大小,后者为容器元素的初值
//若需要这样初始化,除了string之外,其余的都可以后者不写,会自动赋初值为0。
vector<int> a(50);
vector<int> b(50);
deque<int> q(100);
list<string> lst(100);//全为空string
forward_list<int> f_lst(101);

array<int,100> ay;//前者为容器的元素类型,后者为数组大小

vector<int> a;
vector<int> b;
deque<int> q;
list<int> lst;
forward_list<int> f_lst;
string s;
//一堆空容器,size()==0
vector<vector<int>> dp(nums.size(),vector<int>(100,0));
//vector<int>(100,0) 是一个没有名字的临时对象!
//这也是一个对象,只是没有名字。这个对象的值被指定为作为dp的第二维的值

//复习一下类:
class A{
public:
    A(int n){}
    A() {}
}

A();//在没有函数声明的情况下,构造一个临时对象
A(2);//按需求构造一个临时对象

由于只有array是固定大小且指定唯一大小的,array不能接受大小参数初始化,不能用迭代器初始化。可以拷贝构造和列表初始化。

拷贝构造函数:

相同的容器类型,且保存的是相同的元素类型;对于array类型,两者还必须具有相同大小)

deque<pair<int,int> >q(100);
deque<pair<int,int> >q2(q);
deque<pair<int,int> >q3=q;
//除array之外的拷贝构造,只需要类型相同

array<int,100> b;
b[1]=1000;
array<int,100> c=b;
//array的拷贝构造,还需要数组大小相同
迭代器初始化* :

特别注意特别注意!!迭代器范围进行赋值,初始化等,赋值后的容器一定不为空! 所以如果可能为空则会出现问题,原因如下:

        迭代器初始化给定一个范围,或者说任何使用到迭代器范围的,都是左闭右开,那么这个时候就存在一个问题,不能让左迭代器等于右迭代器!如果左迭代器不等于右迭代器则相当于赋值后的容器一定不为空。

        平常我们可能注意不到,因为直接就q.assgin(it,i.end()); 但是如果it等于end()的话程序会异常结束!所以最好在可能出现空的情况下给出判断!

C c(b,e):c初始化为迭代器b和e指定范围中的元素的拷贝。范围中元素的类型必须与c的元素类型相容(array不适用)。b和e可以是任何迭代器类型对象。

它会从b指向的元素开始依次拷贝入c,然后++,直到在e处停止(e不拷贝)。容器不是字符串,不用结束符~(这和拷贝构造不一样,这是元素的拷贝,而不是对象的拷贝)

deque<pair<int,int> >q(1);
deque<pair<int,int> >q2(q);

deque<pair<int,int> > q4(q.begin(),q2.end());//totally wrong,编译没错,运行错误!
deque<pair<int,int> > q4(q.begin(),q.end()-1);//bingo

list<int> lst(100);
vector<double> a(lst.begin(),lst.end());//bingo! 因为是元素的拷贝,而不是对象的拷贝,lst中的元素拷贝进a中
forward_list<int> flst(a.begin(),a.end());//bingo! 因为是元素的拷贝,而不是对象的拷贝,lst中的元素拷贝进a中
初始化列表:

874f891e750149c8bacfa6adaf0f9e8a.png

array<int,100> a={1,2,5}; //其余为0
vector<int> b={1,2,5,4};//初始化后b.size()==4
vector<int> b2{1,2,5,4};//初始化后b.size()==4
forward_list<int> flst{11,5,6};
vector<vector<int> > b3(100,vector<int>{1,2,3});

vector<int> A(){
    return {1,2};//隐类型转换
    return vector<int>{1,2};//列表初始化的匿名对象
} 

plus:

526da3945c3f4f068fb08db425001284.png a7a76177bfbf4f7ab046c47e37ecaf49.png

三、赋值和swap

赋值:

实际上就是类对象的赋值,满足类型相同即可。(当然array的类型相同是长度和元素类型都相同。)

deque<pair<int,int> >q2(100,{1,1});
deque<pair<int,int> >q3=q2;//拷贝构造函数
q3=q2;//赋值函数
q3=q3;//赋值函数
vector<array<int,100>> q5(10,{1});
//你会发现array<int,100>={1},使用的是列表初始化,而不是列表赋值,array的列表赋值不被允许
vector<array<int,100>> q6;
q6=q5;
初始化列表赋值*: 

类似拷贝构造时的列表初始化,不过array此时不再适用,列表初始化大小导致容器的大小唯一确定。

deque<int> q;
q={6,5,0};
vector<deque<int>> v;
v={{},{1,2}};//deque也是一个容器,因此在列表初始化中的元素必须至少是空集合{}

forward_list<int> flst{1,2,3,5};
flst={1,2,5,4,6,7,8};
swap:

4190e62924e642d7ab4a50f8fcec8bbf.png

984a27aa8be64b1c88d4c334985f3cf3.png

3900496a8d244c348d2e0e62eaf0ac1c.png

可以使用容器中定义的swap实例方法,也可以直接使用非成员的swap函数(建议)

array<int,100> a;
array<int,100> b={12};
a.swap(b);//a和b的类型必须一样
swap(a,b);

list<int> a={1,2,34,5,6,2};
list<int> b;
swap(a,b);
a.swap(b);

map<int,string> a;
map<int,string> b;
swap(a,b);
a.swap(b);

359e32c93160465aadb05c9b3f596b2a.png

因为赋值和拷贝构造实际上是把 一个容器中的元素拷贝进另一个容器,而swap仅仅是交换两个容器的数据结构,而没有变化元素位置。

顺序容器assign*:

如果使用迭代器替换容器的元素,迭代器不能指向该容器。

22a92872aae84066844902ac4f5e7dcb.png

assign和初始化差不多,有迭代器,大小参数,列表三种。

forward_list<int> lst(10,2);
deque<double> q;
q.assign(lst.cbegin(),lst.cend());
q.assign(10,2);
q.assign({1,2,3,5});//等价于q={1,2,3,4,5}; 
四、 容器大小操作

0756a950b5914fd2a54b2619dc9e40cb.png

size()是无符号整型,在于负数比较时会出大问题。

 五、关系运算符

e7413dacb76d4bea932a536d48730b66.png

六、容器的遍历

顺序容器(包括内置数组)都可以使用for(auto i:container){} 的方式遍历,非顺序容器只能采用迭代器遍历。

list<string> lst;
deque<string> q;
for(auto i:lst) {
    cout<<i<<endl;
}
for(const auto & i:q) {//常值引用
    cout<<i<<endl;
}

for(int i=0;i<q.size();++i){//采用下标遍历:支持快速随机访问的deque,string,vector,array可以
    cout<<q[i]<<endl;
}

四、顺序容器的操作

注意事项:

①forward_list无法直接访问到末尾元素,因此没有push_back()、pop_back()、back()函数,其insert()和erase(),emplace()也有自己的版本。

②构造函数迭代器初始化,assign(),erase()和insert()迭代器指定的元素范围,都是前闭后开的,比如a.assign(c.begin(),c.end()),c.end()并不会被拷贝进a,这样是不是就很容易记忆了。

③除了erase()之外,assign()和insert()迭代器指定的位置都不能是本身。

如a.insert(a.begin(),a.begin(),a.end())迭代器指定想要插入的元素范围是a本身是错误的。

④erase删除后返回的是删除的最后一个元素的后一个位置的迭代器,insert插入后返回的是插入的所有元素中的第一个元素,指定的是需要在哪个位置前面插入。

一、向顺序容器添加元素*

        这里需要注意容量capacity和大小size的区别,比如说当分配元素超出了capacity会导致重新分配内存,而size才是真实存储的数据,相当于一般capacity为预测大小(预留的大小,可能为初始大小的一倍,但真的真的不一定),当size超过capacity时导致拷贝,原来的所有迭代器失效。


注意forward_list具有特殊的删除和添加操作,这里的push_back()、erase等对forward_list不适用,要单独学。

一、push_back()

除forward_list和array外的顺序容器,向容器尾部添加一个元素。(因为其他元素都能在尾部添加一个元素,而forwar_list有自己的添加方式)

    list<string> lst;
    vector<string> vec;
    deque<string> q;
    string s;
    char  a[]={'I',' ','L','O','V','E',' ','C','+','+'};
    for(auto i:a) {
        s.push_back(i);
        lst.push_back(s);
        vec.push_back(s);
        q.push_back(s);
    }
二、push_front()

双端队列可以在队头添加元素,链表也可以在列表首部添加元素,因此,list、forward_list和deque容器支持push_front()操作

    list<string> lst;
    forward_list<string> flst;
    deque<string> q;
    string s;
    char  a[]={'I',' ','L','O','V','E',' ','C','+','+'};
    for(auto i:a) {
        s.push_back(i);
        lst.push_front(s);
        flst.push_front(s);
        q.push_front(s);
    }

/*
I LOVE C++
I LOVE C+
I LOVE C
I LOVE
I LOVE
I LOV
I LO
I L
I
I
*/
三、insert()——特定位置添加元素

        insert()允许插到容器的任意位置,这包含了push_back()和push_front()的功能,并且vector,list,deque,string都支持,forward_list也有自己的版本。

值得注意的是,在vector、string、deque的中间位置插入元素很耗时。

添加单个值:

        

//和非顺序容器不同,顺序容器的insert()接受两个参数

insert(iterator,value);
iterator是正序迭代器,value是指定的值
insert操作将value插入到迭代器所指定位置的前一个位置
这是有好处的,比如插入到end()、begin()的前一个位置!就和push_back(),push_front()功能一样了
    list<string> lst;
    deque<string> q;
    vector<string> vec;
    string s;
    char  a[]={'I',' ','L','O','V','E',' ','C','+','+'};
    for(auto i:a) {
        s.push_back(i);
        lst.insert(lst.end(),s);
        vec.insert(vec.begin(),s);
        q.insert(q.begin()+q.size(),s);
    }
    for(auto i:q) {
        cout<<i<<endl;
    }

添加指定范围内元素:

insert(iterator,range);
iterator正序迭代器,range是范围

这里的指定范围内元素 和 容器的构造函数非常类似,包括:

①指定参数大小

②迭代器范围

③初始化列表

并且同样的,迭代器不能给定的是本容器的迭代器范围

    list<string> lst;
    vector<string> vec;
    string s;
    char  a[]={'I',' ','L','O','V','E',' ','C','+','+'};
    for(auto i:a) {
        s.push_back(i);
        lst.insert(lst.end(),s);
        vec.insert(vec.end(),s);
    }
    lst.insert(lst.begin(),vec.end()-2,vec.end());
    for(auto &i:lst) {
        cout<<i<<endl;
    }
/*添加后的顺序 和 迭代器范围的顺序是一样的
I LOVE C+
I LOVE C++
I
I
I L
I LO
I LOV
I LOVE
I LOVE
I LOVE C
I LOVE C+
I LOVE C++
*/
    vec.insert(vec.end(),1,"Yorelee");
    vec.insert(vec.cend(),{"Mary"});
    lst.insert(lst.cbegin(),vec.end()-4,vec.end());
    for(auto &i:lst) {
        cout<<i<<endl;
    }
/*
I LOVE C+
I LOVE C++
Yorelee
Mary
I
I
I L
I LO
I LOV
I LOVE
I LOVE
I LOVE C
I LOVE C+
I LOVE C++
*/

insert返回值:

insert会返回一个迭代器,该迭代器指向的是 容器插入的元素中 从左向右第一个元素。

这样我们可以通过不断的给一个迭代器位置赋值,实现不断的向同一个位置不断往前插入元素。下图展示的是不断往前面插入元素。

3.1.1迭代器失效问题:

不断往后插入元素却不用这样做:实际上是因为iter一直指向lst.begin(),刚开始lst.begin()实际上等于lst.end(),list在添加元素之后的迭代器是不会失效的;而往前添加虽然原来的迭代器没失效,但是begin()变化了,导致程序语法没错,但是你要实现的东西错了,因此需要不断更新。

    auto iter=lst.end();
    for(auto i:a){
        s.push_back(i);
        lst.insert(iter,s);
        if(iter==lst.end()) cout<<1<<endl;//不断输出1
    }

力扣hot100:416.分割等和子集(组合/动态规划/STL问题)-CSDN博客​​​​​ 

中提到过,不同容器在插入和删除时,迭代器失效的情况是不一样的。我们最保险的是防止插入或删除之后使用原来的迭代器!!

        list和forward_list在添加元素时,原来的元素存储位置不会变化,因此地址(迭代器)也不会变化,但是别人又不一样了。deque在首尾位置添加元素时,迭代器失效,比如begin()会变,那么begin()+1是原来的begin()!

四、emplace操作

emplace_front()、emplace()、emplace_back() 分别对应

push_front()、insert()、push_back();

emplace和他们的区别是:emplace接受的参数是容器中存放的元素类型的构造函数的参数(包括拷贝构造),它直接在容器管理的内存空间中利用参数直接构造元素。而他们是将对象拷贝到容器中。并且貌似emplace只能插入一个值。

c.emplace_back("978-0590353403",25,15.99);
c.emplace_back();//调用Sales_data的默认构造函数,在容器中直接构造
c.emplace_back(Sales_data("978-0590353403",25,15.99));//拷贝构造
c.push_back(Sales_data("978-0590353403",25,15.99));//构造一个临时对象,再将临时对象拷贝进容器

使用emplace_back直接构造,会比先构造再拷贝进容器中的方式要快。

#include<bits/stdc++.h>
using namespace std;
class A {
public:
    A(int a,int b,int c){}
};
int main(void){
    vector<A> vec;
    A a(1,2,3);
    vec.emplace(vec.begin(),2,2,5);//插入单个值
    vec.emplace(vec.begin(),a);//插入单个值,插入多个值似乎不太行
    return 0;
}
二、访问元素

back()、front()、下标访问和at()访问,它们四个都是返回的元素引用。

其中任何顺序容器除了forward_list没有back()之外(因为单链表不能直接访问到末尾元素,不然它和双链表就没区别了),都可以使用back()和front()直接返回首尾元素的引用。

//非空情况下,若为空容器,以下操作行为未定义,会导致程序崩溃
c.front()  <==>  *c.begin()
c.back()   <==>  *(c.end()-1)//前向迭代器无,即forward_list无

下标访问和at()访问只在能够随机访问元素的容器中使用,即vector,string,deque,array。

vector<int> vec={1,3,5,4};
cout<<vec[0]<<endl;
cout<<vec.at(0)<<endl;
//下标访问越界时,导致程序崩溃,没有报错信息
    vector<int> vec={};
    cout<<vec[0]<<endl;
//进程已结束,退出代码为 -1073741819 (0xC0000005)
//at()访问越界,抛出 out_of_range异常(如果用这个的话你就知道程序的问题是容器访问越界了)
    vector<int> vec={};
    cout<<vec.at(0)<<endl;
/*
terminate called after throwing an instance of 'std::out_of_range'
  what():  vector::_M_range_check: __n (which is 0) >= this->size() (which is 0)
*/

返回引用说明:

array<int,100> a;
a.front()=42;
a.back()=13;
int & temp=a.front();
temp=2;//修改a中的首元素
auto tmp=a.front();
tmp=2;//并不修改a中的首元素,因为tmp是一个变量不是引用。
int & temp2=a.at(0);
temp2=42;//修改a中的首元素
三、删除容器中的元素*

        删除元素主要是pop_back()、pop_front()、erase()和clear(),除了erase()之外函数返回的都是void。

        pop_back()和pop_front() 是和push_back()、push_front()相对应的,有一个就会有另一个。

        insert()和erase()对应,不过区别在于insert()需要指定插入位置,再指定插入元素,而erase()只需要指定删除元素即可。erase删除后返回的是删除的最后一个元素的后一个位置的迭代器,insert插入后返回的是插入的所有元素中的第一个元素,指定的是需要在哪个位置前面插入。

clear()是除array之外的所有容器都有的,直接清空所有元素。

同样的forward_list没有pop_back(),有自己版本的erase()。

deque<int> q={0,1,2,3,4,5};
auto it=q.begin();
auto it2=q.begin()+1;
q.erase(it);
cout<<*it2<<endl;  //输出1

vector<int> vec={0,1,2,3,4,5};
auto it3=vec.begin();
auto it4=vec.begin()+1;
vec.erase(it3);//it4迭代器失效
cout<<*it4;  //输出2

erase():

vector<int> vec;
vec.insert(vec.cbegin(),10,2);
vec.erase(vec.cbegin());
vec.erase(vec.begin(),vec.end());
vec.clear();
for(int i=0;i<=50;++i){
    vec.emplace(vec.end(),i);//emplace和insert相同,在指定位置前插入。//vec.push_back(i);vec.emplace_back(i);
}
vec.erase(vec.begin()+10,vec.cend());

auto it=vec.begin();//auto & it=vec.begin()是错误的,因为begin()迭代器本身是一个常值
while(it!=vec.end()){
    if(*it%2==0) {
        it=vec.erase(it);//删除偶数,并返回的是指向 删除位置之后的 迭代器
    }else ++it;
}

for(auto i:vec) cout<<i<<endl;
/*
1
3
5
7
9

*/
四、forward_list操作

        之前我们所讨论的insert()将插入指定位置之前 并返回从左往右第一个元素;erase()将删除指定范围内的元素并返回指向删除范围的后面那个元素的迭代器。

        但单链表不一样,单链表在删除时会改变删除元素前驱的指向,所以必须知道删除元素的前驱,但是单链表又不能直接找到该位置(双链表可以做到因此list使用的是insert和erase),所以单链表的插入删除需要特殊的版本。该版本是指 在给定元素之后删除或添加元素。因此forward_list也给出了一个首前迭代器。

        当在 forward_list 中添加或删除元素时,我们必须关注两个迭代器——一个指向
我们要处理的元素,另一个指向其前驱。前驱用来给操作指定参数,以便于删除要处理的元素,要处理的元素用来判断是否该位置需要被删除。
  • insert_after:之前的insert()的返回是第一个元素,是不断往前加的;而insert_after返回返回的是插入的最后一个元素,是不断往后加的。实际上也可以理解,因为指定位置你是知道的,而插入之后不知道的头是返回的。
  • emplace_after
  • erase_after:返回指向删除元素之后的迭代器
  • before_begin()迭代器:首前迭代器,指向第一个元素之前,相当于哨兵结点(但实际上和end()一样),用于在前面插入或删除元素。也有cbefore_begin()版本。

五、改变容器大小*

resize():注意给定的新元素值必须是一个对象,而不能是参数。如果不给定新元素的值,那么对应元素类型必须提供类型的默认构造函数。

vector<pair<int,int>> a;
a.emplace_back(1,2);//传入的是构造函数的参数
a.resize(10,pair<int,int>(12,2));//传入的对象
a.resize(12,{1,2});//传入的对象(无名对象)

五、vector容器的增长

        reserve()改变的是容量capacity()的大小,而不会改变size()的大小。reserve()只能让容器的容量增加,当reserve指定的容量比capacity小时,容器不会发生任何变化,当reserve指定的容量比capacity大时,容器的容量至少为reverse指定的容量。同样的resize也不会减少容器的容量,只能增大容器的容量。

不过STL应当是面向对象的抽象接口,不需要在意那么多细节:用就完事

六、部分泛型算法

  • 大多数算法都定义在头文件algorithm中。标准库还在头文件numeric中定义了一组数值泛型算法。
  • 迭代器指定的范围,只要能遍历就可以,对容器没有要求。也可以是反向迭代器。
  • 反向迭代器似乎不会影响reverse和accumulate,对find的影响也可能只是返回的第一个数的位置不一样,但是使用反向迭代器对sort的影响最明显,如果使用反向迭代器输入sort,sort会默认使得反向迭代器范围是升序的,但是正序观察实际上是降序了。
一、find
#include<algorithm>
find(l,r,val);
l和r是迭代器,表示的是迭代器范围:左闭右开,val是想要查找的元素,返回值是一个迭代器,注意val的类型必须与容器中元素的类型相容~
    如果找到了则返回第一个指向val值的迭代器;
    如果范围中无匹配元素,则find返回第二个参数来表示搜索失败。因此,我们可以通过比较返
回值和第二个参数来判断搜索是否成功。
    我们可以用同样的 find函数在任何容器中查找值。
#include<iostream>
#include<algorithm>
#include <vector>
#include<vector>
#include<deque>
#include<list>
#include<string>
#include<forward_list>
#include<array>
#include <utility>//包含pair
int main(void) {
    std::vector<int > vec={11,5,7,8,8,8};
    std::deque<std::pair<int,int> > q={{1,2},{3,4},{5,6}};
    std::list<int> lst={8,9,7,5,4};
    std::string s(10,'a');
    std::forward_list<int> flst={1,2,3,4};
    std::array<int,100> a;
    auto it=find(q.begin(),q.begin()+2,std::pair<int,int>{2,3});
    if(it==q.end()) std::cout<<1;//不输出,因为没找到返回的是q.begin()+2哦,不等于end()
    return 0;
}
string s(10,'a');
auto it=find(s.begin()+1,s.end()-1,'b');//string也只能找元素,找子串需要用string::find
if(it==s.end()-1) cout<<1;/没找到'b' 成功输出
array<int,100> a;
auto it=find(a.begin(),a.end(),5);
if(it==a.end()) cout<<1;
二、accumulate
#include<numeric>

accumulate(l,r,init);
l和r指定迭代器返回,init表示初始化值,该算法会将l到r中的元素依次与init相加,返回最后的值。
init是可以和l到r中指定元素相加的任何类型
array<string,10> a={"1","2","5","2"};
string s=accumulate(a.cbegin(),a.cend(),string(""));
cout<<s;//1252
三、sort

sort的第三个模板要与priority_queue区分开来,sort可以直接使用排序函数。但是priority_queue 的第三个模板参数需要的是一个比较类(或称为函数对象类型),而不是一个普通函数。

#include<algorithm>
sort()仅适用于能快速随机访问的容器:deque、vector、string、array
sort(l,r,cmp);
sort(l,r);
l,r指定需要排序的容器范围,也可以是数组l是数组首地址,r是数组排序范围的尾部地址,cmp指定排序函数,或者函数对象(仿函数)
存在定义大小关系的类型默认为升序。
排序之后原容器 所给定的范围 将被修改成顺序排列
l和r不能是const_iterator类型的迭代器

//用正序迭代器可以实现升序
//用反向迭代器可以实现降序!~
deque<int> q={8,5,6,4,7,1,5,3};
sort(q.begin(),q.begin()+3);
for(auto i:q) cout<<i<<' ';//5 6 8 4 7 1 5 3

array<int,9> q={8,5,6,4,7,1,5,3};
sort(q.begin(),q.begin()+4);
for(auto i:q) cout<<i<<' ';//4 5 6 8 7 1 5 3 0
sort(q.rbegin(),q.rend());
for(auto i:q) cout<<i<<' ';//8 7 6 5 5 4 3 1 0
自定义结构体:
struct Student {
    char name[11];
    int solve;
    int time;
}p[10000];
bool cmp(const Student& a, const Student& b)
{
    if (a.solve != b.solve)
     { 
        if (a.solve > b.solve)
            return true;
        else
            return false;
    }
    else if (a.time != b.time)
           return a.time < b.time;
    else
          return (strcmp(a.name, b.name) < 0);
}
sort(q,q+n,cmp);
四、reverse
#include<algorithm>
reverse()仅适用于 能快速随机访问的容器:deque、vector、string、array 和 list
reverse(l,r);
l,r指定需要排序的容器范围
翻转原容器 所给定的范围
l和r不能是const_iterator类型的迭代器
对于list而言,若要翻转某一部分需要将迭代器移动到那个位置,不能直接快速随机访问
如:
    list<int> q={8,5,6,4,7,1,5,3};
    auto it=q.begin();
    advance(it,4);
    reverse(q.begin(),it);

X、与类相关

①指针常量  和 常量的指针。

        与指针不一样,值能不能被修改看的是const_iterator还是iterator类型(auto自动识别这俩)。而除此之外加的const都表示变量不能修改,即不能更改指向。并且const auto或auto const指的都是,该变量的值不能被更改!常量/指向不能修改的指针。const int * 能指明指针指向的值不能被修改,而auto不能指明,auto只能指明指针指向的内容能不能被修改。auto要指明指向的值能不能被修改,需要看赋值的类型。

(1)指针常量指的是,这个指针不能指向其他人,但是指向的内容可以被更改:

值得注意的是begin()返回的迭代器是一个指向不能被修改的迭代器。因此一旦被引用,则一定要被指明是迭代器常量。

const auto & it=b.begin();//引用必须是const

const auto  it=b.begin();it是一个常迭代器,只能指向一个人

*it=2;//但值可以被更改

(2)常量的指针 指的是,指向的内容不能被更改,只能读:

auto it=b.cbegin();//vector<int>::const_iterator it;

const auto it=b.cbegin();//vector<int>::const_iterator const it;


1.const int * it、2.int  * const it、3.const int * const it;

1.修饰的是指针,因此指向的内容不能被修改

2.修饰的是变量it,因此指针变量不能被修改。

3.既不能修改指针,也不能修改指针指向的值。

int b;
int * pb=&b;
int * & it2=pb;//它可以更改pb的指向
int * & it=&b; //这样是错误的,因为&a是一个不能更改指向的指针,引用对象不能更改它,因此:int * const & it=&a才是正确的
vector<int >b={1,2,3,4};
const auto it=b.begin();//const 表示迭代器对象不能被修改,引用也指的是迭代器对象只能指向这一个
auto & it=b.begin();
auto & it=b.cbegin();
auto it=b.cbegin();

注意事项:

        从begin()函数的实现可以看出,begin()返回的是一个临时迭代器对象(临时的返回值,语句结束对象就被销毁掉了),临时迭代器对象的指向不能更改的,因此需要被引用必须是container::iterator const & it=x.begin();这涉及到的是基础知识,本质跟STL无关了。

        如果不被引用,实际上container::iterator it=x.begin();表示的是it的值被 赋值为begin()指向的位置,但是变量it可以更改指向。

vector<int >b={1,2,3,4};
auto it=b.begin();
cout<<*(++b.begin())<<endl;//在临时对象上,实现++操作,要看++运算符怎么实现的,实际上临时对象本身是不能被自增的,而应该是++重载之后 返回值不一样了,语句结束后临时对象就没了
cout<<*(++it);
//都输出2,但是it会被修改,而b.begin()不会被修改

int a=1;
int * func() {
    return &a;
}
int main(void){
    int * const & i=func();//本质是一样的。这里引用必须const
    return 0;
}

 Y、顺序容器的各异

一、string
  • 容器的初始化:string不接受 只指定大小的初始化,它必须同时给出初始化值。但它可以resize时,只给定大小,新元素值为'\0';
string s;
string t(10,'a');
二、forward_list

单链表和其他人的主要区别在于添加和删除元素,因此它添加和删除元素和别人会存在差别,并且它不能直接访问尾元素。具体添加和删除元素的操作在 "四.四" 中说明了。

它所没有的:

  • back()、push_back、pop_back、insert、emplace、erase

它有的:

  • 默认构造函数指定大小、拷贝构造、迭代器初始化、初始化列表、初始化列表赋值、swap、assign、for遍历、before_begin()、insert_after、emplace_after、erase_after、resize
三、array 

文中标有*号的,都是array所没有的。array不能修改大小,因此以下都没有:

  • push_front、pop_front、push_back、pop_back、erase、insert、emplace、resize、初始化时给定大小和值、assign、初始化列表赋值、resize

以下是它有的:

  • swap、初始化列表初始化、front()、back()、at()、下标访问、for遍历
  • 48
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C++ STL(Standard Template Library)是C++标准库中的一个重要组成部分,提供了一组数据结构和算法的模板类,可以大大简化C++程序的开发过程。STL包含了多个容器类,每个容器类都有其特定的特性和用途。 STL中的容器类主要分为序列容器和关联容器两大类。序列容器包括vector、list、deque和array,它们按照元素在容器中的位置进行存储和访问。关联容器包括set、multiset、map、multimap和unordered系列容器,它们按照键值进行存储和访问。 序列容器具有以下特性: 1. 动态大小:序列容器可以根据需要动态调整大小,可以在任意位置插入和删除元素。 2. 快速随机访问:序列容器中的元素可以通过索引快速访问,时间复杂度为O(1)。 3. 按顺序存储:序列容器中的元素按照插入的顺序存储,并保持元素的相对位置不变。 4. 支持迭代器:序列容器提供了迭代器,可以通过迭代器遍历容器中的元素。 关联容器具有以下特性: 1. 自动排序:关联容器中的元素按照键值自动排序,并且可以根据自定义的比较函数进行排序。 2. 快速查找:关联容器支持快速的查找操作,时间复杂度为O(log n)。 3. 不允许重复键值:set和map容器中的键值是唯一的,而multiset和multimap容器允许重复的键值。 4. 无序容器:unordered系列容器C++11引入的,它们使用哈希函数来存储和访问元素,查找操作的平均时间复杂度为O(1)。 总而言之,C++ STL提供了丰富的容器类,每个容器类都有其独特的特性和适用场景,可以根据具体需求选择合适的容器来存储和操作数据。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C++——STL容器](https://blog.csdn.net/JAN6055/article/details/122758690)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [C++STL容器类详解](https://blog.csdn.net/Jinyizhi2233/article/details/131640448)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yorelee.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值