C++ Primer学习 《函数-Functions》

本文深入探讨函数的概念,包括参数传递方式、返回值类型、重载原则及匹配规则,并讲解了函数指针的应用。

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

函数-Functions

说到函数,很多人觉得很简单,但如果问你重载函数的判别原理,函数返回指针的注意事项,指针函数的定义等等,很多人就头大了。函数是编程的基础,这块一定要打扎实。

函数基本知识

实参(Arguments)。形参(Parameters)。

形参初始化的顺序

尽管我们知道哪个实参初始化了哪个形参,但我们并不知道这个初始化的顺序!编译器可以以任意顺序初始化各个形参!因此,实参中不用包括改变自身的运算(如:++)。

初始化时的转换

如果我们定义了一个函数

int fact(int);
那么,在调用该函数时,实参一定要能转变成形参的type。

fact("hello");//error,string不能转成int
fact(3.14);//ok,float可以转成int

形参列表

在函数的最外层定义的变量,不能和形参重复。即:

int fact(int a)
{
    int a;//error!
    {
        int a;//ok!
    }
}

因为要使用的形参必须是有名字的,所以形参一般都会有名字。然而,如果我们在不断更新代码后,有的形参不再使用,那么我们将该形参设为没有名字的,以示区分。注意的是,即便这个形参没有名字,我们还是要通过实参给其赋值!

函数返回值

一个函数的返回值不能是数组或是函数,但是我们可以返回一个指针指向数组,或是指针指向函数。


局部变量

局部变量主要有两种,一种是automatic objects,另一种是local static objects。

automatic objects

automatic objects在函数调用时生成,在函数结束时便会消亡(每一次重新调用该函数,这个object的值不会被保留)。所有传递来的参数都是automatic objects。

local static objects

而local static objects则不同,它在函数第一次执行时,还未执行到定义该object的语句之前就被初始化,它的值在每一次调用函数时都会被保留。其定义形式为:

static type name;//ex: static int b;可以只声明不初始化,也可以直接初始化


参数传递

参数传递有两种:值传递(passing argument by value)和引用传递(passing argument by reference)。

如果函数定义时,形参是一个reference,那么参数传递便是引用传递;否则,是值传递。

简单地说,引用传递下,函数内对该参数的改变,在函数返回时依然有效,因为函数内改变的是一个对原参数的引用,它和原参数指向同一个内存地址;而值传递下,函数内对参数的改变,在函数返回后不会影响原参数,因为值传递是copy了一个一模一样的备份,然后对该备份进行运算,这个备份和原来的参数的内存地址是不同的。

必须使用值传递的情况

一些类(包括IO 类),不能被复制(cannot be copied)。这种情况下,函数必须使用引用传递!例如:

void ioNotBeCopied(ifstream fin);
void ioNotBeCopied2(ifstream &fin);

void main()
{
    ifstream fin;
    ioNotBeCopied(fin);//error!
    ioNotBeCopied2(fin);//ok!
}
事实上IO类的三个头文件:iostream,fstream,sstream定义的类都是不能复制的,不能直接作为函数参数传递,也不能存储在vector中。




Const 参数

top-level const

当我们初始化一个形参时,top-level const会被忽略(回顾top-level const:object本身不能被改变)。

对于一个top-level const的形参,我们可以用nonconst或top-level const object来初始化它。

对于一个nonconst形参,我们也可以用nonconst或top-level const object来初始化它。

注意:这里的传递都是值传递,而非引用传递。

Pointer or Reference Parameters and const

回顾 low-level const:object指向或引用的对象不能被改变。

在pointer和reference中涉及const时,记住两个原则:

1.我们能用nonconst初始化一个low-level const,但反之不行。

2.对于plain reference(即nonconst reference,别忘了reference只存在low-level const,不存在top-level const),必须使用同样的类型(nonconst)来初始化。(这也解释了上一部分top-level const的原则只适用于值传递)。

一些例子:

void reset(int &a);
void creset(const int &a);

