C++学习第十一

1)适配器的学习
适配器:把一个既有的东西,增加点东西,或者减少点东西,使之成为另外一个东西;
适配器分类: 容器适配器,算法适配器,迭代器适配器
容器适配器:stack、queue两个容器,是属于阉割版的deque,换句话说,deque是stack、queue的祖宗,所以stack、queue当适配器看待。
算法适配器:也就函数适配器,最典型的是绑定器,bind1st,bind2nd; C++11用bind();
bind(less(),40,placeholders::_1);
//less的operator()的第一个参数绑定为40,第二个参数是在被调用时给出;
std::bind函数适配器的使用方法:

int main()
{
    
    
	std::vector<int> vec{
    
     1,20,13,54,51 };
	//auto f = std::bind(less<int>(), 20, std::placeholders::_1); 生成了一个新的对象
	//f(19);
	int num = count_if(vec.begin(),vec.end(),std::bind(less<int>(),10,std::placeholders::_1));
	//std::placeholders::_1就会被迭代器元素所取代!
	cout << num << endl;
	return 0;
}

1.1)终于明白std::bind绑定适配器的使用方法了

void func(int a, int b, int c)
{
    
    
	cout << a << " " << b << " "<< c << endl;
}

int main()
{
    
    
	//std::placeholders::_xxx跟形参位置无关,跟从1掉20依次出现的次数有关!!!
	auto f1 = std::bind(func,2, std::placeholders::_2, std::placeholders::_1); //bind的第一个参数是需要绑定的函数地址
	f1(7,10); //输出的是2 10 7 
	return 0;
}

1.2)C++学习网址

https://cplusplus.com/
https://en.cppreference.com/w/
https://zh.cppreference.com/w/%E9%A6%96%E9%A1%B5

2)重载小括号"()"的3种调用方式:
(1)采用匿名对象调用重载函数;
(2)调用成员函数的方式;
(3)重载的调用方式;

class A
{
    
    
public:
	bool operator()(int x, int y)
	{
    
    
		if (x > y) return true;
		else
			return false;
	}
};

int main()
{
    
    
	cout << A()(20, 4) << endl; //1)匿名对象调用,仿函数是采用这种调用方式!
	A a;
	cout << a.operator()(3, 4) << endl; //2)成员函数调用
	cout << a(3, 4) << endl; //3)重载调用的方式,此写法相当于lambda表达式
	//a(3,4)和下面lambda表达式调用其实是一样的。
	auto fun = [](int a,int b)->int {
    
    
		return a + b;
	};
	cout << fun(4, 5) << endl;

	return 0;
}

3)函数对象包装器std::function()的作用:
对可调用对象(普通函数,lambda,仿函数,成员函数等)进行统一管理(个人理解);
std::function 函数对象包装器 function wrapper 英 [ˈræpə]; w在单词开头,后面紧跟r,w不发音;
在函数对象包装器中,也把普通函数当成对象看待和处理!!!


相关案例代码如下:
#include<iostream>
#include<functional>

using namespace std;

int test(int a,int b)
{
    
    
	cout << a << " " << b << endl;
	return a;
}

class A
{
    
    
public:
	int operator()(int x)
	{
    
    
		cout << x + 10 << endl;
		return 10 + x;
	}
	int test(int a)
	{
    
    
		cout << a + 10 << endl;
		return 10 + a;
	}
};


//普通函数,lambda,仿函数,成员函数
int main()
{
    
    
	//普通函数
	std::function<int(int,int)> f = test;
	f(10,20); // 10 20

	//lambda
	std::function<int(int)> f2 = [](int x)->int {
    
    return x + 4; };
	cout << f2(56) << endl; //60 

	//std::bind返回的新的函数
	std::function<int(int)> f3 = std::bind(test,std::placeholders::_1,4);
	f3(45); //45 4

	仿函数
	std::function<int(int)> f4 = A(); //A():这样会生成一个临时对象
	f4(10);

	//成员函数
	std::function<int(A*, int)> f5 = &A::test;
	A a;
	f5(&a, 10);
	return 0;
}

//输出结果:
10 20
60
45 4
20
20
4)万能引用的学习
万能引用只是一个称呼,是个概念,并不是一个新的引用类型,没有万能引用这种实际类型;
//int &&b = 100; //对这条语句的理解:1) 右值引用绑右值;
//2)虽然&&b是绑定右值的,当b本身是个左值,因为b是在等号左边呆着的;怎么可以证明呢? 如果写 int& m = b;则可以编译通过,所以b是一个左值;
int&& c = 10;
int& m = c;
上面两天语句中,"&&c"叫右值引用;"c"是个左值(有地址),但是它的类型是右值引用!!!
也就是说,不管是左值引用还是右值引用这种概念,说的是"c"的类型,而不是"c"本身!!!
//右值引用的正确写法:int&& c = 10; 左值引用的正确写法: int& m = 10;

using namespace std;
//万能引用也就未定义引用
//万能引用两个语境:1)必须是函数模板,2)参数必须“T&&”;
template<typename T>
//不会推断为 "int&&,如果是这样,就会变成左值引用了。
void func(T&& tmp) //“&&”是和tmp类型的一部分,跟"T"没有任何关系;
{
    
    
	cout << tmp << endl;
}
int main()
{
    
    
	func(23);
	int i = 90;
	func(i);
	return 0;
}

