第1节 STL总述、发展史、组成、数据结构谈
一、几个概念
1、c++标准库 c++ standard library 装c++编译器就都有了
2、标准模板库 Standard Template Library 作为1中的核心
3、泛型编程 Generic Programming 使用template为主要手段编程
二、推荐书籍
c++标准库(黄)、STL源码剖析
三、算法和数据结构谈
树、散列表(哈希表) 重点:栈、队列、链表
推荐书籍:算法导论(黑)
四、STL发展史和各个版本(1998年融入c++标准库)
1、HP STL(始祖)
2、SGI STL(Linux下的GNU c++用的就是这个)
3、P.J.Plauger STL(visual c++用这个)
五、标准库的使用说明(所有东西都在std这个命名空间内)
新版本往往不用include后加.h了,如#include<stdlib.h>可以用#include<cstdlib>替代
六、STL的组成部分
1、容器:vector、list、map等
2、迭代器:用于遍历、访问容器中的元素
3、算法:实现一些功能的函数
4、分配器(内存分配器):容器声明时的第二个参数(第一个参数为元素类型)
5、其他:适配器、仿函数(函数对象)
第2节 容器分类、array、vector精解
一、容器的分类(容器中的元素在内存中不一定是挨着的)
1、顺序容器:放进去哪里,就在哪里,如array、vector等;
2、【树】关联容器:元素是键值对(适合查找),一般来讲不能控制插入的位置,如set、map等;
3、【哈希表】无序容器:c++11引入的,元素位置不重要,重要的是是否在容器里边,如unordered_set、urordered_map等。
【官方】c++标准没有规定任何容器必须用任何特定的实现手段
二、容器的说明和应用事项
1、array:内存空间连续的数组,大小是固定的
【注】对象内存空间是连续的,但是内容的地址不连续!
array<string, 5> mystring = { "i","dhfkldsjahfjkldhfaklsdjhfkjsdlhf","xxxxx" };
for (size_t i = 0; i < 5; ++i) {
const char*p = mystring[i].c_str();
cout << "元素值为" << p << endl;
printf("对象地址为%p\n", &mystring[i]);
printf("元素值的实际地址为%p\n", p);
}
我们注意到,当字符串很长时,实际内容的地址就与对象的地址有一定距离了,任意一个string为28字节,所以array里每一个对象的地址是连续的,然后容器中可能通过重载[ ]的方式来查找实际的地址取得对应的元素。
2、vector:往尾部增加、删除元素应该都很快、内存连续、往中间插入元素代价很大
【特别提醒】vector中有一个空间的概念,第一次push_back时,分配一个空间放入;第二次push_back时,没有足够空间,系统会找一块连续两个块的内存,释放上一个空间,调用拷贝构造函数放入,当空间达到一定数量时,会自动增加空间数。
//求vector的元素数
myvector.size();
//求vector的空间数(永远不小于元素数)
myvector.capacity();
//若已知最多只有10个空间
myvector.reserve(10);
第3节 容器的说明和简单应用
一、deque、stack
1、deque:双端队列(双向开口),相当于动态数组,头尾插入、删除数据快;
deque.push_back(i); deque.push_front(i); 都有
分段连续内存,不是完全连续的!
2、stack:后进先出,只有一个开口,不支持从中间插入、删除元素。
二、queue(普通队列、先进先出)
三、list双向链表
不需要内存连续、查找效率不突出、但是在中间插入删除元素很方便!
【面试可能问】vector和list区别:
a)vector类似于数组、内存空间连续、list为双向链表、内存空间不要求连续
b)vector在头部、中间插入、删除元素效率低
c)内存不够时,vector会析构原对象,在新内存块中构建对象
d)vector能高效地随机存取、list不行(因为这两种容器对应的迭代器的种类不同)
四、其他
1、forward_list(c++11新增):单向链表,只能push_front(箭头往右指)
2、map和set:内部实现的数据结构多为红黑树
1)map:每个元素有两项,一般通过key来查找value,key要求不能相同(若相同,插入失败,相当于没插入)
若非要有相同的key,可以用multimap!
特点:插入速度慢,但是查找速度快。
map<int, string> m;
m.insert(make_pair(1, "zx"));
m.insert(make_pair(2, "sx"));
auto iter = m.find(1);
cout << iter->first << endl;
cout << iter->second.c_str() << endl; //注意字符串的输出要用c.str()
2)set不存在key-value,每个元素就是value而且不能重复
3、unordered_set、unordered_multiset(hash_set等不推荐)
//unordered_set初始有8个篮子
unordered_set<int> myset;
cout << "bucket_count = " << myset.bucket_count()<< endl;
for (int i = 0; i < 8; i++) {
myset.insert(i);
}
myset.insert(50);
cout << "bucket_count = " << myset.bucket_count() << endl;
cout << "元素个数为 = " << myset.size() << endl;
for (int i = 0; i < myset.bucket_count(); ++i) {
printf("第%d个篮子里有%d个元素\n",i,myset.bucket_size(i));
}
auto myfind = myset.find(5);
if(myfind!=myset.end())
cout << *myfind << endl;
if (find(myset.begin(), myset.end(), 5) != myset.end())
cout << "存在元素5" << endl;
容器概括图:
第4节 分配器概述、使用、工作原理说
一、分配器概述(和容器一起使用)
list<int> mylist;
//等价于
list<int,std::allocator<int>> mylist;
内存分配器扮演内存池角色,通过大量减少malloc次数来节省内存,但是经测试,默认的allocator分配器的内存并不连续,似乎只是单纯地调用了malloc,并没有使用内存池。
二、分配器的使用
allocator是个类模板,其实即少使用,用new/delete和malloc和free足矣。
三、其他的分配器及原理
四、自定义分配器:代码复杂、并不好写
第5节 迭代器的概念和分类
一、迭代器的基本概念(第一章第9节)
迭代器:可遍历STL容器全部或者部分元素的对象(行为类似于指针),与容器紧密相关。
二、迭代器的分类
分类依据:移动特性以及在迭代器上能做的操作
1、输出型迭代器:struct output_iterator_tag;
2、输人型迭代器:struct input_iterator_tag;
3、前向迭代器:struct forward_iterator_tag;
4、双向迭代器:struct bidirectional_iterator_tag;
5、随机访问迭代器:struct random_access_iterator_tag。
并不是所容器都有迭代器:比如stack、queue等。
1、输出型迭代器:一步一步往前走,能改写元素值;
2、输人型迭代器:一次一个向前读取,一个一个返回元素值;
3、前向迭代器:比2多了个赋值功能,iter2 = iter1;
4、双向迭代器:比3多了个回退功能,iter--,--iter;
5、随机访问迭代器:比4多了个随机访问能力,也就是增减某个偏移量能够计算距离,如iter[n]、iter+=n(前进n个元素),n+iter(返回iter后第n个元素)。
一般这种迭代器对应的容器的内存是连续的!(deque除外)
第6节 算法概述、内部处理、使用范例
一、算法概述(理解为全局的函数模板)
算法前2个形参类型一般都是迭代器类型,用来表示容器中元素的区间(前闭后开)。
1、算法只要判断迭代器等于后边的开区间,就代表迭代结束
2、第一个形参等于第二个形参表示这是一个空区间
※ 算法与容器无关,只与迭代器有关。
二、算法内部的一些处理
算法根据迭代器种类不同有不同的实现方式,参见for_each源码
三、一些典型算法使用范例(#include<algorithm>)
成员函数和全局函数都有的时候优先前者!
1、for_each(未归类知识点第8节)
void myfunc(int i) {
cout << i << endl;
}
void func() {
vector<int> vec = { 10,20,30,40,50 };
for_each(vec.begin(), vec.end(), myfunc);
}
工作原理:不断地迭代给定范围内的元素,将其作为实参调用第三个位置的可调用对象。
2、find(下面的代码是全局的find函数)
auto iter = find(vec.begin(), vec.end(), 40);
3、find_if
第三个参数是返回值为bool类型的可调用对象,若该可调用对象返回ture则停止遍历。
auto iter = find_if(vec.begin(), vec.end(), [] (int tmp){
if (tmp == 40)
return true;
return false;
});
4、sort(一般只适合顺序容器)
sort(vec.begin(),vec.begin()+3); //从小到大排序前3个元素
若要从大到小排序,可以自定义比较函数,返回值为bool。
bool myfunc(int i, int j) {
return i > j;
}
void func() {
vector<int> vec = { 5,1,3,2,4 };
sort(vec.begin(), vec.end(), myfunc);
for (auto iter = vec.begin(); iter != vec.end();++iter) {
cout << *iter << endl;
}
return;
}
若在类A中:
bool operator()(int i,int j){
return i > j;
}
//调用
A mya;
sort(vec.begin(), vec.end(), mya);
list有自己的sort函数:
bool myfunc(int i, int j) {
return i > j;
}
void func() {
list<int> li = { 5,1,3,2,4 };
li.sort(myfunc);
for (auto iter = li.begin(); iter != li.end(); ++iter) {
cout << *iter << endl;
}
return;
}
map自动排序,unordered_sort也排,但是表面看不出来!
第7节 函数对象回顾、系统函数对象及范例
一、仿函数(functors)回顾
函数对象在STL中一般都是和算法配合使用实现功能
二、标准库中定义的函数对象(可以直接用的,只需加入functional头文件)
分类:算术运算符、关系运算符、逻辑运算符、位运算
实例:
class A {
public:
bool operator()(int i, int j) {
return i > j;
}
};
void func() {
vector<int> vec = { 5,1,3,2,4 };
sort(vec.begin(), vec.end(), greater<int>()); //系统定义的类所产生的临时对象
for (auto iter = vec.begin(); iter != vec.end(); ++iter) {
cout << *iter << endl;
}
return;
}
第8节 适配器概念、分类、范例及总结
一、适配器(类似于转接头)
把一个既有的东西进行改造,比如增加、删除,就构成了适配器。
分类:容器适配器、算法适配器、迭代器适配器
二、容器适配器(deque变为stack和queue)
三、算法适配器:最典型的例子就是绑定器(binder)
例1:
auto bf = bind(less<int>(), 40, placeholders::_1);
cout << bf(50) << endl;
//输出为1
例2:统计容器中大于40元素的个数,用绑定器就不用写类A自己判断了!
vector<int> myvector = { 50,15,80,30,46,80 };
int c = count_if(myvector.begin(), myvector.end(), bind(less<int>(),40,placeholders::_1));
//count_if是个算法
//bind是个绑定器
//less<int>()是个临时对象
//执行count_if的时候会调用less<int>内的operator函数,这个operator函数大概长这样:
constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const
{
return (_Left < _Right);
}
//operator()的第一个参数被bind绑为40,第二个参数为每一个元素值,充当了类A刚才自己写的函数的作用
cout << c << endl;
推荐的两个网站:https://en.cppreference.com/w 或 www.cplusplus.com
四、迭代器适配器:reverse_iterator
vector<int> myvector = { 100,200,300 };
for (vector<int>::reverse_iterator iter = myvector.rbegin(); iter != myvector.rend(); ++iter) {
cout << *iter << endl;
}
//输出为
//300
//200
//100
全章综述图