int c =0;
const int cc = 0;
reset(&c);//error!非常量引用的初始值必须为左值
creset(&cc);//error! const int*和const int&不兼容

reset(42);//error! 不能用plain reference绑定literal
creset(42);//ok!
这里对“非常量引用的初始值必须为左值”做一点说明,这句话的意思是int &a是一个非常量引用,所以它的初始值一定要是一个左值(lvalue),毕竟nonconst reference只能绑定object。而&c其实是c 的地址,是一个右值(rvalue),你无法对&c再次取地址。因此出现了error。

尽可能使用const reference(Use Reference to Const When Possible)

这样做有两个好处:

1.明确告诉使用函数的人,哪些变量可能会被改变,哪些不会。

2.plain reference比const reference有更多的限制,如不能绑定数字(literal),不能绑定一个const object。




数组参数(Array Parameters)

我们不能将数组copy给一个函数(数组没有拷贝构造函数),当我们使用数组时,它经常会被转换成指针。如果我们向函数传递一个数组,实际上我们传递的是指向数组第一个元素的指针。

下面的三个声明都是完全一样的:

void print (const int*);
void print (const int[]);
void print (const int[10]);
而当编译器检查时,它只会检查实参是否是一个指针:

int i=0;
print(&i);//ok!

确保数组传递的正确性

正是因为编译器只会检查是否是指针,因此传递数组很容易出错,例如我们希望得到一个int[10],而事实上传递的是一个int[5],那么在遍历数组时就会越界!

C++ Primer提供了三种方法,来避免这种错误。

1.用一个标记(marker)来表示数组的结束

在C-style string中,我们知道string的结尾是一个'\0‘,当函数读到'\0'时,我们就知道数组结束了。这种方法对于有明显的结尾标记(end marker)的情况很实用。但是对于int类型往往就不那么好用了,因为任何值都是有可能的。

2.传递头元素(first element)和结尾元素(one past the last element)

借助begin()和end()函数,我们可以轻松地获得数组的起始和截止指针,将这两个指针传递到函数内,则遍历就不会出错。例如:

void print(const int*beg,const int *end)
{
    while(beg!=end)
        cout<< *beg++ <<endl;
}

int j[2] = {0,1};
print(begin(j),end(j));
3.传递数组同时传递数组大小

这个思想非常容易想到。值得注意的是,数组的大小一般可以用size_t来表示。

void print(const int ia[],size_t size);

Array Parameters and const

和之前提到的reference尽量声明成const一样,array和pointer都应该尽量声明成const,除非需要改变其中的值。

Array Reference(对array的引用)

我们可以定义一个reference,其指向一个array,作为参数。这样做的好处是,我们可以用range for轻松地遍历数组:

void print(int (&arr)[10])
{
    for(auto elem:arr)
        cout<<elem<<endl;
}
注意:这里的(&arr)两侧的括号是很有必要的!如果没有括号,则是一个由10个reference构成的array。

但是,这样做的缺点是,我们在初始化形参时,必须明确传递一个int[10]:

int j[2] = {0,1};
int k[10];
print(j);//error!
print(k);//ok!
事实上,我们也有办法传递任意大小的数组,这将在很以后学到。

Passing a Multidimensional Array

传递多维数组(本质是有数组构成的数组)时,第二维即以后的数组大小必须被明确定义。

void print(int (*matrix) [10]);//()不能省略!
void print(matrix[][10]);//equivalent defination




命令行参数处理

我们知道,main函数其实可以接受命令行的参数,基本形式如下:

int main(int argc,char *argv[]);
int main(int argc,char **argv);//equivalent
argc表示包括函数名和参数在内的总个数,argv则是包括了函数名和参数在内的具体内容,最后以0结尾。举例说明:

program -d
argc = 2;
argv[0]="program";
argv[1] = "-d";
argv[2] = 0;





不知道参数个数的函数(Functions with Varying Parameters)

如果我们事先不确定具体的函数参数个数,那么有两种主要方法来解决。

1.如果参数的类型都相同,那么可以使用initializer_list类来完成。

