C语言 | 函数核心机制深度解构:从底层架构到工程化实践

个人主页-爱因斯晨

文章专栏-C语言

引言

最近偷懒了,迷上了三国和李贺。给大家分享一下最喜欢的一句诗:吾不识青天高黄地厚,唯见月寒日暖来煎人寿。我还不是很理解27岁的李贺,如何写出如此绝笔。

正文开始,今天我们来探讨一下关于C语言中的函数部分

一、函数的概念:代码的 "模块化" 基石

1.1 函数的定义与意义

  • 定义 :函数是一段可重复使用的代码块 ,具有输入(参数)处理逻辑(函数体)**和**输出(返回值)
  • 意义:
    • 复用性 :避免重复编写相同逻辑(如多次计算最大值,只需调用 max 函数)。
    • 可读性 :通过函数名(如 sortArray)直观理解功能,降低代码复杂度。
    • 可维护性:修改函数内部逻辑时,只需更新一处,不影响其他调用处。

1.2 函数的基本结构

c 复制代码
返回类型 函数名(参数列表) {
    // 函数体:实现具体功能
    return 返回值; // 非void类型必须返回对应类型的值
}
  • 示例:计算两数之和

    c 复制代码
    int add(int a, int b) { // 返回int,参数a、b为int
        return a + b; // 返回和
    }

二、库函数:"开箱即用" 的工具集

关于库函数和其他语言中封装的函数在上篇文章中已经讲到了,详情请看[从库函数到API接口,深挖不同语言背后的"封装"与"调用"思想-CSDN博客]()

2.1 库函数的分类与头文件

  • 标准库:C 语言内置的函数集合,分为:

    • 输入输出(stdio.hprintf(输出)、scanf(输入)。
    • 字符串处理(string.hstrlen(字符串长度)、strcpy(字符串复制)。
    • 数学运算(math.hsqrt(开平方)、pow(幂运算)。
    • 内存管理(stdlib.hmalloc(动态内存分配)、free(释放内存)。
  • 头文件:包含库函数的声明

    (告诉编译器函数的存在、参数和返回值)。使用库函数前必须包含对应头文件,例如:

    c 复制代码
    #include <stdio.h> // 包含printf的声明
    int main() {
        printf("Hello, World!"); // 调用库函数
        return 0;
    }

2.2 库函数的使用步骤(以 fgets 为例)

  1. 查阅文档fgets 从文件中读取字符串,原型为 char *fgets(char *s, int size, FILE *stream);

  2. 包含头文件#include <stdio.h>fgets 声明在此头文件中)。

  3. 调用函数

    c 复制代码
    #include <stdio.h>
    int main() {
        char str[100];
        fgets(str, 100, stdin); // 从标准输入(键盘)读取最多99个字符(含'\0')
        printf("输入内容:%s", str);
        return 0;
    }
  4. 注意事项:

    • size 参数需小于数组长度(避免缓冲区溢出)。
    • 返回值为 NULL 表示读取失败(如文件结束)。

三、自定义函数:"按需定制" 的代码块

3.1 函数定义的详细语法

  • 返回类型:
    • void:无返回值(如仅打印信息的函数)。
    • 基本类型(intfloat 等):返回对应类型的值。
  • 参数列表:
    • 无参数void func()func()(C99 后允许省略 void)。
    • 有参数int add(int a, int b)ab 为形参,接收实参的值)。
  • 函数体 :包含实现逻辑的代码,可使用 return 提前结束函数(void 函数用 return;)。

3.2 示例:实现 "判断素数" 函数

c 复制代码
#include <stdio.h>
#include <math.h>

// 自定义函数:判断n是否为素数(返回1是,0否)
int isPrime(int n) {
    if (n <= 1) return 0; // 1及以下不是素数
    for (int i=2; i<=sqrt(n); i++) { // 优化:只需检查到平方根
        if (n % i == 0) return 0; // 能整除,不是素数
    }
    return 1; // 是素数
}

int main() {
    int num;
    printf("输入一个整数:");
    scanf("%d", &num);
    if (isPrime(num)) {
        printf("%d是素数\n", num);
    } else {
        printf("%d不是素数\n", num);
    }
    return 0;
}
  • 解释:
    • 形参 n 接收实参(用户输入的 num)。
    • 通过循环判断是否有因数,提前返回结果(提高效率)。

四、形参和实参:"值的传递与拷贝"

4.1 实参(实际参数)

  • 定义 :调用函数时传递的具体值或变量 (如 isPrime(num) 中的 num)。
  • 特点:
    • 可以是常量isPrime(7))、变量isPrime(num))、表达式isPrime(2+3))。
    • 传递方式:值传递(形参是实参的拷贝,修改形参不影响实参,除非传递地址)。

