跳到主要内容

C++ array容器

简介

std::array 是C++11引入的固定大小数组容器,它是对C风格数组的封装,提供了现代C++容器的接口和功能,同时保持了传统数组的性能优势。与标准C数组不同,std::array知道自己的大小,并支持迭代器、算法和各种有用的成员函数。

备注

std::array是一个固定大小的容器,一旦创建,其大小就不能改变。如果需要动态大小的数组,应该使用std::vector

array的特点

  • 大小固定:在编译时确定,不能动态改变
  • 连续存储:元素在内存中连续存放,可以进行指针运算
  • 直接访问:可以使用[]运算符或at()方法访问元素
  • 不会自动管理内存:没有动态内存分配,性能优于vector
  • 支持STL迭代器:可以与STL算法无缝配合

基本语法

声明和初始化

cpp
#include <array>
#include <iostream>

int main() {
// 声明一个包含5个int类型元素的array
std::array<int, 5> arr1;

// 使用初始化列表
std::array<int, 5> arr2 = {1, 2, 3, 4, 5};

// C++11后的统一初始化语法
std::array<int, 5> arr3 = {{1, 2, 3, 4, 5}};
std::array<int, 5> arr4{1, 2, 3, 4, 5};

// 部分初始化,未指定的元素将被初始化为0
std::array<int, 5> arr5 = {1, 2, 3};

// 输出arr5的所有元素
for(const auto& element : arr5) {
std::cout << element << " ";
}
// 输出: 1 2 3 0 0

return 0;
}

常用操作

cpp
#include <array>
#include <iostream>
#include <algorithm>

int main() {
std::array<int, 5> arr = {5, 2, 1, 4, 3};

// 访问元素
std::cout << "第一个元素: " << arr[0] << std::endl; // 不进行边界检查
std::cout << "第二个元素: " << arr.at(1) << std::endl; // 进行边界检查
std::cout << "最后一个元素: " << arr.back() << std::endl; // 获取最后一个元素
std::cout << "第一个元素: " << arr.front() << std::endl; // 获取第一个元素

// 获取大小信息
std::cout << "数组大小: " << arr.size() << std::endl; // 元素个数
std::cout << "数组是否为空: " << arr.empty() << std::endl;// 检查容器是否为空

// 修改元素
arr[0] = 10;
arr.at(1) = 20;

// 排序
std::sort(arr.begin(), arr.end());

// 使用迭代器遍历
std::cout << "排序后的数组: ";
for (auto it = arr.begin(); it != arr.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 输出: 排序后的数组: 1 3 4 10 20

// 使用range-based for循环(C++11)
std::cout << "使用range-based for: ";
for (const auto& element : arr) {
std::cout << element << " ";
}
std::cout << std::endl;
// 输出: 使用range-based for: 1 3 4 10 20

// 填充数组
arr.fill(7);
std::cout << "填充后的数组: ";
for (const auto& element : arr) {
std::cout << element << " ";
}
std::cout << std::endl;
// 输出: 填充后的数组: 7 7 7 7 7

return 0;
}

为什么使用std::array而不是C风格数组

C风格数组有一些明显的缺点:

  • 不知道自己的大小
  • 在函数参数中退化为指针
  • 不能复制或赋值
  • 没有边界检查,容易出现越界访问
  • 没有迭代器支持

相比之下,std::array提供了更多的安全性和便利性,同时保持了相同的性能水平。

cpp
#include <array>
#include <iostream>
#include <cstring> // 用于C风格数组操作

int main() {
// C风格数组
int c_array[5] = {1, 2, 3, 4, 5};

// std::array
std::array<int, 5> std_array = {1, 2, 3, 4, 5};

// 获取大小
std::cout << "C数组大小需要使用sizeof技巧: " << sizeof(c_array) / sizeof(c_array[0]) << std::endl;
std::cout << "std::array可以直接获取大小: " << std_array.size() << std::endl;

// 边界检查
// c_array[10] = 100; // 危险!越界访问但编译器不会报错
// std_array.at(10) = 100; // 安全!会抛出std::out_of_range异常

// 复制数组
int c_array2[5];
std::memcpy(c_array2, c_array, sizeof(c_array)); // C风格复制

std::array<int, 5> std_array2 = std_array; // 简单赋值即可复制

// 函数传参时的区别
// 在这里不展示,但C数组会退化为指针,而std::array保持类型信息

return 0;
}

与vector的比较

std::arraystd::vector是两种不同用途的容器:

特性std::arraystd::vector
大小固定动态
内存分配栈上(小型数组)或静态存储区堆上
扩展能力不能扩展可以动态扩展
性能通常更快,无动态分配开销可能有动态分配开销
内存布局严格连续连续但可能重新分配
适用场景大小固定的集合大小未知或变化的集合
cpp
#include <array>
#include <vector>
#include <iostream>
#include <chrono>

int main() {
constexpr int size = 10000;

// 测量std::array性能
auto start1 = std::chrono::high_resolution_clock::now();
std::array<int, size> arr;
for (int i = 0; i < size; ++i) {
arr[i] = i;
}
auto end1 = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> duration1 = end1 - start1;

// 测量std::vector性能
auto start2 = std::chrono::high_resolution_clock::now();
std::vector<int> vec(size);
for (int i = 0; i < size; ++i) {
vec[i] = i;
}
auto end2 = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> duration2 = end2 - start2;

std::cout << "std::array 耗时: " << duration1.count() << " ms" << std::endl;
std::cout << "std::vector 耗时: " << duration2.count() << " ms" << std::endl;

return 0;
}
提示
  • 当你确切知道需要存储的元素数量并且该数量不会改变时,使用std::array
  • 当元素数量可能变化或在编译时未知时,使用std::vector

多维array

std::array可以嵌套使用来创建多维数组:

cpp
#include <array>
#include <iostream>

int main() {
// 创建3x4的二维数组
std::array<std::array<int, 4>, 3> matrix = {{
{{1, 2, 3, 4}},
{{5, 6, 7, 8}},
{{9, 10, 11, 12}}
}};

// 访问和修改元素
matrix[1][2] = 99;

// 打印二维数组
for (const auto& row : matrix) {
for (const auto& element : row) {
std::cout << element << "\t";
}
std::cout << std::endl;
}

/*
输出:
1 2 3 4
5 6 99 8
9 10 11 12
*/

return 0;
}

实际应用场景

场景1:棋盘游戏

在象棋、围棋等棋盘游戏中,可以使用std::array表示棋盘:

cpp
#include <array>
#include <iostream>

enum class ChessPiece { EMPTY, PAWN, ROOK, KNIGHT, BISHOP, QUEEN, KING };

int main() {
// 创建8x8的国际象棋棋盘
std::array<std::array<ChessPiece, 8>, 8> chessboard;

// 初始化为空棋盘
for (auto& row : chessboard) {
row.fill(ChessPiece::EMPTY);
}

// 设置一些棋子
chessboard[0][0] = ChessPiece::ROOK;
chessboard[0][1] = ChessPiece::KNIGHT;
chessboard[0][2] = ChessPiece::BISHOP;
// ... 更多初始化设置

// 模拟移动棋子
ChessPiece piece = chessboard[0][1]; // 取出骑士
chessboard[0][1] = ChessPiece::EMPTY; // 原位置变空
chessboard[2][2] = piece; // 移动到新位置

return 0;
}

场景2:图像处理

在处理小型图像或滤镜时,可以使用固定大小的数组:

cpp
#include <array>
#include <iostream>

// 定义3x3的卷积核用于图像边缘检测
std::array<std::array<int, 3>, 3> createSobelKernelX() {
return {{
{{-1, 0, 1}},
{{-2, 0, 2}},
{{-1, 0, 1}}
}};
}

// 应用卷积核到3x3图像块
int applyKernel(const std::array<std::array<int, 3>, 3>& image,
const std::array<std::array<int, 3>, 3>& kernel) {
int result = 0;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
result += image[i][j] * kernel[i][j];
}
}
return result;
}

