1. 数组基础与核心概念
1.1 数组的本质与内存布局
数组是C++中最基础也是最强大的数据结构之一。本质上,数组是一块连续的内存空间,用于存储相同类型的元素集合。这种连续存储的特性带来了两个重要特点:
- 随机访问高效:通过下标可以直接计算出元素的内存地址,访问时间为O(1)
- 内存局部性好:相邻元素在物理内存上也相邻,这对CPU缓存非常友好
让我们看一个简单的内存布局示例:
cpp复制int arr[5] = {10, 20, 30, 40, 50};
在内存中的表示可能如下:
code复制地址: 0x1000 0x1004 0x1008 0x100C 0x1010
值: [10] [20] [30] [40] [50]
每个int占4字节(32位系统),所以相邻元素地址相差4。这也是为什么数组下标从0开始——第一个元素的地址就是数组基地址,第n个元素的地址=基地址 + n*元素大小。
1.2 数组声明与初始化的六种方式
C++提供了多种数组初始化的语法形式,各有适用场景:
cpp复制// 1. 声明时不初始化(元素值不确定)
int arr1[5];
// 2. 传统初始化列表
int arr2[5] = {1, 2, 3, 4, 5};
// 3. 部分初始化(剩余元素自动初始化为0)
int arr3[5] = {1, 2}; // [1,2,0,0,0]
// 4. 全零初始化(两种等效写法)
int arr4[5] = {0};
int arr5[5] = {}; // C++11起支持
// 5. 自动推断数组大小
int arr6[] = {1,2,3,4,5}; // 编译器推断大小为5
// 6. C++11统一初始化语法
int arr7[5]{10,20,30,40,50};
注意:在C++中,数组大小必须是编译期常量表达式。C99引入的变长数组(VLA)特性在标准C++中不被支持。
1.3 数组越界的深入解析
数组越界是C++中最危险的错误之一,因为它不会立即导致程序崩溃,而是会悄无声息地破坏其他内存区域。理解其底层机制非常重要。
cpp复制int arr[3] = {10,20,30};
int important = 999;
// 内存布局可能如下:
// [arr[0]][arr[1]][arr[2]][important]
// 10 20 30 999
arr[3] = 0; // 实际上修改了important的值!
为什么C++不检查数组边界?主要有三个原因:
- 性能考虑:边界检查会增加运行时开销
- 历史原因:兼容C语言的设计哲学
- 灵活性:允许底层内存操作
防御性编程建议:
- 使用
std::array(C++11)替代原生数组 - 在调试版本中添加边界检查断言
- 使用范围for循环代替传统for循环
- 将数组大小定义为常量
cpp复制constexpr int SIZE = 5;
int arr[SIZE];
// 更安全的遍历方式
for(int i=0; i<SIZE; i++) {
// ...
}
// 或者使用范围for
for(int num : arr) {
// ...
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数组操作与算法实践
2.1 数组遍历的三种范式
遍历数组有多种方式,各有优缺点:
cpp复制int arr[] = {10,20,30,40,50};
int size = sizeof(arr)/sizeof(arr[0]);
// 1. 传统for循环(最灵活)
for(int i=0; i<size; i++) {
cout << arr[i] << " ";
}
// 2. 范围for循环(C++11,最简洁)
for(int num : arr) {
cout << num << " ";
}
// 3. 指针遍历(最接近底层)
for(int *p=arr; p!=arr+size; p++) {
cout << *p << " ";
}
性能考虑:在现代编译器优化下,这三种方式性能几乎相同。范围for循环在可读性上优势明显,但在需要索引时仍需使用传统for循环。
2.2 数组常见算法实现
2.2.1 查找算法
线性查找是最基础的查找方式:
cpp复制int linearSearch(const int arr[], int size, int target) {
for(int i=0; i<size; i++) {
if(arr[i] == target) {
return i;
}
}
return -1; // 未找到
}
对于已排序数组,二分查找效率更高(O(log n)):
cp复制
