STL广义上分为:容器(container)、算法(algorithm)、迭代器(iterator),容器和算法之间通过迭代器进行无缝连接
STL 六大组件:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
容器:各种数据结构,如vector、list、deque、set、map等,从实现角度上看,STL容器是一种class template
算法:各种常用算法,如sort、find、copy、for_each等。从实现角度上看,STL算法是一种function template
迭代器:扮演容器与算法之间的粘合剂,共有五种类型,从实现角度上看,迭代器是一种将operator*, operator→, operator++, operator—等指针相关操作给予重载的class template。所有的STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。
仿函数:行为类似函数,可以作为算法的某种策略。从实现角度上看,仿函数是一种重载了operator()的class或者class template
适配器:一种用来修饰容器或者仿函数或者迭代器接口的东西
空间配置器:负责空间的配置与管理,从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class template
六大组件的交互关系:
容器通过空间配置器取的数据存储空间,算法通过迭代器存储容器中的内容,仿函数可以协助算法完成不同的策略的变化,适配器可以修饰仿函数
优点:
STL的一个重要特性是将数据和操作分离。数据由容器类别加以管理,操作则由可制定的算法定义。迭代器在两者之间充当粘合剂,使得算法和容器可以交互运作。
高可重用性,高性能,高移植性,跨平台
高可重用性:STL几乎所有的代码都采用了模板类和模板函数进行实现的
高性能:如map可以高效地在十万条记录中找出指定的记录,map采用的是红黑树的变体实现的
高移植性:项目之间可以快速移植
跨平台:
STL 三大组件
容器
序列式容器
每个元素有固定的位置,除非用删除或者插入进行改变。比如vector, deque, list等
关联式容器
各元素没有严格的物理上的顺序关系,元素在容器中并没有保存元素插入容器时的逻辑顺序,比如set multiset map multimap等。另一个特定是使用关键字进行索引。
算法
质变算法
会改变区间元素中的内容,比如拷贝、替换、删除等
非质变算法
不会改变区间的元素内容,比如查找,计数,遍历等
迭代器
iterator定义如下:提供一种方法,使之能够依序寻访某个容器中所含的各个元素,而又无需暴露该容器的内部表示方式。
分为五类,输入、输出、前向、双向、随机访问迭代器。
Copy vector<int> v;
v.push_back(10); // 尾插法
vector<int>::iterator itBegin = v.begin(); // 声明一个迭代器, 指向容器的起始位置
vector<int>::iterator itEnd = v.end(); // 指向最后一个元素
*itBegin // 取元素
itBegin++ // 指向下一个元素
// 遍历
while(itBegin!=itEnd) {
cout << *itBegin << endl;
itBegin++;
}
// 遍历2
for (vector<int>iterator it=v.begin(); it != v.end(); it++) {
cout << *it << endl;
// 如果遍历的是对象的话,则需要使用(*it)指向类的成员变量 eg. (*it).m_Value, it->m_Value
}
// 遍历3, 包含在头文件:algorithm
void myPrint(int v) {
cout << v << endl;
}
for_each(v.begin(), v.end(), myPrint);
// 容器嵌套
vector<vector<int> > v;
vector<int> v1;
vector<int> v2;
vector<int> v3;
// 遍历
for (vector<vector<int> >::iterator it=v.begin(); it != v.end(); it++){
for (vector<int>::iterator vit=(*it).begin(); vit != (*it).end(); vit++) {
cout << *vit << " ";
}
cout << endl;
}
常用容器
string 容器
构造函数
Copy string(); // 创建一个空的字符串, eg: string str;
string(const string& str); // 拷贝构造, eg: string str1(str); string str1 = str;
string(const char* s); // 字符串s构造,eg: string str2("abc"); string str2="abc";
string(int n, char c); // 用n个字符串c来初始化, eg: string str3(10, 'a');
基本赋值操作
Copy string& operator=(const char* s); // str = "abc";
string& operator=(const string& str); // str = str1;
string& operator=(const char c);
string& assign(const char* s);
string& assign(const char* s, int n); // 将s字符串的前n个字符复制给调用对象
string& assign(const string& str);
string& assign(int n, char c);
string& assign(const string& s, int start, int n); // 从s对象中start开始,拷贝n个字符,从0开始索引
存取操作
Copy char& operator[](int n);
char& at(int n);
// [] 和 at的区别: [] 访问越界会直接挂掉,at访问越界会抛出异常
try {
}
catch(...) { // 这里的话捕捉所有的异常,用...来代替
}
// 越界异常的写法
try {
cout << s.a[100] << endl; // 假定s只有10个元素
// cout << s[100] << endl; // []的访问方式不会抛出异常
}
catch (out_of_range & e) {
cout << e.what() << endl;
}
拼接操作
Copy string& operator+=(const string& str);
string& operator+=(const char *str);
string& operator+=(const char c);
string& append(const char* s); // 将字符串s连接到当前字符串结尾
string& append(const char* s, int n); // 将字符串s的前n个字符连接到当前字符串结尾
string& append(const string& s);
string& append(const string& s, int pos, int n); // 从pos开始的n个字符连接到结尾
string& append(int n, char c); // 末尾添加n个字符c
查找和替换
Copy // 查找失败的返回 -1
int find(const string& str, int pos = 0) const; // 从pos开始查找str第一次出现的位置
int find(const char* s, int pos = 0) const;
int find(const char* s, int pos, int n); // 查找s前n个字符
int find(const char c, int pos = 0);
// r stands for right, 从右往左找
int rfind(const string& str, int pos = npos) const;// 查找str最后一次出现的位置,从pos开始
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);
string& replace(int pos, int n, const string& str); // 替换从pos开始的n个字符串为str, 注意!n个字符是指源字符串的n个,全部替换为str,两者没有数量关系
string& replace(int pos, int n, const char* s);
比较
Copy // compare函数>时返回1,<时返回-1,等于是返回0
// 大小按照字母顺序,排前面的越小,大学的A比小写的a小
int compare(const string& s);
int compare(const char* s);
子串
Copy string substr(int pos = 0, int n = npos); // 返回由pos开始的n个字符组成的字符串
// 查找邮件的用户名
string email = "test@qq.com";
int pos = email.find("@");
string name = email.substr(0, pos);
插入和删除
Copy string& insert(int pos, const char* s);
string& insert(int pos, const string& str);
string& insert(int pos, int n, char c);
string& erase(int pos, int n = npos);
string 和 c-style字符串转换
Copy // string 转 char*
string str = "hello"
const char* cstr = str.c_str();
// char* 转 string, char*是可以进行隐式类型转换为string的
char* cstr = "hello"
string str(cstr);
补充
Copy toupper(char c);// 将字符c转换成大写
tolower(char c);// 将字符c转换成小写
vector 容器
和数组array的唯一差别在于空间的灵活性
Copy vector<int> v; // 单端数组 动态数组
v.rbegin() // 最后一个元素
v.rend() // 第一个元素
v.begin()// 第一个元素
v.end() // 最后一个元素
v.push_back() // 尾插
v.pop_back() // 尾删
v.capacity() // v的容量,vector的动态扩展是动态的,不是固定扩展两倍
// vector的动态增长,不是在原有空间上增加,而是找一个更大的空间,然后将原有的数据进行拷贝到新的空间上去,然后释放原有的空间,所以要注意一定空间上发生了重新分配,原有的vector的迭代器就失效了,不能再使用,否则将发生错误。
vector 构造函数
Copy vector<T> v;
vector<T> v1(v.begin(), v.end()); // 将v对象的内容拷贝过来进行构造
vector<T> v(n, elem); // 将n个elem拷贝给自身
vector<T> v(const vector& vec); // 拷贝构造函数
int arr[] = {1, 2, 3, 4}
vector<int> v1(arr, arr+sizeof(arr)/sizeof(int));
vector<int> v2(v1.begin(), v1.end();
赋值操作
Copy assign(beg, end); // 将[beg, end)区间的数据拷贝赋值给自身
assign(n, elem); // 将n个元素拷贝给自身
vector& operator=(const vector& vec);
swap(vec); // 将vec与自身的元素互换
// 巧用swap收缩空间
vector<int> v;
for (int i = 0; i < 100000; ++i) {
v.push_back(i);
}
cout << "v's capacity: " << v.capacity() << endl; // 13万多
cout << "v's size: " << v.size() << endl; // 10万
v.resize(3);
cout << "v's capacity: " << v.capacity() << endl; // 13万多
cout << "v's size: " << v.size() << endl; // 3
vector<int>(v).swap(v);// 利用v初始匿名对象,按照v.size()大小开辟匿名对象空间,swap交换匿名对象和v对象的指针,运行结束后,匿名对象销毁,原来的匿名对象空间变成v对象
cout << "v's capacity: " << v.capacity() << endl; // 3
cout << "v's size: " << v.size() << endl; // 3
//
vector<int> v;
int *p = NULL;
int num = 0;
// 这段代码可以计算在插入100000个数据时总共开辟了多少次空间
// 运行结果得到30次
for (int i = 0; i < 100000; ++i) {
v.push_back(i);
if (p != &v[0]) {
p = &v[0];
num++;
}
}
v.reserve(100000); // 则上述代码执行的结果为1,只进行开辟1次空间
大小操作
Copy size();
empty(); // 返回true,表示为空,反之返回false
resize(int num); // 重新指定容器长度,容器变长填充默认值,变短超出元素删除
resize(int num, elem); // 变长以elem填充,变短超出的删除
capacity(); // 容器的容量
reserve(int len); // 容器预留len个元素长度,预留位置不初始化,不可访问
数据存取
Copy at(int idx);
operator[];
front(); // 第一个元素
back(); // 最后一个元素
插入和删除
Copy insert(const_iterator pos, int count, elem);
push_back(elem);
pop_back(elem); // 删除最后一个元素
erase(const_iterator start, const_iterator end);
erase(const_iterator pos); // 删除指定位置元素
clear();
其他
Copy vector<int > v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
// 逆序遍历,注意要使用逆序迭代器
for (vector<int>reverse_iteartor it = v.rbegin(); it != rend(); ++i) {
cout << *it << endl;
}
// vector的迭代器是随机访问的迭代器,支持跳跃访问
vector<int>::iterator itBegin = v.begin();
itBegin = itBegin + 3; // 这个代码如果编译不出错,表明这个容器支持随机访问,list容器不支持随机访问
deque 容器
deque属于双端数组, vector也可以进行头插,但是效率很低
deque内部用的是中控器(保存缓冲区的地址)+缓冲区的结果
大部分的操作和vector是一样的e
Copy iterator // 普通的迭代器
reverse_iterator // 逆序迭代器
const_iterator // const迭代器
// deque特有的
push_front(elem) ; // 前插
pop_front(); // 删除第一个元素
Sort算法
Copy deque<int> d;
d.push_back(1);
d.push_back(9);
d.push_back(4);
sort(d.begin(), d.end());// algorithm头文件中, 从小到大排序
// 排序规则的回调函数
bool myCompare(int v1, int v2) {
return v1 > v2;
}
sort(d.begin(), d.end(), myCompare); // 从大到小
Stack 容器
先进后出的数据结构:First In Last Out(FILO),只能访问栈顶元素
构造
Copy stack<T> s;
stach<T>(const stack &s); // 拷贝构造
存取操作
Copy push(elem); // 栈顶插入元素
pop(); // 栈顶弹出一个元素
top(); // 返回栈顶元素
empty();
size();
queue 容器
队列容器,先进先出的数据结构First In First Out(FIFO),只允许由一端入队,一端出队,不提供遍历和迭代器
Copy // 构造函数
queue<T> q;
queue<T>(const queue& q);
// 存取操作
push(elem);
pop(); // 从队头删除一个元素
front(); // 返回第一个元素
back(); // 返回最后一个元素
// 赋值操作
queue& operator=(const queue &q);
// 大小操作
empty();
size();
List 容器
List容器是一个双向循环链表,动态分配存储信息,消耗较大
List容器提供的迭代器是bidirectional Iterators, 不支持随机访问
List有一个重要性质,插入操作和删除操作不会造成原有迭代器的失效。这个在Vector容器中是不成立的,因为vector容器的插入操作可能引起内存的重新分配,导致原有的迭代器失效。
也可以支持逆序遍历,reverse_iterator
Copy // 构造函数
list<T> l;
list(begin, end);
list(n, elem);
list(const list& l);
// 插入删除操作
push_back(elem);
pop_back();
push_front(elem);
pop_front();
insert(pos, elem);
insert(pos, n, elem);
insert(pos, beg, end);
clear();
erase(beg, end);
erase(pos);
remove(elem);
// 大小操作
size();
empty();
resize();
resize(n, elem);
// 复制操作
assign(beg, end); // 将[beg, end)区间的数据拷贝给本身
assign(n, elem);
list& operator=(const list& l);
swap(l);
// 存取
front();
back();
// 反转排序
reverse(); // 链表逆序
sort() // list排序
// 所有不支持随机访问的迭代器,不可以使用系统提供的算法
// 如果不支持系统提供的算法,那么这个类内部会提供
// eg: l.sort(); // 从小打大排序
// 对于自定义类型需要指定回调函数
// !!! 注意!!!
// 删除自定义类型直接使用remove会报错,可以重载双等号来解决
set、multiset容器
set容器的特性,所有的元素都会根据元素的键值自动排序,set的元素不像map那样可以同时拥有实值和键值,set元素既是键值又是实值。set不允许两个元素有相同的键值。
不可以通过set的迭代器来改变set容器的元素值,换句话说,set的迭代器是一种const_iterator
set有和list一样的性质,原有的迭代器在插入删除元素后还是有效的,当然,被删除那个元素的迭代器除外。
multiset特性和set完全相通,唯一的差别在于multiset允许键值重复。set、multiset的话底层是用红黑树实现的。
相关操作和上面提到的操作基本一致,不在说明。
Copy // 插入
insert(elem); // 元素插入只有这个方法,插入后会自动按照从小到大排序
// 删除
erase(pos);
erase(elem);
erase(beg, end);
// 查找操作,key就是实值
find(key); // 查找键key是否存在,若存在,返回该元素的迭代器,不存在返回set.end()
count(key); // 统计键key的个数
lower_bound(keyElem); // 返回第一个key>=keyElem元素的迭代器
upper_bound(keyElem); // key>keyElem
equal_range(keyElem); // 返回相等的上下限两个迭代器
//set 容器不允许插入重复的值
insert(elem); // 返回值是pair<iterator, bool>, bool返回插入成功或失败
// set容器插入后默认是从小打大的,如果从大到小的话可以使用逆序迭代器
// 或者是在插入之前指定从大到小,指定规则, 仿函数
class myCompare {
public:
// 重载小括号 ()
bool operator()(int v1, int v2) {
return v1 > v2;
}
};
set<T, myCompare> s;
// !!!!插入自定义数据类型需要一开始指定排序方式
Pair对组
Copy // 创建对组
pair<T1, T2> p(T1, T2);
pair<T1, T2> p = make_pair(T1, T2);
// 取值
p.first
p.second
map、multimap容器
map的特性是会根据元素的键值自动排序,map所有的元素都是pair,同时拥有键值和实值,pair的第一个元素被视为键值,第二个元素被视为实值,同样和set一样,map也是不允许有两个元素有相同的键值(注意这里是键值,key值,value值可以相同)。
同样map也是不能根据迭代器更改元素内容的。
map和list一样,插入删除元素迭代器不失效。
multimap可以支持键值重复。
Copy // 构造函数
map<T1, T2> m;
map(const map& m);
// 赋值操作
map& operator=(const map& m);
swap(map);
// 大小
size();
empty();
// 插入
map.insert(...); // 返回的是pair<iterator, bool>
map<int, string> m;
m.insert(pair<int, string>(3, "Mike");
m.insert(make_pair(1, "Jack"); // 推荐使用
m.insert(map<int, string>::value_type(2, "Jason");
m[3] = "Leon"; // 可以用在访问上
// 删除
clear();
erase(pos);
erase(beg, end);
erase(keyElem); // 删除键值的对组
// 查找和set一致
multimap案例:存储员工信息:姓名 年龄 电话 工资等
STL使用时机
vector使用场景:比如软件的历史操作记录的存储。
deque使用场景:比如排队购票系统
vector与deque的比较:
vector.at()比deque.at()的效率高,因为vector.at(0)是固定的,而deque的开始位置是不固定的
如果有大量的释放操作的话,vector花费的时间更少
deque支持头部的快速插入和删除,是属于deque的优点
list的使用场景,公交车乘客信息的存储,随时有可能乘客下车,存在频繁的不确定位置元素的插入和删除
set的使用场景:手机游戏的个人得分的存储,要求从高分到低分顺序存储
map使用场景:比如按照ID账号存储十万个账号。