容器(string、vector、list、duque、set、map设置迭代器)

容器

string
vector
list
deque
map
set
multimap
mutilset

string

专门存放字符的动态的顺序表。

1.string类对象的常见构造

(constructor)函数名称功能说明
string()构造空的string类对象,即空字符串
string(const char* s)用C-string来构造string类对象
string(size_t n, char c)string类对象中包含n个字符c
string(const string&s)拷贝构造函数

2.string类对象的容量操作

函数名称功能说明
size返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty检测字符串释放为空串,是返回true,否则返回false
clear清空有效字符。只清空元素,不清理空间,不改变空间大小
reserve为字符串预留空间预留空间,只改变容量,不改变有效元素个数。扩大增容,底层容量大小改变。缩小不变
resize将有效字符的个数该成n个,多出的空间用字符c填充。1. 增大:n<s.capacity()-----直接填充。n>s.capacity()-----string类会自己扩容 2. 减小:直接改变size值 3. c不赋值默认为0

3.访问及遍历

  • perator[ ]:返回pos位置的字符,const string类对象调用
  • begin+end:begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器。左闭右开
  • rbegin+rend:begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器

4.修改操作

  • operator+=:在字符串后追加字符串str
  • c_str:返回C格式字符串
  • find+npos:从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置

5.非成员函数

  • operator>>:输入运算符重载
  • operator<< :输出运算符重载
  • getline:获取一行字符串
  • relational operators:大小比较

6.浅拷贝
概念:编译器只是将对象中的值拷贝过来值的拷贝

  • 如果对象中管理资源,就会导致多个对象共享同一份资源,当一个对象销毁时会将该资源释放掉,此时另一些对象不知道该资源已经被释放,所以当继续对资源进项操作时,就会发生访问违规。
  • 会引起一块资源销毁多次
  • 会导致内存泄漏

7.深拷贝
概念:给每个对象独立分配资源,保证多个对象之间不会因共享资源而造成多次释放程序崩溃问题

8.写时拷贝
浅拷贝+引用计数+在修改一个对象时分离对象。线程安全问题。

vector

存放任意类型的动态顺序表。
1.概念
表示可变大小数组的序列容器,连续的存储空间来存储元素

2.构造

  • 空构造函数
  • 区间构造
  • n个值data元素的构造
  • 拷贝构造

3.vector (Input Iterator first, Input Iterator last);:使用迭代器进行初始化构造

4.容量操作

  • size:获取数据个数
  • capacity:获取容量大小
  • empty:判断是否为空
  • resize:
    • resize(newSize,data): 将vector中有效元素个数更新到newSize,假设vector中有效元素个数为oldSize。
      • newSize>oldSize
        • newSize<=capacity:增多的元素使用data来进行填充
        • newSize>capacity:
          • 开辟新空间
          • 拷贝元素
          • 释放旧空间
      • newSize<=oldSize
        • 直接改变oldSize,不会缩小底层空间大小
    • data:
      • 内置类型:默认为0
      • 自定义类型:调用无参的构造函数
  • reserve:
    reserve(newCapacity):为vector预留空间,不会改变有效元素个数。
    • newCapacity<=oldCapacity:返回
    • newCapacity>oldCapacity:真正的进行扩容

5.增删改查

  • push_back:尾插
  • pop_back:尾删
  • find:查找
  • insert:在position之前插入val
  • erase:删除position位置的数据
  • clear:清空
  • swap:交换两个vector的数据空间
  • operator[]
    • T&operator[](size_t index)
      const T& operatr[](size_t index) const
    • 头部元素:
      T& front
      const T&front() const
    • 尾部元素:
      T& back()
      const T&back() const
    • ++/–:顺序往后移动

6.迭代器

  • 旧空间释放,重新赋值指向新空间即可:erase返回删除位置的下一个位置
  • 主要是提供给算法来使用,让算法可以透明化(不用关心底层的数据结构)的操作数据
    • begin+end
      • 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator。
      • 左闭右开
      • 从begin–>end移动
    • rbegin+rend
      • 从rend–>rbegin移动
  • 迭代器失效
    • typedef T* iterator
      • auto it=v.begin();v.push_back(data);:it实际已经指向vector底层空间的起始位置,通过it迭代器访问vector中的元素,代码会崩溃
    • 底层空间改变
      • push_back
      • insert resize
      • reserve
      • swap
      • assign
    • erase(pos):pos迭代器失效
    • 解决方式:给迭代器重新赋值