4.2 形参(形式参数)

  • 定义 :函数定义时占位的参数 (如 isPrime(int n) 中的 n)。
  • 特点:
    • 函数调用时分配内存,调用结束后释放(形参是临时变量)。
    • 值传递本质 :形参是实参的副本 (如 nnum 的拷贝,修改 n 不影响 num)。

4.3 地址传递(突破值传递限制)

c 复制代码
// 交换两数(通过地址传递,修改实参)
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x=10, y=20;
    swap(&x, &y); // 实参是x、y的地址(传递指针)
    printf("x=%d, y=%d\n", x, y); // 输出x=20, y=10(实参被修改)
    return 0;
}
  • 解释:
    • 形参 *a*b 接收实参的地址(&x&y),通过解引用(*a)直接修改原变量的值。
    • 这是 值传递的特殊情况(传递地址,实现 "引用传递" 效果)。

五、return 语句:"函数的出口与结果"

5.1 return 的两种用法

  • 返回值 :给调用者一个结果(如 return a + b; 返回和)。
  • 结束函数 :提前退出函数(如 void 函数中的 return;,跳过后续代码)。

5.2 规则与示例

  • void 函数

    c 复制代码
    void printMessage() {
        printf("Hello!\n");
        return; // 可省略(函数体结束自动返回)
    }
  • 非 void 函数

    c 复制代码
    int max(int a, int b) {
        if (a > b) return a; // 返回a,结束函数
        return b; // 必有一个执行(确保返回值)
    }
  • 错误处理

    c 复制代码
    int divide(int a, int b) {
        if (b == 0) {
            printf("除数不能为0!\n");
            return -1; // 错误码(调用者根据返回值判断是否出错)
        }
        return a / b;
    }

六、数组作为函数参数:"传递指针与内存"

6.1 数组传参的本质

  • 数组名 作为参数时,传递的是首元素的地址 (即指针),函数内对数组的修改会影响原数组(因为操作同一块内存)。

  • 语法

    c 复制代码
    void printArray(int arr[], int size) { // 等价于int *arr
        for (int i=0; i<size; i++) {
            printf("%d ", arr[i]); // 等价于*(arr+i)
        }
    }

6.2 示例:数组排序(冒泡排序)

c 复制代码
#include <stdio.h>

