C++ 函数声明与定义

当一个程序里的函数越来越多时,我们往往希望打开文件第一眼就能看到 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++ 中,一个函数在整个程序里只能 “定义” 一次,如果出现两份相同的函数体就会报重复定义错误。但只要返回类型和参数列表完全一致,同一个函数是可以重复 “声明” 多次的。不过在单个源文件里,我们只写一行声明就足够了。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号