C++ STL 常见容器的底层实现结构 and 常用接口梳理 and 迭代器失效问题

一、常见容器的底层实现结构 and 常用接口梳理 and 迭代器失效
 vector list dqueue string stack queue priority_queue  map unordered_map  set unordered_set
 <1> vector
 底层: 数组
 扩容方式: vs下以2倍速度增长,g++ 下以1.5倍速度增长。 (两种环境下STL的版本不同,前者为PJ版本,后者为SGI版本)
 初始化方式: 
     (1)vector<int> v;  // vector 为空,size = 0,没有任何元素
     (2)vector<int> v(v2)  ; //
         等价于 vector<int> v = v2; // v 将具有和 v2 一样的元素以及容量
     (3)vector<int> v = {1,2,3,4,5,6}; // 等价于 vector<int> v{1,2,3,4,5,6} v被初始化为,列表中元素的拷贝。
     (4)vector<int> v1{1,2,3,4,5,6};
         vector<int> v2(v1.begin() + 2, v1.end() - 1); // v2被初始化为两个迭代器指定范围的元素,特备适合用来获取一个序列的子序列
     (5)vector<int> v(7);   //默认值初始化,v中将包含七个元素,每个元素进行缺省的值初始化,对于int来说,就是被赋值为了0。 元素数量大致可以预知时,而元素需要动态获取时,可以采用这种初始化方式。
     (6)vector<int> v(7,3); // 与上面一样,v中将含有7个元素,不同的是,该七个元素被初始化为了3;

常用接口:
    迭代器相关:
        iterator begin();  // 正向迭代器:返回一个迭代器,指向vector对象的第一个元素
        iterator end();    // 正向迭代器:返回一个迭代器,指向vector对象的最后一个元素的后边
        reverse_iterator rbegin();     // 反向迭代器:返回一个迭代器,指向vector对象的最后一个元素
        reverse_iterator rend();    // 反向迭代器:返回一个迭代器,指向vector对象的第一个元素的前面
    容量相关:
        size_t size();     // 获取当前对象中元素的个数
        size_t capacity();     //获取当前对象的容量,是已经分配给当前对象的空间
        void resize(size_t sz,T c = T());     //将当前对象的个数设置为sz个,sz比size大,则默认使用0填充,或者指定使用c来填充
        void reserve(size_t n);     //保证当前对象容量至少有为个
        bool empty() const;     //判空,判断当前对象元素是否为空,为空返回true,否则返回 false
    元素获取:
        front() cosnt; // 获取第一个元素
        back() const;  // 获取最后一个元素
        v[n];          //使用下标随机访问
    元素修改:
        void push_back(const T& x);     //尾插,插入元素之前会先检查是否有足够空间,不够则会进行扩容
        void pop_back();     //尾删,不会改变容量
        void insert(iterator pos,size_t n,const T& x);     //指定位置插入元素,插入n个x
        void insert(iterator pos,IpIter first,IpIter last); //指定位置插入迭代器所指范围的元素  
        iterator erase(iterator pos);     //删除迭代器所指位置的元素
        iterator erase(iterator first,iterator end);     //删除迭代器所指范围内的元素
        swap(); //交换两个对象的内容
        clear(); //清空对象,即size设置为0
    随机查找位置:
        vector本身没有find方法,其find是依靠algorithm来实现的。
        find(iterator begin(),iterator end(), num); // 返回一个迭代器指针 
        例子:
            vector<int> v{ 1,2,3,4,5,6 };
            vector<int>:: iterator it = find(v.begin(),v.end(),5);

