C语言函数基础

函数是C语言程序的基本组成单元,它允许我们将一段独立的功能代码封装起来,通过函数名进行调用。 使用函数可以避免代码重复、提高可读性、便于调试和维护,是实现模块化编程的核心手段。 本章将系统讲解函数的基本概念、定义、声明、调用、参数传递机制以及相关的注意事项。

📦 什么是函数?

函数是一段完成特定任务的独立代码块,它可以接收输入(参数),并可能返回一个结果(返回值)。 C语言程序由多个函数组成,其中必须有且仅有一个名为 main 的函数作为程序入口。 函数的好处包括:

  • 代码复用:避免重复编写相同逻辑的代码。
  • 模块化:将复杂问题分解为小问题,每个函数解决一部分。
  • 易于维护:修改函数内部实现不影响调用者(只要接口不变)。
  • 便于调试:可以独立测试每个函数。

📝 函数的定义

函数定义包括返回类型、函数名、参数列表和函数体。语法格式:

返回类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...) {
    // 函数体
    // 可以包含变量声明、语句等
    return 返回值;  // 如果返回类型为void,则不需要return或直接return;
}

示例:定义一个计算两个整数之和的函数。

int sum(int a, int b) {
    int result = a + b;
    return result;
}

如果函数不需要返回值,返回类型应为 void

void printHello() {
    printf("Hello, World!\n");
    // 可以没有return,或使用 return; 提前结束
}

📄 函数的声明(原型)

在使用一个函数之前,编译器需要知道它的返回类型、函数名和参数类型。如果函数的定义出现在调用之后,就需要在调用前进行函数声明(也称为函数原型)。 函数声明通常放在文件开头或头文件中。

// 函数原型示例
int sum(int a, int b); // 可以省略参数名:int sum(int, int);
void printHello(void);
#include <stdio.h>

// 函数声明(原型)
int sum(int a, int b);
void printMessage(void);

int main() {
    int x = 10, y = 20;
    int result = sum(x, y);   // 调用
    printf("结果: %d\n", result);

    printMessage();
    return 0;
}

// 函数定义
int sum(int a, int b) {
    return a + b;
}

void printMessage() {
    printf("这是消息函数\n");
}
最佳实践: 建议总是为所有函数(除了 main)提供函数原型,并将它们放在文件开头或单独的头文件中。这样可以避免因函数定义顺序导致的编译错误。

📞 函数的调用

调用函数时,需要提供与函数原型匹配的实参(实际参数),并使用函数返回的值(如果有)。函数调用可以出现在表达式中,也可以单独作为语句。

int main() {
    int a = 5, b = 7;
    int result = sum(a, b);   // 调用,将结果赋给result
    printf("Sum = %d\n", result);

    // 直接使用函数返回值
    printf("Sum = %d\n", sum(3, 4));

    // 无返回值函数调用作为语句
    printHello();
    return 0;
}

🎯 参数传递:值传递

C语言中,函数参数传递采用值传递(pass by value)方式。即实参的值被复制给形参,函数内部对形参的修改不会影响外部的实参。 这使得函数内部的变量与外部隔离,保证了数据的安全性。

#include <stdio.h>

void changeValue(int x) {
    x = 100;   // 修改的是形参的副本,不影响实参
    printf("函数内部: x = %d\n", x);
}

int main() {
    int num = 10;
    printf("调用前: num = %d\n", num);
    changeValue(num);
    printf("调用后: num = %d\n", num);   // 仍然是10
    return 0;
}
思考: 如果希望函数能够修改外部变量的值,该怎么办?这需要使用指针(将在下一章学习)来实现“传址”调用。

📤 返回值与return语句

函数通过 return 语句返回一个值给调用者。return 语句会立即结束函数的执行,并将控制权返回给调用处。 一个函数可以有多个 return 语句,但通常建议保持单一出口以提高可读性。

// 返回两个数中的较大者
int max(int a, int b) {
    if (a > b)
        return a;
    else
        return b;
}

// 提前返回示例
int divide(int a, int b) {
    if (b == 0) {
        printf("除数不能为0!\n");
        return 0;   // 提前返回,不继续执行
    }
    return a / b;
}

如果函数返回类型为 void,可以使用 return; 提前结束函数,也可以省略最后的 return

🌐 函数的作用域与生命周期

函数内部定义的变量称为局部变量,它们的作用域仅限于函数内部,生命周期从进入函数开始到函数返回结束。 在所有函数外部定义的变量是全局变量,作用域为整个文件(如果使用 extern 还可以跨文件),生命周期贯穿整个程序运行。

#include <stdio.h>

int global = 100;   // 全局变量

