HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • C 语言入门教程

    • C语言系列教程
    • 第01章 基础概念与编译
    • 第02章 数据类型与变量
    • 第03章 运算符
    • 第04章 输入输出
    • 第05章 流程控制
    • 第06章 数组
    • 第07章 指针
    • 第08章 函数
    • 第09章 构造类型

第08章 函数

8.1 函数基础

8.1.1 什么是函数

函数: 完成特定功能的代码块,是程序的基本模块。

优点:

  • 代码复用
  • 模块化编程
  • 提高可读性
  • 便于维护

8.1.2 函数的定义

语法:

返回类型 函数名(参数列表)
{
    // 函数体
    return 返回值;
}

示例:

#include <stdio.h>

// 无参数,无返回值
void sayHello(void)
{
    printf("Hello World!\n");
}

// 有参数,有返回值
int add(int a, int b)
{
    return a + b;
}

int main(void)
{
    sayHello();

    int result = add(3, 5);
    printf("3 + 5 = %d\n", result);

    return 0;
}

8.1.3 函数的声明

函数声明(函数原型): 告诉编译器函数的存在

语法:

返回类型 函数名(参数类型列表);

示例:

#include <stdio.h>

// 函数声明
int add(int a, int b);
void printArray(int arr[], int n);

int main(void)
{
    int result = add(3, 5);
    printf("result = %d\n", result);

    return 0;
}

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

⚠️ 注意: 函数必须先声明后使用(或在使用前定义)

8.1.4 函数的调用

#include <stdio.h>

int max(int a, int b)
{
    return a > b ? a : b;
}

int main(void)
{
    // 直接调用
    printf("max(10, 20) = %d\n", max(10, 20));

    // 赋值调用
    int result = max(30, 15);
    printf("result = %d\n", result);

    // 作为参数
    printf("max(max(1,2), max(3,4)) = %d\n",
           max(max(1, 2), max(3, 4)));

    return 0;
}

8.2 函数参数

8.2.1 值传递

C语言函数参数采用值传递(传值):

#include <stdio.h>

void func(int x)
{
    x = 100;  // 只修改了局部变量x
    printf("func中x = %d\n", x);
}

int main(void)
{
    int a = 10;

    func(a);

    printf("main中a = %d\n", a);  // a的值未改变

    return 0;
}

输出:

func中x = 100
main中a = 10

8.2.2 指针参数

通过指针可以修改实参:

#include <stdio.h>

void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main(void)
{
    int x = 10, y = 20;

    printf("交换前: x=%d, y=%d\n", x, y);

    swap(&x, &y);

    printf("交换后: x=%d, y=%d\n", x, y);

    return 0;
}

输出:

交换前: x=10, y=20
交换后: x=20, y=10

8.2.3 数组参数

一维数组:

#include <stdio.h>

// 方式1
void printArray(int arr[], int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

// 方式2:等价
void printArray2(int *arr, int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(void)
{
    int arr[] = {1, 2, 3, 4, 5};
    int n = sizeof(arr) / sizeof(arr[0]);

    printArray(arr, n);

    return 0;
}

⚠️ 重要:

  • 数组作为参数时退化为指针
  • 必须同时传递数组大小

二维数组:

#include <stdio.h>

// 列数必须指定
void printMatrix(int m[][4], int rows)
{
    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            printf("%3d ", m[i][j]);
        }
        printf("\n");
    }
}

int main(void)
{
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    printMatrix(matrix, 3);

    return 0;
}

8.2.4 字符串参数

#include <stdio.h>
#include <string.h>

// 方式1
void printString(char str[])
{
    printf("%s\n", str);
}

// 方式2:等价
void printString2(char *str)
{
    printf("%s\n", str);
}

// 使用const保护参数
void printConstString(const char *str)
{
    printf("%s\n", str);
    // str[0] = 'X';  // 错误!不能修改
}

int main(void)
{
    char str[] = "Hello";

    printString(str);
    printConstString("World");

    return 0;
}

8.2.5 可变参数

使用<stdarg.h>实现可变参数:

#include <stdio.h>
#include <stdarg.h>