迭代器失效问题:
    向容器中添加或者删除元素的操作可能使指向容器的指针、引用、迭代器失效。一个失效的指针、引用、迭代器将不再表示任何元素。
    (1)在向容器添加元素后,如果储存空间 “未重新分配” ,指向插入位置之前的元素的迭代器、指针、引用有效,但指向插入位置之后的将会失效。
    (2)在从容器删除元素之后,指向被删元素之前元素的迭代器、引用、指针仍有效。尾后迭代器也就失效。
    两种列子:
        1.遍历时插入元素
            iter = v.insert(iter,n); //正常遍历,想要指向下一个元素,就要跳过当前和被添加的元素
            iter += 2;
        2.遍历时删除元素
            注意:erase函数返回的是删除之后的有效的迭代器
            iter = v.eares(iter);


<2>list
底层实现:双向循环链表,再某些版本中是采用双向循环链表(例 SGI STL)
特点:可以在常规时间内,再序列已知的人任何位置插入或者删除元素,这就是我们使用list 而不适用vector 或者 queue的原因。
初始化方式:
    (1)list<string> ls(20)  等价于  list<string> ls{20};     //初始化为含有20个元素,且都为空的链表
    (2) list<int> ls(10,1);     //和vector一样,这里的ls将含有十个元素,且每一个元素的值是1
    (3) list<int> ls(ls1);         //容器含有拷贝函数,生成另一个list的副本。
    (4) list<int> ls(ls1.begin(),ls1.end()); // 可以用另一个list的开始迭代器和结束迭代器来初始化当前list。
常用接口:
    迭代器:与vector大致相同
    容量操作:
        size_t size();
        bool empty();
        void clear();
    元素访问:
        reference front(); //返回第一个元素的引用
        reference back();  //返回最后一个元素的引用
    元素修改:
        void push_front(const value_type& val);  //在链表头部插入只为val的节点
        void pop_front();   //删除链表头部结点
        void push_back();     //尾插
        void pop_back();    //尾删
        insert(插入):
            iterator insert(iterator pos,const value_type& val);    //在pos之前插入一个元素val
            iterator insert(iterator pos,size_type n,const value_type& val);    //在pos位置之前插入n个val
            iterator insert(iterator pos,iterator first,iterator last);     // pos之前插入[first,last)迭代器范围内的所有元素,左闭右开
        earse(删除):
            iterator erase(const_iterator pos);     //删除pos位置的元素
            iterator erase(const_iterator first,const_iterator last);    //删除[first,last) 迭代器范围内的元素,左闭右开
        void swap(lsit& x);    //交换两个list的元素
        void resize(size_type n,value_type val = value_type());     //将现有的list中的有效元素变为n个,大于出当前size的用val填充
迭代器失效问题: 
    因为vector的元素是顺序存储的,所以删除一个元素会导致后面的所有元素会向前移动一个位置,所以当前位置之后的迭代器就会失效。(影响后面所有)
    但是,list使用了不连续分配的内存,删除会使得当前位置的迭代器失效,但是不会失效其他的迭代器。
    因此:低于list删除引起的迭代器失效的解决方式是为:
    1.与vector相同,erase会返回下一个有效的迭代器
    2.或者erase(iter++); //提前使得iter为下一个有效元素的迭代器
    注:list的插入不会是的迭代器失效


<3>deque(双端队列) 底层结构:采用分块的线性存储结构(块的大小一般为512B,成为deque块),支持随机访问,可以通过下标来访问元素。所有的块儿通过map块进行管理,每个map数据项记录deque块的首地址,所以允许较为快速的随机访问。
特点:头部尾部都可以快速的插入元素,不需要移动其他元素
构造函数:
    deque<T> deqT;                //默认构造形式
    deque(beg, end);            //构造函数将[beg, end)区间中的元素拷贝给本身。
    deque(n, elem);                //构造函数将n个elem拷贝给本身。
    deque(const deque &deq);    //拷贝构造函数