7.list与vector的对比
本质:顺序表和链表区别。

方面vectorlist
底层是一段连续空间是链式结构(带头结点的双向循环链表)
元素访问支持随机访问 O(1)不支持随机访问,访问任意位置的元素时必须要遍历 O(N)
插入、删除任意位置插入或者删除元素,需要搬移大量的元素,效率比较低 O(N)任意位置插入、删除元素,只需要改变指针的指向,效率比较高 O(1)
扩容在插入时,可能需要扩容:1. 申请新空间 2. 拷贝元素 3. 释放旧空间。因此,在插入时,如果没有提取将容量给出,效率会更低不需要扩容
使用场景高效存储+频繁的访问的情况在任意位置插入、删除操作比较多的情况
迭代器原生态指针,原生态的指针可以去遍历空间中的每个元素对原生态指针进行封装,可以将该迭代器按照指针的方式进行使用。达到方便遍历的目的
迭代器失效只要底层空间发生改变,所有的迭代器都会失效(push_back/insert/resize/reserve)插入、删除时,不重新赋值会导致失效插入不会导致失效,删除只会影响当前迭代器
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
接口不同支持随机访问(operator[]),与容量相关的接口有特殊的接口:merage、sort…
针对整型定义了一个 vector,插入 6 个元素,然后打印所有元素:

#include <iostream>
#include <vector>

using namespace std;

int main(int argc, char* argv[])
{
    vector<int> vecTemp;

    for (int i = 0; i<6; i++)
        vecTemp.push_back(i);

    for (int i = 0; i<vecTemp.size(); i++)
        cout << vecTemp[i] <<" "; // 输出:0 1 2 3 4 5

    return 0;
}

list

存放任意类型的数据。带头节点的双向循环链表

  • capacity相关
    • 头删
    • 头插
    • 尾删
    • 尾插
  • access相关
  • modify相关
  • 迭代器
    • 让算法对容器透明化(让算法对于任意的容器都可以操作)
    • 不能给成原生态指针
      • 如果给成原生态指针,没有办法++让迭代器往后移动到下一个节点的位置。需要对原生态的指针进行封装
    • 管理原生态指针 _pNode
      • 构造、拷贝构造
      • 让迭代器具有指针一样的操作
        operator*() /operator ->()
        正向迭代器重新封装。
      • 支持移动操作
        • operator++() /operator++(int)
        • operator–() /operator–(int)
        • 与正向迭代器相反
      • 比较操作
        operator!=() /operator==()
        与正向迭代器相同
    • 分类
      vector/string
      typedef T* iterator;
      底层是一段连续空间
    • ++/–:重载 operator++/–
  • typedef listlterator <> iterator
产生一个空 list,准备放置字符,然后将 'a' 至 'z' 的所有字符插入其中,利用循环每次打印并移除集合的第一个元素,从而打印出所有元素:

#include <iostream>
#include <list>

using namespace std;

int main(int argc, char* argv[])
{
    list<char> listTemp;

    for (char c = 'a'; c <= 'z'; ++c)
        listTemp.push_back(c);

    while (!listTemp.empty())
    {
        cout <<listTemp.front() << " ";
        listTemp.pop_front();
    }

    return 0;
}

优缺点:
优点:

  • 不使用连续内存完成动态操作。
  • 在内部方便的进行插入和删除操作
  • 可在两端进行push、pop

缺点:

  • 不能进行内部的随机访问,即不支持[ ]操作符和vector.at()
  • 相对于verctor占用内存多

deque

由一段一段的定量连续空间构成。
一旦要在 deque 的前端和尾端增加新空间,便配置一段定量连续空间,串在整个 deque 的头端或尾端。因此不论在尾部或头部安插元素都十分迅速。 在中间部分安插元素则比较费时,因为必须移动其它元素。deque 的最大任务就是在这些分段的连续空间上,维护其整体连续的假象,并提供随机存取的接口。

