
数组是 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}; // 大小为5
return 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; // 10
arr[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]); // 行数:3
int 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::sort
int 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的array cout << "大小:" << arr.size() << endl; // 5 for (int num : arr) { // 范围for循环 cout << num << " "; } return 0; } -
std::vector:动态数组,支持自动扩容,是最常用的数组替代方案。cpp
运行
#include <vector> int main() { vector<int> vec = {1, 2, 3}; // 初始大小3 vec.push_back(4); // 动态添加元素(大小变为4) cout << "大小:" << vec.size() << endl; // 4 return 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。 - 操作数组时需注意下标越界和指针退化问题。
掌握数组的使用是学习更复杂数据结构(如链表、栈、队列)的基础,理解其内存模型和性能特性,能帮助开发者写出更高效、更安全的代码。