STL概述
目的
为了建立数据结构和算法的一套标准,并降低它们之间的耦合关系,以提升各自的独立性、弹性、交互操作性(互相合作性),于是有了STL ,即Standard Template Library 标准模板库
基本概念
STL 是惠普实验室开发的一系列软件的统称。
STL 从广义上分为容器、算法、迭代器,容器和算法之间通过迭代器进行无缝连接
STL 几乎所有的代码都采用了模板类和模板函数
六大组件
相互关系:
容器通过空间配置器取得数据存储空间,
算法通过迭代器存储容器中的内容,
仿函数可以协助算法完成不同的策略的变化,
适配器可以修饰仿函数
容器
各种数据结构,如:vector, list, deque, set, map 等,用来存放数据
从实现的角度看,STL 容器是一种类模板
算法
各种常用的算法,如:sort, find, copy, for_each 等
从实现的角度看,STL 算法是一种函数模板
迭代器
扮演了容器预算法之间的胶合剂,共有五种类型
从实现的角度看,STL 迭代器是一种将operator*, operator->, operator++, operator-- 等指针相关操作予以重载的类模板
所有STL 容器都附带有自己专属的迭代器,且只有容器的设计者才知道如何便利自己的元素
原生指针(native pointer )也是一种迭代器
仿函数
行为类似函数,可作为算法的某种策略
从实现的角度看,STL 仿函数是一种重载了operator() 的类或者类模板
适配器(配接器)
一种用来修饰容器或仿函数或迭代器接口的东西
空间配置器
负责空间的配置与管理
从实现的角度看,STL 适配器是一个实现了动态空间配置、空间管理、空间释放的类模板
优点
STL 是c++ 的一部分,内建在编译器中,无需额外安装STL 将数据和操作分离 数据有容器类别加以管理;操作则由可定制的算法定义;迭代器充当两者之间的粘合剂,使得容器核算法交互运作- 高重用性:几乎多有代码都采用了模板编程
- 高性能:如
map 采用红黑树实现,可高效地从十万条数据中找到指定的数据 - 高移植性:如在项目A上用
STL 编写的模块,可以直接移植到项目B中
常用容器
string
介绍
- 是对
char* 的一个封装 一个char* 类型的容器 - 不用考虑释放和越界
string 会管理char* 所分配的内存,每一次string 的赋值和取值都有string 类去负责维护,不用担心复制越界和取值越界问题
构造函数
string();
string(const string &str);
string(const char *s);
string(int n, char c);
赋值
string & operator=(const char* s);
string & operator=(const string &s);
string & operator=(char c);
string & assign(const char *s);
string & assign(const char *s, int n);
string & assign(const string &s);
string & assign(int n, char c);
string & assign(consg string &s, int start, int n);
存取字符
char& operator[](int n);
char& at(int n);
拼接
string & operator+=(const string &str);
string & operator+=(const char* str);
string & operator+=(const char c);
string & append(const char* s);
string & append(const char* s, int n);
string & append(const string &s);
string & append(const string &s, int pos, int n);
string & append(int n, char c);
查找和替换
int find(const string &str, int pos = 0) const;
int find(const char* s, int pos = 0) const;
int find(const char* s, int pos, int n) const;
int find(const char c, int pos = 0) const;
int rfind(const string &str, int pos = npos) const;
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);
string & replace(int pos, int n, const char* s);
比较
int compare(const string &s) const;
int compare(const char* s) const;
取子串
string substr(int pos = 0, int n = npos) const;
插入与删除
string & insert(int pos, const char* s);
string & insert(int pos, const string &s);
string & insert(int pos, int n, char c);
string & erase(int pos, int n = npos);
与char * 的转换
string str = "aaa";
const char * cstr = str.c_str();
const char * cstr2 = "bbb";
string s = string(cstr2);
vector
头文件:include <vector>
动态数组,使用的是动态空间,随着元素的加入,其内部机制会自动扩充空间以容纳新元素
数据存在一组连续空间上,空间不足则找到一块充足的空间,将元数据拷贝过去
缺点:头插需要移动后面的所有元素,效率非常差
构造函数与遍历方式
vector<T> v;
vector(v.begin(), v.end());
vector(n, elem);
vector(const vector &vec);
#include <vector>
#include<algorithm>
void test() {
vector<int> v1;
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
v1.push_back(40);
v1.push_back(50);
for(vector<int>::iterator it = v1.begin(); it < v1.end(); ++it) {
cout << *it << " ";
}
cout << endl;
vector<int> v2(v1.begin(), v1.end());
for (int i = 0; i < v2.size(); ++i) {
cout << v2[i] << " ";
}
cout << endl;
vector<char> v3(3, 'a');
for (char i : v3) {
cout << i << " ";
}
cout << endl;
vector<char> v4(v3);
std::for_each(v4.begin(), v4.end(), [&](const auto &item) {
cout << item << " ";
});
cout << endl;
for(vector<int>::reverse_iterator it = v1.rbegin(); it != v1.rend(); ++it) {
cout << *it << " ";
}
}
赋值
vector & operator=(const vector &vec);
assign(beg, end);
assign(n, elem);
swap(vec);
大小与容量
size();
empty();
resize(int num);
resize(int num, elem);
capacity();
reserve(int len);
存取
at(int idx);
operator[];
front();
back();
插入与删除
insert(const iterator pos, int count, ele);
push_back(ele);
pop_back();
erase(const iterator start, const iterator end);
erase(const iterator pos);
clear();
void test(){
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.insert(v.begin(), 2, 100);
v.pop_back();
v.erase(v.begin());
v.erase(v.begin() + 1, v.end() -1);
v.clear();
}
案例
巧用swap 来收缩内存
void test() {
vector<int> v1;
for (int i = 0; i < 100000; ++i) {
v1.push_back(i);
}
cout << "capacity1: " << v1.capacity() << endl;
cout << "size1: " << v1.size() << endl;
v1.resize(3);
cout << "capacity2: " << v1.capacity() << endl;
cout << "size2: " << v1.size() << endl;
vector<int>(v1).swap(v1);
cout << "capacity3: " << v1.capacity() << endl;
cout << "size3: " << v1.size() << endl;
}
巧用reserve 来预留空间
- 未使用
reserve 时的扩容次数void test() {
vector<int> v;
int *p = nullptr;
int num = 0;
for (int i = 0; i < 100000; ++i) {
v.push_back(i);
if (p != &v[0]) {
p = &v[0];
num++;
}
}
cout << "扩容了 " << num << " 次" << endl;
cout << "最后的首地址:" << p << endl;
}
- 使用
reserve 后的扩容次数void test() {
vector<int> v;
v.reserve(100000);
int *p = nullptr;
int num = 0;
for (int i = 0; i < 100000; ++i) {
v.push_back(i);
if (p != &v[0]) {
p = &v[0];
num++;
}
}
cout << "扩容了 " << num << " 次" << endl;
}
deque
头文件 #include <deque>
双端数组,和vector 相比,deque 的两端都可以操作
采用的是动态分段连续空间存储数据,随时可以增加一段新的空间并链接起来, 所以它没有reserve 功能,但是头删和头插效率都很高(无需像vector 那样移动后面的数据)
stack
头文件:include <stack>
栈,先进后出
栈没有迭代器
构造函数
stack<T> stk;
stack(const stack &stk);
赋值
stack & operator=(const stack &stk);
存取
push(elem);
pop();
top();
queue
头文件:include <queue>
队列,先进先出
队列没有迭代器
构造函数
queue<T> que;
queue(const queue &que);
存取与增删
push(elem);
pop();
back();
front();
赋值
queue & operator=(const queue &que);
list
头文件:#include <list>
链表,一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
链表由一系列结点组成(链表中每一个元素称为一个结点),结点可以在运行时动态生成,每个结点分为两部分:一个是存储数据元素的数据域;一个是存储下一个结点地址的指针域
list是个双向循环链表
- 链表的特点
- 采用动态存储分配,不会造成内存浪费和溢出
- 执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
- 非连续空间,所以遍历没有
vector 数组快 - 链表的迭代器不像
vector 那样可以随机访问,即无法进行如begin() + num 这样的操作
set
头文件:#include <set>
集合,底层为红黑树,set 中所有元素都会根据元素的键值自动排序
和map 不同的是,set 的元素即使键也是值,所以set 的值具有唯一性,即不会重复
另外,无法通过set 的迭代器去修改它的元素,因为其元素值本身又是自身的键,关系到set 元素的排序,如果任意更改,会破坏它的组织结构,换句话说,set 的迭代器是一种const_iterator
multiset
set 一样,底层的实现也是红黑树,不过区别在于,它允许键值重复
pair
对组:pair<iterator, iterator> ,无需引入任何头文件
void test(){
pair<string, int> p1("张三", 21);
cout << "name=" << p1.first << ", age=" << p1.second << endl;
pair<string, int> p2 = make_pair("张三", 21);
cout << "name=" << p1.first << ", age=" << p1.second << endl;
}
#include <set>
void test() {
set<int> s;
for (int i = 0; i < 10; ++i) {
s.insert(i);
}
pair<set<int>::iterator, set<int>::iterator> ret = s.equal_range(5);
if (ret.first != s.end()) {
cout << *ret.first << endl;
}
if (ret.second != s.end()) {
cout << *ret.second << endl;
}
}
修改默认的升序排序规则
需要通过仿函数来实现排序规则的修改
class MyCompareInt {
public:
bool operator()(int a, int b) {
return a > b;
}
};
void test() {
set<int> s;
s.insert(10);
s.insert(30);
s.insert(40);
s.insert(20);
s.insert(50);
for (set<int, MyCompareInt>::iterator it = s.begin(); it != s.end(); ++it) {
cout << *it << " ";
}
cout << endl;
set<int, MyCompareInt> sc;
sc.insert(10);
sc.insert(30);
sc.insert(40);
sc.insert(20);
sc.insert(50);
for (set<int>::iterator it = sc.begin(); it != sc.end(); ++it) {
cout << *it << " ";
}
}
map
头文件:#include <map>
键值对,底层为红黑树实现,map 中所有的元素都会根据元素的键值自动排序
map 中所有的元素都是pair ,map 不允许两个元素拥有相同的键
和set 一样,map 也无法通过它的迭代器去修改它的元素的键,不过可以修改它的值
multimap
和map 的区别在于,它允许相同的键存在
基本语法
#include <map>
void test() {
map<int, int> m;
m.insert(pair<int, int>(1, 10));
m.insert(make_pair(3, 30));
m.insert(map<int, int>::value_type(2, 20));
m[4] = 40;
m[5];
for(map<int, int>::iterator it = m.begin(); it != m.end(); ++it) {
cout << "key=" << it->first << ", value=" << it->second << endl;
}
}
容器的选择
容器对比
| vector | deque | list | set | multiset | map | multimap |
---|
内存结构 | 单端数组 | 双端数组 | 双向循环列表 | 红黑树 | 红黑树 | 红黑树 | 红黑树 | 可随机存取 | 是 | 是 | 否 | 否 | 否 | 对key而言不是 | 否 | 查找速度 | 慢 | 慢 | 非常慢 | 快 | 快 | 对key而言快 | 对key而言快 | 增删速度 | 尾端 | 两端 | 任何位置 | | | | |
使用场景
vector 比如软件历史操作记录的存储,经常需要查看历史记录(遍历),但不会去删除记录,因为记录是事实的描述deque 比如排队购票系统,对排队者的存储可以采用deque ,支持头端的快速移除,尾端的快速添加list 比如公交车乘客的存储,随时可能有乘客上下车,支持频繁的不确定位置元素的移除与插入set 比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列map 比如按ID号存储十万个用户,想要快速通过ID找到对应的用户
函数对象(仿函数)
仿函数可以拥有自己的状态
统计被调用的次数
class MyPrinter {
private:
int m_Count = 0;
public:
void operator()(string name) {
cout << "hello " << name << endl;
m_Count++;
}
int count() const {
return this->m_Count;
}
};
void test() {
MyPrinter myPrinter;
myPrinter("张三");
myPrinter("李四");
myPrinter("王五");
cout << "被调用了 " << myPrinter.count() << " 次" << endl;
}
仿函数可以作为函数参数
void print(MyPrinter myPrinter, const string name) {
myPrinter(name);
}
void test() {
MyPrinter myPrinter;
print(myPrinter, "c++");
}
谓词
指 普通函数或反函数者 返回值是布尔类型时
如果需要一个参数,则称为一元谓词;
如果需要两个参数,则称为二元谓词
一元谓词
找到第一个大于20的数
class MyCompare{
public:
bool operator()(int val) {
return val > 20;
}
};
void test() {
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(50);
vector<int>::iterator ret = find_if(v.begin(), v.end(), MyCompare());
if (ret != v.end()) {
cout << "第一个大于20的数字为:" << *ret << endl;
}
cout << "未找到" << endl;
}
二元谓词
倒序输出数据
class MyCompare{
public:
bool operator()(int v1, int v2) {
return v1 > v2;
}
};
void test() {
vector<int> v;
v.push_back(10);
v.push_back(30);
v.push_back(20);
v.push_back(40);
v.push_back(50);
sort(v.begin(), v.end(), MyCompare());
for_each(v.begin(), v.end(), [](int val){
cout << val << " ";
});
}
内建函数对象
头文件:#include <functional>
算术类函数对象
template<class T> T plus<T>
template<class T> T munus<T>
template<class T> T multiplies<T>
template<class T> T divides<T>
template<class T> T modulus<T>
template<class T> T negate<T>
#include <functional>
void test() {
int a = 10;
negate<int> n;
a = n(a);
cout << a << endl;
plus<int> p;
int c = p(10, 20);
cout << c << endl;
}
关系运算类函数对象
template<class T> bool equal_to<T>
template<class T> bool not_equal_to<T>
template<class T> bool greater<T>
template<class T> bool greater_equal<T>
template<class T> bool less<T>
template<class T> bool less_equal<T>
void test() {
vector<int> v;
v.push_back(10);
v.push_back(30);
v.push_back(20);
v.push_back(40);
v.push_back(50);
sort(v.begin(), v.end(), greater<int>());
for_each(v.begin(), v.end(), [](int val){
cout << val << " ";
});
}
逻辑运算类函数对象
template<class T> bool logical_and<T>
template<class T> bool logical_or<T>
template<class T> bool logical_not<T>
适配器
bind1st & bind2nd
class MyPrint: public binary_function<int, int, void> {
public:
void operator()(int val, int num) const {
cout << val + num << endl;
}
};
void test() {
vector<int> v;
for (int i = 0; i < 10; ++i) {
v.push_back(i);
}
for_each(v.begin(), v.end(), [](int val){cout << val << " ";});
cout << endl;
int num = 1000;
for_each(v.begin(), v.end(), bind2nd(MyPrint(), num));
}
not1 & not2
- 一元取反 - 普通版
class GreaterFive: public unary_function<int, bool>{
public:
bool operator()(int val) const {
return val > 5;
}
};
void test() {
vector<int> v;
for (int i = 0; i < 10; ++i) {
v.push_back(i);
}
vector<int>::iterator r = find_if(v.begin(), v.end(), GreaterFive());
cout << *r << endl;
vector<int>::iterator rr = find_if(v.begin(), v.end(), not1(GreaterFive()));
cout << *rr << endl;
}
- 一元取反 - 增强版
void test() {
vector<int> v;
for (int i = 0; i < 10; ++i) {
v.push_back(i);
}
vector<int>::iterator rr = find_if(v.begin(), v.end(), not1(bind2nd(less<int>(), 5)));
cout << *rr << endl;
}
- 二元取反
void test() {
vector<int> v;
for (int i = 0; i < 10; ++i) {
v.push_back(i);
}
sort(v.begin(), v.end(), not2(less<int>()));
for_each(v.begin(), v.end(), [](int val){cout << val << " ";});
}
ptr_fun
函数适配器:将函数指针适配成仿函数
void myPrint(int val, int num) {
cout << val + num << endl;
}
void test() {
vector<int> v;
for (int i = 0; i < 10; ++i) {
v.push_back(i);
}
int num = 1000;
for_each(v.begin(), v.end(), bind2nd(ptr_fun(myPrint), num));
}
mem_fun_ref
成员函数适配器
class Person {
private:
string m_Name;
int m_Age;
public:
Person(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}
void show() {
cout << "name=" << this-> m_Name << ", age=" << m_Age << endl;
}
};
void test() {
vector<Person> v;
for (int i = 0; i < 10; ++i) {
v.emplace_back("小" + to_string(i), 10 + i);
}
for_each(v.begin(), v.end(), mem_fun_ref(&Person::show));
}
常用算法
算法主要由头文件<algorithm> <functional> <numeric> 组成
<algorithm> :所有STL头文件中最大的一个,包括比较、交换、查找、遍历、复制、修改、反转、排序、合并等
<functional> :定义了一些模板类,用于声明函数对象
<numeric> :体积很小,只包括在序列容器上进行的简单运算的模板函数
遍历算法
for_each(iterator beg, iterator end, _callback);
transform(iterator beg1, iterator end1, iterator beg2, _callback);
void test() {
vector<int> v1;
for (int i = 0; i < 10; ++i) {
v1.push_back(i + 100);
}
vector<int> v2;
v2.resize(v1.size());
transform(v1.begin() + 1, v1.end(), v2.begin(), [](int val) {return val;});
for_each(v1.begin(), v1.end(), [](int val) { cout << val << " "; });
cout << endl;
for_each(v2.begin(), v2.end(), [](int val) { cout << val << " "; });
}
查找算法
find(iterator beg, iterator end, value);
find_if(iterator beg, iterator_end, _callback);
adjacent_find(iterator beg, iterator end, _callback);
binary_search(iterator beg, iterator end, value);
count(iterator beg, iterator end, value);
count_if(iterator beg, iterator end, _callback);
find_if
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;
class People : public binary_function<People *, People *, bool> {
private:
string m_Name;
int m_Age;
public:
People(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}
const string &getMName() const {
return m_Name;
}
int getMAge() const {
return m_Age;
}
};
class MyComparePeople : public binary_function<People *, People *, bool> {
public:
bool operator()(People *p1, People *p2) const {
return p1->getMName() == p2->getMName() && p1->getMAge() == p2->getMAge();
}
};
void test() {
vector<People> v1;
for (int i = 0; i < 10; ++i) {
v1.emplace_back("小" + to_string(i), 10 + i);
}
vector<People *> v;
v.resize(v1.size());
transform(v1.begin(), v1.end(), v.begin(), [](People &p) { return &p; });
for_each(v.begin(), v.end(), [](People *p) {
cout << "name=" << p->getMName() << ", age=" << p->getMAge() << endl;
});
cout << endl;
People p("小1", 11);
vector<People *>::iterator r = find_if(v.begin(), v.end(), bind2nd(MyComparePeople(), &p));
if (r != v.end()) {
cout << "找到了: name=" << (*(*r)).getMName() << ", age=" << (*r)->getMAge() << endl;
} else {
cout << "没找到" << endl;
}
}
int main() {
test();
return EXIT_SUCCESS;
}
adjacent_find
void test() {
vector<int> v;
v.push_back(100);
v.push_back(21);
v.push_back(23);
v.push_back(23);
v.push_back(101);
v.push_back(101);
v.push_back(100);
v.push_back(23);
vector<int>::iterator r = adjacent_find(v.begin(), v.end());
if (r != v.end()) {
cout << *r << ", " << *(++r) << endl;
}
vector<int>::iterator r2 = adjacent_find(v.begin(), v.end(), [](int v1, int v2) {
return v1 == 23 && v2 == 101;
});
if (r2 != v.end()) {
cout << *r2 << ", " << *(++r2) << endl;
}
}
排序算法
merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator desst);
sort(iterator beg, iterator end, _callback);
random_shuffle(iterator beg, iterator end);
reverse(iterator beg, iterator end);
拷贝与替换算法
copy(iterator beg, iterator end, iterator dest);
replace(iterator beg, iterator end, oldvalue, newvalue);
replace_if(iterator beg, iterator end, _callback, newvalue);
swap(container c1, container c2);
copy 的打印用法
#include <iterator>
copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
算术生成算法
accumulate(iterator beg, iterator end, value);
fill(iterator beg, iterator end, value);
集合算法
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator desc);
set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator desc);
set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator desc);
|