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

数组是 C++ 中最基础也最常用的数据结构之一,它是相同类型元素的有序集合,通过连续的内存空间存储,支持快速随机访问。本文将从数组的基本概念、声明使用、内存特性到高级应用,全面解析 C++ 数组的特性与实战技巧。

一、数组的基本概念

1.1 定义与本质

数组(Array)是一种固定大小 的序列容器,用于存储相同数据类型的元素。其核心特性包括:

  • 同质性 :所有元素类型必须相同(如 intdouble 或自定义类型)。
  • 连续性:元素在内存中连续存储,地址依次递增。
  • 固定大小:数组一旦声明,大小不可修改(编译期确定)。
  • 随机访问:通过下标(索引)可直接访问任意元素,时间复杂度 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;
}

注意 :数组大小必须是编译期常量 (如 5const 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 局限性

  1. 大小固定:编译期确定大小,无法动态调整(如需动态扩容,需手动管理内存)。
  2. 传递不便:作为函数参数时退化为指针,丢失大小信息。
  3. 缺乏边界检查:访问越界(如下标为负数或超出范围)时,行为未定义(可能崩溃或产生垃圾值)。

6.2 替代方案:std::arraystd::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 常见错误

  1. 下标越界

    cpp

    运行

    复制代码
    int arr[5] = {1,2,3,4,5};
    cout << arr[5] << endl;  // 越界访问(下标最大为4),行为未定义
  2. 数组名直接赋值

    cpp

    运行

    复制代码
    int arr1[5] = {1,2,3,4,5};
    int arr2[5];
    arr2 = arr1;  // 错误!数组名是常量指针,不能直接赋值
  3. 误用指针计算数组长度

    cpp

    运行

    复制代码
    void func(int arr[]) {
        int n = sizeof(arr) / sizeof(arr[0]);  // 错误!arr是指针,n计算错误
    }

7.2 最佳实践

  1. 使用常量定义数组大小

    cpp

    运行

    复制代码
    const int SIZE = 10;  // 用const定义大小,便于修改和维护
    int arr[SIZE];
  2. 优先使用标准库容器std::arraystd::vector 替代原生数组,减少手动管理的错误。

  3. 避免裸指针操作数组与指针结合时易出错,尽量使用范围 for 循环或迭代器遍历。

  4. 边界检查 访问数组时确保下标在 [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
  • 操作数组时需注意下标越界和指针退化问题。

掌握数组的使用是学习更复杂数据结构(如链表、栈、队列)的基础,理解其内存模型和性能特性,能帮助开发者写出更高效、更安全的代码。

相关推荐
量子炒饭大师1 小时前
【一天一个计算机知识】—— 【编程百度】翻译环境与运行环境
c语言·汇编·c++·gitee·机器翻译
5335ld1 小时前
后端给的post 方法但是要求传表单数据格式(没有{})
开发语言·前端·javascript·vue.js·ecmascript
量子炒饭大师1 小时前
【一天一个计算机知识】—— 【编程百度】预处理指令
java·开发语言
任子菲阳2 小时前
学Java第四十四天——Map实现类的源码解析
java·开发语言
听风吟丶2 小时前
Java 11+ HttpClient 实战:从 HttpURLConnection 到现代 HTTP 客户端的全面升级
java·开发语言·http
今晚打老虎2 小时前
c++(斗罗大陆3)
开发语言·c++·斗罗大陆3
mywpython2 小时前
Python使用消息队列rabbitmq
开发语言·python·rabbitmq
hygge9992 小时前
JVM GC 垃圾回收体系完整讲解
java·开发语言·jvm·经验分享·面试
wuwu_q2 小时前
通俗易懂 + Android 开发实战的方式,详细讲讲 Kotlin 中的 StateFlow
android·开发语言·kotlin