数组是C语言入门的第一个“数据结构”,很多新手会觉得它简单,但其实里面藏着不少细节,比如初始化的坑、数组下标的注意事项,还有排序算法的应用,今天一次性讲透!
一、什么是数组?(新手必懂核心)
1.1 数组的本质
数组是数据结构的一种,也是最基础、最简单的数据结构,属于线性结构中的“顺序存储结构”。
简单来说:数组就是相同类型变量的集合——当我们需要使用多个同类型的变量时,不用一个个定义(比如int a1, a2, a3...),用一个数组就能统一管理,既简洁又高效。
1.2 为什么需要数组?(解决什么问题)
很多新手会问:一个变量直接用就好,为什么需要“结构”?
答案很简单:当我们需要处理一定量的同类型数据时,单个变量会显得非常繁琐。
比如:统计一个班级50个学生的成绩、存储100个随机数、处理一串字符,这时用数组就能批量管理这些数据,后续的遍历、修改、计算都会更方便。
二、数组的定义(语法+细节)
数组的定义有固定语法,核心是“指定类型、数组名、元素个数”,缺一不可,语法格式如下:
type name[nmemb];拆解说明(新手必记):
type:数组中所有变量的统一类型(比如int、char、float),数组中不能混合不同类型的元素;
name:数组名,本质是一个标识符,遵循C语言命名规则(只能由字母、数字、下划线组成,不能以数字开头);
nmemb:数组的成员个数(即数组长度),必须是定长(C语言标准中,数组定义时长度不能是变量,必须是常量或常量表达式,比如int arr[10]合法,int arr[n](n是变量)不合法)。
易错点提醒:数组长度必须是常量,新手不要犯“int n=10; int arr[n]”的错误,这种写法在部分编译器(如Dev-C++)中可能通过,但不符合C语言标准,移植性极差!
三、数组的赋值与访问(核心操作)
3.1 数组元素的访问
数组中的每个元素都有一个唯一的“下标”(索引),通过“数组名[下标]”就能访问或修改对应元素,注意:
数组下标从0开始,不是从1开始;
第一个元素:name[0](下标0);
第二个元素:name[1](下标1);
最后一个元素:name[nmemb - 1](下标 = 数组长度 - 1)。
示例(直观理解):
int arr[5]; // 定义一个长度为5的int数组 arr[0] = 10; // 给第一个元素赋值10 arr[1] = 20; // 给第二个元素赋值20 arr[4] = 50; // 给最后一个元素赋值50(下标5-1=4)3.2 批量赋值(利用循环遍历)
数组下标是从0到nmemb-1的连续整数,因此我们可以用for循环遍历所有下标,批量访问或赋值数组元素,这是数组最常用的操作之一。
语法模板(记熟!):
for (int i = 0; i < nmemb; i++) { // i从0开始,小于数组长度 name[i] = 赋值内容; // 批量赋值 // 或者 printf("%d", name[i]); // 批量访问 }四、数组的初始化(避坑重点)
数组定义后,如果不初始化,元素的值是随机的(垃圾值),因此建议养成“定义即初始化”的习惯,初始化语法如下:
type name[nmemb] = {val1, val2, val3...};两个关键细节(新手必避坑):
初始化时,值的个数可以少于数组长度:未赋值的元素会自动被初始化为0(int数组)、'\0'(char数组);
如果数组完全不初始化,每个元素的值都是随机值(垃圾值),后续使用会导致程序异常(比如乱码、计算错误)。
示例(对比理解):
// 示例1:完全初始化 int arr1[3] = {1, 2, 3}; // arr1[0]=1, arr1[1]=2, arr1[2]=3 // 示例2:部分初始化(未赋值元素为0) int arr2[3] = {1}; // arr2[0]=1, arr2[1]=0, arr2[2]=0 // 示例3:不初始化(元素为随机值,不推荐) int arr3[3]; // arr3[0]、arr3[1]、arr3[2]的值随机五、数组的遍历(必练操作)
数组遍历就是“依次访问数组中的每一个元素”,核心还是利用for循环,结合数组下标的连续性,实现批量访问。
核心逻辑:用循环变量i从0遍历到nmemb-1,通过name[i]访问每个元素。
示例(遍历int数组,打印所有元素):
#include <stdio.h> int main() { int arr[5] = {10, 20, 30, 40, 50}; int len = 5; // 数组长度,建议用变量存储,方便后续修改 // 遍历数组,打印所有元素 for (int i = 0; i < len; i++) { printf("arr[%d] = %d\n", i, arr[i]); } return 0; }运行结果:
arr[0] = 10 arr[1] = 20 arr[2] = 30 arr[3] = 40 arr[4] = 50六、随机数生成(数组实战基础)
很多数组练习需要用到随机数(比如生成随机数组),C语言中生成随机数需要借助两个头文件和两个函数,记熟下面的模板即可直接使用。
6.1 必备头文件
#include <time.h> // 提供时间相关函数,用于设置随机数种子 #include <stdlib.h> // 提供rand()和srand()函数6.2 核心函数说明
srand(time(NULL));:设置随机数种子,作用是让每次运行程序生成的随机数都不同(如果不设置种子,每次生成的随机数序列都是一样的);
rand();:生成随机数,默认范围是0~65535(无符号短整型的范围)。
技巧:如果想生成指定范围的随机数,比如0~99,可以用 rand() % 100;生成1~100,可以用 rand() % 100 + 1,以此类推。
示例(生成1个随机数):
#include <stdio.h> #include <time.h> #include <stdlib.h> int main() { srand(time(NULL)); // 设置随机数种子(必须放在rand()前面) int num = rand(); // 生成随机数 printf("随机数:%d\n", num); return 0; }七、实战练习1:生成随机数组,求最值和平均值
题目要求:定义一个由100个整型数组成的数组,值随机产生,求出数组的最大值、最小值以及平均值。
解题思路:
定义int数组,长度100;
用srand()设置种子,再用for循环给数组赋值(随机数范围建议0~999,避免数值过大);
遍历数组,初始化最大值、最小值(用数组第一个元素作为初始值),累加所有元素求总和;
计算平均值(注意用浮点型计算,避免整数除法丢失精度);
打印结果。
完整代码(可直接复制运行):
#include <stdio.h> #include <time.h> #include <stdlib.h> int main() { // 1. 定义数组(长度100) int arr[100]; int len = 100; int max, min, sum = 0; double avg; // 平均值用double,避免精度丢失 // 2. 设置随机数种子,给数组赋值(0~999的随机数) srand(time(NULL)); for (int i = 0; i < len; i++) { arr[i] = rand() % 1000; // 生成0~999的随机数 sum += arr[i]; // 累加求和 } // 3. 求最大值和最小值(初始化用数组第一个元素) max = arr[0]; min = arr[0]; for (int i = 1; i < len; i++) { if (arr[i] > max) { max = arr[i]; // 更新最大值 } if (arr[i] < min) { min = arr[i]; // 更新最小值 } } // 4. 计算平均值 avg = (double)sum / len; // 强制转换为double,避免整数除法 // 5. 打印结果 printf("数组的最大值:%d\n", max); printf("数组的最小值:%d\n", min); printf("数组的总和:%d\n", sum); printf("数组的平均值:%.2f\n", avg); // 保留2位小数,更美观 return 0; }运行说明:每次运行程序,数组中的随机数都会不同,最值和平均值也会随之变化,符合题目要求。
八、数组排序(3种基础排序算法,新手必练)
排序是数组最核心的应用之一,新手入门必掌握3种基础排序:冒泡排序、选择排序、直接插入排序,下面详细讲解每种排序的原理、步骤和代码实现,结合图解思路(文字模拟图解),一看就懂。
8.1 冒泡排序(最易理解)
核心思想:相邻元素两两比较,如果不符合指定的大小关系(比如从小到大),就交换两个元素的位置;经过一趟排序,会将“最大(或最小)”的元素“冒”到无序序列的最后,加入有序区。
图解思路(文字模拟):
黄色区域:有序区(已经排好序的元素);
蓝色区域:无序区(待排序的元素);
绿色元素:正在比较的两个相邻元素。
排序步骤(以“从小到大”为例):
初始状态:整个数组都是无序区(蓝色),有序区为空;
第一趟排序:从无序区的第一个元素开始,两两比较相邻元素,大的元素往后移,直到将最大的元素移到无序区的最后,此时该元素加入有序区(黄色);
第二趟排序:对剩余的无序区重复上述操作,将第二大的元素移到无序区最后,加入有序区;
重复上述步骤,直到无序区为空,排序完成。
代码实现(从小到大排序):
#include <stdio.h> // 冒泡排序函数(arr:待排序数组,len:数组长度) void bubbleSort(int arr[], int len) { // 外层循环:控制排序趟数(n个元素,需要n-1趟) for (int i = 0; i < len - 1; i++) { // 内层循环:控制每趟比较的次数(每趟少比较1次,因为最后一个元素已排好) for (int j = 0; j < len - 1 - i; j++) { // 相邻元素比较,大的往后移(从小到大) if (arr[j] > arr[j + 1]) { // 交换两个元素 int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } // 打印数组函数(方便查看排序结果) void printArr(int arr[], int len) { for (int i = 0; i < len; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[] = {5, 2, 9, 1, 5, 6}; int len = sizeof(arr) / sizeof(arr[0]); // 计算数组长度(通用写法) printf("排序前:"); printArr(arr, len); bubbleSort(arr, len); // 调用冒泡排序 printf("排序后:"); printArr(arr, len); return 0; }8.2 选择排序(效率略高于冒泡)
核心思想:依次选择待插入有序区的位置(从0到n-2),从该位置开始到数组末尾,找到最大(或最小)的元素;如果该元素不在选择的位置,就和该位置的元素交换,将其加入有序区。
排序步骤(以“从小到大”为例):
初始状态:有序区为空,无序区为整个数组;
第一步:选择位置0(待插入有序区的第一个位置),从位置0到末尾,找到最小的元素,和位置0的元素交换,此时位置0加入有序区;
第二步:选择位置1(待插入有序区的第二个位置),从位置1到末尾,找到最小的元素,和位置1的元素交换,此时位置1加入有序区;
重复上述步骤,直到选择到位置n-2(最后一个待插入位置),排序完成。
代码实现(从小到大排序):
#include <stdio.h> // 选择排序函数 void selectSort(int arr[], int len) { // 外层循环:控制待插入有序区的位置(0~n-2) for (int i = 0; i < len - 1; i++) { int minIndex = i; // 假设当前位置的元素是最小值,记录其下标 // 内层循环:从当前位置到末尾,找到真正的最小值下标 for (int j = i + 1; j < len; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; // 更新最小值下标 } } // 如果最小值不在当前位置,交换元素 if (minIndex != i) { int temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } } } // 打印数组 void printArr(int arr[], int len) { for (int i = 0; i < len; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[] = {5, 2, 9, 1, 5, 6}; int len = sizeof(arr) / sizeof(arr[0]); printf("排序前:"); printArr(arr, len); selectSort(arr, len); // 调用选择排序 printf("排序后:"); printArr(arr, len); return 0; }8.3 直接插入排序(贴近日常思维)
核心思想:从数组的第二个元素(下标1)开始,依次选择每个元素作为“待插入元素”,将其插入到前面已排好序的有序序列中,插入时保持有序序列的顺序不变。
关键细节:选择待插入的元素后,先用变量备份该元素,此时该元素的位置会空出来,供前面的有序元素向后移动(找到合适的插入位置)。
排序步骤(以“从小到大”为例):
初始状态:下标0的元素作为初始有序区,其余元素为无序区;
第一步:选择下标1的元素作为待插入元素,备份后,将其与有序区的元素(下标0)比较,若待插入元素小,则将有序区元素后移,插入待插入元素;
第二步:选择下标2的元素作为待插入元素,备份后,从有序区的末尾开始比较,比待插入元素大的元素依次后移,找到合适位置插入;
重复上述步骤,直到所有元素都插入到有序区,排序完成。
代码实现(从小到大排序):
#include <stdio.h> // 直接插入排序函数 void insertSort(int arr[], int len) { // 外层循环:控制待插入元素(从下标1开始,到len-1结束) for (int i = 1; i < len; i++) { int temp = arr[i]; // 备份待插入元素 int j = i - 1; // 有序区的最后一个元素下标 // 内层循环:找到待插入位置,有序区元素向后移动 while (j >= 0 && arr[j] > temp) { arr[j + 1] = arr[j]; // 元素后移 j--; // 向前移动,继续比较 } arr[j + 1] = temp; // 插入待插入元素 } } // 打印数组 void printArr(int arr[], int len) { for (int i = 0; i < len; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[] = {5, 2, 9, 1, 5, 6}; int len = sizeof(arr) / sizeof(arr[0]); printf("排序前:"); printArr(arr, len); insertSort(arr, len); // 调用直接插入排序 printf("排序后:"); printArr(arr, len); return 0; }九、实战练习2:随机字符数组排序(综合应用)
题目要求:随机产生26个字母组成的字符数组,数组长度是20,满足以下要求:
数组元素是26个英文字母中的某一个(大小写都行);
将数组前10个元素从小到大排序,后10个元素从大到小排序;
要求使用两种不同的排序算法。
解题思路:
定义char数组,长度20;
生成随机字母:利用rand() % 26,结合'a'(小写)或'A'(大写),比如'a' + rand() % 26 生成小写字母;
拆分数组:前10个元素用一种排序算法(比如冒泡排序)从小到大排序;
后10个元素用另一种排序算法(比如选择排序)从大到小排序;
打印排序前后的数组,对比结果。
完整代码(可直接复制运行):
#include <stdio.h> #include <time.h> #include <stdlib.h> // 1. 冒泡排序(从小到大,用于前10个元素) void bubbleSortAsc(char arr[], int start, int end) { // start:排序起始下标,end:排序结束下标(含) int len = end - start + 1; for (int i = 0; i < len - 1; i++) { for (int j = start; j < end - i; j++) { if (arr[j] > arr[j + 1]) { char temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } // 2. 选择排序(从大到小,用于后10个元素) void selectSortDesc(char arr[], int start, int end) { int len = end - start + 1; for (int i = start; i < end; i++) { int maxIndex = i; for (int j = i + 1; j <= end; j++) { if (arr[j] > arr[maxIndex]) { maxIndex = j; } } if (maxIndex != i) { char temp = arr[i]; arr[i] = arr[maxIndex]; arr[maxIndex] = temp; } } } // 打印数组 void printArr(char arr[], int len) { for (int i = 0; i < len; i++) { printf("%c ", arr[i]); } printf("\n"); } int main() { char str[20]; // 定义长度为20的字符数组 int len = 20; // 1. 生成随机字母(小写字母,可改为'A'生成大写) srand(time(NULL)); for (int i = 0; i < len; i++) { str[i] = 'a' + rand() % 26; // 'a'~'z'的随机字母 } printf("排序前的数组:"); printArr(str, len); // 2. 前10个元素(下标0~9):冒泡排序,从小到大 bubbleSortAsc(str, 0, 9); // 3. 后10个元素(下标10~19):选择排序,从大到小 selectSortDesc(str, 10, 19); printf("排序后的数组:"); printArr(str, len); return 0; }代码说明:
生成大写字母:将代码中的'a'改为'A'即可;
排序算法替换:可将冒泡排序换成直接插入排序,选择排序换成冒泡排序,满足“两种排序算法”的要求;
排序范围:通过start和end参数控制排序的区间,前10个(0~9)、后10个(10~19),逻辑清晰。
十、总结与注意事项
数组是C语言的基础,也是后续学习链表、栈、队列等复杂数据结构的前提,今天的内容总结如下,新手必记:
数组是相同类型变量的集合,下标从0开始,最后一个元素下标是len-1;
数组定义时长度必须是常量,初始化时未赋值元素默认为0(int数组);
遍历、赋值数组的核心是for循环,结合下标连续性操作;
3种基础排序:冒泡(易理解)、选择(效率高)、直接插入(贴近日常),务必动手练熟;
生成随机数必须先设置种子(srand(time(NULL))),否则随机数序列固定。
最后提醒:数组操作最容易犯的错误是“下标越界”(比如访问arr[10],但数组长度只有10,最后一个元素是arr[9]),一定要注意控制下标范围,避免程序崩溃!
如果觉得这篇文章对你有帮助,欢迎点赞、收藏、关注,后续会持续更新C语言入门干货,我们下期见~