2.如果参数的类型也不同,那么我们用一种叫做variadic模板的特殊函数来完成(将在很以后介绍)。

另外,有一种参数类型叫做ellipsis(省略)也能完成这一功能,但只有当我们的程序需要和C语言兼容时我们才应该使用它,即在C++中不推荐使用ellipsis参数!

initializer_list参数

C++ 11 新引进一个类:initializer_list类,它用来表示特定类型的数组。
一些操作:
initializer_list<T> lst;//empty list of elements of type T
initializer_list<T> lst{a,b,c...};//elements are copies of the corresponding initializer!!!elements are const!!!
lst2(lst);//copy
lst2 = lst;//assign. Warning:copy or assign an initializer_list does not copy the elements in the list! The original and the copy share the elements!
lst.size();
lst.begin();
lst.end();
注意的是,initializer_list内的元素都是const,无法改变。

我们可以用{}包括起来的内容,直接当做实参传递给一个initiazer_list。
void error_msg(initializer_list<string>il);
//expected,actual are strings
error_msg({"functionX",expected,actual});//ok!

其实,我没有完全想明白initializer_list和vector的差别。initializer_list全是const,这一点vector可以很容易做到。唯一的差别是,initializer_list的copy和assign都是相当于别名(alias),这样的好处是什么呢?疑惑。

我想,可能的解释是:initializer_list的构造只能通过{},这就限制了它的使用范围。正如它的名字的意思,这就是用来初始化一个函数的特殊类,用vector也能实现,但是用initializer_list实现感觉分工更明确。

ellipsis parameters

使用"..."来表示省略的参数:
void foo(parm_list, ...);
具体如何使用我并不清楚,大家可以自己上网学习,但如果是C++程序员,ellipsis parameter不那么重要。




Function Return Types

Never Return a Reference or Pointer to a Local Object

当一个函数运行结束,其占用的内存空间都会被释放。因此,如果你的指针是一个在函数内创建的指针,那么该指针指向的空间将在函数结束时被释放,因此返回后,你将无法获得这些值;同样的,如果你的函数返回类型是一个引用,而这个引用指向的是函数内部创建的object,那么函数结束后,你也将无法获得该值。

const string& manip()
{
    string ret;
    if(!ret.empty())
        return ret;//WRONG!ret是一个local object
    else
        return "EMPTY";//WRONG!"EMPTY"也是一个local object。当然,如果函数返回的是const string,而非const string&,那么这个是可以的
}

Reference Returns Are Lvalues

函数的返回类型,只有当是reference时是lvalue,其它情况都是rvalue。

如果函数的返回参数是一个reference(当然该reference不指向local object),那么我们可以把这个返回参数当做一般的lvalue使用。

char& getValue(string &str,int ix)
{
    return str[ix];
}
int main()
{
    string s("a value");
    get_val(s,0) = 'A';
}

List Initializing the Return Value(使用list initializer来定义返回变量)

这个很好理解,当我们的return type是vector等类型时,我们可以使用list initializer的形式定义返回变量。

vector<int> process()
{
    return {1,2,3,4};
}
注意的是:如果返回的是一个built-int type(如int,float等),那么{}中只能有一个值,且这个值的类型和返回类型必须完全一样,不能有任何转换。而如果返回一个类,那么{}内的内容由该类决定。




返回数组的指针(Return a Pointer to an Array)

因为数组不能被复制,因此我们无法直接返回一个数组。然而,我们可以返回一个指向数组的指针。

要返回这样的一个指针,有四种办法:

1.用type alias

typedef int arrT[10];
using arrT = int[10];//equivalent
arrT* func();//func()返回一个指向int[10]的指针

2.直接定义

基本格式是:

Type (*function(parameter_list))[dimension]
一个例子:

int (*func())[10];//()不能省略!和上面的func等价

3.使用Trailing Return Type(拖尾返回类型)

C++ 11新特性,trailing return type可以定义任何函数,但是在函数的返回类型很复杂时尤其有用。

基本格式是:

auto function(parameter_list) -> return_type
一个例子:

auto func() -> int(*)[10];//和上面的func等价

4.使用decltype

记住,decltype不会把一个array转成对应的pointer类型。
int odd[] = {1,3,5,7,9};
decltype(odd) *func();//和上面的func不同,返回一个int(*)[5]。decltype(odd)得到的是一个int[5]


返回数组的引用(Return a Reference to an Array)

和上一节完全一样,四种方法可以分别使用到数组的引用上。




函数重载

重载函数必须在参数的个数,或者类型上有所区别!如果两个函数仅仅是返回类型不同,那么重载这样两个函数将是错误的。

Overloading and const Parameters

由于top-level const对object的传递不起作用,因此,如果两个函数仅仅是top-level const的差别,那么编译器无法区分两个函数,也就是说这样两个函数不能重载。
void lookup(phone);
void lookup(const phone);//error!redeclaration of lookup(phone)

void lookup(phone*);
void lookup(phone* const);//error!redeclaration of lookup(phone*);
另一方面,如果两个函数是low-level const的差别,那么就可以重载。
void lookup(account&);
void lookup(const account&);//new function

void lookup(account*);
void lookup(const account*);//new function

const_cast and Overloading

有时候,我们会有这样的情况:对于一个函数,如果我们给它nonconst参数,那么希望得到nonconst返回;而如果我们给它const参数,那么希望得到const返回。或者其他情况,总之,两个重载函数,根据参数的差别,一个返回const,一个返回nonconst。这时候,当然我们可以写两个差不多的函数,实现功能。但如果两个函数高度类似,我们可以使用const_cast来简化我们的代码。
假设我们已经拥有以下函数:
const string& shorterString(const string &s1,const string &s2)
{
    return s1.size()<=s2.size()?s1:s2;
}
这时,如果我们向函数传递两个nonconst string,那么得到的结果依旧是const string。现在,我们希望在这种情况下,能得到一个nonconst string,除了重新写一个新的函数外,我们可以这么做:
string& shorterString(string&s1,string&s2)
{
    auto &r = shorterString(const_cast<const string&>(s1),
                            const_cast<const string&>(s2));
    return const_cast<string&>(r);
}
这里auto &r中的&的作用是保证返回的内容就是s1或s2中的一个,而不是重新创造的string。
在这个函数中,我们并没有重新编写一个新的shorterString,而是借助现有的函数,通过const_cast转换,实现了功能。
应该注意的是,在这里,我们知道用const_cast去掉const是安全的,因为我们知道原来的string并非const string。这也是为什么const_cast在重载函数中很有用,但是在一般情况下作用并不大的原因,因为一般来说,去掉const将会导致很危险的结果!

Calling an Overloaded Function

当我们调用一个重载函数时,我们应该明白三点:
1.编译器会找到一个最佳匹配的重载函数(a best match)。
2.如果没有重载函数能匹配调用的参数,那么将无法匹配(no match)并报错。
3.如果有多于一个重载函数能匹配,而且编译器无法决定哪个是最佳的,这种情况被称作模糊调用(ambiguous call)并报错。
具体编译器如何决定哪个函数是最优的,在后面小节中将讲到。

Overloading and Scope

一般来说,在函数内部声明一个函数是非常不好的做法(还真从来没见过有人这么做...),但是为了说明函数重载和scope的关系,我们将打破这一习惯。
我们看一个例子来了解一下:
string read();
void print(const string &);
void print(double);
void foo(int ival)
{
    string s = read();//ok!
    bool read = false;//hides the outer declaration of read
    string s = read();//error!read is a bool variable,not a function
    
    void print(int);//hides previous instances of print
    print("Value: ");//error!
    print(ival);//ok!print(int) is visible
    print(3.14);//ok!calls print(int);print(double) is hidden
}
这个例子很全面地告诉我们,函数内部重新定义的内容(新的函数或是新的变量),将覆盖函数外的内容。重新定义之后的代码里,只能对新定义的函数或变量操作。






缺省参数(Default Arguments)

