跳转到内容

C 语言程序设计

一、C 语言概述

1.1 C 语言简介

C 语言由 Dennis Ritchie 于 1972 年在贝尔实验室开发,用于重写 UNIX 操作系统。它是面向过程的编程语言,兼具高级语言的易用性和低级语言的底层操作能力。

1.2 C 语言特点

特点

说明

高效

编译为机器码直接执行,运行效率极高

可移植

标准 C 代码可在不同平台编译运行

灵活

支持指针,可直接操作内存

结构化

函数式编程,模块化组织代码

丰富运算符

提供位运算、自增自减等底层操作

1.3 第一个 C 程序

#include <stdio.h>   // 预处理器指令:引入标准输入输出头文件

int main() {         // 主函数:程序入口
    printf("Hello, World!\n");  // 输出到控制台
    return 0;        // 返回 0 表示程序正常结束
}

1.4 C 程序编译执行流程

源文件(.c) → 预处理 → 编译 → 汇编 → 链接 → 可执行文件(.exe)

gcc hello.c -o hello    # 编译
./hello                 # 运行

二、数据类型、运算符与表达式

2.1 基本数据类型

类型

关键字

字节数

取值范围

格式符

整型

int

4

-2,147,483,648 ~ 2,147,483,647

%d

短整型

short

2

-32,768 ~ 32,767

%hd

长整型

long

4/8

平台相关

%ld

长长整型

long long

8

-2⁶³ ~ 2⁶³-1

%lld

无符号整型

unsigned int

4

0 ~ 4,294,967,295

%u

单精度浮点

float

4

±3.4×10⁻³⁸ ~ ±3.4×10³⁸

%f

双精度浮点

double

8

±1.7×10⁻³⁰⁸ ~ ±1.7×10³⁰⁸

%lf

字符型

char

1

-128 ~ 127 或 0~255

%c

布尔型

_Bool

1

0(false) / 1(true)

%d

#include <stdio.h>

int main() {
    int age = 25;
    float score = 92.5;
    double pi = 3.14159265358979;
    char grade = 'A';

    printf("年龄: %d\n", age);
    printf("分数: %.1f\n", score);
    printf("圆周率: %.10f\n", pi);
    printf("等级: %c\n", grade);

    // sizeof 查看占用字节数
    printf("int: %zu 字节\n", sizeof(int));
    printf("double: %zu 字节\n", sizeof(double));
    printf("char: %zu 字节\n", sizeof(char));

    return 0;
}

2.2 常量

// 方式一:const 常量
const int MAX = 100;
const float PI = 3.14159;

// 方式二:#define 宏常量(预处理阶段替换,无类型检查)
#define MAX 100
#define PI 3.14159
#define NEWLINE '\n'

const vs #define:

const

#define

本质

变量,有类型

文本替换,无类型

内存

占内存

不占内存

调试

可调试

不可调试(已被替换)

推荐

✅ 有类型安全

仅用于条件编译和宏

2.3 类型转换

// 自动转换(隐式):小类型 → 大类型
int a = 10;
double b = a;        // int → double,安全

// 强制转换(显式):(目标类型) 表达式
double x = 3.14;
int y = (int)x;      // double → int,截断小数部分,y = 3

// 注意:整数除法
int m = 5, n = 2;
double result1 = m / n;           // 结果 2.0(先整数除法得 2,再转 double)
double result2 = (double)m / n;   // 结果 2.5(先转 double,再除法)

2.4 运算符

算术运算符

运算符

含义

示例

+

a + b

-

a - b

*

a * b

/

a / b(整数除法截断)

%

取余

a % b(仅整数)

++

自增

a++(先用后加)/ ++a(先加后用)

--

自减

a-- / --a

关系运算符

运算符

含义

==

等于

!=

不等于

&gt;

大于

&lt;

小于

&gt;=

大于等于

&lt;=

小于等于

逻辑运算符

运算符

含义

短路特性

&&

逻辑与

左边为假则不执行右边

||

逻辑或

左边为真则不执行右边

!

逻辑非

单目运算

位运算符(C 语言特色)

运算符

含义

示例

&

按位与

a & b

|

按位或

a | b

^

按位异或

a ^ b

~

按位取反

~a

&lt;&lt;

左移

a &lt;&lt; 2(乘以 2² = 4)

&gt;&gt;

右移

a &gt;&gt; 1(除以 2)

int a = 5, b = 3;  // 5=0101, 3=0011
printf("%d\n", a & b);   // 0001 = 1
printf("%d\n", a | b);   // 0111 = 7
printf("%d\n", a ^ b);   // 0110 = 6
printf("%d\n", a << 1);  // 5*2 = 10
printf("%d\n", a >> 1);  // 5/2 = 2

运算符优先级(从高到低)

() [] -> .                    最高
! ~ ++ -- + - * & (类型) sizeof
* / %
+ -
<< >>
< <= > >=
== !=
&
^
|
&&
||
?:                            条件运算符
= += -= *= /= %= &= ...      赋值运算符(最低)

记不住就用括号!括号是最清晰的优先级声明。


三、顺序结构:输入与输出

3.1 printf — 格式化输出

int age = 20;
double score = 88.5;
char name[] = "张三";

// 基本输出
printf("年龄: %d\n", age);
printf("分数: %.1f\n", score);
printf("姓名: %s\n", name);

// 多变量输出
printf("%s 今年 %d 岁,成绩 %.1f 分\n", name, age, score);

// 格式控制详解
printf("%d\n", 42);       // 整型
printf("%5d\n", 42);      // 右对齐,占 5 位:   42
printf("%-5d\n", 42);     // 左对齐,占 5 位:42
printf("%05d\n", 42);     // 用 0 填充:00042
printf("%f\n", 3.14);     // 浮点:3.140000
printf("%.2f\n", 3.14);   // 保留 2 位小数:3.14
printf("%8.2f\n", 3.14);  // 占 8 位,2 位小数
printf("%c\n", 'A');      // 字符
printf("%s\n", "hello");  // 字符串
printf("%p\n", &age);     // 指针地址
printf("%x\n", 255);      // 十六进制:ff
printf("%o\n", 10);       // 八进制:12

3.2 scanf — 格式化输入

int age;
float score;
char name[50];

// 基本输入
printf("请输入年龄: ");
scanf("%d", &age);           // ⚠️ 变量前必须加 &(取地址)

printf("请输入分数: ");
scanf("%f", &score);

printf("请输入姓名: ");
scanf("%s", name);           // 数组名本身就是地址,不加 &
// ⚠️ scanf("%s") 遇到空格就停止,不能读带空格的字符串

printf("你输入了: %d 岁, %.1f 分, 姓名 %s\n", age, score, name);

scanf 注意事项:

// 问题:连续输入时缓冲区残留
int a;
char c;
scanf("%d", &a);    // 输入 10 回车,缓冲区留了 \n
scanf("%c", &c);    // c 读到了 \n,不是你想要的字符

// 解决:用 getchar() 吃掉换行符,或在 %c 前加空格
scanf("%d", &a);
getchar();          // 吃掉 \n
scanf("%c", &c);
// 或:scanf(" %c", &c);  // %c 前面的空格会跳过空白字符

3.3 字符输入输出

char ch;

// 读一个字符
ch = getchar();             // 从标准输入读一个字符
putchar(ch);                // 输出一个字符

// 读一行(含空格)
char str[100];
fgets(str, sizeof(str), stdin);  // 安全地读一行,包含空格
puts(str);                        // 输出字符串并换行

四、选择结构

4.1 if-else

int score = 85;

// 单分支
if (score >= 60) {
    printf("及格\n");
}

// 双分支
if (score >= 60) {
    printf("及格\n");
} else {
    printf("不及格\n");
}

