目录
1. STL(Standard Template Library, 标准模板库)
一、模板
1. 使用者决定参数类型
模板技术使类型参数化,编写代码时可以忽略参数类型,让使用者决定参数类型。
如何告诉编译器是模板函数:template<class T>
template<class T>
void MyFun(T& a, T& b){
T temp = a;
a = b;
b = temp;
}
2. 函数模板和普通函数的区别
普通函数可以进行自动类型转换,函数模板必须严格类型匹配。
3. 函数模板被调用的顺序
函数模板->模板函数->被调用
函数模板在调用的时候,可以自动类型推导;
模板必须显示指定类型,例:
template <class T>
class Person{
public:
Person(T id, T age){
…;
}
};
int main(){
Person<int> p(10, 20);
…;
return 0;
}
4. 生成模板函数
想让编译器知道要生成什么样的模板函数(自动类型推导),可以定义为如下形式
template<class T1, class T2> //此处可以定义多个class T
void Myfun(T & a, T& b){
…;
}
int main(){
float f = 0.0;
int i = 0;
Myfun(f, i);
Myfun(i, f); //此时生成两个模板函数
return 0;
}
5. 模板的特点
普通派生类继承模板类时,必须明确定义模板类型;模板派生类集成模板类时,不需要立刻指定模板类型。
二、STL中的强制类型转换
1. static_cast
一般类型转换:
转换基础数据类型;如果转换指针和引用,必须在一个继承树下。
2. dynamic_cast
通常在基类和派生类之间转换时使用:
转换具有集成关系的指针和引用,在转换钱会进行对象类型安全检查,子类指针可以转换为父类指针(从大到小),类型安全,可以转换。父类指针转换为子类指针(从小到大),类型不安全,不可以转换。
3. const_cast
主要针对const的转换
增加和去掉const
4. reinterpret_cast
用于进行没有任何关联之间的转换:
例如一个字符指针转换为一个整形数;无关的指针类型都可以进行转换。
以上四个类型转换,一定要自己写一写,才能加深印象。当使用有问题时,编译器会直接报出错误。
三、STL基本概念
1. STL(Standard Template Library, 标准模板库)
STL从广义上分为:容器(container),算法(algorithm),迭代器(iterator),容器和算法之间通过迭代器(容器和算法之间的桥梁)进行无缝连接,STL几乎所有的代码都采用了模板或者模板函数,这相比传统的有函数和类组成的库来说提供了更好的代码重用机会。
在C++标准中,STL被组织成以下13个头文件:
<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack>、<utility>
2. 容器
容器分为序列容器和关联容器。
// …此处需要补充
2.1 string的特性
-
char*是一个指针,string是一个类:
string封装了char*,管理这个字符串,是一个char*型的容器。
- string封装的成员方法:
查找find,拷贝copy,删除delete,替换replace,插入insert
- 不用考虑内存的释放和越界
string管理char*所分类的内存,每一次string的赋值,取值都由string类负责维护,不用担心
赋值越界和取值越界等问题。
- string和char*通过string提供的c_str()方法进行转换。
2.2 vector容器
vector容器是一个长度动态改变的动态数组,拥有一段连续的内存,具有数组的随机存取的优点。
2.2.1 vector特性:
- vector是动态数组,连续内存空间,具有随机存取效率高的特点。
- vector是单口容器,在队尾插入和删除元素效率高,在制定位置插入会导致数据元素移动,效率低。
如下图:
vector中reserve和resize的区别:
reserve是容器预留空间,但在空间内不会真正创建元素对象,所以在没有添加新的对象之前,不能引用容器内的元素。
resize是改变容器的大小,且会创建对象,因此调用这个函数之后,就可以引用容器内的对象了。
如果提前知晓容器大小,且容器需求容量较大,可以巧用reserve,增加程序效率。
总结:vector是个动态数组,当空间不足时插入新元素,vector会重新申请一块更大的内存空间,将旧空间数据拷贝到新空间,然后释放旧空间,vector是单口容器,所以在尾端插入和删除元素效率较高,在指定位置插入,会引起数据元素移动,效率较低。
2.3 deque容器
2.3.1 deque特性
deque是“double-ended queue”的缩写,deque支持随机存取。deque是一种双向开口的连续性空间,可以再头尾两端分别做元素的插入和删除操作。
deque和vector最大差异有两点:
- 在于deque允许常数时间内对头端进行元素插入和删除操作。
- 在于deque没有容量的概念,因为他是动态的以分段的连续空间组合而成,随时可以增加一段新的空间并链接起来。
2.4 stack容器
2.4.1特点:
先进先出。
没有提供迭代器,不能遍历,不支持随机存取。
2.5 queue容器
2.5.1 queue特性
queue是一种先进先出(first in first out,FIFO)的数据类型,有两个口,数据元素只能从一个口进,另一个口出。队列只允许从队尾加入元素,队头删除元素,必须符合先进先出的原则,queue和stack一样不具有遍历行为。
2.6 list(链表)
2.6.1 链表特点
链表是由一系列的节点组成,节点包含两个域,一个数据域,一个指针域。
- 链表内存是非连续的,添加删除元素,时间复杂度都为常数项,不需要移动元素,比数组添加删除效率高。
- 链表只有在需要的时候,才分配内存。
- 聊表需要额外的空间保存节点关系(前驱和后继)。
- 只要拿到第一个节点,相当于拿到整个链表。
2.6.2 list大小操作
size(); //返回容器中的元素个数
empty(); //判断容器是否为空
resize(num); //重新制定容器的长度为num,如果容器变长,则以默认值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。
resize(num, elem); //重新制定容器的长度为num,如果容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。
2.6.3 list赋值操作
assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身
assign(n, elem); //将n个elem拷贝赋值给本身
list& operator=(const list& list); //重载等号运算符
swap(lst); //将lst与本身的元素互换
2.6.4 list数据的存取
front(); //返回第一个元素
back(); //返回最后一个元素
2.6.5 list数反转排序
reverse(); //反转链表,比如list包含1,3,5元素,运行此方法后,list就包含5,3,1元素
sort(); //list排序(算法sort支持可随机访问的容器,所以list自己提供了sort算法)
链表和数组的区别:
- 数组必须实现定义固定的长度(元素个数),不能适应数据的动态增减,当数据增加时,坑呢超出原先定义的元素个数;当减少时,造成内存浪费。
- 链表动态的进行存储和分配,可以适应数据动态的增减,且可以方便的插入、删除数据元素。(数组中插入、删除数据项时,需要移动其他数据项)
2.7 set/multiset容器
2.7.1 set/multiset特性
set/multiset的特性所有元素会根据元素的值自动进行排序。set是以RB-tree(红黑树,平衡二叉树的一种)为底层机制,其查找效率非常好。set容器中不允许重复元素,multiset允许重复元素。
二叉树就是任何节点最多只允许有两个子节点,分别是左子节点和右子节点。
二叉搜索树,是指二叉树中的节点按照一定的规则进行排序,使得对二叉树中元素访问更加高效。二叉搜索树的放置规则是:任何节点的元素值一定大于其左子树中的每个节点的元素值,并且小于其右子树的值。因此从根节点一直向左走,一直到无路可走,即得到最小值,一直向右走,直至无路可走,可得到最大值。那么在二叉搜索树中找到最大元素和最小元素是非常简单的事儿。
set容器只有insert()方法,没有push等方法。set容器元素值不可以相等,multiset可以放值相等的元素。不可以通过迭代器直接修改元素的值。
2.7.2 set常用API
2.7.2.1 set构造函数
set<T> set; //set默认构造函数
multiset<T> mst; //multiset默认构造函数
set(const set &set); //拷贝构造函数
2.7.2.1 set赋值操作
set& operator=(const set& set); //重载等号操作符
swap(set); //交换两个集合容器
2.7.2.1 set大小操作
size(); //返回容器中元素的个数
empty(); //判断容器是否为空
2.8 map/multimap容器
2.8.1 map/multimap特性
map相对于set区:map具有键值和实值,所有元素根据键值自动排序,pair的第一元素被称为键值,第二元素被称为实值。map也是以红黑树为底层实现机制。
迭代器是不可以修改map的键值,键值关系到排列规则,任意改变键值会破坏容器的排列规则,但可以改变实值。
map和multimap的区别,map不允许相同的key值存在,multimap则允许相同key值存在。
2.8.2 map常用API
2.8.2.1 map删除操作
clear(); //删除所有元素
erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg,end); //删除区间[beg, end)的所有元素,返回下一个元素的迭代器。
earse(keyElement); //删除容器中key为keyElement的对组。
2.8.2.2 map查找操作
find(key); //查找键值key是否存在,存在则返回该键值所在元素的迭代器;不存在则返回map.end();
count(keyElement); //返回容器中key为keyElement的对组个数。对map来说要么是0要么是1;对multimap来说值可能大于1.
lower_bound(keyElement); //返回第一个key<=keyElement元素的迭代器。
upper_bount(keyElement); //返回第一个key>keyElement元素的迭代器。
equal_range(keyElement); //返回容器中key与keyElement相等的上下限的两个迭代器。
2.9 STL容器共性机制
STL容器所提供的都是值寓意,而非引用寓意,也就是说当我们给容器中插入元素的时候,容器内部实施了拷贝动作,将我们要插入的元素再另行拷贝一份放入到容器中,而不是将原数据元素直接放进容器中,也就是说我们提供的元素必须能够被拷贝。
⊙除了queue和stack之外,每个容器都提供可返回迭代器的函数,运用返回的迭代器就可以访问元素。
⊙通常STL不会抛出异常,需要使用者传入正确参数。
⊙每个容器都提供了一个默认的构造函数和拷贝构造函数。
⊙大小相关的构造方法:
- size()返回容器中元素的个数
- empty()判断容器是否为空
2.10 STL容器使用时机
- vector使用场景:比如软件历史操作记录的存储,由于经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。
- deque使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加,如果采用vector,则头端移除时,会移动大量的数据,速度慢。
vector和deque的比较:
一:vector.at()比deque.at()效率高,vector.at(0)是固定的,deque的开始位置是不固定的。
二:如果有大量释放操作,vector花费时间更少,这与二者的内部实现有关。
三:deque支持头部快速插入与快速移除,这是deque的优点。
- list使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。
- set使用场景:比如对手机游戏的个人得分纪录存储,存储要求从高分到低分的数序排列。
- map使用场景:比如按ID存储十万个用户,想要快速通过ID查找到对应用户。二叉树的查找效率,这是就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。
三、常用算法
3.1函数对象
3.1.1函数对象的概念
重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象,也叫仿函数(functor),其实就是重载“()”操作符,使得类对象可以像函数那样调用。
注意:
- 函数对象是一个类,不是一个函数。
- 函数对象重载了“()”操作符使得他可以像函数一样调用。
假定某个类有一个重载operator(),而且重载的operator()要求获取一个参数,我们就将这个类称为“一元仿函数”(unary functor);如果重载的operator()要求获取两个参数,就将这个类称为“二元仿函数”(binary functor)…
3.1.2谓词概念
谓词是指普通函数或重载的operator()返回值是bool的函数对象(仿函数),如果operator接受一个参数,那么叫一元谓词,如果接受两个参数,那么叫做二元谓词,谓词可以作为一个判断式。
例如:
struct myfunc01{
bool operator()(int i){} //接受一个参数,并且返回值为bool 即一元谓词
};
bool compare01(int i) //同样是叫做一元谓词
struct myfunc02{
bool operator()(int i, int j){} //接受两个参数,并且返回值为bool 即二元谓词
};
bool compare02(int i, int j) //同样是叫做二元谓词
3.1.3 内建函数对象
STL内建了一些函数对象。分为:算数类函数对象,关系运算类函数对象,逻辑运算类仿函数。这些仿函数所产生的对象,用法和一般函数完全相同,我们还可以产生无名的临时对象来履行函数功能。使用内建函数对象,需要引入头文件 #include <functional>。
- 6个算数类函数对象,除了negate是一元运算,其他都是二元运算。
template<class T> T plus<T> //加法仿函数
template<class T> T minute<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> //取反仿函数
- 6个关系类函数对象,每一种都是二元运算。
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> //小于等于
- 逻辑运算类运算函数,logical_not为一元运算,其余是二元运算。
template<class T> bool logical_and<T> //逻辑与
template<class T> bool logical_or<T> //逻辑或
template<class T> bool logical_not<T> //逻辑非
例子:
// 使用内建函数对象声明一个对象
plus<int> myPlus;
cout << myPlus(10, 20) << endl;
// 使用匿名临时对象
cout << plus<int>()(10, 20) << endl;
3.1.4 函数对象适配器
函数对象适配器是完成一些配接工作,这些配接包括绑定(bind),否定(negate),以及对一般函数或成员函数的修饰,使其成为函数对象,重点掌握函数对象适配器(红色字体):
bind1st : 将参数绑定为函数对象的第一个参数
bind2nd : 将参数绑定为函数对象的第二个参数
not1 : 对一元函数对象取反
not2 : 对二元函数对象取反
ptr_fun : 将普通函数修饰成函数对象
mem_fun : 修饰成员函数
mem_fun_ref : 修饰成员函数
预定义函数对象:
仿函数适配器:bind1st bind2nd 绑定适配器看如下代码test01()
仿函数适配器:not1 not2 取反适配器看如下代码test02()
仿函数适配器:ptr_fun 看如下代码test03()
成员函数适配器:mem_fun mem_fun_ref 看如下代码test04()
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
using namespace std;
struct MyPrint : public binary_function<int, int, void>{
void operator()(int i, int j) const{
cout << i + j << " ";
}
};
// 仿函数适配器bind1st bind2nd 绑定适配器
void test01(){
vector<int> vec;
for(int iCnt = 0; iCnt < 10; iCnt++){
vec.push_back(iCnt);
}
// 绑定适配器 将一个二元函数对象转变成一元函数对象
// bind1st和bind2nd的区别
// bind1st,将addNum绑定为函数对象的第一个参数
// bind2nd,将addNum绑定为函数对象的第二个参数
int addNum = 100;
for_each(vec.begin(), vec.end, bind2nd(MyPrint(), addNum));
// for_each(vec.begin(), vec.end, bind1st(MyPrint(), addNum));
}
// 仿函数适配器 not1 not2 取反适配器
struct MyCompare() : publid binary_function<int, int, void>{
bool operator()(int i, int j) const{
return i > j;
}
};
struct MyPrint02(){
void operator()(int i){
cout << i << " ";
}
};
struct MyGreater : public unary_function(int, bool){
bool operator()(int i) const{
return i > 5;
}
};
void test02(){
vector<int> vec;
for(int iCnt = 0; iCnt < 10; iCnt++){
vec.push_back(iCnt);
}
for_each(vec.begin(), vec.end, MyPrint02);
cout << endl;
sort(vec.begin, vec.end, not2(MyCompare()));
for_each(vec.begin(), vec.end, MyPrint02);
cout << endl;
// not1 not2
// 如果对二元谓词取反,用not2
// 如果对一元谓词取反,用not1
vector<int>::iterator it = find_if(vec.begin(), vec.end(), not1(MyGreater()));
if(it == vec.end()){
cout << "没有找到" <<endl;
}else{
cout << *it <<endl;
}
}
// 仿函数适配器 ptr_fun
void MyPrint03(int i, int j){
cout << "i:" << i << " j:" << j << endl;
cout << i + j << endl;
};
void test03(){
vector<int> vec;
for(int iCnt = 0;iCnt < 10; iCnt++){
vec.push_back(iCnt);
}
// ptr_fun把普通函数转成函数对象
for_each(vec.begin(), vec.end(), bind2nd(ptr_fun(MyPrint03), 10));
}
// 成员函数适配器 mem_fun mem_fun_ref
class Person{
public:
Person(int age, int id):m_age(age), m_id(id){}
void show(){
cout << "age:" << m_age << " id:" << m_id << endl;
}
public:
int m_age;
int m_id;
};
void test04(){
// 如果容器中存放的对象或者对象指针,for_each算法打印的收,调用类自己提供的打印函数
vector<Person> vec;
Person p1(10, 20), p2(30, 40), p3(50, 60);
vec.push_back(p1);
vec.push_back(p2);
vec.push_back(p3);
// 格式:&类名::函数名
for_each(vec.begin(), vec.end(), mem_fun_ref(&Person::show));
vector<Person*> vec1;
vec1.push_back(&p1);
vec1.push_back(&p2);
vec1.push_back(&p3);
for_each(vec1.begin(), vec1.end(), mem_fun(&Person::show));
// mem_fun和mem_fun_ref区别
// 如果存放的对象指针,使用mem_fun
// 如果存放的是对象,使用mem_fun_ref
}
void main(){
test01();
test02();
test03();
test04();
}
3.2 常用查找算法
/*
find算法
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 查找的元素
@return 返回查找元素的位置
*/
find(iterator beg, iterator end, value);
/*
adjacent_find 算法:查找相邻重复元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param _callback 回调函数或者谓词(返回bool类型的函数对象)
@return 返回相邻元素的第一个位置的迭代器
*/
adjacent_find(iterator beg, iterator end, _callback);
/*
binary_search算法:二分查找法
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 查找的元素
@return bool 找到返回true,否则返回false
*/
bool binary_search(iterator beg, iterator end, value)
/*
find_if算法:条件查找
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param _callback 回调函数或者谓词(返回bool类型的函数对象)
@return 返回查找元素的位置
*/
find_if(iterator beg, iterator end, _callback);
3.2 常用遍历算法
/*
遍历算法
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param _callback 函数回调或者函数对象
@return 函数对象
*/
for_each(iterator beg, iterator end, _callback);
/*
transform算法:将指定容器区间元素搬运到另一个容器中
注意:transform不会给目标容器分配内存,所以需要提前分配好内存
@param beg1 源容器开始迭代器
@param end1 源容器结束迭代器
@param beg2 目标容器开始迭代器
@param _callback 函数回调或者函数对象
@return 返回目标容器迭代器
*/
transform(iterator beg1, iterator end1, iterator beg2, _callback);
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
// transform将一个容器的元素 搬运 到另一个容器中
struct MyPlus{
int operator()(int i){
return i + 10;
}
};
void MyPrint(int i){
cout << i << " ";
}
void test01(){
vector<int> vec1;
vector<int> vec2;
for(int iCnt = 0; iCnt < 10; iCnt++){
vec1.push_back(iCnt);
}
vec2.resize(vec1.size());
transform(vec1.begin(), vec1.end(), vec2.begin(), MyPlus());
for_each(vec2.begin(), vec2.end(), MyPrint);
}
void main(){
test01();
return;
}
感谢黑马程序员老师的讲解,故整理如上内容,希望大家共同学习,共同进步!!!