C++ 数组:基础与进阶全解析

数组是 C++ 中最基础也最常用的数据结构之一,它是相同类型元素的有序集合,通过连续的内存空间存储,支持快速随机访问。本文将从数组的基本概念、声明使用、内存特性到高级应用,全面解析 C++ 数组的特性与实战技巧。
一、数组的基本概念
1.1 定义与本质
数组(Array)是一种固定大小的序列容器,用于存储相同数据类型的元素。其核心特性包括:
- 同质性:所有元素类型必须相同(如
int、double或自定义类型)。 - 连续性:元素在内存中连续存储,地址依次递增。
- 固定大小:数组一旦声明,大小不可修改(编译期确定)。
- 随机访问:通过下标(索引)可直接访问任意元素,时间复杂度 O (1)。
示意图:int arr[5] = {1, 2, 3, 4, 5} 在内存中的存储(假设起始地址为 0x1000):
plaintext
地址:0x1000 0x1004 0x1008 0x100C 0x1010
元素:1 2 3 4 5
下标:0 1 2 3 4
1.2 与其他容器的区别
| 容器类型 | 大小特性 | 内存连续性 | 随机访问 | 动态扩容 |
|---|---|---|---|---|
| 数组(Array) | 固定(编译期) | 连续 | 支持 | 不支持 |
std::vector | 动态(运行期) | 连续 | 支持 | 支持 |
std::list | 动态 | 不连续 | 不支持 | 支持 |
选择建议:
- 需固定大小、追求极致性能时用数组。
- 需动态调整大小、频繁增删元素时用
vector。
二、一维数组的声明与初始化
2.1 声明方式
数组声明的基本语法:数据类型 数组名[元素个数];
cpp
运行
#include <iostream>
using namespace std;int main() {// 1. 声明时指定大小(元素个数必须是常量表达式)int arr1[5]; // 大小为5的int数组,元素默认初始化(局部变量为随机值)// 2. 声明并初始化(部分初始化,未指定的元素为0)int arr2[5] = {1, 2, 3}; // 等价于 {1,2,3,0,0}// 3. 声明时省略大小(由初始化列表长度决定)int arr3[] = {1, 2, 3, 4}; // 大小为4// 4. C++11 列表初始化(可省略等号)int arr4[]{1, 2, 3, 4, 5}; // 大小为5return 0;
}
注意:数组大小必须是编译期常量(如 5、const int n=10),不能是变量(C99 支持变长数组,但 C++ 标准不支持)。
2.2 数组的访问与遍历
通过下标运算符 [] 访问数组元素(下标从 0 开始),遍历可使用循环或范围 for 循环(C++11)。
cpp
运行
int main() {int arr[] = {10, 20, 30, 40, 50};int n = sizeof(arr) / sizeof(arr[0]); // 计算数组长度(通用方法)// 1. 下标访问cout << "arr[0] = " << arr[0] << endl; // 10arr[2] = 300; // 修改元素cout << "arr[2] = " << arr[2] << endl; // 300// 2. 普通for循环遍历cout << "普通循环遍历:";for (int i = 0; i < n; ++i) {cout << arr[i] << " ";}// 输出:10 20 300 40 50 // 3. 范围for循环遍历(C++11,适合只读或修改元素)cout << "\n范围for循环:";for (int num : arr) {cout << num << " ";}// 输出:10 20 300 40 50 return 0;
}
计算数组长度:sizeof(arr) / sizeof(arr[0]) 是通用方法,但仅对数组名有效(对指针无效)。
三、多维数组
C++ 支持多维数组(如二维、三维),本质上是数组的数组(内存仍为连续存储)。
3.1 二维数组的声明与初始化
cpp
运行
int main() {// 1. 声明3行4列的二维数组int matrix1[3][4]; // 未初始化,元素为随机值// 2. 初始化(按行初始化)int matrix2[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}};// 3. 部分初始化(未指定的元素为0)int matrix3[3][4] = {{1}, {5,6}, {9}};// 4. 省略第一维大小(由初始化列表行数决定)int matrix4[][4] = {{1,2}, {3,4}, {5,6}}; // 3行4列return 0;
}
3.2 二维数组的访问与遍历
通过双重下标 arr[i][j] 访问第 i 行第 j 列的元素,遍历需嵌套循环。
cpp
运行
int main() {int matrix[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}};int rows = sizeof(matrix) / sizeof(matrix[0]); // 行数:3int cols = sizeof(matrix[0]) / sizeof(matrix[0][0]); // 列数:4// 访问元素cout << "matrix[1][2] = " << matrix[1][2] << endl; // 7// 遍历二维数组cout << "二维数组遍历:" << endl;for (int i = 0; i < rows; ++i) {for (int j = 0; j < cols; ++j) {cout << matrix[i][j] << "\t";}cout << endl;}return 0;
}
内存布局:二维数组在内存中按行优先存储(连续的一维空间),例如 matrix[3][4] 的元素顺序为:matrix[0][0], matrix[0][1], ..., matrix[0][3], matrix[1][0], ..., matrix[2][3]。
四、数组与指针的关系
在 C++ 中,数组名通常会隐式转换为指向首元素的指针,这是数组操作的核心特性之一。
4.1 数组名与指针的转换
cpp
运行
int main() {int arr[] = {1, 2, 3, 4, 5};int* p = arr; // 数组名隐式转换为指向首元素的指针(等价于 &arr[0])cout << "arr = " << arr << endl; // 数组首地址(如 0x7fff5fbff7c0)cout << "&arr[0] = " << &arr[0] << endl; // 与 arr 相同cout << "p = " << p << endl; // 与 arr 相同// 通过指针访问元素(等价于数组下标访问)cout << "p[0] = " << p[0] << endl; // 1(等价于 *p)cout << "p[2] = " << p[2] << endl; // 3(等价于 *(p+2))return 0;
}
关键结论:arr[i] 等价于 *(arr + i),指针的算术运算与元素类型相关(p+1 指向 next 元素,而非 next 字节)。
4.2 数组作为函数参数
数组作为函数参数时,会退化为指针(丢失大小信息),因此需额外传递数组长度。
cpp
运行
// 错误示例:函数内无法通过 sizeof 获取数组真实长度
void printArray1(int arr[]) {int n = sizeof(arr) / sizeof(arr[0]); // 错误!arr 是指针,sizeof(arr)=8(64位系统)for (int i = 0; i < n; ++i) {cout << arr[i] << " ";}
}// 正确示例:显式传递数组长度
void printArray2(int arr[], int n) {for (int i = 0; i < n; ++i) {cout << arr[i] << " ";}
}int main() {int arr[] = {1, 2, 3, 4, 5};int n = sizeof(arr) / sizeof(arr[0]);printArray1(arr); // 行为未定义(可能输出乱码)printArray2(arr, n); // 正确输出:1 2 3 4 5 return 0;
}
传递多维数组:需指定除第一维外的所有维度大小:
cpp
运行
// 二维数组作为参数(必须指定列数)
void printMatrix(int matrix[][4], int rows) {for (int i = 0; i < rows; ++i) {for (int j = 0; j < 4; ++j) {cout << matrix[i][j] << " ";}cout << endl;}
}
五、数组的常见操作与算法
5.1 查找元素
- 线性查找:遍历数组逐一比较,时间复杂度 O (n)。
- 二分查找:适用于有序数组,时间复杂度 O (log n)(需先排序)。
cpp
运行
// 线性查找
int linearSearch(int arr[], int n, int target) {for (int i = 0; i < n; ++i) {if (arr[i] == target) {return i; // 返回索引}}return -1; // 未找到
}// 二分查找(需数组有序)
int binarySearch(int arr[], int n, int target) {int left = 0, right = n - 1;while (left <= right) {int mid = left + (right - left) / 2;if (arr[mid] == target) return mid;else if (arr[mid] < target) left = mid + 1;else right = mid - 1;}return -1;
}
5.2 排序算法
数组排序是最常用的操作之一,C++ 标准库提供 std::sort(基于快速排序的优化版本)。
cpp
运行
#include <algorithm> // 包含 std::sortint main() {int arr[] = {3, 1, 4, 1, 5, 9};int n = sizeof(arr) / sizeof(arr[0]);// 升序排序(默认)sort(arr, arr + n); // 排序范围 [arr, arr+n)// 输出:1 1 3 4 5 9 // 降序排序(使用 greater 比较器)sort(arr, arr + n, greater<int>());// 输出:9 5 4 3 1 1 return 0;
}
5.3 数组逆置
cpp
运行
void reverseArray(int arr[], int n) {int left = 0, right = n - 1;while (left < right) {// 交换元素int temp = arr[left];arr[left] = arr[right];arr[right] = temp;left++;right--;}
}
六、数组的局限性与替代方案
6.1 局限性
- 大小固定:编译期确定大小,无法动态调整(如需动态扩容,需手动管理内存)。
- 传递不便:作为函数参数时退化为指针,丢失大小信息。
- 缺乏边界检查:访问越界(如下标为负数或超出范围)时,行为未定义(可能崩溃或产生垃圾值)。
6.2 替代方案:std::array 与 std::vector
std::array(C++11):封装固定大小数组,保留数组性能的同时提供容器接口(如size()、begin()/end())。cpp
运行
#include <array> // 需包含头文件int main() {array<int, 5> arr = {1, 2, 3, 4, 5}; // 大小为5的arraycout << "大小:" << arr.size() << endl; // 5for (int num : arr) { // 范围for循环cout << num << " ";}return 0; }std::vector:动态数组,支持自动扩容,是最常用的数组替代方案。cpp
运行
#include <vector>int main() {vector<int> vec = {1, 2, 3}; // 初始大小3vec.push_back(4); // 动态添加元素(大小变为4)cout << "大小:" << vec.size() << endl; // 4return 0; }
选择建议:
- 大小固定且需高性能 →
std::array(比原生数组更安全)。 - 大小动态变化 →
std::vector(功能最丰富)。 - 与 C 代码交互 → 原生数组(兼容性好)。
七、常见错误与最佳实践
7.1 常见错误
下标越界
cpp
运行
int arr[5] = {1,2,3,4,5}; cout << arr[5] << endl; // 越界访问(下标最大为4),行为未定义数组名直接赋值
cpp
运行
int arr1[5] = {1,2,3,4,5}; int arr2[5]; arr2 = arr1; // 错误!数组名是常量指针,不能直接赋值误用指针计算数组长度
cpp
运行
void func(int arr[]) {int n = sizeof(arr) / sizeof(arr[0]); // 错误!arr是指针,n计算错误 }
7.2 最佳实践
使用常量定义数组大小
cpp
运行
const int SIZE = 10; // 用const定义大小,便于修改和维护 int arr[SIZE];优先使用标准库容器用
std::array或std::vector替代原生数组,减少手动管理的错误。避免裸指针操作数组与指针结合时易出错,尽量使用范围 for 循环或迭代器遍历。
边界检查访问数组时确保下标在
[0, n-1]范围内,必要时添加断言:cpp
运行
#include <cassert> int getElement(int arr[], int n, int index) {assert(index >= 0 && index < n); // 断言检查下标合法性return arr[index]; }
八、总结
数组是 C++ 中存储同类型元素的基础结构,依托连续内存实现 O (1) 随机访问,适合需要快速读写的场景。其核心特性包括固定大小、同质性和内存连续性。
在实际开发中:
- 简单固定大小场景可使用原生数组或
std::array。 - 动态大小场景优先使用
std::vector。 - 操作数组时需注意下标越界和指针退化问题。
掌握数组的使用是学习更复杂数据结构(如链表、栈、队列)的基础,理解其内存模型和性能特性,能帮助开发者写出更高效、更安全的代码。