// 多分支
if (score >= 90) {
    printf("优秀\n");
} else if (score >= 80) {
    printf("良好\n");
} else if (score >= 60) {
    printf("及格\n");
} else {
    printf("不及格\n");
}

// 条件表达式(三元运算符)
char* result = (score >= 60) ? "及格" : "不及格";
int max = (a > b) ? a : b;

4.2 switch-case

int day = 3;

switch (day) {
    case 1:
        printf("星期一\n");
        break;              // ⚠️ 忘记 break 会导致"穿透"
    case 2:
        printf("星期二\n");
        break;
    case 3:
        printf("星期三\n");
        break;
    case 6:
    case 7:                 // 多个 case 共享一个执行体
        printf("周末\n");
        break;
    default:
        printf("无效日期\n");
}

五、循环结构

5.1 while 循环

// 先判断,后执行(可能一次都不执行)
int i = 1;
while (i <= 10) {
    printf("%d ", i);
    i++;
}

// 死循环
while (1) {
    // 需要 break 退出
}

5.2 do-while 循环

// 先执行一次,后判断(至少执行一次)
int i = 1;
do {
    printf("%d ", i);
    i++;
} while (i <= 10);

5.3 for 循环

// for(初始化; 条件; 更新)
for (int i = 1; i <= 10; i++) {
    printf("%d ", i);
}

// 嵌套循环:打印乘法口诀表
for (int i = 1; i <= 9; i++) {
    for (int j = 1; j <= i; j++) {
        printf("%d×%d=%-2d  ", j, i, i * j);
    }
    printf("\n");
}

5.4 break 与 continue

// break:跳出整个循环
for (int i = 1; i <= 10; i++) {
    if (i == 5) break;     // 到 5 就退出循环
    printf("%d ", i);       // 输出:1 2 3 4
}

// continue:跳过本次循环,进入下一次
for (int i = 1; i <= 10; i++) {
    if (i % 2 == 0) continue;  // 跳过偶数
    printf("%d ", i);           // 输出:1 3 5 7 9
}

六、数组

6.1 一维数组

// 定义并初始化
int arr[5] = {10, 20, 30, 40, 50};
int arr2[5] = {1, 2};          // 等价 {1, 2, 0, 0, 0}
int arr3[] = {1, 2, 3, 4, 5};  // 自动推断长度

// 访问元素(下标从 0 开始)
printf("%d\n", arr[0]);         // 10(第一个)
printf("%d\n", arr[4]);         // 50(最后一个)

// 遍历数组
for (int i = 0; i < 5; i++) {
    printf("arr[%d] = %d\n", i, arr[i]);
}

// 数组名是首元素地址
printf("arr = %p\n", arr);      // 首地址
printf("&arr[0] = %p\n", &arr[0]);  // 与上面相同

数组的经典算法:

// 1. 求最大值
int arr[] = {23, 45, 12, 67, 34, 89, 21};
int n = sizeof(arr) / sizeof(arr[0]);
int max = arr[0];
for (int i = 1; i < n; i++) {
    if (arr[i] > max) max = arr[i];
}
printf("最大值: %d\n", max);

// 2. 冒泡排序
int a[] = {64, 34, 25, 12, 22, 11, 90};
n = sizeof(a) / sizeof(a[0]);
for (int i = 0; i < n - 1; i++) {
    for (int j = 0; j < n - 1 - i; j++) {
        if (a[j] > a[j + 1]) {
            int temp = a[j];
            a[j] = a[j + 1];
            a[j + 1] = temp;
        }
    }
}

// 3. 二分查找(数组必须有序)
int binarySearch(int arr[], int n, int target) {
    int left = 0, right = n - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] == target) return mid;
        else if (arr[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;  // 未找到
}

6.2 二维数组

// 定义并初始化
int matrix[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

// 访问
printf("%d\n", matrix[1][2]);    // 第 2 行第 3 列 = 7

// 遍历
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        printf("%-3d ", matrix[i][j]);
    }
    printf("\n");
}

// 二维数组在内存中是按行存储的(行优先)
// matrix[i][j] 的地址 = 首地址 + (i * 列数 + j) * sizeof(元素类型)

七、字符串

7.1 字符串的本质

C 语言没有专门的字符串类型,字符串是\0(空字符)结尾的字符数组

char str1[] = "hello";   // 等价 {'h','e','l','l','o','\0'},占 6 字节
char str2[6] = "hello";  // 显式指定长度
char str3[10] = "hello"; // 后面自动补 \0

// '\0' 是字符串结束标志
// 忘记给 \0 留空间 → 数组越界 → 未定义行为

7.2 字符串输入输出

char name[50];

// 方式一:scanf(遇空格停止)
scanf("%s", name);

// 方式二:gets(危险,不检查越界,C11 已移除)
// gets(name);  // ❌ 不要用

// 方式三:fgets(推荐,安全)
fgets(name, sizeof(name), stdin);  // 会保留末尾的 \n

// 方式四:puts 输出
puts(name);    // 自动追加换行
printf("%s\n", name);

7.3 常用字符串函数(string.h)

#include <string.h>

char s1[100] = "hello";
char s2[] = "world";
char dest[100];

// 长度
size_t len = strlen(s1);         // 5(不包含 \0)
printf("长度: %zu\n", len);

// 复制
strcpy(dest, s1);                // dest = "hello"
strncpy(dest, s2, 2);           // 复制前 2 个字符

// 拼接
strcat(s1, " ");                 // s1 = "hello "
strcat(s1, s2);                  // s1 = "hello world"
strncat(s1, s2, 3);             // 拼接前 3 个字符

// 比较
int cmp = strcmp(s1, s2);       // 0=相等, 负=s1<s2, 正=s1>s2
strncmp(s1, s2, 3);             // 比较前 3 个字符

// 查找字符
char* p = strchr(s1, 'l');      // 返回第一次出现 'l' 的位置
p = strrchr(s1, 'l');           // 最后一次出现 'l' 的位置

// 查找子串
p = strstr("hello world", "wo"); // 返回 "world" 的起始地址

// 转大小写(非标准,但常见)
#include <ctype.h>
char ch = toupper('a');          // 'A'
ch = tolower('Z');               // 'z'

7.4 字符串转换

#include <stdlib.h>

// 字符串 → 数字
int num = atoi("12345");               // 12345(atoi 无错误检测)
long lnum = atol("123456789");
double dnum = atof("3.14");

// 推荐使用 strtol / strtod(有错误检测)
char* end;
long n = strtol("12345abc", &end, 10);  // n=12345, end 指向 "abc"
if (*end != '\0') {
    printf("转换失败: 非数字字符\n");
}

7.5 手动实现字符串函数

// 手写 strlen
size_t my_strlen(const char* s) {
    size_t len = 0;
    while (*s++) len++;
    return len;
}

// 手写 strcpy
char* my_strcpy(char* dest, const char* src) {
    char* p = dest;
    while ((*dest++ = *src++));
    return p;
}

// 手写 strcmp
int my_strcmp(const char* s1, const char* s2) {
    while (*s1 && *s1 == *s2) {
        s1++; s2++;
    }
    return *(unsigned char*)s1 - *(unsigned char*)s2;
}

// 手写 strcat
char* my_strcat(char* dest, const char* src) {
    char* p = dest;
    while (*p) p++;             // 移到 dest 末尾
    while ((*p++ = *src++));    // 复制 src
    return dest;
}

八、函数

8.1 函数定义与调用

// 函数声明(告诉编译器函数存在)
int add(int a, int b);

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

// 函数调用
int result = add(3, 5);

8.2 参数传递

// ⚠️ C 语言只有值传递(传值),没有引用传递
// 实参的值被复制给形参,修改形参不影响实参

