[C.C++] C语言入门:从零到精通的系统化学习路径

42 0
Honkers 昨天 21:26 来自手机 | 显示全部楼层 |阅读模式

前言

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个关键字
  1. #include <stdio.h>
  2. #define PI 3.14159 // 宏常量
  3. int main() {
  4. // 基本数据类型声明与初始化
  5. int age = 25; // 整型变量
  6. float price = 99.99f; // 单精度浮点
  7. double pi_value = PI; // 双精度浮点,使用宏常量
  8. char grade = 'A'; // 字符型
  9. // const常量
  10. const int MAX_SIZE = 100;
  11. // 输出变量值
  12. printf("年龄: %d\n", age);
  13. printf("价格: %.2f\n", price);
  14. printf("PI值: %lf\n", pi_value);
  15. printf("等级: %c\n", grade);
  16. printf("最大尺寸: %d\n", MAX_SIZE);
  17. return 0;
  18. }
复制代码

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语言流程控制结构图

  1. #include <stdio.h>
  2. int main() {
  3. int score = 85;
  4. // 1. 选择结构示例
  5. printf("--- 选择结构 ---\n");
  6. if (score >= 90) {
  7. printf("成绩优秀\n");
  8. } else if (score >= 80) {
  9. printf("成绩良好\n"); // 这里会执行
  10. } else if (score >= 60) {
  11. printf("成绩及格\n");
  12. } else {
  13. printf("成绩不及格\n");
  14. }
  15. // switch-case 示例
  16. char grade = 'B';
  17. switch (grade) {
  18. case 'A': printf("优秀\n"); break;
  19. case 'B': printf("良好\n"); break; // 这里会执行
  20. case 'C': printf("及格\n"); break;
  21. default: printf("未知等级\n"); break;
  22. }
  23. // 2. 循环结构示例
  24. printf("\n--- 循环结构 ---\n");
  25. // for 循环:计算1-5的和
  26. int sum = 0;
  27. for (int i = 1; i <= 5; i++) {
  28. sum += i;
  29. }
  30. printf("1-5的和(for循环): %d\n", sum);
  31. // while 循环:计算1-5的乘积
  32. int product = 1;
  33. int j = 1;
  34. while (j <= 5) {
  35. product *= j;
  36. j++;
  37. }
  38. printf("1-5的积(while循环): %d\n", product);
  39. // do...while 循环:至少执行一次
  40. int k = 10;
  41. do {
  42. printf("do...while执行,k=%d\n", k);
  43. k++;
  44. } while (k < 5); // 条件不成立,但已执行一次
  45. // 3. break 和 continue 示例
  46. printf("\n--- break与continue ---\n");
  47. for (int i = 1; i <= 10; i++) {
  48. if (i == 3) {
  49. continue; // 跳过本次迭代,不打印3
  50. }
  51. if (i == 8) {
  52. break; // 跳出循环,不打印8及以后
  53. }
  54. printf("%d ", i); // 输出: 1 2 4 5 6 7
  55. }
  56. printf("\n");
  57. return 0;
  58. }
复制代码

2.2 数组

  • 一维数组:定义、初始化、遍历、越界问题
  • 多维数组:二维数组(矩阵)的定义与使用
  • 字符数组与字符串
    • 字符串的存储与初始化
    • 基本字符串操作(手动遍历)
    • 引入 常用函数:strlen, strcpy, strcat, strcmp

完整代码示例:

  1. #include <stdio.h>
  2. #include <string.h> // 引入字符串处理库
  3. int main() {
  4. printf("=== 一维数组示例 ===\n");
  5. // 1. 一维数组的定义与初始化
  6. int numbers[5]; // 定义长度为5的整型数组(未初始化)
  7. int scores[5] = {85, 90, 78, 92, 88}; // 定义并初始化
  8. int ages[] = {18, 21, 25, 30, 22}; // 省略长度,编译器自动计算
  9. // 2. 一维数组的遍历与访问
  10. printf("scores数组元素:");
  11. for (int i = 0; i < 5; i++) {
  12. printf("%d ", scores[i]); // 通过下标访问数组元素
  13. }
  14. printf("\n");
  15. // 3. 数组越界问题演示(危险!)
  16. // printf("尝试访问越界元素:%d\n", scores[10]); // 未定义行为,可能导致程序崩溃
  17. printf("\n=== 二维数组(矩阵)示例 ===\n");
  18. // 4. 二维数组的定义与初始化
  19. int matrix[3][3] = {
  20. {1, 2, 3},
  21. {4, 5, 6},
  22. {7, 8, 9}
  23. };
  24. // 5. 二维数组的遍历(嵌套循环)
  25. printf("3x3矩阵:\n");
  26. for (int i = 0; i < 3; i++) {
  27. for (int j = 0; j < 3; j++) {
  28. printf("%d ", matrix[i][j]); // 访问二维数组元素
  29. }
  30. printf("\n");
  31. }
  32. printf("\n=== 字符数组与字符串示例 ===\n");
  33. // 6. 字符数组的定义与初始化
  34. char str1[20] = "Hello"; // 字符串字面量初始化
  35. char str2[] = {'W', 'o', 'r', 'l', 'd', '\0'}; // 字符数组初始化,手动添加结束符'\0'
  36. char str3[10]; // 未初始化的字符数组
  37. // 7. 字符串的基本操作(手动遍历)
  38. printf("手动遍历str1:");
  39. int len = 0;
  40. while (str1[len] != '\0') { // 遍历直到遇到字符串结束符
  41. printf("%c", str1[len]);
  42. len++;
  43. }
  44. printf(" (长度:%d)\n", len);
  45. // 8. 使用<string.h>库函数
  46. printf("\n--- 使用<string.h>库函数 ---\n");
  47. // strlen: 获取字符串长度(不包括'\0')
  48. printf("str1的长度:%lu\n", strlen(str1));
  49. // strcpy: 字符串复制
  50. strcpy(str3, "C Language"); // 将"C Language"复制到str3
  51. printf("复制后str3:%s\n", str3);
  52. // strcat: 字符串连接
  53. strcat(str1, " "); // 先添加空格
  54. strcat(str1, str3); // 连接str3到str1末尾
  55. printf("连接后str1:%s\n", str1);
  56. // strcmp: 字符串比较
  57. char str4[] = "apple";
  58. char str5[] = "banana";
  59. int cmp_result = strcmp(str4, str5);
  60. if (cmp_result < 0) {
  61. printf(""%s" 在字典序中小于 "%s"\n", str4, str5);
  62. } else if (cmp_result > 0) {
  63. printf(""%s" 在字典序中大于 "%s"\n", str4, str5);
  64. } else {
  65. printf(""%s" 与 "%s" 相同\n", str4, str5);
  66. }
  67. // 9. 字符数组与字符串的区别
  68. printf("\n--- 字符数组与字符串的区别 ---\n");
  69. char arr1[] = {'a', 'b', 'c'}; // 字符数组,没有'\0'结束符
  70. char arr2[] = "abc"; // 字符串,自动添加'\0'
  71. printf("字符数组arr1(无'\\0'):");
  72. for (int i = 0; i < 3; i++) {
  73. printf("%c", arr1[i]);
  74. }
  75. printf("\n");
  76. printf("字符串arr2(有'\\0'):%s\n", arr2);
  77. printf("arr2作为字符串的长度:%lu\n", strlen(arr2));
  78. // 注意:将arr1当作字符串使用是危险的
  79. // printf("错误使用arr1:%s\n", arr1); // 可能打印乱码或导致程序崩溃
  80. return 0;
  81. }