我们可以将函数中的若干个参数定义为缺省参数,但是如果我们定义了一个参数为缺省参数,那么其后的所有参数都必须具有缺省值。

Default Argument Declaration

一般来说,一个函数的声明应该处处是一样的。下面的说明,只是表明C++的规则,强烈不建议任何人这么做。
对于有default arguments的函数,在不同地方声明时,可以有所不同。关键在于,同一个缺省参数不能有不同的缺省值,而我们可以在另一个函数声明中追加一些之前没确定的缺省值。例如:
<pre name="code" class="cpp">string screen(int,int,char=' ');//ok
string screen (int,int,char='*');//error! redeclaration
string screen (int = 24,int =80,char);//ok! adds default arguments,现在两个screen的缺省值是一样的!都是int=24,int=80,char=' '
string screen(int =24,int=80,char=' ');//error! 最后一个char不能再次定义!

注意:我在VS2013测试时,输入如下:
string screen(int, int=20);
string screen(int = 10, int);
在编辑器内,第二行的screen下面会有红色波浪线,显示“错误:默认实参不在形参列表的结尾”,但是编译过程没有报错,也可以正常运行。诡异。。

Default Argument Initializers

当一个函数具有缺省参数时,这些缺省参数并不需要是常量!事实上,我们可以改变这些缺省参数,以改变函数本身!
对缺省参数的唯一限制就是:它们不能是local variable,必须是全局变量。
而正因为是全局变量,所以我们能改变它们,以改变函数。
int a=1;
int b();
string screen(int =a,int =b());//现在的默认调用是screen(1,b());

void func()
{
    a=2;
    screen();//调用screen(2,b());
}







Inline和constexpr函数

inline Functions

之前我们定义过这个函数:
const string& shorterString(const string &s1,const string &s2)
{
    return s1.size()<=s2.size()?s1:s2;
}
定义这种短小的函数有如下四个好处:
1.比起复杂的比较来说更直观易懂。
2.统一的函数行为,不同函数内调用都得到一样的结果。
3.易于修改。
4.方便其他函数调用。
然而,这样做也有一个显而易见的缺点:函数调用比起仅仅是比较来说,要慢非常多!因此对于追求高效率的代码,需要做一些调整。

inline Functions Avoid Function Call Overhead

如果我们将一个函数定义为inline函数,就能避免无谓的函数调用。事实上,编译器在编译时,会将inline函数的内容完全替代调用这个函数的地方,这样我们的代码简洁高效,同时运行速度也没有受到任何影响。
inline函数定义很简单:
inline const string& shorterString(const string &s1,const string &s2)
{
    return s1.size()<=s2.size()?s1:s2;
}
应该注意的是:inline函数只应该用来定义那些简短小巧的代码。inline只是一个request(申请),编译器可以选择忽略这个申请。大部分编译器对递归函数的inline将忽略,对于超过75行的函数将几乎肯定被忽略。

constexpr Functions

这是C++ 11的新特性。一个constexpr函数可以被用在常量表达式中(constant expression)。constexpr函数的返回值和所有参数,都必须是literal或const!!而且函数只允许有一个return语句。
由于constexpr代表的一定是一个常值,因此为了尽快展开这个函数已得到结果,constexpr函数默认都是inline的。
一个constexpr也可以包含其他语句,只要这些语句在运行过程中不产生行为(generate no actions at run time),例如:空语句,type aliases和using语句。
一个constexpr并不一定要返回const类型变量,例如:
constexpr int scale(int cnt){return 3*cnt;}//只要cnt是一个const,那么scale返回的便是一个常量
再看个列子:
int arr[scale(2)];//ok!scale(2)是常表达式
int i=2;             //i is not a constant expression
int a2[scale(i)];//error!
最后要说的是!constexpr目前还没有被Visual Studio所接受!晕,具体原因涉及到编译器的实现细节,和类模板似乎有一定关系。我也不是很懂,大家可以参考这篇文章:
http://blog.youkuaiyun.com/hikaliv/article/details/4484789

Put inline and constexpr Functions in Header Files