5)std::forward完美转发
std::forward,要么返回一个左值,要么返回一个右值;
std::forward的能力就是按照原有类型转发;两种理解:
1)实参是左值,啥也没干;
2)实参是右值,到了形参是左值,因为forward按照原类型处理,所以forward之后是个右值;
forward的能力就是保持原有类型进行转发,std::forward有一个模板参数;
调用模板函数,模板函数参数是万能引用类型,模板函数负责转发;

void printMsg(int& obj)
{
    
    
	cout <<"左值引用: "<< obj << endl;
}

void printMsg(int&& obj)
{
    
    
	cout << "右值引用:   " << obj << endl;
}

template<typename T>
//实参是左值还是右值,这个信息会被保存到func里面的'T'类型中去;
void func(T&& tvalue) //虽然"T&&"中的"&&"和'T'表面结合在一起,但实际上是和tvalue在一起,本质上是"T &&tvalue";
{
    
    
	//T有自己的类型,tvalue也有自己的类型,这两个类型是完全两码事!!!
	//如果传递tvalue的实参右值,那么T = int类型,     tvalue = int &&类型; 但 tvalue本身是左值;
	//如果传递tvalue的实参左值,那么T = int&类型,tvalue = int &类型; tvalue本身是左值;
	printMsg(tvalue); 
	printMsg(std::move(tvalue));
	printMsg(std::forward<T>(tvalue));
}

int main()
{
    
    
	//传递一个右值
	func(1); //左值 右值 右值
	//int i = 10;
	//传递一个左值
	//func(i);   //左值 右值 左值
	return 0;
}

6)引用折叠

引用折叠四种发生情况:引用折叠是一种规则!!!只有编译器内部能用
void func(int& &&tvalue) //两组,第一组 int&,左值引用; 第二组,&&tvalue右值引用;
//分成两组,四种可能情况,都会进行引用折叠:
左值引用/右值引用 		左值引用/右值引用		结果
左值引用&				左值引用&				左值引用&
左值引用&				右值引用&&				左值引用&
右值引用&&				左值引用&				左值引用&
右值引用&&				右值引用&&				右值引用&&
引用折叠规则:如果任意一个引用为左值引用,那么结果就是左值引用(传染),否则就是右值引用;

引用折叠案例分析:
template<typename T>
void func(T&& tvalue){
    
    }
int i = 9;
//如果传递tvalue的实参右值,那么T = int类型,     tvalue = int &&类型; 但 tvalue本身是左值;
//如果传递tvalue的实参左值,那么T = int&类型,tvalue = int &类型; tvalue本身是左值;
func(i); //传递一个左值,则调用上面的模板函数,编译器最终推断的实际函数类型是: void func(int& tvalue)({} //把"tvalue"的类型"int &"直接带进来就成这个样子
void func(int& && tvalue){
    
    } //理论上推断是这个类型,或者写成void func(int& &&tvalue)这样,注意:“&”和“&&”是有空格的,这个很重要;

7)引用的引用
编译器内部在模板进行类型推断的时候,可以出现引用的引用,利用引用折叠规则进行优化,但自己的代码中不能出现引用的引用
//void func(int& &&tmp){}//编译器会报错,不允许使用对引用的引用
int a = 10;
int& b = a;
//int& c = b; //这不叫引用的引用
int& &c = b; //这叫引用的引用,注意有空格,编译器不支持引用的引用

template<typename F,typename T1,typename T2>
void funcTemp(F f, T1 t1, T2 t2) //f为目标函数
{
//这条语句明显是错误的,但可以编译通过,主要是没有调用funcTemp这个函数模板,所以不会生成对应的模板函数;
aaaa(t1, t2);
}
8)转发std::forward()
函数模板目的:把收到的参数以及这些参数的类型,一五一十的转发给其他函数;
测试代码如下:


#include<iostream>
#include<functional>

using namespace std;
//函数模板目的:把收到的参数以及这些参数的类型(如左值引用,右值引用,const等),
//原封不动的转发给其他函数,如(void func(int v1, int v2))
template<typename F,typename T1,typename T2>
//void funcTemp(F f, T1 t1, T2 t2) //f为目标函数
void funcTemp(F f, T1&& t1, T2&& t2) //需要这样修改
{
    
    
	//针对funcTemp(func, 20, j);函数调用,经过万能引用,编译器会推导出下面类型:
	//T1 = int,t1 = int&& T2 = int& t2 = int&
	//这条语句明显是错误的,但可以编译通过,主要是没有调用funcTemp这个函数模板,所以不会生成对应的模板函数;
	//主函数如果调用 funcTemp(func, 20, j);会出错
	//f(t1, t2); //t1本身是左值,而func里面的v1类型是右值引用,注定v1要绑定一个右值,所以出错;解决了左值问题,右值会有问题!
	f(std::forward<T1>(t1), std::forward<T2>(t2)); //需要这样修改,既可以解决左值,也可以解决右值
}

void func(int v1, int& v2)
{
    
    
	++v2;
	cout << v1 + v2 << endl;
}
int main()
{
    
    
	//int i = 50;
	//func(41,i); //这里i编程51是对的
	//cout << i << endl;

	int j = 70;
	funcTemp(func, 20, j);//如果不用std::forward,这里j并没有变为71
	//为什么没有改变呢:因为j绑定的是模板函数的t2,实际上并没有绑定func上的v2上;
	//那怎么修改呢,需要用到std::forward函数,参数采用的使用万能引用就可以了
	cout << j << endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_30143193/article/details/132668098