目录
一、迭代器
1.原理:
2.迭代器的分类:
①正向迭代器:
②反向迭代器:
③常正向迭代器: const_iterator (容器中的一个类对象)
④常反向迭代器:const_reverse_iterator
补:自己实现一个简单的迭代器
3.其他分类方式:
按功能分类
4.迭代器辅助函数:
5.特殊迭代器--流型迭代器-->一般用于辅助打印
①输出流型:
②输入流型:
二、Lambda表达式
1.定义:
2.Lambda表达式的组成部分:
3.捕获方式:(函数使用外部的变量的方式
实例代码
三、仿函数
1.什么仿函数?
2.作用:
3.标准库中的仿函数:
四、函数适配器:
1.概念:什么是函数适配器
2.误区/注意点:
3.用法:
①普通函数bind()使用
②类成员函数的bind()使用
③其他用法:可以通过占位符,所以调整参数位置,形成不同的调用形态
五、函数包装器
1.概念:
2.写法:
①普通函数包装
②仿函数的包装:
③成员函数:
④bind和function结合使用,实现改变传参顺序的效果。
迭代器: 就是一个类中类,通过运算符重载通过类中类的对象去遍历容器
iterator中的 begin() end()
反向迭代器: reverse_iterator (从容器的后面向前面遍历)
->迭代器只能++运算,不能-- rbegin() rend()
#include
#include
using namespace std;
int main()
{
list mylist = { 1,2,3,4,5,6,7 };
/*正向遍历*/
for (auto Piter = mylist.begin(); Piter != mylist.end(); Piter++)
{
cout << *Piter << "\t";
}
cout << endl;
/*反向遍历*/
for (auto Niter = mylist.rbegin(); Niter != mylist.rend(); Niter++)
{
cout << *Niter << "\t";
}
return 0;
}
输出:
1 2 3 4 5 6 7
7 6 5 4 3 2 1
( 与非const的区别就是:不能通过此迭代器对容器中元素进行修改 cbegin() cend()
crbegin() crend()
template
struct Node
{
_Ty data;
Node<_Ty>* next;
Node(_Ty data):data(data),next(nullptr){}
Node(_Ty data,Node<_Ty>*next):data(data),next(next){}
};
template
class My_List
{
public:
My_List() :frontNode(nullptr), tailNode(nullptr) { curSize = 0; }
void push_front(int data)
{
if (curSize == 0)
{
frontNode = new Node<_Ty>(data);
tailNode = frontNode;
}
else
{
frontNode = new Node<_Ty>(data, frontNode);
}
curSize++;
}
void printList()
{
Node<_Ty>*pmove = frontNode;
while (pmove != nullptr)
{
cout << pmove->data << "\t";
pmove = pmove->next;
}
cout << endl;
}
class iterator;//前向声明,不让编译器认为是std中的iterator
iterator begin()
{
return iterator(frontNode);
}
iterator end()
{
return iterator(tailNode->next);
}
class iterator
{
public:
iterator(Node<_Ty>* pmove=nullptr):pmove(pmove){}
iterator(const iterator&obj):pmove(obj.pmove){}
bool operator!=(const iterator& obj)const
{
return this->pmove != obj.pmove;
}
iterator operator++(int)
{/*注:此处未实现后置++,只是想让下面的代码能用*/
pmove = pmove->next;
return iterator(pmove);
}
_Ty operator*()
{
return pmove->data;
}
protected:
Node<_Ty>* pmove; /*迭代的实质就是通过这个数据成员去访问链表*/
};
protected:
Node<_Ty>* frontNode; /*头结点*/
Node<_Ty>* tailNode; /*尾结点*/
int curSize;
};
void testMylist()
{
My_Listlist;
list.push_front(1);
list.push_front(2);
list.push_front(3);
list.push_front(4);
//list.printList();
My_List::iterator iter = list.begin();
for (; iter != list.end(); iter++)
{
cout << *iter << "\t";
}
}
int main()
{
testMylist();
return 0;
}
按功能分类
①正向迭代
②双向迭代
③随机访问迭代器
移动:advance(iterator iter,n);
间距:distance(iterator begin,iterator end);
交换:iter_swap(iterator first,iterator end);
示例:
/*辅助迭代器函数*/
void testExFunction()
{
vectordata = { 1,2,3,4,5,6,7,8,9 };
auto iter = data.begin();
advance(iter, 3);
cout << *iter<
输出:
4
距离count: 9
9 2 3 4 5 6 7 8 1
再次提醒:end()是指向容器最后一个位置的后一个位置!!!!!!想访问最后一个元素,一定要减去1!!!!
ostream_iterator<_Ty> iter(ostream& out);
ostream_iterator<_Ty> iter(ostream& out,char* str);
输出流型迭代做赋值运算,意味着就是打印数据到屏幕上
实例:
void testOstreamIterator()
{
ostream_iterator iter(cout);
iter = 666; /*效果:将等号后面的内容打印至屏幕上*/
cout < iter2(cout, "kkkk");
iter2 = 999;
cout << endl << "----------------" << endl;
vectordata = { 1,2,3,4,5,6,7,8,9 };
copy(data.begin(), data.end(), ostream_iterator(cout, "**"));
}
输出:
666
----------------
999kkkk
----------------
1**2**3**4**5**6**7**8**9**
部分解释:
(i)第一种构造方式,赋值等同于打印(参数是cout)
(ii)第二种构造方式,会多出来一个char*类型的参数,在赋值的时候将其加到赋值的右值的后面。copy是将前面的这个data容器中的值一一取出依次赋值,每次赋值都会自动加上这个char*类型.
istream_iterator<_Ty> iter; //构造无参对象,是一个错误流 end_of_ostream ->常用来判定输入终止的一个流值
istream_iterator<_Ty> iter(istream& in);/*istream类的对象作为参数
*iter 等效cin>>操作
示例:
void testIstreamIterator()
{
istream_iterator end; //无参构造,返回一个end_of_stream错误流对象,用于比较
istream_iteratorin(cin); //有参构造!
vector inputData;
while (in != end)
{
inputData.push_back(*in);
++in;
}
for (auto v : inputData)
{
cout << v <<"\t";
}
}
输入:111 666 777 999 555 ¥#
输出:111 666 777 999 555
显然:当输入到第一个不是int类型的数据时,in就会返回一个end_of_stream即和end相等,这样数据类型不对的数据就不会再继续录入。
注意:
(i)*in表示读入一个数据
(ii)++in表示移动一个位置,才能继续下一次的读入。
若将++in注释掉,后果,while无法终止!!!
相关的好文章---来源C++primer
是一个返回函数指针的表达式,它定义和返回值函数指针在一起的
// [捕获方式](函数参数)mutable exception->函数返回值类型{函数体;}
/*mutable表示能否修改*/ /*exception表示是否有异常 */->常写noexcept表示无异常
[=] //值的方式捕获(不会同步到外部变量)->只能当做右值使用,不可以当做左值赋值!
[&] //引用的方式捕获(会同步到外部变量)->可改变!
[this]//this指针方式捕获(捕获类中的数据成员)
[ ] //不捕获任何变量
[=,&x];//x用引用方式捕获,其他变量用值的方式捕获
包含①完整版lambda表达式和偷懒版的写法 ②最常用的写法与应用场景:回调表达式
③三种回调表达式的区别。④lambda表达式中的“函数模板”(在函数中是不允许auto推断参数的,这样写类似于函数模板
#include
using namespace std;
void print(int(*pMax)(int, int), int a, int b)
{
cout << pMax(a, b) << endl;
}
class Wbm
{
public:
void print()
{
[this] {cout << name << "\t" << age << endl; }(); /*定义和调用一步到位*/
}
protected:
string name="default";
int age=18;
};
int main()
{
int(*pMax)(int, int) = nullptr;//创建函数指针 (指向参数为两个int类型的且返回值也是int类型的函数
/*完整版:Lambda表达式*/
pMax = [](int a, int b)mutable noexcept->int {return a > b ? a : b; };
cout << pMax(1, 3) << endl;
/*省略常用版本:写代码越简单约好*/
auto pp = [](int a, int b) {return a > b ? a : b; };
cout << pp(1, 3) << endl;
/*实际使用的时候可以一步到位*/
cout << [](int a, int b)mutable noexcept->int {return a > b ? a : b; }(1, 3) << endl;
print([](int a, int b) {return a > b ? a : b; }, 1, 3);
print([](int a, int b) {return a > b ? b : a; }, 1, 3);
print([](int a, int b) {return a + b; }, 1, 3);
/*捕获方式的区别*/
//①用值和&的捕获:在lambda中不能把值当做左值使用,函数调用不会因为值的改变而改变。
int data = 101010;
auto pFunc = [=] {cout << data << endl; }; //无参 ()可以省略
auto pFunc2 = [&] {cout << data << endl; };
pFunc();//输出101010
pFunc2();//101010
data = 808080;
pFunc();//输出101010!
pFunc2();//输出808080!
/*②用this捕获*/
Wbm bmw;
bmw.print();
/*特殊的东西->结合auto使用(类似于函数模板)*/
auto pAuto = [](auto a, auto b)->auto{return a > b ? a : b; };//直接写这样函数是不可以的
cout << pAuto(1, 3) << endl; //[](auto a, auto b)->auto{return a > b ? a : b; }(1,3)
cout << pAuto("stringa", "stringb");
return 0;
}
运行结果:
3
3
3
3
1
4
101010
101010
101010
808080
default 18
3
stringb
类模仿函数调用行为,实质是无名对象调用重载的()函数,
所以仿函数的关键点在于重载()
一般情况仿函数是做排序准则,或者一些算法的计算准则
算术类、关系类、逻辑类、选择,证同,投射(几何相关)
示例:注意注释部分!头文件
#include
#include
#include //仿函数所在头文件
#include
用来绑定函数调用时候的参数,让函数适应其他调用的用法(使得接口、参数能够对上)
bind并没有改变函数参数的个数与类型,原来是两个参数bind后仍然是两个参数(单纯的给你绑定一个参数,只是增加调用性态,并没有真正改变这个函数指针的类型)。
实例代码:
int Max(int a, int b)
{
return a > b ? a : b;
}
void print(int(*pMax)(int,int), int a,int b)
{
cout << pMax(a,b) << endl;
}
int main()
{
cout << Max(1, 3) << endl;
//基本用法
//std::placeholders::_1占位符
auto pMax = bind(Max, std::placeholders::_1, 100); //把第二个参数置为100
//只是增加调用行为,并没有真正改变了这个函数指针类型
cout << pMax(34) << endl; /*相当于调用Max(34,100);*/
cout << pMax(13, 44) << endl; //绑定后的函数指针,不再支持传参的,第二个参数无效
//语法上没问题,但是尽量别这样做
using namespace std::placeholders;
auto pMax2 = bind(Max, _1, 100); //把第二个参数置为100
cout << pMax2(34) << endl;
return 0;
}
输出:
3
100
100
100
实际应用场景:
利用count_if条件统计函数来找出vector中>60的个数。count_if(,)
法一:利用函数适配器(将greater
/*应用实例,count_if(条件)算法,找出大于60分的个数*/
vector vecData = { 19,43,89,89,34,54,67,54 };
//法一:适配器
cout << count_if(vecData.begin(), vecData.end(),
bind(greater(), std::placeholders::_1, 60)) << endl;
法二:Lambda表达式
//法二:直接lambda表达式
cout << count_if(vecData.begin(), vecData.end(),
[](int a) {return a > 60; }) << endl;
法三:自己写计算准则
bool Greater_than_60(int val)
{
return val > 60;
}
void main()
{
vector vecData = { 19,43,89,89,34,54,67,54 };
cout << count_if(vecData.begin(), vecData.end(), Greater_than_60)<< endl;
}
bind的第一个参数传&类成员函数,
第二个参数&具体的某一个对象(绑定后返回值就无须通过对象调用了)
第三个参数:占位符.....上限20个
第n个参数:需要固定的值
示例:
void testClassFunc()
{
Test test;
auto testFunc = bind(&Test::print, &test, std::placeholders::_1, std::placeholders::_2, 99);
/*placeholders::_1和_2将参数1和2的位置处于等待传参的状态,参数3固定为99*/
testFunc(1, 3);
//调用,直接调用(在绑定的过程中已经指定对象了,所以调用时不需要对象)
}
原则:占位符_n代表原函数的参数 在调用形态n的第个位置
比如bind()的第一个参数是_3,所以原来此处的int类型数据要去第三个参数的位置呆着。
void printData(int one, Test two, string str)
{
cout << "调用成功" << endl;
}
void testExUser()
{
//占位符代表原函数的参数 在调用形态 的第二个位置
auto testFunc = bind(printData,std::placeholders::_3,
std::placeholders::_1, std::placeholders::_2);
/*第一个参数对应_3的位置,所以testFunc中int在第三个位置*/
printData(1, Test(), "ILoveyou");
//testFunc(1,Test(), "ILoveyou"); 错误调用,
testFunc(Test(), "ILoveyou", 1);
}
注意区分testFunc调用参数的位置!!!
头文件:#include
就是把函数指针包装成一个对象。通过这个对象调用函数。一旦函数指针被函数包装器包装了,那这个包装器对象可以直接替换函数指针的用法去调用函数
函数包装器类的实例化传参:
function<函数返回值类型(参数类型)>
#include
#include
#include
using namespace std;
int Max(int a, int b)
{
cout << "包装普通函数:" << endl;
return a > b ? a : b;
}
int main()
{ /*写法一:显式*/
function funcMax(Max);
cout << funcMax(1, 3) << endl;
/*写法二:隐式*/
function funcMax2 = Max;
return 0;
}
注意区别:需要通过一个实例对象test来包装这个仿函数(类)
void testFunctors()
{
//仿函数(无名对象+()模仿函数行为)包装
Test test;
function func = test;
func("包装仿函数");
/*无需通过类or对象去访问,直接调用!*/
}
(i)静态成员函数vs(ii)普通成员函数
class Wbm
{
public:
/*其(成员函数)在被调用时都会传入一个this指针*/
void print(int a)
{
cout << "包装成员函数" << endl;
}
static void printStatic()
{
cout << "包装静态成员函数" << endl;
}
protected:
};
void testMemberFunc()
{
//(1)包装静态成员函数
function funcStatic = Wbm::printStatic;
/*等效于function funcStatic(Wbm::printStatic);*/
funcStatic();
//(2)包装普通成员函数 ->注意:一定要结合bind函数结合在一起
Wbm bmw;
functionfunc(bind(&Wbm::print, &bmw, placeholders::_1));
/*bind的第一个参数:需要修改的函数指针
第二个参数的原理:调用成员函数默认(在声明最后一个参数的位置的后面增添)会传入一个指向这个对象(不是类)的指针this 即Wbm*类型->所以要通过一个实在的对象bmw来取地址传入
第三个参数是占位符,仅这一个位置需要传参(this指针在前面一个参数已经被bind绑定好了)*/
func(555);
}
(a)包装普通成员函数 ->注意:一定要结合bind函数结合在一起
(b)/*bind的第一个参数:需要修改的函数指针,即普通成员函数名
第二个参数的原理:调用成员函数默认(在声明最后一个参数的位置的后面增添)会传入一个指向这个对象(不是类)的指针this 即Wbm*类型->所以要通过一个实在的对象bmw来取地址传入
第三个参数是占位符,仅这一个位置需要传参(this指针在前面一个参数已经被bind绑定好了)*/
void printData(int a, MM mm, string str)
{
cout << "bind和function" << endl;
}
void TestFuncBind()
{
function pf = bind(printData,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);
pf("string", 1, Wbm());
//传参顺序的原理:见bind相关讲解!
}
//传参顺序的原理:见bind相关讲解!