优点:

  • 支持随机访问,即 [] 操作和 .at(),查询效率高;
  • 可在双端进行 pop,push。
  • 在内部方便的进行插入和删除操作

缺点:

  • 不适合中间插入删除操作;
  • 占用内存多。

适用场景:适用于既要频繁随机存取,又要关心两端数据的插入与删除的场景。

声明了一个浮点类型的 deque,并在容器尾部插入 6 个元素,最后打印出所有元素。

#include <iostream>
#include <deque>

using namespace std;

int main(int argc, char* argv[])
{
    deque<float> dequeTemp;

    for (int i = 0; i<6; i++)
        dequeTemp.push_back(i);

    for (int i = 0; i<dequeTemp.size(); i++)
        cout << dequeTemp[i] << " "; // 输出:0 1 2 3 4 5

    return 0;
}
vector VS. list VS. deque:

a、若需要随机访问操作,则选择vector;
b、若已经知道需要存储元素的数目,则选择vector;
c、若需要随机插入/删除(不仅仅在两端),则选择list
d、只有需要在首端进行插入/删除操作的时候,还要兼顾随机访问效率,才选择deque,否则都选择vector。
e、若既需要随机插入/删除,又需要随机访问,则需要在vector与list间做个折中-deque。
f、当要存储的是大型负责类对象时,list要优于vector;当然这时候也可以用vector来存储指向对象的指针,同样会取得较高的效率,但是指针的维护非常容易出错,因此不推荐使用。

map

map 由红黑树实现,其元素都是 “键值/实值” 所形成的一个对组(key/value pairs)。每个元素有一个键,是排序准则的基础。每一个键只能出现一次,不允许重复。
map 主要用于资料一对一映射的情况,map 内部自建一颗红黑树,这颗树具有对数据自动排序的功能,所以在 map 内部所有的数据都是有序的。比如一个班级中,每个学生的学号跟他的姓名就存在着一对一映射的关系。

特点:
自动建立 Key - value 的对应。key 和 value 可以是任意你需要的类型。
根据 key 值快速查找记录,查找的复杂度基本是 O(logN),如果有 1000 个记录,二分查找最多查找 10次(1024)。
增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。
对于迭代器来说,可以修改实值,而不能修改 key。

  • 优点:使用平衡二叉树实现,便于元素查找,且能把一个值映射成另一个值,可以创建字典。
  • 缺点:每次插入值的时候,都需要调整红黑树,效率有一定影响。
  • 适用场景:适用于需要存储一个数据字典,并要求方便地根据key找value的场景。
#include "stdafx.h"
#include <iostream>
#include <map>
#include <string>

using namespace std;

int main(int argc, char* argv[])
{
    map<int, string> mapTemp;

    mapTemp.insert({ 5,"张三" });
    mapTemp.insert({ 3, "李四"});
    mapTemp.insert({ 4, "隔壁老王" });

    map<int, string>::iterator it;
    for (it = mapTemp.begin(); it != mapTemp.end(); it++)
    {
        printf("学号:%d 姓名:%s\n", (*it).first, (*it).second.c_str());
    }

    return 0;
}

/*
输出结果:
学号:3 姓名:李四
学号:4 姓名:隔壁老王
学号:5 姓名:张三
*/
set

set(集合)由红黑树实现,其内部元素依据其值自动排序,每个元素值只能出现一次,不允许重复。
set 中的元素都是排好序的,集合中没有重复的元素;
map 和 set 的插入删除效率比用其他序列容器高,因为对于关联容器来说,不需要做内存拷贝和内存移动。

  • 优点:使用平衡二叉树实现,便于元素查找,且保持了元素的唯一性,以及能自动排序。
  • 缺点:每次插入值的时候,都需要调整红黑树,效率有一定影响。
  • 适用场景:适用于经常查找一个元素是否在某群集中且需要排序的场景。
下面子演示 set(集合)的两个特点:

#include <iostream>
#include <set>

using namespace std;

int main(int argc, char* argv[])
{
    set<int> setTemp;

    setTemp.insert(3);
    setTemp.insert(1);
    setTemp.insert(2);
    setTemp.insert(1);

    set<int>::iterator it;
    for (it = setTemp.begin(); it != setTemp.end(); it++)
    {
        cout << *it << " ";
    }

    return 0;
}