主要操作函数:    
    push_back(T elem);            //在容器尾部添加一个数据
    push_front(T elem);            //在容器头部插入一个数据
    pop_back();                    //删除容器最后一个数据
    pop_front();                //删除容器第一个数据


    insert(iterator pos, T elem);                        //在pos位置插入一个elem元素的拷贝,返回新数据的位置。
    insert(iterator pos,size_t n, T elem);                //在pos位置插入n个elem数据,无返回值。
    insert(iterator pos,iterator beg, iterator end);    //在pos位置插入[beg,end)区间的数据,无返回值。

    clear();//移除容器的所有数据
    erase(iterator beg,iterator end);                    //    删除[beg,end)区间的数据,返回下一个数据的位置。
    erase(iterator pos);                                //    删除pos位置的数据,返回下一个数据的位置
迭代器失效问题:
    插入操作:
        1、在队前或队后插入元素时(push_back(),push_front()),由于可能缓冲区的空间不够,需要增加map中控器,而中控器的个数也不够,所以新开辟更大的空间来容纳中控器,所以可能会使迭代器失效;但指针、引用仍有效,因为缓冲区已有的元素没有重新分配内存。
        2、在队列其他位置插入元素时,由于会造成缓冲区的一些元素的移动(源码中执行copy()来移动数据),所以肯定会造成迭代器的失效;并且指针、引用都会失效。
    删除操作:
        1、删除队头或队尾的元素时,由于只是对当前的元素进行操作,所以其他元素的迭代器不会受到影响,所以一定不会失效,而且指针和引用也都不会失效;
        2、删除其他位置的元素时,也会造成元素的移动,所以其他元素的迭代器、指针和引用都会失效。

<4> priority_queue 优先级队列(堆)
底层:本质上是通过一个堆实现的,包括队列的所有特性,只是在基础上添加了一个内部排序,它本质是一个堆实现的。
定义方式:
    priority_queue<Type,Container,Functional>  Type就是数据类型,Container是容器类型(如vector,queue等等,但不能使用list,默认使用vector),Functional就是比较方式。
    //升序,小顶堆,堆顶元素最小
    priority_queue<int,vector<int>,greater<int> > q;
    //降序,大顶堆,堆顶元素最小
    priority_queue<int,vector<int>,less<int> > q;

    注意:
    1.定义priority_queue时,若不指定容器类型,则默认使用vector,若不指定比较方式,则默认生成大堆,底层按照小于号进行比较。
    例:
        priority_queue<int> q;     //定义为大堆,底层容器使用vector
    2.当数据类型为自定义类型时,则需要在该类中对 > 或者 < 运算符进行重载,使其可以进行比较。 
    3.有些情况下,用户也需要提供比较器规则(重写仿函数)
    例:
            class Less
             {
                 public:
                 bool operator()(const Date* pLeft,const Date* pRight)
                 {
                     return *pLeft < *pRight;
                 }
             };
 
             void TestPriority_queue()
             {
                 //自己制定的比较规则
                 priority_queue<Date*,vector<Date*>,Less> q;
                 q.push(&Date(2019,10,11)); 
                 q.push(&Date(2019,10,12)); 
                 q.push(&Date(2019,10,13)); 
                 cout<<*q.top()<<endl;
             }
接口: 和queue基本一致,包括 push(),pop(),empty(),size() 等;

<5> map && unordered_map && mutile_map 
[1]    map
底层实现:红黑树
基本特点:map是一类关联式容器,它的特点是删除和插入节点对迭代器的影响比较小,除了当前操作节点,对其他节点没有什么影响。
        对于迭代器来说,只可以修改节点的value值,而不能修改key
功能:
    1.有自动排序功能(默认升序less,降序为greater)
    2.去重功能,不能有key值相同的节点
    3.快速插入节点
    4.快速删除
    5.快速查找(重要)

插入元素方式:
    map<int,int> mp;
    1.//赋值方式插入
      //插入时若key存在,则相当于修改当前key 的 vaule 为后面数值
      //插入时若key不存在,则相当于相当于插入(1,1)
    mp[1] = 1;  //相当于插入(1,1)
    2. insert();
    由于map的每一个元素都是一个键值对,因此插入时需要插入一个pair
    因此: mp.insert(pair<int,int>(1,1));
    注意:insert在插入时,若插入的key值不存在,则会将当前键值对插入进去,否则无论vaule值是否不同,都不进行插入。
