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;
}