数组
目录
需求:一个字符串(如 "welcome to bit")从两端逐步向中间显示,带动态效果。
需求:在有序数组中查找目标值(如 1~10 中的 7),返回其下标(效率远高于逐个遍历)。
优势:效率极高!100 万个元素最多查找 20 次(时间复杂度 O (log₂n))。
5.2 strlen 与 sizeof 的区别(字符串数组常用)
✨ 引言:
如果说变量是 C 语言的 “单个储物盒”,那数组就是 “一排同款储物格”—— 能整齐存放多个同类型数据(比如 10 个成绩、5 个字符),是处理批量数据的 “神器”。它不仅是指针、结构体的学习基础,更是实战中高频使用的核心知识点(比如排序、查找、数据统计)。
一、先搞懂:数组到底是什么?(3 个核心特点)
数组本质是 **“相同类型元素的有序集合”**,可以用 “超市货架” 来理解:
- 📦 所有 “货架格子”(元素)类型一致(比如都是 int 型整数、char 型字符);
- 📏 格子数量固定(创建时指定,普通数组不能改,C99 变长数组除外);
- 📌 每个格子有唯一 “编号”(下标),从 0 开始,方便快速找到对应元素。
数组的分类(新手重点掌握前两个)
- 一维数组:单行货架(
int arr[10])→ 日常最常用; - 二维数组:多行多列货架(
int arr[3][5])→ 处理表格类数据; - 多维数组:三维及以上(如
int arr[2][3][4])→ 极少用,新手暂不深究。
二、一维数组:基础中的基础(必会!)
一维数组是数组的 “入门形态”,核心掌握 “创建 - 初始化 - 使用 - 存储” 四个环节,步骤清晰无压力~
2.1 一维数组的创建:定义 “货架规格”
语法格式(直接套用)
数据类型 数组名[常量值]; // 常量值=货架格子数(元素个数)
示例 + 避坑
#include <stdio.h>
int main()
{
int score[10]; // 正确:10个int型格子(存10个成绩,40字节)
char name[5]; // 正确:5个char型格子(存4个字符+1个'\0',5字节)
double height[8]; // 正确:8个double型格子(存8个身高,64字节)
// 以下都是错误写法,新手直接避开!
// int err1[]; // 错误:未初始化时,必须指定格子数
// int err2[-3]; // 错误:格子数不能为负数
// int err3[3.5]; // 错误:格子数必须是整数常量(如10、3+5)
return 0;
}
关键规则
- 数组名命名:和变量一致(字母、数字、下划线,不重复关键字,区分大小写);
- 格子数要求:C99 之前必须是 “常量”(如 10、5*2),不能用变量(如
int n=5; int arr[n];)。
2.2 一维数组的初始化:给 “货架装东西”
初始化是 “创建时给格子赋值”,避免格子里存 “垃圾数据”,分 3 种场景,新手优先掌握前两种:
(1)不完全初始化(部分赋值,剩余默认 0)
int main()
{
int arr1[10] = {0}; // 推荐!第一个格子=0,其余9个默认=0
int arr2[10] = {1,2,3};// 前3个格子=1、2、3,后7个默认=0
char ch[5] = {'a','b'};// 前2个格子='a'、'b',后3个默认=0('\0')
return 0;
}
✅ 实用场景:初始化所有元素为 0(直接写= {0}),不用逐个赋值。
(2)完全初始化(所有格子都赋值)
int main()
{
int arr3[5] = {1,2,3,4,5};// 5个格子依次赋值1~5
// int arr4[5] = {1,2,3,4,5,6};// 错误:赋值个数>格子数,超出范围
return 0;
}
(3)省略格子数(仅初始化时可用)
int main()
{
// 编译器自动根据赋值个数算格子数(赋值个数=格子数)
int arr5[] = {1,2,3,4};// 正确:格子数=4(等价于int arr5[4])
char ch2[] = "abc"; // 特殊:字符串初始化,自动加'\0',格子数=4(a、b、c、'\0')
return 0;
}
💡 小技巧:不确定数组大小时,用这种方式最方便(比如存已知的几个固定值)。
2.3 一维数组的使用:访问 “货架格子”
数组通过 “下标” 访问元素,下标从 0 开始(第一个格子下标 = 0,最后一个 = 格子数 - 1),核心是[](下标引用操作符)。
(1)单个元素访问
int main()
{
int arr[] = {10,20,30,40,50};// 下标0~4
printf("%d\n", arr[0]);// 输出10(第一个格子)
printf("%d\n", arr[2]);// 输出30(第三个格子)
printf("%d\n", arr[4]);// 输出50(最后一个格子)
// printf("%d\n", arr[5]);// 错误:下标越界(最大下标4),结果乱码/程序崩溃
return 0;
}
⚠️ 敲黑板:下标越界是新手高频错!编译器不报错,但会访问非法内存,导致程序异常。
(2)批量输入输出(循环遍历)
批量处理数据时,用for循环遍历所有格子,搭配sizeof计算格子数(通用写法,推荐!):
int main()
{
int arr[10] = {0};// 初始化所有格子为0
int i = 0;
// 计算格子数:数组总大小 / 单个元素大小(通用公式,改数组大小不用改这里)
int sz = sizeof(arr) / sizeof(arr[0]);
// 输入:给每个格子赋值
printf("请输入10个整数:\n");
for (i = 0; i < sz; i++)
{
scanf("%d", &arr[i]);// &取地址:arr[i]是单个元素,需传地址
}
// 输出:打印所有格子内容
printf("你输入的数组是:\n");
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
2.4 一维数组在内存中的存储(可视化)
数组元素在内存中是连续存放的,下标增长时,地址从低到高递增(每个格子占的字节数 = 数据类型长度)。
代码验证(打印地址)
int main()
{
int arr[] = {1,2,3,4,5};// int占4字节
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("arr[%d] = %d, 地址 = %p\n", i, arr[i], &arr[i]);
}
return 0;
}
输出结果(示例)
arr[0] = 1, 地址 = 00BFFD70
arr[1] = 2, 地址 = 00BFFD74 // 比前一个地址+4(int占4字节)
arr[2] = 3, 地址 = 00BFFD78 // 连续递增,没有空隙
arr[3] = 4, 地址 = 00BFFD7C
arr[4] = 5, 地址 = 00BFFD80
可视化图示(货架模型)
地址:低 → 高
货架格子:[1] [2] [3] [4] [5]
下标: 0 1 2 3 4
✅ 核心结论:连续存储是数组的关键特性,也是后续排序、查找的基础。
三、二维数组:多行多列的 “货架”
二维数组可以理解为 “把多个一维数组叠成多层货架”,比如int arr[3][5]就是 “3 层货架,每层 5 个格子”,适合处理表格类数据(如 3 个班级,每个班级 5 个学生成绩)。
3.1 二维数组的创建:定义 “多层货架规格”
语法格式
数据类型 数组名[行数(常量)][列数(常量)];
示例 + 避坑
int main()
{
int class_score[3][5];// 正确:3行5列,15个int型格子(存3个班级成绩)
char name[2][6]; // 正确:2行6列,12个char型格子(存2个名字)
// 以下是错误写法,新手避开!
// int err1[][]; // 错误:行数和列数不能都省略
// int err2[3][]; // 错误:列数不能省略(编译器需知道每层有多少格子)
// int err3[3.5][2];// 错误:行数/列数必须是整数常量
return 0;
}
3.2 二维数组的初始化:给 “多层货架装东西”
分 3 种方式,按行初始化最清晰,新手优先掌握:
(1)不完全初始化(剩余格子默认 0)
int main()
{
int arr1[3][5] = {1,2,3};// 按行顺序赋值,前3个=1、2、3,其余12个=0
int arr2[3][5] = {0}; // 推荐!所有格子默认=0
return 0;
}
(2)完全初始化(按行顺序赋值)
int main()
{
// 3行5列,按“第一行→第二行→第三行”顺序赋值
int arr3[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
// 第一行:1,2,3,4,5;第二行:2,3,4,5,6;第三行:3,4,5,6,7
return 0;
}
(3)按行初始化(逻辑清晰,推荐!)
用大括号分组,每组对应一行,一眼能看出每行的内容:
int main()
{
int arr4[3][5] = { {1,2}, {3,4}, {5,6} };
// 第一行:1,2,0,0,0;第二行:3,4,0,0,0;第三行:5,6,0,0,0
return 0;
}
(4)省略行数(仅初始化时可用)
编译器会根据 “列数” 和 “赋值个数” 自动算行数:
int main()
{
int arr5[][5] = {1,2,3,4,5,6};// 列数=5,赋值6个元素→行数=2(5+1)
// 第一行:1,2,3,4,5;第二行:6,0,0,0,0
int arr6[][5] = { {1,2}, {3,4}, {5,6} };// 3组→行数=3
return 0;
}
3.3 二维数组的使用:双循环遍历 “多层货架”
二维数组需要 “外层循环控制行,内层循环控制列”,才能访问到每个格子:
输入 + 输出(按行 / 按列)
int main()
{
int arr[3][5] = {0};
int i = 0;// 行下标(0~2)
int j = 0;// 列下标(0~4)
// 输入:按行赋值
printf("请输入3行5列的整数:\n");
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
scanf("%d", &arr[i][j]);// &取地址:单个元素需传地址
}
}
// 输出:按行打印(常用)
printf("按行打印:\n");
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");// 每行结束换行,排版整齐
}
// 输出:按列打印(特殊场景用)
printf("按列打印:\n");
for (j = 0; j < 5; j++)// 列下标先循环
{
for (i = 0; i < 3; i++)// 行下标后循环
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
3.4 二维数组在内存中的存储(关键!)
很多新手以为二维数组是 “按行分开存储”,其实它和一维数组一样 ——所有元素连续存放,顺序是 “第一行所有元素→第二行所有元素→第三行所有元素”。
代码验证(打印地址)
int main()
{
int arr[3][5] = {0};
int i = 0, j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("arr[%d][%d] = %p\n", i, j, &arr[i][j]);
}
printf("\n");// 换行方便观察
}
return 0;
}
输出结果(示例)
arr[0][0] = 00BFFD50
arr[0][1] = 00BFFD54 // +4字节(int)
arr[0][2] = 00BFFD58 // 连续递增
arr[0][3] = 00BFFD5C
arr[0][4] = 00BFFD60
arr[1][0] = 00BFFD64 // 紧跟第一行最后一个元素(无空隙)
arr[1][1] = 00BFFD68
...
arr[2][0] = 00BFFD78 // 紧跟第二行最后一个元素
可视化图示
地址:低 → 高
元素:[0][0] [0][1] [0][2] [0][3] [0][4] [1][0] [1][1] ... [2][4]
✅ 核心结论:二维数组本质是 “伪装成多行的一维数组”,连续存储的特性是后续指针操作的基础。
四、实战练习:数组的两个经典应用(必练!)
光看语法不够,动手练两个实战案例,才能真正掌握数组的用法~
4.1 练习 1:字符从两端向中间汇聚(动态效果)
需求:一个字符串(如 "welcome to bit")从两端逐步向中间显示,带动态效果。
完整代码(可直接复制运行)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <stdlib.h>
int main()
{
char target[] = "welcome to bit!!!!!!";// 目标字符串(含'\0')
char mask[] = "********************";// 占位符字符串(长度和target一致)
int left = 0; // 左指针(从最左边开始)
int right = strlen(target) - 1; // 右指针(从最右边开始,减1是因为strlen不含'\0')
while (left <= right)
{
mask[left] = target[left]; // 左指针位置替换为目标字符
mask[right] = target[right];// 右指针位置替换为目标字符
printf("%s\n", mask); // 打印当前效果
Sleep(300); // 暂停300毫秒(让效果可见,头文件windows.h)
system("cls"); // 清空屏幕(头文件stdlib.h)
left++; // 左指针右移
right--; // 右指针左移
}
printf("%s\n", mask); // 最后显示完整字符串(避免清空后看不到)
return 0;
}
核心思路:双指针法
- 左指针
left从 0 开始,右指针right从字符串末尾开始; - 每次循环替换两个指针位置的字符,然后指针向中间移动;
- 用
Sleep和system("cls")实现动态效果。
4.2 练习 2:二分查找(有序数组高效查找)
需求:在有序数组中查找目标值(如 1~10 中的 7),返回其下标(效率远高于逐个遍历)。
完整代码(修正原笔记笔误)
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};// 前提:数组必须有序(升序/降序)
int target = 7; // 要查找的目标值
int left = 0; // 左边界下标
int right = sizeof(arr)/sizeof(arr[0]) - 1; // 右边界下标
int find_flag = 0; // 标记是否找到(0=未找到,1=找到)
while (left <= right)
{
// 计算中间下标(避免left+right溢出,比(left+right)/2更安全)
int mid = left + (right - left) / 2;
if (arr[mid] < target)
{
left = mid + 1; // 目标值在右半部分,左边界右移
}
else if (arr[mid] > target)
{
right = mid - 1; // 目标值在左半部分,右边界左移
}
else
{
find_flag = 1;
printf("找到了!目标值%d的下标是:%d\n", target, mid);
break; // 找到后退出循环
}
}
if (find_flag == 0)
{
printf("未找到目标值%d\n", target);
}
return 0;
}
核心思路:折半缩小范围
- 每次取数组中间元素
mid,与目标值比较; - 目标值大→缩小到右半部分,目标值小→缩小到左半部分;
- 重复步骤,直到找到目标值或边界交叉(未找到)。
优势:效率极高!100 万个元素最多查找 20 次(时间复杂度 O (log₂n))。
五、补充知识点:数组的高频考点(新手必记)
5.1 数组名的特殊含义(面试常考!)
数组名大部分时候是 “数组首元素的地址”,但有两个例外:
sizeof(数组名):数组名代表整个数组,计算的是数组总大小(如int arr[10]; sizeof(arr)=40);&数组名:数组名代表整个数组,取的是整个数组的地址(类型是int(*)[10],不是普通指针)。
代码验证
int main()
{
int arr[5] = {1,2,3,4,5};
printf("arr = %p\n", arr); // 首元素地址(00BFFD70)
printf("&arr[0] = %p\n", &arr[0]);// 首元素地址(00BFFD70)
printf("&arr = %p\n", &arr); // 整个数组地址(00BFFD70,值相同但类型不同)
printf("sizeof(arr) = %zd\n", sizeof(arr));// 20(5×4字节)
printf("sizeof(&arr[0]) = %zd\n", sizeof(&arr[0]));// 4/8(指针大小)
return 0;
}
5.2 strlen 与 sizeof 的区别(字符串数组常用)
| 函数 / 操作符 | 功能 | 适用场景 | 示例(char arr[] = "abc") |
|---|---|---|---|
strlen | 统计字符串长度('\0' 前的字符数) | 字符串数组 | strlen(arr) = 3 |
sizeof | 计算总内存字节数(含 '\0') | 所有数据类型 / 数组 | sizeof(arr) = 4 |
5.3 数组作为函数参数(进阶必备)
数组作为函数参数时,会退化为首元素地址,函数无法直接获取数组大小,必须手动传入:
// 错误写法:函数内sizeof(arr)算的是指针大小(4/8字节)
void printArray(int arr[])
{
int sz = sizeof(arr)/sizeof(arr[0]);// 错误:sz=1(4/4)
for (int i=0; i<sz; i++)
{
printf("%d ", arr[i]);
}
}
// 正确写法:手动传入数组大小
void printArray(int arr[], int sz)
{
for (int i=0; i<sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[] = {1,2,3,4,5};
int sz = sizeof(arr)/sizeof(arr[0]);
printArray(arr, sz); // 传入数组名(首元素地址)和大小
return 0;
}
六、新手必避的 6 个坑(红标警告!)
- 下标越界:下标从 0 开始,最大下标 = 元素个数 - 1(如
int arr[5]的最大下标是 4); - 二维数组省略列数:
int arr[3][](错误,列数不能省略); - 数组初始化时赋值过多:
int arr[3] = {1,2,3,4}(错误,超出格子数); - 二分查找用于无序数组:必须先排序,否则查找结果错误;
- 数组作为函数参数未传大小:函数内无法获取正确的元素个数;
- 字符串数组未加 '\0':
char arr[] = {'a','b','c'}(打印时乱码,无结束标志)。
📝 总结
数组是 C 语言处理批量数据的 “核心工具”,核心是 “相同类型、连续存储、下标访问”。掌握一维数组的创建、初始化、遍历,二维数组的行名列名逻辑,再通过两个实战练习巩固,就能为后续指针、结构体学习打下坚实基础。
如果这篇博客帮你理清了数组的逻辑,欢迎点赞收藏!
1253

被折叠的 条评论
为什么被折叠?