void swap_wrong(int a, int b) {    // ❌ 错误:不能交换
    int temp = a;
    a = b;
    b = temp;
}

void swap_correct(int* a, int* b) {  // ✅ 正确:通过指针
    int temp = *a;
    *a = *b;
    *b = temp;
}

int x = 10, y = 20;
swap_correct(&x, &y);  // 传地址,x=20, y=10

8.3 数组作为函数参数

// 数组作为参数时,退化为指针(丢失长度信息)
// 以下三种写法等价:
void printArray(int arr[], int n);
void printArray(int arr[10], int n);  // 10 被忽略
void printArray(int* arr, int n);

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

// 所以传数组时必须同时传长度
int a[] = {1, 2, 3, 4, 5};
int n = sizeof(a) / sizeof(a[0]);
printArray(a, n);

8.4 递归

// 阶乘
int factorial(int n) {
    if (n <= 1) return 1;           // 基线条件
    return n * factorial(n - 1);    // 递归条件
}

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

// 汉诺塔
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);
}

8.5 变量的作用域与生命周期

类型

作用域

生命周期

存储位置

默认值

局部变量

函数/块内部

函数调用期间

随机值

全局变量

整个文件

程序运行期间

静态区

0

static 局部

函数/块内部

程序运行期间(只初始化一次)

静态区

0

static 全局

当前文件

程序运行期间

静态区

0

int global = 100;          // 全局变量

void func() {
    static int count = 0;  // 静态局部变量:只初始化一次,值在调用之间保持
    count++;
    printf("调用第 %d 次\n", count);

    int local = 10;        // 局部变量:每次调用重新分配
}

九、指针(C 语言的灵魂)

9.1 什么是指针

指针就是存储内存地址的变量。

int a = 100;

int* p = &a;     // p 是指针变量,存储 a 的地址
                 // & 是取地址运算符
                 // * 在定义时表示"这是一个指针"

printf("a 的值: %d\n", a);        // 100
printf("a 的地址: %p\n", &a);     // 0x7fff5fbff83c(示例)
printf("p 的值: %p\n", p);        // 与 &a 相同
printf("p 指向的值: %d\n", *p);   // 100(解引用:读取 p 指向的值)

9.2 指针的核心操作

int a = 10, b = 20;
int* p = &a;

*p = 30;             // 通过指针修改 a 的值,a 现在是 30
p = &b;              // 让 p 指向 b
*p = 50;             // b 现在是 50

// 指针输出
printf("%d\n", *p);  // 解引用:输出 p 指向的值

// 多级指针
int** pp = &p;       // 指向指针的指针
printf("%d\n", **pp); // 50(两级解引用)

9.3 指针与数组

int arr[] = {10, 20, 30, 40, 50};
int* p = arr;          // 数组名是首元素地址,等价于 int* p = &arr[0];

// 以下四者等价
printf("%d\n", arr[2]);      // 30
printf("%d\n", *(arr + 2));  // 30
printf("%d\n", p[2]);        // 30
printf("%d\n", *(p + 2));    // 30

// 遍历数组
for (int i = 0; i < 5; i++) {
    printf("%d ", *(p + i));   // 指针偏移
}

// 指针相减:两个指针之间的元素个数
int* p1 = &arr[1];   // 指向 20
int* p2 = &arr[4];   // 指向 50
printf("相差: %ld\n", p2 - p1);  // 3

9.4 指针与字符串

// 字符串常量(指向只读内存)
char* str = "hello";           // str 指向字符串常量区,不可修改
// str[0] = 'H';               // ❌ 未定义行为!可能崩溃

// 字符数组(可修改)
char str2[] = "hello";         // 在栈上分配,可修改
str2[0] = 'H';                 // ✅ OK

// 遍历字符串
char* s = "hello";
while (*s) {
    printf("%c", *s);
    s++;                       // 指针后移
}

9.5 指针数组与数组指针

// 指针数组:数组里存的是指针
int a = 1, b = 2, c = 3;
int* ptrArr[3] = {&a, &b, &c};  // ptrArr 是数组,元素类型是 int*
printf("%d\n", *ptrArr[0]);      // 1

// 指针数组常见应用:字符串数组
char* colors[] = {"red", "green", "blue", "yellow"};
for (int i = 0; i < 4; i++) {
    printf("%s\n", colors[i]);
}

// 数组指针:指向数组的指针
int matrix[3][4];
int (*p)[4] = matrix;   // p 指向一个包含 4 个 int 的数组
// p + 1 跳过一整行(4 × sizeof(int) 字节)

9.6 函数指针

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

// 函数指针:指向函数的指针
int (*funcPtr)(int, int);  // funcPtr 是指向"返回int、参数两个int"的函数指针
funcPtr = add;
printf("%d\n", funcPtr(3, 5));  // 8

funcPtr = sub;
printf("%d\n", funcPtr(10, 3)); // 7

// 应用:回调函数
void operate(int x, int y, int (*op)(int, int)) {
    printf("结果: %d\n", op(x, y));
}
operate(10, 5, add);  // 结果: 15
operate(10, 5, sub);  // 结果: 5

9.7 动态内存分配

#include <stdlib.h>

// malloc — 分配内存(不初始化,内容是随机值)
int* p = (int*)malloc(10 * sizeof(int));  // 分配 10 个 int 的空间
if (p == NULL) {
    printf("内存分配失败\n");
    return 1;
}
for (int i = 0; i < 10; i++) {
    p[i] = i * 10;
}

// calloc — 分配并初始化为 0
int* q = (int*)calloc(10, sizeof(int));

// realloc — 调整已分配内存的大小
p = (int*)realloc(p, 20 * sizeof(int));  // 扩大到 20 个 int

// free — 释放内存(⚠️ 必须释放,否则内存泄漏)
free(p);
free(q);
p = NULL;  // 防止野指针
q = NULL;

9.8 野指针与空指针

// 野指针:指向未知/已释放内存的指针
int* p;           // 未初始化,野指针
// *p = 10;       // ❌ 危险!不知道会写到哪

// 正确的初始化方式
int* p1 = NULL;           // 空指针
int a = 10;
int* p2 = &a;             // 指向已知变量

// 使用前检查
if (p1 != NULL) {
    *p1 = 100;
}

// free 之后置 NULL
int* p3 = (int*)malloc(sizeof(int));
free(p3);
p3 = NULL;                // 防止被再次误用

十、结构体

10.1 结构体定义与使用

// 定义结构体类型
struct Student {
    int id;
    char name[50];
    int age;
    double score;
};   // ⚠️ 分号不能少

// 创建结构体变量
struct Student s1 = {1, "张三", 20, 88.5};
struct Student s2;
s2.id = 2;
strcpy(s2.name, "李四");
s2.age = 21;
s2.score = 92.0;

// 访问成员
printf("姓名: %s, 分数: %.1f\n", s1.name, s1.score);

// typedef 简化类型名
typedef struct Student Student;
Student s3 = {3, "王五", 19, 85.0};

10.2 结构体指针

Student s = {1, "张三", 20, 88.5};
Student* p = &s;

// 通过指针访问成员:-> 运算符
printf("姓名: %s\n", p->name);     // 等价于 (*p).name
printf("年龄: %d\n", p->age);
p->score = 95.0;                    // 通过指针修改

// 结构体数组与指针
Student class[3] = {
    {1, "张三", 20, 88},
    {2, "李四", 21, 92},
    {3, "王五", 19, 85}
};

Student* ptr = class;  // 指向数组首元素
for (int i = 0; i < 3; i++) {
    printf("%s: %.1f\n", (ptr + i)->name, ptr[i].score);
}

10.3 链表(结构体 + 动态内存)

// 单链表节点
typedef struct Node {
    int data;
    struct Node* next;   // 指向下一个节点
} Node;

