跳到主要内容
🔍

代码示例: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 调试章节展开)。
    #
    # 一句可背的结论:数组越界 = 未定义行为;标准不保证报错,也不保证"看起来正常"。