STL(C++标准模板库)的容器初解

目录

一、STL诞生的背景是什么?

二、STL是什么?

三、STL中的算法与迭代器

四、STL中的容器

五、容器的分类

string容器 

vector容器

deque容器

stack容器

 queue容器

 list容器(链表)

set/multiset容器

pair使用-pair对组的创建

map/multimap容器


一、STL诞生的背景是什么?

        为了建立一种可重复利用的东西,提高代码的复用性,因此建立了数据结构和算法的一套标准,即STL。

二、STL是什么?

        STL(Standard Template Library),中文名标准模板库,大体分为六大组件,分别为:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器(由于本文章的重点在于STL中的容器,因此想更多知道STL的相关内容请上网搜索)。

三、STL中的算法与迭代器

        想要学习容器,那算法与迭代器的了解必不可少。

1.算法分为两种:

内容 举例
质变算法 运算过程中更改区间内的元素内容 拷贝,替换,删除等
非质变算法 运算过程中不改变区间内的元素内容         查找、计数、遍历、寻找极值等

2.迭代器:容器与算法之间的粘合剂

  •         提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
  •         每个容器都有自己专属的迭代器,迭代器的使用非常类似于指针,初学阶段可以先理解迭代器为指针。   
  •         通过迭代器(iterator)来访问容器中的数据:
    std::vector::iterator itBegin = v.begin();//起始迭代器 指向容器中第一个元素
    
    std::vector::iterator itEnd = v.end();//结束迭代器 指向容器中最后一个元素的下一个位置
    
     //1.遍历方式
    while (itBegin != itEnd)
    {
      std::cout << *itBegin << std::endl;
      itBegin++;
    }
    
     //2.第二种遍历
    for (std::vector::iterator it = v.begin(); it != v.end(); it++)
    {
       std::cout << *it << char(10);
    }
    
     //3.第三种遍历 利用STL提供的遍历算法 需包含头文件#include
    void myPrint(int val)
    {
       std::cout << val << char(10);
    }
    
    for_each(v.begin(),v.end(),myPrint);

四、STL中的容器

        STL中的容器就是将运用最广泛的一些数据结构实现出来,哪怕你不懂这些数据结构,也可以通过STL容器进行使用,常用的数据结构为:数组,链表,树,栈,队列,集合,映射表等。容器的使用需包含所对应的头文件,使用string需包含#include,使用vector需包含#include等。

五、容器的分类

常用容器 特点
string c++中的字符串。字符串对象是一种特殊类型的容器,专门设计来操作的字符序列
vector(使用频率最高) vector数据结构与数据非常相似,也称为单端数组,可以动态扩展

deque(双端队列) 

双端队列,可以对头端进行插入和删除,且内部有中控器来维护每段缓冲区中的内容

stack(栈)

是一种先进后出的数据结构,只有一个出口,且不允许有遍历行为
queue(队列) 是一种先进先出的数据结构,有两个出口,只有队头和队尾才可以被外界使用,不允许有遍历行为

list(链表)

是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的,是一个双向循环链表

set/multiset

所有元素在插入时会自动被排序,set中不允许有重复元素,miltiset中允许有重复的元素

map/multimap

map中所有元素都是pair,pair中第一个元素为key(键值),第二个元素为value(实值),所有元素都会根据元素的键值自动排序