int sum(int count, ...)
{
    va_list args;
    va_start(args, count);

    int total = 0;
    for (int i = 0; i < count; i++)
    {
        total += va_arg(args, int);
    }

    va_end(args);

    return total;
}

int main(void)
{
    printf("sum(3, 1, 2, 3) = %d\n", sum(3, 1, 2, 3));
    printf("sum(5, 1, 2, 3, 4, 5) = %d\n", sum(5, 1, 2, 3, 4, 5));

    return 0;
}

8.3 函数返回值

8.3.1 return语句

作用:

  1. 结束函数执行
  2. 返回值给调用者
#include <stdio.h>

int max(int a, int b)
{
    if (a > b)
    {
        return a;  // 返回a,函数结束
    }
    return b;      // 返回b,函数结束
}

int main(void)
{
    printf("max(10, 20) = %d\n", max(10, 20));

    return 0;
}

8.3.2 void函数

无返回值的函数:

#include <stdio.h>

void printLine(char ch, int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%c", ch);
    }
    printf("\n");

    // return;  // 可选,用于提前退出
}

int main(void)
{
    printLine('=', 40);
    printLine('-', 40);

    return 0;
}

8.3.3 返回指针

#include <stdio.h>
#include <string.h>

// 返回字符串中第一个字符c的位置
char* findChar(char *str, char c)
{
    while (*str != '\0')
    {
        if (*str == c)
        {
            return str;
        }
        str++;
    }
    return NULL;  // 未找到
}

int main(void)
{
    char str[] = "Hello World";
    char *p = findChar(str, 'W');

    if (p != NULL)
    {
        printf("找到字符'W': %s\n", p);
    }

    return 0;
}

⚠️ 警告: 不要返回局部变量的地址!

int* func()
{
    int a = 10;
    return &a;  // 错误!a的生命周期结束
}

8.3.4 返回结构体

#include <stdio.h>

typedef struct
{
    int x;
    int y;
} Point;

Point createPoint(int x, int y)
{
    Point p = {x, y};
    return p;  // 返回结构体副本
}

int main(void)
{
    Point p = createPoint(10, 20);
    printf("Point: (%d, %d)\n", p.x, p.y);

    return 0;
}

8.4 函数的递归

8.4.1 什么是递归

递归: 函数直接或间接调用自己。

要素:

  1. 递归出口(基准情况)
  2. 递归体(递归关系)

8.4.2 递归示例

示例1: 阶乘

#include <stdio.h>

int factorial(int n)
{
    // 递归出口
    if (n == 0 || n == 1)
    {
        return 1;
    }

    // 递归体
    return n * factorial(n - 1);
}

int main(void)
{
    printf("5! = %d\n", factorial(5));  // 120
    printf("10! = %d\n", factorial(10)); // 3628800

    return 0;
}

示例2: 斐波那契数列

#include <stdio.h>

int fibonacci(int n)
{
    // 递归出口
    if (n == 0) return 0;
    if (n == 1) return 1;

    // 递归体
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main(void)
{
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", fibonacci(i));
    }
    printf("\n");

    return 0;
}

输出: 0 1 1 2 3 5 8 13 21 34

示例3: 汉诺塔

#include <stdio.h>

void hanoi(int n, char from, char to, char aux)
{
    if (n == 1)
    {
        printf("将盘子1从 %c 移到 %c\n", from, to);
        return;
    }

    hanoi(n - 1, from, aux, to);
    printf("将盘子%d从 %c 移到 %c\n", n, from, to);
    hanoi(n - 1, aux, to, from);
}

int main(void)
{
    int n = 3;
    printf("汉诺塔(%d个盘子):\n", n);
    hanoi(n, 'A', 'C', 'B');

    return 0;
}

8.4.3 递归vs迭代

递归的优缺点:

优点:

  • 代码简洁
  • 易于理解
  • 适合解决递归问题

缺点:

  • 效率较低
  • 可能栈溢出
  • 空间复杂度高

迭代实现阶乘:

int factorial_iterative(int n)
{
    int result = 1;
    for (int i = 2; i <= n; i++)
    {
        result *= i;
    }
    return result;
}

8.5 函数指针

8.5.1 函数指针的定义

语法:

返回类型 (*指针名)(参数类型列表);

示例:

#include <stdio.h>

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

int main(void)
{
    int (*func)(int, int);  // 函数指针声明

    func = add;             // 指向add函数
    // func = &add;         // 等价写法

    int result = func(3, 5);  // 通过函数指针调用
    printf("result = %d\n", result);

    return 0;
}

8.5.2 函数指针作为参数

回调函数: 通过函数指针调用的函数

#include <stdio.h>

void forEach(int arr[], int n, void (*callback)(int))
{
    for (int i = 0; i < n; i++)
    {
        callback(arr[i]);
    }
}

void printElement(int x)
{
    printf("%d ", x);
}

void printSquare(int x)
{
    printf("%d ", x * x);
}

int main(void)
{
    int arr[] = {1, 2, 3, 4, 5};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("元素: ");
    forEach(arr, n, printElement);
    printf("\n");

    printf("平方: ");
    forEach(arr, n, printSquare);
    printf("\n");

    return 0;
}

8.5.3 函数指针数组

#include <stdio.h>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }

int main(void)
{
    // 函数指针数组
    int (*ops[])(int, int) = {add, sub, mul, divide};
    char *symbols[] = {"+", "-", "*", "/"};

    int a = 20, b = 10;

    for (int i = 0; i < 4; i++)
    {
        printf("%d %s %d = %d\n", a, symbols[i], b, ops[i](a, b));
    }

    return 0;
}

输出:

20 + 10 = 30
20 - 10 = 10
20 * 10 = 200
20 / 10 = 2

8.5.4 qsort函数示例

#include <stdio.h>
#include <stdlib.h>

// 比较函数
int compareInt(const void *a, const void *b)
{
    return (*(int*)a - *(int*)b);
}

int compareIntDesc(const void *a, const void *b)
{
    return (*(int*)b - *(int*)a);
}

void printArray(int arr[], int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(void)
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("原数组: ");
    printArray(arr, n);

    // 升序排序
    qsort(arr, n, sizeof(int), compareInt);
    printf("升序: ");
    printArray(arr, n);

    // 降序排序
    qsort(arr, n, sizeof(int), compareIntDesc);
    printf("降序: ");
    printArray(arr, n);

    return 0;
}

8.6 内联函数

8.6.1 inline关键字

内联函数: 建议编译器将函数体直接嵌入调用处,减少函数调用开销。

#include <stdio.h>

inline int square(int x)
{
    return x * x;
}

int main(void)
{
    int a = 5;
    printf("square(%d) = %d\n", a, square(a));

    return 0;
}

⚠️ 注意:

  • inline只是建议,编译器可能不采纳
  • 适用于简单、频繁调用的函数
  • 不适用于递归函数

8.6.2 inline vs 宏

特性inline函数宏
类型检查是否
调试容易困难
副作用无可能有
推荐度高低
// 宏的副作用
#define SQUARE(x) ((x) * (x))

int a = 5;
int b = SQUARE(a++);  // 展开为: ((a++) * (a++))
// a被递增两次!

// inline函数无此问题
inline int square(int x)
{
    return x * x;
}

int c = 5;
int d = square(c++);  // c只递增一次

8.7 静态函数

8.7.1 static函数

作用: 限制函数只在当前文件可见

// file1.c
static void helper()  // 只在file1.c中可见
{
    printf("Helper function\n");
}

void publicFunc()
{
    helper();  // 正确
}

// file2.c
void publicFunc();  // 声明
void helper();      // 声明

int main(void)
{
    publicFunc();  // 正确
    helper();      // 链接错误!找不到helper
    return 0;
}

用途:

  • 隐藏实现细节
  • 避免命名冲突
  • 提高代码封装性

8.8 实践练习

练习1: 实现字符串函数

#include <stdio.h>

// 字符串长度
size_t myStrlen(const char *str)
{
    const char *p = str;
    while (*p != '\0')
    {
        p++;
    }
    return p - str;
}

// 字符串拷贝
char* myStrcpy(char *dest, const char *src)
{
    char *ret = dest;
    while ((*dest++ = *src++) != '\0');
    return ret;
}

