数组概述
为什么需要数组?
用一块连续的内存空间,一次性管理多个相同类型的数据。
什么是数组
定义:数组是相同类型,有序数据的集合。
数组的特征
数组中的每个数据称为元素(也就是匿名的变量空间),所有元素类型相同。
数组中的元素存放在连续的内存空间中(
char player_name[6])申请在内存中开辟6块连续的基于char类型的变量空间。
衍生概念:下标(索引)
下标或者索引代表了数组中元素距离第一个元素的偏移量(本质是每个元素首地址距离第一个元素首地址的偏移量。)
下标 = (每个元素首地址 - 第一个元素首地址)/数组元素类型大小
数组的下标是从0开始:
数组的最大下标 = 数组的元素个数(数组容量或大小)-1
int a:在内存中开辟一个空间,该空间的大小是4个字节int a1,a2,a3,a4,a5;:在内存中开辟连续的5块内存,每一块空间的大小是4个字节,通过变量名访问int arr[5];在内存中开辟连续的5块内存,每一块空间的大小是4个字节,通过数组下标访问(因为数组中的元素都是匿名变量)
一维数组
数组的定义
语法:
数据类型 数组名[数组容量];
注意:数据类型又被称作类型说明符,数组容量又被称作数组元素个数或者数组的大小/长度。
说明:
数组的数据类型由数组中的元素来决定。也就是元素是什么类型,数组就是什么类型,同一个数组中,所有元素的类型都是一致的。
数组名也是标识符,即数据类型为数组的变量名。命名规范与变量相同,区别是变量一般用单数、数组用复数。
数组容量还可以叫常量表达式,其值必须是整数。关于数组容量的类型:
C89标准:只支持常量和符号常量,不支持变量
#define SIZE 5 // 符号常量,使用无参宏定义 int length = 5; // 变量 int arr[5]; // 正确,支持常量(字面量) int arr2[SIZE]; // 正确,支持符号常量 int arr3[length]; // 错误,C89不支持变量
C99标准:引入变长数组(VLA: MSVC(微软编译器)对C99的VLA支持有限,GCC/Clong等编译器支持较好),就是可以使用变量,数组在运行的时候决定大小,举例:
int length = 5; // 创建变量,赋值5 int arr[length]; // C99标准,GCC/Clong支持较好,此时数组大小为5 length = 10; //
类型:代表了数组中元素的类型,数组的空间大小 = 数组中所有空间元素之和
容量:数组中能存多少个元素,数组容量一定是一个整型
深入理解:
定义一个数组,相当于申请了一个可以容纳指定元素个数的内存单元,所申请的内存是连续的。
定义一个数组,相当于定义了多个匿名变量,这些变量因为匿名,所以需要通过数组名[下标]来访问。
范例:
// 定义一个数组 int arr[10]; // 定义一个存储10个int类型元素的数组
数组元素的访问
原则:数组中的元素不能一次性访问所有,只能一个一个访问。
语法:
取值:
[数据类型 变量名] = 数组名[下标];
赋值:
数组名[下标] = 值;
举例:
// 所有举例都是基于局部作用域 // 定义一个存储10个元素的int数组 int arr[10]; // 给数组的第一个元素赋值 arr[0] = 88; // int a = arr[0]; // a = 88
案例:一维数组的遍历
需求:利用循环给数组元素
a[0]~a[9]赋值0~9,并且要逆序输出。代码:
/************************************************************************* > File Name: demo04.c > Author: *** > Description: 一维数组的遍历 > Created Time: 2026年08月08日 星期六 21时29分37秒 ************************************************************************/ #include <stdio.h> int main(int argc,char *argv[]) { // 创建一个数组,用来存储0~9 int arr[10]; // 计算数组的大小 int len = sizeof(arr) / sizeof(arr[0]); // 定义循环变量,同时是数组的下标,从0开始 int i; // 通过for循环给数组元素赋值 for (i = 0; i < len; i++) arr[i] = i; // 逆序输出数组中的元素 for (i = len - 1; i >= 0; i--) printf("%-4d", arr[i]); printf("\n"); return 0; }运行结果:
an-vm:/mnt/hgfs/code/20260806$ gcc demo04.c an-vm:/mnt/hgfs/code/20260806$ ./a.out 9 8 7 6 5 4 3 2 1 0
数组的初始化
说明:所谓的初始化,就是定义数组的时候,用指定的数据给对应的元素赋值
语法:
数据类型 数组名[数组容量] = {...};注意事项:
数组可以部分初始化:也就是可以给数组中的前几个元素初始化,未初始化的元素系统将自动初始化,初始值是
0数组根据初始化的元素自动分配大小:
// 由初始化的元素来决定数组的容量 int arr[]; // 错误,未初始化,必须指定 int arr[] = {11,12,13,14,15}; // 正确,推荐,根据初始化的
排序算法:冒泡排序
排序思想(向前冒泡)
对一组数列一次只排好一个元素,针对n个元素,最差情况需要
n-1次就可以排好;最好情况需要0次(只需要1轮遍历,无交换操作)每次排序假定第一个元素是最大或者最小的。然后相邻的两个元素进行比较,遇到较大或者较小的元素进行交换,访问完数组的最后一个元素,就拍好了一个数。
在余下的元素中,再次应用第2步操作,直到只剩下一个数。
排序推理
例如:将5,4,3,2,1冒泡排序为1,2,3,4,5
排序演示
第⓪轮:5,4,3,2,1 → 4,3,2,1,5比较4次 = 数组长度5 - 轮数0 - 1
第①轮:4,3,2,1,5→ 3,2,1,4,5比较3次 = 数组长度5 - 轮数1 - 1
第②轮:3,2,1,4,5→ 2,1,3,4,5比较2次 = 数组长度5 - 轮数2 - 1
第③轮:2,1,3,4,5→ 1,2,3,4,5比较1次 = 数组长度5 - 轮数3 - 1
总结
案例涉及到5个数列的排序,排序了4轮,得到:轮数 = 数组长度 - 1,我们可以通过外层的for循环实现轮数的遍历。
案例涉及到每一轮中数列的排序次数,得到:次数 = 数组长度 - 轮数 [- 1],我们都可以通过一个内层的for循环实现每一轮的次数的遍历。
每一次比较过程中,两个数涉及到位置的交换,比如 a = 3, b = 4, 交换a,b的数据变为 a = 4, b = 3,应该如何实现:
引入一个临时变量temp,将a的值赋值给temp, int temp = a;
将b的值赋值给a, a = b;
将temp的值赋值给b,b = temp;
代码实现:
/************************************************************************* > File Name: demo03.c > Author: 李昕怡 > Description: 冒泡排序 > Created Time: 2026年08月09日 星期日 13时01分17秒 ************************************************************************/ #include <stdio.h> // 定义符号常量,用来控制是升序还是降序,0-升序/非0-降序,默人为升序 #define SORT_DESC 0 /** * 冒泡排序(向前冒泡) */ void test1() { // 创建一个数组,用来存放冒泡排序用的数列 int arr[10]; // 定义循环变量i和j,分别控制轮数和次数 int i, j; // 定义一个临时变量,用来存储交换的值 int temp; // 获取数组的大小 int len = sizeof(arr) / sizeof(arr[0]); printf("请输入%d个整数\n",len); // 通过for循环,为数组元素赋值 for (i = 0;i < len; i++) scanf("%d", &arr[i]); printf("\n排序前:"); for (i = 0; i < len; i++) printf("%-4d", arr[i]); printf("\n"); // 外层循环:实现轮数的遍历:轮数 = 数组容量 - 1 for (i = 0; i < len - 1; i++) { // 记录一个标志位 int flag = 0; // 内层循环:实现每一轮比较次数的遍历:次数 = 数组长度 - 轮数 - 1 for (j = 0; j < len - i - 1; j++) { // 降序排序 if(SORT_DESC) { if(arr[j] < arr[j+1]) { temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; flag = 1; } } // 升序排序 else { if (arr[j] > arr [j+1]) { temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; flag = 1; } } } if(!flag) break; } // 排序完后,再次遍历 printf("\n排序后:"); for (i = 0; i < len; i++) printf("%-4d", arr[i]); printf("\n"); } int main(int argc,char *argv[]) { test1(); return 0; }运行结果:
yueqian@yueqian-vm:/mnt/hgfs/code/20260807$ gcc demo03.c yueqian@yueqian-vm:/mnt/hgfs/code/20260807$ ./a.out 请输入10个整数 1 5 3 7 3 9 6 11 54 2 排序前:1 5 3 7 3 9 6 11 54 2 排序后:1 2 3 3 5 6 7 9 11 54
二维数组
定义
二维数组本质上是数组的数组,即外层数组的每个元素都是一个一维数组,由行和列两部分构成,属于多维数组。
可以按照先行后列的方式解读。
二维数组可被视作一个特殊的一维数组,也就是说,当一个数组的每一个元素都是一维数组的时候,那么这个数组就是二维数组。
语法
数据类型 数组名[行容量][列容量];
行容量:外层数组的数组容量,也就是二维数组中一维数组的个数(也就是二维数组中有几个一维数组)
列容量:内层数组的数组容量,也就是一维数组中元素的个数(见一维数组)
说明
二维数组在初始化的时候,可以省略行容量,系统会通过初始化后的数据自动推断行容量
二维数组和一维数组一样,可以部分初始化,未初始化部分的元素使用
0填充二维数组在初始化的时候,不能省略列容量,否则编译报错
初始化
特殊写法:
下标可以是整型表达式,如
arr[2-1][2*2-1]等价于arr[1][3]下标可以是已经有值的变量或者数组的元素。如
arr[2*x-1][b[3][1]],[]中最终需要的是一个>=0且小于数组对应维度容量的整数。数组元素可以出现在表达式,如
b[1][2] = a[2][3]/2演示:
数组:arr 列-0 列-1 列-2 举例 说明 行-0 111213arr[0][1]数组arr的0行1列对应的元素 12行-1 212223arr[1][2]数组arr的1行2列对应的元素 23注意:使用数组元素的下标应在已定义数组的大小范围内;应注意区别定义数组大小和引用数组元素的区别。
内存存储
C语言中二维数组按行优先存储:先连续存储第0行的所有列元素,再存储第1行的所有列元素,以此类推;反映在下标上,行下标(第一维)变化慢,列下标(第二维)变化快(例如arr[2][3],先填满arr[0][0]→arr[0][1]→arr[0][2],再到arr[1][0]→arr[1][1]→arr[1][2])。
应用场合
主要是应用对行列有要求的情况,比如说我们现在要存储西安粤嵌所有的在班学生的成绩。
还有就是字符数组的应用,比如用数组存储学生的姓名。char names[50][20] = {"张三","李四","王五"};
存放一个班的所有学生的成绩,使用一维数组。举例:
double scores[50] = {...};存放五个班的所有学生的成绩,使用二维数组。举例:
double scores[5][50] = {...};存放6个校区的所有学生的成绩,使用三维数组。举例:
double scores[6][5][50] = {{{}},{{}}}
案例1:二维数组的遍历
分析:
二维数组本质上属于行列式,遍历的时候需要借助于嵌套的for循环,外层for负责行的遍历,内层的for负责列的遍历。
取值:
[数据类型 变量名 =] 数组名[行下标][列下标];
赋值:
数组名[行下标][列下标] = 值;
行和列的容量计算:
// 计算行的大小 int row_len = sizeof(数组名) / sizeof(数组名[行下标0]); // 二维数组中,一维数组的个数 // 计算列的大小 int col_len = sizeof(数组名[行下标0]) / sizeof(数组名[行下标0][列下标0]);
代码:
/************************************************************************* > File Name: demo04.c > Author: 李昕怡 > Description: 二维数组的遍历 > Created Time: 2026年08月16日 星期日 12时58分21秒 ************************************************************************/ #include <stdio.h> int main(int argc,char *argv[]) { // 创建一个二维数组,用来存储测试数据 int arr[][3] = {{11},{22,33},{44,55,66}}; // 计算行容量和列容量 int row_len = sizeof(arr) / sizeof(arr[0]); int col_len = sizeof(arr[0]) / sizeof(arr[0][0]); // 外循环:遍历行 for (int i = 0; i < row_len; i++) { // 内循环:遍历列 for (int j = 0; j < col_len; j++) { printf("%-4d", arr[i][j]); } printf("\n"); } printf("\n"); return 0; }运行结果:
yueqian@yueqian-vm:/mnt/hgfs/code/20260807$ gcc demo04.c yueqian@yueqian-vm:/mnt/hgfs/code/20260807$ ./a.out 11 0 0 22 33 0 44 55 66
案例2:矩阵的转置
分析:
所谓矩阵的转置:就是原本的行变列,列变行
代码:
/************************************************************************* > File Name: demo05.c > Author: 李昕怡 > Description: 二维数组:矩阵的转置 > Created Time: 2026年08月16日 星期日 13时10分58秒 ************************************************************************/ #include <stdio.h> #define ROW 2 #define COL 3 int main(int argc,char *argv[]) { // 定义循环变量 int i, j; // 准备两个数组,用来存储转置前后的数据 int arr_before[ROW][COL] = {11,12,13,21,22,23}; int arr_after[COL][ROW] = {0}; // 计算数组的大小 int arr_before_row = sizeof(arr_before) / sizeof(arr_before[0]); int arr_before_col = sizeof(arr_before[0]) / sizeof(arr_before[0][0]); int arr_after_row = sizeof(arr_after) / sizeof(arr_after[0]); int arr_after_col = sizeof(arr_after[0]) / sizeof(arr_after[0][0]); // 使用双重for循环遍历二维数组 printf("转置前:\n"); // 遍历行 for (i = 0; i < arr_before_row; i++) { // 遍历列 for (j = 0; j < arr_before_col; j++) { // 打印转置前的数据 printf("%-4d", arr_before[i][j]); // 转置:行便列,列变行 arr_after[j][i] = arr_before[i][j]; } printf("\n"); } printf("转置后:\n"); // 遍历行 for (i = 0; i < arr_after_row; i++) { // 遍历列 for (j = 0; j < arr_after_col; j++) { // 打印转置前的数据 printf("%-4d", arr_after[i][j]); } printf("\n"); } return 0; }运行结果:
yueqian@yueqian-vm:/mnt/hgfs/code/20260807$ gcc demo05.c yueqian@yueqian-vm:/mnt/hgfs/code/20260807$ ./a.out 转置前: 11 12 13 21 22 23 转置后: 11 21 12 22 13 23