string容器 

  1. 基本概念:
    (1)特点:string类内部封装了很多成员方法,管理char*所分配的内存,不用担心复制越界和取值越界等问题,由类内部进行负责
    (2)本质:是c++风格的字符串,本质上是一个类
  2. 构造函数:
    string s1();                     创建一个空的字符串
    
    string s2(const char* str);      使用字符串str初始化
    
    string s3(const string& str);    使用一个string对象(str)初始化另一个string对象
    	
    string s4(int n,char c)          使用n个字符c初始化
  3. 赋值操作:
    string& operator=(const char* str);        char*类型字符串赋值给当前的字符串
    
    string& operator=(const string str);       把字符串str赋给当前的字符串
    
    string& operator=(char str);               字符赋值给当前的字符串
    
    string& assign(const char* str);           把字符串str赋给当前的字符串
         /*std::string str1;
           str1.assign("hello world");*/
    string& assign(const char* str,int n);     把字符串str的前n个字符赋给当前的字符串
    
    string& assign(const std::string &str);    把字符串str赋给当前字符串
    
    string& assign(int n,char str);            用n个字符str赋给当前字符串
  4. 字符串拼接:
    string& operator +=(const char *str)            /*std::string str1 = "Hello";
                                                      str1 += " Word";*/
    string& operator +=(const char c)
    
    string& operator +=(const string *str)          //前三个都为重载+=操作符
    
    string& append(const char* s)                   把字符串s连接到当前字符串结尾
    
    string& append(const char* s,int n)             把字符串s的前n个字符连接到当前字符串尾部
    
    string& append(const string &s)
    
    string& append(const char* s,int pos,int n)     字符串s中从pos开始的n个字符连接到字符串结尾
  5. 字符串查找和替换:
    //查找
    
    int find(const string& str,int pos=0) const;      查找str第一次出现的位置,从pos开始查找
    	//std::string str1 = "kjvdesghjdka";
        //short pos = str1.find("de");    pos=3              查不到则返回-1
    
    int find(const char* s,int pos=0) const;          查找s第一次出现的位置,从pos开始查找
    
    int find(const char* s,int pos,int n) const;	  从pos位置查找s的前n个字符第一次位置
    
    int find(const char c,int pos=0) const;           查找字符c第一次出现的位置
    
    int rfind(const string& str,int pos=npos)const;   rfind是查找最后一次出现的位置
    
    int rfind(const char* s,int pos=npos)const;
    
    int rfind(const char* s,int pos,int n)const;
    
    int rfind(const char c,int pos=0)const;
    
    //替换
    
    string& replace(int pos,int n,const string& str); 替换从pos开始n个字符为字符串str
                /*std::string str1 = "abcdesghjdka";
    	          str1.replace(0, 3, "acb");*/
    string& replace(int pos,int n,const char* s);     替换从pos开始的n个字符为字符串s
  6. 字符串比较: 按照ASCII码进行对比,等于=则返回0,大于>则返回1,小于<则返回-1
    int compare(const char* s) const;
            /*std::string str1 = "abcdesghjdka";
    	      std::string str = "sad";
           	  int pos = str1.compare(str);    -1*/
    int compare(const string& s) const;
  7. 字符串存取:
    char& operator[](int n);    通过[]方式取字符
    
    char& at(int n)            	通过at方式获取字符
    
  8. 字符串长度:
    str.size()
  9. 插入和删除:
    string& insert(int pos,const char* s)
    
    string& insert(int pos,const string& str)
    
    string& insert(int pos,int n,char c)        在指定位置pos插入n个字符c
    
    string& erase(int pos,int n)        	    删除从pos开始的n个字符
  10. 字符获取:
    string substr(int pos=0,int n)const;    返回由pos开始的n个字符组成的字符串
    
    string substr(int n)                	返回第n个字符之后的字符串


