当一个程序里的函数越来越多时,我们往往希望打开文件第一眼就能看到 main() 主流程,而把各个子函数的具体实现放到后面。这时就会遇到一个问题:如果把子函数的定义写在 main() 函数的后面,我们在前面调用它时,编译器怎么知道这个函数存在呢?
为了解决这个问题,C++ 提供了 “函数声明”(有时也叫 “函数原型”)。简单来说,函数声明就是提前跟编译器打个招呼,告诉它 “后面会有这样一个函数”。而函数定义则是真正把函数体写出来,如下图所示。

为什么要使用函数声明?
在前面学习自定义函数时,我们都是把被调用的子函数直接写在 main() 前面。这是因为 C++ 编译器是按 “从上往下” 的顺序依次读取和检查代码的。
如果我们把计算矩形面积的 getArea() 函数直接挪到 main() 后面,并且不在前面做任何交代,编译时就会直接报错。请看下面例子。
示例 1:未声明就直接调用
#include <iostream>
int main() {
int area = getArea(8, 5);
std::cout << area << '\n';
return 0;
}
int getArea(int width, int height) {
return width * height;
}运行结果如下。
(报错)error: 'getArea' was not declared in this scope分析:
编译器从上往下读到 getArea(8, 5) 时,在前面的代码里还没有见过 getArea 这个名字,不知道它的参数和返回类型。因此编译器直接报出 was not declared in this scope 错误,提示该函数在当前作用域中尚未声明。
解决办法非常简单:我们只需要在 main() 前面先补上一行 “函数声明”,提前交代清楚这个函数的基本格式,再把完整的函数定义留在 main() 后面就可以了。
C++ 函数声明的用法
在 C++ 中,编写函数声明只需要把函数定义的第一行复制过来,去掉花括号 {} 函数体,并在末尾加上一个英文分号(;)即可。
语法:
返回类型 函数名(参数列表);说明:
函数声明专门用来提前告诉编译器函数的返回类型、函数名以及函数参数的类型和个数。比如对于上面的 getArea() 函数,它的函数声明就是 int getArea(int width, int height);,注意末尾的英文分号不能漏掉。
初学函数时,小伙伴们经常会遇到 “函数声明”、“函数定义” 和 “函数调用” 这三个词。它们之间的区别如下表所示。
| 概念名称 | 代码形式 | 主要作用 |
|---|---|---|
| 函数声明 | int getArea(int width, int height); | 说明 “函数长什么样”,末尾带分号,无函数体 |
| 函数定义 | int getArea(int width, int height) { ... } | 实现 “函数具体怎么做”,包含完整的花括号函数体 |
| 函数调用 | int area = getArea(8, 5); | 传入实际数据,让函数体真正执行起来 |
搞清楚三者的分工后,下面我们通过两个例子来看看 “先声明、再调用、最后定义” 的配合方式。
示例 2:先声明后定义
#include <iostream>
// 1. 函数声明
int getArea(int width, int height);
int main() {
// 2. 函数调用
int area = getArea(8, 5);
std::cout << "矩形面积:" << area << '\n';
return 0;
}
// 3. 函数定义
int getArea(int width, int height) {
return width * height;
}运行结果如下。
矩形面积:40分析:
int getArea(int width, int height); 放在 main() 上方,用来提前声明该函数。编译器先读到这行声明并记住了它的格式,随后检查 main() 里的 getArea(8, 5) 确认调用合法,最后关联到下方的函数定义算出结果 40。
实际上,只要写在调用语句之前,函数声明既可以放在 main() 函数外部,也可以放在 main() 函数内部。下面 2 种方式都是可行的。
// 方式 1(推荐)
int getArea(int width, int height);
int main() {
int area = getArea(8, 5);
return 0;
}
// 方式 2
int main() {
int getArea(int width, int height);
int area = getArea(8, 5);
return 0;
}在实际开发中,我们统一推荐使用方式 1,把函数声明放在所有函数外面的顶部。这样当前文件中的所有函数都能共用这行声明,不需要在每个函数里面重复写一遍。
示例 3:集中声明多个函数
#include <iostream>
// 集中声明函数
int getPerimeter(int width, int height);
int getArea(int width, int height);
int main() {
std::cout << "矩形周长:" << getPerimeter(8, 5) << '\n';
std::cout << "矩形面积:" << getArea(8, 5) << '\n';
return 0;
}
// 依次定义函数
int getPerimeter(int width, int height) {
return (width + height) * 2;
}
int getArea(int width, int height) {
return width * height;
}运行结果如下。
矩形周长:26
矩形面积:40分析:
在这个例子中,我们在 main() 上方集中声明了计算周长的 getPerimeter() 和计算面积的 getArea(),就像在文件开头列了一份清晰的函数清单。紧接着我们在 main() 中写出主流程,最后在文件底部逐一补齐两个函数的具体实现。
那么平时写代码时,我们应该把函数直接定义在 main() 前面,还是先声明再放到 main() 后面呢?当练习代码比较短、只有一两个小函数时,直接定义在 main() 前面最省事。当自定义函数较多时,我们先在顶部集中声明,再把定义放到后面,代码结构会更加整洁。
另外,如果遇到两个函数互相调用的情况,不管把哪一个写在前面,调用对方时对方的完整定义都还在下面。这时我们就必须提前写好函数声明。至于大型项目中如何把函数声明放进头文件(.h)、把定义放进源文件(.cpp),后面的多文件编程还会展开介绍。
函数声明的注意事项
在使用函数声明时,小伙伴们需要注意以下 2 点:
- 声明与定义必须保持一致:同一个函数的声明与定义,在返回类型、函数名、参数个数和参数类型上都必须严格一致。平时有个省心的小窍门:我们可以先把函数定义写好,再复制第一行放到
main()前面并加上英文分号(;),这样就不容易写错。 - 调用过的函数必须有定义:在前面介绍编译与运行过程时我们讲过,生成可执行程序需要经过编译和链接两步。函数声明只能让编译阶段通过检查,如果我们调用了该函数却漏写对应的函数定义,链接阶段找不到真正的函数体,就会报
undefined reference(未定义的引用)错误。
比如对于第 1 点,如果函数声明写的是返回 int 类型,而后面的函数定义却改成了 double 类型,前后不一致就会直接引发编译报错。下面这种写法就是错误的。
int getArea(int width, int height);
double getArea(int width, int height) {
return width * height;
}常见问题
1. 在写函数声明时,小括号里的参数名字可以省略吗?
在语法上是可以省略的。因为在函数声明阶段,编译器只关心函数参数的个数和类型,并不关心参数具体叫什么名字。也就是说,我们把 int getArea(int width, int height); 简写成 int getArea(int, int); 也能正常通过编译。
不过在实际开发中,我们依然建议大家在函数声明里保留 width、height 这样有意义的参数名。这样阅读顶部声明时,一眼就能看懂每个参数代表什么含义,不需要再往下翻看函数定义。
2. 同一个函数可以重复声明多次吗?
在 C++ 中,一个函数在整个程序里只能 “定义” 一次,如果出现两份相同的函数体就会报重复定义错误。但只要返回类型和参数列表完全一致,同一个函数是可以重复 “声明” 多次的。不过在单个源文件里,我们只写一行声明就足够了。
