目录
一、前言
- 学校课程进度太慢,自学的知识又太零碎,所以我决定每周写一篇这样成体系的文章,一来给自己一点压力、也方便记忆查阅,二来也便于后来人一栏CS全貌,方便学习。本套文章重点讲应用,多敲代码,不讲玄而又玄的纯理论,因为各种理论都是在具体的应用场景上提炼出来的,无论如何,根都在代码上。
- 刚好最近准备参加CCF考试,便找了几篇CCF的题解,发现凡是用C++写的答案,都要用上STL。想来STL也是C++学习者的基本功了。在此将最近搜集的资料整理一下。这篇文章是参考网友的文章而作,也部分参考了C++参考手册。
- 本文过长,需善用浏览器搜索功能,如果您感到不适,请点击 每周系统CS|第一周:C++ STL模板库系统讲解(多文章版)
- 笔者也是初学,疏漏之处望批评指正。
- 参考:王刚博客
二、STL大体总览
- STL是什么:Standard Template Library,标准模板库,就是大牛们用c++的模板机制写好的一整套供我们使用的库文件。图灵奖N.Wirth(沃斯)说得好:程序 = 算法 + 数据结构,这STL大概就是其中的数据结构了。
- STL怎么用:STL就是一堆写好的头文件,我们在使用时只需include一下自己需要的文件就OK,注意,引用STL是不需要加“.h”后缀。
- STL包含六大类:
- 容器(containers):容器是C++的机制,数组是C++唯一提供原生支持的容器。STL使用类模板的方式提供多种容器,使我们可以方便的进行各种数据存取。
- 适配器(adapters):以序列式容器为基础,提供的栈,队列和优先级队列等高级容器。
- 迭代器(iterators):类似于指针,每种容器有相应迭代器,它们用来统一容器的操作方式。
- 算法(algorithm):包含一系列常见算法。
- 空间配置器(allocator):其中主要工作包括两部分:1,对象的创建与销毁。2,内存的创建与释放。
- 仿函数(functor):仿函数又称为函数对象,其实就是重载了()操作符的struct,没有什么特别的地方。
接下来,我们将逐步学习这些优秀工具的使用方法。由于容器与适配器非常相似,所以我将他们合并,统一放到“容器”里来讲。
三、详细整理
一、容器与适配器
在总览中提到,容器就是存放数据的高级数据结构,数组是C++唯一提供原生支持的容器。STL中提供的容器(和适配器)有:
#include | Container Class |
<vector> //序列式 | vector, vector<bool> |
<deque> //序列式 | deque |
<list> //序列式 | list |
<map> //关联式 | map, multimap |
<set> //关联式 | set, multiset |
<stack> //适配 | stack |
<queue> //适配 | queue, priority_queue |
<string> | string |
|
|
序列式容器:内部元素是按照写入顺序排列的。
- vector(向量):自由的数组,底层数据结构就是数组,自动分配内存,可以像数组一样用索引(下标)直接存取元素,数组的尾部添加和移除元素很快,但在头部和中部插入元素比较耗时。
- deque(双端队列):更加自由的数组,底层数据结构还是数组,可以像数组一样用索引(下标)直接存取元素,在数组的头部和尾部插入和删除元素很快。
- list(列表):底层数据结构是双向链表,不能使用索引存取数据元素(需要按顺序走到要存取的元素),在任何位置插入和删除都很快,只需要简单的移动一下指针。
关联式容器:元素位置取决于特定的排序准则,和插入的顺序无关,底层数据结构为二叉树。
- set(集合):内部元素依据其值自动排序,set内相同的数值元素只能出现一次。
- multiset(多重集合):也即允许重复值出现的集合。
- map(映射):也即“提供索引”的集合,元素是成对的键值对,内部元素的值依据键自动排序,键只允许出现一次。
- multimap(多重映射):允许键对出现多次的map。
适配器:也即高级的序列式容器,出于功能需要,没有迭代器。
- stack(栈):先进后出,可以使用序列式容器中的vector,deque,list中的任意一种作为其底层的数据结构。默认基于deque。
- queue(队列):先进先出,队列可以使用deque和list中的任意一种作为其底层的数据结构。默认基于deque。
- priority_queue(优先队列):对元素进行自动排序的队列,可以使用vector和deque来实现其底层结构,默认基于vector。
一、字符串 string
- string是STL封装的一个类,是一个容器。
- string不需要考虑内存,string会自动管理内存。
- string提供了一些列的成员函数,简化了字符串操作。
一、字符的初始化
// 构造一个空字符串
string str;
//用双引号初始化字符串
string str("HelloString");
string str = "HelloString";
// 重复某个字符n次
string str(10,'A');
//基于已有字符串初始化
string str(str1);
string str = str1;
二、操作符重载函数
1.赋值操作符(=)
str = "Hello!";
str = str2;
2.移位操作符(<< >>)
cout << str <<endl;//输出字符串
cin >> str;//标准输入到字符串str
3.数组下标操作符([]) 与数组操作相同
char c = str[5]; //取得某值
str[5] = 'A'; //赋值
4.加法运算符(+)
str1 = str2 + "World";//将字符串str2和"World"相加并赋值给str1
//注意:+左右两边需至少一边为字符串类型
5.加法赋值运算符(+=)
str1 += str2;//将字符串str1和str2相加,赋值给str1
三、常用成员函数
int len = str.length(); //获取字符串长度
bool isEmpty = str.empty(); //字符串是否为空,空返回1,非空返回0
bool result = str.compare(str2); //对比字符串,相同返回0,不同返回1
string str = str.substr(int pos, int num); //获取下标pos开始num个字符
int location = str.find(char* str,int pos); //返回str的首字符索引,从pos开始往后查找,没有则返回-1
rfind(char* str,int pos); //返回字符串str的首字符索引,从pos位置开始往前查找,没有返回-1
str.replace(int pos, int num, string str1);//将从索引pos开始的num个字符替换为str1
str.erase(int pos, int num); // 删除str从索引pos开始的num个字符
str.insert(int pos, string str1); // 在索引pos之后插入str1
str.insert(int pos, int times, char ch); // 在索引pos之后插入字符ch times次
str.assign(string str1); //...
str.assign(string str1, int num); //将str1开头的前num个字符赋值给str
str.assign(string str1, int pos, int num); //将str1从pos开始num个字符...
str.assign(int times, char 'c'); //...
str.append(string str1); //向str尾部追加str1
str.append(string str1, int pos, int num); // str追加str2的从第pos个开始的num个字符
str.append(int times, char ch); // str追加times个字符ch
二、vector
- vector是有程序自主管理长度的数组,即自由的数组。
- vector支持索引(下标)存取。
- vector容器在尾部插入和删除数据比较快,在中部或者头部插入或删除元素比较费时。
- 头文件:# include<vector>
一、初始化
1.vector支持多种类型
// 定义一个存放int类型的向量容器
vector<int> v1;
// 定义存放一个double类型的向量容器
vector<double> v2;
// 定义一个存放string类型的向量容器
vector<string> v3;
// 定义一个存放自定义类型的向量容器,该类型必须提供拷贝构造函数,因为容器的存放是按值复制的方式
vector<Student> v4;
2.vector的有参构造函数
// 使用数组初始化vector
int array[] = { 1,2,3,4,5,6,7,8,9 };
vector<int> v1(array, array + 5);
// 使用vector初始化vector
vector<int> v2(v1.begin(), v1.begin() + 3);
// 使用n个相同的元素初始化vector
vector<char> v3(n, 'A');
3.vector的拷贝构造函数
// 拷贝构造函数
vector<char> v2 = v1;
二、操作符重载函数
1.赋值操作符
// 赋值操作符重载
v2 = v1;
2.数组下标操作符
// 获取第i个元素
char c2 = v[i];
// 修改第二个元素
v[i] = 'X';
三、成员函数
1.vector尾部添加和尾部删除
// vector的尾部添加元素
v.push_back('A');
// vector的尾部删除元素
v.pop_back();
2.vector获取头部和尾部元素
// 获取vector的头元素和尾部元素
char first = v.front();
char last = v.back();
3.vector的长度
// 获取vector的长度
int v_size = v.size();
4.vector是否为空及清空元素操作
// 判断vector容器是否为空
bool isEmpty = v2.empty();
// vector容器的元素
v2.clear();
5.vector元素的获取和修改
// 获取第二个元素
char c2 = v[2];
// 修改第二个元素
v[2] = 'X';
6.vector的遍历
// 增强for遍历
for (int tmp : v1) {
cout << tmp << " ";
}
// 迭代器正向遍历
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
*it += 1;
cout << *it << " ";
}
// 迭代器逆向遍历
for (vector<int>::reverse_iterator it = v1.rbegin(); it != v1.rend(); it++) {
cout << *it << " ";
}
7.vector的插入
// 创建vector容器
vector<char> v(10, 'A');
// 在vector容器的第二个位置插入字符'X'
v.insert(v.begin() + 2, 'X');
// 在vector容器的最后位置插入5个'B'
v.insert(v.end(), 5, 'B');
8.vector的删除
// 删除第it个元素
v.erase(iterator it);
// 删除两个iterator之间的字符
v.erase(iterator it1,iterator it2);
五,vector容器添加自定义数据类型
1.定义老师类
#pragma once
# include<iostream>
using namespace std;
/* 定义老师类 */
class Teacher
{
private:
char * name;
int age;
public:
Teacher();
Teacher(char * name, int age);
/* 如果往容器中存入数据,必须定义拷贝构造函数 */
Teacher(const Teacher& teacher);
~Teacher();
public:
friend ostream& operator<<(ostream& out, Teacher& teacher);
};
2.老师类的实现
# define _CRT_SECURE_NO_WARNINGS
# include<iostream>
# include"Teacher.h"
using namespace std;
/* 无参构造 */
Teacher::Teacher()
{
this->name = NULL;
this->age = 0;
}
/* 有参构造 */
Teacher::Teacher(char * name, int age)
{
this->name = new char[strlen(name)+1];
strcpy(this->name, name);
this->age = age;
}
/* 拷贝构造函数 */
Teacher::Teacher(const Teacher& teacher)
{
this->name = new char[strlen(teacher.name)+1];
strcpy(this->name, teacher.name);
this->age = teacher.age;
}
/* 析构函数 */
Teacher::~Teacher()
{
if (this->name != NULL)
{
delete [] this->name;
this->name = NULL;
this->age = 0;
}
}
/* 友元函数:重载左移操作符 */
ostream& operator<<(ostream& out, Teacher& teacher)
{
cout << teacher.name << " = " << teacher.age;
return out;
}
3.向量容器存入老师对象
# include<iostream>
# include<vector>
# include"Teacher.h"
using namespace std;
int main()
{
// 定义容器
vector<Teacher> v;
// 定义5个老师对象
Teacher t1("刘备", 56);
Teacher t2("关羽", 45);
Teacher t3("张飞", 34);
Teacher t4("赵云", 30);
Teacher t5("马超", 25);
// 存入向量容器
v.push_back(t1);
v.push_back(t2);
v.push_back(t3);
v.push_back(t4);
v.push_back(t5);
// 遍历
for (Teacher t : v)
{
cout << t << endl;
}
return 0;
}
三、stack
- Stack(堆栈) 提供了堆栈的全部功能,是先进后出(FILO)的数据结构。
- 常用于代替函数递归,以降低资源消耗。
- #include <stack>
一、stack声明与初始化
stack<int> s = s1;
二、c++ stl stack的成员函数
s.empty(); //堆栈为空则返回真
s.push(); //在栈顶增加元素
s.top(); //返回栈顶元素
s.pop(); //移除栈顶元素
s.size(); //返回栈中元素数目
四、set
- set是一个集合容器,其中所包含的元素是唯一的,集合中的元素按照一定的顺序排列,元素是按照排序规则插入,不能指定插入位。
- set的底层数据结构是红黑二叉树,红黑树属于平衡二叉树。在插入操作和删除操作比vector快。
- set不能使用下标存取,需要使用迭代器。
- multiset允许插入多个重复的元素。
- 不可以直接修改set和multiset容器中的元素值,因为该类容器是自动排序的,如果想要修改某个元素的值,需要先删除该元素,然后再重新插入新的元素。
- # include<set>。
二,set和multiset的代码示例
1.set和multiset的基本比较
# include<iostream>
# include<set>
# include<string>
using namespace std;
int main()
{
// 定义set集合
set<string> s1;
// 定义multiset集合
multiset<string> s2;
// 往set集合中插入元素,其返回结果是一个pair,包含插入进去后的迭代器以及是否插入成功的标志
pair<set<string>::iterator, bool> p1 = s1.insert("Hello");
cout << *p1.first << "," << p1.second << endl;
// 我们发现往set集合中插入相同的元素会插入不成功
pair<set<string>::iterator, bool> p2 = s1.insert("Hello");
cout << *p2.first << "," << p2.second << endl;
// 遍历
for (string tmp : s1)
{
cout << tmp << endl;
}
// 我们往multiset集合中插入元素
s2.insert("Hello");
s2.insert("Hello");
for (string tmp : s2)
{
cout << tmp << endl;
}
return 0;
}
2.set集合的遍历
# include<iostream>
# include<string>
# include<set>
using namespace std;
int main02()
{
// 定义集合
set<string> s;
// 插入集合
s.insert("Hello");
s.insert("world");
s.insert("C++");
// 增强for遍历
for (string tmp : s)
{
cout << tmp << " ";
}
cout << endl;
// 迭代器正向遍历
for (set<string>::iterator it = s.begin(); it != s.end(); it++)
{
cout << *it << " ";
}
cout << endl;
// 迭代器逆向遍历
for (set<string>::reverse_iterator it = s.rbegin(); it != s.rend(); it++)
{
cout << *it << " ";
}
cout << endl;
return 0;
}
3.set集合的删除
# include<iostream>
# include<set>
# include<string>
using namespace std;
int main03()
{
// 创建集合
set<string> s;
// 插入元素
s.insert("Hello");
s.insert("World");
s.insert("Unreal");
s.insert("C++");
// 删除元素World
for (set<string>::iterator it = s.begin(); it != s.end();)
{
if (*it == "World")
{
it = s.erase(it);
}
else {
it++;
}
}
// 输出集合的长度
int size1 = s.size();
cout << "size1 = " << size1 << endl;
// 遍历元素删除
set<string>::iterator it = s.begin();
while (!s.empty())
{
cout << *it << endl;
it = s.erase(it);
}
// 输出集合的长度
int size2 = s.size();
cout << "size = " << size2 << endl;
return 0;
}
4.set常用的函数
s.insert(3);
s.insert(2);
s.insert(0);
s.insert(1);
// 高级for遍历元素
for (int tmp : s)
{
cout << tmp << " ";
}
// 统计元素出现的次数
int cs = s.count(0);
// 查找元素为2的迭代器
set<int>::iterator it = s.find(2);
5.自定义函数对象实现对学生类的排序
# include<iostream>
# include<string>
# include<set>
using namespace std;
/*
定义学生类,按照其年龄进行排序
*/
class Student
{
public:
string name;
int age;
public:
Student(string name, int age)
{
this->name = name;
this->age = age;
}
};
/* 重载了()操作符
定义函数对象:所谓的函数对象,就是的结构体
*/
struct StudentFunctor
{
bool operator()(const Student& stu1,const Student& stu2)
{
// 如果是>则是从大到小排序,如果是<则是从小到大排序
return stu1.age > stu2.age;
}
};
int main()
{
// 定义学生集合
set<Student,StudentFunctor> s;
// 插入学生
s.insert(Student("张飞",34));
s.insert(Student("关羽", 45));
s.insert(Student("赵云", 30));
s.insert(Student("马超", 21));
// 打印学生
for (Student stu : s)
{
cout << stu.name << " = " << stu.age << endl;
}
return 0;
五、map
- map是关联式容器,一个map是一个键值对序列,提供基于key的快速检索能力。
- map中key值是唯一的,集合中的元素按照一定的顺序排列,元素插入过程是按照排序规则插入的,不能指定插入位置。
- map的底层数据结构是红黑树变体的平衡二叉树。在插入和删除操作中比vector要快。
- map可以直接存取key所对应得value,支持[]操作符,如map[key] = value;
- multimap和map的区别:
- map中每个键只能出现一次,而multimap中相同的键可以出现多次
- map支持 [ ] 操作符,multimap不支持 [ ] 操作符。
- # include<map>
一、map和multimap的初始化
// map的无参构造函数
map<string,string> m1;
// multimap的无参构造函数
multimap<string, string> m2;
二、运算符重载
//插入值 multimap不支持[]操作
m["nokia"] = "Qt";
//修改值
m["nokia"] = "NewQt";
?multimap插入怎样?
三、成员函数
1.获取属性而不修改内容
//统计key,map可能结果为0或者1,multimap结果多变
int s = m.count("aaa");
//查找,根据key来查找,返回该key的迭代器 multimap返回第一个找到的值,it++是下一个
map<string,string>::iterator it = m.find("aaa");
//获取map的大小
int size = m.size();
2.修改map内容
//删除操作
map<string, string>::iterator it = m.begin();
it = m.erase(it);
/* 插入操作 */
m.insert(make_pair("aaa", "AAA"));
m.insert(make_pair("bbb", "BBB"));
// 插入方式1,返回结果是是否插入成功
pair<map<string,string>::iterator,bool> p1 = m.insert(pair<string, string>("Epic", "UnrealEngine"));
// 插入方式2,返回结果是是否插入成功
pair<map<string, string>::iterator, bool> p2 = m.insert(make_pair("Unity", "Unity3D"));
// 插入方式3,返回结果是是否插入成功
pair<map<string, string>::iterator, bool> p3 = m.insert(map<string, string>::value_type("Oracle", "Java"));
// 插入方式4
key下标//multimap不支持
//清除内容
m.clear();
四、迭代器
// 正向遍历
for (map<string, string>::iterator it = m.begin(); it != m.end(); it++) {
cout << it->first << " = " << it->second << endl;
}
// 反向遍历
for (map<string, string>::reverse_iterator it = m.rbegin(); it != m.rend(); it++) {
cout << it->first << " = " << it->second << endl;
}
//查找
//根据key来查找,返回该key的迭代器
//multimap返回找到的第一个键值对,it++是下一个找到的键值对
map<string,string>::iterator it = m.find("aaa");