vector容器

  1. 基本概念:
            vector数据结构和数据结构非常相似,也称为单端数组,与普通数组的区别在于数组是静态空间,而vector可以动态扩展,动态扩展不是在原空间之后续接新空间,而是寻找更大的内存空间,然后将原数据拷贝到新空间中,释放原空间。

    STL(C++标准模板库)的容器初解_第1张图片
     
  2. 构造函数:
    vector v;                 使用模板实现类实现,默认构造函数
    
    vector(const vector &vec)    拷贝构造函数
    
    vector(v.begin(),v.end()); 	 将v[begin(),end())区间中的元素拷贝给本身 前闭后开
      /*std::vectorv;
    	v.push_back(1);
    	v.push_back(3);
    	v.push_back(2);
    	std::vectorv1(v.begin(), v.end()); */
    vector(n,elem)               将n个elem拷贝给本身
  3. 赋值操作:
    vector& operator=(const vector &vec);    	重载等号操作符
    
    assign(v.begin(),v.end())                   将[begin(),end())区间中的数据拷贝赋值给本身
    	//v2.assign(v1.begin(), v1.end());
    
    assign(n,elem)    	                        将n个elem拷贝赋值给本身
  4. 容量和大小:
    
    empty()        返回值为bool 空为ture //判断容器是否为空
    
    capacity()     容器的容量
                   /*capacity()>=size() 
                     当容器的大小等于容量时,容器每次增加多少容量,取决于算法的实现*/
    size()         返回容器中元素的个数
    
    //若容器变短,则大小改变,容量不变
    resize(int num)
    	            /*重新指定容器的长度为num,若容器变长,则以默认值0填充新位置
    	                如果容器变短,则末尾超出容器长度的元素被删除*/
    
    resize(int num,elem)
    	            /*重新指定容器的长度为num,若容器变长,则以elem值填充新位置
    	                如果容器变短,则末尾超出容器长度的元素被删除*/
  5. 插入和删除:
    push_back(ele);                                 尾部插入元素ele(尾插法)
    
    pop_back();                                     删除最后一个元素(尾删法)
    
    insert(const_iterator pos,ele)                  迭代器指向位置pos插入元素ele
    	//v1.insert(v1.begin()+1, 100);
    
    insert(const_iterator pos,int count,ele)        迭代器指向位置pos插入count个元素ele
    
    erase(const_iterator pos)                       删除迭代器指向的元素
    
    erase(const_iterator start,const_iterator end)	删除迭代器从[start到end)之间的元素
    
    clear() 	                                    删除容器中所有元素
  6. 数据存取:
    at(int idx)       返回索引dix所指的数据
    
    operator[]        返回索引dix所指的数据
    
    front();          返回容器中第一个数据元素
    
    back()            返回容器中最后一个数据元素
  7. 互换容器:
            vector的容量是动态分配的,由STL内部算法实现,因此容量会大于大小,造成一定的空间浪费
    //实现两个容器内元素进行互换
    swap(vec);    //v1.swap(v2)    将v1和v2的元素进行互换
    
    vector(v).swap(v);
    
    //vector(v) 匿名对象,匿名对象执行完会立刻回收
    
    //系统会按照当前v的大小给匿名对象赋予容量和大小
    
    //举例 用swap收缩内存
    //vector的容量是动态分配的,由STL内部算法实现,因此容量会大于大小,造成一定的空间浪费
    
    for (int i{}; i < 1000000; i++)
     {
        v1.push_back(i);
     }
     std::cout << v1.capacity() << std::endl;
     std::cout << v1.size() << std::endl;
     v1.resize(3);
    
     std::cout << v1.capacity() << std::endl;
     std::cout << v1.size() << std::endl;
    
     std::vector(v1).swap(v1);
    
     std::cout << v1.capacity() << std::endl;
     std::cout << v1.size() << std::endl;
    
     //输出 1049869 1000000 1049869 3 3 3
  8. 预留空间:减少vector在动态扩展容量时的扩展次数
    reserve(int len);	容器预留len个元素长度(容量),预留位置不初始化,元素不可访问
    
    //统计内存开辟次数
     std::vector v;
     int* p = NULL;
     int num{};
    
     //v.reserve(1000);
    
     for (int i{}; i < 1000; i++)
     {
      v.push_back(i);
      if (p != &v.at(0))
      {
       p = &v[0];
       num++;
      }
     }
    //num = 1
  9. 排序:
    sort(v.begin(),v.end());    需包含头文件#include
  10. 总结:
            vector使用方便,在容器的使用频率是最高的,但是它的一个缺点就是当数据量大的时候,会额外占用很大的时间,因此建议在数据量大时,使用swap()来互换容器,减少空间的浪费。