int main() {
// 3x3图像区域
std::array<std::array<int, 3>, 3> imageRegion = {{
{{25, 40, 30}},
{{35, 45, 50}},
{{40, 50, 55}}
}};

// 创建Sobel X方向卷积核
auto sobelX = createSobelKernelX();

// 应用卷积计算边缘检测值
int edgeValue = applyKernel(imageRegion, sobelX);

std::cout << "边缘检测值: " << edgeValue << std::endl;

return 0;
}

场景3:RGB颜色表示

使用固定大小的数组表示RGB颜色:

cpp
#include <array>
#include <iostream>
#include <iomanip>
#include <string>

// RGB颜色类型
using Color = std::array<uint8_t, 3>;

// 颜色混合函数
Color mixColors(const Color& color1, const Color& color2, float ratio) {
Color result;
for (size_t i = 0; i < 3; ++i) {
result[i] = static_cast<uint8_t>(color1[i] * (1 - ratio) + color2[i] * ratio);
}
return result;
}

// 将颜色转换为十六进制字符串
std::string toHexString(const Color& color) {
std::stringstream ss;
ss << "#";
for (auto& component : color) {
ss << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(component);
}
return ss.str();
}

int main() {
Color red = {255, 0, 0};
Color blue = {0, 0, 255};

// 创建紫色 (50% 红色, 50% 蓝色)
Color purple = mixColors(red, blue, 0.5);

std::cout << "红色: " << toHexString(red) << std::endl;
std::cout << "蓝色: " << toHexString(blue) << std::endl;
std::cout << "紫色: " << toHexString(purple) << std::endl;

return 0;
}

总结

std::array是C++11引入的一个重要容器,它提供了传统C数组的高性能与STL容器的便利性和安全性的完美结合。它适用于需要固定大小数组的场景,特别是那些对性能有较高要求的场景。

主要优点:

  • 固定大小,没有动态内存分配的开销
  • 支持STL迭代器和算法
  • 提供了边界检查功能
  • 知道自己的大小
  • 可以安全地复制和传递

主要缺点:

  • 大小固定,无法动态调整
  • 定义非常大的数组可能导致栈溢出(如果是自动存储期)

练习题

  1. 编写一个函数,接受一个std::array<int, 10>参数,将其元素倒序排列并返回一个新的数组。

  2. 创建一个5x5的二维数组,初始化为单位矩阵(对角线为1,其余为0)。

  3. 实现一个简单的井字棋游戏,使用std::array<std::array<char, 3>, 3>表示棋盘。

额外资源

通过掌握std::array,你已经迈出了学习现代C++ STL容器的第一步。接下来,你可以继续深入学习其他容器,比如std::vectorstd::list等,它们在不同场景下各有优势。