// 创建节点
Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}

// 头插法
Node* insertHead(Node* head, int data) {
    Node* newNode = createNode(data);
    newNode->next = head;
    return newNode;
}

// 尾插法
Node* insertTail(Node* head, int data) {
    Node* newNode = createNode(data);
    if (head == NULL) return newNode;

    Node* p = head;
    while (p->next) p = p->next;
    p->next = newNode;
    return head;
}

// 遍历链表
void printList(Node* head) {
    Node* p = head;
    while (p) {
        printf("%d -> ", p->data);
        p = p->next;
    }
    printf("NULL\n");
}

// 释放链表
void freeList(Node* head) {
    while (head) {
        Node* temp = head;
        head = head->next;
        free(temp);
    }
}

十一、共用体与枚举

11.1 共用体(Union)

共用体的所有成员共享同一块内存空间,大小等于最大成员的大小。

union Data {
    int i;
    float f;
    char str[20];
};

union Data d;
d.i = 10;
printf("%d\n", d.i);    // 10

d.f = 3.14;             // 覆盖了之前的 i
printf("%f\n", d.f);    // 3.14
printf("%d\n", d.i);    // 无意义的值(已被 f 覆盖)

printf("union 大小: %zu\n", sizeof(union Data));  // 20(最大成员 str[20])

11.2 枚举(Enum)

// 默认值从 0 开始
enum Color { RED, GREEN, BLUE };
// RED=0, GREEN=1, BLUE=2

// 自定义值
enum Weekday { MON = 1, TUE, WED, THU, FRI, SAT, SUN };
// MON=1, TUE=2, ..., SUN=7

// 使用枚举
enum Color c = RED;
switch (c) {
    case RED:   printf("红色\n"); break;
    case GREEN: printf("绿色\n"); break;
    case BLUE:  printf("蓝色\n"); break;
}

十二、文件操作

12.1 文件指针与打开模式

#include <stdio.h>

// FILE* 是文件指针类型
FILE* fp;

// 打开模式
// "r"  — 只读(文件必须存在)
// "w"  — 只写(文件不存在则创建,存在则清空)
// "a"  — 追加(文件不存在则创建,存在则在末尾追加)
// "r+" — 读写(文件必须存在)
// "w+" — 读写(创建或清空)
// "a+" — 读+追加
// "rb"/"wb" — 二进制模式

fp = fopen("test.txt", "w");
if (fp == NULL) {
    printf("文件打开失败\n");
    return 1;
}

12.2 读写单个字符

// 写一个字符
fputc('A', fp);
fputc('\n', fp);

// 读一个字符
char ch = fgetc(fp);
while (ch != EOF) {
    putchar(ch);
    ch = fgetc(fp);
}

// 判断文件结束
if (feof(fp)) {
    printf("文件已读完\n");
}

12.3 读写字符串

// 写字符串
fputs("Hello, World!\n", fp);
fputs("第二行内容\n", fp);

// 读字符串(读一行,最多 n-1 个字符)
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
    printf("%s", line);  // fgets 保留 \n,不需要额外加 \n
}

12.4 格式化读写

// 写格式化数据
fprintf(fp, "姓名: %s, 年龄: %d, 分数: %.1f\n", "张三", 20, 88.5);

// 读格式化数据
char name[50];
int age;
float score;
fscanf(fp, "姓名: %s, 年龄: %d, 分数: %f\n", name, &age, &score);

// 注意:fscanf 对格式要求严格,实际项目更推荐 fgets + sscanf
char buffer[256];
if (fgets(buffer, sizeof(buffer), fp)) {
    sscanf(buffer, "姓名: %s, 年龄: %d", name, &age);
}

12.5 二进制读写

// 二进制写入
int arr[] = {10, 20, 30, 40, 50};
fwrite(arr, sizeof(int), 5, fp);

// 二进制读取
int buffer[5];
fread(buffer, sizeof(int), 5, fp);

// 结构体的二进制读写
typedef struct {
    int id;
    char name[20];
    int age;
} Student;

Student s1 = {1, "张三", 20};
fwrite(&s1, sizeof(Student), 1, fp);

Student s2;
fread(&s2, sizeof(Student), 1, fp);
printf("ID: %d, 姓名: %s, 年龄: %d\n", s2.id, s2.name, s2.age);

12.6 文件定位

// 获取当前读写位置
long pos = ftell(fp);
printf("当前位置: %ld\n", pos);

// 移动到文件开头
rewind(fp);                  // 等价于 fseek(fp, 0, SEEK_SET);

// 移动到指定位置
fseek(fp, 0, SEEK_SET);      // 文件开头
fseek(fp, 0, SEEK_END);      // 文件末尾
fseek(fp, 10, SEEK_CUR);     // 从当前位置向后移 10 字节

// 获取文件大小
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
printf("文件大小: %ld 字节\n", size);

12.7 关闭文件与错误处理

// 关闭文件(⚠️ 必须关闭)
fclose(fp);

// 检查错误
if (ferror(fp)) {
    perror("文件操作错误");  // 打印 errno 对应的错误信息
    clearerr(fp);            // 清除错误标志
}

// 缓冲区刷新(确保数据写入磁盘)
fflush(fp);

12.8 实战:学生管理系统(文件版)

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

typedef struct {
    int id;
    char name[50];
    int age;
    double score;
} Student;

void addStudent() {
    FILE* fp = fopen("students.dat", "ab");  // 二进制追加
    if (!fp) { printf("文件打开失败\n"); return; }

    Student s;
    printf("输入学号 姓名 年龄 分数: ");
    scanf("%d %s %d %lf", &s.id, s.name, &s.age, &s.score);

    fwrite(&s, sizeof(Student), 1, fp);
    fclose(fp);
    printf("添加成功\n");
}

void listStudents() {
    FILE* fp = fopen("students.dat", "rb");
    if (!fp) { printf("暂无学生数据\n"); return; }

    Student s;
    printf("学号\t姓名\t年龄\t分数\n");
    while (fread(&s, sizeof(Student), 1, fp) == 1) {
        printf("%d\t%s\t%d\t%.1f\n", s.id, s.name, s.age, s.score);
    }
    fclose(fp);
}

void searchStudent(int id) {
    FILE* fp = fopen("students.dat", "rb");
    if (!fp) { printf("暂无学生数据\n"); return; }

    Student s;
    int found = 0;
    while (fread(&s, sizeof(Student), 1, fp) == 1) {
        if (s.id == id) {
            printf("学号: %d, 姓名: %s, 年龄: %d, 分数: %.1f\n",
                   s.id, s.name, s.age, s.score);
            found = 1;
            break;
        }
    }
    if (!found) printf("未找到该学生\n");
    fclose(fp);
}

十三、预处理指令

13.1 宏定义

// 无参宏
#define PI 3.14159
#define MAX 100

// 带参宏(⚠️ 宏只是文本替换,注意括号陷阱)
#define SQUARE(x) ((x) * (x))         // 正确:加括号
// #define SQUARE(x) x * x            // 错误:SQUARE(1+2) → 1+2*1+2 = 5

#define MAX(a, b) ((a) > (b) ? (a) : (b))

// # 和 ## 运算符
#define STR(x) #x                     // 转为字符串
#define CONCAT(a, b) a##b             // 拼接

printf("%s\n", STR(hello));           // "hello"
int xy = 100;
printf("%d\n", CONCAT(x, y));         // 100

13.2 条件编译

// #ifdef / #ifndef — 是否定义了某个宏
#define DEBUG

#ifdef DEBUG
    printf("调试信息: x = %d\n", x);
#endif

// #if — 表达式条件
#if defined(WIN32)
    // Windows 特定代码
#elif defined(__linux__)
    // Linux 特定代码
#else
    // 其他平台
