目录
一、STL诞生的背景是什么?
为了建立一种可重复利用的东西,提高代码的复用性,因此建立了数据结构和算法的一套标准,即STL。
二、STL是什么?
STL(Standard Template Library),中文名标准模板库,大体分为六大组件,分别为:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器(由于本文章的重点在于STL中的容器,因此想更多知道STL的相关内容请上网搜索)。
三、STL中的算法与迭代器
想要学习容器,那算法与迭代器的了解必不可少。
1.算法分为两种:
内容 | 举例 | |
质变算法 | 运算过程中更改区间内的元素内容 | 拷贝,替换,删除等 |
非质变算法 | 运算过程中不改变区间内的元素内容 | 查找、计数、遍历、寻找极值等 |
2.迭代器:容器与算法之间的粘合剂
- 提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
- 每个容器都有自己专属的迭代器,迭代器的使用非常类似于指针,初学阶段可以先理解迭代器为指针。
- 通过迭代器(iterator)来访问容器中的数据:
std::vector<int>::iterator itBegin = v.begin();//起始迭代器 指向容器中第一个元素 std::vector<int>::iterator itEnd = v.end();//结束迭代器 指向容器中最后一个元素的下一个位置 //1.遍历方式 while (itBegin != itEnd) { std::cout << *itBegin << std::endl; itBegin++; } //2.第二种遍历 for (std::vector<int>::iterator it = v.begin(); it != v.end(); it++) { std::cout << *it << char(10); } //3.第三种遍历 利用STL提供的遍历算法 需包含头文件#include<algorithm> void myPrint(int val) { std::cout << val << char(10); } for_each(v.begin(),v.end(),myPrint);
四、STL中的容器
STL中的容器就是将运用最广泛的一些数据结构实现出来,哪怕你不懂这些数据结构,也可以通过STL容器进行使用,常用的数据结构为:数组,链表,树,栈,队列,集合,映射表等。容器的使用需包含所对应的头文件,使用string需包含#include<string>,使用vector需包含#include<vector>等。
五、容器的分类
常用容器 | 特点 |
string | c++中的字符串。字符串对象是一种特殊类型的容器,专门设计来操作的字符序列 |
vector(使用频率最高) | vector数据结构与数据非常相似,也称为单端数组,可以动态扩展 |
deque(双端队列) | 双端队列,可以对头端进行插入和删除,且内部有中控器来维护每段缓冲区中的内容 |
stack(栈) | 是一种先进后出的数据结构,只有一个出口,且不允许有遍历行为 |
queue(队列) | 是一种先进先出的数据结构,有两个出口,只有队头和队尾才可以被外界使用,不允许有遍历行为 |
list(链表) | 是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的,是一个双向循环链表 |
set/multiset | 所有元素在插入时会自动被排序,set中不允许有重复元素,miltiset中允许有重复的元素 |
map/multimap | map中所有元素都是pair,pair中第一个元素为key(键值),第二个元素为value(实值),所有元素都会根据元素的键值自动排序 |
string容器
- 基本概念:
(1)特点:string类内部封装了很多成员方法,管理char*所分配的内存,不用担心复制越界和取值越界等问题,由类内部进行负责
(2)本质:是c++风格的字符串,本质上是一个类 - 构造函数:
string s1(); 创建一个空的字符串 string s2(const char* str); 使用字符串str初始化 string s3(const string& str); 使用一个string对象(str)初始化另一个string对象 string s4(int n,char c) 使用n个字符c初始化
- 赋值操作:
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赋给当前字符串
- 字符串拼接:
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个字符连接到字符串结尾
- 字符串查找和替换:
//查找 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
- 字符串比较: 按照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;
- 字符串存取:
char& operator[](int n); 通过[]方式取字符 char& at(int n) 通过at方式获取字符
- 字符串长度:
str.size()
- 插入和删除:
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个字符
- 字符获取:
string substr(int pos=0,int n)const; 返回由pos开始的n个字符组成的字符串 string substr(int n) 返回第n个字符之后的字符串
vector容器
- 基本概念:
vector数据结构和数据结构非常相似,也称为单端数组,与普通数组的区别在于数组是静态空间,而vector可以动态扩展,动态扩展不是在原空间之后续接新空间,而是寻找更大的内存空间,然后将原数据拷贝到新空间中,释放原空间。
- 构造函数:
vector<T> v; 使用模板实现类实现,默认构造函数 vector(const vector &vec) 拷贝构造函数 vector(v.begin(),v.end()); 将v[begin(),end())区间中的元素拷贝给本身 前闭后开 /*std::vector<int>v; v.push_back(1); v.push_back(3); v.push_back(2); std::vector<int>v1(v.begin(), v.end()); */ vector(n,elem) 将n个elem拷贝给本身
- 赋值操作:
vector& operator=(const vector &vec); 重载等号操作符 assign(v.begin(),v.end()) 将[begin(),end())区间中的数据拷贝赋值给本身 //v2.assign(v1.begin(), v1.end()); assign(n,elem) 将n个elem拷贝赋值给本身
- 容量和大小:
empty() 返回值为bool 空为ture //判断容器是否为空 capacity() 容器的容量 /*capacity()>=size() 当容器的大小等于容量时,容器每次增加多少容量,取决于算法的实现*/ size() 返回容器中元素的个数 //若容器变短,则大小改变,容量不变 resize(int num) /*重新指定容器的长度为num,若容器变长,则以默认值0填充新位置 如果容器变短,则末尾超出容器长度的元素被删除*/ resize(int num,elem) /*重新指定容器的长度为num,若容器变长,则以elem值填充新位置 如果容器变短,则末尾超出容器长度的元素被删除*/
- 插入和删除:
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() 删除容器中所有元素
- 数据存取:
at(int idx) 返回索引dix所指的数据 operator[] 返回索引dix所指的数据 front(); 返回容器中第一个数据元素 back() 返回容器中最后一个数据元素
- 互换容器:
vector的容量是动态分配的,由STL内部算法实现,因此容量会大于大小,造成一定的空间浪费//实现两个容器内元素进行互换 swap(vec); //v1.swap(v2) 将v1和v2的元素进行互换 vector<int>(v).swap(v); //vector<int>(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<int>(v1).swap(v1); std::cout << v1.capacity() << std::endl; std::cout << v1.size() << std::endl; //输出 1049869 1000000 1049869 3 3 3
- 预留空间:减少vector在动态扩展容量时的扩展次数
reserve(int len); 容器预留len个元素长度(容量),预留位置不初始化,元素不可访问 //统计内存开辟次数 std::vector<int> 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
- 排序:
sort(v.begin(),v.end()); 需包含头文件#include<algorithm>
- 总结:
vector使用方便,在容器的使用频率是最高的,但是它的一个缺点就是当数据量大的时候,会额外占用很大的时间,因此建议在数据量大时,使用swap()来互换容器,减少空间的浪费。
deque容器
- 基本概念:
(1)双端数组,可以对头端进行插入和删除操作。
(2)和vector的区别:
vector对于头部的插入删除效率低,数据量越大,效率越低
deque相对而言,对头部的插入删除速度会比vector快
vector访问元素时的速度会deque快,这和两者的内部实现有关
(3)访问中间元素速度较慢;
(4)工作原理:
deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据,中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间
(5)迭代器支持随机访问
(6)设置为只读迭代器void printDeque(const std::deque<int>d) { for (std::deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { // *it = 100; 无法修改 std::cout << *it << " "; } }
- 构造函数:
deque<T>deqT; 默认构造形式 deque(const deque &deq) 拷贝构造函数 deque(begin(),end()); 构造函数将[begin(),end())区间中的元素拷贝给本身 deque(n,elem) 构造函数将n个elem拷贝给本身
- 赋值操作:
assign(n,elem) assign(begin(),end()) deque& operator=(const deque& deq)
- 大小操作:
deque没有容量的概念,只有大小deque.size() 返回容器中元素的个数 deque.empty() 判断是否为空 deque.resize(num,elem) /*重新指定容器的长度为num,若容器变长,则以elem填充新位置。 如果容器变短,则末尾超出容器长度的元素被删除*/ deque.resize(num); /*重新指定容器的长度为num,若容器变长,则以默认值填充新位置。 如果容器变短,则末尾超出容器长度的元素被删除*/
- 插入删除:
两端插入: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)区间的数据,返回下一个数据的位置
- 数据存取:
front(); 返回容器中第一个数据元素 operator[] 返回索引dix所指的数据 back() 返回容器中最后一个数据元素 at(int idx) 返回索引dix所指的数据
- 排序算法:
对于支持随机访问的迭代器的容器,都可以利用sort算法直接对其进行排序。#include<algorithm> sort(d.begin(),d.end()) 对beg和end区间内元素进行排序 从小到大
stack容器
- 基本概念:
(1)是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口;
(2)不允许有遍历行为,只有栈顶元素才能被外界访问到。 - 构造函数:
stack<T> stk stack采用模板类实现,stack对象的默认构造形式 stack(const stack &stk) 拷贝构造函数
- 赋值操作:
stack& operator=(const stack &stk) 重载等号操作符
- 数据存取:
push(elem) 向栈顶添加元素 pop() 从栈顶移除第一个元素 top() 返回栈顶元素
- 大小操作:
size() 返回栈的大小 empty() 判断堆栈是否为空
queue容器
- 基本概念:
(1)是一种先进先出(First In First Out,FIFO)的数据结构,有两个出口;
(2)队列容器允许从一端新增元素,从另一端移除元素;
(3)队列中只有队头和队尾才可以被外界使用,因此队列中不允许有遍历行为;
(4)队列中进数据称为入队,队列中出数据称为出队。 - 构造函数:
queue<T> stk queue采用模板类实现,queue对象的默认构造形式 queue(const queue &stk) 拷贝构造函数
- 赋值操作:
queue& operator = (const queue &stk) 重载等号操作符
- 数据存取:
back() 返回最后一个元素 front() 返回第一个元素 pop() 从队头移除第一个元素 push(elem) 向队尾添加元素
- 大小操作:
size() 返回队列的大小 empty() 判断队列是否为空
list容器(链表)
- 基本概念:
(1)功能:将数据进行链式存储;
(2)链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的;
(3)由一系列的结点组成,一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域;
(4)STL的链表是一个双向循环链表;
(5)由于链表的存储方式并不是连续的内存空间,因此链表中的迭代器只支持前移和后移,属于双向迭代器;std::list<int>::iterator it = l.begin(); it++;//支持双向 it--; //it = it + 1; 不支持
(6)优点:采用动态存储分配,不会造成内存浪费和溢出;
链表执行插入和删除十分方便快捷,修改指针即可,不需要移动大量元素。
(7)缺点:容器遍历速度没有数组快,得去找地址;
占用空间大,还得保存指针域。
(8)总结: 链表灵活,但是空间(指针域)和时间(遍历)额外耗费较大
(9)重要性质:插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的 - 构造函数:
list<T> lst list采用模板类实现,对象的默认构造形式 list(const list& lst) 拷贝构造函数 list(beg,end) 构造函数将[beg,end)区间中的元素拷贝给本身 list(n,elem) 构造函数将n个elem拷贝给本身
- 赋值和交换:
list& operator=(const list &lst) 重载等号操作符 assign(n,elem) 将n个elem拷贝赋值给本身 swap(lst) 将lst与本身的元素互换 assign(beg,end) 将[beg,end)区间中的数据拷贝赋值给本身
- 大小操作:
size() 返回容器中元素个数 empty() 判断容器是否为空 resize(num) /*重新指定容器的长度为num,若容器变长,则以默认值填充 如果容器变短,则末尾超出容器长度的元素被删除*/ resize(num,else) /*重新指定容器的长度为num,若容器变长,则以elem填充 如果容器变短,则末尾超出容器长度的元素被删除*/
- 插入和删除:
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值匹配的元素
- 数据存取:
(1)用++进行跳跃式访问,可以多写几个
(2)L[]和L.at()都无法访问list容器中的元素,List本质是链表,不是用连续线性空间存储数据,迭代器也是不支持随机访问front() 返回第一个元素 back() 返回最后一个元素
- 反转和排序:
(1)反转:reserve();
(2)排序:
sort() 从小到大 sort(myCompare) 从大到小 bool myCompare(int v1,int v2) { return v1>v2; } //回调函数 指定排序规则
- 排序案例:
/*将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)所有元素在插入时自动被排序;
(2)set/multiset属于关联式容器,底层结构是用二叉树实现;
(3)set不允许容器中有重复的元素,multiset允许容器中有重复的元素;
(4)包含set的头文件也可以使用multiset。 - 构造和赋值:
set& operator=(const set &st) set(const set &st) set<T> st
- 大小和交换:
empty() swap(st) size()
- 插入和删除:
erase(beg,end) erase(elem) 删除容器中值为elem的元素 erase(pos) clear() insert(elem)
- 查找和统计:
find(key) /*查找key是否存在,若存在,返回该键的元素的迭代器,若不存在,返回set.end()*/ std::set<int>::iterator pos = s1.find(1); if (pos != s1.end()) { std::cout << "找到元素" << *pos << std::endl; } count(key) /*统计key的元素个数 对于set而言要么是0要么是1*/
- set和multiset的区别:
(1)multiset不会检测数据,因此可以插入重复数据;
(2)set插入数据的同时会返回插入结果,表示插入是否成功;
(3)set不可以插入重复的数据,而multiset可以。 - 内置类型指定排序规则:
利用仿函数,可以指定排序规则 在未插入之前就指定排序规则 class MyCompare { public: bool operator()(int r1, int r2)const { return r1 > r2; } }; std::set<int,MyCompare>s2;
- 自定义数据类型指定排序规则:
自定义的数据类型都得指定排序规则,否则无法插入 class comparePerson { public: bool operator()(const Person& s1,const Person& s2)const { return s1.m_age > s2.m_age; } }; std::set<Person,comparePerson>s1;
pair使用-pair对组的创建
成对出现的数据,利用对组可以返回两个数据。
std::pair<type,type> p(value1,value2)
std::pair<std::string, int>p("Tom", 20);
std::cout << "姓名: " << p.first << " 年龄: " << p.second << std::endl;
std::pair<type,type> p = std::make_pair(value1,value2)
std::pair<std::string, int>p2 = std::make_pair("Jerry", 30);
std::cout << "姓名: " << p2.first << " 年龄: " << p2.second << std::endl;
map/multimap容器
- 基本概念:
(1)简介:map中所有元素都是pair;
pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值);
所有元素都会根据元素的键值自动排序。
(2)本质:map/multimap属于关联式容器,底层结构是用二叉树实现
(3)优点:可以根据key值来快速找到value值
(4)map和multimap的区别:map不允许容器中有重复key值,multimap允许容器中有重复key值元素。 - 构造和赋值:
map(const map &mp); map<T1,T2> mp; map& operator=(const map &map);
- 大小和交换:
size() empty() swap()
- 插入和删除:
insert(elem) m.insert(std::pair<int, int>(1, 10)); m.insert(std::make_pair(2, 20)); //好记 m.insert(std::map<int, int>::value_type(3, 30)); m[4] = 50; //[]不建议插入,但是可以利用key访问到value erase(beg,end) erase(key) erase(pos) clear()
- 查找和统计:
find(key) //查找key是否存在,若存在,返回该键的元素的迭代器,若不存在,返回map.end() count(key) //统计key的元素个数
- 排序:
(1)自定义的数据类型都得指定排序规则,否则无法插入;
(2)内置类型指定排序规则为从小到大,利用仿函数,可以指定排序规则。 - 总结:
map的key值既可以当作普通数组的下标来使用,也可以看成是一个会排序的万能下标,这个下标可以是整型,也可以是一个字符串,视使用情况而定。
图文参考:
https://www.bilibili.com/video/BV1et411b73Z?p=1&vd_source=dc3686d454b60a8bbdc03949afb2b41f(第一次写csdn,若各位大佬什么好的建议或者哪里写错了,还请不吝赐教!!)