前言
C语言是一门高效、灵活且功能强大的计算机编程语言,它不仅是现代软件开发的基石,更是深入理解计算机系统工作原理的钥匙。从操作系统内核到嵌入式设备,从高性能服务器到游戏引擎,C语言的身影无处不在。对于零基础的学习者而言,掌握C语言意味着打开了通往底层编程世界的大门,但面对庞杂的语法和抽象的概念,许多人常常感到迷茫——从哪里开始?如何循序渐进?难点如何突破?
别担心,这份精心设计的路线图正是为你准备的!无论你是编程小白,还是有一定基础想系统巩固的开发者,本大纲将为你规划一条从零到精通的清晰路径。我们不仅涵盖核心语法,更注重思维训练与实战应用,帮助你建立扎实的编程根基,突破指针与内存管理等关键难点,最终能够独立完成实际项目。
🎯 你将学到
通过本路线图的学习,你将系统掌握以下核心内容:
- 扎实的C语言语法基础:从开发环境搭建、程序结构到数据类型、运算符、流程控制,打下坚实的编程基础。
- 深入理解指针与内存管理:攻克C语言最核心的难点,理解内存地址、指针运算、动态内存分配,避免常见内存错误。
- 复杂数据类型与底层操作能力:掌握结构体、文件操作、预处理与位运算,能够处理更复杂的数据结构和系统级任务。
- 数据结构与算法实现:亲手实现链表、栈、队列等基本数据结构,理解排序、查找等经典算法。
- 完整的项目实战经验:通过学生成绩管理系统、通讯录、计算器等实战项目,将所学知识融会贯通,培养工程化思维。
本大纲的每个阶段都配有清晰的学习目标、关键知识点和实践建议,并辅以丰富的代码示例和可视化图表(如图1所示),帮助你直观理解抽象概念。记住,学习编程没有捷径,但有了正确的路线图和持续的练习,你一定能从入门走向精通,最终用C语言创造出令人惊叹的作品!
图1:C语言学习路线总览图
第一阶段:基础语法与环境搭建 (1-2周)
1.1 开发环境配置
- 选择IDE/编辑器:Visual Studio、Code::Blocks、Dev-C++ 或 VS Code + MinGW/GCC。
- 理解编译流程:预处理 -> 编译 -> 汇编 -> 链接 -> 可执行文件。
- 第一个程序:编写、编译并运行“Hello, World!”。
1.2 程序基本结构
- 预处理指令 (#include, #define)
- 主函数 main() 及其返回值
- 语句与代码块
- 注释的使用 (//, /* */)
1.3 基本数据类型与变量
- 基本类型:int, float, double, char
- 变量:声明、定义、初始化、命名规则
- 常量:字面常量、const 常量、宏常量
- 标识符与关键字:ANSI C 32个关键字
- #include <stdio.h>
- #define PI 3.14159 // 宏常量
- int main() {
- // 基本数据类型声明与初始化
- int age = 25; // 整型变量
- float price = 99.99f; // 单精度浮点
- double pi_value = PI; // 双精度浮点,使用宏常量
- char grade = 'A'; // 字符型
-
- // const常量
- const int MAX_SIZE = 100;
-
- // 输出变量值
- printf("年龄: %d\n", age);
- printf("价格: %.2f\n", price);
- printf("PI值: %lf\n", pi_value);
- printf("等级: %c\n", grade);
- printf("最大尺寸: %d\n", MAX_SIZE);
-
- return 0;
- }
复制代码1.4 运算符与表达式
- 算术运算符:+, -, *, /, %
- 关系与逻辑运算符:>, <, ==, !=, &&, ||, !
- 位运算符:&, |, ^, ~, <<, >>
- 赋值与复合赋值运算符:=, +=, -= 等
- 自增自减:++, --
- 运算符优先级与结合性
1.5 标准输入/输出
- printf() 格式化输出
- scanf() 格式化输入
- 字符输入/输出:getchar(), putchar()
图2:C语言数据类型、变量与存储类别关系图
第二阶段:流程控制与数据结构基础 (2-3周)
2.1 流程控制
- 顺序结构
- 选择结构:
- if / else if / else
- switch / case / break / default
- 循环结构:
- for 循环
- while 循环
- do...while 循环
- 循环控制:break (跳出循环), continue (跳过本次迭代)
【选择语句方式】
【循环语句方式】
图3:C语言流程控制结构图 - #include <stdio.h>
- int main() {
- int score = 85;
-
- // 1. 选择结构示例
- printf("--- 选择结构 ---\n");
- if (score >= 90) {
- printf("成绩优秀\n");
- } else if (score >= 80) {
- printf("成绩良好\n"); // 这里会执行
- } else if (score >= 60) {
- printf("成绩及格\n");
- } else {
- printf("成绩不及格\n");
- }
-
- // switch-case 示例
- char grade = 'B';
- switch (grade) {
- case 'A': printf("优秀\n"); break;
- case 'B': printf("良好\n"); break; // 这里会执行
- case 'C': printf("及格\n"); break;
- default: printf("未知等级\n"); break;
- }
-
- // 2. 循环结构示例
- printf("\n--- 循环结构 ---\n");
-
- // for 循环:计算1-5的和
- int sum = 0;
- for (int i = 1; i <= 5; i++) {
- sum += i;
- }
- printf("1-5的和(for循环): %d\n", sum);
-
- // while 循环:计算1-5的乘积
- int product = 1;
- int j = 1;
- while (j <= 5) {
- product *= j;
- j++;
- }
- printf("1-5的积(while循环): %d\n", product);
-
- // do...while 循环:至少执行一次
- int k = 10;
- do {
- printf("do...while执行,k=%d\n", k);
- k++;
- } while (k < 5); // 条件不成立,但已执行一次
-
- // 3. break 和 continue 示例
- printf("\n--- break与continue ---\n");
- for (int i = 1; i <= 10; i++) {
- if (i == 3) {
- continue; // 跳过本次迭代,不打印3
- }
- if (i == 8) {
- break; // 跳出循环,不打印8及以后
- }
- printf("%d ", i); // 输出: 1 2 4 5 6 7
- }
- printf("\n");
-
- return 0;
- }
复制代码2.2 数组
- 一维数组:定义、初始化、遍历、越界问题
- 多维数组:二维数组(矩阵)的定义与使用
- 字符数组与字符串:
- 字符串的存储与初始化
- 基本字符串操作(手动遍历)
- 引入 常用函数:strlen, strcpy, strcat, strcmp
完整代码示例: - #include <stdio.h>
- #include <string.h> // 引入字符串处理库
- int main() {
- printf("=== 一维数组示例 ===\n");
-
- // 1. 一维数组的定义与初始化
- int numbers[5]; // 定义长度为5的整型数组(未初始化)
- int scores[5] = {85, 90, 78, 92, 88}; // 定义并初始化
- int ages[] = {18, 21, 25, 30, 22}; // 省略长度,编译器自动计算
-
- // 2. 一维数组的遍历与访问
- printf("scores数组元素:");
- for (int i = 0; i < 5; i++) {
- printf("%d ", scores[i]); // 通过下标访问数组元素
- }
- printf("\n");
-
- // 3. 数组越界问题演示(危险!)
- // printf("尝试访问越界元素:%d\n", scores[10]); // 未定义行为,可能导致程序崩溃
-
- printf("\n=== 二维数组(矩阵)示例 ===\n");
-
- // 4. 二维数组的定义与初始化
- int matrix[3][3] = {
- {1, 2, 3},
- {4, 5, 6},
- {7, 8, 9}
- };
-
- // 5. 二维数组的遍历(嵌套循环)
- printf("3x3矩阵:\n");
- for (int i = 0; i < 3; i++) {
- for (int j = 0; j < 3; j++) {
- printf("%d ", matrix[i][j]); // 访问二维数组元素
- }
- printf("\n");
- }
-
- printf("\n=== 字符数组与字符串示例 ===\n");
-
- // 6. 字符数组的定义与初始化
- char str1[20] = "Hello"; // 字符串字面量初始化
- char str2[] = {'W', 'o', 'r', 'l', 'd', '\0'}; // 字符数组初始化,手动添加结束符'\0'
- char str3[10]; // 未初始化的字符数组
-
- // 7. 字符串的基本操作(手动遍历)
- printf("手动遍历str1:");
- int len = 0;
- while (str1[len] != '\0') { // 遍历直到遇到字符串结束符
- printf("%c", str1[len]);
- len++;
- }
- printf(" (长度:%d)\n", len);
-
- // 8. 使用<string.h>库函数
- printf("\n--- 使用<string.h>库函数 ---\n");
-
- // strlen: 获取字符串长度(不包括'\0')
- printf("str1的长度:%lu\n", strlen(str1));
-
- // strcpy: 字符串复制
- strcpy(str3, "C Language"); // 将"C Language"复制到str3
- printf("复制后str3:%s\n", str3);
-
- // strcat: 字符串连接
- strcat(str1, " "); // 先添加空格
- strcat(str1, str3); // 连接str3到str1末尾
- printf("连接后str1:%s\n", str1);
-
- // strcmp: 字符串比较
- char str4[] = "apple";
- char str5[] = "banana";
- int cmp_result = strcmp(str4, str5);
- if (cmp_result < 0) {
- printf(""%s" 在字典序中小于 "%s"\n", str4, str5);
- } else if (cmp_result > 0) {
- printf(""%s" 在字典序中大于 "%s"\n", str4, str5);
- } else {
- printf(""%s" 与 "%s" 相同\n", str4, str5);
- }
-
- // 9. 字符数组与字符串的区别
- printf("\n--- 字符数组与字符串的区别 ---\n");
- char arr1[] = {'a', 'b', 'c'}; // 字符数组,没有'\0'结束符
- char arr2[] = "abc"; // 字符串,自动添加'\0'
-
- printf("字符数组arr1(无'\\0'):");
- for (int i = 0; i < 3; i++) {
- printf("%c", arr1[i]);
- }
- printf("\n");
-
- printf("字符串arr2(有'\\0'):%s\n", arr2);
- printf("arr2作为字符串的长度:%lu\n", strlen(arr2));
-
- // 注意:将arr1当作字符串使用是危险的
- // printf("错误使用arr1:%s\n", arr1); // 可能打印乱码或导致程序崩溃
-
- return 0;
- }
复制代码代码说明:
- 一维数组:展示了定义、初始化和遍历,特别注释了数组越界访问的危险性。
- 二维数组:以3x3矩阵为例,演示了二维数组的定义、初始化和嵌套循环遍历。
- 字符数组与字符串:
- 展示了字符数组的两种初始化方式
- 演示了手动遍历字符串(查找结束符\0)
- 引入了库的四个核心函数:strlen、strcpy、strcat、strcmp
- 强调了字符数组与字符串(以\0结尾的字符数组)的区别
- 关键注释:为重要代码行添加了注释,解释其作用和注意事项。
运行结果示例: - === 一维数组示例 ===
- scores数组元素:85 90 78 92 88
- === 二维数组(矩阵)示例 ===
- 3x3矩阵:
- 1 2 3
- 4 5 6
- 7 8 9
- === 字符数组与字符串示例 ===
- 手动遍历str1:Hello (长度:5)
- --- 使用<string.h>库函数 ---
- str1的长度:5
- 复制后str3:C Language
- 连接后str1:Hello C Language
- "apple" 在字典序中小于 "banana"
- --- 字符数组与字符串的区别 ---
- 字符数组arr1(无'\0'):abc
- 字符串arr2(有'\0'):abc
- arr2作为字符串的长度:3
复制代码2.3 函数与模块化编程
- 函数定义、声明与调用
- 函数参数:形参与实参,值传递
- 函数返回值
- 变量的作用域与生命周期:
- 局部变量 vs 全局变量
- 存储类别:auto, static, register, extern
- 递归函数:原理、实现与经典案例(如阶乘、斐波那契数列)
- 存储类别详解:
- auto:自动存储期。这是局部变量的默认存储类别。auto 变量在进入其所在的代码块时被创建,在退出代码块时被销毁。其生命周期仅限于该代码块内部。由于是默认类别,通常省略不写(例如 int x; 等价于 auto int x;)。
- register:建议寄存器存储。用于提示编译器将该变量存储在CPU寄存器中,以期获得更快的访问速度。但这是一个建议,编译器可以忽略。register 变量不能取地址(即不能使用 & 运算符),因为寄存器没有内存地址。在现代优化编译器下,此关键字已很少需要显式使用。
- static:静态存储期。使局部变量的生命周期延长至整个程序运行期间,但作用域仍限制在定义它的函数或文件内。在函数内定义的 static 变量只会被初始化一次,并在多次函数调用间保持其值。
- extern:外部链接。用于声明一个在其他文件中定义的全局变量或函数,表示该标识符具有外部链接,可以在多个源文件之间共享。
代码示例:
1. 百分比显示函数示例: - #include <stdio.h>
- // 函数声明
- void displayPercentage(float part, float total);
- float calculatePercentage(float part, float total);
- int main() {
- printf("=== 百分比显示函数示例 ===\n\n");
-
- // 示例1:计算并显示百分比
- float score = 85.0;
- float maxScore = 100.0;
-
- printf("示例1:考试成绩百分比\n");
- printf("得分:%.1f/%.1f\n", score, maxScore);
- displayPercentage(score, maxScore);
-
- // 示例2:计算百分比但不显示
- float budget = 5000.0;
- float spent = 3250.0;
- float percentage = calculatePercentage(spent, budget);
-
- printf("\n示例2:预算使用情况\n");
- printf("预算:¥%.2f,已花费:¥%.2f\n", budget, spent);
- printf("使用比例:%.2f%%\n", percentage);
-
- if (percentage > 100.0) {
- printf("警告:已超出预算!\n");
- } else if (percentage > 80.0) {
- printf("提示:预算使用已超过80%%,请注意控制支出。\n");
- } else {
- printf("状态:预算使用正常。\n");
- }
-
- // 示例3:处理除零错误
- printf("\n示例3:错误处理演示\n");
- displayPercentage(50.0, 0.0); // 除数为零的情况
-
- return 0;
- }
- // 函数定义:显示百分比
- // 参数:part - 部分值,total - 总值
- // 功能:计算并显示part占total的百分比
- void displayPercentage(float part, float total) {
- // 输入验证
- if (total == 0.0) {
- printf("错误:除数不能为零!\n");
- return;
- }
-
- if (part < 0 || total < 0) {
- printf("错误:数值不能为负数!\n");
- return;
- }
-
- // 计算百分比
- float percentage = (part / total) * 100.0;
-
- // 显示结果
- printf("百分比:%.2f%% (%.1f/%.1f)\n", percentage, part, total);
-
- // 附加信息
- if (percentage >= 90.0) {
- printf("评级:优秀\n");
- } else if (percentage >= 70.0) {
- printf("评级:良好\n");
- } else if (percentage >= 60.0) {
- printf("评级:及格\n");
- } else {
- printf("评级:不及格\n");
- }
- }
- // 函数定义:计算百分比
- // 参数:part - 部分值,total - 总值
- // 返回值:百分比值(0-100)
- float calculatePercentage(float part, float total) {
- // 输入验证
- if (total == 0.0) {
- printf("警告:除数为零,返回0%%\n");
- return 0.0;
- }
-
- if (part < 0 || total < 0) {
- printf("警告:数值为负数,返回0%%\n");
- return 0.0;
- }
-
- // 计算并返回百分比
- return (part / total) * 100.0;
- }
复制代码代码说明:
- 函数声明与定义:展示了函数声明(原型)和定义的完整流程
- 参数传递:displayPercentage函数接收两个float参数,演示了值传递
- 返回值:calculatePercentage函数返回float类型的百分比值
- 错误处理:包含除零检查和负数检查,提高程序健壮性
- 函数调用:在main函数中多次调用这两个函数,展示不同用法
- 格式化输出:使用printf的格式化功能,特别是%.2f%%显示两位小数的百分比
运行结果示例: - === 百分比显示函数示例 ===
- 示例1:考试成绩百分比
- 得分:85.0/100.0
- 百分比:85.00% (85.0/100.0)
- 评级:良好
- 示例2:预算使用情况
- 预算:¥5000.00,已花费:¥3250.00
- 使用比例:65.00%
- 状态:预算使用正常。
- 示例3:错误处理演示
- 错误:除数不能为零!
复制代码2. 数组累加函数示例: - #include <stdio.h>
- // 函数声明
- int sumArray(int arr[], int size);
- float averageArray(int arr[], int size);
- void printArray(int arr[], int size);
- int main() {
- printf("=== 数组累加函数示例 ===\n\n");
-
- // 示例数组
- int numbers[] = {78, 92, 85, 67, 90, 88, 76, 95, 81, 73};
- int size = sizeof(numbers) / sizeof(numbers[0]);
-
- printf("数组元素:");
- printArray(numbers, size);
-
- // 调用数组求和函数
- int total = sumArray(numbers, size);
- printf("数组元素总和:%d\n", total);
-
- // 调用数组求平均值函数
- float avg = averageArray(numbers, size);
- printf("数组元素平均值:%.2f\n", avg);
-
- // 示例2:部分数组元素求和
- printf("\n=== 部分数组求和 ===\n");
- int partialSum = sumArray(numbers, 5); // 只计算前5个元素
- printf("前5个元素的和:%d\n", partialSum);
-
- // 示例3:空数组处理
- printf("\n=== 边界情况测试 ===\n");
- int emptyArray[] = {};
- int emptySize = 0;
-
- printf("空数组求和:%d\n", sumArray(emptyArray, emptySize));
- printf("空数组平均值:%.2f\n", averageArray(emptyArray, emptySize));
-
- // 示例4:单个元素数组
- int singleArray[] = {100};
- int singleSize = 1;
-
- printf("\n单元素数组[100]:\n");
- printf("求和:%d\n", sumArray(singleArray, singleSize));
- printf("平均值:%.2f\n", averageArray(singleArray, singleSize));
-
- return 0;
- }
- // 函数定义:计算数组元素总和
- // 参数:arr - 整型数组,size - 数组大小
- // 返回值:数组所有元素的和
- int sumArray(int arr[], int size) {
- // 处理空数组
- if (size <= 0) {
- printf("提示:数组为空或大小无效\n");
- return 0;
- }
-
- int sum = 0;
- for (int i = 0; i < size; i++) {
- sum += arr[i];
- }
- return sum;
- }
- // 函数定义:计算数组平均值
- // 参数:arr - 整型数组,size - 数组大小
- // 返回值:数组元素的平均值
- float averageArray(int arr[], int size) {
- // 处理空数组
- if (size <= 0) {
- printf("提示:数组为空,无法计算平均值\n");
- return 0.0;
- }
-
- // 调用sumArray函数获取总和
- int total = sumArray(arr, size);
-
- // 计算平均值(注意类型转换)
- return (float)total / size;
- }
- // 函数定义:打印数组
- // 参数:arr - 整型数组,size - 数组大小
- void printArray(int arr[], int size) {
- if (size <= 0) {
- printf("[]\n");
- return;
- }
-
- printf("[");
- for (int i = 0; i < size; i++) {
- printf("%d", arr[i]);
- if (i < size - 1) {
- printf(", ");
- }
- }
- printf("]\n");
- }
复制代码代码说明:
- 数组作为函数参数:演示了数组作为函数参数的用法(实际上传递的是指针)
- 数组大小参数:必须传递数组大小,因为函数内无法通过sizeof获取数组元素个数
- 函数复用:averageArray函数内部调用了sumArray函数,展示了函数间的调用
- 边界情况处理:包含对空数组、单个元素数组的特殊处理
- 类型转换:在计算平均值时进行(float)类型转换,确保得到浮点数结果
- 模块化设计:将不同功能分离到不同函数中,提高代码可读性和可维护性
运行结果示例: - === 数组累加函数示例 ===
- 数组元素:[78, 92, 85, 67, 90, 88, 76, 95, 81, 73]
- 数组元素总和:825
- 数组元素平均值:82.50
- === 部分数组求和 ===
- 前5个元素的和:412
- === 边界情况测试 ===
- 提示:数组为空或大小无效
- 空数组求和:0
- 提示:数组为空,无法计算平均值
- 空数组平均值:0.00
- 单元素数组[100]:
- 求和:100
- 平均值:100.00
复制代码关键知识点总结:
- 函数声明与定义分离:良好的编程习惯是先声明函数原型,再定义函数体
- 数组参数传递:C语言中数组作为参数传递时,实际传递的是数组首元素的地址
- 错误处理:在函数开始处检查输入参数的合法性
- 代码复用:通过函数调用其他函数,避免重复代码
- 模块化思想:每个函数只负责一个明确的任务,提高代码的可测试性和可维护性数
第三阶段:核心难点突破 - 指针与内存管理 (3-4周)
图4:指针与内存管理核心概念图
3.1 指针基础
- 内存地址与指针概念
- 指针变量的定义与初始化
- 取地址运算符 (&) 与 解引用运算符 (*)
- 空指针 (NULL) 与野指针
- #include <stdio.h>
- int main() {
- int num = 42; // 普通整型变量
- int *ptr = NULL; // 指针变量声明,初始化为NULL
-
- // 1. 取地址运算符(&) - 获取变量的内存地址
- ptr = # // ptr现在存储num的地址
- printf("变量num的值: %d\n", num);
- printf("变量num的地址: %p\n", (void*)&num);
- printf("指针ptr存储的地址: %p\n", (void*)ptr);
- printf("指针ptr自己的地址: %p\n", (void*)&ptr);
-
- // 2. 解引用运算符(*) - 通过指针访问/修改变量值
- printf("\n通过ptr访问num的值: %d\n", *ptr); // 输出: 42
-
- *ptr = 100; // 通过指针修改变量值
- printf("修改后num的值: %d\n", num); // 输出: 100
- printf("修改后*ptr的值: %d\n", *ptr); // 输出: 100
-
- // 3. 指针的指针(二级指针)
- int **pptr = &ptr; // 指向指针的指针
- printf("\n二级指针pptr存储的地址: %p\n", (void*)pptr);
- printf("通过pptr访问ptr的地址: %p\n", (void*)*pptr);
- printf("通过pptr访问num的值: %d\n", **pptr); // 输出: 100
-
- // 4. 空指针与野指针示例
- int *null_ptr = NULL; // 空指针,安全的初始化
- int *wild_ptr; // 野指针,未初始化,危险!
-
- printf("\n空指针null_ptr的值: %p\n", (void*)null_ptr);
- // printf("尝试解引用空指针: %d\n", *null_ptr); // 运行时错误!
-
- // 安全检查空指针
- if (null_ptr == NULL) {
- printf("null_ptr是空指针,不能解引用\n");
- }
-
- // 野指针演示(不要在实际代码中这样做)
- // printf("野指针wild_ptr的值: %p\n", (void*)wild_ptr); // 未定义行为
- // *wild_ptr = 10; // 危险!可能导致程序崩溃
-
- return 0;
- }
复制代码3.2 指针与数组
完整代码示例:指针遍历数组与算术运算对比 - #include <stdio.h>
- int main() {
- printf("=== 一维数组的指针遍历 ===\n");
-
- // 1. 一维数组的定义与初始化
- int arr[5] = {10, 20, 30, 40, 50};
-
- // 2. 数组名与指针的关系
- printf("数组名 arr 的值(首元素地址): %p\n", (void*)arr);
- printf("&arr[0] 的值(首元素地址): %p\n", (void*)&arr[0]);
- printf("arr == &arr[0] ? %s\n", arr == &arr[0] ? "是" : "否");
-
- // 3. 通过指针遍历一维数组
- printf("\n通过指针遍历一维数组:\n");
- int *ptr = arr; // ptr指向数组首元素
-
- for (int i = 0; i < 5; i++) {
- printf("arr[%d] = %d, *(ptr + %d) = %d, *(arr + %d) = %d\n",
- i, arr[i], i, *(ptr + i), i, *(arr + i));
- }
-
- // 4. 指针算术运算(一维数组)
- printf("\n=== 指针算术运算(一维数组)===\n");
- printf("ptr 当前指向: %p, 值: %d\n", (void*)ptr, *ptr);
-
- ptr++; // 指针加1,指向下一个元素
- printf("ptr++ 后指向: %p, 值: %d\n", (void*)ptr, *ptr);
-
- ptr += 2; // 指针加2,跳过两个元素
- printf("ptr += 2 后指向: %p, 值: %d\n", (void*)ptr, *ptr);
-
- ptr--; // 指针减1,指向前一个元素
- printf("ptr-- 后指向: %p, 值: %d\n", (void*)ptr, *ptr);
-
- // 5. 数组名与指针在算术运算上的异同
- printf("\n=== 数组名与指针的异同 ===\n");
- printf("数组名 arr 的值: %p\n", (void*)arr);
- printf("指针 ptr 的值: %p\n", (void*)ptr);
-
- // 相同点:都可以用于指针算术
- printf("\n相同点 - 都可以用于指针算术:\n");
- printf("*(arr + 2) = %d\n", *(arr + 2)); // 等价于 arr[2]
- printf("*(ptr + 1) = %d\n", *(ptr + 1)); // 等价于 ptr[1]
-
- // 不同点:数组名是常量指针,不能重新赋值
- printf("\n不同点 - 数组名是常量指针:\n");
- // arr = arr + 1; // 错误!数组名不能作为左值
- ptr = arr + 1; // 正确!指针可以重新赋值
- printf("ptr = arr + 1 后,ptr指向: %p\n", (void*)ptr);
-
- // 6. 二维数组的指针遍历
- printf("\n=== 二维数组的指针遍历 ===\n");
- int matrix[3][4] = {
- {1, 2, 3, 4},
- {5, 6, 7, 8},
- {9, 10, 11, 12}
- };
-
- // 方法1:使用数组下标(传统方式)
- printf("\n方法1:使用数组下标遍历:\n");
- for (int i = 0; i < 3; i++) {
- for (int j = 0; j < 4; j++) {
- printf("%2d ", matrix[i][j]);
- }
- printf("\n");
- }
-
- // 方法2:使用指针遍历(将二维数组视为一维存储)
- printf("\n方法2:使用指针遍历(一维视角):\n");
- int *p = &matrix[0][0]; // 指向第一个元素
-
- for (int i = 0; i < 3 * 4; i++) {
- printf("%2d ", *(p + i));
- if ((i + 1) % 4 == 0) printf("\n"); // 每4个元素换行
- }
-
- // 方法3:使用指针数组方式
- printf("\n方法3:使用行指针遍历:\n");
- for (int i = 0; i < 3; i++) {
- int *row_ptr = matrix[i]; // matrix[i]是第i行的首地址
- for (int j = 0; j < 4; j++) {
- printf("%2d ", *(row_ptr + j));
- }
- printf("\n");
- }
-
- // 7. 二维数组的指针算术
- printf("\n=== 二维数组的指针算术 ===\n");
- printf("matrix 的值(首行地址): %p\n", (void*)matrix);
- printf("matrix[0] 的值(首行首元素地址): %p\n", (void*)matrix[0]);
- printf("&matrix[0][0] 的值: %p\n", (void*)&matrix[0][0]);
-
- printf("\nmatrix + 1 跳过一行(4个int): %p\n", (void*)(matrix + 1));
- printf("matrix[0] + 1 跳过一个元素: %p\n", (void*)(matrix[0] + 1));
-
- // 访问 matrix[1][2] 的多种方式
- printf("\n访问 matrix[1][2](值为7)的多种方式:\n");
- printf("matrix[1][2] = %d\n", matrix[1][2]);
- printf("*(matrix[1] + 2) = %d\n", *(matrix[1] + 2));
- printf("*(*(matrix + 1) + 2) = %d\n", *(*(matrix + 1) + 2));
-
- return 0;
- }
复制代码代码说明与运行结果分析:
-
一维数组遍历:
- 展示了三种访问方式:arr、*(ptr + i)、*(arr + i)
- 验证了 arr == &arr[0],数组名表示首元素地址
-
指针算术运算:
- ptr++:移动到下一个元素(地址增加 sizeof(int))
- ptr += 2:跳过两个元素
- ptr--:移动到前一个元素
- 指针算术的单位是类型大小,不是字节数
-
数组名与指针的异同:
- 相同点:都可以用于指针算术访问数组元素
- 不同点:数组名是常量指针,不能重新赋值;普通指针变量可以重新指向其他地址
-
二维数组遍历:
- 方法1:传统下标法 matrix[j]
- 方法2:一维指针法,将二维数组视为连续内存
- 方法3:行指针法,每行用一个指针遍历
-
二维数组指针算术:
- matrix + 1:跳过一行(4个int)
- matrix[0] + 1:跳过一个元素
- 展示了访问 matrix[1][2] 的三种等价方式
运行结果示例: - === 一维数组的指针遍历 ===
- 数组名 arr 的值(首元素地址): 0x7ffc5a3b4a10
- &arr[0] 的值(首元素地址): 0x7ffc5a3b4a10
- arr == &arr[0] ? 是
- 通过指针遍历一维数组:
- arr[0] = 10, *(ptr + 0) = 10, *(arr + 0) = 10
- arr[1] = 20, *(ptr + 1) = 20, *(arr + 1) = 20
- arr[2] = 30, *(ptr + 2) = 30, *(arr + 2) = 30
- arr[3] = 40, *(ptr + 3) = 40, *(arr + 3) = 40
- arr[4] = 50, *(ptr + 4) = 50, *(arr + 4) = 50
- === 指针算术运算(一维数组)===
- ptr 当前指向: 0x7ffc5a3b4a10, 值: 10
- ptr++ 后指向: 0x7ffc5a3b4a14, 值: 20
- ptr += 2 后指向: 0x7ffc5a3b4a1c, 值: 40
- ptr-- 后指向: 0x7ffc5a3b4a18, 值: 30
- === 数组名与指针的异同 ===
- 数组名 arr 的值: 0x7ffc5a3b4a10
- 指针 ptr 的值: 0x7ffc5a3b4a18
- 相同点 - 都可以用于指针算术:
- *(arr + 2) = 30
- *(ptr + 1) = 40
- 不同点 - 数组名是常量指针:
- ptr = arr + 1 后,ptr指向: 0x7ffc5a3b4a14
- === 二维数组的指针遍历 ===
- 方法1:使用数组下标遍历:
- 1 2 3 4
- 5 6 7 8
- 9 10 11 12
- 方法2:使用指针遍历(一维视角):
- 1 2 3 4
- 5 6 7 8
- 9 10 11 12
- 方法3:使用行指针遍历:
- 1 2 3 4
- 5 6 7 8
- 9 10 11 12
- === 二维数组的指针算术 ===
- matrix 的值(首行地址): 0x7ffc5a3b4a20
- matrix[0] 的值(首行首元素地址): 0x7ffc5a3b4a20
- &matrix[0][0] 的值: 0x7ffc5a3b4a20
- matrix + 1 跳过一行(4个int): 0x7ffc5a3b4a30
- matrix[0] + 1 跳过一个元素: 0x7ffc5a3b4a24
- 访问 matrix[1][2](值为7)的多种方式:
- matrix[1][2] = 7
- *(matrix[1] + 2) = 7
- *(*(matrix + 1) + 2) = 7
复制代码关键理解点:
- 数组名在大多数情况下会退化为指向首元素的指针
- 指针算术的步长由指针类型决定(int* 加1移动4字节)
- 二维数组在内存中是按行连续存储的
- matrix[j] 等价于 *(*(matrix + i) + j)
- 数组名是常量,不能重新赋值;指针变量可以改变指向
3.3 指针与字符串
3.4 指针与函数
- 指针作为函数参数:实现"模拟引用传递",修改实参
- 数组作为函数参数(退化为指针)
- 函数指针:定义、赋值与调用(回调函数基础)
完整代码示例:指针与函数的三种应用 - #include <stdio.h>
- #include <string.h>
- // ==================== 1. 指针作为函数参数 ====================
- // 函数声明
- void swapByValue(int a, int b); // 值传递 - 无法修改实参
- void swapByPointer(int *a, int *b); // 指针传递 - 可以修改实参
- // ==================== 2. 数组作为函数参数 ====================
- // 数组作为参数时,会退化为指针
- void printArray(int arr[], int size); // 形式1:数组表示法
- void printArrayPointer(int *arr, int size); // 形式2:指针表示法
- void modifyArray(int arr[], int size); // 修改数组元素
- // ==================== 3. 函数指针 ====================
- // 函数指针类型声明
- typedef int (*CompareFunc)(int, int); // 定义函数指针类型
- // 比较函数(用于回调)
- int compareAscending(int a, int b); // 升序比较
- int compareDescending(int a, int b); // 降序比较
- // 使用函数指针的回调函数
- void sortArray(int arr[], int size, CompareFunc compare);
- void printWithCallback(int arr[], int size, void (*printFunc)(int));
- // 回调函数示例
- void printSquare(int num); // 打印平方
- void printCube(int num); // 打印立方
- int main() {
- printf("=== 1. 指针作为函数参数 ===\n");
-
- int x = 10, y = 20;
- printf("交换前:x = %d, y = %d\n", x, y);
-
- // 1.1 值传递 - 无法修改实参
- swapByValue(x, y);
- printf("值传递交换后:x = %d, y = %d (未改变)\n", x, y);
-
- // 1.2 指针传递 - 可以修改实参
- swapByPointer(&x, &y); // 传递变量的地址
- printf("指针传递交换后:x = %d, y = %d (已交换)\n", x, y);
-
- // 1.3 返回多个值的示例
- printf("\n--- 返回多个值示例 ---\n");
- int num = 15;
- int square, cube;
-
- // 通过指针参数返回多个计算结果
- void calculatePower(int n, int *sq, int *cb) {
- *sq = n * n; // 修改指针指向的值
- *cb = n * n * n;
- }
-
- calculatePower(num, &square, &cube);
- printf("%d 的平方:%d,立方:%d\n", num, square, cube);
-
- printf("\n=== 2. 数组作为函数参数 ===\n");
-
- // 2.1 数组定义与初始化
- int numbers[] = {5, 2, 8, 1, 9, 3};
- int size = sizeof(numbers) / sizeof(numbers[0]);
-
- printf("原始数组:");
- printArray(numbers, size); // 数组表示法
-
- printf("通过指针访问:");
- printArrayPointer(numbers, size); // 指针表示法
-
- // 2.2 验证数组参数退化为指针
- printf("\n--- 验证数组参数退化为指针 ---\n");
- printf("main函数中数组大小:%lu 字节\n", sizeof(numbers));
- printf("main函数中元素个数:%d\n", size);
-
- // 在函数内部检查数组大小
- void checkArraySize(int arr[]) {
- printf("函数内数组大小:%lu 字节 (退化为指针大小)\n", sizeof(arr));
- printf("函数内通过sizeof(arr)/sizeof(arr[0])计算元素个数:%lu (错误!)\n",
- sizeof(arr) / sizeof(arr[0]));
- }
- checkArraySize(numbers);
-
- // 2.3 修改数组元素
- printf("\n修改前数组:");
- printArray(numbers, size);
-
- modifyArray(numbers, size); // 通过指针修改原数组
-
- printf("修改后数组:");
- printArray(numbers, size);
-
- printf("\n=== 3. 函数指针 ===\n");
-
- // 3.1 函数指针的基本使用
- printf("--- 函数指针基本用法 ---\n");
-
- // 声明函数指针变量
- int (*funcPtr)(int, int);
-
- // 将函数地址赋值给函数指针
- funcPtr = compareAscending;
-
- // 通过函数指针调用函数
- int result1 = funcPtr(10, 5);
- printf("compareAscending(10, 5) = %d (10 > 5 返回正数)\n", result1);
-
- // 切换函数指针指向
- funcPtr = compareDescending;
- int result2 = funcPtr(10, 5);
- printf("compareDescending(10, 5) = %d (10 > 5 返回负数)\n", result2);
-
- // 3.2 函数指针数组
- printf("\n--- 函数指针数组 ---\n");
-
- // 定义函数指针数组
- int (*operations[4])(int, int) = {
- NULL, // 占位,索引0不使用
- [1] = compareAscending, // 索引1:升序比较
- [2] = compareDescending, // 索引2:降序比较
- };
-
- // 通过索引调用不同的函数
- printf("operations[1](8, 3) = %d\n", operations[1](8, 3));
- printf("operations[2](8, 3) = %d\n", operations[2](8, 3));
-
- // 3.3 回调函数应用:排序
- printf("\n--- 回调函数:排序示例 ---\n");
-
- int data[] = {5, 2, 8, 1, 9, 3, 7, 4, 6};
- int dataSize = sizeof(data) / sizeof(data[0]);
-
- printf("原始数据:");
- printArray(data, dataSize);
-
- // 使用升序比较函数排序
- sortArray(data, dataSize, compareAscending);
- printf("升序排序后:");
- printArray(data, dataSize);
-
- // 使用降序比较函数排序
- sortArray(data, dataSize, compareDescending);
- printf("降序排序后:");
- printArray(data, dataSize);
-
- // 3.4 回调函数应用:不同的打印方式
- printf("\n--- 回调函数:不同打印方式 ---\n");
-
- int values[] = {1, 2, 3, 4, 5};
- int valuesSize = sizeof(values) / sizeof(values[0]);
-
- printf("原始值:");
- printArray(values, valuesSize);
-
- printf("平方值:");
- printWithCallback(values, valuesSize, printSquare);
-
- printf("立方值:");
- printWithCallback(values, valuesSize, printCube);
-
- // 3.5 匿名函数指针(直接传递函数名)
- printf("\n--- 直接传递函数名 ---\n");
-
- // 函数名本身就是函数指针
- sortArray(data, 5, compareAscending); // compareAscending 等价于 &compareAscending
- printf("部分数据升序排序:");
- printArray(data, 5);
-
- return 0;
- }
- // ==================== 函数定义 ====================
- // 1. 指针作为函数参数
- void swapByValue(int a, int b) {
- int temp = a;
- a = b;
- b = temp;
- // 这里的修改只影响形参,不影响实参
- }
- void swapByPointer(int *a, int *b) {
- int temp = *a; // 解引用获取a指向的值
- *a = *b; // 修改a指向的内存
- *b = temp; // 修改b指向的内存
- }
- // 2. 数组作为函数参数
- void printArray(int arr[], int size) {
- for (int i = 0; i < size; i++) {
- printf("%d ", arr[i]);
- }
- printf("\n");
- }
- void printArrayPointer(int *arr, int size) {
- for (int i = 0; i < size; i++) {
- printf("%d ", *(arr + i)); // 指针算术访问元素
- }
- printf("\n");
- }
- void modifyArray(int arr[], int size) {
- for (int i = 0; i < size; i++) {
- arr[i] = arr[i] * 2; // 修改原数组元素
- }
- }
- // 3. 函数指针相关函数
- int compareAscending(int a, int b) {
- return a - b; // 升序:a>b返回正数,a<b返回负数,a=b返回0
- }
- int compareDescending(int a, int b) {
- return b - a; // 降序:a>b返回负数,a<b返回正数,a=b返回0
- }
- // 简单的冒泡排序,使用函数指针决定排序顺序
- void sortArray(int arr[], int size, CompareFunc compare) {
- for (int i = 0; i < size - 1; i++) {
- for (int j = 0; j < size - i - 1; j++) {
- if (compare(arr[j], arr[j + 1]) > 0) {
- // 交换元素
- int temp = arr[j];
- arr[j] = arr[j + 1];
- arr[j + 1] = temp;
- }
- }
- }
- }
- void printWithCallback(int arr[], int size, void (*printFunc)(int)) {
- for (int i = 0; i < size; i++) {
- printFunc(arr[i]);
- if (i < size - 1) printf(", ");
- }
- printf("\n");
- }
- void printSquare(int num) {
- printf("%d", num * num);
- }
- void printCube(int num) {
- printf("%d", num * num * num);
- }
复制代码代码说明与运行结果分析:
1. 指针作为函数参数
- 值传递:swapByValue 函数接收的是参数的副本,修改不影响原变量
- 指针传递:swapByPointer 函数接收变量的地址,通过解引用修改原变量
- 应用场景:需要函数修改多个值或大型数据结构时使用指针参数
2. 数组作为函数参数
- 数组退化:数组作为参数时,会退化为指向首元素的指针
- 两种等价形式:int arr[] 和 int *arr 在函数参数中是等价的
- 大小信息丢失:函数内无法通过 sizeof(arr) 获取数组元素个数,需要额外传递大小参数
- 修改原数组:通过指针可以直接修改调用者的数组
3. 函数指针
- 函数指针声明:返回类型 (*指针名)(参数列表)
- typedef 简化:使用 typedef 定义函数指针类型,提高可读性
- 回调函数:将函数指针作为参数传递给其他函数,实现灵活的行为定制
- 函数指针数组:创建函数指针数组,实现类似"函数表"的功能
运行结果示例: - === 1. 指针作为函数参数 ===
- 交换前:x = 10, y = 20
- 值传递交换后:x = 10, y = 20 (未改变)
- 指针传递交换后:x = 20, y = 10 (已交换)
- --- 返回多个值示例 ---
- 15 的平方:225,立方:3375
- === 2. 数组作为函数参数 ===
- 原始数组:5 2 8 1 9 3
- 通过指针访问:5 2 8 1 9 3
- --- 验证数组参数退化为指针 ---
- main函数中数组大小:24 字节
- main函数中元素个数:6
- 函数内数组大小:8 字节 (退化为指针大小)
- 函数内通过sizeof(arr)/sizeof(arr[0])计算元素个数:2 (错误!)
- 修改前数组:5 2 8 1 9 3
- 修改后数组:10 4 16 2 18 6
- === 3. 函数指针 ===
- --- 函数指针基本用法 ---
- compareAscending(10, 5) = 5 (10 > 5 返回正数)
- compareDescending(10, 5) = -5 (10 > 5 返回负数)
- --- 函数指针数组 ---
- operations[1](8, 3) = 5
- operations[2](8, 3) = -5
- --- 回调函数:排序示例 ---
- 原始数据:5 2 8 1 9 3 7 4 6
- 升序排序后:1 2 3 4 5 6 7 8 9
- 降序排序后:9 8 7 6 5 4 3 2 1
- --- 回调函数:不同打印方式 ---
- 原始值:1 2 3 4 5
- 平方值:1, 4, 9, 16, 25
- 立方值:1, 8, 27, 64, 125
- --- 直接传递函数名 ---
- 部分数据升序排序:1 2 3 4 5
复制代码关键理解点:
- 指针参数实现"引用传递":C语言没有真正的引用传递,但通过指针可以实现类似效果
- 数组参数的本质:int arr[] 在函数参数中等价于 int *arr,都是指针
- 函数指针的灵活性:允许运行时决定调用哪个函数,是实现回调、策略模式的基础
- 回调函数的应用:排序函数通过函数指针参数决定排序规则,实现通用性
- 函数名即指针:函数名本身就是指向函数代码的指针,func 等价于 &func
3.5 动态内存管理
- 堆内存的概念
- 动态内存分配函数:malloc, calloc, realloc
- 内存释放:free
- 常见内存错误:内存泄漏、重复释放、访问已释放内存
第四阶段:复杂数据类型与底层操作 (2-3周)
4.1 结构体 (struct)
- 结构体的定义与变量声明
- 成员访问 (. 运算符)
- 结构体数组
- 结构体指针与箭头运算符 (->)
- 结构体嵌套
完整代码示例:结构体的定义与使用 - #include <stdio.h>
- #include <string.h>
- // 1. 结构体定义
- struct Student {
- int id; // 学号
- char name[50]; // 姓名
- int age; // 年龄
- float score; // 成绩
- };
- // 2. 结构体嵌套示例
- struct Date {
- int year;
- int month;
- int day;
- };
- struct Employee {
- int emp_id;
- char emp_name[50];
- struct Date hire_date; // 嵌套结构体
- float salary;
- };
- int main() {
- printf("=== 结构体基础 ===\n");
-
- // 3. 结构体变量声明与初始化
- // 方式1:声明后逐个赋值
- struct Student stu1;
- stu1.id = 1001;
- strcpy(stu1.name, "张三");
- stu1.age = 20;
- stu1.score = 85.5;
-
- // 方式2:声明时初始化
- struct Student stu2 = {1002, "李四", 21, 90.0};
-
- // 方式3:指定成员初始化(C99及以上)
- struct Student stu3 = {
- .id = 1003,
- .name = "王五",
- .score = 78.5,
- .age = 22 // 成员顺序可以打乱
- };
-
- // 4. 成员访问(点运算符)
- printf("学生1:学号=%d, 姓名=%s, 年龄=%d, 成绩=%.1f\n",
- stu1.id, stu1.name, stu1.age, stu1.score);
- printf("学生2:学号=%d, 姓名=%s, 年龄=%d, 成绩=%.1f\n",
- stu2.id, stu2.name, stu2.age, stu2.score);
- printf("学生3:学号=%d, 姓名=%s, 年龄=%d, 成绩=%.1f\n",
- stu3.id, stu3.name, stu3.age, stu3.score);
-
- // 5. 结构体数组
- printf("\n=== 结构体数组 ===\n");
- struct Student class[3] = {
- {2001, "小明", 19, 88.0},
- {2002, "小红", 20, 92.5},
- {2003, "小刚", 21, 76.0}
- };
-
- // 遍历结构体数组
- for (int i = 0; i < 3; i++) {
- printf("学生%d:%s,成绩:%.1f\n",
- class[i].id, class[i].name, class[i].score);
- }
-
- // 6. 结构体指针与箭头运算符(->)
- printf("\n=== 结构体指针 ===\n");
- struct Student *ptr_stu = &stu1; // 指向stu1的指针
-
- // 使用箭头运算符访问成员
- printf("通过指针访问:姓名=%s, 成绩=%.1f\n",
- ptr_stu->name, ptr_stu->score);
-
- // 修改指针指向的结构体成员
- ptr_stu->score = 88.0;
- printf("修改后成绩:%.1f\n", stu1.score);
-
- // 7. 结构体嵌套
- printf("\n=== 结构体嵌套 ===\n");
- struct Employee emp1 = {
- .emp_id = 5001,
- .emp_name = "赵经理",
- .hire_date = {2018, 5, 15}, // 嵌套结构体初始化
- .salary = 15000.0
- };
-
- printf("员工:%s (工号:%d)\n", emp1.emp_name, emp1.emp_id);
- printf("入职日期:%d年%d月%d日\n",
- emp1.hire_date.year, emp1.hire_date.month, emp1.hire_date.day);
- printf("月薪:%.2f元\n", emp1.salary);
-
- // 8. 结构体作为函数参数(值传递)
- printf("\n=== 结构体作为函数参数 ===\n");
-
- // 定义打印学生信息的函数
- void printStudent(struct Student s) {
- printf("函数内打印:%s,年龄:%d\n", s.name, s.age);
- }
-
- // 定义修改学生信息的函数(使用指针)
- void updateScore(struct Student *s, float new_score) {
- s->score = new_score;
- printf("已将%s的成绩更新为:%.1f\n", s->name, s->score);
- }
-
- printStudent(stu1); // 值传递
- updateScore(&stu1, 95.0); // 指针传递,可以修改原结构体
-
- // 9. 结构体大小与内存对齐
- printf("\n=== 结构体大小与内存对齐 ===\n");
- printf("struct Student 大小:%lu 字节\n", sizeof(struct Student));
- printf("struct Employee 大小:%lu 字节\n", sizeof(struct Employee));
- printf("stu1 地址:%p\n", (void*)&stu1);
- printf("stu1.id 地址:%p\n", (void*)&stu1.id);
- printf("stu1.name 地址:%p\n", (void*)&stu1.name);
- printf("stu1.age 地址:%p\n", (void*)&stu1.age);
- printf("stu1.score 地址:%p\n", (void*)&stu1.score);
-
- return 0;
- }
复制代码代码说明:
- 结构体定义:展示了如何定义包含不同类型成员的结构体。
- 变量声明与初始化:演示了三种初始化方式,包括C99的指定成员初始化。
- 成员访问:使用点运算符(.)访问结构体成员。
- 结构体数组:创建和遍历结构体数组,适合管理多个相同类型的记录。
- 结构体指针:使用箭头运算符(->)通过指针访问和修改结构体成员。
- 结构体嵌套:展示了结构体中可以包含其他结构体作为成员。
- 结构体作为函数参数:演示了值传递和指针传递的区别,指针传递可以修改原结构体。
- 内存对齐:打印结构体大小和成员地址,帮助理解内存布局。
运行结果示例: - === 结构体基础 ===
- 学生1:学号=1001, 姓名=张三, 年龄=20, 成绩=85.5
- 学生2:学号=1002, 姓名=李四, 年龄=21, 成绩=90.0
- 学生3:学号=1003, 姓名=王五, 年龄=22, 成绩=78.5
- === 结构体数组 ===
- 学生2001:小明,成绩:88.0
- 学生2002:小红,成绩:92.5
- 学生2003:小刚,成绩:76.0
- === 结构体指针 ===
- 通过指针访问:姓名=张三, 成绩=85.5
- 修改后成绩:88.0
- === 结构体嵌套 ===
- 员工:赵经理 (工号:5001)
- 入职日期:2018年5月15日
- 月薪:15000.00元
- === 结构体作为函数参数 ===
- 函数内打印:张三,年龄:20
- 已将张三的成绩更新为:95.0
- === 结构体大小与内存对齐 ===
- struct Student 大小:64 字节
- struct Employee 大小:68 字节
- stu1 地址:0x7ffc5a3b4a10
- stu1.id 地址:0x7ffc5a3b4a10
- stu1.name 地址:0x7ffc5a3b4a14
- stu1.age 地址:0x7ffc5a3b4a64
- stu1.score 地址:0x7ffc5a3b4a68
复制代码关键理解点:
- 结构体是自定义的复合数据类型,可以包含多个不同类型的成员。
- 点运算符(.)用于直接访问结构体变量成员,箭头运算符(->)用于通过指针访问。
- 结构体作为函数参数时,值传递会复制整个结构体(可能开销大),指针传递更高效。
- 结构体在内存中可能不是紧凑存储,编译器会进行内存对齐以提高访问效率。
- 结构体嵌套可以构建更复杂的数据结构,如员工信息中包含入职日期。
4.3 文件操作
- 文件指针 (FILE *)
- 文件的打开 (fopen) 与关闭 (fclose)
- 文本文件操作:fprintf, fscanf, fgets, fputs
- 二进制文件操作:fread, fwrite
- 文件定位:fseek, ftell, rewind
4.4 预处理与宏
- 宏定义 (#define):对象宏、函数宏
- 条件编译 (#ifdef, #ifndef, #if, #else, #endif)
- 文件包含 (#include)
4.5 位操作深入
第五阶段:算法、数据结构与项目实战 (4-6周+)
5.1 基础算法
- 排序算法:冒泡排序、选择排序、插入排序、快速排序(理解思想)
- 查找算法:顺序查找、二分查找
5.2 线性数据结构实现
- 链表:
- 单链表的创建、遍历、插入、删除
- 双向链表、循环链表简介
- 栈 (Stack):数组实现与链表实现
- 队列 (Queue):数组实现与链表实现
5.3 综合项目实战 (选做2-3个)
- 学生成绩管理系统:文件存储、增删改查、排序统计。
- 通讯录管理程序:结构体数组/链表存储、联系人管理。
- 简单计算器:支持多运算符、括号,理解表达式解析。
- 文本分析工具:统计文件单词数、行数、字符频率。
- 贪吃蛇/推箱子小游戏 (控制台版):综合运用数组、循环、控制。
5.4 工程化与调试
- 多文件编程:头文件 (.h) 与源文件 (.c) 的组织
- Makefile 基础:理解编译规则,自动化构建
- 调试技巧:使用 GDB 或 IDE 调试器进行断点、单步、查看变量
- 代码规范与注释:培养良好的编程习惯
图5:数据结构、项目实战与工程化工具全景图
学习建议与资源
- 核心原则:理解重于记忆,实践高于理论。
- 动手编码:每个知识点都必须伴随足够的代码练习。
- 画图理解:尤其是指针、链表、内存布局,动笔画图能极大加深理解。
- 阅读源码:阅读优秀的开源C项目(如Linux内核、Redis的部分模块)或经典教材代码。
- 善用工具:重视编译器的警告信息,熟练使用调试工具。
- 克服难点:指针和内存管理是分水岭,投入时间反复练习,切勿急躁。
- 经典教材:《C程序设计语言》(K&R)、《C Primer Plus》、《C和指针》。
|