#endif

// 防止头文件重复包含
#ifndef _MY_HEADER_H_
#define _MY_HEADER_H_
// ... 头文件内容 ...
#endif

// 或使用 #pragma once(更简洁,但非标准)
#pragma once

13.3 #include

#include <stdio.h>       // 从系统目录搜索
#include "myheader.h"    // 先从当前目录搜索,再搜索系统目录

十四、多文件编程

14.1 头文件(.h)与源文件(.c)

项目结构:
├── main.c          # 主程序
├── student.h       # 学生模块头文件(声明)
├── student.c       # 学生模块源文件(实现)
├── utils.h         # 工具函数头文件
└── utils.c         # 工具函数源文件
// ============ student.h ============
#ifndef _STUDENT_H_
#define _STUDENT_H_

typedef struct {
    int id;
    char name[50];
    int age;
} Student;

// 函数声明(只声明,不实现)
void printStudent(const Student* s);
void inputStudent(Student* s);

#endif

// ============ student.c ============
#include <stdio.h>
#include "student.h"

void printStudent(const Student* s) {
    printf("ID: %d, 姓名: %s, 年龄: %d\n", s->id, s->name, s->age);
}

void inputStudent(Student* s) {
    printf("输入学号 姓名 年龄: ");
    scanf("%d %s %d", &s->id, s->name, &s->age);
}

// ============ main.c ============
#include "student.h"

int main() {
    Student s;
    inputStudent(&s);
    printStudent(&s);
    return 0;
}

14.2 extern 关键字

// ============ global.c ============
int globalVar = 100;       // 定义全局变量

// ============ main.c ============
extern int globalVar;      // 声明:告诉编译器变量在其他文件定义
printf("%d\n", globalVar);

14.3 static 限制作用域

// static 函数:仅在本文件可见(类似 private)
static void helper() {
    printf("这个函数只能在当前 .c 文件中使用\n");
}

// static 全局变量:仅本文件可见
static int counter = 0;

十五、常用标准库函数详解

15.1 &lt;stdio.h&gt; — 标准输入输出

printf — 格式化输出

int printf(const char* format, ...);

// 返回:成功输出的字符数,失败返回负数
int n = printf("Hello %s, you are %d years old\n", "张三", 20);
printf("输出了 %d 个字符\n", n);

// 全部格式符速查
printf("%d\n", 42);          // %d/%i: 有符号十进制整数
printf("%u\n", 42);          // %u: 无符号十进制整数
printf("%x\n", 255);         // %x: 十六进制小写 → ff
printf("%X\n", 255);         // %X: 十六进制大写 → FF
printf("%o\n", 10);          // %o: 八进制 → 12
printf("%f\n", 3.14);        // %f: 浮点数(默认6位小数)
printf("%e\n", 3.14);        // %e: 科学计数法 → 3.140000e+00
printf("%g\n", 3.14);        // %g: 自动选 %f 或 %e(较短的那个)
printf("%c\n", 'A');         // %c: 字符
printf("%s\n", "hello");     // %s: 字符串
printf("%p\n", &a);          // %p: 指针地址
printf("%%\n");              // %%: 输出百分号本身

// 修饰符
printf("%10d\n", 42);        // 最少占 10 位,右对齐
printf("%-10d\n", 42);       // 左对齐
printf("%010d\n", 42);       // 用 0 填充 → 0000000042
printf("%.2f\n", 3.14159);   // 保留 2 位小数 → 3.14
printf("%*d\n", 10, 42);     // 动态指定宽度 → 占 10 位

scanf — 格式化输入

int scanf(const char* format, ...);

// 返回:成功匹配并赋值的输入项数,失败返回 EOF
int age;
char name[50];
int n = scanf("%d %s", &age, name);  // n=2 表示成功读入两个

// ⚠️ 经典陷阱:%c 会读取缓冲区残留的换行符
scanf("%d", &age);            // 输入 20 回车,缓冲区留 \n
// scanf("%c", &ch);          // ch 会读到 \n!
scanf(" %c", &ch);            // ✅ %c 前面加空格,跳过空白字符

// %s 遇到空白字符停止,不能读带空格的字符串
// 读带空格的行使用 fgets

sprintf / sscanf — 字符串格式化

// sprintf: 把格式化内容写入字符串
char buffer[256];
sprintf(buffer, "姓名: %s, 年龄: %d, 分数: %.1f", "张三", 20, 88.5);
printf("%s\n", buffer);

// snprintf: 安全版本,指定最大写入长度(防止缓冲区溢出)
snprintf(buffer, sizeof(buffer), "姓名: %s", name);

// sscanf: 从字符串中按格式解析数据
char input[] = "张三 20 88.5";
char name[50];
int age;
float score;
sscanf(input, "%s %d %f", name, &age, &score);
// 返回成功解析的项数

fopen / fclose — 文件打开与关闭

FILE* fopen(const char* filename, const char* mode);
int fclose(FILE* stream);

// 模式:
// "r"  只读,文件必须存在
// "w"  只写,创建或清空
// "a"  追加,创建或不覆盖
// "r+" 读写,文件必须存在
// "w+" 读写,创建或清空
// "a+" 读+追加
// 加 "b" 为二进制模式:"rb" "wb" "ab" "r+b"

FILE* fp = fopen("data.txt", "r");
if (fp == NULL) {
    perror("打开文件失败");  // 打印系统错误原因
    return 1;
}
// ... 操作文件 ...
fclose(fp);  // ⚠️ 必须关闭!

fgets / fputs — 行读写

// fgets: 读一行(保留 \n,最多 n-1 个字符,安全)
char* fgets(char* str, int n, FILE* stream);
// 返回 str(成功)或 NULL(文件结束/错误)

char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
    // line 包含 \n,如不需要可去掉
    line[strcspn(line, "\n")] = '\0';  // 去掉末尾换行
    printf("读到: %s\n", line);
}

// fputs: 写一个字符串(不会自动加 \n)
int fputs(const char* str, FILE* stream);
fputs("Hello\n", fp);

fread / fwrite — 二进制块读写

// fwrite: 写数据块
size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
// 参数: 数据地址, 每个元素大小, 元素个数, 文件指针
// 返回: 实际写入的元素个数

int arr[] = {10, 20, 30, 40, 50};
size_t written = fwrite(arr, sizeof(int), 5, fp);
// written == 5 表示全部写入成功

// fread: 读数据块
size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
// 返回: 实际读取的元素个数

int buffer[5];
size_t read = fread(buffer, sizeof(int), 5, fp);
// read 可能小于 5(文件不够长或出错)

// ⚠️ 判断 fread 是否成功应该检查返回值,不要用 feof

feof / ferror / clearerr — 状态判断

// feof: 文件是否读到末尾
if (feof(fp)) printf("文件读完了\n");

// ferror: 是否发生读写错误
if (ferror(fp)) printf("读写出错\n");

// clearerr: 清除错误标志和 EOF 标志
clearerr(fp);

fseek / ftell / rewind — 文件定位

// fseek: 移动文件位置指针
int fseek(FILE* stream, long offset, int origin);
// origin: SEEK_SET(文件头) SEEK_CUR(当前位置) SEEK_END(文件尾)
fseek(fp, 0, SEEK_SET);    // 回到文件开头
fseek(fp, 100, SEEK_SET);  // 跳到第 100 字节
fseek(fp, 0, SEEK_END);    // 跳到文件末尾
fseek(fp, -10, SEEK_CUR);  // 回退 10 字节

// ftell: 获取当前文件位置(距文件开头的字节数)
long pos = ftell(fp);

// rewind: 回到文件开头(等价于 fseek(fp, 0, SEEK_SET))
rewind(fp);

// 获取文件大小
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
printf("文件大小: %ld 字节\n", size);