复制代码

代码说明:

  1. 一维数组:展示了定义、初始化和遍历,特别注释了数组越界访问的危险性。
  2. 二维数组:以3x3矩阵为例,演示了二维数组的定义、初始化和嵌套循环遍历。
  3. 字符数组与字符串
    • 展示了字符数组的两种初始化方式
    • 演示了手动遍历字符串(查找结束符\0)
    • 引入了库的四个核心函数:strlen、strcpy、strcat、strcmp
    • 强调了字符数组与字符串(以\0结尾的字符数组)的区别
  4. 关键注释:为重要代码行添加了注释,解释其作用和注意事项。

运行结果示例:

  1. === 一维数组示例 ===
  2. scores数组元素:85 90 78 92 88
  3. === 二维数组(矩阵)示例 ===
  4. 3x3矩阵:
  5. 1 2 3
  6. 4 5 6
  7. 7 8 9
  8. === 字符数组与字符串示例 ===
  9. 手动遍历str1:Hello (长度:5)
  10. --- 使用<string.h>库函数 ---
  11. str1的长度:5
  12. 复制后str3:C Language
  13. 连接后str1:Hello C Language
  14. "apple" 在字典序中小于 "banana"
  15. --- 字符数组与字符串的区别 ---
  16. 字符数组arr1(无'\0'):abc
  17. 字符串arr2(有'\0'):abc
  18. arr2作为字符串的长度:3
复制代码

2.3 函数与模块化编程

  • 函数定义、声明与调用
  • 函数参数:形参与实参,值传递
  • 函数返回值
  • 变量的作用域与生命周期
    • 局部变量 vs 全局变量
    • 存储类别:auto, static, register, extern
  • 递归函数:原理、实现与经典案例(如阶乘、斐波那契数列)
    • 存储类别详解
      • auto:自动存储期。这是局部变量的默认存储类别。auto 变量在进入其所在的代码块时被创建,在退出代码块时被销毁。其生命周期仅限于该代码块内部。由于是默认类别,通常省略不写(例如 int x; 等价于 auto int x;)。
      • register:建议寄存器存储。用于提示编译器将该变量存储在CPU寄存器中,以期获得更快的访问速度。但这是一个建议,编译器可以忽略。register 变量不能取地址(即不能使用 & 运算符),因为寄存器没有内存地址。在现代优化编译器下,此关键字已很少需要显式使用。
      • static:静态存储期。使局部变量的生命周期延长至整个程序运行期间,但作用域仍限制在定义它的函数或文件内。在函数内定义的 static 变量只会被初始化一次,并在多次函数调用间保持其值。
      • extern:外部链接。用于声明一个在其他文件中定义的全局变量或函数,表示该标识符具有外部链接,可以在多个源文件之间共享。
        代码示例:

1. 百分比显示函数示例:

  1. #include <stdio.h>
  2. // 函数声明
  3. void displayPercentage(float part, float total);
  4. float calculatePercentage(float part, float total);
  5. int main() {
  6. printf("=== 百分比显示函数示例 ===\n\n");
  7. // 示例1:计算并显示百分比
  8. float score = 85.0;
  9. float maxScore = 100.0;
  10. printf("示例1:考试成绩百分比\n");
  11. printf("得分:%.1f/%.1f\n", score, maxScore);
  12. displayPercentage(score, maxScore);
  13. // 示例2:计算百分比但不显示
  14. float budget = 5000.0;
  15. float spent = 3250.0;
  16. float percentage = calculatePercentage(spent, budget);
  17. printf("\n示例2:预算使用情况\n");
  18. printf("预算:¥%.2f,已花费:¥%.2f\n", budget, spent);
  19. printf("使用比例:%.2f%%\n", percentage);
  20. if (percentage > 100.0) {
  21. printf("警告:已超出预算!\n");
  22. } else if (percentage > 80.0) {
  23. printf("提示:预算使用已超过80%%,请注意控制支出。\n");
  24. } else {
  25. printf("状态:预算使用正常。\n");
  26. }
  27. // 示例3:处理除零错误
  28. printf("\n示例3:错误处理演示\n");
  29. displayPercentage(50.0, 0.0); // 除数为零的情况
  30. return 0;
  31. }
  32. // 函数定义:显示百分比
  33. // 参数:part - 部分值,total - 总值
  34. // 功能:计算并显示part占total的百分比
  35. void displayPercentage(float part, float total) {
  36. // 输入验证
  37. if (total == 0.0) {
  38. printf("错误:除数不能为零!\n");
  39. return;
  40. }
  41. if (part < 0 || total < 0) {
  42. printf("错误:数值不能为负数!\n");
  43. return;
  44. }
  45. // 计算百分比
  46. float percentage = (part / total) * 100.0;
  47. // 显示结果
  48. printf("百分比:%.2f%% (%.1f/%.1f)\n", percentage, part, total);
  49. // 附加信息
  50. if (percentage >= 90.0) {
  51. printf("评级:优秀\n");
  52. } else if (percentage >= 70.0) {
  53. printf("评级:良好\n");
  54. } else if (percentage >= 60.0) {
  55. printf("评级:及格\n");
  56. } else {
  57. printf("评级:不及格\n");
  58. }
  59. }
  60. // 函数定义:计算百分比
  61. // 参数:part - 部分值,total - 总值
  62. // 返回值:百分比值(0-100)
  63. float calculatePercentage(float part, float total) {
  64. // 输入验证
  65. if (total == 0.0) {
  66. printf("警告:除数为零,返回0%%\n");
  67. return 0.0;
  68. }
  69. if (part < 0 || total < 0) {
  70. printf("警告:数值为负数,返回0%%\n");
  71. return 0.0;
  72. }
  73. // 计算并返回百分比
  74. return (part / total) * 100.0;
  75. }
