本文最后更新于 2025-03-14,学习久了要注意休息哟

第一章 函数的概念

1.1 函数的定义

📌 核心理解

函数是 具有特定功能的独立代码模块,包含四个基本要素:

要素说明示例
函数名符合标识符规则,见名知意calculateSum
参数列表输入接口,定义接收的数据类型(int a, int b)
返回类型输出结果的数据类型(无返回值用void)double / void
函数体实现具体功能的代码块{ return a+b; }
// 典型函数定义示例
int isEven(int num) {         // 判断奇偶的函数
    return (num % 2 == 0);    // 返回布尔值结果
}

1.2 函数的作用

四大核心价值

模块化设计

✅ 将复杂程序分解为多个简单函数

✅ 例:学生管理系统 = 输入函数 + 计算函数 + 输出函数

代码复用

✅ 避免重复代码

✅ 例:多次调用sort()函数实现排序

逻辑封装

✅隐藏实现细节,暴露必要接口

✅ 例:直接使用printf()无需了解内部实现

协作开发

✅多人并行开发不同函数

1.3 函数的语法

📜 标准语法结构

// 函数声明(告诉编译器函数存在)
返回类型 函数名(参数类型列表);  

// 函数定义(具体实现)
返回类型 函数名(形参列表) {
    局部变量声明;
    执行语句;
    return 返回值;  // void函数可省略
}

🔑 关键语法点

参数传递

// 值传递示例:原始数据不受影响
void add(int x) { x = x + 1; }

// 地址传递示例:原始数据会被修改
void realAdd(int *x) { *x = *x + 1; }

返回值规则

  • 基本类型直接返回
  • 数组通过指针返回
  • 无返回值时使用void

函数调用

// 带返回值调用
int result = func(3, 5);

// 无返回值调用
func();

⚠️ 常见错误

// 错误1:缺少返回值
int sum(int a, int b) {
    int s = a + b;  // 缺少return语句
}

// 错误2:参数类型不匹配
double avg(int a, int b) {...}
avg(3.5, 4.2);  // 实际传入double类型,但形参是int

📌 本章要点总结

  1. 函数 = 功能模块 + 输入接口 + 输出结果
  2. 先声明后使用的基本原则
  3. 值传递与地址传递的本质区别
  4. return语句的正确使用方法

第二章 函数的基础

2.1 函数调用

📌 完整调用流程演示

示例代码

#include <stdio.h>

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

int main() {
    int x = 3, y = 5;
    // 函数调用
    int result = add(x, y);  
    printf("Sum: %d\n", result);
    return 0;
}

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

🎨 调用过程图解

[主函数栈帧]            [add函数栈帧]
| main()执行环境 |       | a=3, b=5  |
| x=3           | ◀──┐  |临时变量区 |
| y=5           |    │  |返回值=8  |
| result未初始化 |    └──参数传递
↓
控制权转移 → 执行add函数
                ↓
[主函数恢复执行]
result = 8

关键步骤说明

  1. 参数压栈:将x=3和y=5的值复制到add函数的参数a,b
  2. 控制转移:跳转到add函数代码段
  3. 栈帧创建:为add函数分配局部变量存储空间
  4. 执行运算:计算a+b得到8
  5. 返回值:将结果存入指定寄存器
  6. 栈帧销毁:释放add函数的局部变量空间
  7. 结果接收:主函数获取返回值存入result

2.2 参数传递

🔄 值传递 vs 地址传递

示例对比

// 值传递(复制数据)
void modifyCopy(int num) {
    num = 99;  // 只修改副本
}

// 地址传递(操作原数据)
void modifyReal(int *num) {
    *num = 99; // 修改原变量
}

int main() {
    
    int a = 10;
    
    modifyCopy(a);    // 调用后a仍为10
    modifyReal(&a);   // 调用后a变为99
    
    return 0;
}

内存变化演示

调用modifyCopy(a)时:
主函数内存     函数参数内存
a: 0x1000     num: 0x2000
| 10 |        | 10 | → 修改为99

调用modifyReal(&a)时:
主函数内存     指针操作
a: 0x1000     num存储0x1000
| 99 | ←───────通过地址修改

📦 数组参数传递规范