deque容器

  1. 基本概念:
    (1)双端数组,可以对头端进行插入和删除操作。
    (2)和vector的区别:
                    vector对于头部的插入删除效率低,数据量越大,效率越低
                    deque相对而言,对头部的插入删除速度会比vector快
                    vector访问元素时的速度会deque快,这和两者的内部实现有关
    (3)访问中间元素速度较慢;
    (4)工作原理:
                    deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据,中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间

    STL(C++标准模板库)的容器初解_第2张图片


    (5)迭代器支持随机访问
    STL(C++标准模板库)的容器初解_第3张图片
    (6)设置为只读迭代器
    void printDeque(const std::dequed)
    {
    for (std::deque::const_iterator it = d.begin(); it != d.end(); it++)
     {
      // *it = 100;   无法修改
      std::cout << *it << " ";
     }
    }
  2. 构造函数:
    dequedeqT;            	默认构造形式
    
    deque(const deque &deq)     拷贝构造函数
    
    deque(begin(),end());    	构造函数将[begin(),end())区间中的元素拷贝给本身
    
    deque(n,elem)            	构造函数将n个elem拷贝给本身
  3. 赋值操作:
    assign(n,elem)
    
    assign(begin(),end())
    
    deque& operator=(const deque& deq)
  4. 大小操作:
           deque没有容量的概念,只有大小
    deque.size()     返回容器中元素的个数
    
    deque.empty()    判断是否为空
    
    deque.resize(num,elem)
    	/*重新指定容器的长度为num,若容器变长,则以elem填充新位置。
    	如果容器变短,则末尾超出容器长度的元素被删除*/
    
    deque.resize(num);
    	/*重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
    	如果容器变短,则末尾超出容器长度的元素被删除*/
    
  5. 插入删除:
           两端插入:
    push_back(elem)    在容器尾部添加一个数据
    
    push_front(elem)   在容器头部添加一个数据
    
    pop_back()         删除容器最后一个数据
    
    pop_front()        删除容器第一个数据

            指定位置插入:

    insert(d1.begin(),beg,end)   在d1.begin()位置插入[beg,end)区间的数据,无返回值
    
    insert(d1.begin(),n,elem)    在d1.begin()位置插入n个elem数据,无返回值
    
    insert(d1.begin(),elem)      在di.begin()位置插入一个elem元素的拷贝,返回新数据的位置
    
    clear()                      清空容器所有的数据
    
    erase(d1.begin())            删除d1.begin()位置的数据,返回下一个数据的位置
    
    erase(d1.end()-1)            删除最后一位数据
    
    erase(beg,end)    	         删除[beg,end)区间的数据,返回下一个数据的位置
    
  6. 数据存取:
    front();        返回容器中第一个数据元素
    
    operator[]   	返回索引dix所指的数据
    
    back()          返回容器中最后一个数据元素
    
    at(int idx)     返回索引dix所指的数据
  7. 排序算法:
    对于支持随机访问的迭代器的容器,都可以利用sort算法直接对其进行排序。
    #include
    
    sort(d.begin(),d.end())    对beg和end区间内元素进行排序 从小到大
    

