函数的定义、声明和调用

上一节正式地介绍了什么是函数,并简单了解了形式参数和返回值。这一节主要从语法出发来讲解如何使用函数。

函数定义

当我们像前两节所述的那样写出一个函数的时候,这个函数就可以使用了。同时,这个函数还拥有它的名字,叫做函数名。因此,这既是一个声明又是一个定义。一般地,函数定义的写法是这样的:

返回值类型 函数名(形参列表)
    复合语句

这样的语法引入了一个名字叫 函数名 的函数。它接收一些形参,这些形参会写在 形参列表 中。形参列表 是一列由逗号 , 分隔的

形参类型说明符 形参名

组成(类似不带初始化器的声明)。同时,函数的 返回值类型 也需明确给出。若没有返回值,需将其写为 void。一般地,函数所代表的那段过程由 复合语句 给出。称这段 复合语句 为函数的函数体(Function body)。

举个例子:

int max(int x1, int x2) {
    int y;
    if (x1 > x2) {
        y = x1;
    } else {
        y = x2;
    }
    return y;
}

其中,max 是函数名,是一个被引入的名字,因此这个名字遵从和变量名相同的命名规则。形参列表为 int x1, int x2,它是由逗号 , 分隔的两个形参声明 int x1int x2。这两个形参的类型都是 int,一个叫 x1,一个叫 x2。除此之外,函数的返回值类型指定为 int,写在了整个定义的最前面。

函数的函数体为从第 1 行 { 开始,第 9 行 } 结束的复合语句。这个函数所代表的的过程即执行这个复合语句。因此在使用函数的时候,从第 2 行开始从上到下依次执行这些子语句。

return 语句

函数中可以使用一种特别的语句称为 return 语句。return 语句的写法如下:

return 返回值;

执行 return 语句需要做两件事情。第一,立即结束函数的执行;第二,将 返回值 传回使用这个函数的地方(若有)。

若函数的返回值类型为 void,则无需在 return 语句中写出 返回值,此时 return 语句只用于结束函数执行。若函数的返回值类型不是 void,则必须写出 返回值,否则编译错误。

return 语句不一定只能出现一次。但一次函数执行过程中最多执行一次 return 语句。请看下例:

int max(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

这个函数的定义与之前的定义是完全等价的。同样,当 a > b 时,只会执行第 3 行的 return 语句,此时返回值为 a 的值;反之只会执行第 5 行的 return 语句,此时返回值为 b 的值。

函数调用运算符

使用一个函数需要通过表达式来使用。使用函数的表达式被称为函数调用表达式,这种表达式的运算符为函数调用运算符

运算符名称作用
f(a)函数调用运算符以实际参数 a 调用函数 f

正如上表所述,函数表达式写作

函数名(实参列表)

其中,实参列表 是以逗号 , 分隔的一系列值。这些值被称为实际参数(Argument),简称实参。比如函数调用表达式

max(42, 56);

中,4256 就是两个实参。实参列表中实参的个数和类型需要与 函数名 这个函数的形参列表中的形参相吻合。(这里吻合的意思是相同或者可以发生隐式转换。)比如刚刚定义的 max 函数需要两个 int 类型的形参,这里提供了两个 int 类型的值作为实参,这就是吻合的。

在执行表达式运算的时候,会执行以下步骤:

  1. 将实参“代入” 函数名 这个函数的形参;
  2. 随后执行 函数名 这个函数的函数体;
  3. 执行过程中若遇到 return 语句,则结束函数执行,将返回值作为整个调用表达式的值

还是用最初的例子

int max(int x1, int x2) {
    int y;
    if (x1 > x2) {
        y = x1;
    } else {
        y = x2;
    }
    return y;
}

,则运算 max(42, 56) 这个函数调用表达式的时候,

  1. 用实参代入形参:变量 x1 的值初始化为 42,变量 x2 的值初始化为 56
  2. 执行函数体:运算得到变量 y 的值为 56
  3. 执行 return y:将 56 作为函数返回值,函数执行结束,得到函数调用表达式的值为 56

再比如下面的赋值语句

a = max(42, 56);

中,赋值运算符的右侧操作数是一个函数调用表达式,而函数调用表达式的值为 56,这就相当于将 56 赋给了变量 a

像这样,通过函数调用表达式来使用一个函数的方法称为函数的调用(Call)。

函数的声明

看看这段代码。

#include <iostream>
using namespace std;
int main() {
    printNum(42);
}
void printNum(int x) {
    cout << x << endl;
}

我稍稍挪动了一下 printNum 函数定义的位置,把它放在了最下面。然而仅仅这样一个改动就不行了,编译会报错。报的什么错误呢?编译器告诉我们:“第 4 行名字 printNum 未声明。”

那么问题就很显然了:名字 printNum 的作用域不包括第 4 行。因为它的首次声明(声明点)在第 6 行,只有第 6 行后面才是 printNum 的作用域。所以第 4 行无法使用这个名字,从而就无法调用这个函数。那怎么解决呢?

当然我们仍然可以把函数定义写在最前面——除此之外,还可以这样做:在前面写上函数的声明,但不定义。因为 C++ 事实上允许多次声明一个名字(但不允许多次定义一个东西)。

#include <iostream>
using namespace std;
void printNum(int x);
int main() {
    printNum(42);
}
void printNum(int x) {
    cout << x << endl;
}

像这样,第 3 行就是函数 printNum 的声明了。函数的声明和定义非常相似,只不过把作为函数体的复合语句改成了分号 ;

返回值类型 函数名(形参列表);

如果没有函数体,那么执行这个函数的时候计算机根本不知道要做什么,从而无法正常使用这个函数。所以这种写法只是一个声明而不是一个定义。

你也许注意到了 形参列表 中,每个形参的名字是可选的。这是因为,如果形参的名字用不上的话, 形参列表 中就不需要给出这个形参的名字。而函数声明中没有函数体,故没有形参名字存在的必要。所以一般人们写函数声明但不定义时,都不给出形参的名字:

#include <iostream>
using namespace std;
void printNum(int); // 不需要形参名字
int main() {
    printNum(42);
}
void printNum(int x) {
    cout << x << endl;
}

函数也是 C++ 中的实体,故它也拥有自己的类型标识。比如上例中,printNum 的类型标识为 void(int),而 max 的类型标识为 int(int,int)

最近更新:
代码未运行