引言数组是C语言中最基础也是最重要的数据结构之一它允许我们存储多个相同类型的数据元素并通过索引快速访问。本文将通过5个经典代码示例系统总结C语言数组的全部核心知识点包括一维数组、二维数组、数组与指针的关系等并对每个知识点进行深入拓展帮助你彻底掌握C语言数组的精髓。一、一维数组基础1.1 一维数组的定义与初始化一维数组是最简单的数组形式它由连续的内存位置组成每个位置存储一个相同类型的元素。代码实例#include stdio.h int main() { // 方式1完整初始化指定数组长度 int arr1[5] {1, 2, 3, 4, 5}; // 方式2省略数组长度由初始化列表自动确定 int arr2[] {3, 4, 17, 8, 31, 2, 9, 15}; // 长度为8 // 方式3部分初始化未初始化的元素自动为0 int arr3[5] {1, 2}; // 等价于 {1, 2, 0, 0, 0} // 方式4全部初始化为0 int arr4[5] {0}; // 所有元素都是0 // 错误示例初始化元素个数超过数组长度 // int arr5[3] {1, 2, 3, 4}; // 编译错误 return 0; }1.2 数组长度的计算sizeof运算符在C语言中我们可以使用sizeof运算符来计算数组的总字节数然后除以单个元素的字节数得到数组的元素个数。代码实例#include stdio.h int main() { int a[] {3, 4, 17, 8, 31, 2, 9, 15}; // 计算数组总字节数 printf(数组总字节数%zu\n, sizeof(a)); // 输出328个int每个4字节 // 计算单个int元素的字节数 printf(单个int字节数%zu\n, sizeof(int)); // 输出4 // 计算数组元素个数 int n sizeof(a) / sizeof(int); printf(数组元素个数%d\n, n); // 输出8 // 更通用的写法不依赖元素类型 int n2 sizeof(a) / sizeof(a[0]); printf(数组元素个数通用写法%d\n, n2); // 输出8 return 0; }1.3 一维数组的遍历数组的遍历是指依次访问数组中的每个元素通常使用for循环来实现。代码实例#include stdio.h int main() { int a[] {3, 4, 17, 8, 31, 2, 9, 15}; int n sizeof(a) / sizeof(a[0]); // 方式1使用下标访问 printf(使用下标访问); for (int i 0; i n; i) { printf(%d , a[i]); } printf(\n); // 方式2使用指针访问 printf(使用指针访问); for (int i 0; i n; i) { printf(%d , *(a i)); } printf(\n); return 0; }1.4 应用实例冒泡排序冒泡排序是一种简单的排序算法它重复地走访过要排序的数列一次比较两个元素如果它们的顺序错误就把它们交换过来。代码实例#include stdio.h int main() { // 初始化8个int的数组 int a[] {3, 4, 17, 8, 31, 2, 9, 15}; int n, i, j; int t; // 临时变量用于交换两个数组元素时的中转存储 // 计算数组的元素总个数 n sizeof(a) / sizeof(int); // 冒泡排序外层循环控制排序的总轮数 // n个元素排序最多只需要n-1轮因为每一轮都会确定1个最大元素的最终位置 for (i 0; i n - 1; i) { // 冒泡排序内层循环控制每一轮的相邻元素比较与交换 // 每完成i轮排序数组末尾的i个元素已经是有序的无需再比较 // 因此每轮只需要比较到n-1-i的位置减少无效循环 for (j 0; j n - 1 - i; j) { // 判断相邻两个元素前一个大于后一个不符合升序 if (a[j] a[j 1]) { // 用临时变量t交换两个相邻元素的位置 // 把较大的元素往后移实现大数冒泡到数组末尾 t a[j]; a[j] a[j 1]; a[j 1] t; } } } // 循环遍历数组逐个输出元素 printf(排序后的数组); for (i 0; i n; i) { printf(%d , a[i]); } printf(\n); return 0; }一维数组基础知识点汇总表知识点说明示例数组定义类型 数组名[长度];int arr[5];完整初始化提供所有元素的初始值int arr[5] {1, 2, 3, 4, 5};省略长度初始化由初始化列表自动确定数组长度int arr[] {1, 2, 3};部分初始化未初始化的元素自动为0int arr[5] {1, 2};全部初始化为0只写一个0即可int arr[5] {0};数组长度计算sizeof(数组名) / sizeof(元素类型)int n sizeof(arr) / sizeof(int);通用长度计算sizeof(数组名) / sizeof(数组名[0])int n sizeof(arr) / sizeof(arr[0]);下标访问数组名[索引]索引从0开始arr[0] 10;指针访问*(数组名 索引)*(arr 0) 10;冒泡排序每轮将最大元素冒泡到末尾见上述代码实例二、二维数组基础2.1 二维数组的定义与初始化二维数组可以看作是一个表格有行和列两个维度。在C语言中二维数组实际上是数组的数组即一个一维数组它的每个元素又是一个一维数组。代码实例#include stdio.h int main() { // 方式1完整初始化指定行数和列数 int arr1[3][2] {{1, 6}, {9, 12}, {61, 12}}; // 方式2省略内层大括号按顺序初始化 int arr2[3][2] {1, 6, 9, 12, 61, 12}; // 与arr1等价 // 方式3部分初始化未初始化的元素自动为0 int arr3[3][2] {{1}, {9}, {61}}; // 等价于 {{1,0}, {9,0}, {61,0}} // 方式4省略行数由初始化列表自动确定 int arr4[][2] {{1, 6}, {9, 12}, {61, 12}}; // 行数为3 // 方式5全部初始化为0 int arr5[10][10] {{0}}; // 所有元素都是0 // 错误示例不能省略列数 // int arr6[3][] {{1, 6}, {9, 12}, {61, 12}}; // 编译错误 return 0; }2.2 二维数组的内存布局二维数组在内存中是按行优先的顺序连续存储的即先存储第一行的所有元素再存储第二行的所有元素以此类推。代码实例#include stdio.h int main() { int a[3][2] {{1, 6}, {9, 12}, {61, 12}}; // 打印每个元素的地址观察内存布局 printf(二维数组内存布局\n); for (int i 0; i 3; i) { for (int j 0; j 2; j) { printf(a[%d][%d] %d, 地址%p\n, i, j, a[i][j], a[i][j]); } } return 0; }输出结果分析a[0][0] 1, 地址0x7ffd8b7c5a40 a[0][1] 6, 地址0x7ffd8b7c5a44 // 比a[0][0]大4字节一个int a[1][0] 9, 地址0x7ffd8b7c5a48 // 比a[0][1]大4字节 a[1][1] 12, 地址0x7ffd8b7c5a4c // 比a[1][0]大4字节 a[2][0] 61, 地址0x7ffd8b7c5a50 // 比a[1][1]大4字节 a[2][1] 12, 地址0x7ffd8b7c5a54 // 比a[2][0]大4字节可以看到二维数组的元素在内存中是连续存储的按行优先排列。2.3 二维数组的遍历二维数组的遍历需要使用嵌套的for循环外层循环控制行内层循环控制列。代码实例#include stdio.h int main() { int a[3][2] {{1, 6}, {9, 12}, {61, 12}}; // 方式1使用下标访问 printf(使用下标访问\n); for (int i 0; i 3; i) { for (int j 0; j 2; j) { printf(%d , a[i][j]); } printf(\n); } // 方式2使用指针访问利用内存连续性 printf(\n使用指针访问内存连续\n); int *p a[0][0]; for (int i 0; i 3 * 2; i) { printf(%d , *(p i)); } printf(\n); return 0; }2.4 应用实例杨辉三角杨辉三角是一个经典的数学问题它的特点是每行的第一个和最后一个元素都是1中间的每个元素等于它上方两个元素之和代码实例#include stdio.h int main() { // 定义10行10列的二维整型数组用于存储杨辉三角的数值 // 初始化将所有元素初始化为0 int a[10][10] {{0}}; int i, j; // 循环控制变量i为行号j为列号 // 外层循环遍历10行计算每一行的数值 for (i 0; i 10; i) { // 杨辉三角核心规则1每一行的第0列第一个元素固定为1 a[i][0] 1; // 内层循环计算当前行的中间元素首尾元素固定 // 循环条件j i列号j从1开始到i-1结束不包含i for (j 1; j i; j) { // 杨辉三角核心规则2中间元素上一行左上角元素上一行正上方元素 a[i][j] a[i-1][j-1] a[i-1][j]; } } // 外层循环遍历10行逐行打印杨辉三角 printf(杨辉三角前10行\n); for (i 0; i 10; i) { // 内层循环打印当前行的所有元素列号j从0到i第i行有i1个元素 for (j 0; j i; j) { // %-8d表示左对齐输出整型固定占8个字符宽度 printf(%-8d, a[i][j]); } putchar(\n); } return 0; }二维数组基础知识点汇总表知识点说明示例二维数组定义类型 数组名[行数][列数];int arr[3][2];完整初始化提供所有元素的初始值int arr[3][2] {{1,6}, {9,12}, {61,12}};省略内层大括号按行优先顺序初始化int arr[3][2] {1,6,9,12,61,12};部分初始化未初始化的元素自动为0int arr[3][2] {{1}, {9}};省略行数由初始化列表自动确定行数int arr[][2] {{1,6}, {9,12}};全部初始化为0只写一个{0}即可int arr[10][10] {{0}};内存布局按行优先连续存储见上述代码实例下标访问数组名[行索引][列索引]arr[0][0] 10;指针访问连续利用内存连续性用int*遍历int *p arr[0][0]; *(pi);杨辉三角每行首尾为1中间元素上两元素之和见上述代码实例三、数组与指针的深度关系3.1 数组名的退化机制在C语言中数组名在大多数情况下会退化为指向数组第一个元素的指针。但有两个例外当数组名作为sizeof运算符的操作数时当数组名作为取地址运算符的操作数时代码实例#include stdio.h int main() { int a[5] {1, 2, 3, 4, 5}; // 数组名a退化为指向第一个元素的指针int* printf(a %p\n, a); // 输出数组首地址 printf(a[0] %p\n, a[0]); // 输出与a相同 // 例外1sizeof(a)计算整个数组的字节数而不是指针的大小 printf(sizeof(a) %zu\n, sizeof(a)); // 输出205个int每个4字节 printf(sizeof(int*) %zu\n, sizeof(int*)); // 输出864位系统 // 例外2a是指向整个数组的指针int (*)[5]而不是指向第一个元素的指针 printf(a %p\n, a); // 输出与a相同地址值相同但类型不同 printf(a 1 %p\n, a 1); // 输出首地址4字节跳过一个int printf(a 1 %p\n, a 1); // 输出首地址20字节跳过整个数组 return 0; }3.2 一维数组与指针一维数组名退化为指向第一个元素的指针因此我们可以使用指针来操作一维数组。代码实例#include stdio.h int main() { int a[5] {1, 2, 3, 4, 5}; int *p a; // 数组名a退化为int*赋值给指针p // 使用指针访问数组元素 printf(使用指针访问数组元素\n); for (int i 0; i 5; i) { printf(*(p %d) %d\n, i, *(p i)); } // 指针的算术运算 printf(\n指针的算术运算\n); printf(p %p\n, p); printf(p 1 %p\n, p 1); // 加1跳过一个int4字节 printf(p 2 %p\n, p 2); // 加2跳过两个int8字节 // 指针的自增自减运算 printf(\n指针的自增运算\n); int *q a; printf(*q %d\n, *q); // 先取*q的值再q printf(*q %d\n, *q); // 输出2 return 0; }3.3 二维数组与指针数组指针二维数组名退化为指向第一行的数组指针而不是指向第一个元素的指针。数组指针是指向数组的指针它的类型是类型 (*)[列数]。代码实例#include stdio.h int main() { int a[3][2] {{1, 6}, {9, 12}, {61, 12}}; // a是二维数组名退化为指向第一行的数组指针int (*)[2] printf(a %p\n, a); // 输出数组首地址 printf(a 1 %p\n, a 1); // 输出首地址8字节跳过一行2个int // *a对数组指针解引用得到第一行的一维数组名a[0] // a[0]又退化为指向第一个元素的指针int* printf(*a %p\n, *a); // 输出与a相同 printf(*a 1 %p\n, *a 1); // 输出首地址4字节跳过一个int // 访问二维数组元素的多种方式 printf(\n访问二维数组元素的多种方式\n); printf(a[1][0] %d\n, a[1][0]); // 最常用的方式 printf(*(a[1] 0) %d\n, *(a[1] 0)); // 等价于a[1][0] printf(*(*(a 1) 0) %d\n, *(*(a 1) 0)); // 完全用指针表示 printf((*(a 1))[0] %d\n, (*(a 1))[0]); // 另一种等价形式 // 定义数组指针 int (*p)[2] a; // p是指向包含2个int元素的一维数组的指针 printf(\n使用数组指针访问\n); printf((*p)[0] %d\n, (*p)[0]); // 输出1a[0][0] printf((*(p 1))[0] %d\n, (*(p 1))[0]); // 输出9a[1][0] return 0; }3.4 指针数组指针数组是一个数组它的每个元素都是一个指针。指针数组的类型是类型 *数组名[长度]。代码实例#include stdio.h int main() { int a[2][3] {{1, 5, 6}, {9, 8, 12}}; // 定义指针数组包含两个int类型指针的数组 int *pb[2]; // 给指针数组的元素赋值 pb[0] a[0]; // a[0]是第一行的一维数组名退化为int* pb[1] a[1]; // a[1]是第二行的一维数组名退化为int* // 使用指针数组访问二维数组元素 printf(*(pb[0] 1) %d\n, *(pb[0] 1)); // 输出5a[0][1] printf(*(pb[1] 1) %d\n, *(pb[1] 1)); // 输出8a[1][1] // 指针数组与数组指针的区别 printf(\n指针数组与数组指针的大小区别\n); printf(sizeof(pb) %zu\n, sizeof(pb)); // 输出162个指针每个8字节 int (*p)[3] a; printf(sizeof(p) %zu\n, sizeof(p)); // 输出8一个指针 return 0; }3.5 常见错误与陷阱在使用数组与指针时有一些常见的错误需要特别注意。代码实例#include stdio.h int main() { int a[3][2] {{1, 6}, {9, 12}, {61, 12}}; int *p1, *p2, *p3; // 错误1类型不匹配 p1 a; // 报警告类型不匹配 // a是int (*)[2]数组指针p1是int* // 虽然地址值相同但类型不同编译器会报警告 // 正确写法 p2 a[0]; // a[0]是int*类型匹配 // 错误2错误的指针赋值 // p3 a[0][0]; // 严重错误 // a[0][0]是int类型的值1直接赋值给指针p3 // 相当于让p3指向内存地址0x00000001这是操作系统保留的非法地址 // 正确写法 p3 a[0][0]; // 取a[0][0]的地址赋值给p3 // 错误3数组越界访问 // int arr[5] {1, 2, 3, 4, 5}; // printf(%d\n, arr[5]); // 数组越界行为未定义 return 0; }数组与指针知识点汇总表知识点说明示例数组名退化大多数情况下数组名退化为指向第一个元素的指针int *p a;退化例外1sizeof(数组名)计算整个数组的字节数sizeof(a) 205个int退化例外2数组名是指向整个数组的指针a 1跳过整个数组一维数组与指针数组名退化为int*可用指针访问元素*(a i) a[i]二维数组名退化退化为指向第一行的数组指针int (*p)[2] a;数组指针指向数组的指针类型类型 (*)[列数]int (*p)[2];指针数组元素为指针的数组类型类型 *数组名[长度]int *pb[2];二维数组元素访问a[i][j] *(a[i] j) *(*(a i) j)a[1][0] *(*(a1)0)常见错误1数组指针与int*类型不匹配int *p a;a是二维数组名常见错误2将值直接赋值给指针int *p 1;常见错误3数组越界访问arr[5]数组长度为5四、格式化输出技巧在数组的输出中我们经常需要使用格式化输出函数printf来控制输出的格式使输出更加整齐美观。4.1 基本格式化输出代码实例#include stdio.h int main() { int a[] {3, 4, 17, 8, 31, 2, 9, 15}; int n sizeof(a) / sizeof(a[0]); // %d输出十进制整数 printf(基本输出); for (int i 0; i n; i) { printf(%d , a[i]); } printf(\n); // %5d右对齐输出固定占5个字符宽度 printf(右对齐输出); for (int i 0; i n; i) { printf(%5d, a[i]); } printf(\n); // %-5d左对齐输出固定占5个字符宽度 printf(左对齐输出); for (int i 0; i n; i) { printf(%-5d, a[i]); } printf(\n); // %*s动态宽度控制输出指定宽度的空字符串 printf(动态宽度输出); for (int i 0; i n; i) { printf(%d%*s, a[i], 2, ); // 每个元素后输出2个空格 } printf(\n); return 0; }格式化输出知识点汇总表格式符说明示例输出效果%d输出十进制整数printf(%d, 123);123%5d右对齐输出固定占5个字符宽度printf(%5d, 123);123%-5d左对齐输出固定占5个字符宽度printf(%-5d, 123);123%*s动态宽度控制输出指定宽度的空字符串printf(%d%*s, 123, 2, );123putchar(\n)输出换行符putchar(\n);换行总结本文通过5个经典代码示例系统总结了C语言数组的全部核心知识点包括一维数组基础定义、初始化、长度计算、遍历和冒泡排序二维数组基础定义、初始化、内存布局、遍历和杨辉三角数组与指针的深度关系数组名的退化机制、一维数组与指针、二维数组与数组指针、指针数组格式化输出技巧基本格式化输出、对齐控制和动态宽度控制每个知识点都配有详细的代码实例和注释并在每个部分结束后提供了汇总表格方便你快速查阅和记忆。掌握这些知识点你就能够熟练地使用C语言数组来解决各种编程问题。