复制代码

代码说明:

  1. 函数声明与定义:展示了函数声明(原型)和定义的完整流程
  2. 参数传递:displayPercentage函数接收两个float参数,演示了值传递
  3. 返回值:calculatePercentage函数返回float类型的百分比值
  4. 错误处理:包含除零检查和负数检查,提高程序健壮性
  5. 函数调用:在main函数中多次调用这两个函数,展示不同用法
  6. 格式化输出:使用printf的格式化功能,特别是%.2f%%显示两位小数的百分比

运行结果示例:

  1. === 百分比显示函数示例 ===
  2. 示例1:考试成绩百分比
  3. 得分:85.0/100.0
  4. 百分比:85.00% (85.0/100.0)
  5. 评级:良好
  6. 示例2:预算使用情况
  7. 预算:¥5000.00,已花费:¥3250.00
  8. 使用比例:65.00%
  9. 状态:预算使用正常。
  10. 示例3:错误处理演示
  11. 错误:除数不能为零!
复制代码

2. 数组累加函数示例:

  1. #include <stdio.h>
  2. // 函数声明
  3. int sumArray(int arr[], int size);
  4. float averageArray(int arr[], int size);
  5. void printArray(int arr[], int size);
  6. int main() {
  7. printf("=== 数组累加函数示例 ===\n\n");
  8. // 示例数组
  9. int numbers[] = {78, 92, 85, 67, 90, 88, 76, 95, 81, 73};
  10. int size = sizeof(numbers) / sizeof(numbers[0]);
  11. printf("数组元素:");
  12. printArray(numbers, size);
  13. // 调用数组求和函数
  14. int total = sumArray(numbers, size);
  15. printf("数组元素总和:%d\n", total);
  16. // 调用数组求平均值函数
  17. float avg = averageArray(numbers, size);
  18. printf("数组元素平均值:%.2f\n", avg);
  19. // 示例2:部分数组元素求和
  20. printf("\n=== 部分数组求和 ===\n");
  21. int partialSum = sumArray(numbers, 5); // 只计算前5个元素
  22. printf("前5个元素的和:%d\n", partialSum);
  23. // 示例3:空数组处理
  24. printf("\n=== 边界情况测试 ===\n");
  25. int emptyArray[] = {};
  26. int emptySize = 0;
  27. printf("空数组求和:%d\n", sumArray(emptyArray, emptySize));
  28. printf("空数组平均值:%.2f\n", averageArray(emptyArray, emptySize));
  29. // 示例4:单个元素数组
  30. int singleArray[] = {100};
  31. int singleSize = 1;
  32. printf("\n单元素数组[100]:\n");
  33. printf("求和:%d\n", sumArray(singleArray, singleSize));
  34. printf("平均值:%.2f\n", averageArray(singleArray, singleSize));
  35. return 0;
  36. }
  37. // 函数定义:计算数组元素总和
  38. // 参数:arr - 整型数组,size - 数组大小
  39. // 返回值:数组所有元素的和
  40. int sumArray(int arr[], int size) {
  41. // 处理空数组
  42. if (size <= 0) {
  43. printf("提示:数组为空或大小无效\n");
  44. return 0;
  45. }
  46. int sum = 0;
  47. for (int i = 0; i < size; i++) {
  48. sum += arr[i];
  49. }
  50. return sum;
  51. }
  52. // 函数定义:计算数组平均值
  53. // 参数:arr - 整型数组,size - 数组大小
  54. // 返回值:数组元素的平均值
  55. float averageArray(int arr[], int size) {
  56. // 处理空数组
  57. if (size <= 0) {
  58. printf("提示:数组为空,无法计算平均值\n");
  59. return 0.0;
  60. }
  61. // 调用sumArray函数获取总和
  62. int total = sumArray(arr, size);
  63. // 计算平均值(注意类型转换)
  64. return (float)total / size;
  65. }
  66. // 函数定义:打印数组
  67. // 参数:arr - 整型数组,size - 数组大小
  68. void printArray(int arr[], int size) {
  69. if (size <= 0) {
  70. printf("[]\n");
  71. return;
  72. }
  73. printf("[");
  74. for (int i = 0; i < size; i++) {
  75. printf("%d", arr[i]);
  76. if (i < size - 1) {
  77. printf(", ");
  78. }
  79. }
  80. printf("]\n");
  81. }
复制代码

代码说明:

  1. 数组作为函数参数:演示了数组作为函数参数的用法(实际上传递的是指针)
  2. 数组大小参数:必须传递数组大小,因为函数内无法通过sizeof获取数组元素个数
  3. 函数复用:averageArray函数内部调用了sumArray函数,展示了函数间的调用
  4. 边界情况处理:包含对空数组、单个元素数组的特殊处理
  5. 类型转换:在计算平均值时进行(float)类型转换,确保得到浮点数结果
  6. 模块化设计:将不同功能分离到不同函数中,提高代码可读性和可维护性

运行结果示例:

  1. === 数组累加函数示例 ===
  2. 数组元素:[78, 92, 85, 67, 90, 88, 76, 95, 81, 73]
  3. 数组元素总和:825
  4. 数组元素平均值:82.50
  5. === 部分数组求和 ===
  6. 前5个元素的和:412
  7. === 边界情况测试 ===
  8. 提示:数组为空或大小无效
  9. 空数组求和:0
  10. 提示:数组为空,无法计算平均值
  11. 空数组平均值:0.00
  12. 单元素数组[100]:
  13. 求和:100
  14. 平均值:100.00
复制代码

关键知识点总结:

  1. 函数声明与定义分离:良好的编程习惯是先声明函数原型,再定义函数体
  2. 数组参数传递:C语言中数组作为参数传递时,实际传递的是数组首元素的地址
  3. 错误处理:在函数开始处检查输入参数的合法性
  4. 代码复用:通过函数调用其他函数,避免重复代码
  5. 模块化思想:每个函数只负责一个明确的任务,提高代码的可测试性和可维护性数

第三阶段:核心难点突破 - 指针与内存管理 (3-4周)

图4:指针与内存管理核心概念图