删除元素方式:
    erase(T key);            //通过key值删除
    erase(iterator ite);    //通过迭代器删除
查找接口:
    iterator find(T key);   //查找当前map中是否含有当前key,如果有返回指向该key位置的迭代器,如果没有则返回迭代器end();
迭代器失效问题:
    由于map的底层是红黑树,因此每一个节点都是独立不连续的,所以使用迭代器来删除当前结点,只会是的当前的迭代器失效。插入不会使得迭代器失效。
    因此为了防止该问题,我们在遍历时删除元素应如下处理:
    mp.insert(iter++);     //提前使得迭代器++到下一个有效的位置;

[2]multimap
底层及特点:基本与map相同,不同之处在于multimap可含有重复的key。
其次功能上的不同在于:multimap没有重载[],因此不支通过[]访问元素的value
其他常用接口:
    count(T key);    //返回某一个key出现的次数
    
[3]unordered_map
底层及特点:与map不同,unordered_map 采用哈希的方式实现,与map不同之处在于其没有排序功能
<6> set && unordered_set && multimap
对应特点与map类相似,区别在于set的元素不是键值对,是单一的数据类型。

<7> string(字符串)
对于字符串string我们最应掌握的就是一系列字符串接口的使用:
构造函数:
    string s;
    string s("hello") 等价于 string s = "hello";
    string s(s1);   //拷贝构造
容量类的接口:
    size();    //返回字符串有效字符串的长度
    capacity();    //返回总空间的大小
    empty();    //判空
    clear();    //清除有效字符,不改变空间大小

    resize(8)   //重新设置s字符串的有效个数为8,更改的是size
    resize(10,'x')   //重新设置字符串有效字符的长度为10,并且多余位置用x填充
遍历方式:
    1.operator[]    //类似与数组方括号操作
    2.iterator  begin() -> end()  //迭代器遍历
    3.范围for

修改操作:
    追加操作:
        append("hello");    //在字符串末尾追加一个字符串
        append(2,'x');        //在字符串末尾追加两个字符x
        append(string s,size_t pos,size_t n); //在当前字符串末尾追加,从s的pos位置开始,复制n个
        也可以使用 += 等运算符进行上面操作。
提取子串:
    substr(size_t pos,size_t n);     //从pos位置开始,提取n个字符
    c_str() ;        //返回const char* 

查找接口:
    size_t find(char c);     //查找某一个字符在字符串中第一次出现的位置
    size_t find(string s);    //查找某一个子串在当前字符串中第一次出现的位置

    size_t rfind(char c);     //从末尾开始,查找某一个字符在字符串中第一次出现的位置
    size_t rfind(string s);    //从末尾开始,查找某一个子串在当前字符串中第一次出现的位置

删除接口:
    erase(size_t pos,size_t n);        //从pos位置开始,删除n个字符,若不够则直接删除到字符串结束
    erase(iterator iter);        //删除当前迭代器所指向位置的字符
    erase(iterator begin,iterator end);        //删除迭代器所指范围内的字符

插入接口:
    insert(size_t pos,string s);    //从字符串的pos位置,开始插入字符串s
    insert(size_t pos,size_t n,char c);    //从pos位置开始插入n个字符c
    s1.insert(size_t pos,string s2,size_t indes,size_t n);        //从s1的0位置开始,把字符串s2的第2个位置复制3个字符插入

替换接口:
    replace(size_t pos,size_t n,string s);    //从索引为pos的位置替换n个字符换为 s
    replace(iterator begin,iterator end,string s); //将迭代器所指范围内的字符替换为 s

交换函数:
    s.swap(s1);
    swap(s1,s2);


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值