C程序如何调用C语言函数?

99ANYc3cd6
预计阅读时长 22 分钟
位置: 首页 C语言 正文

为什么需要函数?

在学习如何调用之前,我们先明白为什么要用函数。

c 程序调用c语言函数
(图片来源网络,侵删)
  • 代码重用: 将常用的功能封装成一个函数,可以在程序的任何地方多次调用,避免重复编写相同的代码。
  • 模块化: 将一个复杂的大任务分解成多个小而简单的函数,使代码结构更清晰,易于理解和维护。
  • 简化逻辑: 主函数(main)可以变得更简洁,只负责调用不同的函数来完成整体流程,每个函数负责一个具体的子任务。

函数调用的基本三要素

一个完整的函数调用包含三个核心部分:

  1. 函数声明: 告诉编译器“这个函数存在,它叫什么名字,返回什么类型,需要什么参数”,这通常放在文件的开头。
  2. 函数定义: 提供函数的具体实现,即“这个函数具体要做什么”,这可以放在文件的任何位置(通常是声明之后)。
  3. 函数调用: 在需要执行该功能的地方,使用函数名并传入实际参数来执行函数。

一个最简单的例子

下面是一个完整的、可运行的 C 程序,演示了如何定义、声明和调用一个函数。

示例代码:add_numbers.c

#include <stdio.h>
// 1. 函数声明
// 告诉编译器有一个名为 add 的函数,它接收两个 int 参数,并返回一个 int 值。
// 这个声明也被称为 "函数原型" (Function Prototype)。
int add(int a, int b);
// 2. 函数定义
// 这才是 add 函数的实际实现。
int add(int a, int b) {
    printf("    -> 正在执行 add 函数,计算 %d + %d\n", a, b);
    int sum = a + b;
    return sum; // 返回计算结果
}
// 3. 主函数 - 程序的入口
int main() {
    int num1 = 10;
    int num2 = 20;
    int result;
    printf("主函数:准备调用 add 函数...\n");
    // 4. 函数调用
    // 将 num1 和 num2 作为实际参数传递给 add 函数。
    // add 函数执行完毕后,返回值被赋给 result 变量。
    result = add(num1, num2);
    printf("主函数:add 函数调用结束,结果是 %d\n", result);
    return 0;
}

如何编译和运行

  1. 将代码保存为 add_numbers.c
  2. 打开终端或命令提示符。
  3. 使用 GCC 编译器进行编译:
    gcc add_numbers.c -o add_numbers
  4. 运行生成的可执行文件:
    ./add_numbers

预期输出

主函数:准备调用 add 函数...
    -> 正在执行 add 函数,计算 10 + 20
主函数:add 函数调用结束,结果是 30

代码解析

  • #include <stdio.h>: 包含标准输入输出库,这样我们才能使用 printf 函数。
  • int add(int a, int b);: 函数声明,它向编译器承诺:我们稍后会定义一个名为 add 的函数,它接受两个 int 类型的参数,并返回一个 int 类型的值,如果没有这个声明,main 函数中的 add(num1, num2) 调用可能会在编译时出错。
  • int add(int a, int b) { ... }: 函数定义
    • int add: 函数名是 add,返回值类型是 int
    • (int a, int b): 这是形式参数,它们是函数内部的局部变量,用于接收调用时传入的值。
    • 函数体,包含了要执行的代码。
    • return sum;: return 关键字用于从函数中返回一个值,并立即终止函数的执行。
  • result = add(num1, num2);: 函数调用
    • add: 函数名。
    • num1, num2: 这是实际参数,它们是 main 函数中已经存在的变量,它们的值被复制传递给 add 函数的形式参数 ab
    • result = ...: add 函数返回的值被赋给 result 变量。

函数参数传递方式

C 语言中,函数参数传递是“传值调用” (Call by Value)

这意味着在调用函数时,实际参数的值会被复制一份,传递给形式参数,函数内部操作的是这个副本,不会改变原始实际参数的值

c 程序调用c语言函数
(图片来源网络,侵删)

示例:证明传值调用

#include <stdio.h>
// 定义一个函数,试图修改传入的参数
void modify_value(int x) {
    printf("  modify_value 函数内部:x 的初始值是 %d\n", x);
    x = 100; // 修改的是副本 x
    printf("  modify_value 函数内部:修改后 x 的值是 %d\n", x);
}
int main() {
    int num = 10;
    printf("main 函数中:调用 modify_value 前,num 的值是 %d\n", num);
    modify_value(num); // 传递 num 的值给 x
    printf("main 函数中:调用 modify_value 后,num 的值是 %d\n", num);
    return 0;
}

输出结果

main 函数中:调用 modify_value 前,num 的值是 10
  modify_value 函数内部:x 的初始值是 10
  modify_value 函数内部:修改后 x 的值是 100
main 函数中:调用 modify_value 后,num 的值是 10

可以看到,尽管 modify_value 函数内部将 x 修改为 100,但 main 函数中的 num 变量仍然是 10,这就是传值调用的特性。