3.1 指针基础

  • 内存地址与指针概念
  • 指针变量的定义与初始化
  • 取地址运算符 (&)解引用运算符 (*)
  • 空指针 (NULL) 与野指针
  1. #include <stdio.h>
  2. int main() {
  3. int num = 42; // 普通整型变量
  4. int *ptr = NULL; // 指针变量声明,初始化为NULL
  5. // 1. 取地址运算符(&) - 获取变量的内存地址
  6. ptr = &num; // ptr现在存储num的地址
  7. printf("变量num的值: %d\n", num);
  8. printf("变量num的地址: %p\n", (void*)&num);
  9. printf("指针ptr存储的地址: %p\n", (void*)ptr);
  10. printf("指针ptr自己的地址: %p\n", (void*)&ptr);
  11. // 2. 解引用运算符(*) - 通过指针访问/修改变量值
  12. printf("\n通过ptr访问num的值: %d\n", *ptr); // 输出: 42
  13. *ptr = 100; // 通过指针修改变量值
  14. printf("修改后num的值: %d\n", num); // 输出: 100
  15. printf("修改后*ptr的值: %d\n", *ptr); // 输出: 100
  16. // 3. 指针的指针(二级指针)
  17. int **pptr = &ptr; // 指向指针的指针
  18. printf("\n二级指针pptr存储的地址: %p\n", (void*)pptr);
  19. printf("通过pptr访问ptr的地址: %p\n", (void*)*pptr);
  20. printf("通过pptr访问num的值: %d\n", **pptr); // 输出: 100
  21. // 4. 空指针与野指针示例
  22. int *null_ptr = NULL; // 空指针,安全的初始化
  23. int *wild_ptr; // 野指针,未初始化,危险!
  24. printf("\n空指针null_ptr的值: %p\n", (void*)null_ptr);
  25. // printf("尝试解引用空指针: %d\n", *null_ptr); // 运行时错误!
  26. // 安全检查空指针
  27. if (null_ptr == NULL) {
  28. printf("null_ptr是空指针,不能解引用\n");
  29. }
  30. // 野指针演示(不要在实际代码中这样做)
  31. // printf("野指针wild_ptr的值: %p\n", (void*)wild_ptr); // 未定义行为
  32. // *wild_ptr = 10; // 危险!可能导致程序崩溃
  33. return 0;
  34. }
复制代码

3.2 指针与数组

  • 数组名与指针的关系
  • 通过指针遍历数组
  • 指针算术运算

完整代码示例:指针遍历数组与算术运算对比

  1. #include <stdio.h>
  2. int main() {
  3. printf("=== 一维数组的指针遍历 ===\n");
  4. // 1. 一维数组的定义与初始化
  5. int arr[5] = {10, 20, 30, 40, 50};
  6. // 2. 数组名与指针的关系
  7. printf("数组名 arr 的值(首元素地址): %p\n", (void*)arr);
  8. printf("&arr[0] 的值(首元素地址): %p\n", (void*)&arr[0]);
  9. printf("arr == &arr[0] ? %s\n", arr == &arr[0] ? "是" : "否");
  10. // 3. 通过指针遍历一维数组
  11. printf("\n通过指针遍历一维数组:\n");
  12. int *ptr = arr; // ptr指向数组首元素
  13. for (int i = 0; i < 5; i++) {
  14. printf("arr[%d] = %d, *(ptr + %d) = %d, *(arr + %d) = %d\n",
  15. i, arr[i], i, *(ptr + i), i, *(arr + i));
  16. }
  17. // 4. 指针算术运算(一维数组)
  18. printf("\n=== 指针算术运算(一维数组)===\n");
  19. printf("ptr 当前指向: %p, 值: %d\n", (void*)ptr, *ptr);
  20. ptr++; // 指针加1,指向下一个元素
  21. printf("ptr++ 后指向: %p, 值: %d\n", (void*)ptr, *ptr);
  22. ptr += 2; // 指针加2,跳过两个元素
  23. printf("ptr += 2 后指向: %p, 值: %d\n", (void*)ptr, *ptr);
  24. ptr--; // 指针减1,指向前一个元素
  25. printf("ptr-- 后指向: %p, 值: %d\n", (void*)ptr, *ptr);
  26. // 5. 数组名与指针在算术运算上的异同
  27. printf("\n=== 数组名与指针的异同 ===\n");
  28. printf("数组名 arr 的值: %p\n", (void*)arr);
  29. printf("指针 ptr 的值: %p\n", (void*)ptr);
  30. // 相同点:都可以用于指针算术
  31. printf("\n相同点 - 都可以用于指针算术:\n");
  32. printf("*(arr + 2) = %d\n", *(arr + 2)); // 等价于 arr[2]
  33. printf("*(ptr + 1) = %d\n", *(ptr + 1)); // 等价于 ptr[1]
  34. // 不同点:数组名是常量指针,不能重新赋值
  35. printf("\n不同点 - 数组名是常量指针:\n");
  36. // arr = arr + 1; // 错误!数组名不能作为左值
  37. ptr = arr + 1; // 正确!指针可以重新赋值
  38. printf("ptr = arr + 1 后,ptr指向: %p\n", (void*)ptr);
  39. // 6. 二维数组的指针遍历
  40. printf("\n=== 二维数组的指针遍历 ===\n");
  41. int matrix[3][4] = {
  42. {1, 2, 3, 4},
  43. {5, 6, 7, 8},
  44. {9, 10, 11, 12}
  45. };
  46. // 方法1:使用数组下标(传统方式)
  47. printf("\n方法1:使用数组下标遍历:\n");
  48. for (int i = 0; i < 3; i++) {
  49. for (int j = 0; j < 4; j++) {
  50. printf("%2d ", matrix[i][j]);
  51. }
  52. printf("\n");
  53. }
  54. // 方法2:使用指针遍历(将二维数组视为一维存储)
  55. printf("\n方法2:使用指针遍历(一维视角):\n");
  56. int *p = &matrix[0][0]; // 指向第一个元素
  57. for (int i = 0; i < 3 * 4; i++) {
  58. printf("%2d ", *(p + i));
  59. if ((i + 1) % 4 == 0) printf("\n"); // 每4个元素换行
  60. }
  61. // 方法3:使用指针数组方式
  62. printf("\n方法3:使用行指针遍历:\n");
  63. for (int i = 0; i < 3; i++) {
  64. int *row_ptr = matrix[i]; // matrix[i]是第i行的首地址
  65. for (int j = 0; j < 4; j++) {
  66. printf("%2d ", *(row_ptr + j));
  67. }
  68. printf("\n");
  69. }
  70. // 7. 二维数组的指针算术
  71. printf("\n=== 二维数组的指针算术 ===\n");
  72. printf("matrix 的值(首行地址): %p\n", (void*)matrix);
  73. printf("matrix[0] 的值(首行首元素地址): %p\n", (void*)matrix[0]);
  74. printf("&matrix[0][0] 的值: %p\n", (void*)&matrix[0][0]);
  75. printf("\nmatrix + 1 跳过一行(4个int): %p\n", (void*)(matrix + 1));
  76. printf("matrix[0] + 1 跳过一个元素: %p\n", (void*)(matrix[0] + 1));
  77. // 访问 matrix[1][2] 的多种方式
  78. printf("\n访问 matrix[1][2](值为7)的多种方式:\n");
  79. printf("matrix[1][2] = %d\n", matrix[1][2]);
  80. printf("*(matrix[1] + 2) = %d\n", *(matrix[1] + 2));
  81. printf("*(*(matrix + 1) + 2) = %d\n", *(*(matrix + 1) + 2));
  82. return 0;
  83. }
