(点击上方公众号,可快速关注)
相对于其他一些语言,如Haskell,C++函数相关的概念相对较多,大部分概念跟语言实现有关。实际上大部分时间没有必要区分清楚,但如果要阅读外文资料时,最好还是尽量把概念分析清楚,否则会经常遇到“鸡同鸭讲”的情况。以前看《数理逻辑》的教材,中文教材差不多了,换看英文教材时完全不是一回事,遇到的第一个困难就是中文概念在英文里的表达,而且英文概念更多,分得更清。
本篇文章主要把英文资料里常见的6个函数相关概念说明一下,对应的中文名称在括号里,后续章节按中文名称分节。
function definition(函数定义)
function prototype(函数原型)
function declaration(函数声明)
function header(函数头)
function body(函数体)
function signature(函数签名)
函数定义
简单来说,函数定义就是告诉编译器函数是怎么实现的,用来产生二进制代码,通常放到源代码中。如:
int add(int a, int b) {
return a + b;
}
上面的代码就是一典型的函数定义,编译器会将其编译为二进制代码,通常一个函数只能定义一次,即我们通常说的ODR(One Definition Rule)。在C++中,这套规则异常复杂,这里不展开。
函数头和函数体
这两个名词依附于函数定义,拿上面的例子来说:
int add(int a, int b)
就是函数头,包括函数名、函数参数和函数返回值。{ return a + b;}
是函数体,包含实际的实现代码。
这里有两点需要注意的情况:
函数头后没有
;
分号函数体并不一定都是由大括号包围的代码块
如,C++11引入的
= default
等就没有大括号。
函数原型
再考虑上面的add
函数,在C语言中通常的做法是将其分为两部分:
头文件中放:
int add(int a, int b);
源代码中放:
int add(int a, int b) {
return a + b;
}
头文件中的这部分就是函数原型,直观上看,由函数头+分号组成。但函数原型是允许省略掉参数名的:
int add(int, int);
C/C++之所以要引入这个概念,跟C/C++编译过程息息相关。整体上,C/C++编译过程分为三部分:预编译->编译->链接。函数原型主要在编译阶段使用,若有编译单元调用了该函数,编译器仅知道原型(函数名、参数个数和类型、返回值)即可,会在调用的地方打个标记,这个标记待到链接阶段由链接器处理。
函数声明
最宽泛、最容易混淆的就是函数声明这个概念。在特定情况下,函数声明作为函数定义使用,也可以作为函数原型使用。声明的目的是将名字引入到程序,这个名字可以是变量名、函数名,或者是其他类型名。拿上面的例子,头文件将add
引入到了程序,所以头文件中的函数原型同时也是函数声明。
如果我们将函数定义放在头文件中:
// xx.h
int add(int a, int b) {
return a + b;
}
我们可以看到该函数声明既给出了函数定义,同时也给出了函数原型。
函数签名
函数签名引入的主要目的是为函数重载服务:在C++中函数是允许重载的,即,我们不能通过单一的函数名区分不同的函数。函数签名由函数名,以及函数参数的个数和参数的类型决定,由编译器根据实际调用的情况选择不同的函数实现。