void func() {
    int local = 10;   // 局部变量
    printf("局部变量: %d, 全局变量: %d\n", local, global);
    // 可以修改全局变量
    global++;
}

int main() {
    func();
    func();
    printf("全局变量最终值: %d\n", global);   // 102
    return 0;
}
注意: 过多使用全局变量会增加模块间的耦合,降低代码的可维护性,应谨慎使用。建议尽量通过参数传递数据。

🔁 函数的嵌套调用

函数可以调用其他函数,甚至调用自身(递归调用)。下面是一个简单的嵌套调用示例:

#include <stdio.h>

int add(int x, int y) {
    return x + y;
}

int multiply(int x, int y) {
    return x * y;
}

int calculate(int a, int b, int c) {
    int sum = add(a, b);
    return multiply(sum, c);
}

int main() {
    int result = calculate(2, 3, 4);   // (2+3)*4 = 20
    printf("结果: %d\n", result);
    return 0;
}

🔄 递归函数

递归函数是指函数在其定义中直接或间接调用自身。递归需要满足两个条件:存在基线条件(终止条件)和递归条件(向基线靠近)。 经典示例:计算阶乘。

#include <stdio.h>

// 递归计算 n!
long long factorial(int n) {
    if (n <= 1)          // 基线条件
        return 1;
    else
        return n * factorial(n - 1);  // 递归调用
}

int main() {
    int n = 5;
    printf("%d! = %lld\n", n, factorial(n));
    return 0;
}
注意: 递归虽然简洁,但每次调用都会消耗栈空间,深度过大可能导致栈溢出。对于循环可轻易解决的问题,应优先使用迭代。

💡 函数设计原则

  • 单一职责:一个函数只做一件事,功能明确。
  • 函数不宜过长:通常建议不超过一个屏幕(约50-100行),过长应考虑拆分。
  • 参数数量不宜过多:超过5个参数时,可考虑使用结构体封装。
  • 避免全局变量依赖:尽量通过参数传递数据。
  • 函数命名清晰:使用动词或动宾结构,如 calculateSumisPrime
  • 合理使用返回值和错误处理:通过返回值传递结果或状态,必要时使用 errno 或输出参数。

📋 综合示例:简易计算器

#include <stdio.h>

// 函数声明
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);
void showMenu();

int main() {
    int choice;
    double x, y, result;

    do {
        showMenu();
        printf("请选择(1-5): ");
        scanf("%d", &choice);

        if (choice >= 1 && choice <= 4) {
            printf("请输入两个数字: ");
            scanf("%lf %lf", &x, &y);
        }

        switch (choice) {
            case 1:
                result = add(x, y);
                printf("%.2f + %.2f = %.2f\n", x, y, result);
                break;
            case 2:
                result = subtract(x, y);
                printf("%.2f - %.2f = %.2f\n", x, y, result);
                break;
            case 3:
                result = multiply(x, y);
                printf("%.2f * %.2f = %.2f\n", x, y, result);
                break;
            case 4:
                if (y == 0)
                    printf("除数不能为0!\n");
                else {
                    result = divide(x, y);
                    printf("%.2f / %.2f = %.2f\n", x, y, result);
                }
                break;
            case 5:
                printf("退出程序\n");
                break;
            default:
                printf("无效选择,请重新输入\n");
        }
        printf("\n");
    } while (choice != 5);

    return 0;
}

// 函数定义
void showMenu() {
    printf("===== 计算器菜单 =====\n");
    printf("1. 加法\n");
    printf("2. 减法\n");
    printf("3. 乘法\n");
    printf("4. 除法\n");
    printf("5. 退出\n");
    printf("=====================\n");
}

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

double subtract(double a, double b) {
    return a - b;
}

double multiply(double a, double b) {
    return a * b;
}

double divide(double a, double b) {
    return a / b;
}

⚠️ 常见错误与注意事项

  • 函数声明与定义不匹配:返回类型、参数类型或数量不一致导致编译错误。
  • 忘记返回语句:对于非void函数,如果执行路径没有return,将返回不确定的值。
  • 局部变量返回地址:返回指向局部变量的指针是错误的,因为局部变量在函数返回后会被销毁。
  • 未声明的函数调用:C89中若未声明函数,编译器会隐式声明为 int func(),可能导致参数传递错误。C99起禁止隐式声明。
  • 递归深度过大:导致栈溢出,应考虑迭代实现。
  • 参数传递混淆值传递与地址传递:若需要修改外部变量,必须使用指针。

函数是C语言模块化编程的基石。掌握函数的定义、声明、调用和参数传递机制后,你已经能够将程序分解为可重用的模块。 下一章我们将学习指针,这是C语言的精髓,也是实现更高级数据结构(如动态数组、链表)和高效参数传递的基础。