复制代码

代码说明与运行结果分析:

  1. 一维数组遍历

    • 展示了三种访问方式:arr、*(ptr + i)、*(arr + i)
    • 验证了 arr == &arr[0],数组名表示首元素地址
  2. 指针算术运算

    • ptr++:移动到下一个元素(地址增加 sizeof(int))
    • ptr += 2:跳过两个元素
    • ptr--:移动到前一个元素
    • 指针算术的单位是类型大小,不是字节数
  3. 数组名与指针的异同

    • 相同点:都可以用于指针算术访问数组元素
    • 不同点:数组名是常量指针,不能重新赋值;普通指针变量可以重新指向其他地址
  4. 二维数组遍历

    • 方法1:传统下标法 matrix[j]
    • 方法2:一维指针法,将二维数组视为连续内存
    • 方法3:行指针法,每行用一个指针遍历
  5. 二维数组指针算术

    • matrix + 1:跳过一行(4个int)
    • matrix[0] + 1:跳过一个元素
    • 展示了访问 matrix[1][2] 的三种等价方式

运行结果示例:

  1. === 一维数组的指针遍历 ===
  2. 数组名 arr 的值(首元素地址): 0x7ffc5a3b4a10
  3. &arr[0] 的值(首元素地址): 0x7ffc5a3b4a10
  4. arr == &arr[0] ? 是
  5. 通过指针遍历一维数组:
  6. arr[0] = 10, *(ptr + 0) = 10, *(arr + 0) = 10
  7. arr[1] = 20, *(ptr + 1) = 20, *(arr + 1) = 20
  8. arr[2] = 30, *(ptr + 2) = 30, *(arr + 2) = 30
  9. arr[3] = 40, *(ptr + 3) = 40, *(arr + 3) = 40
  10. arr[4] = 50, *(ptr + 4) = 50, *(arr + 4) = 50
  11. === 指针算术运算(一维数组)===
  12. ptr 当前指向: 0x7ffc5a3b4a10, 值: 10
  13. ptr++ 后指向: 0x7ffc5a3b4a14, 值: 20
  14. ptr += 2 后指向: 0x7ffc5a3b4a1c, 值: 40
  15. ptr-- 后指向: 0x7ffc5a3b4a18, 值: 30
  16. === 数组名与指针的异同 ===
  17. 数组名 arr 的值: 0x7ffc5a3b4a10
  18. 指针 ptr 的值: 0x7ffc5a3b4a18
  19. 相同点 - 都可以用于指针算术:
  20. *(arr + 2) = 30
  21. *(ptr + 1) = 40
  22. 不同点 - 数组名是常量指针:
  23. ptr = arr + 1 后,ptr指向: 0x7ffc5a3b4a14
  24. === 二维数组的指针遍历 ===
  25. 方法1:使用数组下标遍历:
  26. 1 2 3 4
  27. 5 6 7 8
  28. 9 10 11 12
  29. 方法2:使用指针遍历(一维视角):
  30. 1 2 3 4
  31. 5 6 7 8
  32. 9 10 11 12
  33. 方法3:使用行指针遍历:
  34. 1 2 3 4
  35. 5 6 7 8
  36. 9 10 11 12
  37. === 二维数组的指针算术 ===
  38. matrix 的值(首行地址): 0x7ffc5a3b4a20
  39. matrix[0] 的值(首行首元素地址): 0x7ffc5a3b4a20
  40. &matrix[0][0] 的值: 0x7ffc5a3b4a20
  41. matrix + 1 跳过一行(4个int): 0x7ffc5a3b4a30
  42. matrix[0] + 1 跳过一个元素: 0x7ffc5a3b4a24
  43. 访问 matrix[1][2](值为7)的多种方式:
  44. matrix[1][2] = 7
  45. *(matrix[1] + 2) = 7
  46. *(*(matrix + 1) + 2) = 7
复制代码

关键理解点:

  1. 数组名在大多数情况下会退化为指向首元素的指针
  2. 指针算术的步长由指针类型决定(int* 加1移动4字节)
  3. 二维数组在内存中是按行连续存储的
  4. matrix[j] 等价于 *(*(matrix + i) + j)
  5. 数组名是常量,不能重新赋值;指针变量可以改变指向

3.3 指针与字符串

  • 字符指针与字符数组的区别
  • 使用指针操作字符串

3.4 指针与函数

  • 指针作为函数参数:实现"模拟引用传递",修改实参
  • 数组作为函数参数(退化为指针)
  • 函数指针:定义、赋值与调用(回调函数基础)

