Day19:C++STL迭代器/Lambda表达式/仿函数/函数适配器和包装器

目录

一、迭代器

        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结合使用,实现改变传参顺序的效果。


一、迭代器

        1.原理:

                迭代器: 就是一个类中类,通过运算符重载通过类中类的对象去遍历容器

        2.迭代器的分类:

                ①正向迭代器:

                         iterator中的 begin() end()

                ②反向迭代器:

                        反向迭代器: reverse_iterator (从容器的后面向前面遍历)

                        ->迭代器只能++运算,不能--                  rbegin() rend()

#include<iostream>
#include<list>
using namespace std;

int main()
{
	list<int> 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_iterator (容器中的一个类对象)       

                                ( 与非const的区别就是:不能通过此迭代器对容器中元素进行修改                                                   cbegin()  cend()        

                        ④常反向迭代器:const_reverse_iterator

                                        crbegin()       crend()

        补:自己实现一个简单的迭代器

template<class _Ty>
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 _Ty>
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_List<int>list;
	list.push_front(1);
	list.push_front(2);
	list.push_front(3);
	list.push_front(4);
	//list.printList();
	My_List<int>::iterator iter = list.begin();
	for (; iter != list.end(); iter++)
	{
		cout << *iter << "\t";
	}
}
int main()
{
	testMylist();
	return 0;
}

        3.其他分类方式:

                按功能分类

                        ①正向迭代
                        ②双向迭代
                        ③随机访问迭代器

        4.迭代器辅助函数:       

  • 移动:advance(iterator iter,n);

  • 间距:distance(iterator begin,iterator end);

  • 交换:iter_swap(iterator first,iterator end);

 示例:

/*辅助迭代器函数*/
void testExFunction()
{
	vector<int>data = { 1,2,3,4,5,6,7,8,9 };
	auto iter = data.begin();
	advance(iter, 3);
	cout << *iter<<endl;
	/*distance优化后,距离可以为负数*/
	cout << "距离count:  " << distance(data.begin(), data.end()) << endl;
	iter_swap(data.begin(), data.end()-1);
	for (auto v : data)
	{
		cout << v << "\t";
	}
	cout << endl;
}

输出:

 4
距离count:  9
9       2       3       4       5       6       7       8       1

 再次提醒:end()是指向容器最后一个位置的后一个位置!!!!!!想访问最后一个元素,一定要减去1!!!!

        5.特殊迭代器--流型迭代器-->一般用于辅助打印

                ①输出流型:  

  • ostream_iterator<_Ty> iter(ostream& out);

  • ostream_iterator<_Ty> iter(ostream& out,char* str);

  • 输出流型迭代做赋值运算,意味着就是打印数据到屏幕上

                实例:

void testOstreamIterator()
{
	ostream_iterator<int> iter(cout);
	iter = 666;				/*效果:将等号后面的内容打印至屏幕上*/
	cout <<endl<< "----------------"<<endl;
	ostream_iterator<int> iter2(cout, "kkkk");
	iter2 = 999;
	cout << endl << "----------------" << endl;
	vector<int>data = { 1,2,3,4,5,6,7,8,9 };
	copy(data.begin(), data.end(), ostream_iterator<int>(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<int> end;			//无参构造,返回一个end_of_stream错误流对象,用于比较
	istream_iterator<int>in(cin);		//有参构造!
	vector<int> 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无法终止!!!

二、Lambda表达式

        相关的好文章---来源C++primer

        1.定义:

                是一个返回函数指针的表达式,它定义和返回值函数指针在一起的

       2.Lambda表达式的组成部分:

// [捕获方式](函数参数)mutable exception->函数返回值类型{函数体;}
/*mutable表示能否修改*/        /*exception表示是否有异常 */->常写noexcept表示无异常

       3.捕获方式:(函数使用外部的变量的方式

    [=]   //值的方式捕获(不会同步到外部变量)->只能当做右值使用,不可以当做左值赋值!
    [&]      //引用的方式捕获(会同步到外部变量)->可改变!
    [this]//this指针方式捕获(捕获类中的数据成员)
    [ ]    //不捕获任何变量
    [=,&x];//x用引用方式捕获,其他变量用值的方式捕获

        实例代码

                包含①完整版lambda表达式和偷懒版的写法  ②最常用的写法与应用场景:回调表达式

                       ③三种回调表达式的区别。④lambda表达式中的“函数模板”(在函数中是不允许auto推断参数的,这样写类似于函数模板

#include<iostream>

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

 三、仿函数

        1.什么仿函数?

                类模仿函数调用行为,实质是无名对象调用重载的()函数,

                        所以仿函数的关键点在于重载()

        2.作用:

                一般情况仿函数是做排序准则,或者一些算法的计算准则

        3.标准库中的仿函数:          

                  算术类、关系类、逻辑类、选择,证同,投射(几何相关)

                示例:注意注释部分!头文件<functional>

#include <iostream>
#include <string>
#include <functional>		//仿函数所在头文件
#include <map>
using namespace std;
class Sum 
{
public:
	int  operator()(int a, int b) const 
	{
		return a + b;
	}
};
int main()
{
	//重载的()的调用方式
	Sum s;
	cout << "显式调用:" << s.operator()(1, 3) << endl;		//显式调用重载函数
	cout << "隐式调用:" << s(1, 3) << endl;
	//用{}和()帮助编译器去做解析
	cout << "无名调用:" << Sum{}(1, 3) << endl;			//类模仿函数调用行为--->仿函数
	cout << "无名调用:" << Sum()(1, 3) << endl;			//类模仿函数调用行为--->仿函数
	//算术
	cout << plus<int>{}(1, 3) << endl;			   //作为算法的计算准则->可以自己写,传参
	//关系
	cout << equal_to<int>{}(1, 3) << endl;
	map<int, less<int>> map1;				/*常用(记),传入一个仿函数*/
	map<int, greater<int>> map3;
	//逻辑类
	cout << logical_and<int>{}(1,0) << endl;
	//求大于3 小于10的数字
	//没必要的做的事情
	int a = 34;
	if (logical_and<int>{}(a > 3, a < 10)) 		/*吃饱后的代码->等效(a>3)&&(a<10)*/
	{
		cout << "大于3 小于10的数字" << endl;
	}

	return 0;
}

 四、函数适配器:

1.概念:什么是函数适配器

        用来绑定函数调用时候的参数,让函数适应其他调用的用法(使得接口、参数能够对上)

2.误区/注意点:

        bind并没有改变函数参数的个数与类型,原来是两个参数bind后仍然是两个参数(单纯的给你绑定一个参数,只是增加调用性态,并没有真正改变这个函数指针的类型)。

3.用法:

        ①普通函数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<int>比较的第二个参数改为60,left>60返回bool类型给这个位置)

	/*应用实例,count_if(条件)算法,找出大于60分的个数*/
	vector<int> vecData = { 19,43,89,89,34,54,67,54 };
    //法一:适配器
	cout << count_if(vecData.begin(), vecData.end(),
         bind(greater<int>(), 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<int> vecData = { 19,43,89,89,34,54,67,54 };
    cout << count_if(vecData.begin(), vecData.end(), Greater_than_60)<< endl;
}

         ②类成员函数的bind()使用

                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<functional>

1.概念:

        就是把函数指针包装成一个对象。通过这个对象调用函数。一旦函数指针被函数包装器包装了,那这个包装器对象可以直接替换函数指针的用法去调用函数        

2.写法:

        函数包装器类的实例化传参:

                function<函数返回值类型(参数类型)>

        ①普通函数包装                        

#include <iostream>
#include <string>
#include <functional>
using namespace std;

int Max(int a, int b)
{
	cout << "包装普通函数:" << endl;
	return a > b ? a : b;
}
int main()
{	/*写法一:显式*/
	function<int(int, int)> funcMax(Max);
	cout << funcMax(1, 3) << endl;
	/*写法二:隐式*/
	function<int(int, int)> funcMax2 = Max;
	return 0;
}

        ②仿函数的包装:

                注意区别:需要通过一个实例对象test来包装这个仿函数(类)

void testFunctors()
{
	//仿函数(无名对象+()模仿函数行为)包装
	Test test;
	function<void(string)> 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<void()> funcStatic = Wbm::printStatic;
	/*等效于function<void()> funcStatic(Wbm::printStatic);*/
	funcStatic();
	//(2)包装普通成员函数 ->注意:一定要结合bind函数结合在一起
	Wbm bmw;
	function<void(int)>func(bind(&Wbm::print, &bmw, placeholders::_1));
	/*bind的第一个参数:需要修改的函数指针
			第二个参数的原理:调用成员函数默认(在声明最后一个参数的位置的后面增添)会传入一个指向这个对象(不是类)的指针this 即Wbm*类型->所以要通过一个实在的对象bmw来取地址传入
			第三个参数是占位符,仅这一个位置需要传参(this指针在前面一个参数已经被bind绑定好了)*/
	func(555);
}

 (a)包装普通成员函数 ->注意:一定要结合bind函数结合在一起

 (b)/*bind的第一个参数:需要修改的函数指针,即普通成员函数名
            第二个参数的原理:调用成员函数默认(在声明最后一个参数的位置的后面增添)会传入一个指向这个对象(不是类)的指针this 即Wbm*类型->所以要通过一个实在的对象bmw来取地址传入
            第三个参数是占位符,仅这一个位置需要传参(this指针在前面一个参数已经被bind绑定好了)*/

           ④bind和function结合使用,实现改变传参顺序的效果。

void printData(int a, MM mm, string str)
{
	cout << "bind和function" << endl;
}
void TestFuncBind()
{
	function<void(string, int, Wbm)> pf = bind(printData,
		std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);
	pf("string", 1, Wbm());
	//传参顺序的原理:见bind相关讲解!
}

                        //传参顺序的原理:见bind相关讲解!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Ocean__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值