文章

函数声明与函数定义

函数声明告诉编译器函数名和参数,函数定义提供具体实现,分开便于代码组织和复用。

函数声明与函数定义

函数声明与函数定义

概念

函数声明

  • 作用:告诉编译器函数的名称、返回类型、参数类型及(可选的)参数名,以便在调用处进行类型检查、布局调用约定等。

  • 位置:通常放在头文件(.h)或源文件顶部。

  • 语法

1
返回类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, );
  • 示例
1
2
3
// math_utils.h
int add(int a, int b);
double power(double base, int exp);

函数定义

  • 作用:给出函数的具体实现,包括函数体(大括号 {} 内的语句)。

  • 位置:通常放在源文件(.cpp)中;如果函数体很短,也可以在头文件中(常用于 inline 函数)。

  • 语法

    1
    2
    3
    
    返回类型 函数名(参数列表) {
        // 函数体:具体执行语句
    }
    
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    // math_utils.cpp
    #include "math_utils.h"
      
    int add(int a, int b) {
        return a + b;
    }
      
    double power(double base, int exp) {
        double result = 1.0;
        for (int i = 0; i < exp; ++i) {
            result *= base;
        }
        return result;
    }
    

区别和联系

 函数声明函数定义
是否包含实现否,只有函数签名是,包含完整的函数体
编译器作用提供接口信息,检查调用合法性生成可执行代码
出现次数同一个函数可声明多次,但通常不超过几次每个函数只能定义一次(否则链接错误)
放置位置头文件(或源文件顶部)源文件(或头文件内的 inline 定义)

为什么要有函数声明

1. 允许先调用后定义

C++ 是 编译型语言,编译器从上到下顺序处理代码。如果在函数定义之前就使用了它(即先调用后定义),没有提前声明会导致编译错误

示例:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

int main() {
    int result = add(3, 4); // 错误:此时编译器不知道 add 是什么
    std::cout << result << std::endl;
    return 0;
}

int add(int a, int b) {
    return a + b;
}

正确写法(有函数声明):

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

int add(int a, int b); // 函数声明

int main() {
    int result = add(3, 4); // 编译器已经知道 add 的签名
    std::cout << result << std::endl;
    return 0;
}

int add(int a, int b) {
    return a + b;
}

2. 支持多文件编程(模块化开发)

当把函数定义放在一个 .cpp 文件中,另一个文件想要用这个函数,就需要通过声明(通常放在 .h 文件中)来共享接口。

示例结构:

1
2
3
math_utils.h       // 函数声明
math_utils.cpp     // 函数定义
main.cpp           // 函数调用

math_utils.h

1
2
3
4
5
6
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

int add(int a, int b);

#endif

math_utils.cpp

1
2
3
4
5
#include "math_utils.h"

int add(int a, int b) {
    return a + b;
}

main.cpp

1
2
3
4
5
6
7
#include <iostream>
#include "math_utils.h"

int main() {
    std::cout << add(1, 2) << std::endl;
    return 0;
}

3. 函数声明实现接口与实现分离

头文件中的函数声明可以被别人引用,而实现隐藏在 .cpp 中,有利于:

  • 信息封装
  • 编译依赖减小
  • 团队协作更清晰

4. 支持递归函数或相互调用函数

比如函数 A 调用 B,B 又调用 A,这时就必须有声明:

1
2
3
4
5
6
7
8
9
void A(); // 声明 A

void B() {
    A(); // 可以调用 A,因为已经声明
}

void A() {
    B();
}

为什么导个头文件就能使用到定义

#include "math_utils.h" 看起来像是引入了 .cpp 文件里的函数,但实际上,它并不是直接“使用 cpp 文件”,而是通过函数声明 + 链接过程来实现跨文件调用的。这是 C++ 编译机制的核心之一。

1. C++ 编译的本质:“编译 + 链接” 两阶段

C++ 编译流程通常如下:

1
2
3
每个 .cpp 文件 --> 独立编译成 .obj / .o 文件(目标文件)
       
        所有目标文件和库 --> 链接器(Linker)组合为最终可执行文件

2. 头文件(.h)的作用:提供声明(接口)

当在 main.cpp 里写:

1
#include "math_utils.h"

引入了函数的声明,告诉编译器:

“有一个叫 add(int, int) 的函数,返回 int,你先相信我,它确实存在。”

这允许编译器通过类型检查并成功生成目标文件 main.o

3. .cpp 文件的作用:提供定义(实现)

然后 math_utils.cpp 中真正写了这个函数:

1
2
3
int add(int a, int b) {
    return a + b;
}

这段代码单独被编译成 math_utils.o,包含了 add() 函数的机器码实现。

4. 链接阶段:把引用和实现对上号

最后,链接器(linker)将:

  • main.o 中对 add()未解析引用
  • math_utils.oadd()实现

匹配起来,完成程序的拼装。

5. 总结

角色作用举例
math_utils.h说:“add 这个函数存在,参数是啥”提供声明给 main.cpp
math_utils.cpp真正定义了这个函数是怎么做加法的提供函数体给链接器找定义
#include只是复制头文件的内容进当前文件相当于把声明粘贴进来了
编译器负责翻译语法,生成目标代码.cpp 文件编成 .o 文件
链接器把“你调用了什么”对上“我实现了什么”组合目标文件生成程序

为什么不 #include "math_utils.cpp"

也可以这样做:

1
#include "math_utils.cpp"  // 不推荐!

但这是极不推荐的做法:

  • 会造成多重定义(多个 .cpp 都包含的话)
  • 无法实现编译单元分离(每个 .cpp 独立编译失效)
  • 会降低编译效率模块化结构

#include 用于头文件(声明),.cpp 文件应独立编译,不被直接包含。

为什么不能把函数定义写在头文件里

示例( 错误用法):

1
2
3
4
// math_utils.h
int add(int a, int b) {
    return a + b;
}

如果这样写,并在多个 .cpp 文件里都 #include "math_utils.h",会得到:

“multiple definition of add” 链接错误

原因:

  • 每个 .cpp 文件都包含了一份完整函数定义
  • 编译器会分别把它编译进每个 .o 文件
  • 链接时多个定义冲突

什么时候可以在头文件里写函数定义

1. 使用 inline(内联函数)

1
2
3
4
// math_utils.h
inline int add(int a, int b) {
    return a + b;
}

inline 表示编译器可以将函数体复制到调用点,避免函数调用开销,并允许多个 .cpp 中包含而不报错。

适合写短小函数(getter/setter、工具函数)。

2. 使用 static(内部链接)

1
2
3
4
// math_utils.h
static int add(int a, int b) {
    return a + b;
}

static 表示这个函数对当前编译单元(.cpp 文件)私有,不参与链接。

这样每个 .cpp 文件都有一个自己的 add() 副本,不冲突。但一般不推荐滥用。

extern 的用法:跨文件共享变量/函数

extern 告诉编译器:“这个变量/函数定义在别处,我只是来用一下的”。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// math_utils.h
extern int global_count;

void addOne();

// math_utils.cpp
#include "math_utils.h"
int global_count = 0;  // 变量定义
void addOne() {
    ++global_count;
}

// main.cpp
#include "math_utils.h"
int main() {
    addOne();
    std::cout << global_count << std::endl;
}
  • extern声明
  • int global_count = 0;定义
  • 只有一处定义,任意处声明
本文由作者按照 CC BY 4.0 进行授权