正确写法

// 必须显式传递数组长度
void printArray(int arr[], int size) {
    for(int i=0; i<size; i++) {  // 安全访问
        printf("%d ", arr[i]);
    }
}

int main() {
    int data[] = {1,2,3,4,5};
    printArray(data, 5);  // 同时传递数组和长度
    return 0;
}

错误示范分析

void printArrayWrong(int arr[5]) {  // 错误!实际仍视为指针
    int size = sizeof(arr)/sizeof(int); // 得到指针大小而非数组长度
    // 导致循环次数错误
}

2.3 作用域

🌐 三级作用域演示

示例代码

int global = 100;  // 全局作用域

void demo() {
    int func_var = 50;  // 函数作用域
    
    if(1) {
        int block_var = 20;  // 块作用域
        printf("Block内: %d %d %d\n", 
               global, func_var, block_vasr);  // 全部可见
    }
    
    // printf("%d", block_var); // 错误!block_var不可见
}

int main() {
    // printf("%d", func_var); // 错误!func_var不可见
    demo();
    return 0;
}

可视化作用域范围

程序内存布局:
┌──────────────┐
│ 全局变量区    │ ← global
├──────────────┤
│ 栈区         │
│ ├─demo()     │ ← func_var
│ │ └─if块     │ ← block_var
│ └─main()     │
└──────────────┘

2.4 生命周期

⏳ 三类变量对比

完整示例

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

int global_var;          // 静态存储期(自动初始化为0)

void test() {
    int auto_var = 0;    // 自动存储期
    static int static_var = 0;  // 静态存储期
    
    auto_var++;
    static_var++;
    
    int *heap_var = malloc(sizeof(int));  // 动态存储期
    *heap_var = 100;
    
    printf("auto:%d static:%d heap:%d\n", 
           auto_var, static_var, *heap_var);
           
    free(heap_var);  // 必须手动释放
}

int main() {
    test();  // auto:1 static:1 heap:100
    test();  // auto:1 static:2 heap:100
    return 0;
}

生命周期示意图

程序执行过程:
┌─────────────┐      test()第一次调用
│ global_var  │────┐
├─────────────┤    │ 创建auto_var
│ 堆内存分配   │◀─┐ │ 创建static_var
└─────────────┘  │ │
                 │ │
test()结束后:     │ │
auto_var销毁      │ │
heap_var内存释放 ←┘ │
static_var保留 ────┘

📌 本章重点总结

概念核心要点
函数调用通过栈帧实现环境隔离,严格遵循声明→调用顺序
参数传递基本类型默认值传递,数组/指针使用地址传递
作用域全局>函数>块,同名变量遵循就近原则
生命周期自动变量随栈帧销毁,静态变量持久存在,堆内存需手动管理

第三章 函数的高级应用

3.1 递归函数

📌 核心概念

  • 基线条件:递归终止条件
  • 递归条件:函数调用自身的条件
  • 执行原理:函数调用栈(后进先出)

💡 经典案例

// 阶乘计算    5 * 4 * 3 * 2 * 1 
int factorial(int n) {  
    if(n <= 1) return 1;     // 基线条件
    return n * factorial(n-1); // 递归条件
}
// n = 5  factorial(4)								(n = 5) * 24 	return 120
//		n = 4  factorial(3)						(n = 4) * 6		return 24
//			n = 3  factorial(2)				( n = 3 ) * 2	return 6
//				n = 2  factorial(1)		( n = 2 ) * 1  return 2
//					n = 1  return 1


// 斐波那契数列
int fibonacci(int n) {
    if(n <= 1) return n;
    return fibonacci(n-1) + fibonacci(n-2);
}

🚨 风险与优化

栈溢出:深度递归可能导致栈空间耗尽

优化策略

  • 尾递归优化(需编译器支持)
  • 改为迭代实现

3.2 回调函数

📌 实现原理

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

