内联函数和constexpr函数
上一节编写的比较两个string长度的函数有很多好处:
1、阅读和理解shorterString函数的调用要比读懂等价的条件表达式容易得多
2、使用函数可以确保行为的统一,每次相关操作能保证按照同样的方式进行
3、如果需要修改计算过程,显然更加容易
4、函数可以重复利用
缺点:调用函数一般比求等价表达式的值要慢一些。在大多数机器上,一次函数调用其实包含着一系列工作:调用前要先保存寄存器,并在返回时恢复;可能需要拷贝实参;程序转向一个新的位置继续执行
内联函数可以避免函数调用的开销
通常就是将它在每个调用点上“内联地”展开,在函数的返回类型前加上关键字inline,内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求
inline const string &
shorterString(const string &s1,const string &s2)
{
return s1.size()<=s2.size()?s1:s2;
}
内联机制用于优化规模较小、流程直接、频繁调用的函数。很多编译器不支持内联递归函数
constexpr函数
constexpr函数是指能用于常量表达式的函数,遵循约定:
函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句
constexpr int new_sz(){return 42;}
constexpr int foo=new_sz();
把new_sz定义成无参数的constexpr函数,因为编译器能在程序编译时验证new_sz函数返回的是常量表达式,所以可以用new_sz函数初始化constexpr类型变量foo
执行该初始化任务时,编译器把对constexpr函数的调用替换成结果值,为了能在编译过程中随时展开,constexpr函数可以有空语句、类型别名及using声明。
我们允许constexpr返回值并非一个常量:
//如果arg时常量表达式,那么scale也是常量表达式
constexpr size_t scale(size_t cnt){return new_sz()*cnt;}
当scale的实参是常量表达式时,它的返回值也是常量表达式
int arr[scale(2)];
int i=2;
int a2[scale(i)];
constexpr函数不一定返回常量表达式
内联函数的定义应该放在头文件中。因为内联函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码,所以仅有函数的原型不够。并且,与一般的函数不同,内联函数有可能在程序中定义不止一次,此时必须保证在所有源文件中定义完全相同,把内联函数的定义放在头文件中可以确保这一点。
调试帮助
string word;
if(0<1)
{
cerr<<"Error:"<<__FILE__
<<":in function "<<__func__
<<" at line "<<__LINE__<<endl
<<" Compiled on "<<__DATE__
<<"at "<<__TIME__<<endl
<<" Word read was \""<<word
<<"\":length too short"<<endl;
}
关闭调试状态:
#include<iostream>
#include<vector>
#define NDEBUG
using namespace std;
void print(vector<int> vi,unsigned index)
{
unsigned sz=vi.size();
#ifndef NDBUG
cout<<"vector对象的大小是:"<<sz<<endl;
#endif // NDBUG
if(!vi.empty()&&index<sz){
cout<<vi[index]<<endl;
print(vi,index+1);
}
}
int main()
{
vector<int> v={1,3,5,7,9,11,13,15};
print(v,0);
return 0;
}
函数指针
函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。例如:
//比较两个string类型的长度
bool lengthCompare(const string &,const string &);
该函数的类型是bool (const string &,const string &)。要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可:
//pf指向一个函数,该函数的参数是两个const string的引用,返回值是bool类型
bool (*pf) (const string &,const string &);
pf前边有个*,因此pf是指针;右侧是形参列表,表示pf指向的是函数;再观察左侧,发现函数的返回类型是布尔值。因此,pf就是一个指向函数的指针,其中该函数的参数是两个引用
如果不加*pf两端的(),则pf是一个返回值为bool指针的函数
使用函数指针
当我们把函数名作为一个值使用时,该函数自动地转换成指针。例如按照如下形式我们可以将lenghCompare的地址赋给pf:
pf=lengthCompare; //pf指向名为lengthCompare的函数
pf=&lengthCompare;//等价的赋值语句
此外还能直接使用指向函数的指针调用该函数,无需提前解引用指针:
bool b1=pf("hello","goodbye"); //调用lengthCompare函数
bool b2=(*pf) ("hello","goodbye"); //等价
bool b3=lengthCompare("hello","goodbye"); //等价
在指向不同函数类型的指针建不存在转换规则。但是和往常一样,我们可以为函数指针赋一个nullptr或者值为0的整型常量表达式,表示该指针没有指向任何一个函数:
string::size_type sumLength(const string&,const string&);
bool cstringCompare(const char*,const char*);
pf=0; //正确:pf不指向任何函数
pf=sumLength; //错误:返回类型不匹配
pf=cstringCompare; //错误:形参列表不匹配
pf=lengthCompare; //正确:函数和指针类型匹配
重载函数的指针
使用重载函数必须清晰地界定到底应该选用哪个函数。如果定义了指向重载函数的指针
void ff(int*);
void ff(unsigned int);
void (*pf1) (unsigned int)=ff;//pf指向ff(unsigned);
函数指针形参
和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时形参看起来是函数类型,实际上是当指针使用:
void useBigger(const string &s1,const string &s2,
bool pf(const string &,const string &));
//等价
void useBigger(const string &s1,const string &s2,
bool (*pf) (const string &,const string &));
可以把函数作为实参使用,此时会自动转换成指针
//自动将函数lengthCompare转换成指向该函数的指针
useBigger(s1,s2,lengthCompare);
类型别名和decltype能让我们简化使用了函数指针的代码
decltype的结果是函数类型
//函数类型
typedef bool Func(const string &,const string &);
typedef decltype(lengthCompare) Func2;
//指向函数的指针
typedef bool (*FuncP) (const string&,const string&);
typedef decltype(lengthCompare) *FuncP2;
重新声明useBigger:
void useBigger(const string &s1,const string &s2,Func);
//等价
void useBigger(const string &s1,const string &s2,FuncP2);
返回指向函数的指针
能返回指向函数类型的指针。必须把返回类型写成指针形式,编译器不会自动的将函数返回类型当成对应的指针处理,声明一个返回函数指针的函数
using F=int(int*,int); //F是函数类型,不是指针
using PF=int (*)(int*,int); //PF是指针类型:指向函数类型的指针
返回类型不会自动地转换成指针。必须显式地将返回类型指定为指针:
PF f1(int);//正确:PF是指向函数的指针,f1返回指向函数的指针
F f1(int);//错误:F是函数类型,f1不能返回一个函数
F *f1(int); //正确:显式地指定返回类型是指向函数的指针
int (*f1(int)) (int*,int);
使用尾置返回类型的方式声明一个返回函数指针的函数
auto f1(int)->int (*)(int*,int);
将auto和decltype用于函数指针类型
如果明确知道返回的函数是哪一个,能使用decltype简化书写函数返回指针返回类型的过程,假定有两个函数返回类型都是string::size_type,并且各有两个const string&类型的形参,此时编写第三个函数,它接受一个string类型的参数,返回一个指针
string::size_type sumLength(const string&,const string&);
string::size_type largerLength(const string&,const string&);
//根据其形参的取值,getFcn函数返回指向其中一个函数的指针
decltype(sumLength) *getFcn(const string&);
练习
6.54:编写函数的声明,令其接受两个int形参并且返回类型也是int;然后声明一个vector对象,令其元素是指向该函数的指针。
int func(int,int);
vector<decltype(func) *> vf;
6.55:
编写4个函数分别进行加减乘除,在vector保存这些指针,输出结果
#include<iostream>
#include<vector>
using namespace std;
int func1(int a, int b) {
return a + b;
}
int func2(int a, int b) {
return a - b;
}
int func3(int a, int b) {
return a * b;
}
int func4(int a, int b) {
return a / b;
}
void compute(int a,int b,int (*p)(int,int))
{
cout<<p(a,b)<<endl;
}
int main()
{
int i=5,j=10;
decltype(func1) *p1=func1,*p2 = func2, *p3 = func3, *p4 = func4;
vector<decltype(func1) *>vf={p1,p2,p3,p4};
for(auto p:vf)
{
// 遍历 vector 中的每个元素,依次调用四则运算函数
compute(i,j,p);
}
return 0;
}