完整代码示例:指针与函数的三种应用

  1. #include <stdio.h>
  2. #include <string.h>
  3. // ==================== 1. 指针作为函数参数 ====================
  4. // 函数声明
  5. void swapByValue(int a, int b); // 值传递 - 无法修改实参
  6. void swapByPointer(int *a, int *b); // 指针传递 - 可以修改实参
  7. // ==================== 2. 数组作为函数参数 ====================
  8. // 数组作为参数时,会退化为指针
  9. void printArray(int arr[], int size); // 形式1:数组表示法
  10. void printArrayPointer(int *arr, int size); // 形式2:指针表示法
  11. void modifyArray(int arr[], int size); // 修改数组元素
  12. // ==================== 3. 函数指针 ====================
  13. // 函数指针类型声明
  14. typedef int (*CompareFunc)(int, int); // 定义函数指针类型
  15. // 比较函数(用于回调)
  16. int compareAscending(int a, int b); // 升序比较
  17. int compareDescending(int a, int b); // 降序比较
  18. // 使用函数指针的回调函数
  19. void sortArray(int arr[], int size, CompareFunc compare);
  20. void printWithCallback(int arr[], int size, void (*printFunc)(int));
  21. // 回调函数示例
  22. void printSquare(int num); // 打印平方
  23. void printCube(int num); // 打印立方
  24. int main() {
  25. printf("=== 1. 指针作为函数参数 ===\n");
  26. int x = 10, y = 20;
  27. printf("交换前:x = %d, y = %d\n", x, y);
  28. // 1.1 值传递 - 无法修改实参
  29. swapByValue(x, y);
  30. printf("值传递交换后:x = %d, y = %d (未改变)\n", x, y);
  31. // 1.2 指针传递 - 可以修改实参
  32. swapByPointer(&x, &y); // 传递变量的地址
  33. printf("指针传递交换后:x = %d, y = %d (已交换)\n", x, y);
  34. // 1.3 返回多个值的示例
  35. printf("\n--- 返回多个值示例 ---\n");
  36. int num = 15;
  37. int square, cube;
  38. // 通过指针参数返回多个计算结果
  39. void calculatePower(int n, int *sq, int *cb) {
  40. *sq = n * n; // 修改指针指向的值
  41. *cb = n * n * n;
  42. }
  43. calculatePower(num, &square, &cube);
  44. printf("%d 的平方:%d,立方:%d\n", num, square, cube);
  45. printf("\n=== 2. 数组作为函数参数 ===\n");
  46. // 2.1 数组定义与初始化
  47. int numbers[] = {5, 2, 8, 1, 9, 3};
  48. int size = sizeof(numbers) / sizeof(numbers[0]);
  49. printf("原始数组:");
  50. printArray(numbers, size); // 数组表示法
  51. printf("通过指针访问:");
  52. printArrayPointer(numbers, size); // 指针表示法
  53. // 2.2 验证数组参数退化为指针
  54. printf("\n--- 验证数组参数退化为指针 ---\n");
  55. printf("main函数中数组大小:%lu 字节\n", sizeof(numbers));
  56. printf("main函数中元素个数:%d\n", size);
  57. // 在函数内部检查数组大小
  58. void checkArraySize(int arr[]) {
  59. printf("函数内数组大小:%lu 字节 (退化为指针大小)\n", sizeof(arr));
  60. printf("函数内通过sizeof(arr)/sizeof(arr[0])计算元素个数:%lu (错误!)\n",
  61. sizeof(arr) / sizeof(arr[0]));
  62. }
  63. checkArraySize(numbers);
  64. // 2.3 修改数组元素
  65. printf("\n修改前数组:");
  66. printArray(numbers, size);
  67. modifyArray(numbers, size); // 通过指针修改原数组
  68. printf("修改后数组:");
  69. printArray(numbers, size);
  70. printf("\n=== 3. 函数指针 ===\n");
  71. // 3.1 函数指针的基本使用
  72. printf("--- 函数指针基本用法 ---\n");
  73. // 声明函数指针变量
  74. int (*funcPtr)(int, int);
  75. // 将函数地址赋值给函数指针
  76. funcPtr = compareAscending;
  77. // 通过函数指针调用函数
  78. int result1 = funcPtr(10, 5);
  79. printf("compareAscending(10, 5) = %d (10 > 5 返回正数)\n", result1);
  80. // 切换函数指针指向
  81. funcPtr = compareDescending;
  82. int result2 = funcPtr(10, 5);
  83. printf("compareDescending(10, 5) = %d (10 > 5 返回负数)\n", result2);
  84. // 3.2 函数指针数组
  85. printf("\n--- 函数指针数组 ---\n");
  86. // 定义函数指针数组
  87. int (*operations[4])(int, int) = {
  88. NULL, // 占位,索引0不使用
  89. [1] = compareAscending, // 索引1:升序比较
  90. [2] = compareDescending, // 索引2:降序比较
  91. };
  92. // 通过索引调用不同的函数
  93. printf("operations[1](8, 3) = %d\n", operations[1](8, 3));
  94. printf("operations[2](8, 3) = %d\n", operations[2](8, 3));
  95. // 3.3 回调函数应用:排序
  96. printf("\n--- 回调函数:排序示例 ---\n");
  97. int data[] = {5, 2, 8, 1, 9, 3, 7, 4, 6};
  98. int dataSize = sizeof(data) / sizeof(data[0]);
  99. printf("原始数据:");
  100. printArray(data, dataSize);
  101. // 使用升序比较函数排序
  102. sortArray(data, dataSize, compareAscending);
  103. printf("升序排序后:");
  104. printArray(data, dataSize);
  105. // 使用降序比较函数排序
  106. sortArray(data, dataSize, compareDescending);
  107. printf("降序排序后:");
  108. printArray(data, dataSize);
  109. // 3.4 回调函数应用:不同的打印方式
  110. printf("\n--- 回调函数:不同打印方式 ---\n");
  111. int values[] = {1, 2, 3, 4, 5};
  112. int valuesSize = sizeof(values) / sizeof(values[0]);
  113. printf("原始值:");
  114. printArray(values, valuesSize);
  115. printf("平方值:");
  116. printWithCallback(values, valuesSize, printSquare);
  117. printf("立方值:");
  118. printWithCallback(values, valuesSize, printCube);
  119. // 3.5 匿名函数指针(直接传递函数名)
  120. printf("\n--- 直接传递函数名 ---\n");
  121. // 函数名本身就是函数指针
  122. sortArray(data, 5, compareAscending); // compareAscending 等价于 &compareAscending
  123. printf("部分数据升序排序:");
  124. printArray(data, 5);
  125. return 0;
  126. }
  127. // ==================== 函数定义 ====================
  128. // 1. 指针作为函数参数
  129. void swapByValue(int a, int b) {
  130. int temp = a;
  131. a = b;
  132. b = temp;
  133. // 这里的修改只影响形参,不影响实参
  134. }
  135. void swapByPointer(int *a, int *b) {
  136. int temp = *a; // 解引用获取a指向的值
  137. *a = *b; // 修改a指向的内存
  138. *b = temp; // 修改b指向的内存
  139. }
  140. // 2. 数组作为函数参数
  141. void printArray(int arr[], int size) {
  142. for (int i = 0; i < size; i++) {
  143. printf("%d ", arr[i]);
  144. }
  145. printf("\n");
  146. }
  147. void printArrayPointer(int *arr, int size) {
  148. for (int i = 0; i < size; i++) {
  149. printf("%d ", *(arr + i)); // 指针算术访问元素
  150. }
  151. printf("\n");
  152. }
  153. void modifyArray(int arr[], int size) {
  154. for (int i = 0; i < size; i++) {
  155. arr[i] = arr[i] * 2; // 修改原数组元素
  156. }
  157. }
  158. // 3. 函数指针相关函数
  159. int compareAscending(int a, int b) {
  160. return a - b; // 升序:a>b返回正数,a<b返回负数,a=b返回0
  161. }
  162. int compareDescending(int a, int b) {
  163. return b - a; // 降序:a>b返回负数,a<b返回正数,a=b返回0
  164. }
  165. // 简单的冒泡排序,使用函数指针决定排序顺序
  166. void sortArray(int arr[], int size, CompareFunc compare) {
  167. for (int i = 0; i < size - 1; i++) {
  168. for (int j = 0; j < size - i - 1; j++) {
  169. if (compare(arr[j], arr[j + 1]) > 0) {
  170. // 交换元素
  171. int temp = arr[j];
  172. arr[j] = arr[j + 1];
  173. arr[j + 1] = temp;
  174. }
  175. }
  176. }
  177. }
  178. void printWithCallback(int arr[], int size, void (*printFunc)(int)) {
  179. for (int i = 0; i < size; i++) {
  180. printFunc(arr[i]);
  181. if (i < size - 1) printf(", ");
  182. }
  183. printf("\n");
  184. }
  185. void printSquare(int num) {
  186. printf("%d", num * num);
  187. }
  188. void printCube(int num) {
  189. printf("%d", num * num * num);
  190. }
