
前言欢迎来到第六章到目前为止我们学的变量都是 单打独斗 的 —— 一个变量存一个数据。但如果我要存 100 个学生的成绩呢定义 100 个变量score1、score2、score3……score100这也太蠢了而且根本没法循环处理。这时候就需要数组了。数组就是把多个相同类型的数据放在一起用一个统一的名字管理通过下标来访问每一个数据。本章我们将学习什么是数组为什么需要数组一维数组的定义、初始化、访问和遍历一维数组的常见应用求和、找最值、排序、查找二维数组的基本使用字符数组与字符串入门数组是 C 语言中非常重要的数据结构后面学指针、函数、字符串都会用到一定要认真学哦准备好了吗让我们开始一、什么是数组1.1 为什么需要数组先来看一个问题要存 5 个学生的成绩计算平均分。不用数组的写法int s1 85; int s2 90; int s3 78; int s4 92; int s5 88; int sum s1 s2 s3 s4 s5; double avg sum / 5.0;5 个还好那 100 个呢1000 个呢定义 1000 个变量写 1000 遍加法疯了。用数组的写法int scores[5] {85, 90, 78, 92, 88}; int sum 0; for (int i 0; i 5; i) { sum scores[i]; } double avg sum / 5.0;看到区别了吗有了数组100 个、1000 个数据都只需要几行代码配合循环轻松搞定。1.2 数组的定义数组就是相同类型数据的有序集合。几个关键点相同类型一个数组里的所有元素类型必须一样都是 int 就全是 int都是 double 就全是 double有序元素是按顺序排列的每个元素有一个编号下标连续存储在内存中数组的元素是挨在一起存放的你可以把数组想象成一排连在一起的盒子每个盒子大小一样因为类型相同每个盒子有一个编号下标所有盒子共用一个名字数组名scores[0] scores[1] scores[2] scores[3] scores[4] ┌────────┬────────┬────────┬────────┬────────┐ │ 85 │ 90 │ 78 │ 92 │ 88 │ └────────┴────────┴────────┴────────┴────────┘二、一维数组2.1 数组的声明声明一个一维数组的语法类型名 数组名[元素个数];比如int scores[10]; // 能存10个int的数组 double heights[5]; // 能存5个double的数组 char name[20]; // 能存20个char的数组⚠️注意元素个数必须是常量或者常量表达式不能是变量C99 之前数组一旦定义大小就固定了不能改小知识C99 标准支持 变长数组VLA可以用变量定义数组大小。但为了兼容性和性能初学阶段建议还是用常量。2.2 数组的初始化声明数组的同时可以给它赋值这叫初始化。方式一全部初始化int scores[5] {85, 90, 78, 92, 88};大括号里依次写每个元素的值用逗号隔开。方式二部分初始化int scores[5] {85, 90}; // 只给前两个赋值只写了前两个值后面没写的会被自动初始化为 0。所以上面的数组实际是{85, 90, 0, 0, 0}方式三全部初始化为 0int scores[5] {0}; // 第一个元素是0剩下的自动也是0这是把数组全部清零的常用写法很方便。方式四省略大小让编译器自己数int scores[] {85, 90, 78, 92, 88};中括号里不写数字编译器会根据大括号里的值的个数自动确定数组大小。这里就是 5 个元素。2.3 数组的访问下标数组的元素通过下标也叫索引来访问。语法数组名[下标]⚠️超级重要数组下标从 0 开始不是从 1 开始比如int scores[5]有 5 个元素它们的下标是第 1 个元素 →scores[0]第 2 个元素 →scores[1]第 3 个元素 →scores[2]第 4 个元素 →scores[3]第 5 个元素 →scores[4]下标 0 1 2 3 4 ┌──────┬──────┬──────┬──────┬──────┐ scores: │ 85 │ 90 │ 78 │ 92 │ 88 │ └──────┴──────┴──────┴──────┴──────┘来看例子#include stdio.h int main() { int scores[5] {85, 90, 78, 92, 88}; printf(第1个元素%d\n, scores[0]); // 85 printf(第3个元素%d\n, scores[2]); // 78 printf(最后一个元素%d\n, scores[4]); // 88 // 修改元素 scores[2] 95; // 把第3个元素改成95 printf(修改后第3个%d\n, scores[2]); // 95 return 0; }下标从 0 开始是 C 语言的经典设定也是新手最容易搞错的地方一定要记住2.4 数组越界 —— 超级大坑⚠️C 语言最危险的坑之一数组越界如果数组只有 5 个元素下标 0-4你非要访问scores[5]或者scores[-1]会发生什么答案是行为未定义C 语言不会检查下标是否合法越界访问会访问到数组外面的内存可能什么事都没有可能程序崩溃可能数据错乱最可怕的是有时候能跑通但结果是错的很难调试int scores[5] {1, 2, 3, 4, 5}; printf(%d\n, scores[10]); // ❌ 越界了结果不可预测重要提醒写代码的时候一定要检查下标是否在合法范围内。数组有 n 个元素下标就是 0 ~ n-1不要多也不要少。2.5 数组的遍历把数组的每个元素都访问一遍叫遍历。遍历数组最常用的就是 for 循环。#include stdio.h int main() { int scores[5] {85, 90, 78, 92, 88}; int i; // 遍历输出所有元素 for (i 0; i 5; i) { printf(%d , scores[i]); } printf(\n); return 0; }运行结果85 90 78 92 88注意循环条件是i 5不是i 5—— 因为下标最大是 4等于 5 就越界了。三、一维数组的常见应用学了数组怎么用我们来看几个最常见的应用场景。3.1 求和与平均值#include stdio.h int main() { int scores[5] {85, 90, 78, 92, 88}; int sum 0; double avg; int i; // 求和 for (i 0; i 5; i) { sum scores[i]; } avg sum / 5.0; // 注意除以5.0不是5否则是整数除法 printf(总分%d\n, sum); printf(平均分%.2f\n, avg); return 0; }运行结果总分433 平均分86.603.2 找最大值和最小值#include stdio.h int main() { int scores[5] {85, 90, 78, 92, 88}; int max, min; int i; // 先假设第一个元素就是最大/最小的 max scores[0]; min scores[0]; // 从第二个元素开始比 for (i 1; i 5; i) { if (scores[i] max) { max scores[i]; // 找到了更大的更新max } if (scores[i] min) { min scores[i]; // 找到了更小的更新min } } printf(最高分%d\n, max); printf(最低分%d\n, min); return 0; }运行结果最高分92 最低分783.3 查找在数组里找某个值在不在或者在哪个位置。#include stdio.h int main() { int scores[5] {85, 90, 78, 92, 88}; int target 90; // 要找的数 int i; int found 0; // 标记有没有找到0表示没找到 for (i 0; i 5; i) { if (scores[i] target) { printf(找到了在下标%d的位置\n, i); found 1; break; // 找到了就不用再找了 } } if (!found) { printf(没找到这个数\n); } return 0; }运行结果找到了在下标1的位置这种一个一个挨着找的方法叫顺序查找简单但效率不高。后面学了排序之后还可以用更快的二分查找。3.4 冒泡排序入门排序是数组最经典的应用之一。这里给大家简单介绍一下冒泡排序的基本思想。冒泡排序的原理每一轮把最大的数 冒 到最后面有 n 个数就排 n-1 轮#include stdio.h int main() { int arr[5] {5, 2, 8, 1, 9}; int i, j, temp; int n 5; printf(排序前); for (i 0; i n; i) printf(%d , arr[i]); printf(\n); // 冒泡排序 for (i 0; i n - 1; i) // 外层n-1轮 { for (j 0; j n - 1 - i; j) // 内层每轮比较的次数 { if (arr[j] arr[j 1]) // 前面比后面大就交换 { temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; } } } printf(排序后); for (i 0; i n; i) printf(%d , arr[i]); printf(\n); return 0; }运行结果排序前5 2 8 1 9 排序后1 2 5 8 9冒泡排序是排序算法里最简单的一种虽然效率不高但很适合初学者理解排序的思想。四、二维数组4.1 什么是二维数组一维数组像一排盒子二维数组就像一个表格或者矩阵—— 有行有列。比如存 3 个学生每人 4 科成绩学生语文数学英语物理学生 185908892学生 278859080学生 392889587这就是一个 3 行 4 列的二维数组。4.2 二维数组的声明和初始化声明类型名 数组名[行数][列数];int scores[3][4]; // 3行4列的int数组初始化方式一按行初始化int scores[3][4] { {85, 90, 88, 92}, // 第0行 {78, 85, 90, 80}, // 第1行 {92, 88, 95, 87} // 第2行 };方式二连续写自动分行int scores[3][4] {85,90,88,92, 78,85,90,80, 92,88,95,87};按顺序填满一行再填下一行效果和上面一样。方式三省略行数int scores[][4] { {85, 90, 88, 92}, {78, 85, 90, 80}, {92, 88, 95, 87} };行数可以省略编译器会数有几行大括号。但列数不能省4.3 二维数组的访问访问二维数组的元素需要两个下标行下标和列下标。数组名[行下标][列下标]同样下标都是从 0 开始的。// 第0行第1列的元素 printf(%d\n, scores[0][1]); // 90 // 修改第2行第3列的元素 scores[2][3] 100;4.4 二维数组的遍历二维数组需要两层循环来遍历外层循环控制行内层循环控制列。#include stdio.h int main() { int scores[3][4] { {85, 90, 88, 92}, {78, 85, 90, 80}, {92, 88, 95, 87} }; int i, j; // 遍历输出 for (i 0; i 3; i) // 外层行 { for (j 0; j 4; j) // 内层列 { printf(%d\t, scores[i][j]); } printf(\n); // 一行打完换行 } return 0; }运行结果85 90 88 92 78 85 90 80 92 88 95 874.5 二维数组在内存中的存储虽然我们把二维数组想象成表格但在内存中它其实是线性存储的 —— 按行连续存放。比如scores[3][4]在内存中是这样排的第0行 → 第1行 → 第2行 (0,0)(0,1)(0,2)(0,3) (1,0)(1,1)... (2,0)(2,1)...这叫行优先存储C 语言就是按行优先的。理解了这一点后面学指针和数组的时候会轻松很多。五、字符数组与字符串入门5.1 字符数组元素类型是char的数组就是字符数组每个元素存一个字符。char arr[5] {H, e, l, l, o};这就是一个存了 5 个字符的字符数组。5.2 什么是字符串在 C 语言中字符串就是以 \0 结尾的字符数组。\0是一个特殊的字符叫字符串结束标志它的 ASCII 码是 0。比如字符串Hello在内存中是这样的H e l l o \0 ┌────┬────┬────┬────┬────┬────┐ │ 72 │ 101│ 108│ 108│ 111│ 0 │ └────┴────┴────┴────┴────┴────┘⚠️注意字符串末尾有一个隐藏的\0所以 Hello 看起来是 5 个字符但实际占 6 个字节。5.3 字符串的初始化字符数组有两种初始化方式方式一逐个字符写char str[6] {H, e, l, l, o, \0};方式二用双引号字符串字面量char str[] Hello; // 编译器会自动加\0第二种方式更常用也更方便。注意用双引号的时候编译器会自动在末尾加\0不用你手动写。5.4 字符串的输入输出输出字符串用 % schar str[] Hello World; printf(%s\n, str); // 输出Hello World%s会从给定的地址开始一直输出到遇到\0为止。输入字符串用 scanf 的 % schar name[20]; printf(请输入你的名字); scanf(%s, name); // 注意数组名前面不用加 printf(你好%s\n, name);⚠️注意scanf用%s读字符串时遇到空格就停止所以它不能读带空格的句子比如 Hello World 只能读到 Hello要注意数组大小避免输入太长导致越界5.5 常用字符串函数简介C 语言标准库提供了很多处理字符串的函数都在string.h头文件里。这里先简单介绍几个最常用的函数作用strlen(s)求字符串的长度不包括 \0strcpy(dest, src)把 src 字符串复制到 deststrcat(dest, src)把 src 拼接到 dest 的后面strcmp(s1, s2)比较两个字符串的大小例子#include stdio.h #include string.h // 要用字符串函数需要包含这个头文件 int main() { char str[100] Hello; printf(长度%zu\n, strlen(str)); // 5 strcat(str, World); // 拼接 printf(%s\n, str); // Hello World return 0; }字符串函数还有很多后面我们会专门用一章来详细讲。六、本章小结本章我们学习了 C 语言中的数组1. 一维数组定义类型 数组名[大小];下标从0开始最大下标是 大小 - 1⚠️数组越界是大忌一定要检查下标范围遍历用 for 循环循环条件是i n2. 一维数组常见应用求和、求平均值找最大值、最小值顺序查找排序冒泡排序入门3. 二维数组定义类型 数组名[行数][列数];两个下标行和列都从 0 开始遍历需要两层循环外层行内层列内存中按行优先存储4. 字符数组与字符串C 语言没有专门的字符串类型用字符数组表示字符串以\0结尾输入输出用%s字符串处理函数在string.h中学习建议数组是编程的核心数据结构之一一定要多动手练习。建议你把经典的数组题都敲一遍逆序输出、统计次数、插入元素、删除元素、各种排序…… 数组玩熟了后面学数据结构就轻松了。七、写在最后Simplicity is prerequisite for reliability.——Edsger W. Dijkstra艾兹格・迪杰斯特拉图灵奖得主翻译成中文就是简单是可靠的前提。数组就是一个很好的例子 —— 结构简单就是一排连续的盒子但却能解决无数复杂的问题。真正强大的东西往往都是简单的。写代码也是一样能用简单数组解决的就不要搞复杂的数据结构。简单的代码更容易写对也更容易维护。⚠️ 关于本文由于笔者水平有限文中难免存在疏漏、错误或表述不够准确的地方。如果你在阅读过程中发现了任何问题 —— 无论是概念错误、代码 bug、还是表述不清 —— 都欢迎在评论区留言指正我会及时修正。技术之路道阻且长行则将至。我们互相学习共同进步