函数调用运算符
圆括号()就是函数调用的明显标记,()有一个称呼叫作函数调用运算符
如果在类中重载类(),那么我们就可以像使用函数一样使用该类的对象了对象(实参)
class biggerthanzero
{
public:
//重载()
biggerthanzero(){
cout<<"biggerthanzero()"<<endl;
}
biggerthanzero(int){
cout<<"biggerthanzero(int)"<<endl;
}
int operator()(int value) const{
if(value<0) return 0;
return value;
}
};
int i = 100;
biggerthanzero obj;
int result = obj(i); //等价于int result = obj.operator()(i);
biggerthanzero obj1(i); //定义对象并初始化
obj1(i); //调用()
只要一个对象所属的类重载了()函数调用运算符,那么这个类就变成了可调用的了,而且可以 调用多个版本的(),只要参数类型或者数量上有差别即可
如果一个类重载了(),那么该类也叫做函数对象
不同调用对象的相同调用形式
int echovalue(int value){ //调用参数与返回值与biggerthanzero中的()相同
cout<<value<<endl;
return value;
}
函数echovalue()与对象biggerthanzero的重载的()调用函数与返回值相同,就叫作调用形式相同,他们都是int(int)型,接受一个Int,返回一个int
可调用对象:
1)echovalue()函数
2)重载了函数调用运算符的biggerthanzero类对象
把这些可调用对象的指针保存起来,目的是方便我们随时调用这些可调用对象,这些指针类似于函数指针,可以保存在map容器中,每一项两个数据,一个键一个值map<string,int(*)(int)> myoper
map<string,int(*)(int)> myoper;
myoper.insert({"ev",echovalue}); //成功
biggerthanzero obj;
myoper.insert({"bt",obj}); //失败
标准库function
首先应该#include<functional>
function类模板:要提供模板参数来表示该function类型能够表示的对象的调用形式
//function<int(int)> //用来声明一个function类型,用来表示一个可调用对象
function<int(int)> f1 =echovalue; //函数指针,如果有重载,则不能放入
function<int(int)> f2 = obj; //类对象
function<int(int)> f3 = biggerthanzero(); //用类名生成一个对象,也可以
f1(5); //成功
f2(5); //成功
f3(5); //成功
map<string,function<int(int)>> myoper={
{"ev",echovalue},
{"bt",obj},
{"bt2",biggerthanzero()},
}
myoper["ev"](5); //成功
myoper["bt"](5); //成功
myoper["bt2"](5) //成功
可以通过定义函数指针来解决函数重载不能使用function<int(int)>的问题定义函数指针不会产生二义性
int echovalue(int value){
cout<<value<<endl;
return value;
}
int echovalue(){
return 0;
}
int (*fp)(int) = echovalue;
int (*fp2)() = echovalue;
function<int(int)> f2 = fp;
function<int(int)> f2 = fp2;
万能引用/未定义引用
void func(int &&tmp){
cout<<tmp<<endl;
}
func(10); //成功,右值
int i= 100;
func(i); //失败,i是左值
/*****************************************************/
template<typename T>
void func(T&& tmp){
cout<<tmp<<endl;
}
func(10); //成功,右值
int i= 100;
func(i); //成功
func()修改为函数模板之后
1)tmp既能接受左值,也能接受右值
2)tmp的类型为T&&
从接收左值没报错可知,T被推断出来的应该不仅仅是int型。
万能引用需要的语境
1)必须是函数模板
2)必须发生了模板类型推断并且函数模板形参的样子是:T&&;auto也存在万能引用的概念
T&&就是万能引用
万能引用和右值引用的区别:
1)右值引用传递右值,否则编译器报错
2)万能引用做函数形参时,可以传递左值,也可以传递右值;如果传递的是左值,那么这个万能引用就变成了左值引用,T&;如果传递的是右值,那么这个万能引用就变成了右值引用,T&&
template<typename T>
void func(std::vector<T>&& tmp){ // 右值引用,必须是T&&才是万能引用
cout<<tmp<<endl;
}
const修饰词会剥夺一个引用成为万能引用的资格,会被强制认为右值引用
template<typename T>
void func(const T&& tmp){ //右值引用,而不是万能引用
cout<<tmp<<endl;
}
template<typename T>
class A
{
public:
void testfunc(T&& tmp){}; //右值引用,而不是万能引用
template<typename T2>
void testfunc2(T2&& tmp){}; //万能引用
};
引用折叠
c++11新标准,是一条规则,引用坍塌
c++中明确含义的引用只有两种,一种是&左值引用,一种是&&右值引用
在函数模板推断时,会出现下列这些情况;自己不能这么写
void func(int& &&tmp){}
,左值引用+右值引用,最终为左值
void func(int&& &tmp){}
,右值引用+左值引用,最终为左值
void func(int& &tmp){}
,左值引用+左值引用,最终为左值
void func(int&& &&tmp){}
,右值引用+右值引用,最终为右值
折叠规则:如果任意一个引用为左值引用,那么结果就为左值引用
引用的引用
int b =500;
int &by = b; //by是b的引用
int & &byy = by; //非法,byy是by的引用,引用的引用非法
//引用的引用不能在自己写的代码中出现,只能在编译器内部推断时出现;有这个叫法,没这个写法
转发/完美转发
void func(int v1,int v2){
++v2; //改变v2值,自加1
cout<<v1+v2<<endl; //91
}
int i = 50;
func(40,i);
模板函数:目的要把收到的参数以及这些参数相对应的类型不变的转发给其他函数(转发给func函数)
template<typename F,typename T1,typename T2>
void functemp(F f,T1 t1,T2 t2) //F就是第三方我们要调用的函数,就是要转发到的目标函数
{
f(t1,t2);
}
void func1(int v1,int &v2){
++v2; //改变v2值,自加1
cout<<v1+v2<<endl; //91
}
int j =70;
functemp(func,20,j); //91,j=70
functemp(func1,20,j); //91,j=70,j的数值有问题,应该为71
修改functemp模板函数,让这个模板函数的参数能够保持给定实参的左值性。万能引用可以,实参的所有信息都会传递到万能引用中,从而让编译器推导出来函数模板的最终形参类型;如果是普通引用T&,则实参中只有const属性能传递到函数模板,实参中的左值和右值性质就不能传递到函数模板中
template<typename F,typename T1,typename T2>
void functemp(F f,T1 &&t1,T2 &&t2) //F就是第三方我们要调用的函数,就是要转发到的目标函数
{
f(t1,t2); //形参总是左值,t1,t1本身为左值;而func2里边的v1类型是个右值引用,注定v1要绑定一个右值
}
void func1(int v1,int &v2){
++v2; //改变v2值,自加1
cout<<v1+v2<<endl;
}
void func2(int &&v1,int &v2){
++v2; //改变v2值,自加1
cout<<v1<<" "<<v2<<endl;
}
int j =70;
int &&youzhi = 80; //右值引用绑右值,虽然&&youzhi是绑定到右值的,但是youzhi本身是左值
// int &&t = youzhi //报错,所以youzhi是左值
// func2(youzhi,j); //报错,youzhi不是右值
functemp(func1,20,j); //91,j=71
func2(20,j); //20 70
functemp(func2,20,j); //报错,无法将参数1从int转为int &&,说明这个20有问题
完美转发:上面的例子不能接受右值引用,而完美转发让我们可以写接受任意类型实参的函数模板,并将其转发到目标函数,目标函数会接受到与转发函数所接受到的完全相同(左值和右值性质)的实参。只有通过std::forward实现
std::forward:c++11新函数,专门为转发而存在,要么返回一个左值,要么返回一个右值。能力是按照参数本来地类型(保持原始实参类型)转发。实参原来是左值,到了形参中还是左值,forward是按照实参原来的类型处理,即处理后还是左值;实参原来是个右值,到了形参中变成了左值,forward按照实参原来的类型处理,即处理后是个右值。
发挥作用的条件:调用模板函数,模板函数参数是万能引用类型,模板函数负责转发
template<typename F,typename T1,typename T2>
void functemp(F f,T1 &&t1,T2 &&t2)
{
f(std::forward<T1>(t1),std::forward<T2>(t2)); //完美转发
}
auto推断
c++11,auto用于变量的自动类型推断:在声明变量时根据变量的初始值的类型自动为此变量选择匹配类型,不需要我们显式指定
1)auto发生在程序编译期间,不会影响程序执行的性能
2)auto定义变量必须立即初始化,这样编译器才能推断它的实际类型
3)auto的使用很灵活,可以和指针,引用,和const等限定符结合使用
auto推断出来后代表一个具体的类型,所以auto是类型声明的一部分,可以理解为类型占位符
auto x=27; //x:int,auto:int
const auto x2 = 27; //x2:const int,auto:int
const auto x3 = x;//x3:const int,auto:int
const auto &x4 = x; //x4:const int &,auto:int
auto x5 = x; //x5:int,auto:int
auto &x6 = x4; // x6:const int &,auto:const int 引用被丢掉,const属性保留
auto y = new auto(100); //y:int *,auto:int *
const auto *xp = &x; //xp:const int *,auto:int
auto *xp2 = &x; //xp2:int *,auto:int
auto xp3 = &x; //xp3:int *,auto:int *
auto&& xy = x; //x是左值,xy:int &,auto:int & 万能引用
auto&& xy2 = x2; //x2是左值,xy2:const int &,auto:int &
auto&& xy3 = 100; //100是右值,xy3:int &&,auto:int
传值方式的auto会抛弃引用,const等限定符
const char mystr[] = "i love china!" //mystr:const char[14]
auto myarr = mystr; //const char *
auto &myarr2 = mystr; //const char &(14)
int a[2] = {1,2};
auto t = a; //t:int *,auto:int *
void func(double,int){};
auto tmpf = func; //void(*)(double,int) 函数指针
auto tmpf2 = func; //void(&)(double,int) 函数引用
auto z = {10}; //z: std::initializer_list<int>
auto z2{10}; //z2:int
auto不能用于函数参数;类中的普通成员变量不能使用auto,静态成员可以使用auto,使用auto后,其值必须在类内初始化
decltype:用于推导类型,自动类型推断发生在编译阶段,declytpe不会真正计算表达式的值
const int i = 0;
const int &iy = i;
auto j1 = i; //j1:int
decltype(i) j2 = 15; //j2:const int
decltype(iy) j3 = j2; //j3:const int &
评论区