第6章 函数
6.1 函数基础
一个典型的函数定义包括以下部分:返回类型、函数名字、由0个或多个形参组成的列表以及函数体。
通过调用运算符来执行函数。调用运算符的形式是一对圆括号,它作用于一个表达式,该表达式是函数或者指向函数的指针;圆括号之内是一个用逗号隔开的实参列表,用它来初始化函数的形参。调用表达式的类型就是函数的返回类型。
6.1.1 局部对象
C++语言中,名字有作用域,对象有生命周期。
名字的作用域是程序文本的一部分,名字在其中可见。
对象的生命周期是程序执行过程中该对象存在的一段时间。
自动对象
对于普通局部变量对应的对象来说,当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块末尾时销毁它。我们把只存在于块执行期间的对象称为自动对象。当块的执行结束后,块中创建的自动对象的值就变成未定义的了。形参就是一种自动对象。
对于局部变量对应的自动对象来说,分为两种情况:若变量定义本身含有初始值,就用这个初始值进行初始化;否则,若本身不含初始值,执行默认初始化,意味着内置类型的未初始化局部变量将产生未定义的值。
局部静态对象
某些时候,有必要令局部变量的生命周期贯穿函数调用及之后的时间,可以将局部变量定义成static类型从而获得这样的对象。局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。若局部静态变量没有显式的初始值,它将执行值初始化,内置类型的局部静态变量初始化为0;
6.2 参数传递
6.2.1 传值参数
形参类型 | 对实参的影响 |
---|---|
非引用类型的形参 | 实参的值被拷贝给形参,对形参变量的改动不会影响实参的值。 |
指针类型的形参 | 和非引用类型的形参一样,实参指针的值被拷贝给形参,指向的对象相同,所以通过指针的解引用操作修改所指对象的值,即会影响实参的值。 |
引用类型的形参 | 引用类型的形参绑定在实参对象上,是实参对象的一个别名,修改形参的值就是修改实参的值。 |
使用引用避免拷贝
拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型在内)根本就不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。
例如,编写一个函数比较两个string对象的长度,因为string对象可能会非常长,所以应该避免直接拷贝它们,这时使用引用形参比较明智。有因为无须改变string对象的内容,所以把形参定义成对常量的引用:
bool isShorter(const string &s1,const string &s2){
return s1.size()<s2.size();
}
当函数无须修改引用形参的值时最好使用常量引用。
使用引用形参返回额外信息
一个函数只能返回一个值,然而有时需要同时返回多个值,引用参数为我们一次返回多个结果提供了有效途径。例如,定义一个名为find_char的函数,它返回在string对象中某个指定字符第一次出现的位置,同时希望函数返回该字符出现的总次数。
如何定义函数使它既能返回位置也能返回出现次数呢?一种方法是定义一个新的数据类型,让它包含位置和数量两个成员;还有一种是,我们可以给函数传入一个额外的引用实参,令其保存字符出现的次数:
//返回s中字符c第一次出现的位置索引
//引用形参occurs负责统计c出现的总次数
string::size_type find_char(const string &s,char c,string::size_type &occurs){
auto ret=s.size();//若s中没有c 返回s的长度
occurs=0;
for(decltype(ret) i=0;i!=s.size();++i){
if(s[i]==c){
if(ret==s.size()){
ret=i; //记录c第一次出现的位置
}
++occurs;
}
}
return ret; //出现次数通过occurs隐式地返回
}
int main(){
string s="hhhhheeellohhafsdfsag";
char c='h';
string::size_type str;
auto index=find_char(s,c,str);
cout<<"字母:"<<c<<"出现次数为:"<<str<<endl;
}
6.2.3 const形参和实参
当形参是const时,必须注意顶层const,顶层const作用于对象本身:
const int ci=42;
int i=ci;//正确:当拷贝ci时,忽略了它的顶层cosnt
int * const p=&i;//正确:const是顶层的 不可以给p赋值
*p=0;//正确,通过p该变对象的内容是允许的,i=0
和其他初始化过程一样,当用实参初始化形参时忽略掉顶层const。当形参有顶层const时,传给它常量对象或者非常量对象都是可以的。
void fcn(const int i){
//fcn能够读取i,但是不能向i写值
}
调用fcn函数时,既可以传入const int,也可以传入int。忽略掉形参的顶层const可能产生意想不到的结果:
void fcn(const int i){}
void fcn(int i){} //错误:重复定义了fcn(int)
因为顶层const被忽略了,所以上面的代码中传入了两个fcn函数的参数一样。
指针或引用形参与const
形参的初始化方式和变量的初始化方式是一样的。如我们可以使用非常量初始化一个底层const对象,但是反过来不行;同时一个普通的引用必须用同类型的对象初始化。
int i=42;
const int *cp=&i;//正确:cp不可以改变i
const int &r=i;//正确:r不可以改变i
const int &r2=42;//正确:可以用字面值初始化常量引用
int *p=cp;//错误:cp是const int * ,p是int *,类型不匹配
int &r3=r;//错误:r是const int &,r3是int &,类型不匹配
int &r4=42;//错误:不能用字面值初始化一个非常量引用
将同样的变量初始化规则应用到参数传递上可得:
int i=0;
const int ci=i;
string::size_type ctr=0;
reset(&i);//调用形参是int*的reset函数
reset(&ci);//错误:不能用指向const int对象的指针初始化int *
reset(i);//调用形参为int&的reset函数
reset(ci);//错误:不能把普通引用绑定在const对象ci上
reset(42);//错误:不能把普通引用绑定在字面值上
reset(ctr);//错误:类型不匹配,ctr是无符号类型
若想调用引用版本的reset函数,实参只能是int类型的对象;调用指针版本的reset函数,实参只能是int*类型的对象。
尽量使用常量引用
把函数不会改变的形参定义成(普通的)引用是一种比较常见的错误,这么做带给函数的调用者一种误导,即函数可以修改它的实参的值。此外,使用引用而非常量引用也会极大的限制函数所能接受的实参类型。就像上面所说,我们不能把const对象、字面值或者需要类型转换的对象传递给普通的引用形参。
例如,以6_15.cpp中的find_char函数将它的string类型的形参定义成常量引用。假如我们把它定义成普通的string&,那么只能将find_char函数作用于string对象。
string::size_type find_char(string &s,char c,string::size_type &occurs){
/* */
}
auto index=find_char("Hello",'o',ctr);//编译时错误,因为不能将普通string对象引用绑定在字面值上
还有一个更难察觉的问题,假如其他函数(正确地)将它们的形参定义成常量引用,那么第二个版本的find_char无法在此类函数中正常使用。如,我们希望在一个判断string对象是否是句子的函数中使用find_char:
bool is_sentence(const string &s){
//若在s的末尾有且仅有一个句号,则s是一个句子
string::size_type ctr=0;
return find_char(s,'.',ctr)==s.size()-1&&ctr==1;
}
如果find_char的第一个形参类型是string&,那么上面调用find_char的语句将在编译时发生错误。原因时s时常量引用,但find_char被不正确地定义成只能接受普通引用。
6.2.4 数组形参
如果传给函数的是一个数组,则实参自动地转换成指向数组首元素的指针,数组的大小对函数的调用没有影响。
因为数组是以指针的形式传递给函数的,所以一开始函数并不直到数组的确切尺寸,调用者应该为此提供一些额外的消息。管理指针形参有三种常用形式的技术。
使用标记指定数组长度
管理数组实参的第一种方法是要求数组本身含有一个结束标记,使用这种方法的典型示例是C风格字符串。C风格字符串存储在字符数组中,并且在最后一个字符后面跟着一个空字符(‘\0’)。函数在处理C风格字符串时停止:
void print(const char *cp){
if(cp){
while(*cp){ //指针所指的字符不是空字符
cout<<*cp++;
}
}
}
使用标准库规范
第二种方法是传递数组首元素和尾后元素的指针。
void print(const int *begin,const int *end){
while(beg!=end){
cout<<*beg++<<endl;
}
}
显式传递一个表示数组大小的形参
第三种方法是专门定义一个表示数组大小的形参。
//const int ia[]等价于const int *ia
//size表示数组的大小,将它显式地传递给函数用于控制对ia元素的访问
void print(const int *ia,size_t size){
for(size_t i=0;i!=size;++i){
cout<<ia[i]<<endl;
}
}
6.2.5 mian : 处理命令行选项
目前为止,我们定义的main函数都只有空形参列表:
int main(){ ...}
然而,有时我们确实需要给main传递实参,一种常见的情况是用户通过设置一组选项来确定函数要执行的操作。例如,假定main函数位于可执行文件prog之内,我们可以向程序传递下面的选项:
prog -d -o ofile data0
这些命令通过两个(可选的)形参传递给main函数:
int main(int argc,char *argv[]){...}
第二个形参argv是一个数组,它的元素是指向C风格字符串的指针;第一个形参argc表示数组中字符串的数量。当实参给main函数之后,argv的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参。最后一个指针之后的元素值保证为0。
以上面提供的命令行为例,argc应该等于5,argv应该包含如下的C风格字符串:
argv[0]="prog";//或者argc[0]也可以指向一个空字符串 使用argv中的实参时从argv[1]开始
argv[1]="-d";
argv[2]="-o";
argv[3]="ofile";
argv[4]="data0";
argv[5]=0;
6.2.6 含有可变形参的函数
有时我们无法提前预知应该向函数传递几个形参。例如,我们想要编写代码输出程序产生的错误信息,此时最好使用同一个函数实现该项功能,以便对所有错误的处理能够整齐划一。然而,错误信息的种类不同,所以调用错误输出函数时传递的实参也各不相同。
为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:
1、如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型。
2、如果实参的类型不同,我们可以编写一种特殊的函数,也就是所谓的可变参数模板,将在16.4节介绍。
C++还有一种特殊的形参类型即省略符,可以用它传递可变数量的实参,这种功能一般只用于与C函数交互的接口程序。
initalizer_list形参
如果函数的实参数量未知但是全部实参的类型都相同,我们可以使用initalizer_list类型的形参,它时一种标准库类型,用于表示某种特定类型的值的数组。
initializer_list也是一种模板类型,定义initializer_list对象时,必须说明列表中所含元素的类型。注:initializer_list对象中的元素永远是常量值,无法改变initializer_list对象中元素的值。
initializer_list<T> lst;//模板
initializer_list<string> ls;//initializer_list的元素类型是string
initializer_list<int> li;//initializer_list的元素类型是int
省略符形参
省略符形参是为了便于C++程序访问某些特殊的C代码而设置的,这些代码使用了名为varargs的C标准库功能。通常,省略符不应用于其他目的。
省略符形参只能出现在形参列表的最后一个位置,它的形式为以下两种:
void foo(param_list,....);
void foo(...);
第一种形式指定了foo函数的部分形参的类型,对于这些形参的实参将会执行正常的类型检测。省略符形参所对应的实参无须类型检查。
6.3 返回类型和return语句
return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方,return语句有两种形式:
return;
return expression;
6.3.2 有返回值函数
值是如何被返回的
返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
**必须注意当函数返回局部变量时的初始化规则。**如函数make_plural,给定计数值、单词和结束符之后,判断计数值是否大于1;如果是,返回单词的复数形式;如果不是,返回单词原形。
string make_plural(size_t ctr,const string &word,const string &ending){
return (ctr>1)?word+ending:word;
}
该函数的返回类型是string,意味着返回值将被拷贝到调用点。因此,该函数将返回word的副本或者一个未命名的临时string对象,该对象的内容是word和ending的和。
**若函数返回引用,则该引用是它所引对象的一个别名。**举例,shorterString函数挑出两个string形参中较短的那个并返回其引用:
const string &shorterString(const string &s1,const string &s2){
return s1.size()<=s2.size()?s1:s2;
}
函数形参和返回类型都是const string的引用,不管是调用函数还是返回结果都不会真正拷贝string对象。
不要返回局部对象的引用或指针
函数完成,它所占用的存储空间也随之被释放掉。因此,函数终止意味着局部变量的引用将指向不再有效的内存区域:
//严重错误:这个函数试图返回局部变量的引用
const string &mainp(){
string ret;
if(!ret.empty()){
return ret; //错误:返回局部变量的引用!
}else{
return "Empty";//错误:"Empty"是一个局部临时量
}
}
上面两条return语句都将返回未定义的值,也就是说,试图使用mainp函数的返回值将引发未定义的行为。第二条语句中,字符串字面值转换成一个局部临时string对象,对于mainp函数来数,该对象和ret一样是局部的。
引用返回左值
**函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值,其他类型返回右值。**可以像使用其他左值那样来使用返回引用的函数的调用,特别是,我们能为返回类型是非常量引用的函数的结果赋值:
char &get_val(string &str,string::size_type ix){
return str[ix];
}
int main(){
string s("a value");
cout<<s<<endl;
get_val(s,0)='A';//将s[0]的值改为A
cout<<s<<endl;
}
递归
如果一个函数调用了它自身,不管这种调用是直接还是间接的,都称该函数为递归函数。例如,我们可以使用递归函数重新实现求阶乘的功能:
int factorial(int val){
if(val>1){
return factorial(val-1)*val;
}
return 1;
}
在递归函数中,一定有某条路径是不包含递归调用的;否则,函数将永远递归下去,换句话说,函数将不断调用它自身直到程序栈空间耗尽为止。下面是给factorial函数传入参数5 时,函数的执行轨迹:
调用 | 返回 | 值 |
---|---|---|
factorial(5) | factorial(4)*5 | 120 |
factorial(4) | factorial(3)*4 | 24 |
factorial(3) | factorial(2)*3 | 6 |
factorial(2) | factorial(1)*2 | 2 |
factorial(1) | 1 | 1 |
注:main函数不能调用自己。
6.3.3 返回数组指针
因为数组不能被拷贝,所以函数不能返回数组。不过,函数可以返回数组的指针或引用。从语法上来说,要想定义一个返回数组的指针或引用的函数比较繁琐,可以用使用类型别名来简化操作。
typedef int arrT[10];//arrT是一个类型别名,它表示的类型是含有10个整数的数组
using arrT=int[10];//arrT的等价声明
arrT* func(int i);//funto func(int i)->int(*)[10];//func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组c返回一个指向含有10个整数的数组的指针
声明一个返回数组指针的函数
返回数组指针的函数形式如下所示:
Type (*function(parameter_list))[dimension]
//Type表示元素类型,dimension表示数组的大小,(*function(parameter_list))两端的括号必须存在
int (*function(int i))[10];
使用尾置返回类型
在C++11新标准中存在一种方法可以简化上述func声明的方法,就是使用尾置返回类型。**任何函数的定义都能使用尾置返回,但是这种形式对于返回类型比较复杂的函数最有效,**比如返回类型是数组的指针或者数组的引用。尾置返回类型跟在形参列表后面并以->符号开头。
auto func(int i)->int(*)[10];//func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
6.4 函数重载
若同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。
注:main函数不能重载;不允许两个函数除了返回类型以外的其他所有要素都相同。
Record lookup(const Acount&);
bool lookup(const Acount&);//错误:与上一个函数相比只有返回类型不同
判断两个形参的类型是否相异
//声明的是同一函数
Record lookup(const Acount &acct);
Record lookup(const Acount&);//没有给形参起名字。形参的名字仅仅起到帮助记忆的作用,并不影响形参列表的内容
重载和const形参
底层const不影响传入函数的对象。一个拥有顶层const的形参和另一个没有顶层const的形参区分开来:
Record lookup(Phone);
Record lookup(const Phone);//重复声明
Record lookup(Phone*);
Record lookup(Phone* const);//重复声明
另一方面,如果形参是某种类型的指针或者引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的:
Record lookup(Account&);//函数作用于Account的引用
Record lookup(const Account&);//新函数,作用于常量引用
Record lookup(Account*);//作用于指向Account的指针
Record lookup(const Account*)//作用于指向常量的指针
因为const对象不能转换成其他类型,所以只能把const对象(或指向cosnt的指针)传递给const形参。相反地,因为非常量可以转换成const,所以上面的4个函数都能作用于非常量对象或者指向非常量对象的指针。当我们传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数。
调用重载的函数
函数匹配是指我们把函数调用与一组重载函数中的某一个关联起来,函数匹配也叫做重载确定。
注:有多于一个函数可以匹配,但是每一个都不是明显的最佳选择,此时将发生错误,称为二义性调用。
6.4.1 重载与作用域
注:一般不将函数声明置于局部作用域内。
6.5 特殊用途语言特性
介绍三种函数相关的语言特性:默认实参、内联函数和constexpr函数,以及在程序调试过程中常用的一些功能。
6.5.1 默认实参
某些函数有这样一种形参,在函数的很多次调用中它们被赋予一个相同的值,此时,我们把这个反复出现的值称为函数的默认参数。调用含有默认实参的函数时,可以包含该实参,也可以省略该实参。
注:一旦函数的某个形参被赋予了默认值,它后面的所有形参都必须有默认值。
注:在设计含有默认实参的函数时,其中一项任务是合理设置形参的顺序,尽量让不怎么使用默认值的形参出现在前面,而让那些经常使用默认值的形参出现在后面。
默认实参声明
在给定作用域中一个形参只能被赋予一次默认实参。换句话说,函数的后续声明只能为之前那么没有默认值的形参添加默认实参,而且该形参右侧的所有形参必须都有默认值。
string screen(sz,sz,char='');//高度和宽度的形参没有默认值
string screen(sz,sz,char='*');//错误:重复声明
string screen(sz=25,sz=52,char);//正确:添加默认实参
默认实参初始化
注:局部变量不能作为默认实参。
6.5.2 内联函数和constexpr函数
调用函数一般比求等价表达式的值要慢一些。
内联函数可避免函数调用的开销
在函数的返回类型前加上关键字inline,就可以将它声明成内联函数了,内联机制一般用于优化规模较小,流程直接、频繁调用的函数。
inline const string& shorterString(const string&s1,const string&s2){
return s1.size()<=s2.size()?s1:s2;
}
调用时就将它在每个调用点上“内联地”展开:
cout<<shorterString(s1,s2)<<endl;
//在编译过程中展开类似于下面的形式
cout<<(s1.size()<=s1.size()?s1:s2)<<endl;
constexpr函数
constexpr函数是指能用于常量表达式的函数。
注:constexpr函数的的返回类型及所有形参的类型都得是字面值类型;函数体中必须有且只有一条return语句:
constexpr int new_sz(){return 42;}
constexpr int foo=new_sz();//正确:foo是一个常量表达式
执行初始化任务时,编译器把对constexpr函数的调用替换成其结果值。为了能够在编译过程中随时展开,constexpr函数将被隐式地指定为内联函数。
把内联函数和constexpr函数放在头文件内
和其他函数不一样,内联函数和constexpr函数可以在程序中多次定义。不过,对于某个给定的内联函数和constexpr函数来说,它的多个定义必须完全一致。基于这个原因,内联函数和constexpr函数通常定义在头文件中。
6.5.3 调试帮助
C++会使用头文件保护的技术,以便有选择地执行调试代码。基本思想是在程序开头定一个宏,调试时该宏有效。当应用程序完成后,屏蔽该宏。主要用到两项预处理功能:assert和NDEBUG
assert预处理宏
assert宏使用一个表达式作为它的条件:
assert(expr);//expr为0时,assert输出信息并终止程序的执行;expr为1时,assert什么也不做
assert宏定义在cassert头文件中,由预处理器管理,直接使用assert而不用std::assert。
NDEBUG预处理变量
assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。
assert应该仅用于验证那些确实不可能发生的事情。我们可以把assert当成调试程序的一种辅助手段,但是不能用它替代真正的运行时逻辑检查,也不能替代程序本身应该包含的错误检查。
#define NDEBUG
#define assert(expr) (static_cast <bool> (expr) ? void (0) : __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION))
int main(){
int ia[]={1,2,3,4,5,6,7,8,9,10};
print(ia,sizeof(ia)/sizeof(ia[0]));
assert(0);//程序中断执行
return 0;
};
//报错:6.out: /home/hrh/Code/C++ Primer/chapter_6/6.cpp:49: int main(): Assertion `0' failed.
除了用于assert外,也可以使用NDEBUG编写自己的条件调试代码。如果NDEBUG未定义,将执行#ifndef和#endif之间的代码;如果定义了NDEBUG,这些代码将被忽略掉:
#define NDEBUG
void print(const int ia[],size_t size){
#ifndef NDEBUG
cerr<<__func__<<":array size is"<<size<<endl;
#endif
#ifdef NDEBUG
cout<<"array size is"<<size<<endl;
#endif
}
int main(){
int ia[]={1,2,3,4,5,6,7,8,9,10};
print(ia,sizeof(ia)/sizeof(ia[0]));
return 0;
}
在这段代码中,若定义了NDEBUG,则输出array size is 10;若未定义,输出print array size is 10。func__输出当前调试的函数的名字,编译器为每个函数都定义了__func,它是const char的一个静态数组,用于存放函数的名字。
除了C++编译器定义的__func__之外,预处理器还定义了另外4个对于程序调试很有用的名字:
程序调试变量名 | |
---|---|
FILE | 存放文件名的字符串字面值 |
LINE | 存放当前行号的整型字面值 |
TIME | 存放文件编辑时间的字符串字面值 |
DATA | 存放文件编辑日起的字符串字面值 |
6.7 函数指针
函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型,函数的类型由它的返回类型和形参类型共同决定,与函数名无关。例如:
//该函数的类型是bool(const string &s,const string &s)
bool lengthCompare(const string &,const string &);
//要想声明一个可以指向该函数的指针,只需用指针替换函数名即可
//pf指向一个函数,该函数的参数是两个const string的引用,返回值bool类型
bool (*pf)(const string &,const string &);
//声明一个名为pf的函数,该函数返回bool*
bool *pf(const string &,const string &);
注!!!:*pf两端的括号必不可少。如果不写这对括号,则bf是一个返回值为bool指针的函数。
使用函数指针
当我们把函数名作为一个值使用时,该函数会自动地转换成指针。
//可以将lengthCompare的地址赋给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 (*pf2)(int)=ff;//错误:没有任何一个ff与该形参列表匹配
double (*pf3)(int *)=ff;//错误:ff和pf3的返回类型不匹配
函数指针形参
不能定义函数类型的形参,但是形参可以是指向函数的指针。
//第三个形参是函数类型,它会自动地转换成指向函数的指针
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 &));
返回指向函数的指针
不能返回一个函数,但是能返回指向函数类型的指针。注!!!:必须把返回类型写成指针形式,编译器不会自动将函数返回类型当成对应的指针类型处理,必须显式地将返回类型指定为指针。要想声明一个返回函数指针的函数,最简单的方法就是使用类型别名:
using F=int(int*,int);//F是函数类型
using PF=int(*)(int *,int);//PF是指针
PF f1(int);//正确:f1返回指向函数的指针
F f1(int);//错误:F是函数类型,f1不能返回一个函数
F *f1(int); //正确:显式地指定返回类型是指向函数的指针
int (*f1(int))(int *,int);//由内到外阅读
auto f1(int)->int (*)(int *,int);
将auto和decltype用于函数指针类型
若明确直到返回的函数是哪一个,就能使用decltype简化书写函数指针返回类型的过程.函数sumLength返回类型是string::size_type,并且有两个const string&类型的形参,此时可以编写另一个函数,它接受一个string类型的参数,返回一个指针,该指针返回函数sumLength:
string size_type sumLength(const string &,const string &);
decltype(sumLength) *getFcn(const string &);
//decltype作用于某个函数时,它返回函数类型而非指针类型,需要显式加上*表明我们需要返回指针,而非函数本身