复制代码

代码说明与运行结果分析:

1. 指针作为函数参数

  • 值传递:swapByValue 函数接收的是参数的副本,修改不影响原变量
  • 指针传递:swapByPointer 函数接收变量的地址,通过解引用修改原变量
  • 应用场景:需要函数修改多个值或大型数据结构时使用指针参数

2. 数组作为函数参数

  • 数组退化:数组作为参数时,会退化为指向首元素的指针
  • 两种等价形式:int arr[] 和 int *arr 在函数参数中是等价的
  • 大小信息丢失:函数内无法通过 sizeof(arr) 获取数组元素个数,需要额外传递大小参数
  • 修改原数组:通过指针可以直接修改调用者的数组

3. 函数指针

  • 函数指针声明:返回类型 (*指针名)(参数列表)
  • typedef 简化:使用 typedef 定义函数指针类型,提高可读性
  • 回调函数:将函数指针作为参数传递给其他函数,实现灵活的行为定制
  • 函数指针数组:创建函数指针数组,实现类似"函数表"的功能

运行结果示例:

  1. === 1. 指针作为函数参数 ===
  2. 交换前:x = 10, y = 20
  3. 值传递交换后:x = 10, y = 20 (未改变)
  4. 指针传递交换后:x = 20, y = 10 (已交换)
  5. --- 返回多个值示例 ---
  6. 15 的平方:225,立方:3375
  7. === 2. 数组作为函数参数 ===
  8. 原始数组:5 2 8 1 9 3
  9. 通过指针访问:5 2 8 1 9 3
  10. --- 验证数组参数退化为指针 ---
  11. main函数中数组大小:24 字节
  12. main函数中元素个数:6
  13. 函数内数组大小:8 字节 (退化为指针大小)
  14. 函数内通过sizeof(arr)/sizeof(arr[0])计算元素个数:2 (错误!)
  15. 修改前数组:5 2 8 1 9 3
  16. 修改后数组:10 4 16 2 18 6
  17. === 3. 函数指针 ===
  18. --- 函数指针基本用法 ---
  19. compareAscending(10, 5) = 5 (10 > 5 返回正数)
  20. compareDescending(10, 5) = -5 (10 > 5 返回负数)
  21. --- 函数指针数组 ---
  22. operations[1](8, 3) = 5
  23. operations[2](8, 3) = -5
  24. --- 回调函数:排序示例 ---
  25. 原始数据:5 2 8 1 9 3 7 4 6
  26. 升序排序后:1 2 3 4 5 6 7 8 9
  27. 降序排序后:9 8 7 6 5 4 3 2 1
  28. --- 回调函数:不同打印方式 ---
  29. 原始值:1 2 3 4 5
  30. 平方值:1, 4, 9, 16, 25
  31. 立方值:1, 8, 27, 64, 125
  32. --- 直接传递函数名 ---
  33. 部分数据升序排序:1 2 3 4 5
复制代码

关键理解点:

  1. 指针参数实现"引用传递":C语言没有真正的引用传递,但通过指针可以实现类似效果
  2. 数组参数的本质:int arr[] 在函数参数中等价于 int *arr,都是指针
  3. 函数指针的灵活性:允许运行时决定调用哪个函数,是实现回调、策略模式的基础
  4. 回调函数的应用:排序函数通过函数指针参数决定排序规则,实现通用性
  5. 函数名即指针:函数名本身就是指向函数代码的指针,func 等价于 &func

3.5 动态内存管理

  • 堆内存的概念
  • 动态内存分配函数:malloc, calloc, realloc
  • 内存释放:free
  • 常见内存错误:内存泄漏、重复释放、访问已释放内存

第四阶段:复杂数据类型与底层操作 (2-3周)

4.1 结构体 (struct)

  • 结构体的定义与变量声明
  • 成员访问 (. 运算符)
  • 结构体数组
  • 结构体指针与箭头运算符 (->)
  • 结构体嵌套