// 字符串比较
int myStrcmp(const char *s1, const char *s2)
{
    while (*s1 && (*s1 == *s2))
    {
        s1++;
        s2++;
    }
    return *(unsigned char*)s1 - *(unsigned char*)s2;
}

int main(void)
{
    char str1[] = "Hello";
    char str2[20];

    printf("长度: %lu\n", myStrlen(str1));

    myStrcpy(str2, str1);
    printf("拷贝: %s\n", str2);

    printf("比较: %d\n", myStrcmp(str1, str2));

    return 0;
}

练习2: 快速排序

#include <stdio.h>

void quickSort(int arr[], int left, int right)
{
    if (left >= right) return;

    int pivot = arr[left];
    int i = left, j = right;

    while (i < j)
    {
        while (i < j && arr[j] >= pivot) j--;
        if (i < j) arr[i++] = arr[j];

        while (i < j && arr[i] <= pivot) i++;
        if (i < j) arr[j--] = arr[i];
    }

    arr[i] = pivot;

    quickSort(arr, left, i - 1);
    quickSort(arr, i + 1, right);
}

void printArray(int arr[], int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(void)
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("排序前: ");
    printArray(arr, n);

    quickSort(arr, 0, n - 1);

    printf("排序后: ");
    printArray(arr, n);

    return 0;
}

练习3: 计算器(使用函数指针)

#include <stdio.h>

typedef double (*Operation)(double, double);

double add(double a, double b) { return a + b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double divide(double a, double b) { return b != 0 ? a / b : 0; }

double calculate(double a, double b, char op)
{
    Operation ops[] = {add, sub, mul, divide};
    char symbols[] = {'+', '-', '*', '/'};

    for (int i = 0; i < 4; i++)
    {
        if (op == symbols[i])
        {
            return ops[i](a, b);
        }
    }

    return 0;
}

int main(void)
{
    double a, b;
    char op;

    printf("请输入表达式(如: 10 + 5): ");
    scanf("%lf %c %lf", &a, &op, &b);

    double result = calculate(a, b, op);
    printf("%.2f %c %.2f = %.2f\n", a, op, b, result);

    return 0;
}

8.9 常见错误

错误1: 忘记返回值

int func(int a)
{
    if (a > 0)
    {
        return 1;
    }
    // 错误!a<=0时没有返回值
}

// 正确
int func(int a)
{
    if (a > 0)
    {
        return 1;
    }
    return 0;
}

错误2: 返回局部变量地址

int* func()
{
    int a = 10;
    return &a;  // 错误!a的生命周期结束
}

错误3: 数组参数未传大小

void func(int arr[])
{
    int n = sizeof(arr) / sizeof(arr[0]);  // 错误!arr是指针
    // ...
}

// 正确
void func(int arr[], int n)
{
    // ...
}

错误4: 递归无出口

int func(int n)
{
    return func(n - 1);  // 错误!无递归出口,栈溢出
}

// 正确
int func(int n)
{
    if (n == 0) return 1;  // 递归出口
    return func(n - 1);
}

错误5: 函数未声明

int main(void)
{
    func();  // 错误!func未声明
    return 0;
}

void func()
{
    printf("Hello\n");
}

// 正确:先声明
void func();

int main(void)
{
    func();
    return 0;
}

void func()
{
    printf("Hello\n");
}

8.10 小结

本章学习了:

函数的定义、声明和调用 函数参数传递(值传递、指针、数组) 函数返回值 递归函数及经典问题 函数指针与回调函数 内联函数(inline) 静态函数(static)

重点提示:

  1. C语言采用值传递,通过指针可以修改实参
  2. 数组作为参数时必须传递大小
  3. 不要返回局部变量的地址
  4. 递归要有明确的出口
  5. 使用const保护不需要修改的参数
  6. 函数指针用于回调和多态
  7. inline适用于简单、频繁调用的函数

函数设计原则:

  • 单一职责:一个函数只做一件事
  • 函数名要有意义
  • 参数不宜过多(建议不超过5个)
  • 避免过长的函数(建议不超过50行)

上一章: 第07章 指针下一章: 第09章 构造类型

Prev
第07章 指针
Next
第09章 构造类型