数组的基本概念与操作
中等9轻松掌握数组:你的第一个数据结构
数组就像一个带编号的储物柜,能快速存取物品,但柜子数量固定,要在中间加塞或拿走东西就得搬动后面的所有东西。在编程世界里,数组是最基础、最常用的数据结构,它把相同类型的数据(比如全班同学的考试成绩)整齐排列在一起,每个数据都有一个从0开始的编号(下标),你只要知道编号就能瞬间拿到对应数据。
生活中的数组——储物柜、班级座位、零食架
想象一下,学校体育馆有一排编号为0到49的储物柜(注意,编程里习惯从0开始编号)。你把自己的运动鞋放在3号柜里,水杯放在8号柜里。取鞋时,直接走到3号柜,掏出钥匙打开——不用翻遍所有柜子。即使有1000个柜子,只要知道编号,存取都一步到位。
再看教室里的座位:第一排从左到右有6个座位,编号0、1、2、3、4、5。小明坐在3号位,小红坐在5号位。老师要发作业本,只要根据座位号就能瞬间找到对应的同学。但如果突然插入一个新同学,让他坐在3号和4号之间,那就要把4号、5号的同学都往后挪一个位置,还得重新调整座位号,非常麻烦。
这些例子都体现了数组的两大特点:
- 快速存取:通过下标(编号)直接拿到数据,速度不随数据量增大而变慢(O(1))。
- 固定大小,插入删除慢:一旦创建,柜子数量就不能变。想在中间插入一个数据,必须把后面的数据一个一个往后挪(O(n))。
数组的核心原理——连续的内存格子
在计算机内存里,数组是一块 连续 的空间。比如你声明了一个存放5个整数的数组,每个整数占4个字节,那么这5个整数在内存里是紧挨着存放的。假设数组的起始地址是100,那么:
- arr[0] 占地址 100~103
- arr[1] 占地址 104~107
- arr[2] 占地址 108~111
- arr[3] 占地址 112~115
- arr[4] 占地址 116~119
所以,要访问 arr[3],计算机只需要计算:100 + 3 * 4 = 112,直接跳过去读数据,不需要像链表那样一个个找。这就是数组“随机访问”的秘诀——速度与数组长度无关(O(1))。
不过,连续也带来一个麻烦:创建数组时就要告诉计算机“我要5个格子”,计算机在内存里找一块连续的空地给你。一旦用完,不能再往旁边加格子,因为旁边的内存可能已经被别人占用了。如果你想多存一个数,只能重新找一块更大的连续空地,把旧数据全部搬过去(这就是动态数组的原理)。
数组的基本操作——增删改查
数组的常见操作有:创建、访问、修改、遍历、查找、插入、删除。下面我们一个个来看,同时指出新手常犯的错误。
1. 创建数组
- C++:必须指定大小(用常量或字面量),可以同时初始化。
- Python:直接用列表(list),它虽然是动态数组,但我们可以先把它当作固定大小数组来学习。
// C++
const int SIZE = 5;
int scores[SIZE] = {85, 92, 78, 90, 88}; // 一个班级5个学生的成绩
# Python
scores = [85, 92, 78, 90, 88]
常见错误:忘记指定大小或大小写错。C++中数组大小必须是编译时确定的常量,不能用变量(除非用动态分配,但那是进阶内容)。
2. 访问和修改元素
通过 数组名[下标] 读写。下标从0开始,最大是长度减1。
// C++
cout << "第一个学生成绩:" << scores[0] << endl; // 输出85
scores[0] = 100; // 修改第一个成绩为100
cout << "修改后:" << scores[0] << endl;
# Python
print("第一个学生成绩:", scores[0]) # 输出85
scores[0] = 100
print("修改后:", scores[0])
常见错误:下标越界。比如数组长度是5,却访问 scores[5] 或 scores[-1]。C++中会读取内存垃圾数据(甚至崩溃);Python中会报 IndexError。
3. 遍历数组
用循环依次访问每个元素。常用 for 循环。
// C++
cout << "全班成绩:";
for (int i = 0; i < 5; i++) {
cout << scores[i] << " ";
}
cout << endl;
# Python
print("全班成绩:", end="")
for score in scores: # 可以直接遍历元素
print(score, end=" ")
print()
常见错误:循环条件写错,比如 i <= 5 导致越界;或者 Python 中同时需要下标时误用 for i in scores 而不知道索引。
4. 查找特定值
从第一个元素开始依次比较,直到找到目标值。这叫 顺序查找,最坏情况下要比较所有元素(O(n))。
// C++
int target = 90;
int found_index = -1; // 没找到就用-1表示
for (int i = 0; i < 5; i++) {
if (scores[i] == target) {
found_index = i;
break; // 找到立刻退出循环
}
}
if (found_index != -1)
cout << "分数" << target << "在第" << found_index << "个位置" << endl;
else
cout << "没找到" << endl;
# Python
target = 90
if target in scores: # 更Pythonic的写法
idx = scores.index(target)
print(f"分数{target}在第{idx}个位置")
else:
print("没找到")
5. 插入元素(在指定位置)
插入前,必须确保数组有空间(因为长度固定)。实际操作中,我们会创建一个更大的临时数组,把原数组元素和新元素按顺序放进去。这个过程需要移动插入位置后面的所有元素。
生活例子:你有一个5格零食架,上面摆着薯片、饼干、巧克力、糖果、果冻。你想在饼干(位置1)和巧克力(位置2)之间插入一包瓜子,就必须把巧克力、糖果、果冻都往后挪一格,腾出第三个格子放瓜子。
// C++ 演示插入(模拟,原数组大小不变,我们用临时数组)
const int SIZE = 5;
int scores[SIZE] = {85, 92, 78, 90, 88};
int new_scores[SIZE+1]; // 扩大一格
int insert_pos = 2; // 在下标2处插入(原来下标2是78)
int insert_val = 100;
// 分三段复制
for (int i = 0; i < SIZE+1; i++) {
if (i < insert_pos)
new_scores[i] = scores[i];
else if (i == insert_pos)
new_scores[i] = insert_val;
else
new_scores[i] = scores[i-1]; // 原数组元素后移一位
}
// 现在new_scores为 [85,92,100,78,90,88]
# Python 直接用列表内置方法
scores = [85, 92, 78, 90, 88]
scores.insert(2, 100) # 在索引2插入100,自动后移
print(scores) # [85, 92, 100, 78, 90, 88]
6. 删除元素(在指定位置)
删除后,后面的元素要往前移动填补空位,数组长度减1(同样需要新建数组)。
// C++ 演示删除
int del_pos = 2; // 删除下标2的元素(原78)
int smaller_scores[SIZE-1];
for (int i = 0; i < SIZE; i++) {
if (i < del_pos)
smaller_scores[i] = scores[i];
else if (i > del_pos)
smaller_scores[i-1] = scores[i];
// 跳过 i == del_pos 不复制
}
// smaller_scores 为 [85, 92, 90, 88]
# Python 直接用pop
scores = [85, 92, 78, 90, 88]
scores.pop(2) # 删除下标2的元素,返回78
print(scores) # [85, 92, 90, 88]
重要提示:插入和删除操作的时间都是 O(n),因为需要移动最多 n 个元素。如果你程序里频繁执行这类操作,数组可能不是最好的选择——这时候就该考虑链表了。
完整可运行的代码示例
下面用 C++ 和 Python 分别写一个包含所有基础操作的完整程序。变量名用简短英文单词,每行都加了中文注释。
C++ 完整示例
#include <iostream>
using namespace std;
int main() {
// 1. 创建:5个整数的数组,存储某次考试分数
const int SIZE = 5;
int scores[SIZE] = {85, 92, 78, 90, 88}; // 初始化
// 2. 访问与修改
cout << "原第1个分数: " << scores[0] << endl; // 输出85
scores[0] = 100; // 把第一个改为100
cout << "修改后第1个分数: " << scores[0] << endl;
// 3. 遍历
cout << "所有分数: ";
for (int i = 0; i < SIZE; i++) {
cout << scores[i] << " ";
}
cout << endl;
// 4. 查找:找到分数90的下标
int target = 90;
int found_index = -1; // -1表示没找到
for (int i = 0; i < SIZE; i++) {
if (scores[i] == target) {
found_index = i;
break;
}
}
if (found_index != -1)
cout << "分数" << target << "在下标" << found_index << endl;
else
cout << "分数" << target << "未找到" << endl;
// 5. 插入演示:在下标2处插入99,需要新数组
int insert_pos = 2;
int insert_val = 99;
int new_scores[SIZE + 1]; // 新数组比原数组多一格
for (int i = 0; i < SIZE + 1; i++) {
if (i < insert_pos) {
new_scores[i] = scores[i]; // 原封不动复制前面
} else if (i == insert_pos) {
new_scores[i] = insert_val; // 放入新值
} else {
new_scores[i] = scores[i - 1]; // 原数组后移一位
}
}
cout << "插入后新数组: ";
for (int i = 0; i < SIZE + 1; i++) {
cout << new_scores[i] << " ";
}
cout << endl;
// 6. 删除演示:删除下标2的元素(原78),注意大小减少
int del_pos = 2;
int smaller_scores[SIZE - 1]; // 新数组少一格
for (int i = 0; i < SIZE; i++) {
if (i < del_pos) {
smaller_scores[i] = scores[i]; // 前面不变
} else if (i > del_pos) {
smaller_scores[i - 1] = scores[i]; // 后面前移
}
// i == del_pos 跳过
}
cout << "删除后新数组: ";
for (int i = 0; i < SIZE - 1; i++) {
cout << smaller_scores[i] << " ";
}
cout << endl;
return 0;
}
Python 完整示例
# 1. 创建数组(Python 列表)
scores = [85, 92, 78, 90, 88] # 5个分数
# 2. 访问与修改
print("原第1个分数:", scores[0])
scores[0] = 100 # 修改
print("修改后第1个分数:", scores[0])
# 3. 遍历
print("所有分数: ", end="")
for score in scores:
print(score, end=" ")
print()
# 4. 查找:找到分数90的下标
target = 90
if target in scores:
idx = scores.index(target) # 返回第一个匹配的下标
print(f"分数{target}在下标{idx}")
else:
print(f"分数{target}未找到")
# 5. 插入:在下标2处插入99
insert_pos = 2
insert_val = 99
scores.insert(insert_pos, insert_val) # 自动移动元素
print("插入后数组:", scores)
# 6. 删除:删除下标2的元素(原78被移到了下标3?注意插入后数组变了)
# 我们重新创建一个删除的例子
scores2 = [85, 92, 78, 90, 88] # 新数组,原样
del_pos = 2
deleted_value = scores2.pop(del_pos) # 删除并返回
print(f"删除下标{del_pos}的值{deleted_value}后数组:", scores2)
运行上面 Python 代码时要注意:步骤5的插入会改变 scores 列表,所以步骤6应该用另一个独立的列表来演示删除,避免混淆。我已在代码中说明。
常见错误与注意事项
-
下标越界:新手最容易犯的错误。比如数组有5个元素,却访问
arr[5](最后一个下标是4)。C++ 中不会报错,但会返回垃圾值(或导致程序崩溃);Python 中会抛出IndexError。记住:下标从0开始,最大是长度-1。 -
忘记给数组分配足够空间:在C++里,如果声明
int arr[10];然后试图放20个元素,会覆盖后面的内存,导致不可预料的错误。 -
遍历时循环条件写错:比如
for (int i=0; i<=SIZE; i++)会多循环一次导致越界。常用i < SIZE。 -
插入删除时移动元素顺序搞反:比如插入时,应该从最后一个元素开始往后移,而不是从插入位置开始(否则会覆盖数据)。上面的C++代码使用临时数组避开了这个坑,但如果你试图在原数组上操作,要多加小心。
-
混淆静态数组和动态数组:C++静态数组大小在编译时就固定了;Python的list是动态的,可以自动扩容,但底层也是数组,插入删除依然要移动元素。
总结与下一步
数组就像一排编号固定的储物格,存取快(O(1)),但修改中间的内容慢(O(n))。它是计算机科学里最基础的数据结构,理解数组是学习其他数据结构(如链表、栈、队列、哈希表)的垫脚石。
如果你经常需要在中间插入或删除数据,用数组就会很痛苦——每次都要搬动一堆元素。这时候就要请出它的好兄弟——链表。链表就像一条锁链,每个节点既存数据又存下一个节点的位置,插入和删除只需要断开或接通两端的链子,非常快(O(1)),但查找某个元素必须从头一个个找(O(n))。这两种结构各有优势,根据不同需求选择。
接下来,你可以继续学习 链表,看看它是怎么解决数组的痛点的,以及动态数组(如Python的list、C++的vector)是如何结合两者优点的。
例题精讲
以下关于数组的描述,哪一项是正确的?
已知数组A有n个元素(下标从0到n-1),现要在下标为i的位置(0 ≤ i ≤ n)插入一个新元素,则需要移动的元素个数为(假设插入后数组长度变为n+1):
在数组中,通过下标访问任意元素的时间复杂度是O(1)。
在大多数主流编程语言(如C、C++、Java、Python)中,数组的下标都是从1开始的。
以下C语言代码定义了一个长度为5的整型数组,并将第一个元素赋值为10。请补充完整。
int arr[5];
___ = 10;