完整代码示例:结构体的定义与使用

  1. #include <stdio.h>
  2. #include <string.h>
  3. // 1. 结构体定义
  4. struct Student {
  5. int id; // 学号
  6. char name[50]; // 姓名
  7. int age; // 年龄
  8. float score; // 成绩
  9. };
  10. // 2. 结构体嵌套示例
  11. struct Date {
  12. int year;
  13. int month;
  14. int day;
  15. };
  16. struct Employee {
  17. int emp_id;
  18. char emp_name[50];
  19. struct Date hire_date; // 嵌套结构体
  20. float salary;
  21. };
  22. int main() {
  23. printf("=== 结构体基础 ===\n");
  24. // 3. 结构体变量声明与初始化
  25. // 方式1:声明后逐个赋值
  26. struct Student stu1;
  27. stu1.id = 1001;
  28. strcpy(stu1.name, "张三");
  29. stu1.age = 20;
  30. stu1.score = 85.5;
  31. // 方式2:声明时初始化
  32. struct Student stu2 = {1002, "李四", 21, 90.0};
  33. // 方式3:指定成员初始化(C99及以上)
  34. struct Student stu3 = {
  35. .id = 1003,
  36. .name = "王五",
  37. .score = 78.5,
  38. .age = 22 // 成员顺序可以打乱
  39. };
  40. // 4. 成员访问(点运算符)
  41. printf("学生1:学号=%d, 姓名=%s, 年龄=%d, 成绩=%.1f\n",
  42. stu1.id, stu1.name, stu1.age, stu1.score);
  43. printf("学生2:学号=%d, 姓名=%s, 年龄=%d, 成绩=%.1f\n",
  44. stu2.id, stu2.name, stu2.age, stu2.score);
  45. printf("学生3:学号=%d, 姓名=%s, 年龄=%d, 成绩=%.1f\n",
  46. stu3.id, stu3.name, stu3.age, stu3.score);
  47. // 5. 结构体数组
  48. printf("\n=== 结构体数组 ===\n");
  49. struct Student class[3] = {
  50. {2001, "小明", 19, 88.0},
  51. {2002, "小红", 20, 92.5},
  52. {2003, "小刚", 21, 76.0}
  53. };
  54. // 遍历结构体数组
  55. for (int i = 0; i < 3; i++) {
  56. printf("学生%d:%s,成绩:%.1f\n",
  57. class[i].id, class[i].name, class[i].score);
  58. }
  59. // 6. 结构体指针与箭头运算符(->)
  60. printf("\n=== 结构体指针 ===\n");
  61. struct Student *ptr_stu = &stu1; // 指向stu1的指针
  62. // 使用箭头运算符访问成员
  63. printf("通过指针访问:姓名=%s, 成绩=%.1f\n",
  64. ptr_stu->name, ptr_stu->score);
  65. // 修改指针指向的结构体成员
  66. ptr_stu->score = 88.0;
  67. printf("修改后成绩:%.1f\n", stu1.score);
  68. // 7. 结构体嵌套
  69. printf("\n=== 结构体嵌套 ===\n");
  70. struct Employee emp1 = {
  71. .emp_id = 5001,
  72. .emp_name = "赵经理",
  73. .hire_date = {2018, 5, 15}, // 嵌套结构体初始化
  74. .salary = 15000.0
  75. };
  76. printf("员工:%s (工号:%d)\n", emp1.emp_name, emp1.emp_id);
  77. printf("入职日期:%d年%d月%d日\n",
  78. emp1.hire_date.year, emp1.hire_date.month, emp1.hire_date.day);
  79. printf("月薪:%.2f元\n", emp1.salary);
  80. // 8. 结构体作为函数参数(值传递)
  81. printf("\n=== 结构体作为函数参数 ===\n");
  82. // 定义打印学生信息的函数
  83. void printStudent(struct Student s) {
  84. printf("函数内打印:%s,年龄:%d\n", s.name, s.age);
  85. }
  86. // 定义修改学生信息的函数(使用指针)
  87. void updateScore(struct Student *s, float new_score) {
  88. s->score = new_score;
  89. printf("已将%s的成绩更新为:%.1f\n", s->name, s->score);
  90. }
  91. printStudent(stu1); // 值传递
  92. updateScore(&stu1, 95.0); // 指针传递,可以修改原结构体
  93. // 9. 结构体大小与内存对齐
  94. printf("\n=== 结构体大小与内存对齐 ===\n");
  95. printf("struct Student 大小:%lu 字节\n", sizeof(struct Student));
  96. printf("struct Employee 大小:%lu 字节\n", sizeof(struct Employee));
  97. printf("stu1 地址:%p\n", (void*)&stu1);
  98. printf("stu1.id 地址:%p\n", (void*)&stu1.id);
  99. printf("stu1.name 地址:%p\n", (void*)&stu1.name);
  100. printf("stu1.age 地址:%p\n", (void*)&stu1.age);
  101. printf("stu1.score 地址:%p\n", (void*)&stu1.score);
  102. return 0;
  103. }
复制代码

代码说明:

  1. 结构体定义:展示了如何定义包含不同类型成员的结构体。
  2. 变量声明与初始化:演示了三种初始化方式,包括C99的指定成员初始化。
  3. 成员访问:使用点运算符(.)访问结构体成员。
  4. 结构体数组:创建和遍历结构体数组,适合管理多个相同类型的记录。
  5. 结构体指针:使用箭头运算符(->)通过指针访问和修改结构体成员。
  6. 结构体嵌套:展示了结构体中可以包含其他结构体作为成员。
  7. 结构体作为函数参数:演示了值传递和指针传递的区别,指针传递可以修改原结构体。
  8. 内存对齐:打印结构体大小和成员地址,帮助理解内存布局。

运行结果示例:

  1. === 结构体基础 ===
  2. 学生1:学号=1001, 姓名=张三, 年龄=20, 成绩=85.5
  3. 学生2:学号=1002, 姓名=李四, 年龄=21, 成绩=90.0
  4. 学生3:学号=1003, 姓名=王五, 年龄=22, 成绩=78.5
  5. === 结构体数组 ===
  6. 学生2001:小明,成绩:88.0
  7. 学生2002:小红,成绩:92.5
  8. 学生2003:小刚,成绩:76.0
  9. === 结构体指针 ===
  10. 通过指针访问:姓名=张三, 成绩=85.5
  11. 修改后成绩:88.0
  12. === 结构体嵌套 ===
  13. 员工:赵经理 (工号:5001)
  14. 入职日期:2018年5月15日
  15. 月薪:15000.00元
  16. === 结构体作为函数参数 ===
  17. 函数内打印:张三,年龄:20
  18. 已将张三的成绩更新为:95.0
  19. === 结构体大小与内存对齐 ===
  20. struct Student 大小:64 字节
  21. struct Employee 大小:68 字节
  22. stu1 地址:0x7ffc5a3b4a10
  23. stu1.id 地址:0x7ffc5a3b4a10
  24. stu1.name 地址:0x7ffc5a3b4a14
  25. stu1.age 地址:0x7ffc5a3b4a64
  26. stu1.score 地址:0x7ffc5a3b4a68
复制代码

关键理解点:

  1. 结构体是自定义的复合数据类型,可以包含多个不同类型的成员。
  2. 点运算符(.)用于直接访问结构体变量成员,箭头运算符(->)用于通过指针访问。
  3. 结构体作为函数参数时,值传递会复制整个结构体(可能开销大),指针传递更高效。
  4. 结构体在内存中可能不是紧凑存储,编译器会进行内存对齐以提高访问效率。
  5. 结构体嵌套可以构建更复杂的数据结构,如员工信息中包含入职日期。

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 位操作深入

  • 位运算符的实战应用
  • 位字段 (bit-field)

第五阶段:算法、数据结构与项目实战 (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和指针》。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2026 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行