remove / rename — 文件操作

// 删除文件
if (remove("old.txt") == 0) {
    printf("文件已删除\n");
}

// 重命名文件
if (rename("old.txt", "new.txt") == 0) {
    printf("重命名成功\n");
}

fflush — 刷新缓冲区

// 将输出缓冲区的内容强制写入文件/终端
fflush(fp);     // 刷新指定文件
fflush(stdout); // 刷新标准输出(确保 printf 内容立即显示)
fflush(NULL);   // 刷新所有打开的输出流

tmpfile / tmpnam — 临时文件

// 创建临时文件(关闭时自动删除)
FILE* tmp = tmpfile();  // wb+ 模式
fprintf(tmp, "临时数据\n");
// fclose(tmp) 时文件自动删除

// 生成不重复的临时文件名
char name[L_tmpnam];
tmpnam(name);

15.2 &lt;string.h&gt; — 字符串与内存操作

strlen — 字符串长度

size_t strlen(const char* str);
// 返回 \0 之前的字符数(不包含 \0)
printf("%zu\n", strlen("hello"));  // 5
printf("%zu\n", strlen(""));       // 0

// ⚠️ strlen 从给定地址开始一直数到 \0,如果字符串没有 \0 → 越界!

strcpy / strncpy — 字符串复制

// strcpy: 把 src 复制到 dest(包括 \0)
char* strcpy(char* dest, const char* src);
char dest[50];
strcpy(dest, "hello");
// ⚠️ dest 必须足够大,否则缓冲区溢出!

// strncpy: 安全版,最多复制 n 个字符
char* strncpy(char* dest, const char* src, size_t n);
strncpy(dest, src, sizeof(dest) - 1);  // 预留 \0 的位置
dest[sizeof(dest) - 1] = '\0';         // 手动确保 \0
// ⚠️ 如果 src 长度 >= n,dest 不会自动加 \0!

strcat / strncat — 字符串拼接

// strcat: 把 src 追加到 dest 末尾(覆盖 dest 的 \0,最后加 \0)
char* strcat(char* dest, const char* src);
char s[50] = "Hello ";
strcat(s, "World");  // s = "Hello World"
// ⚠️ dest 必须有足够空间!

// strncat: 安全版,最多追加 n 个字符(会自动加 \0)
char* strncat(char* dest, const char* src, size_t n);
char s[20] = "Hello ";
strncat(s, "World!!!", 5);   // 只追加 "World" 5 个字符

strcmp / strncmp — 字符串比较

// strcmp: 逐字符比较(按 ASCII 码)
int strcmp(const char* s1, const char* s2);
// 返回值: 0(s1==s2)  <0(s1<s2)  >0(s1>s2)

strcmp("abc", "abc");  // 0
strcmp("abc", "abd");  // <0 ('c' < 'd')
strcmp("abc", "ABC");  // >0 ('a' > 'A') 小写字母 > 大写字母

// strncmp: 只比较前 n 个字符
strncmp("abc123", "abc456", 3);  // 0(前 3 个相同)

// ⚠️ 不能直接用 == 比较字符串!
// if (s1 == s2) 是比较指针地址,不是比较内容

strchr / strrchr — 字符查找

// strchr: 从左向右查找字符第一次出现的位置
char* strchr(const char* str, int c);
char* p = strchr("hello", 'l');   // 指向第一个 'l'
printf("位置: %ld\n", p - str);    // 2
if (strchr("hello", 'x') == NULL) printf("未找到\n");

// strrchr: 从右向左查找字符最后一次出现的位置
char* p = strrchr("hello", 'l');  // 指向最后一个 'l'
printf("位置: %ld\n", p - str);    // 3

strstr — 子串查找

// strstr: 在 haystack 中查找 needle 子串首次出现的位置
char* strstr(const char* haystack, const char* needle);
char* p = strstr("hello world", "world");
if (p) {
    printf("找到: %s\n", p);  // "world"
    printf("位置: %ld\n", p - haystack);  // 6
}

// 常见用法:判断字符串是否包含某子串
if (strstr(url, "https://")) {
    printf("是 HTTPS 链接\n");
}

strtok — 字符串分割

// strtok: 按分隔符切割字符串(⚠️ 会修改原字符串!)
char* strtok(char* str, const char* delimiters);
// 第一次调用传 str,后续调用传 NULL 继续切割同一个字符串

char s[] = "张三,20,北京,13800000001";  // ⚠️ 必须是可修改的 char 数组
char* token = strtok(s, ",");
while (token != NULL) {
    printf("%s\n", token);
    token = strtok(NULL, ",");
}
// 输出: 张三 20 北京 13800000001

// ⚠️ strtok 不是线程安全的(用静态变量保存状态),多线程用 strtok_r

strspn / strcspn — 字符集匹配

// strspn: 开头连续匹配字符集的最大长度
strspn("123abc", "0123456789");  // 3(开头 3 个是数字)

// strcspn: 开头连续不匹配字符集的最大长度
strcspn("hello world", " ");     // 5(第一个空格前有 5 个字符)

// 常见用法:去掉字符串末尾的换行
line[strcspn(line, "\n")] = '\0';

strerror — 错误码转字符串

#include <string.h>
#include <errno.h>

FILE* fp = fopen("notexist.txt", "r");
if (fp == NULL) {
    printf("错误: %s\n", strerror(errno));  // "No such file or directory"
}
// perror 等价效果,直接打印到 stderr

memcpy — 内存复制

// memcpy: 从 src 复制 n 个字节到 dest
void* memcpy(void* dest, const void* src, size_t n);
// ⚠️ src 和 dest 不能重叠(重叠用 memmove)
// ⚠️ 不检查 \0,严格复制 n 个字节

int src[5] = {1, 2, 3, 4, 5};
int dest[5];
memcpy(dest, src, sizeof(src));  // 复制整个数组

// 复制结构体
struct Student s1 = {1, "张三", 20}, s2;
memcpy(&s2, &s1, sizeof(struct Student));

memmove — 安全内存复制(支持重叠)

// memmove: 和 memcpy 类似,但支持 src 和 dest 重叠
void* memmove(void* dest, const void* src, size_t n);
// 原理:先判断重叠方向,选择从前或从后复制

char s[] = "abcdefg";
memmove(s + 2, s, 4);  // s = "ababcdg"
// 如果 src 和 dest 可能重叠,优先用 memmove

memset — 内存填充

// memset: 将 n 个字节都设置为 c
void* memset(void* str, int c, size_t n);

// 将数组全部清零
int arr[100];
memset(arr, 0, sizeof(arr));

// 将字符数组初始化为特定字符
char s[50];
memset(s, '-', sizeof(s) - 1);
s[49] = '\0';  // "- - - - ..."

// ⚠️ memset 按字节赋值!非 char 类型小心:
int arr2[10];
memset(arr2, 0, sizeof(arr2));   // 每个字节都是 0,int 也为 0 → ✅
memset(arr2, 1, sizeof(arr2));   // 每个字节都是 1,int = 0x01010101 → ❌ 不是 1!

memcmp — 内存比较

// memcmp: 逐字节比较 n 个字节
int memcmp(const void* s1, const void* s2, size_t n);
// 返回值同 strcmp

int a[] = {1, 2, 3}, b[] = {1, 2, 4};
if (memcmp(a, b, sizeof(a)) < 0) {
    printf("a < b\n");
}

15.3 &lt;stdlib.h&gt; — 通用工具

malloc / calloc / realloc / free — 动态内存

// malloc: 分配 size 字节(不初始化,内容是随机值)
void* malloc(size_t size);
int* p = (int*)malloc(100 * sizeof(int));  // 100 个 int