典型应用

  • 排序算法(如qsort
  • 事件驱动编程

💡 案例:通用排序

#include <stdlib.h>  

回调函数  过程
    通过传入 一个函数指针 去是实现某种功能 这种函数使用的方式叫做 回调函数

主函数
    负责调用 传入的函数指针

函数指针
    负责指向 功能函数

功能函数
    负责实现 主函数中所需的某个功能

// 比较函数原型
typedef int (*CompareFunc)(const void*, const void*);  

void sortArray(int arr[], int n, CompareFunc cmp) {
    
    qsort(arr, n, sizeof(int), cmp);
    
}

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

// 使用  
int nums[] = {5,2,7,1};  
sortArray(nums, 4, compareInt);  

3.3 可变参函数

📌 实现方法

头文件#include <stdarg.h>

关键宏

功能
va_list定义参数列表指针
va_start初始化参数列表
va_arg获取下一个参数
va_end清理参数列表

💡 案例:可变参数求和

#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, i);  
    }
    va_end(args);  
    return total;  
}  

// 调用  
int s = sum(3, 10, 20, 30);  // s=60  

⚠️ 注意事项

  • 必须明确参数类型
  • 缺乏类型安全检查

3.4 内联函数

📌 特性与使用

  • 关键字inline
  • 作用:减少函数调用开销(编译器决定是否内联)
  • 适用场景:简单且频繁调用的函数
inline int max(int a, int b) {  
    return a > b ? a : b;  
}

// 编译建议(GCC)
__attribute__((always_inline));  

🆚 与宏函数对比

特性内联函数宏函数
类型检查
调试支持支持展开后不可见
副作用可能产生意外副作用

3.5 宏函数

📌 定义与使用

#define SQUARE(x) ((x) * (x))
#define MAX(a,b) ({ \
    typeof(a) _a = (a); \
    typeof(b) _b = (b); \
    _a > _b ? _a : _b; \
})

💡 案例:泛型打印

#define PRINT_VAR(x) _Generic((x), \  
    int: printf("%d\n", x), \  
    float: printf("%f\n", x), \  
    default: printf("Unsupported type\n") \  
)

// 使用
int num = 5;
PRINT_VAR(num);

🚨 常见陷阱

错误示例问题分析修正方案
#define SQUARE(x) x*xSQUARE(3+2) → 3+2*3+2=11添加括号:(x)*(x)
多语句未用do{}while(0)宏展开后逻辑错误使用语句块包裹

3.6 命令行参数

在 C 语言中,main 函数可以接受命令行参数,使得程序可以在运行时动态获取输入参数,增强程序的灵活性。

📌 main 函数的参数格式

int main(int argc, char *argv[]);

argc:表示命令行参数的个数,至少为 1(包括程序自身的名称)。
argv:存储命令行参数的字符串数组,
    	argv[0] 是程序自身的名称
    	argv[1] 及之后的元素为用户输入的参数。

💡命令行参数的示例

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("参数个数: %d\n", argc);
    for (int i = 0; i < argc; i++) {
        printf("参数 %d: %s\n", i, argv[i]);
    }
    return 0;
}

📜字符串转换成整数

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

int main(int argc, char *argv[]) {
    if (argc < 3) {
        printf("使用方法: %s <num1> <num2>\n", argv[0]);
        return 1;
    }

    int num1 = atoi(argv[1]);  // 将字符串转换为整数
    int num2 = atoi(argv[2]);
    
    printf("两个数的和: %d\n", num1 + num2);
    return 0;
}

综合案例:日志系统

#include <stdarg.h>  

// 支持可变参数的日志函数  
void log_message(const char* format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
    printf("\n");
}

// 带调试级别的宏  
#define LOG_DEBUG(...) log_message("[DEBUG] " __VA_ARGS__)
#define LOG_ERROR(...) log_message("[ERROR] " __VA_ARGS__)

// 使用
LOG_DEBUG("用户%d登录", 1001);  
LOG_ERROR("内存分配失败!");  

🚨 高频错误总结

错误类型错误示例解决方案
递归无终止条件void f() { f(); }添加基线条件
错误函数指针类型int (*fp)(char) = &atoi类型严格匹配
宏参数多次求值MAX(++a, ++b)使用内联函数替代

📚 核心要点记忆卡

1. 递归必须包含基线条件  
2. 回调函数通过函数指针实现  
3. 可变参函数需用va_系列宏  
4. 内联函数由编译器决定是否展开  
5. 宏函数注意括号和副作用