和其他函数不同,inline和constexpr函数可以被多次定义(be defined multiple times)。所有对inline和constexpr的定义都必须完全一样(all of the definitions of a given inline or constexpr must match exactly),因此我们一般将inline和constexpr定义在头文件中,而非源文件中!
我个人不是非常明白多次定义的意思。我测试下来的结果是:inline和constexpr定义在头文件中,头文件必须有#ifndef...#define...#endif,否则编译报错。而如果定义好了宏,那么在多个源文件中多次引用该头文件确实是不会报错的。






Aids for Debugging

我们在开发代码的过程,需要一些特殊的工具来帮助我们debug,而一旦开发完成,我们希望直接输出一个去掉这些debug代码的release版本。我们通过assert和NDEBUG两个宏来实现这个功能。

the assert Preprocessor Macro

assert定义在cassert头文件中。基本形式是:
assert(expression);
如果语句运算的结果为真,那么assert不做任何动作;如果结果为假,那么将中断程序,在终端上会显示debug结果(在哪个具体位置遇到了assert失败的情况)。

the NDEBUG Preprocessor Variable

NDEBUG,顾名思义就是NO DEBUG!
如果NDEBUG被定义了,那么assert就不会执行;否则assert会被执行。默认情况下,NDEBUG是没有被定义的。
定义NDEBUG的方法并不是定义一个叫NDEBUG的变量,我们需要定义一个叫NDEBUG的预处理变量(Preprocessor Variable)!
一种方法是使用命令行定义:
$ CC -D NDEBUG main.C  #linux
$ CC /D NDEBUG main.C  #microsoft
配合NDEBUG,我们还可以实现比assert更复杂的debug功能。例如:
void print()
{
#ifndef NDEBUG
cerr<<__FILE__<<endl;
#endif
}
即通过宏定义来控制cerr<<__FILE__<<endl这段代码是否执行。
这里我们用了__FILE__这个编译器预先定义好的变量,还有一些其他类似变量。
__FILE__:string, name of file
__LINE__:int, current line number
__TIME__:string,time the file was compiled
__DATE__:string,date the file was compiled







Function Matching

前面我们讲到对于重载函数,编译器会寻找最佳匹配的重载函数来执行,那么如何做呢?这一节我们会讲明白。
考虑以下四个函数,和我们的调用。
void f();
void f(int);
void f(int,int);
void f(double,double=3.14);
f(5.6);//calls void f(double,double)

Candidate and Viable Functions

我们把只要名称一样的所有重载函数,都看做候选函数(Candidate Functions)。这里有四个candidate functions
我们把候选函数中,能够被提供的实参调用的函数叫做可行函数(Viable Functions)。这里有两个viable functions

寻找最佳匹配函数

对于最佳匹配,总的思想是:argument和parameter的type越接近,那么匹配越好。
在上一个例子中,如果f(5.6)调用f(int),那么需要进行double到int的转换,然而如果调用f(double,double = 3.14),那么无需进行任何转换,这是一个精准匹配(exact match)!

更准确的最佳匹配的定义是,存在一个且仅存在一个最佳匹配函数,使得:
1.调用这个函数的话,任何实参都不比调用其它可行函数来的差。(好和差的定义将在后面给出)
2.至少有一个实参调用,比其它可行函数来得好。

对于上面这个例子,如果我们调用f(42,2.56),会发型有两个viable functions,分别是f(int,int)和f(double,double=3.14),然而无论调用哪一个,都需要转换一个参数。因此,不存在最优的匹配函数,这种情况下,编译器就会报错!

Argument Type Conversions

为了判断匹配的好坏,编译器对各种类型转换进行了排序。
1.精准匹配:
形参和实参类型完全一样(identical)
形参从数组转变成指针
top-level const从实参添加或去除
2.经过const转换
3.经过整形提升(integral promotion),从比int小的整形编程int。
4.经过算数转换(如浮点数和整数的转换,或是char到short,int到unsigned int)或指针转换(void*和其他指针的转换)
5.经过类转换(以后会讲到)