无参数和无返回值的函数

函数可以没有参数,也可以没有返回值。

  • 无返回值: 使用 void 作为返回类型。
  • 无参数: 在参数列表中使用 void 或者直接留空(现代 C 标准更推荐用 void 以增加清晰度)。

示例:打印欢迎信息

#include <stdio.h>
// 函数声明:无参数,无返回值
void print_welcome_message(void);
int main() {
    print_welcome_message(); // 调用函数
    return 0;
}
// 函数定义
void print_welcome_message(void) {
    printf("====================================\n");
    printf("      欢迎使用 C 语言世界!\n");
    printf("====================================\n");
}

递归函数

一个函数可以调用它自己,这种技术称为递归,递归通常用于解决可以被分解成相似子问题的问题,例如计算阶乘、斐波那契数列等。

示例:使用递归计算阶乘

n! = n * (n-1) * (n-2) * ... * 1 0! = 1

c 程序调用c语言函数
(图片来源网络,侵删)
#include <stdio.h>
// 函数声明
long factorial(int n);
int main() {
    int number = 5;
    printf("%d 的阶乘是 %ld\n", number, factorial(number));
    return 0;
}
// 函数定义:递归实现
long factorial(int n) {
    // 基线条件:递归的终止点
    if (n == 0 || n == 1) {
        return 1;
    } 
    // 递归步骤:函数调用自身
    else {
        return n * factorial(n - 1);
    }
}

执行过程(计算 factorial(5)

  1. factorial(5) -> 5 * factorial(4)
  2. factorial(4) -> 4 * factorial(3)
  3. factorial(3) -> 3 * factorial(2)
  4. factorial(2) -> 2 * factorial(1)
  5. factorial(1) -> 返回 1 (满足基线条件)

结果开始层层返回: 6. factorial(2) 返回 2 * 1 = 2 7. factorial(3) 返回 3 * 2 = 6 8. factorial(4) 返回 4 * 6 = 24 9. factorial(5) 返回 5 * 24 = 120

警告:递归必须有一个明确的基线条件来终止递归,否则会导致栈溢出,程序崩溃。


多文件项目中的函数调用

在实际开发中,一个项目通常由多个 .c 文件和 .h 文件组成,这是一种良好的组织方式。

  • .c 文件:源文件,包含函数的定义
  • .h 文件:头文件,包含函数的声明、宏定义、结构体定义等,供其他文件包含。

示例结构

my_project/
├── main.c
├── calculator.h
└── calculator.c

calculator.h (头文件)

#ifndef CALCULATOR_H
#define CALCULATOR_H
// 函数声明
int add(int a, int b);
int subtract(int a, int b);
#endif // CALCULATOR_H
  • #ifndef ... #define ... #endif 是一个头文件保护机制,防止同一个头文件被重复包含导致编译错误。

calculator.c (源文件)

#include "calculator.h" // 包含自己的头文件,确保声明和定义一致
// 函数定义
int add(int a, int b) {
    return a + b;
}
int subtract(int a, int b) {
    return a - b;
}

main.c (主程序文件)

#include <stdio.h>
#include "calculator.h" // 包含我们自己创建的头文件
int main() {
    int x = 10, y = 5;
    printf("x + y = %d\n", add(x, y));
    printf("x - y = %d\n", subtract(x, y));
    return 0;
}

如何编译多文件项目

# 将 main.c 和 calculator.c 一起编译
gcc main.c calculator.c -o my_program
# 运行
./my_program

输出

x + y = 15
x - y = 5

这种方式的优势在于:

  • 模块化calculator.ccalculator.h 构成了一个独立的计算器模块。main.c 只需要知道函数的声明(通过头文件),不需要关心函数是如何实现的。
  • 可重用性calculator.ccalculator.h 可以被其他项目轻松复用。
  • 编译效率:修改 calculator.c 时,只需要重新编译它,而无需重新编译 main.c(在更复杂的项目构建系统中如 Makefile)。

概念 描述 示例
函数声明 告诉编译器函数的签名(名、返回类型、参数),不包含实现。 int add(int a, int b);
函数定义 提供函数的具体实现代码。 int add(int a, int b) { return a+b; }
函数调用 执行函数,传递实际参数。 result = add(10, 20);
传值调用 默认的参数传递方式,传递的是值的副本。 函数内修改参数不影响外部变量。
递归 函数调用自身,必须有基线条件。 long fact(int n) { if (n<=1) return 1; return n*fact(n-1); }
多文件项目 将声明放在 .h 文件,定义放在 .c 文件,通过 #include 关联。 #include "mylib.h"

掌握函数调用是编写结构化、可维护 C 程序的基础,从简单的单文件程序开始,逐步过渡到多文件项目,是提升编程能力的必经之路。

-- 展开阅读全文 --
头像
模型如何自定义与管理?
« 上一篇 04-08
如何高效编辑C/C语言软件?
下一篇 » 04-08
取消
微信二维码
支付宝二维码

目录[+]