// calloc: 分配 count × size 字节并初始化为 0
void* calloc(size_t count, size_t size);
int* q = (int*)calloc(100, sizeof(int));   // 100 个 int,全是 0

// realloc: 调整已分配内存的大小
void* realloc(void* ptr, size_t new_size);
// 可能原地扩展,也可能移到新地址并复制旧数据
int* tmp = (int*)realloc(p, 200 * sizeof(int));
if (tmp != NULL) p = tmp;  // ⚠️ 不要直接 p = realloc(p, ...)

// free: 释放动态分配的内存
void free(void* ptr);
free(p);
p = NULL;  // 防止野指针

atoi / atol / atof — 字符串转数字

// atoi: 字符串 → int(无错误检测,转换失败返回 0)
int a = atoi("12345");    // 12345
int b = atoi("  -42");    // -42(自动跳过前导空白)
int c = atoi("42abc");    // 42(遇到非数字停止)
int d = atoi("abc");      // 0(无法转换)

// atol: 字符串 → long
// atof: 字符串 → double
double x = atof("3.14159");  // 3.14159

strtol / strtod — 字符串转数字(带错误检测,推荐)

// strtol: 字符串 → long,带错误检测
long strtol(const char* str, char** endptr, int base);
// base: 进制(2-36),0 表示自动检测(0x 开头为 16 进制,0 开头为 8 进制)

char* end;
long n = strtol("12345abc", &end, 10);
printf("数字: %ld\n", n);     // 12345
printf("剩余: %s\n", end);    // "abc"(第一个非数字字符的位置)

if (*end != '\0') {
    printf("警告: 存在非数字字符\n");
}

// strtod: 字符串 → double,同理
char* end2;
double x = strtod("3.14abc", &end2);

rand / srand — 随机数

// rand: 生成伪随机数(0 到 RAND_MAX,通常 32767)
int r = rand();
int dice = rand() % 6 + 1;    // 1-6

// srand: 设定随机种子(同样的种子 → 同样的随机序列)
srand(time(NULL));             // 用当前时间做种子(每次运行不同)
srand(42);                     // 固定种子(可复现的随机)

// 生成指定范围的随机数
int n = rand() % (max - min + 1) + min;  // [min, max]

exit / atexit / abort — 程序终止

// exit: 正常终止程序,会执行 atexit 注册的函数 + 刷新缓冲区
exit(0);         // 0 表示成功
exit(EXIT_SUCCESS);
exit(EXIT_FAILURE);

// atexit: 注册程序退出时执行的清理函数
void cleanup() {
    printf("程序退出,执行清理...\n");
}
atexit(cleanup);  // 在 main 中注册,exit 时调用

// abort: 异常终止程序(不执行 atexit 函数,可能生成 core dump)
abort();
// 通常用于不可恢复的错误

system — 执行系统命令

int ret = system("ls -la");     // Linux 下执行命令
int ret = system("dir");        // Windows 下执行命令
int ret = system("mkdir test"); // 创建目录
// 返回命令退出状态,失败返回 -1

qsort — 快速排序

void qsort(void* base, size_t count, size_t size,
           int (*compar)(const void*, const void*));

// 比较函数:返回负数(a<b) / 0(a==b) / 正数(a>b)
int cmpInt(const void* a, const void* b) {
    return *(int*)a - *(int*)b;
}

int arr[] = {34, 12, 56, 78, 23, 9, 45};
int n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(int), cmpInt);

// 结构体排序:按分数降序
typedef struct { char name[50]; int score; } Stud;
int cmpScore(const void* a, const void* b) {
    return ((Stud*)b)->score - ((Stud*)a)->score;  // 降序
}

Stud class[5] = {{"张三",88}, {"李四",92}, {"王五",76}};
qsort(class, 3, sizeof(Stud), cmpScore);

bsearch — 二分查找

void* bsearch(const void* key, const void* base, size_t count,
              size_t size, int (*compar)(const void*, const void*));
// ⚠️ 数组必须已排序!

int arr[] = {9, 12, 23, 34, 45, 56, 78};
int n = 7, key = 34;
int* found = (int*)bsearch(&key, arr, n, sizeof(int), cmpInt);
if (found) printf("找到: %d\n", *found);

15.4 &lt;math.h&gt; — 数学函数

#include <math.h>
// 编译时需要链接数学库:gcc prog.c -lm

// ===== 基本运算 =====
double sqrt(double x);           // 平方根: sqrt(16) = 4.0
double pow(double x, double y);  // x 的 y 次幂: pow(2, 10) = 1024.0
double fabs(double x);           // 绝对值: fabs(-3.14) = 3.14
double fmod(double x, double y); // 浮点取余: fmod(7.5, 2.0) = 1.5

// ===== 三角函数(参数为弧度) =====
double sin(double x);    // 正弦
double cos(double x);    // 余弦
double tan(double x);    // 正切
double asin(double x);   // 反正弦
double acos(double x);   // 反余弦
double atan(double x);   // 反正切
double atan2(double y, double x); // 反正切(y/x),能判断象限

// ===== 指数与对数 =====
double exp(double x);    // e 的 x 次幂: exp(1) = e ≈ 2.718
double log(double x);    // 自然对数 ln: log(e) = 1.0
double log10(double x);  // 以 10 为底: log10(100) = 2.0

// ===== 取整 =====
double ceil(double x);   // 向上取整: ceil(3.14) = 4.0, ceil(-3.14) = -3.0
double floor(double x);  // 向下取整: floor(3.14) = 3.0, floor(-3.14) = -4.0
double round(double x);  // 四舍五入: round(3.6) = 4.0, round(3.4) = 3.0
double trunc(double x);  // 向 0 取整: trunc(3.9) = 3.0, trunc(-3.9) = -3.0

// ===== 实用示例 =====
double radius = 5.0;
double area = M_PI * pow(radius, 2);   // 圆面积
double hypotenuse = sqrt(pow(3, 2) + pow(4, 2)); // 斜边:5.0

// 把角度转为弧度
double radians = 30.0 * M_PI / 180.0;
printf("sin(30°) = %f\n", sin(radians));  // 0.5

15.5 &lt;ctype.h&gt; — 字符处理

#include <ctype.h>

// ===== 字符分类(返回非 0 = 真,0 = 假) =====
isalpha(c);   // 是否字母(a-z, A-Z)
isdigit(c);   // 是否数字(0-9)
isalnum(c);   // 是否字母或数字
isspace(c);   // 是否空白字符(空格、\t、\n、\r、\f、\v)
isupper(c);   // 是否大写字母
islower(c);   // 是否小写字母
isxdigit(c);  // 是否十六进制数字(0-9, a-f, A-F)
ispunct(c);   // 是否标点符号
isprint(c);   // 是否可打印字符(含空格)
isgraph(c);   // 是否可打印字符(不含空格)
iscntrl(c);   // 是否控制字符

// ===== 字符转换 =====
char ch = tolower('A');   // 'a'
ch = toupper('a');        // 'A'

// ===== 实战:统计字符串中各类型字符数 =====
char* s = "Hello, 2024!";
int letters = 0, digits = 0, spaces = 0;
for (int i = 0; s[i]; i++) {
    if (isalpha(s[i])) letters++;
    else if (isdigit(s[i])) digits++;
    else if (isspace(s[i])) spaces++;
}
printf("字母:%d 数字:%d 空格:%d\n", letters, digits, spaces);
// 字母:5 数字:4 空格:1

15.6 &lt;time.h&gt; — 时间处理

#include <time.h>

// time: 获取当前时间戳(从 1970-01-01 00:00:00 UTC 起的秒数)
time_t now = time(NULL);
printf("Unix 时间戳: %ld\n", now);

// ctime: 时间戳 → 可读字符串
printf("当前时间: %s", ctime(&now));  // 自带 \n

