第6章 函数

本文详细介绍了C++中的函数,包括函数基础、参数传递、返回类型和return语句、函数重载以及特殊用途的语言特性。在参数传递中,讨论了传值参数、引用形参、const形参和数组形参的用法。函数返回类型中强调了不能返回局部对象的引用或指针,并介绍了如何返回数组指针。函数重载部分解释了如何通过形参列表区分不同的重载函数,并讨论了内联函数和constexpr函数。最后,文章还探讨了函数指针的使用和重载函数指针的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第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)*5120
factorial(4)factorial(3)*424
factorial(3)factorial(2)*36
factorial(2)factorial(1)*22
factorial(1)11

注: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作用于某个函数时,它返回函数类型而非指针类型,需要显式加上*表明我们需要返回指针,而非函数本身
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值