输出结果:1 2 3。一共插入了 4 个数,但是集合中只有 3 个数并且是有序的。可见之前说过的 set 集合的两个特点,有序和不重复。

当 set 集合中的元素为结构体时,该结构体必须实现运算符 ‘<’ 的重载:

#include <iostream>
#include <set>
#include <string>

using namespace std;

struct People
{
    string name;
    int age;

    bool operator <(const People p) const
    {
        return age < p.age;
    }
};

int main(int argc, char* argv[])
{
    set<People> setTemp;

    setTemp.insert({"张三",14});
    setTemp.insert({ "李四", 16 });
    setTemp.insert({ "隔壁老王", 10 });

    set<People>::iterator it;
    for (it = setTemp.begin(); it != setTemp.end(); it++)
    {
        printf("姓名:%s 年龄:%d\n", (*it).name.c_str(), (*it).age);
    }

    return 0;
}

/*
输出结果
姓名:王二麻子 年龄:10
姓名:张三 年龄:14
姓名:李四 年龄:16 
*/ 

可以看到结果是按照年龄由小到大的顺序排列。另外 string 要使用c_str()转换一下,否则打印出的是乱码。

如何设置迭代器

  • 应该根据该数据结构的特性,实现一个迭代器类
  • 在该类中给迭代器类型取别名:iterator
    • 使用iterator将不同容器迭代器类型统一,可以降低用户使用成本
  • begin()/end()
    • 左闭右开
  • 正向迭代器
    • ++:从begin–>end
    • –:从end–>begin
  • 反向迭代器
    • ++:从end–>begin
    • –:从begin–>end
各容器的特点总结

vector 头部与中间插入和删除效率较低,在尾部插入和删除效率高,支持随机访问。
deque 是在头部和尾部插入和删除效率较高,支持随机访问,但效率没有 vector 高。
list 在任意位置的插入和删除效率都较高,但不支持随机访问。
set 由红黑树实现,其内部元素依据其值自动排序,每个元素值只能出现一次,不允许重复,且插入和删除效率比用其他序列容器高。
map 可以自动建立 Key - value 的对应,key 和 value 可以是任意你需要的类型,根据 key 快速查找记录。
在实际使用过程中,到底选择这几种容器中的哪一个,应该根据遵循以下原则:

1、如果需要高效的随机存取,不在乎插入和删除的效率,使用 vector。
2、如果需要大量的插入和删除元素,不关心随机存取的效率,使用 list。
3、如果需要随机存取,并且关心两端数据的插入和删除效率,使用 deque。
4、如果打算存储数据字典,并且要求方便地根据 key 找到 value,一对一的情况使用 map,一对多的情况使用 multimap。
5、如果打算查找一个元素是否存在于某集合中,唯一存在的情况使用 set,不唯一存在的情况使用 multiset。

各容器的时间复杂度分析

vector 在头部和中间位置插入和删除的时间复杂度为 O(N),在尾部插入和删除的时间复杂度为 O(1),查找的时间复杂度为 O(1);
deque 在中间位置插入和删除的时间复杂度为 O(N),在头部和尾部插入和删除的时间复杂度为 O(1),查找的时间复杂度为 O(1);
list 在任意位置插入和删除的时间复杂度都为 O(1),查找的时间复杂度为 O(N);
set 和 map 都是通过红黑树实现,因此插入、删除和查找操作的时间复杂度都是 O(log N)。

各容器的共性

各容器一般来说都有下列函数:默认构造函数、复制构造函数、析构函数、empty()、max_size()、size()、operator=、operator<、operator<=、operator>、operator>=、operator==、operator!=、swap()。

顺序容器和关联容器都共有下列函数:
begin() :返回容器第一个元素的迭代器指针;
end():返回容器最后一个元素后面一位的迭代器指针;
rbegin():返回一个逆向迭代器指针,指向容器最后一个元素;
rend():返回一个逆向迭代器指针,指向容器首个元素前面一位;
clear():删除容器中的所有的元素;
erase(it):删除迭代器指针it处元素。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值