stack容器

  1. 基本概念:
    (1)是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口;
    (2)不允许有遍历行为,只有栈顶元素才能被外界访问到。

    STL(C++标准模板库)的容器初解_第4张图片

  2. 构造函数:
    stack stk                stack采用模板类实现,stack对象的默认构造形式
    
    stack(const stack &stk)    	拷贝构造函数
    
  3. 赋值操作:
    stack& operator=(const stack &stk)   	重载等号操作符
    
  4. 数据存取:
    push(elem)     	向栈顶添加元素
    
    pop()           从栈顶移除第一个元素
    
    top()           返回栈顶元素
  5. 大小操作:
    size()    	返回栈的大小
    
    empty()    	判断堆栈是否为空
    

 queue容器

  1. 基本概念:
    (1)是一种先进先出(First In First Out,FIFO)的数据结构,有两个出口;
    (2)队列容器允许从一端新增元素,从另一端移除元素;
    (3)队列中只有队头和队尾才可以被外界使用,因此队列中不允许有遍历行为;
    (4)队列中进数据称为入队,队列中出数据称为出队。STL(C++标准模板库)的容器初解_第5张图片
  2. 构造函数:
    queue stk               queue采用模板类实现,queue对象的默认构造形式
    
    queue(const queue &stk)    拷贝构造函数
    
  3. 赋值操作:
    queue& operator = (const queue &stk)    	重载等号操作符
    
  4. 数据存取:
    back()        	返回最后一个元素
    
    front()         返回第一个元素
    
    pop()    	    从队头移除第一个元素
    
    push(elem)    	向队尾添加元素
    
  5. 大小操作:
    size()	        返回队列的大小
    
    empty()	        判断队列是否为空
    

 list容器(链表)

  1. 基本概念:
    (1)功能:将数据进行链式存储;
    (2)链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的;
    (3)由一系列的结点组成,一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域;
    (4)STL的链表是一个双向循环链表;
    (5)由于链表的存储方式并不是连续的内存空间,因此链表中的迭代器只支持前移和后移,属于双向迭代器;
    std::list::iterator it = l.begin();
    
     it++;//支持双向
    
     it--;
    
     //it = it + 1; 不支持

    (6)优点:采用动态存储分配,不会造成内存浪费和溢出;
                        链表执行插入和删除十分方便快捷,修改指针即可,不需要移动大量元素。
    (7)缺点:容器遍历速度没有数组快,得去找地址;
                         占用空间大,还得保存指针域。
    (8)总结: 链表灵活,但是空间(指针域)和时间(遍历)额外耗费较大
    (9)重要性质:插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的STL(C++标准模板库)的容器初解_第6张图片

  2. 构造函数:
    list lst              list采用模板类实现,对象的默认构造形式
    
    list(const list& lst)    拷贝构造函数
    
    list(beg,end)            构造函数将[beg,end)区间中的元素拷贝给本身
    
    list(n,elem)        	 构造函数将n个elem拷贝给本身
  3. 赋值和交换:
    list& operator=(const list &lst)    重载等号操作符
    
    assign(n,elem)                    	将n个elem拷贝赋值给本身
    
    swap(lst)                        	将lst与本身的元素互换
    
    assign(beg,end)                    	将[beg,end)区间中的数据拷贝赋值给本身
  4. 大小操作:
    size()          返回容器中元素个数
    
    empty()        	判断容器是否为空
    
    resize(num)
                	/*重新指定容器的长度为num,若容器变长,则以默认值填充
                	如果容器变短,则末尾超出容器长度的元素被删除*/
    
    resize(num,else)
                	/*重新指定容器的长度为num,若容器变长,则以elem填充
                	如果容器变短,则末尾超出容器长度的元素被删除*/
    
  5. 插入和删除:
    push_back(elem)         在容器尾部加入一个元素
    
    pop_back()              删除容器中最后一个元素
    
    push_front(elem)        在容器开头插入一个元素
    
    pop_front();            从容器开头移除第一个元素
    
    insert(pos,elem)    	在pos位置插入elem元素的拷贝,返回新数据的位置
    
    insert(pos,n,elem)    	在pos位置插入n个elem数据,无返回值
    
    insert(pos,beg,end)    	在pos位置插入[beg,end)区间的数据,无返回值
    
    clean()                	移除容器中的所有数据
    
    erase(beg,end)        	删除[beg,end)区间的数据,返回下一个数据的位置
    
    erase(pos)            	删除pos位置的数据,返回下一个数据的位置
    
    remove(elem)        	删除容器中所有与elem值匹配的元素
    
  6. 数据存取:
    (1)用++进行跳跃式访问,可以多写几个
    (2)L[]和L.at()都无法访问list容器中的元素,List本质是链表,不是用连续线性空间存储数据,迭代器也是不支持随机访问
    front()    	返回第一个元素
    
    back()    	返回最后一个元素
    
  7. 反转和排序:
    (1)反转:
    reserve();

    (2)排序:

    sort()              从小到大
    
    sort(myCompare)     从大到小
    	bool myCompare(int v1,int v2)
        {
           return v1>v2;
        }
        //回调函数 指定排序规则
  8. 排序案例:
    /*将Person自定义数据类型进行排序,Person中的属性有姓名、年龄、身高
    规则:按照年龄进行升序,如果年龄相同按照身高进行降序*/
    
    bool comparePerson(Person& p1, Person& p2)
    {
     if (p1.m_Age == p2.m_Age)
     {
      return p1.m_Height > p2.m_Height;
     }
     return p1.m_Age < p2.m_Age;
    }
    void test01()
    {
    L.sort(comparePerson); 
    }

