STL概况
STL(Standard Template Library,标准模板库)是C++针对数据结构和算法提出的一套标准
广义上分为:
容器(container)、算法(algorithm)、迭代器(iterator)
容器和算法之间通过迭代器衔接
STL六大组件
容器、算法、迭代器、仿函数、适配器、空间配置器
- 容器,各种数据结构,如vector、list、deque、set、map
- 算法,如sort、find、copy、for_each
- 迭代器,用来衔接容器和算法
- 仿函数,行为类似函数,可以用来作为算法的某种策略(就是重载小括号())
- 适配器,用于修饰容器、仿函数或迭代器的东西
- 空间适配器,负责空间的配置和管理
容器
分为序列式容器和关联式容器两种
序列式容器:强调值的排序,序列式容器中的每个元素都有固定的位置
关联式容器:二叉树结构,没有严格的位置
算法
分为质变算法和非质变算法
质变算法:运算期间改变区间内元素(如插入拷贝删除)
非质变算法:不改变(如查找遍历)
迭代器
每个容器都有自己专属的迭代器
类似指针
种类 | 功能 | 支持运算 |
---|---|---|
输入迭代器 | 只读访问 | 只读++、==、!= |
输出迭代器 | 只写访问 | 只写++ |
前向迭代器 | 读写,向前推进 | 读写++、==、!= |
双向迭代器 | 读写,向前向后都行 | 读写++、– |
随机访问迭代器 | 读写,可以跳跃+3+4+5都行,功能最强 | 读写++、–、[n]、-n、<、<=、>、>= |
常用双向迭代器和随机访问迭代器
Vector的迭代
#include <iostream>
#include<vector>
#include<algorithm>//标准算法库
using namespace std;
void myPrint(int val) {
cout << val << endl;
}
void test01() {
//初始化了一个vector,实际上是int类型的数组
vector<int> v;
//向容器插入数据
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
//第一种遍历
//通过vector的迭代器遍历数组
vector<int>::iterator itBegin = v.begin();// v.begin()指向的是容器中起始元素
vector<int>::iterator itEnd = v.end();//同理,注意,v.end()指向的是容器中最后一个元素的下一个位置
while (itBegin != itEnd) {
cout << *itBegin << endl;//跟指针的写法一模一样
itBegin++;
}
//第二种遍历
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << endl;
}
//第三种遍历,利用STL提供的for_each
for_each(v.begin(), v.end(), myPrint);//这里传入函数名,回调了myPrint
}
Vector存放自定义数据类型
存放的对象
class Person {
public:
Person(string name,int age) {
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
};
void myprint(Person& p) {
cout << p.m_name << " " << p.m_age << endl;
}
void test01() {
vector<Person> v;
Person p1("alex", 20);
Person p2("jim", 16);
Person p3("peter", 34);
Person p4("king", 26);
Person p5("gog", 66);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);
//遍历数据
for (vector<Person>::iterator it = v.begin(); it < v.end(); it++) {
//cout << (*it).m_name << " " << (*it).m_age << endl;
cout << it->m_name << " " << it->m_age << endl;
}
for_each (v.begin(), v.end(), myprint);
}
存放的是指针
void test02() {
//当vector存放的是Person的指针
vector<Person *> v;
Person p1("alex", 20);
Person p2("jim", 16);
Person p3("peter", 34);
Person p4("king", 26);
Person p5("gog", 66);
v.push_back(&p1);
v.push_back(&p2);
v.push_back(&p3);
v.push_back(&p4);
v.push_back(&p5);
//遍历数据
for (vector<Person *>::iterator it = v.begin(); it < v.end(); it++) {
cout << (*it)->m_name << " " << (*it)->m_age << endl;//尖括号里是什么类型,it就是指向这个类型的指针
}
}
Vector中嵌套容器
void myloop(vector<int> &v) {
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << endl;
}
}
void test01() {
vector<int> v1;
vector<vector<int>> v2;
//初始化v1
for (int i = 0; i < 5; i++) {
v1.push_back(i);
}
//初始化v2
for (int i = 0; i < 2; i++) {
v2.push_back(v1);//v2里存放了两个v1
}
for_each(v2.begin(), v2.end(), myloop);
}
其中,遍历外层vector使用了for_each,遍历内层vector使用了for
string容器
本质
string是c++风格的字符串,本质是一个类
- char*是一个指针
- string是一个类,类内部封装了char*,管理这个字符串,是一个char*类型的容器
特点
string封装了如find、copy、delete、replace、insert的成员方法
不用担心内存越界
构造函数
- string() 空的string容器
- string(const char* s ) 用字符串s初始化
- string(const string& str) 用另一个string容器初始化
- string(int n,char c) 用n个字符初始化
void test01() {
string s1;
const char* str = "helloworld";
string s2(str);
cout << "s2 " << s2 << endl;
string s3(s2);
cout << "s3 " << s3 << endl;
string s4(10, '0');
cout << "s4 " << s4 << endl;
}
string容器的赋值
assign有妙用
string容器的字符串拼接
append有妙用
string容器的字符串查找和替换
- find查找是从左往右,rfind是从右往左
- find找到字符串后返回第一个字符位置,找不到返回-1
- replace是指定从哪个位置起,多少个字符,替换成什么样的字符串
string容器的字符串比较
原理
将字符串转换为ASCII计算
string容器的字符串字符存取
- 重载中括号[]
- at()方法
string容器的字符串插入和删除
插入和删除都是从下标0开始的
Vector容器
非常类似数组,又称单端数组
可以动态扩展,动态扩展不是在原空间向后扩展,而是开辟新空间,拷贝源数据,释放原空间
Vector的迭代器是支持随机访问的迭代器
vector的构造
void myPrint(int val) {
cout << val << " ";
}
void test01() {
//用10个1初始化vector
vector<int> v1(10,1);
for_each(v1.begin(), v1.end(), myPrint);
cout << endl;
//用区间初始化
vector<int> v2(v1.begin(), v1.end());//实际上和v1相同
for_each(v2.begin(), v2.end(), myPrint);
cout << endl;
//拷贝构造
vector<int> v3(v2);
for_each(v3.begin(), v3.end(), myPrint);
cout << endl;
}
vector的赋值
//=赋值
for (vector<int>::iterator it = v2.begin(); it != v2.end(); it++) {
*it = 2;
}
//assign赋值
v3.assign(5, 3);//assign会改变原容器的大小
vector的容量和大小
vector的插入删除
注意
insert和erase都要求传入的是一个迭代器,而不是index,迭代器也可以直接进行加法运算
void test01() {
vector<int> v(5, 1);
v.insert(v.begin() + 2, 100);
for_each(v.begin(), v.end(), myPrint);
}
vector容器互换
vector数据存取
vector预留空间
void test01() {
vector<int> v;
v.reserve(100000);
int num = 0;
int* p = NULL;
for (int i = 0; i < 100000; i++) {
v.push_back(i);
if (p != &v[0]) {
p = &v[0];
num++;
}
}
cout << num << endl;
}
deque容器
双端数组
对比Vector
- vector对于头部的插入删除效率低,数据量越大效率越低
- vector对内部元素的访问比deque快
push_front()、pop_front()
push_back()、pop_back()
void printDeque(deque<int>&d) {
for (deque<int>::iterator it = d.begin(); it != d.end(); it++) {
cout << *it << endl;
}
}
//通常,为了放置在引用对象的过程中修改了对象的属性,使用const_iterator
void printDeque2(const deque<int>& d) {
for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
//*it = 100;//const就不能改变属性了
cout << *it << endl;
}
}
void test01() {
deque<int> d1;
for (int i = 0; i < 5; i++) {
d1.push_back(i);
}
printDeque(d1);
printDeque2(d1);
}
deque构造函数
deque赋值
deque插入和删除
deque数据存取
deque排序
案例:评委打分
需求
如果想要同时改变形参和实参,就需要用引用(&)的方式传入数据
实现
class Person {
public:
Person(char name, int score=0) {
this->m_name = name;
this->m_score = score;
}
char m_name;
int m_score;
};
void printPerson(Person &p) {
cout << "name: " << p.m_name << " " << "score: " << p.m_score;
}
void test01() {
//随机数种子
srand((unsigned int)time(NULL));
//初始化5个选手
vector<Person> pList;
string sName = "ABCDE";
for (int i = 0; i < 5; i++) {
Person p(sName[i]);
pList.push_back(p);
}
for (vector<Person>::iterator it = pList.begin(); it != pList.end(); it++) {
//10名评委打分
deque<int> pScore;
for (int j = 0; j < 10; j++) {
int score = rand() % 41 + 60;
pScore.push_back(score);
}
//掐头去尾,计算平均分,绑定到选手
pScore.pop_front();
pScore.pop_back();
int sum = 0;
for (deque<int>::iterator it2 = pScore.begin(); it2 != pScore.end(); it2++) {
sum += *it2;
}
int avg = sum / pScore.size();
(*it).m_score = avg;
}
for (vector<Person>::iterator it = pList.begin(); it != pList.end(); it++) {
cout << "name: " << (*it).m_name << " score: " << (*it).m_score << endl;
}
}
Stack容器
栈没有遍历!
class Person {
public:
Person(char name, int score=0) {
this->m_name = name;
this->m_score = score;
}
char m_name;
int m_score;
};
void printPerson(Person &p) {
cout << "name: " << p.m_name << " " << "score: " << p.m_score << endl;
}
void test01() {
stack<Person> sta;
//初始化5名乘客
string pName = "ABCDE";
for (int i = 0; i < 5; i++) {
Person p(pName[i], 0);
//入栈
sta.push(p);
}
//只要栈不为空,就读取栈顶并出栈
while (!sta.empty()) {
Person p=sta.top();
printPerson(p);
sta.pop();
}
}
Queue容器
class Person {
public:
Person(char name, int score=0) {
this->m_name = name;
this->m_score = score;
}
char m_name;
int m_score;
};
void printPerson(Person& p) {
cout << "name: " << p.m_name << " " << "score: " << p.m_score << endl;
}
void test01() {
queue<Person> que;
string pName = "ABCDE";
for (int i = 0; i < 5; i++) {
Person p(pName[i], 0);
que.push(p);
}
while (!que.empty()) {
Person p=que.front();
printPerson(p);
que.pop();
}
}
List容器
链表将数据链式存储
由一系列结点组成,结点包括数据域和指针域
STL的链表是双向循环链表
优缺点
优点:可以对任意位置快速插入和删除
缺点:遍历速度没有数组快,占用空间比数组大
因为链表是链式存储,所以链表的迭代器只支持前移和后移
链表的插入和删除不会让原迭代器失效
构造函数
void printList(const list<int> &l) {
for (list<int>::const_iterator it = l.begin(); it != l.end(); it++) {
cout << " " << *it << " ";
}
cout << endl;
}
void test01() {
list<int> l1;
for (int i = 0; i < 5; i++) {
l1.push_back(i);
}
list<int> l2(l1.begin(), l1.end());
list<int> l3(l1);
list<int> l4(10, 200);
printList(l1);
printList(l2);
printList(l3);
printList(l4);
}
赋值和交换
大小操作
数据存取
反转和排序
所有不支持随机访问的容器,都不能调用标准算法库了的算法
void printList(const list<int> &l) {
for (list<int>::const_iterator it = l.begin(); it != l.end(); it++) {
cout << " " << *it << " ";
}
cout << endl;
}
bool myCompare(int val1, int val2) {
return val1 > val2;
}
void test01() {
list<int> l1;
l1.push_back(20);
l1.push_back(40);
l1.push_back(30);
l1.push_back(10);
l1.push_back(50);
cout << "排序前: " << endl;
printList(l1);
//所有不支持随机访问的容器,都不能调用标准算法库了的算法
//但是自己会内部实现各种算法
l1.sort();
cout << "排序后: " << endl;
printList(l1);
//降序排列
l1.sort(myCompare);
printList(l1);
}
案例:List排序
class Person {
public:
Person(string name, int age,int height) {
this->m_name = name;
this->m_age = age;
this->m_height = height;
}
string m_name;
int m_age;
int m_height;
};
void printPerson(Person& p) {
cout << "name: " << p.m_name << " " << " age: " << p.m_age <<" height: "<<p.m_height<< endl;
}
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() {
//排序案例:按照年龄升序,如果年龄相同,按照身高降序
Person p1("alex", 22, 187);
Person p2("jim", 24, 178);
Person p3("amy", 21, 165);
Person p4("jerry", 24, 198);
Person p5("fred", 19, 180);
list<Person> lp;
lp.push_back(p1);
lp.push_back(p2);
lp.push_back(p3);
lp.push_back(p4);
lp.push_back(p5);
//排序前
cout << "排序前: " << endl;
for (list<Person>::iterator it = lp.begin(); it != lp.end(); it++) {
printPerson((*it));
}
lp.sort(comparePerson);//这里要传入一个回调函数,必须是bool类型,以两个Person类的形参来指定规则
//排序后
cout << "排序后: " << endl;
for (list<Person>::iterator it = lp.begin(); it != lp.end(); it++) {
printPerson((*it));
}
}
Set/Multiset容器
特点
所有元素会在插入时自动排序,插入只有insert
本质
关联式容器,底层结构是二叉树实现的
区别
- set不允许重复元素(不报错,但是不会插入)
- multiset允许
因为set的insert方法会返回一个是否插入成功的bool
构造和赋值
大小和交换
插入和删除
set和multiset区别
Set容器排序
利用仿函数
//仿函数的类型
//具有类型“const MyCompare”的表达式会丢失一些 const - volatile 限定符以调用...
class MyCompare {
public:
bool operator() (int v1,int v2) const//仿函数要加上const限定
{
return v1 > v2;
}
};
void printSet2(set<int, MyCompare>& s1) {
for (set<int, MyCompare>::iterator it = s1.begin(); it != s1.end(); it++) {
cout << *it << endl;
}
}
//set容器排序
void test01() {
set<int, MyCompare> s1;
s1.insert(20);
s1.insert(10);
s1.insert(50);
s1.insert(70);
s1.insert(30);
printSet2(s1);
}
Set容器排序(自定义数据类型)
class Person {
public:
Person(string name, int age) {
this->pName = name;
this->pAge = age;
}
void printPerson() {
cout << "name: " << this->pName << " " << "age: " << this->pAge << endl;
}
string pName;
int pAge;
};
//自定义的数据类型通常都要实现仿函数
class MyCompare {
public:
bool operator() (const Person& p1,const Person& p2) const//仿函数要加上const限定
{
return p1.pAge > p2.pAge;
}
};
//set容器排序(自定义类型)
void test01() {
set<Person, MyCompare> s1;
Person p1("alex", 24);
Person p2("gog", 56);
s1.insert(p1);
s1.insert(p2);
for (set<Person, MyCompare>::iterator it = s1.begin(); it != s1.end(); it++) {
cout << (*it).pName<<" "<<(*it).pAge << endl;
}
}
Pair容器
- 不需要引用头文件
- 可嵌套使用
void test01() {
pair<int, string> p1(1, "first");
pair<int, string> p2(2, "second");
pair<pair<int, string>, pair<int, string>> p(p1, p2);
//取出第一个pair
cout << p.first.first << " " << p.first.second << endl;
cout << p.second.first << " " << p.second.second << endl;
}
map/multimap容器
简介
- map中所有元素都是pair
- pair<key,value>
- 所有元素都会根据key自动排序
本质
关联性容器,底层是二叉树
优点
按key查找高效
map和multimap区别
- map不允许重复
- multimap允许
构造和赋值
- 构造时<key,value>
- insert(pair<>)
class Person {
public:
Person(string name, int age) {
this->pName = name;
this->pAge = age;
}
void printPerson() {
cout << "name: " << this->pName << " " << "age: " << this->pAge << endl;
}
string pName;
int pAge;
};
//自定义的数据类型通常都要实现仿函数
class MyCompare {
public:
bool operator() (const Person& p1,const Person& p2) const//仿函数要加上const限定
{
return p1.pAge > p2.pAge;
}
};
void printMap(map<int, Person> &m1) {
for (map<int, Person>::iterator it = m1.begin(); it != m1.end(); it++) {
(*it).second.printPerson();
}
}
//set容器排序(自定义类型)
void test01() {
map<int, Person> m1;
Person p1("alex", 24);
pair<int, Person> pair1= make_pair(1, p1);
Person p2("gog", 56);
pair<int, Person> pair2 = make_pair(2, p2);
m1.insert(pair1);
m1.insert(pair2);
printMap(m1);
}
大小和交换
插入和删除
查找和统计
map排序
class Person {
public:
Person(string name, int age) {
this->pName = name;
this->pAge = age;
}
void printPerson() {
cout << "name: " << this->pName << " " << "age: " << this->pAge << endl;
}
string pName;
int pAge;
};
//自定义的数据类型通常都要实现仿函数
class MyCompare {
public:
bool operator() (const int& k1,const int& k2) const//仿函数要加上const限定
{
return k1>k2;
}
};
void printMap(map<int, Person, MyCompare> &m1) {
for (map<int, Person, MyCompare>::iterator it = m1.begin(); it != m1.end(); it++) {
(*it).second.printPerson();
}
}
void printMap2(map<int, Person>& m1) {
for (map<int, Person>::iterator it = m1.begin(); it != m1.end(); it++) {
(*it).second.printPerson();
}
}
//set容器排序(自定义类型)
void test01() {
map<int, Person,MyCompare> m1;
//map<int, Person> m1;
Person p1("alex", 24);
pair<int, Person> pair1= make_pair(1, p1);
Person p2("gog", 56);
pair<int, Person> pair2 = make_pair(2, p2);
Person p3("yum", 12);
pair<int, Person> pair3 = make_pair(3, p3);
Person p4("owen", 17);
pair<int, Person> pair4 = make_pair(4, p4);
m1.insert(pair1);
m1.insert(pair2);
m1.insert(pair3);
m1.insert(pair4);
printMap(m1);
}
STL案例-员工分组
需求
代码
#include <iostream>
#include<algorithm>
#include<vector>
#include<map>
#define CEHUA 0
#define MEISHU 1
#define YANFA 2
using namespace std;
class Person {
public:
Person() {}
Person(string name, int salary) {
this->pName = name;
this->pSalary = salary;
}
void printPerson() {
cout << "name: " << this->pName << " " << "salary: " << this->pSalary << endl;
}
string pName;
int pSalary;
};
void createWorker(vector<Person> &pList) {
string nameString = "ABCDEFGHIJ";
for (int i = 0; i < nameString.size(); i++) {
Person p;
p.pName = "员工";
p.pName += nameString[i];
p.pSalary = rand() % 10000 + 10000;
pList.push_back(p);
}
}
void printVector(vector<Person> &v) {
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
(*it).printPerson();
}
}
void setGroup(vector<Person> &v,multimap<int,Person> &mWorker) {
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
//产生一个部门的随机号
int deptId = rand() % 3;//0 1 2
//将员工插入到分组中
mWorker.insert(make_pair(deptId, (*it)));
}
}
void showWorker(multimap<int,Person> &mWorker) {
for (multimap<int, Person>::iterator it = mWorker.begin(); it != mWorker.end(); it++) {
//*it解出来是一个个pair
cout << "部门:" << (*it).first << " ";
(*it).second.printPerson();
}
}
void showDeptWorker(multimap<int, Person>& m, multimap<int, Person>::iterator& pos, int count,int index) {
for (; pos != m.end() && index < count; pos++, index++) {
cout << "部门:" << (*pos).first << " ";
(*pos).second.printPerson();
}
}
//set容器排序(自定义类型)
void test01() {
//创建10个员工
vector<Person> pList;
createWorker(pList);
测试是否添加成功
//printVector(pList);
//遍历vector容器,取出每个员工,进行随机分组
//分组后,员工编号作为key,具体员工作为value,放入multimap容器中
multimap<int, Person> mWorker;
setGroup(pList,mWorker);
显示所有员工信息
//showWorker(mWorker);
//分部门显示员工信息
multimap<int, Person>::iterator pos = mWorker.find(CEHUA);
int count = mWorker.count(CEHUA);
int index = 0;
showDeptWorker(mWorker, pos, count, index);
}
int main() {
srand((unsigned int)time(NULL));//每次都生成新的随机数种子
test01();
system("pause");
return 0;
}
STL函数对象
讲的就是仿函数,重载了()
class MyAdd {
public:
int operator()(int v1,int v2){
this->sum= v1 + v2;
return this->sum;
}
private:
int sum;
};
class MyPrint {
public:
MyPrint() {
this->count = 0;
}
void operator()(string s) {
cout << s << endl;
this->count++;
}
int count;
};
void doPrint(MyPrint &mp,string s) {
mp(s);
}
void test01() {
MyAdd myAdd;
//仿函数可以像普通函数一样调用
cout << myAdd(10, 10) << endl;
}
void test02() {
//仿函数跟普通函数不同,仿函数可以有自己的状态,如可以统计自己被调用了多少次
MyPrint mp;
mp("hello world");
mp("hello world");
mp("hello world");
mp("hello world");
mp("hello world");
cout << "执行了: " << mp.count << "次" << endl;
}
void test03() {
//仿函数对象可以作为参数传递
MyPrint mp;
doPrint(mp, "hello alex");
}
谓词
返回bool类型的仿函数被称为谓词
一元谓词
重载()接收一个参数的时候
//一元谓词
class GreaterFive {
public:
//由于遍历期间会取出来每一个数据,这里要挨个接受,所以要指定形参v1
bool operator()(int v1) {
return v1 > 5;
}
};
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
//查找v中大于5的元素
//GreaterFive()是一个匿名的对象
vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
for (it; it < v.end(); it++) {
if (it == v.end()) {
cout << "未找到" << endl;
}
else
{
cout << *it << endl;
}
}
}
二元谓词
重载()接收两个参数的时候
//二元谓词
class MyCompare {
public:
bool operator()(int v1,int v2) {
return v1 > v2;
}
};
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
//sort默认是升序,这里要用谓词实现降序,这里的MyCompare()同样是匿名对象
sort(v.begin(),v.end(),MyCompare());
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
STL内建函数对象
三类
- 算数仿函数
- 关系仿函数
- 逻辑仿函数
算数仿函数
#include<functional>
void test01() {
//一元,negate
negate<int> n;
cout << n(10) << endl;
//二元,plus
plus<int> pl;
cout<<pl(10, 10) << endl;
}
关系仿函数
#include<functional>
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
//调用内建的关系仿函数来实现降序排序
sort(v.begin(), v.end(), greater<int>());
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
逻辑仿函数
#include<functional>
void test01() {
vector<bool> v;
v.push_back(false);
v.push_back(true);
v.push_back(false);
v.push_back(true);
for (vector<bool>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
//利用逻辑非 取反
vector<bool> v2;
v2.resize(v.size());
transform(v.begin(), v.end(), v2.begin(), logical_not<bool>());
for (vector<bool>::iterator it = v2.begin(); it != v2.end(); it++) {
cout << *it << " ";
}
cout << endl;
}