void bubbleSort(int arr[], int size) {
    for (int i=0; i<size-1; i++) {
        for (int j=0; j<size-i-1; j++) {
            if (arr[j] > arr[j+1]) { // 交换相邻元素
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

int main() {
    int nums[] = {5, 3, 8, 1, 2};
    int size = sizeof(nums) / sizeof(nums[0]); // 计算数组长度
    bubbleSort(nums, size); // 传递数组名(首地址)和长度
    for (int i=0; i<size; i++) {
        printf("%d ", nums[i]); // 输出1 2 3 5 8(原数组已排序)
    }
    return 0;
}
  • 注意 :函数无法自动获取数组长度(需手动传递 size),因为形参 arr 是指针(丢失长度信息)。

七、函数调用:嵌套与链式

7.1 嵌套调用(函数内调用其他函数)

c 复制代码
void printHeader() {
    printf("===== 欢迎使用系统 =====\n");
}

void printMenu() {
    printHeader(); // 嵌套调用printHeader
    printf("1. 登录\n2. 注册\n3. 退出\n");
}

int main() {
    printMenu(); // 输出:===== 欢迎使用系统 ===== → 菜单选项
    return 0;
}

7.2 链式访问(函数返回值作为参数)

c 复制代码
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }

int main() {
    // 先算add(2,3)=5,再算mul(5,4)=20(链式调用)
    int result = mul(add(2, 3), 4); 
    printf("结果:%d\n", result); // 输出20
    return 0;
}

八、函数的声明与定义:"多文件开发"

8.1 单个文件中的声明

  • 定义在前:直接调用(无需声明)。

  • 定义在后:需先声明(告诉编译器函数存在)。

    c 复制代码
    int add(int, int); // 声明(参数名可省略,只写类型)
    int main() {
        int res = add(3,5); // 调用时,编译器通过声明知道add存在
        return 0;
    }
    int add(int a, int b) { return a + b; } // 定义在后

8.2 多文件开发(模块化)

  • 步骤:

    1. 创建头文件(func.h):声明函数

      c 复制代码
      #ifndef FUNC_H
      #define FUNC_H
      int add(int a, int b); // 声明
      #endif
    2. 创建源文件(func.c):定义函数

      c 复制代码
      #include "func.h" // 包含头文件(双引号表示当前目录)
      int add(int a, int b) { return a + b; } // 定义
    3. 主文件(main.c):调用函数

      c 复制代码
      #include <stdio.h>
      #include "func.h" // 包含头文件,获取声明
      int main() {
          printf("%d\n", add(3,5)); // 调用func.c中的add
          return 0;
      }
  • 编译 :需同时编译 main.cfunc.c(如 gcc main.c func.c -o main)。

8.3 static 关键字:"限制作用域"

  • 静态函数(static 修饰函数)

    • 作用:仅当前文件可见(其他文件无法调用,避免命名冲突)。

    • 示例(func.c

      c 复制代码
      static int add(int a, int b) { return a + b; } // main.c调用会报错(未定义)
  • 静态变量

    • 静态局部变量(函数内)

      生命周期为程序运行期(保留值,如计数器)。

      c 复制代码
      void count() {
          static int num = 0; // 第一次调用初始化,后续保留值
          num++;
          printf("第%d次调用\n", num);
      }
      // 调用:count() → 第1次,count() → 第2次(num保留1)
    • 静态全局变量(文件内,函数外):仅当前文件可见(同文件内函数可访问,其他文件不可见)。

相关推荐
yeziyfx9 分钟前
kotlin中集合的用法
android·开发语言·kotlin
祁思妙想11 分钟前
【LeetCode100】--- 1.两数之和【复习回滚】
数据结构·算法·leetcode
橘颂TA19 分钟前
【C++】红黑树的底层思想 and 大厂面试常问
数据结构·c++·算法·红黑树
三体世界1 小时前
TCP传输控制层协议深入理解
linux·服务器·开发语言·网络·c++·网络协议·tcp/ip
随心点儿1 小时前
使用python 将多个docx文件合并为一个word
开发语言·python·多个word合并为一个
不学无术の码农1 小时前
《Effective Python》第十三章 测试与调试——使用 Mock 测试具有复杂依赖的代码
开发语言·python
tomcsdn311 小时前
SMTPman,smtp的端口号是多少全面解析配置
服务器·开发语言·php·smtp·邮件营销·域名邮箱·邮件服务器
<但凡.1 小时前
数据结构与算法之美:广义表
数据结构·c++·算法
EnigmaCoder1 小时前
Java多线程:核心技术与实战指南
java·开发语言
偷偷的卷3 小时前
【算法笔记 day three】滑动窗口(其他类型)
数据结构·笔记·python·学习·算法·leetcode