龙岩网站优化,h5编辑工具,临汾外贸网站建设价格,客户关系管理系统函数调用与函数调用运算符
先写一个简单的函数#xff0c;如下#xff1a;
/*函数的定义*/
int func(int i)
{cout这是一个函数\tiendl;
}void test()
{func(1);//函数的调用
}
通过这个普通的函数可以看到#xff0c;调用一个函数很…函数调用与函数调用运算符
先写一个简单的函数如下
/*函数的定义*/
int func(int i)
{cout这是一个函数\tiendl;
}void test()
{func(1);//函数的调用
}
通过这个普通的函数可以看到调用一个函数很简单首先是函数名后面跟一对圆括号如果这个函数有参数就在函数调用位置的函数名后圆括号中给出实参在函数定义位置函数名后圆括号中给出形参 函数调用总离不开一对圆括号这个“”就叫函数调用运算符 特别的如果在类中重载了这个函数调用运算符我们就可以像使用函数一样使用类的对象换句话说就可以像函数调用一样来“调用”该类的对象。 这种重载了的类对象就叫做函数对象如下 class biggerThanZero
{
public:int m_i;biggerThanZero(int i):m_i(i){cout构造函数调用了endl;}int operator()(int num){if(num0) return 0;return num;}
};void test()
{biggerThanZero bz1(10);//这是对象定义的初始化会调用类的构造函数int num-20;coutbz1(num)endl;//这才是调用了类的函数调用运算符} 分析代码并观察运行结果可以看到测试函数中的第一行代码是一个正常的调用构造函数对类对象进行初始化的代码而接下来的两行代码里的bz1num才是对函数调用运算符的调用观察其书写形式可以发现其调用形式与文章开头的普通函数func调用形式是相同的。 这就是可调用对象即 func函数重载了函数调用运算符“”的biggerthanzero类所生成的对象bz1我们调用bz1时可以像调用函数那样进行调用 问题的引出
继续观察这两个可调用对象可以发现他们的调用形式都是相同的也就是int (int)同时这也是func的函数类型 注函数类型就是返回值类型和参数类型的组合。 如func函数中的参数类型是int而返回值类型int因此func的函数类型就是int (int)。 同理假如现在有一个函数的声明为 void fun(double);则这个fun函数的函数类型就是 void (double) 而我们又知道我们可以使用一个函数指针来保存函数地址那么操作这个函数指针就等同于操作这个函数指针所指向的函数如下所示
void test()
{int (*fp)(int);//定义一个函数指针也就是可以保存函数地址的指针fpfunc;//在c中函数名就是这个函数的地址也可以使用fpfuncfp(1);//由于fp现在指向func因此调用fp(1)就等同于调用func(1)
}
但是对于相同的调用形式的函数对象我们却不能使用函数指针进行统一调用
void test()
{int (*fp)(int,int);biggerThanZero bz(0);//类对象初始化fpbz;//报错
} 这说明系统并没有把类biggerthanzero的对象bz看成一个函数指针当然系统更不可能把类biggerthanzero看成一个函数指针 那么问题来了对于具有相同调用形式的不同可调用对象是否可以使用一个统一的调用呢 function类模板的引出
暂时先搁置一下上边提到的概念我们暂时已经知道以下三点 普通函数和重载了函数调用运算符的对象都是可调用对象可以使用函数指针统一调用普通函数这个可调用对象但是同为可调用对象的函数对象却无法使用函数指针进行统一调用 接下来我们做两个实验来引出function类模板
观察以下代码
int max(int a,int b)
{return (ab)?a:b;
}int min(int a,int b)
{return (ab)?a:b;
}void test()
{coutmax(1,2)endl;coutmin(1,2)endl;
}
在这段代码里我们定义了两个函数分别是max函数和min函数并且我们在测试函数中调用了他们。
实验一
仔细观察这段代码可以发现max函数和min函数的函数类型是相同的都是int(int ,int )因此我们可以使用一个函数指针来对他们进行统一形式的调用如下
void test()
{int (*fp)(int,int);//定义一个函数指针fpmax;//将max函数的函数地址赋给函数指针fpcoutfp(1,2)endl;//现在fp指向的是max函数调用fp(1,2)等同于调用max(1,2)fpmin;//将min函数的函数地址赋给函数指针fpcoutfp(1,2)endl;//现在fp指向的是min函数调用fp(1,2)等同于调用min(1,2)
}
运行结果如下 其实这就是函数指针最开始的作用但是后来面向对象诞生之后这种写法就被替代了。 实验二
再进一步我们可以使用一个map来对这段逻辑进行重写如下
void test()
{mapstring,int(*)(int,int) mp;mp.insert({max,max});mp.insert({min,min});coutmp[max](1,2)endl;//等同于调用max(1,2)coutmp[min](1,2)endl;//等同于调用min(1,2)
} 在上述代码里我们使用一个map对上述调用形式进行了统一的改写map键值对的键类型是string而值类型是函数类型为int(int,int)的函数指针 综上观察我们可以说 同为可调用对象且函数对象和普通函数的调用形式是相同的但是我们只能使用函数指针对调用形式相同函数类型名相同的普通函数进行统一调用但是我们却无法再进一步对相同调用形式的函数对象进行统一调用 为了解决以上问题c引入了function模板的概念我们直接看代码
int func(int i)
{cout这是一个函数\tiendl;
}class biggerThanZero
{
public:int m_i;biggerThanZero(int i):m_i(i){cout构造函数调用了endl;}int operator()(const int num){if(num0) return 0;return num;}
};void test()
{functionint(int) fp2;fp2func;coutfp2(10)endl;biggerThanZero bz(0);fp2bz;coutfp2(-1)endl;
}现在我们可以拿出function类模板的定义和作用了 function的类模板就是用来包装一个可调用对象的换句话说它可以像函数指针那样将不同的可调用对象包装成一个相同的东西以方便我们使用 正如我们使用函数指针那样对于同一个函数指针我们可以赋给它不同的函数地址只要这些函数的调用形式是相同的我们就可以使用这一个指针实现不同的函数调用而function类模板实现的就是类似的功能 现在我们就可以进一步使用map来对可调用对象进行统一封装了
void test()
{biggerThanZero bz(0);mapstring,functionint(int) mp{{func,func},{biggerThanZero,bz}};mp[func](20);coutmp[biggerThanZero](-10)endl;
} 因此我们可以把函数、可调用对象等都包装成functionintint对象。 但是值得说明的是无法对重载的函数包装进function中 我们只能这样写 void test()
{functionint(int,int) fc;int(*fp)(int,int)max;fcfp;coutfc(1,2)endl;} 可调用对象
在上边我们已经说了有关可调用对象的概念这里再强调一下普通函数和重载了函数调用运算符的类对象也就是函数对象都是可调用对象他们都可以像使用普通函数调用那样进行调用
除了上述两个可调用对象外其余还有一些可调用对象我们一并进行介绍
1.函数指针
普通函数的函数名就是一个函数地址是一个可调用对象我们已经介绍过了 void func()
{coutfunc运行了endl;
}void test()
{void(*fp)()func;//定义函数指针并使用func的函数地址进行初始化fp();//调用函数这是一个可调用对象
}
2.函数对象仿函数
具有operator成员函数的类对象也是一个可调用对象我们也已经介绍过了。在这里我们给出函数对象仿函数的定义 仿函数的定义仿函数functors又称为函数对象function objects是一个能行使函数功能的类所定义的对象。仿函数的语法几乎和普通的函数调用一样 class TC
{
public:void operator()(int tv){coutTC::operator()执行了\ttvendl;}
};void test()
{TC tc;tc(10);//像调用函数那样调用操作符等价于tc.operator(10)
} 3.可转换为函数指针的类对象
其实函数指针都是可调用对象可以在代码中像函数一样使用。
这在我们之前探讨函数指针的时候已经见到过了当时我们定义了两个函数类型为int(int,int)的max函数与min函数并使用同一个函数指针分别指向max函数和min函数的地址进行调用使用函数指针进行调用的时候我们也观察到其调用与普通函数调用无异 现在我们说一种特殊的函数指针如下
可转换为函数指针的类对象也叫做函数对象或者仿函数
在说这个问题之前先说一下有关类型转换的问题
我们知道 一个类对象的类型就是定义这个对象的类本身比如上边的对象tc的类型就是TC。常用的普通类型之间是可以进行类型转换的如int可以转换成double而double可以转换成int再进一步我们又知道我们可以将一个int类型的变量转换为一个类类型只要这个类提供了对应的构造函数如下 class TS
{
public:int m_i;TS(){cout无参构造函数调用了endl;};TS(int a):m_i(a){cout带参数的构造函数调用了endl;}
};void test()
{TS ts;ts10;//通过构造一个临时对象将int类型的10转换为一个TS类型的变量
}当然上述这种类型转换是隐式而如果我们不希望编译器为我们使用这种int到类型之间的隐式类型转换使用显示类型转换也是可以的如下 class TS
{
public:int m_i;TS(){cout无参构造函数调用了endl;};explicit TS(int a):m_i(a)//声明explicit不允许编译器使用隐式类型转换{cout带参数的构造函数调用了endl;}
};void test()
{TS ts;//由于构造函数使用了explicit关键字不允许编译器使用隐式类型转换因此我们只能使用显示类型转换tsTS(10);
} 那么问题来了是否可以将一个类类型转换为一个普通类型呢如将上述的TS类类型转换成一个int类型
答案当然也是肯定的。 类型转换运算符 类型转换运算符也叫做类型转换函数是类的一种特殊成员函数它能将一个类类型对象转成某个其他类型数据 其一般形式为 先说以下几点说明我们再使用代码进行实验 末尾的const是可选项表示不应该改变待转换对象的内容但不是必须有const“类型名”表示要转换成的某种类型一般只要是能作为函数返回类型的类型都可以。所以一般不可以转成数组类型或者函数类型把一个函数声明去掉函数名剩余的部分就是函数类型如voidintaintb但是转换成数组指针、函数指针、引用等都是可以的。类型转换运算符没有形参形参列表必须为空因为类型转换运算符是隐式执行的所以无法给这些函数传递参数。同时也不能指定返回类型但是却会返回一个对应类型“类型名”所指定的类型的值。必须定义为类的成员函数 接下来我们做实验以做验证
class TS
{
public:int m_i;TS(){cout无参构造函数调用了endl;};TS(int a):m_i(a)//声明explicit不允许编译器使用隐式类型转换{cout带参数的构造函数调用了endl;}//类型转换运算符必须定义为一个成员函数//表示编译器可以调用这个函数隐式的将TS类型的变量转换为一个int类型的变量operator int() const{return m_i;}
};void test()
{TS ts1;//普通构造ts110;//调用带参构造将int类型的10隐式转换为TS类型的变量int rests120;//隐式调用operator int()成员函数将对象ts1转换为int类型变量之后再做加法运算coutresendl;int res2ts1.operator int()30;//也可以显示的调用operator int()成员函数将对象ts1转换为int类型变量coutres2endl;
} 上述代码执行过程为 调用无参构造函数构造ts1对象调用有参构造函数并生成一个临时对象隐式的将int类型变量的10转换为一个TS类型变量再把这个临时对象赋给ts1隐式的调用operator int()成员函数将ts1转换为int类型再进行加法运算执行1020显示的调用operator int()成员函数将ts1转换为int类型再进行加法运算执行1030 同样的如果我们想拒绝隐式类型转换也可以使用显示类型转换运算如下 class TS
{
public:int m_i;TS(){cout无参构造函数调用了endl;};TS(int a):m_i(a)//声明explicit不允许编译器使用隐式类型转换{cout带参数的构造函数调用了endl;}//类型转换运算符必须定义为一个成员函数//表示编译器可以调用这个函数隐式的将TS类型的变量转换为一个int类型的变量explicit operator int() const//拒绝编译器使用隐式类型转换{return m_i;}
};void test()
{TS ts1;//普通构造ts110;//调用带参构造将int类型的10隐式转换为TS类型的变量int resstatic_castint(ts1)20;//显示类型转换coutresendl;
} 现在我们转回正题我们现在已经知道了类类型也可以转换为其他类型如int类型而类类型也同样可以转换为函数指针类型而这种可以转换为函数指针的类对象也是一种可调用对象 为了说明问题我们继续写一个测试代码
class TS
{
public:int m_i;TS(){cout无参构造函数调用了endl;};TS(int a):m_i(a)//声明explicit不允许编译器使用隐式类型转换{cout带参数的构造函数调用了endl;}static void myfunc(int v1){coutstatic myfunc函数执行了\tv1endl;}//类型定义void(*)(int)是一个函数指针类型使用tfpoint替换这个类型//等价于typedef void(*tfpoint)(int)using tfpointvoid(*)(int);operator tfpoint(){cout类型转换运算符调用了endl;return myfunc;}
};void test()
{TS ts;//执行operator tfpoint()之后再执行myfunc(123)ts(123);
} 4.lambda表达式
lambda表达式也是一种可调用对象参见
lambda表达式c-CSDN博客 可调用对象总结 其实可调用对象首先被看作一个对象程序员可以对其使用函数调用运算符“”那就可以称其为“可调用的” function类模板
如果找通用性上述提到的这几种可调用对象的调用形式都比较统一那么有没有什么方法能够把这些可调用对象的调用形式统一一下呢有那就是使用std::function把这些可调用对象包装起来。这在我们之前也已经实验过了 但有一点需要注意function类模板用来往里装各种可调用对象但是它不能装类成员函数指针因为类成员函数指针是需要类对象参与才能完成调用的。 std::function类模板的特点是通过指定模板参数它能够用统一的方式来处理各种可调用对象。 1.绑定普通函数
void func1(int num)
{cout这是一个普通函数func1numendl;
}void test()
{functionvoid(int) fp1func1;//绑定一个普通函数func1(10);
}
2.绑定类的静态成员函数 class TC
{
public:static void stcfunc(int num){coutTC类的静态成员函数执行了:numendl;}
};void test()
{functionvoid(int) fp2TC::stcfunc;fp2(20);
}3.绑定仿函数 class TC
{
public:int m_i;TC(int i0):m_i(i){cout构造函数运行了endl;}void operator()(int num){cout函数调用运算符运行了numendl;}static void stcfunc(int num){coutTC类的静态成员函数执行了:numendl;}
};void test()
{
// functionvoid(int) fp1func1;//绑定一个普通函数
// func1(10);functionvoid(int) fp2TC::stcfunc;fp2(20);TC tc;functionvoid(int) fp3tc;//TC类声明了函数调用运算符因此对象tc是一个函数对象fp3(30);
}
std::bind绑定器 std::bind能将对象以及相关的参数绑定到一起绑定完后可以直接调用也可以用std::function进行保存在需要的时候调用。 std::bind有两个意思·
将可调用对象和参数绑定到一起构成一个仿函数所以可以直接调用。·如果函数有多个参数可以绑定部分参数其他的参数在调用的时候指定。
绑定普通函数
#includeiostream
#includefunctional
using namespace std;void func1(int x,int y,int z)
{coutxx,y,zzendl;
}void test()
{functionvoid(int,int,int) bf1bind(func1,10,20,30);bf1(10,20,30);auto bf2bind(func1,10,20,30);bf2();}int main()
{test();// system(pause);return 0;
}
参数占位符
#includeiostream
#includefunctional
using namespace std;
using namespace placeholders;void func1(int x,int y,int z)
{coutxx,yy,zzendl;
}void test()
{//_1和_2分别表示func1的第一个参数和第二个参数暂时不定需要自己传入auto bf2bind(func1,_1,_2,30);bf2(10,20);auto bf3bind(func1,_2,_1,30);bf3(10,20);
}int main()
{test();// system(pause);return 0;
} 可以看到分别调整参数占位符的位置后输出的结果是不一样的
绑定函数对象 class CQ
{
public:void operator()(int x,int y){coutx\tyendl;}
};void test()
{CQ cq;bind(cq,10,20)();bind(cq,_1,20)(5);
}
绑定类成员函数
#includeiostream
#includefunctional
using namespace std;
using namespace placeholders;void func1(int x,int y,int z)
{coutxx,yy,zzendl;
}class CQ
{
public:int m_a0;void operator()(int x,int y){coutx\tyendl;}void classFunc(int num){coutthis is a class func:numendl;m_anum;}
};void test()
{CQ cq;// bind(cq,10,20)();// bind(cq,_1,20)(5);bind(CQ::classFunc,cq,55)();coutm_acq.m_aendl;bind(CQ::classFunc,cq,_1)(23);coutm_acq.m_aendl;}int main()
{test();// system(pause);return 0;
} 注意上面的代码中
第一个std::bind的调用中第二个参数cq会导致生成一个临时的CQ对象std::bind是将该临时对象和相关的成员函数以及多个参数绑定到一起后续对myfunpt成员函数的调用修改的是这个临时的CQ对象的m_a值并不影响真实的cq对象的m_a值。
如果将std::bind的第二个参数cq前增加这样就不会导致生成一个临时的CQ对象后续的myfunpt调用修改的就会是cq对象的m_a值。
这就是为什么第二个std::bind模板调用后m_a的值改变的原因 绑定类成员变量
#includeiostream
#includefunctional
using namespace std;
using namespace placeholders;void func1(int x,int y,int z)
{coutxx,yy,zzendl;
}class CQ
{
public:int m_a;CQ(int num0):m_a(num){cout构造函数调用了endl;}CQ(const CQ cq):m_a(cq.m_a){cout拷贝构造函数调用了endl;}void operator()(int x,int y){coutx\tyendl;}void classFunc(int num){coutthis is a class func:numendl;m_anum;}virtual ~CQ(){cout析构函数执行了endl;}};void test()
{CQ cq;functionint(void) fpbind(CQ::m_a,cq);fp()66;coutcq.m_aendl;
}int main()
{test();// system(pause);return 0;
}
注意观察输出 把成员变量地址当函数一样绑定绑定的结果放在std::functionint void里保存。换句话说就是用一个可调用对象的方式来表示这个变量bind这个能力还是比较神奇的。
重点分析一下代码行“bf760”因为其上面的那行代码用了cq所以这里等价于cq.m_a60。如果cq前不用发现会调用两次CQ类的拷贝构造函数。
为什么调用两次拷贝构造函数呢
第一次是因为第一个参数为cq所以利用cq产生一个临时的CQ对象
第二次是因为std::bind要返回一个CQ对象确切地说是经过std::bind包装起来的CQ对象所以要返回的这个CQ对象仿函数复制自这个临时CQ对象但bind这行执行完毕后临时CQ对象被释放返回的这个CQ对象仿函数放到了bf7里。 所以上述std::bind代码行中一般都应该用cq否则最终会多调用两次拷贝构造函数和两次析构函数用了cq后这4次调用全省了提高了程序运行效率。
void test()
{CQ cq;functionint(void) fpbind(CQ::m_a,cq);fp()66;coutcq.m_aendl;
} 总结 因为有了占位符placeholder这种概念所以std::bind的使用就变得非常灵活。可以直接绑定函数的所有参数也可以仅绑定部分参数。绑定部分参数时就需要通过std::placeholders来决定bind所在位置的参数将会属于调用发生时的第几个参数。std::bind的思想实际上是一种延迟计算的思想将可调用对象保存起来然后在需要的时候再调用。std::function一般要绑定一个可调用对象类成员函数不能被绑定。而std::bind更加强大成员函数、成员变量等都能绑定。 现在通过std::function和std::bind的配合所有的可调用对象都有了统一的操作方法。
应用
至此我们已经详细的讲解了function类模板与std::bind接下来具体看看他们的使用场景
模拟muduo使用bind与function模板创建线程池-CSDN博客