// difftime: 计算两个时间之差(秒)
time_t start = time(NULL);
// ... 执行一些操作 ...
time_t end = time(NULL);
printf("耗时: %.0f 秒\n", difftime(end, start));

// clock: 程序运行时钟(更精确,单位:clock_t 滴答数)
clock_t c1 = clock();
// ... 操作 ...
clock_t c2 = clock();
double cpu_time = (double)(c2 - c1) / CLOCKS_PER_SEC;
printf("CPU 时间: %f 秒\n", cpu_time);

// ===== 时间格式化 =====
time_t t = time(NULL);
struct tm* local = localtime(&t);  // 转为本地时间

printf("%d-%02d-%02d %02d:%02d:%02d\n",
    local->tm_year + 1900,  // 年:从 1900 起算
    local->tm_mon + 1,      // 月:0-11
    local->tm_mday,         // 日:1-31
    local->tm_hour,         // 时:0-23
    local->tm_min,          // 分:0-59
    local->tm_sec);         // 秒:0-59

// strftime: 自定义格式化
char buf[100];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", local);
printf("%s\n", buf);  // "2026-05-20 14:30:00"

// 常用格式符:
// %Y: 四位年份  %y: 两位年份  %m: 月份(01-12)
// %d: 日期(01-31)  %H: 时(00-23)  %M: 分(00-59)  %S: 秒(00-59)
// %A: 星期全称  %a: 星期缩写  %B: 月份全称  %b: 月份缩写

15.7 &lt;stdlib.h&gt; 补充 — 环境变量与进程

// getenv: 获取环境变量的值
char* home = getenv("HOME");    // Linux
char* home = getenv("USERPROFILE"); // Windows
if (home) printf("Home: %s\n", home);

// system: 执行 shell 命令
int status = system("ping -c 1 baidu.com");

// abs / labs: 整数绝对值(在 <stdlib.h> 中)
int a = abs(-42);           // 42
long b = labs(-123456789L); // 123456789

15.8 &lt;assert.h&gt; — 调试断言

#include <assert.h>

// assert: 表达式为假时终止程序并打印诊断信息
int divide(int a, int b) {
    assert(b != 0);  // b 为 0 时程序终止
    return a / b;
}

// 在 release 版本中禁用断言:编译时加 -DNDEBUG
// gcc -DNDEBUG prog.c  → 所有 assert 被忽略

15.9 &lt;limits.h&gt;&lt;float.h&gt; — 类型边界

#include <limits.h>
#include <float.h>

// 整型范围
printf("INT_MAX  = %d\n", INT_MAX);    // 2147483647
printf("INT_MIN  = %d\n", INT_MIN);    // -2147483648
printf("UINT_MAX = %u\n", UINT_MAX);   // 4294967295
printf("LLONG_MAX = %lld\n", LLONG_MAX);

// 浮点范围
printf("float  范围: %e ~ %e\n", FLT_MIN, FLT_MAX);
printf("double 范围: %e ~ %e\n", DBL_MIN, DBL_MAX);
printf("float  精度: %d 位十进制\n", FLT_DIG);    // 6
printf("double 精度: %d 位十进制\n", DBL_DIG);    // 15

15.10 &lt;stdarg.h&gt; — 可变参数

#include <stdarg.h>

// 实现一个求和的变参函数
int sum(int count, ...) {
    va_list args;
    va_start(args, count);  // 初始化,count 是最后一个固定参数

    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);  // 每次取出一个 int
    }

    va_end(args);  // 清理
    return total;
}

int result = sum(5, 10, 20, 30, 40, 50);  // 150

15.11 &lt;setjmp.h&gt; — 非局部跳转

#include <setjmp.h>

jmp_buf env;

void func() {
    printf("func 中发生错误,跳回去\n");
    longjmp(env, 1);  // 跳回 setjmp 处,返回值为 1
}

int main() {
    if (setjmp(env) == 0) {
        printf("正常执行\n");
        func();          // 这里调用
    } else {
        printf("从 longjmp 跳回来的\n");
    }
    return 0;
}
// 输出:
// 正常执行
// func 中发生错误,跳回去
// 从 longjmp 跳回来的

15.12 函数手册速查表

头文件

函数

功能

返回值

stdio.h

printf

格式化输出

字符数

stdio.h

scanf

格式化输入

成功匹配数

stdio.h

sprintf

格式化写入字符串

字符数

stdio.h

sscanf

从字符串格式化读取

成功匹配数

stdio.h

fopen

打开文件

FILE* / NULL

stdio.h

fclose

关闭文件

0 / EOF

stdio.h

fgets

读一行

char* / NULL

stdio.h

fread

读数据块

读取的块数

stdio.h

fwrite

写数据块

写入的块数

stdio.h

fseek

文件定位

0 / 非 0

stdio.h

ftell

获取文件位置

字节偏移 / -1L

string.h

strlen

字符串长度

字符数

string.h

strcpy

字符串复制

dest 指针

string.h

strcmp

字符串比较

0 / &lt;0 / &gt;0

string.h

strcat

字符串拼接

dest 指针

string.h

strchr

查找字符

位置指针 / NULL

string.h

strstr

查找子串

位置指针 / NULL

string.h

strtok

字符串分割

token 指针 / NULL

string.h

memcpy

内存复制

dest 指针

string.h

memset

内存填充

str 指针

string.h

memcmp

内存比较

0 / &lt;0 / &gt;0

string.h

memmove

安全内存复制

dest 指针

stdlib.h

malloc

分配内存

指针 / NULL

stdlib.h

free

释放内存

stdlib.h

atoi

字符串→int

转换结果

stdlib.h

strtol

字符串→long(安全)

转换结果

stdlib.h

rand

生成随机数

0 ~ RAND_MAX

stdlib.h

qsort

快速排序

stdlib.h

bsearch

二分查找

指针 / NULL

stdlib.h

exit

终止程序

无返回

math.h

sqrt

平方根

double

math.h

pow

幂运算

double

math.h

ceil

向上取整

double

math.h

floor

向下取整

double

ctype.h

isalpha

是否字母

非 0 / 0

ctype.h

isdigit

是否数字

非 0 / 0

time.h

time

获取时间戳

time_t

time.h

strftime

格式化时间

写入字符数

assert.h

assert

断言

无(假则终止)


十六、C 语言知识体系总结

C 语言知识体系
│
├── 基础语法
│   ├── 数据类型(int/float/double/char)
│   ├── 常量与变量
│   ├── 运算符与表达式
│   └── 类型转换
│
├── 程序结构
│   ├── 顺序结构(输入输出)
│   ├── 选择结构(if-else/switch)
│   └── 循环结构(while/do-while/for)
│
├── 数组
│   ├── 一维数组
│   ├── 二维数组
│   └── 字符数组 = 字符串
│
├── 函数
│   ├── 定义与调用
│   ├── 参数传递(值传递)
│   ├── 递归
│   └── 作用域与生命周期
│
├── 指针 ★★★(核心难点)
│   ├── 指针与变量
│   ├── 指针与数组
│   ├── 指针与字符串
│   ├── 指针与函数
│   ├── 多级指针
│   ├── 指针数组 vs 数组指针
│   ├── 函数指针
│   └── 动态内存分配
│
├── 构造类型
│   ├── 结构体(struct)
│   ├── 共用体(union)
│   └── 枚举(enum)
│
├── 链表(结构体 + 指针综合应用)
│
├── 文件操作
│   ├── 文本文件读写
│   └── 二进制文件读写
│
├── 预处理
│   ├── 宏定义(#define)
│   ├── 条件编译(#ifdef)
│   └── 文件包含(#include)
│
└── 多文件编程
    ├── 头文件与源文件分离
    ├── extern 外部声明
    └── static 文件内作用域