在 C 语言程序设计中当需要管理批量且类型相同的数据时数组是最基础、最高效的数据结构。1. 一维数组的创建、初始化与类型本质1.1 语法形式与元素定义一维数组是具有相同类型元素的线性集合。其创建的标准语法形式为Ctype arr_name[常量值];type决定了数组中每个元素的数据类型如char、int、float等。arr_name为数组标识符。[]内的常量值用于显式指定数组的大小即元素的个数且该数值在传统 C 语言语法中不能为 0 或变量。1.2 初始化缺省规则与清零小技巧初始化是指在创建数组的同时为内存空间赋予初值。主要分为以下三种场景完全初始化指定的初值个数与数组大小完全一致。Cint arr[5] {1, 2, 3, 4, 5};不完全初始化指定的初值个数少于数组大小。Cint arr[10] {1, 2, 3};关键规则未被初始化的剩余元素编译器会自动将其默认初始化为0。利用这一缺省规则我们在开发中常使用int arr[10] {0};来将整个数组快速清零。省略大小创建如果在创建数组时提供了完整的初始值列表可以省略[]中的常量值。Cint arr[] {1, 2, 3, 4}; // 编译器会自动推导数组大小为 41.3 数组的类型本质在高级语法与指针运算中必须明确数组本身也是一种复合的自定义类型。判定方法去掉数组的名字剩下的部分就是该数组的类型。示例说明int arr1[10];的数组类型是int [10]。char ch[5];的数组类型是char [5]。数组的类型不仅决定了其所占内存的总字节数更决定了后续指针步长运算的底层逻辑。2. 下标控制与一维数组的内存存储2.1 下标引用操作符[]与边界C 语言规定数组中所有元素都有一个从0开始的编号称为下标。若数组包含 $n$ 个元素则最大有效下标为 $n-1$。程序通过下标引用操作符[]访问特定位置的代码。例如arr[0]访问首元素arr[7]访问第 8 个元素。在复习时需格外注意越界访问陷阱C 语言编译器本身不会在编译期检查下标是否越界运行期的越界访问会引发未定义行为如篡改栈区其他变量的内容。2.2 物理内存的绝对连续性为了深入理解数组的读取效率可以通过打印地址来观察其底层存储行为Cint arr[5] {1, 2, 3, 4, 5}; for(int i 0; i 5; i) { printf(arr[%d] %p\n, i, arr[i]); }观察输出的十六进制地址可以发现随着数组下标的递增内存地址呈现出严格的由小到大的连续变化。相邻两个元素之间的地址差值精确等于该数据类型所占的字节数如int类型相邻元素地址恰好相差 4 个字节。结论一维数组在物理内存中是严格连续存放的。正是由于这种连续存储的特性CPU 才能通过首地址加偏移量的数学公式实现 $O(1)$ 时间复杂度的随机访问。2.3 动态计算元素个数的万能公式在遍历数组或作为函数参数传递时为了提高代码的可移植性不应将循环边界固定写死。应当使用sizeof操作符动态计算元素个数Cint arr[10] {0}; int sz sizeof(arr) / sizeof(arr[0]);sizeof(arr)计算的是整个数组在内存中所占用的总字节数此处为 $4 \times 10 40$ 字节。sizeof(arr[0])计算的是数组单个首元素所占用的字节数此处为 4 字节。二者相除即可在编译期精确得到数组的元素个数10。无论后续如何修改数组的初始声明大小该公式都能自动适配更新。3. 二维数组一维数组的折叠与内存真相3.1 创建与逻辑表格结构二维数组在逻辑上常被理解为带有行和列的二维表格。其定义语法为Ctype arr_name[行数][列数]; // 例如int arr[3][5]; 代表一个 3 行 5 列的整型网格3.2 初始化组合与省略规则二维数组的初始化同样依托大括号但拥有更精细的按行划定规则按行初始化使用嵌套的大括号明确指定每一行的数据未填满的列自动补0。Cint arr[3][5] {{1, 2}, {3, 4}, {5, 6}};完全平铺初始化不使用内层大括号数据按从左到右、从上到下的顺序依次填满整个物理空间。Cint arr[3][5] {1, 2, 3, 4, 5, 6, 7};关键省略规则核心考点在对二维数组进行初始化时行数可以省略但列数绝对不能省略。Cint arr1[][5] {1, 2, 3, 4, 5, 6}; // 合法编译器会根据列数 5 推导出行数为 2 int arr2[3][] {{1, 2}, {3, 4}}; // 非法编译报错底层原因列数决定了每一行物理空间的边界。如果缺少列数编译器将无法知晓何时应该“换行”并划分出下一行的一维数组从而无法计算出准确的内存跨度。3.3 二维数组的内存真相尽管在逻辑上二维数组是一个表格但在物理内存中二维数组依然是一条连续的直线。通过打印二维数组所有元素的地址可以发现第一行最后一个元素arr[0][4]的下一个字节紧挨着的就是第二行的首元素arr[1][0]。底层本质二维数组本质上是一个一维数组只不过它的每一个元素都是另一个“包含固定列数的一维数组”。也就是说arr[3][5]是一个包含 3 个元素的一维数组每个元素的类型是int [5]。这一结论是后续进阶研究“数组指针”的理论根基。4. C99 标准特性变长数组 (VLA) 的边界4.1 运行期长度确定在 C99 标准出台前C语言创建数组时[]内必须是编译期就能确定的常量表达式。C99 引入了变长数组Variable-Length Array简称 VLA特性允许使用变量来指定数组的大小Cint n; scanf(%d, n); int arr[n]; // 数组的大小在程序运行到此处时根据 n 的当前值动态分配4.2 硬性限制与误区辨析严禁初始化核心考点变长数组的根本特征是其大小直到运行期才被分配而初始化操作是在编译期由编译器构建的初值表。因此变长数组在声明时绝对不能进行初始化例如int arr[n] {0};会导致直接编译报错。“变长”的真实含义变长数组指的是“创建时的大小是可变的取决于变量”而不是指创建之后大小还可以随意拉伸或缩短。数组一旦在运行期被创建出来其生命周期内的大小就彻底固定无法再做改变。开发环境注意在 VS2026 (MSVC 编译器) 中默认对 C99 变长数组的特性的支持并不完整。若要测试或编写包含变长数组的代码通常需要在 GCC 编译器如 Linux 环境或开源的集成工具下才能成功编译运行。5. 算法实战与下标精细化控制数组在实际算法开发中的核心在于对左右边界和中间下标的精准调配。以下展示两项经典的数组控制算法框架。5.1 字符从两端移动向中间汇聚双指针控制该算法通过左右两个下标作为“双指针”同时向中间逼近通过精准控制边界实现覆盖效果C#include stdio.h #include string.h #include windows.h // 引入 Windows API 以调用 Sleep 函数 int main() { char arr1[] Welcome to C Language!!; char arr2[] #######################; int left 0; int right strlen(arr1) - 1; // 锁定右侧有效字符的末尾下标 while (left right) { arr2[left] arr1[left]; arr2[right] arr1[right]; printf(%s\r, arr2); // \r 使光标回到行首实现原地刷新动画效果 Sleep(500); // 延迟 500 毫秒 left; right--; } printf(\n); return 0; }5.2 高性能二分查找折半查找及溢出优化二分查找用于在已排序升序或降序的数组中高效检索特定值其核心是通过不断折半区间将检索时间复杂度降到 O(log N)。C#include stdio.h int main() { int arr[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int key 7; // 设定目标查找值 int sz sizeof(arr) / sizeof(arr[0]); int left 0; int right sz - 1; int find 0; // 查找标记位 int mid 0; while (left right) { // 核心考点避坑优化写法 // 不推荐使用 mid (left right) / 2; 当 left 和 right 数值极大时二者相加可能超出 int 的最大范围导致整型溢出。 // 推荐使用下面的防溢出公式 mid left (right - left) / 2; if (arr[mid] key) { right mid - 1; // 目标值在左半区间更新右边界 } else if (arr[mid] key) { left mid 1; // 目标值在右半区间更新左边界 } else { find 1; break; // 命中目标跳出循环 } } if (1 find) { printf(找到了目标的下标是%d\n, mid); } else { printf(找不到该元素\n); } return 0; }
学习笔记:C 语言数组全解析
在 C 语言程序设计中当需要管理批量且类型相同的数据时数组是最基础、最高效的数据结构。1. 一维数组的创建、初始化与类型本质1.1 语法形式与元素定义一维数组是具有相同类型元素的线性集合。其创建的标准语法形式为Ctype arr_name[常量值];type决定了数组中每个元素的数据类型如char、int、float等。arr_name为数组标识符。[]内的常量值用于显式指定数组的大小即元素的个数且该数值在传统 C 语言语法中不能为 0 或变量。1.2 初始化缺省规则与清零小技巧初始化是指在创建数组的同时为内存空间赋予初值。主要分为以下三种场景完全初始化指定的初值个数与数组大小完全一致。Cint arr[5] {1, 2, 3, 4, 5};不完全初始化指定的初值个数少于数组大小。Cint arr[10] {1, 2, 3};关键规则未被初始化的剩余元素编译器会自动将其默认初始化为0。利用这一缺省规则我们在开发中常使用int arr[10] {0};来将整个数组快速清零。省略大小创建如果在创建数组时提供了完整的初始值列表可以省略[]中的常量值。Cint arr[] {1, 2, 3, 4}; // 编译器会自动推导数组大小为 41.3 数组的类型本质在高级语法与指针运算中必须明确数组本身也是一种复合的自定义类型。判定方法去掉数组的名字剩下的部分就是该数组的类型。示例说明int arr1[10];的数组类型是int [10]。char ch[5];的数组类型是char [5]。数组的类型不仅决定了其所占内存的总字节数更决定了后续指针步长运算的底层逻辑。2. 下标控制与一维数组的内存存储2.1 下标引用操作符[]与边界C 语言规定数组中所有元素都有一个从0开始的编号称为下标。若数组包含 $n$ 个元素则最大有效下标为 $n-1$。程序通过下标引用操作符[]访问特定位置的代码。例如arr[0]访问首元素arr[7]访问第 8 个元素。在复习时需格外注意越界访问陷阱C 语言编译器本身不会在编译期检查下标是否越界运行期的越界访问会引发未定义行为如篡改栈区其他变量的内容。2.2 物理内存的绝对连续性为了深入理解数组的读取效率可以通过打印地址来观察其底层存储行为Cint arr[5] {1, 2, 3, 4, 5}; for(int i 0; i 5; i) { printf(arr[%d] %p\n, i, arr[i]); }观察输出的十六进制地址可以发现随着数组下标的递增内存地址呈现出严格的由小到大的连续变化。相邻两个元素之间的地址差值精确等于该数据类型所占的字节数如int类型相邻元素地址恰好相差 4 个字节。结论一维数组在物理内存中是严格连续存放的。正是由于这种连续存储的特性CPU 才能通过首地址加偏移量的数学公式实现 $O(1)$ 时间复杂度的随机访问。2.3 动态计算元素个数的万能公式在遍历数组或作为函数参数传递时为了提高代码的可移植性不应将循环边界固定写死。应当使用sizeof操作符动态计算元素个数Cint arr[10] {0}; int sz sizeof(arr) / sizeof(arr[0]);sizeof(arr)计算的是整个数组在内存中所占用的总字节数此处为 $4 \times 10 40$ 字节。sizeof(arr[0])计算的是数组单个首元素所占用的字节数此处为 4 字节。二者相除即可在编译期精确得到数组的元素个数10。无论后续如何修改数组的初始声明大小该公式都能自动适配更新。3. 二维数组一维数组的折叠与内存真相3.1 创建与逻辑表格结构二维数组在逻辑上常被理解为带有行和列的二维表格。其定义语法为Ctype arr_name[行数][列数]; // 例如int arr[3][5]; 代表一个 3 行 5 列的整型网格3.2 初始化组合与省略规则二维数组的初始化同样依托大括号但拥有更精细的按行划定规则按行初始化使用嵌套的大括号明确指定每一行的数据未填满的列自动补0。Cint arr[3][5] {{1, 2}, {3, 4}, {5, 6}};完全平铺初始化不使用内层大括号数据按从左到右、从上到下的顺序依次填满整个物理空间。Cint arr[3][5] {1, 2, 3, 4, 5, 6, 7};关键省略规则核心考点在对二维数组进行初始化时行数可以省略但列数绝对不能省略。Cint arr1[][5] {1, 2, 3, 4, 5, 6}; // 合法编译器会根据列数 5 推导出行数为 2 int arr2[3][] {{1, 2}, {3, 4}}; // 非法编译报错底层原因列数决定了每一行物理空间的边界。如果缺少列数编译器将无法知晓何时应该“换行”并划分出下一行的一维数组从而无法计算出准确的内存跨度。3.3 二维数组的内存真相尽管在逻辑上二维数组是一个表格但在物理内存中二维数组依然是一条连续的直线。通过打印二维数组所有元素的地址可以发现第一行最后一个元素arr[0][4]的下一个字节紧挨着的就是第二行的首元素arr[1][0]。底层本质二维数组本质上是一个一维数组只不过它的每一个元素都是另一个“包含固定列数的一维数组”。也就是说arr[3][5]是一个包含 3 个元素的一维数组每个元素的类型是int [5]。这一结论是后续进阶研究“数组指针”的理论根基。4. C99 标准特性变长数组 (VLA) 的边界4.1 运行期长度确定在 C99 标准出台前C语言创建数组时[]内必须是编译期就能确定的常量表达式。C99 引入了变长数组Variable-Length Array简称 VLA特性允许使用变量来指定数组的大小Cint n; scanf(%d, n); int arr[n]; // 数组的大小在程序运行到此处时根据 n 的当前值动态分配4.2 硬性限制与误区辨析严禁初始化核心考点变长数组的根本特征是其大小直到运行期才被分配而初始化操作是在编译期由编译器构建的初值表。因此变长数组在声明时绝对不能进行初始化例如int arr[n] {0};会导致直接编译报错。“变长”的真实含义变长数组指的是“创建时的大小是可变的取决于变量”而不是指创建之后大小还可以随意拉伸或缩短。数组一旦在运行期被创建出来其生命周期内的大小就彻底固定无法再做改变。开发环境注意在 VS2026 (MSVC 编译器) 中默认对 C99 变长数组的特性的支持并不完整。若要测试或编写包含变长数组的代码通常需要在 GCC 编译器如 Linux 环境或开源的集成工具下才能成功编译运行。5. 算法实战与下标精细化控制数组在实际算法开发中的核心在于对左右边界和中间下标的精准调配。以下展示两项经典的数组控制算法框架。5.1 字符从两端移动向中间汇聚双指针控制该算法通过左右两个下标作为“双指针”同时向中间逼近通过精准控制边界实现覆盖效果C#include stdio.h #include string.h #include windows.h // 引入 Windows API 以调用 Sleep 函数 int main() { char arr1[] Welcome to C Language!!; char arr2[] #######################; int left 0; int right strlen(arr1) - 1; // 锁定右侧有效字符的末尾下标 while (left right) { arr2[left] arr1[left]; arr2[right] arr1[right]; printf(%s\r, arr2); // \r 使光标回到行首实现原地刷新动画效果 Sleep(500); // 延迟 500 毫秒 left; right--; } printf(\n); return 0; }5.2 高性能二分查找折半查找及溢出优化二分查找用于在已排序升序或降序的数组中高效检索特定值其核心是通过不断折半区间将检索时间复杂度降到 O(log N)。C#include stdio.h int main() { int arr[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int key 7; // 设定目标查找值 int sz sizeof(arr) / sizeof(arr[0]); int left 0; int right sz - 1; int find 0; // 查找标记位 int mid 0; while (left right) { // 核心考点避坑优化写法 // 不推荐使用 mid (left right) / 2; 当 left 和 right 数值极大时二者相加可能超出 int 的最大范围导致整型溢出。 // 推荐使用下面的防溢出公式 mid left (right - left) / 2; if (arr[mid] key) { right mid - 1; // 目标值在左半区间更新右边界 } else if (arr[mid] key) { left mid 1; // 目标值在右半区间更新左边界 } else { find 1; break; // 命中目标跳出循环 } } if (1 find) { printf(找到了目标的下标是%d\n, mid); } else { printf(找不到该元素\n); } return 0; }