Matches Requiring Promotion or Arithmetic Conversion

记住:一个良好的代码很少包含类型非常近的参数的重载函数!

在整形promotion中,整形会更倾向于适应int类型。
void ff(int);
void ff(long);
void ff(short);
void ff(long long);
ff('a');//调用ff(int)
另一方面,如果没有int类型,可能就无法判断。
void ff(long);
void ff(short);
void ff(long long);
ff('a');//报错!无法判断

所有的算数转换都是一样的!从int到unsigned int不比从int到double来的优先级更高。另外,literal中,整数一般默认是int,浮点数默认是double。
void manip(int);
void manip(float);
manip(3.14);//error!无法判断
一个特殊的例子:
void ff(int);
void ff(long long);
ff(999999999999999);//调用ff(long long)

Function Matching and Const Arguments

如果两个重载函数差别在于low-level const,那么编译器会倾向调用const类型匹配的那个函数。
void lookup(account&);
void lookup(const account&);
const account a;
account b;
lookup(a);//调用lookup(const account&)
lookup(b);//调用lookup(account&);



函数指针(Pointers to Functions)

Define a Function Pointer

一个函数的类型由函数的返回值和其所有参数类型共同决定!
bool lengthCompare(const string&,const string&);//该函数的类型是bool(const string&,const string&)
bool (*pf)(const string&,const string&);//定义的函数指针
注意定义函数指针的()是非常必要的!否则你就定义了一个函数!

Using a Function Pointer

我们可以直接将函数的地址赋给函数指针,具体的:
pf = lengthCompare;
pf = &lengthCompare;//equivalent!
我们也可以直接用函数指针来调用该函数,具体的:
bool b1 = pf("hello","goodbye");
bool b2 = (*pf)("hello","goodbye");//equivalent!
bool b3 = lengthCompare("hello","goodbye");//equivalent!
任意两个不同的function type之间不存在转换方法(废话么。。。),当然我们可以对任何function pointer赋值nullptr或0,来表示他们没有指向任何函数。

Function Pointer Parameter

和数组一样,函数的参数不能是一个函数,但却可以是一个指向函数的指针。
void f(int,bool pf(int));//ok!第二个参数被认为是函数指针
void f(int,bool (*pf)(int));//equivalent!显式表明第二个参数是函数指针
使用type aliases和decltype能帮我们简化代码。
bool lengthCompare(int);

//Func,Func2是函数类型
typedef bool Func(int);
type decltype(lengthCompare) Func2;//equivalent

//FuncP,FuncP2是函数指针类型
type bool (*FuncP)(int);
type decltype(lengthCompare) *FuncP2;//equivalent
void f(int,Func);//这四个定义和之前的f()完全一样
void f(int,Func2);
void f(int,FuncP);
void f(int,FuncP2);

Returning a Pointer to Function

和数组一样,函数的返回类型不能是一个函数,但可以是一个函数指针。
using F = int(int*,int);//F是函数类型,不是指针
using PF = int(*)(int*,int);//PF函数指针类型
记住和参数列表中不同,参数列表中一个函数类型会被自动转换成函数指针类型,但是返回类型却不会!我们必须使用一个显式的函数指针类型来定义函数的返回类型。
PF f(int);//ok
F f(int);//error!
F* f(int);//ok!
当然,我们也可以显式地定义这样的函数。
int (*f(int)) (int*,int);
另外,也可以用拖尾返回类型(trailing return)来定义。
auto f(int) ->int(*)(int*,int);

Using auto or decltype for Function Pointer Types

最后,如果我们已知一个函数,然后想使用这个函数的类型作为函数指针,那么可以使用decltype或auto来帮助完成。
使用decltype:
int sumLength(string,string);
decltype(sumLength) *f(int);
需要注意的是,decltype返回的是函数类型,所以我们需要在函数名前加一个*来表示返回函数指针,上面例子中如果缺少了*,则会报错。
或者使用auto和decltype,不过似乎有些多此一举:
auto f(int) -> decltype(sumLength)*;
同样,这里最后的*也不能省略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值