为什么需要函数
函数是一个有名字的代码块,你可以通过调用它,随时让它运行。你不必在好几个地方重复同样的逻辑,而是只写一次,给它起个名字,然后在需要的地方调用这个名字。这能让程序更短、更易读,也更容易修复:在一个地方修改逻辑,每个调用者都会得到更新。
其实你一直都在调用一个函数:main。它是每个 C++ 程序开始运行的入口。现在你要编写自己的函数了。你之前见过的循环,比如基于范围的 for,常常就写在函数里面,这样你就能通过名字复用一整段逻辑。
函数的结构
每个函数都有四个部分:返回类型、函数名、括号中的参数列表,以及花括号中的函数体。
int add(int a, int b) { // return type | name | parameters
return a + b; // body
}
int是返回类型,也就是函数交回的值的种类。add是你调用它时使用的名字。(int a, int b)是参数,即由调用者提供的输入。- 花括号里是函数体,也就是调用函数时运行的代码。
下面是它在完整程序中的样子。把函数定义写在 main 的上方,main 调用它时就能看到它。
调用 add(2, 3) 时,函数以 a = 2、b = 3 运行,整个表达式就变成了返回的值。你可以把它存进变量,也可以像第二行 cout 那样直接用在另一个表达式中。
返回值
return 语句做两件事:把一个值交还给调用者,并立即结束函数。return 执行之后,它后面的代码都不会再运行,控制权会直接跳回调用函数的地方。
返回值的类型必须与声明的返回类型一致(或能转换成它)。声明为 int 的函数应该返回一个 int;对任何非 void 函数来说,什么都不返回,或者没有 return 就走到了末尾,都是 bug。
void 函数
并不是每个函数都会产生一个值。当一个函数只是做某件事,比如打印输出、更新状态时,它的返回类型就是 void。void 函数可以用单独的 return; 提前退出,也可以一直运行到右花括号。
试图使用 void 函数的结果,比如 int x = greet("Ada");,会导致编译错误,因为根本没有值可以赋给变量。一个常见的错误是在 void 函数里写 return someValue;,编译器同样会拒绝它。
声明与定义
C++ 从上到下读取文件,所以默认情况下,函数必须出现在调用它的代码之前。当这种顺序不方便时,你可以把函数拆成声明(也叫原型)和定义两部分。
声明写出函数的签名,以分号结尾,没有函数体。它向编译器承诺:“这个函数存在,调用方式是这样的。”完整的定义可以放在后面,甚至放在 main 之后。
如果没有第 4 行的原型,编译器会在 main 里先遇到 square(5),而此前它从没见过 square,于是构建就会失败。头文件也正是靠原型,让多个源文件共享同样的函数。注意,声明中的参数名是可选的,写成 int square(int); 也完全可以;对编译器来说,只有类型才重要。
常见错误
有几个陷阱会让新手一次又一次地栽跟头:
- 先调用、后声明。如果你看到“
addwas not declared in this scope”这样的错误,说明函数定义在它第一次被调用的位置之后,而且没有原型。把定义移到前面,或者加上原型。 - 忘记 return。非
void函数没有执行return就到达末尾是未定义行为,调用者会收到垃圾值。编译时打开警告(-Wall),编译器就会把它指出来。 - 把定义和声明弄混。定义有花括号包起来的函数体,末尾没有分号。声明有分号,没有函数体。把两者搞混,比如在一个本想定义的函数的参数列表后面直接加了分号,就会产生令人困惑的错误。
- 忽略返回值。单独一行的
add(2, 3);能通过编译,但算出来的和被悄悄丢掉了。确保你真正用上了函数返回的值。
// Looks like a definition, but the stray ; turns it into a
// declaration followed by a leftover block - a frequent typo:
int triple(int n); // <- this ; ends the statement
{
return n * 3; // n is undefined here; this block is now orphaned
}
下一步:函数参数
你已经看到函数通过参数列表接收输入,但这里面还有很多内容。下一页深入讲解函数参数:值传递与引用传递、默认实参、const 参数,以及这些选择如何决定你的函数能否修改调用者的数据。
常见问题
C++ 中怎么写一个函数?
给它一个返回类型、一个名字、一对放参数的括号,以及用花括号包起来的函数体:int add(int a, int b) { return a + b; }。用名字加实参来调用它,比如 add(2, 3)。如果函数不返回任何值,就用 void 作为返回类型。
C++ 中函数声明和函数定义有什么区别?
声明(也叫原型)告诉编译器函数的名字、返回类型和参数,以分号结尾:int add(int a, int b);。定义还会在花括号中给出函数体。你可以在 main 之前声明一个函数,之后再定义它;有了声明,就能在定义出现之前调用这个函数。
C++ 函数没有返回值会怎样?
对于 void 函数,什么都不会发生,它就这样结束了。但对于非 void 函数,没有执行 return 就到达末尾是未定义行为:调用者会拿到一个垃圾值,程序可能出现异常行为。大多数编译器会对此给出警告;在非 void 函数的每条路径上都一定要返回一个值。