代码示例:1.2.5 数组
全部示例均标注编译命令、适用环境与实测状态;完整验证记录见《03.5-代码编译运行验证记录》。
examples/ex1-array-basics.c · 一维数组:部分初始化补零 / 长度推导 / 数组名即首元素地址
/* ex1-array-basics.c —— 一维数组:初始化 / 遍历 / 补零 / 长度推导 / 数组名即地址(1.2.5) * 适用环境:Linux / Windows(MinGW) 标准:C11 * 编译:gcc ex1-array-basics.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic * 运行:Linux: ./ex1 Windows: ex1.exe * 要点:① 部分初始化时未列出的元素自动补 0;② 长度推导 sizeof(b)/sizeof(b[0]); * ③ 数组名是首元素地址(a 与 &a[0] 相等)——数组名是常量,不能赋值(1.3.1 展开) */ #include <stdio.h> int main(void) { int a[5] = {10, 20, 30}; /* 后两个元素自动补 0 */ int b[] = {1, 2, 3}; /* 长度由初始化列表推导 = 3 */ int i; printf("部分初始化(声明长度 5,只给 3 个值):\n"); for (i = 0; i < 5; i++) { printf("a[%d] = %d\n", i, a[i]); } printf("长度推导: sizeof(b)/sizeof(b[0]) = %d\n", (int)(sizeof(b) / sizeof(b[0]))); printf("数组名是首元素地址: a = %p, &a[0] = %p\n", (void *)a, (void *)&a[0]); return 0; }examples/ex2-2d-array.c · 二维数组行优先布局(打印每个元素的地址)
/* ex2-2d-array.c —— 二维数组:行优先内存布局(1.2.5) * 适用环境:Linux / Windows(MinGW) 标准:C11 * 编译:gcc ex2-2d-array.c -o ex2 -std=c11 -Wall -Wextra -Wpedantic * 运行:Linux: ./ex2 Windows: ex2.exe * 要点:观察相邻元素的地址差(本机 int 为 4 字节)——m[0][0]、m[0][1]、m[0][2]、m[1][0]... * 地址依次 +4:证明二维数组在内存里按"行优先"连续存放(地址值每次运行不同,看差值即可) */ #include <stdio.h> int main(void) { int m[2][3] = {{1, 2, 3}, {4, 5, 6}}; int i, j; for (i = 0; i < 2; i++) { for (j = 0; j < 3; j++) { printf("m[%d][%d] = %d @ %p\n", i, j, m[i][j], (void *)&m[i][j]); } } return 0; }examples/ex3-sorts.c · 冒泡/选择/插入三种排序与交换/移动代价统计
/* ex3-sorts.c —— 冒泡/选择/插入三种排序与代价统计(1.2.5) * 适用环境:Linux / Windows(MinGW) 标准:C11 * 编译:gcc ex3-sorts.c -o ex3 -std=c11 -Wall -Wextra -Wpedantic * 运行:Linux: ./ex3 Windows: ex3.exe * 要点:同一组数据 {5,2,8,1,9} 分别用三种排序,统计"交换/移动"次数: * 冒泡统计相邻交换、选择统计每轮一次交换、插入统计元素后移次数——数量级差异即三种算法 * 在"最坏代价"上的区别(复杂度分析在 2.1.1 展开) */ #include <stdio.h> static void print_array(const char *name, const int a[], int n) { int i; printf("%s: ", name); for (i = 0; i < n; i++) { printf("%d ", a[i]); } printf("\n"); } static void bubble_sort(int a[], int n, int *swaps) { int i, j; *swaps = 0; for (i = 0; i < n - 1; i++) { for (j = 0; j < n - 1 - i; j++) { if (a[j] > a[j + 1]) { int tmp = a[j]; a[j] = a[j + 1]; a[j + 1] = tmp; (*swaps)++; } } } } static void selection_sort(int a[], int n, int *swaps) { int i, j, min; *swaps = 0; for (i = 0; i < n - 1; i++) { min = i; for (j = i + 1; j < n; j++) { if (a[j] < a[min]) { min = j; } } if (min != i) { int tmp = a[i]; a[i] = a[min]; a[min] = tmp; (*swaps)++; } } } static void insertion_sort(int a[], int n, int *moves) { int i, key, j; *moves = 0; for (i = 1; i < n; i++) { key = a[i]; j = i - 1; while (j >= 0 && a[j] > key) { a[j + 1] = a[j]; j--; (*moves)++; } a[j + 1] = key; } } int main(void) { int raw[] = {5, 2, 8, 1, 9}; int a[5]; int cost; print_array("原始数据", raw, 5); for (int i = 0; i < 5; i++) a[i] = raw[i]; bubble_sort(a, 5, &cost); print_array("冒泡排序", a, 5); printf(" 相邻交换次数: %d\n", cost); for (int i = 0; i < 5; i++) a[i] = raw[i]; selection_sort(a, 5, &cost); print_array("选择排序", a, 5); printf(" 交换次数: %d\n", cost); for (int i = 0; i < 5; i++) a[i] = raw[i]; insertion_sort(a, 5, &cost); print_array("插入排序", a, 5); printf(" 元素后移次数: %d\n", cost); return 0; }examples/ex4-ub-notes.txt · 数组越界 = 未定义行为(概念讲解,不运行越界代码)
# 1.2.5 数组越界:未定义行为(conceptual 讲解,不编译不运行) # 本文件是"概念讲解型"示例(example_kind=conceptual):只引语言标准,不执行任何越界代码。 # # 为什么本章不运行越界演示? # C 标准(ISO/IEC 9899:2011)把"下标超出数组边界"规定为未定义行为(UB): # - J.2 未定义行为清单:数组下标越界; # - 6.5.6 加法运算符:指针运算结果超出数组对象(含尾后一个位置之外)即未定义。 # 未定义行为的含义:标准对结果不做任何保证——可能读到垃圾值、可能恰好正常、可能段错误崩溃、 # 可能破坏相邻变量;不同编译器、优化级别、运行环境结果不同。 # 因此"跑一次越界程序观察输出"得不到任何可依赖的规律,也不能作为教学结论(违反 # 本课程"预期结果与实测结果严格区分、不依赖偶然行为"的纪律)。 # # 正确学习方式(配合 1.2.4 的循环边界): # 1. 记住安全公式:n 个元素的数组,合法下标是 0..n-1,遍历条件 i < n; # 2. 能说出越界是 UB 而不是"一定崩溃"——面试高频考点; # 3. 用"边界代入法"审查循环(i=0 与 i=n-1 代入验证); # 4. 防御手段预告:编译期 -Wall/-Wextra 对部分越界有警告(如初始化列表超长), # 运行时工具有 AddressSanitizer(-fsanitize=address,5.6 调试章节展开)。 # # 一句可背的结论:数组越界 = 未定义行为;标准不保证报错,也不保证"看起来正常"。