set/multiset容器

  1. 基本概念:
    (1)所有元素在插入时自动被排序;
    (2)set/multiset属于关联式容器,底层结构是用二叉树实现;
    (3)set不允许容器中有重复的元素,multiset允许容器中有重复的元素;
    (4)包含set的头文件也可以使用multiset。
  2. 构造和赋值:
    set& operator=(const set &st)
    
    set(const set &st)
    
    set st
    
  3. 大小和交换:
    empty()
    
    swap(st)
    
    size()
    
  4. 插入和删除:
    erase(beg,end)
    
    erase(elem)        删除容器中值为elem的元素
    
    erase(pos)
    
    clear()
    
    insert(elem)
    
  5. 查找和统计:
    find(key)    /*查找key是否存在,若存在,返回该键的元素的迭代器,若不存在,返回set.end()*/
    	
    std::set::iterator pos = s1.find(1);
     if (pos != s1.end()) 
    {
     std::cout << "找到元素" << *pos << std::endl;
    }
    
    count(key)
            	/*统计key的元素个数
    	        对于set而言要么是0要么是1*/
    
  6. set和multiset的区别:
    (1)multiset不会检测数据,因此可以插入重复数据;
    (2)set插入数据的同时会返回插入结果,表示插入是否成功;
    (3)set不可以插入重复的数据,而multiset可以。
  7. 内置类型指定排序规则:
    利用仿函数,可以指定排序规则	在未插入之前就指定排序规则
    
    class MyCompare
    {
    public:
     bool operator()(int r1, int r2)const
     {
      return r1 > r2;
     }
    };
    
    std::sets2;
  8. 自定义数据类型指定排序规则:
    自定义的数据类型都得指定排序规则,否则无法插入
    
    class comparePerson
    {
    public:
     bool operator()(const Person& s1,const Person& s2)const
     {
      return s1.m_age > s2.m_age;
     }
    };
    
    std::sets1;

pair使用-pair对组的创建

成对出现的数据,利用对组可以返回两个数据。

std::pair p(value1,value2)
    std::pairp("Tom", 20); 
    std::cout << "姓名: " << p.first << " 年龄: " << p.second << std::endl;

std::pair p = std::make_pair(value1,value2)
    std::pairp2 = std::make_pair("Jerry", 30);
    std::cout << "姓名: " << p2.first << " 年龄: " << p2.second << std::endl;

map/multimap容器

  1. 基本概念:
    (1)简介:map中所有元素都是pair;
                        pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值);
                        所有元素都会根据元素的键值自动排序。
    (2)本质:map/multimap属于关联式容器,底层结构是用二叉树实现
    (3)优点:可以根据key值来快速找到value值
    (4)map和multimap的区别:map不允许容器中有重复key值,multimap允许容器中有重复key值元素。
  2. 构造和赋值:
    map(const map &mp);
    
    map mp;
    
    map& operator=(const map &map);
    
  3. 大小和交换:
    size()
    
    empty()
    
    swap()
    
  4. 插入和删除:
    insert(elem)
        m.insert(std::pair(1, 10));
        m.insert(std::make_pair(2, 20));          //好记
        m.insert(std::map::value_type(3, 30));
    	m[4] = 50;        //[]不建议插入,但是可以利用key访问到value
    
    erase(beg,end)
    
    erase(key)
    
    erase(pos)
    
    clear()
    
  5. 查找和统计:
    find(key)
    	        //查找key是否存在,若存在,返回该键的元素的迭代器,若不存在,返回map.end()
    
    count(key)
            	//统计key的元素个数
    
  6. 排序:
    (1)自定义的数据类型都得指定排序规则,否则无法插入;
    (2)内置类型指定排序规则为从小到大,利用仿函数,可以指定排序规则。
  7. 总结:
            map的key值既可以当作普通数组的下标来使用,也可以看成是一个会排序的万能下标,这个下标可以是整型,也可以是一个字符串,视使用情况而定。

图文参考:

https://www.bilibili.com/video/BV1et411b73Z?p=1&vd_source=dc3686d454b60a8bbdc03949afb2b41f(第一次写csdn,若各位大佬什么好的建议或者哪里写错了,还请不吝赐教!!)

你可能感兴趣的:(c++,数据结构)