一、STL的基础知识
1.STL基本概念
···STL(Standard Template Library)标准模板库
···STL的诞生是为了建立数据结构和算法的一套标准
···STL从广义上分为:容器(container)算法(algorithm)迭代器(iterator)
···容器和算法之间通过迭代器进行无缝连接
···STL几乎所有的代码都采用了模板类或者模板函数
2.STL六大组件
STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
···容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
···算法:各种常用的算法,如sort、find、copy、for_each等
···迭代器:容器和算法之间沟通的桥梁
···仿函数:行为类似函数,可作为算法的某种策略
···适配器:用于修饰容器或者仿函数或者迭代器接口
···空间配置器:负责空间的配置与管理
(1)容器
STL容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组、链表、树、栈、队列、集合、映射表等
容器分为 序列式容器 和 关联式容器 两种:
序列式容器:强调值的排序,序列实容器中的每个元素均有固定的位置,也就是说容器中数据的排序是按传入顺序排序的。
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系,也就是说容器中数据的排序是按某种规则排序的,比如升序、降序。
(2)算法
算法(algorithms) 是用有限的步骤 来解决逻辑上或数学上的问题
算法分为:质变算法 和 非质变算法
质变算法:在运算过程中会更改区间内的元素的内容,例如拷贝、替换、删除等等
非质变算法:在运算过程中不会更改区间内的元素内容,如查找、计数、遍历等等
(3)迭代器
迭代器(iterator) 提供方法使得 算法 能够访问某个容器所含的各个元素、而又无需暴露该容器的内部表示方式。每个容器都有自己专属的迭代器
迭代器的使用非常类似于指针
种类 | 功能 | 支持运算 |
输入迭代器 | 对数据只读访问 | 支持++、==、!= |
输出迭代器 | 对数据只写访问 | 支持++ |
前向迭代器 | 读写,并能向前操作(++) | 支持++、==、!= |
双向迭代器 | 读写,并能向前和向后操作(++和 - -) | 支持++、- - |
随机访问迭代器 | 读写,可以以跳跃的方式访问任意数据,是功能最强大的迭代器 | 支持++、- -、[n]、-n、<、<=、>、>= |
常用的容器中迭代器种类为双向迭代器 和 随机访问迭代器,所以只学习这两个迭代器
3.STL的运用
假设我们要使用vector容器,for_each算法,它们的迭代器为vector<int>::iterator
例:
#include <vector> //使用前要包含头文件
#include <algorithm>
int main()
{
vector<int> v1; //创建一个vector容器
//向容器中插入数据
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
//通过迭代器访问容器中的数据
vector<int>::iterator itBegin = v1.begin(); //起始迭代器 指向容器中第一个元素
vector<int>::iterator itEnd = v1.end(); //结束迭代器 指向容器中最后一个元素的下一个位置
//第一种遍历方式
while (itBegin != itEnd)
{
cout << *itBegin << endl;
itBegin++;
}
//第二种遍历方式
for(vector<int>::iterator it = v1.begin(); it !=v1.end(); it++)
{
cout << *it << endl;
}
//第三种遍历方式,利用STL提供的for_each算法
for_each( v1.begin(), v1.end() , [ ] ( int a ){ cout << a << endl;});
return 0;
}
容器可以存放自定义类型数据,容器中也能嵌套容器,例如vector嵌套vector,就是一个二维数组
例:
#include <vector> //使用前要包含头文件
#include <algorithm>
int main()
{
vector<vector<int>>V; //创建一个大容器V
//创建小容器
vector<int> v1;
vector<int> v2;
vector<int> v3;
//向小容器插入数据
for(int i = 0 ; i < 4 ; i++)
{
v1.push_back(i+1);
v2.push_back(i+2);
v3.push_back(i+3);
}
//将小容器插入大容器中
V.push_back(v1);
V.push_back(v2);
V.push_back(v3);
//遍历大容器中的数据
for(vector<vector<int>>::iterator it = V.begin(); it != V.end() ; it++)
{
for(vector <int>::iterator it2 = it->begin(); it2 != it->end(); it2++) // it此时表示小容器
{
cout << *it2 << " ";
}
cout<<endl;
}
return 0;
}
二、string容器
本质:string是C++风格的字符串,而string本质上是一个类,类内部封装了char*指针,管理这个字符串,是一个char*型容器。
特点:string类内部封装了很多成员方法,例如:find、copy、delete、replace、insert;string管理char*所分配的内存,不用担心赋值越界和取值越界,由类内部进行负责
1.string的构造
int main()
{
string s1; //默认构造,创建空的字符串
const char * ch = "hello world";
string s2(ch); //使用字符串ch初始化
string s3(s2); //拷贝构造
string s4(10 , 'a') //构造连续10个a的字符串
}
2.string的赋值
string str1;
str1 = "hello world";
string str2;
str2 = str1;
string str3;
str3 = "a";
string str4;
str4.assign("hello world");
string str5;
str5.assign("hello world",5); // 截取前5个字符
string str6;
str6.assign(str1);
string str7;
str7.assign(5,'a'); // 赋值5个a
3.string的字符串拼接
string str1 = "hello";
str1 += " world";
string str2;
str2 += str1;
string str3;
str3.append("hello world");
string str4;
str4.append("hello world", 5); //截取前五个字符
string str5;
str5.append(str1, 5 , 6); //截取从第5个字符开始,截取6个字符
4.string的查找和替换
查找:查找指定字符串是否存在 替换:在指定的位置替换字符串
string str1="Hello World";
//查找
str1.find("World"); //查找并返回World的位置下标,找不到则返回-1
str1.rfind("World");//rfind是从右往左查找并返回World的位置下标
//替换
str1.replace(0,5,"Hi");//第0到第五个字符替换成Hi
5.string的字符串比较
比较方式:
字符串比较是按字符的ASCⅡ码进行对比,从左到右一个个比较字符
···相等=返回0
···大于>返回1
···小于<返回-1
string str1="Hello";
string str2="World";
str1.compare(str2);//返回两个字符串比较的结果
6.string的访问和修改
string str1="Hello";
//通过 [ ]访问单个字符串
for(int i = 0; i < str1.size(); i++)
{
cout << str1[i];
}
//通过at访问单个字符串
for(int i = 0; i < str1.size(); i++)
{
cout << str1.at(i);
}
//通过substr访问字符串的子串
str1.substr(0,2); //访问第一到第三这个区间的子串
//通过[ ]修改单个字符串
str1[0] = 'h'; //修改第一个字符
//通过at修改单个字符串
str1.at(3) = 'L'; //修改第四个字符
7.string的插入和删除
string str1="Hello";
str1.insert(4," World"); //从第五个元素开始插入" World"
str1.erase(4,10); //删除从第五个元素开始到第十个元素之间的字符
三、vector容器
vector容器和数组非常相似,也成为单端数组,不同在于vector可以动态扩展,而数组是静态空间大小规定后不能改变。
动态扩展并不是在原空间后接新空间,而是寻找更大的空间,然后将原数据拷贝到新空间,释放原空间。
vector容器的迭代器是支持随机访问的迭代器
1.vector的构造
vector<int> v1; //默认构造 无参构造
vector<int> v2(v1.begin(),v1.end()); //通过区间方式进行构造
vector<int> v3(10,50); //通过n个elem方式构造,赋予10个50
vector<int> v4(v3); //拷贝构造
2.vector的赋值
vector<int>v1;
for (int i = 0; i< 10 ; i++)
{
v1.push_back(i); //依次插入元素i
}
vector<int>v2;
v2 = v1; //把v1的数据都赋予v2
vector<int>v3;
v3.assign(v1.begin(),v1.end());
vector<int>v4;
v4.assign(10 , 50) //赋予10 个 50
3.vector的容量和大小
函数原型:
empty(); //判断容器是否为空
capacity(); //容器的容量
size(); //返回容器中元素的个数
resize(int num); //重新指定容器的大小为num
//若容器变长,则默认值填充新位置,若容器变短,则超出容器长度的元素被删除
resize(int num,elem); //重新指定容器的大小为num
//若容器变长,则以elem填充新位置,若容器变短,则超出容器长度的元素被删除
4.vector的插入和删除
函数原型:
push_back(ele); //在尾部插入元素ele
pop_back(); //删除最后一个元素
insert(const_iterator pos , ele); //迭代器指向位置pos插入元素ele
insert(const_iterator pos , int coout,ele); //迭代器指向位置pos插入count个元素ele
erase(const_iterator pos); //删除迭代器指向位置pos的元素
erase(const_iterator start , const_iterator end); //删除迭代器从start到end之间的元素
clear(); //删除容器中所有元素
5.vector数据存取
函数原型:
通过at(idx); //返回索引idx所指的数据
通过运算符[idx]; //返回索引idx所指的数据
front(); //返回容器中第一个数据元素
back(); //返回容器中最后一个数据元素
6.vector互换容器
互换容器就是实现两个容器内元素进行互换
函数原型:
v1.swap(v2);将v2与本身的元素互换
7.vector预留空间
预留空间的意义在于减少vector在动态扩展容量时的扩展次数
函数原型:
reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问
四、deque容器
deque容器是一种双端数组,可以对头端进行插入删除操作。
deque和vector的区别:
···vector对于头部数据的插入删除效率低,数据量越大,效率越低
···deque相对而言,对头部的插入删除速度比vector快
···vector访问元素的速度比deque快
1.deque的构造
#include "iostream"
using namespace std;
#include "deque"
void printDeque( deque<int >&d)
{
for(deque<int>::iterator it = d.begin() ; it != d.end() ; it++)
{
cout<< *it <<" ";
}
}
int main()
{
deque<int> d1; //构造d1;
for(int i = 0 ; i < 10 ; i++)
{
d1.push_back( i ) ;
}
deque<int>d2(d1.begin() , d1.end() ); // 构造d2为d1的首元素到尾元素
deque<int>d3(10 , 5); //构造d3,10个5
deque<int>d4(d3); // 拷贝构造
printDeque(d1); cout<<endl;
printDeque(d2); cout<<endl;
printDeque(d3); cout<<endl;
printDeque(d4);
return 0;
}
2.deque的赋值
deque<int> d1; //构造d1;
for(int i = 0 ; i < 10 ; i++)
{
d1.push_back( i ) ; //依次插入元素
}
deque<int> d2;
d2 = d1; //使用赋值符号赋值
deque<int> d3;
d3.assign(d1.begin() , d1.end() ); //使用成员函数assign
deque<int> d4;
d4.assign(10 , 5); //使用assign赋予10个5
3.deque的大小操作
函数原型:
deque.empty(); //判断容器是否为空
deque.size(); //返回容器中元素的个数
deque.resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置
//若容器变短,则删除末尾超出长度的元素
deque.resize(num , elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置
//若容器变短,则删除末尾超出长度的元素
例:
deque<int> d1; //构造d1;
for(int i = 0 ; i < 10 ; i++)
{
d1.push_back( i ) ; //依次插入元素
}
if(d1.empty())
{
cout<<"d1为空" <<endl;
}
cout<<"d1的大小为"<<d1.size();
4.deque的插入和删除
函数原型:
两端插入操作:
···push.back(elem); //在容器尾部添加一个数据
···push.front(elem); //在容器头部插入一个数据
···pop.back(); //删除容器最后一个数据
···pop.front(); //删除容器第一个数据
指定位置操作:
···insert( pos , elem) ; //在pos位置插入一个elem元素,并返回新数据的位置
···insert( pos , n . elem); //在pos位置插入n个elem数据,无返回值
···inset( pos , beg ,end); //在pos位置插入[beg , end]区间的数据,无返回值
···clear(); //清空容器所有数据
```erase(beg , end) ; //删除[beg , end]区间的数据,返回下一个数据的位置
···erase(pos) ; //删除pos位置的数据,返回下一个数据的位置
5.deque的数据存取
函数原型:
···at(int idx); //通过at函数返回索引idx所指的数据
···[ idx ] ; //通过[]运算符返回索引idx所指的元素
···front() ; //返回容器中第一个数据元素
···back() ; //返回容器中最后一个数据元素
五、stack容器
1.stack(栈)基本概念
stack(栈)是一种先进后出的数据结构,它只有一个出口,栈中只有栈顶的元素可以被外界使用,因此栈不允许有遍历操作。
栈可以判断容器是否为空,可以返回元素个数
2.stack常用接口
构造函数:
···stack<T> stk; //默认构造
···stack(const stack &stk) ; //拷贝构造
赋值操作:
···stack& operator=(const stack &stk); //重载等号操作符
数据存取:
···push(elem) ; //向栈顶添加元素
···pop() ; //从栈顶移除第一个元素
···top() ; //返回栈顶元素
大小操作:
···empty() ; //判断堆栈是否为空
···size() ; //返回栈的大小
六、queue容器
1.queue(队列)基本概念
queue(队列)是一种先进先出的数据结构,他有两个出口。
队列从一端新增元素,从另一端移除元素。
队列中只有队头和队尾才可以被外界使用,因此队列中不允许有遍历行为。
队列中插入数据称为入队,移出数据称为出队。
2.queue常用接口
构造函数:
···queue<T> que ; //默认构造
···queue(const queue &que); //拷贝构造
赋值操作:
···queue& operator=(const queue &que); //重载等号操作符
数据存取:
···push(elem) ; //向队尾添加元素
···pop() ; //从队头移除一个元素
···back(); //返回最后一个元素
···front(); //返回第一个元素
大小操作:
···empty() ; //判断队列是否为空
···size() ; //返回队列的大小
七、list容器
1.list(链表)基本概念
list(链表)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的。
链表是由一系列结点组成的。
链表的结点由存储数据元素的数据域 和 存储下一结点地址的指针域 组成。
STL中的链表是一个双向循环链表。
优点:
可以对任意位置进行快速插入和删除
采用动态存储分配,不会造成内存浪费和溢出
缺点:
容器遍历速度没有数组快,占用空间比数组大
2.list容器的常用接口
构造函数:
···list<T> lst1; //默认构造
···list(beg , end) ; //将[beg , end]区间中的元素拷贝给本身
···list(n , elem); //将n个elem元素拷贝给本身
···list(const list &lst); //拷贝构造函数
赋值与交换:
···assign(beg , end) ; // 将[beg , ned]区间中的数据拷贝赋值给本身
···assign(n , elem) ; //将n个elem拷贝赋值给本身
···list& operator=(const list &lst); //重载等号操作符
···swap(lst) ; //将lst与本身元素互换
大小操作:
···size(); //返回容器中元素的个数
···empty() ; //判断容器是否为空
···resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置
//若容器变短,则删除末尾超出长度的元素
···resize(num , elem); //重新指定容器的长度为num,若容器变长,则以elem填充新位置
//若容器变短,则删除末尾超出长度的元素
插入操作:
···push_back(elem); //在容器尾部添加一个元素
···push_front(elem); //在容器头部插入一个元素
···insert(pos , elem); //在pos位置插入elem元素,返回新数据的位置
···insert(pos , n ,elem); //在pos位置插入n个elem元素,无返回值
···insert(pos, beg , end); //在pos位置插入[beg ,end]区间的数据,无返回值
删除操作:
···pop_back() ;// 删除容器中最后一个元素
···pop_front(); //从容器开头移除第一个元素
···clear(); //删除容器所有数据
```erase(beg , end) ; //删除[beg , end]区间的数据,返回下一个数据的位置
···erase(pos) ; //删除pos位置的数据,返回下一个数据的位置
···remove(elem); //删除容器中所有与elem值匹配的元素
存取操作:
···front(); //返回第一个元素
···back(); //返回最后一个元素
反转和排序操作:
···reverse() ; //反转链表
···sort() ; //链表排序
八、set / multiset容器
set / multiset容器属于关联式容器,所有元素都会在插入时自动被排序,底层结构是用二叉树实现的。
1.set的构造和赋值
构造:
···set<T> st; //默认构造
···set(const set &st); //拷贝构造
赋值:
···set& operator = (const set &st); //重载等号操作符
2.set的大小和交换
···size(); //返回容器中元素的数目
···empty(); //判断容器是否为空
···swap(st); //交换两个集合容器
3.set的插入和删除
···insert(elem); //在容器中插入元素
···clear(); //清除容器所有元素
···erase(pos); //删除pos位置的元素,返回下一个元素的位置
···erase(beg , end); //删除[beg , end]区间上的元素,返回下一个元素的位置
···erase(elem); //删除容器中值为elem的元素
4.set的查找和统计
···find(key) ; //查找key是否存在,若存在,返回该元素的位置,若不存在,返回set.end();
```count(key); //统计key的元素个数
5.set和multise的区别
···set容器不允许容器中有重复的元素
···multiset允许容器中有重复的元素
···set插入数据的同时会返回插入结果,表示插入是否成功
···multiset不会检测数据,因此可以插入重复数据
6.pair对组创建
对组是成对出现的两个数据,利用对组可以返回两个数据
两种创建方式:
···pair<T , T> p (value1 , value2) ;
```pair<T , T> p = make_pair (value1 , value2) ;
例:
pair<string,int> p1("QHL",20);
cout << p1.first << "的年龄是:" << p1.second << endl;
pair<string,int> p2("HX",20);
cout<<p2.first<<"的年龄是:"<<p2.second<<endl;
九、map/multimap容器
map/multimap属于关联式容器,他们的所有元素都是pair,底层结构式用二叉树实现的。
···pair中的第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
···所有元素都会根据元素的键值自动排序,可以根据key值快速查找到value
···map不允许有重复key值的元素,而multimap可以
1.map的构造和赋值
···map<T , T> mp; //默认构造
···map(const map &mp); //拷贝构造
2.map的大小和交换
···size(); //返回容器中元素的个数
···empty(); //判断容器是否为空
···swap(st); //交换两个集合容器
3.map的插入和删除
···insert(elem); //在容器中插入元素
```clear(); //清除容器中所有的元素
```erase(pos) ; //删除pos位置上的元素,返回下一个元素的位置
```erase(beg , end) ; //删除[beg , end]区间的元素,返回下一个元素的位置
```erase(key) ; //删除容器中值为key的元素
4.map的查找和统计
find(key); //查找key是否存在,若存在,返回该key元素的位置,若不存在,返回set.end()
count(key); //统计key的元素个数