C++ 模板类array用法详解
C++ 模板类 array 简介
在 C++ 编程中,std::array
是 C++11 引入的标准模板库(STL)中的一个容器。它提供了一种在栈上存储固定大小数组的方式,与传统的 C 风格数组相比,std::array
具有更多的功能和更好的安全性。
从本质上来说,std::array
是一个封装了固定大小数组的类模板。它在编译时就确定了大小,这意味着其大小在运行时不可改变。std::array
定义在 <array>
头文件中,使用时需要包含该头文件。
例如,声明一个包含 5 个 int
类型元素的 std::array
:
#include <array>
std::array<int, 5> myArray;
这里 std::array<int, 5>
表示一个存储 int
类型数据、大小为 5 的数组。
std::array
的特点
- 固定大小:
std::array
的大小在编译期就确定,一旦声明,运行时无法改变其大小。这与std::vector
等动态大小的容器形成鲜明对比。例如:
std::array<int, 10> fixedArray;
// 下面这行代码会导致编译错误,因为 std::array 大小不能改变
// fixedArray.resize(20);
- 栈上存储:与传统 C 风格数组类似,
std::array
的元素通常存储在栈上(取决于其作用域)。这使得它在性能上对于小尺寸数组有一定优势,因为避免了堆内存分配和释放的开销。 - 安全性:
std::array
提供了一些方法来安全地访问元素,如at()
方法。at()
方法在访问越界时会抛出std::out_of_range
异常,而传统 C 风格数组访问越界会导致未定义行为。例如:
std::array<int, 5> arr = {1, 2, 3, 4, 5};
try {
std::cout << arr.at(10) << std::endl;
} catch (const std::out_of_range& e) {
std::cerr << "Out of range exception: " << e.what() << std::endl;
}
- 支持 STL 算法:
std::array
与 STL 算法兼容,因为它满足 STL 容器的要求,如提供了迭代器。这使得我们可以方便地对std::array
使用各种 STL 算法,例如排序、查找等。
std::array
的声明和初始化
- 默认初始化:可以像声明普通变量一样声明
std::array
,此时数组元素会进行默认初始化。例如,对于int
类型,默认初始化为 0。
std::array<int, 3> defaultInitArray;
for (int i = 0; i < defaultInitArray.size(); ++i) {
std::cout << defaultInitArray[i] << " ";
}
输出结果为 0 0 0
。
- 列表初始化:使用花括号
{}
进行列表初始化,可以指定数组元素的值。初始化列表中的元素个数不能超过std::array
的大小。
std::array<int, 4> listInitArray = {1, 2, 3, 4};
如果初始化列表中的元素个数小于 std::array
的大小,剩余元素将进行默认初始化。例如:
std::array<int, 5> partialListInitArray = {1, 2};
for (int i = 0; i < partialListInitArray.size(); ++i) {
std::cout << partialListInitArray[i] << " ";
}
输出结果为 1 2 0 0 0
。
- 聚合初始化:在 C++17 及以后,
std::array
支持聚合初始化,允许省略等号=
。
std::array<int, 3> aggregateInitArray {1, 2, 3};
std::array
的常用成员函数
at()
:该函数用于访问指定位置的元素,并进行边界检查。如果索引越界,会抛出std::out_of_range
异常。
std::array<int, 5> arr = {1, 2, 3, 4, 5};
try {
std::cout << arr.at(2) << std::endl;
std::cout << arr.at(10) << std::endl;
} catch (const std::out_of_range& e) {
std::cerr << "Out of range exception: " << e.what() << std::endl;
}
operator[]
:提供与传统 C 风格数组相同的方括号访问方式。它不进行边界检查,因此访问越界会导致未定义行为。
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::cout << arr[2] << std::endl;
// 下面这行代码访问越界,会导致未定义行为
// std::cout << arr[10] << std::endl;
front()
:返回数组的第一个元素。如果数组为空,调用front()
会导致未定义行为。
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::cout << arr.front() << std::endl;
back()
:返回数组的最后一个元素。同样,如果数组为空,调用back()
会导致未定义行为。
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::cout << arr.back() << std::endl;
data()
:返回一个指向数组首元素的指针。这使得std::array
可以与需要 C 风格数组指针的函数进行交互。
std::array<int, 5> arr = {1, 2, 3, 4, 5};
int* ptr = arr.data();
for (int i = 0; i < arr.size(); ++i) {
std::cout << ptr[i] << " ";
}
size()
:返回数组中元素的个数。这个值在编译时就确定,并且不会改变。
std::array<int, 5> arr;
std::cout << arr.size() << std::endl;
empty()
:检查数组是否为空。对于std::array
,除非其大小为 0,否则永远不会为空。
std::array<int, 5> arr;
std::cout << (arr.empty()? "Empty" : "Not Empty") << std::endl;
std::array<int, 0> emptyArr;
std::cout << (emptyArr.empty()? "Empty" : "Not Empty") << std::endl;
std::array
与 STL 算法
由于 std::array
支持迭代器,它可以与 STL 算法很好地配合使用。
- 排序:可以使用
std::sort
算法对std::array
进行排序。
#include <algorithm>
#include <array>
#include <iostream>
int main() {
std::array<int, 5> arr = {3, 1, 4, 1, 5};
std::sort(arr.begin(), arr.end());
for (int num : arr) {
std::cout << num << " ";
}
return 0;
}
- 查找:使用
std::find
算法在std::array
中查找元素。
#include <algorithm>
#include <array>
#include <iostream>
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
auto it = std::find(arr.begin(), arr.end(), 3);
if (it != arr.end()) {
std::cout << "Element found at position: " << std::distance(arr.begin(), it) << std::endl;
} else {
std::cout << "Element not found" << std::endl;
}
return 0;
}
std::array
的嵌套使用
std::array
可以嵌套使用,形成多维数组。例如,创建一个二维数组:
std::array<std::array<int, 3>, 2> twoDArray = {
{1, 2, 3},
{4, 5, 6}
};
for (const auto& innerArray : twoDArray) {
for (int num : innerArray) {
std::cout << num << " ";
}
std::cout << std::endl;
}
这里外层 std::array
的每个元素又是一个包含 3 个 int
的 std::array
,从而形成了一个 2x3 的二维数组。
std::array
与传统 C 风格数组的比较
- 安全性:如前文所述,
std::array
的at()
方法提供了边界检查,而传统 C 风格数组没有。这使得std::array
在访问元素时更安全,减少了越界访问导致的错误。 - 功能:
std::array
提供了一系列成员函数,如size()
、empty()
等,方便获取数组的信息。传统 C 风格数组则需要额外的代码来实现类似功能。 - 性能:在小尺寸数组的情况下,
std::array
由于在栈上存储,性能与传统 C 风格数组相近甚至更好,因为避免了堆内存分配。但对于大尺寸数组,动态内存分配的std::vector
可能更具优势,因为栈空间有限。 - 类型安全性:
std::array
是类型安全的,而传统 C 风格数组本质上是指针,容易出现类型错误,例如指针类型不匹配。
std::array
的内存布局
std::array
的内存布局与传统 C 风格数组类似,元素在内存中是连续存储的。这使得 std::array
在性能上有一定优势,特别是在需要顺序访问元素的场景下,因为连续的内存布局有利于提高缓存命中率。
例如,假设我们有一个 std::array<int, 10>
,其元素在内存中是依次排列的,从第一个元素的地址开始,每个 int
类型元素占用 4 个字节(假设 int
为 4 字节),第二个元素的地址就是第一个元素地址加上 4 字节,以此类推。
这种连续的内存布局也使得 std::array
可以方便地与需要 C 风格数组指针的函数进行交互,通过 data()
方法获取的指针指向的内存布局与传统 C 风格数组完全一致。
std::array
在函数参数传递中的应用
- 按值传递:可以将
std::array
作为函数参数按值传递。此时函数会得到数组的一份拷贝。
void printArray(std::array<int, 5> arr) {
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
}
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
printArray(arr);
return 0;
}
按值传递的缺点是如果数组较大,拷贝的开销会比较大。
- 按引用传递:为了避免拷贝开销,可以按引用传递
std::array
。
void modifyArray(std::array<int, 5>& arr) {
for (auto& num : arr) {
num *= 2;
}
}
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
modifyArray(arr);
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
按引用传递可以直接修改原数组,同时避免了拷贝开销。
- 传递指针:还可以通过
data()
方法获取指针,将std::array
以指针的形式传递给函数。这种方式在与需要 C 风格数组指针的函数交互时很有用。
void processArray(int* arr, size_t size) {
for (size_t i = 0; i < size; ++i) {
arr[i] += 1;
}
}
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
processArray(arr.data(), arr.size());
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
std::array
在模板元编程中的应用
std::array
在模板元编程中也有一定的应用。由于其大小在编译期确定,结合模板特化等技术,可以实现一些编译期的计算和操作。
例如,我们可以定义一个模板函数,计算 std::array
所有元素的和,并且在编译期完成计算。
template <typename T, size_t N>
constexpr T sumArray(const std::array<T, N>& arr) {
T result = 0;
for (size_t i = 0; i < N; ++i) {
result += arr[i];
}
return result;
}
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
constexpr int sum = sumArray(arr);
std::cout << "Sum: " << sum << std::endl;
return 0;
}
这里 sumArray
函数使用 constexpr
修饰,使得其可以在编译期执行,从而提高了程序的性能。
std::array
的局限性
- 大小固定:虽然固定大小在某些场景下是优点,但在需要动态改变数组大小的情况下,
std::array
就无法满足需求,此时需要使用std::vector
等动态大小的容器。 - 缺乏一些高级功能:与
std::vector
相比,std::array
缺乏一些高级功能,如自动内存管理策略的调整、更灵活的内存分配器支持等。
总结 std::array
的适用场景
- 已知固定大小的数组:当数组大小在编译期就确定,并且不会改变时,
std::array
是一个很好的选择,例如表示 RGB 颜色值的数组(通常是 3 个元素)。 - 性能敏感的场景:对于小尺寸数组,由于
std::array
在栈上存储,避免了堆内存分配的开销,在性能敏感的场景下可以提高效率。 - 需要安全性和 STL 兼容性:如果既需要数组的安全性(如边界检查),又希望能方便地使用 STL 算法,
std::array
能很好地满足这些需求。
综上所述,std::array
作为 C++ 标准库中的一个重要容器,在很多场景下都能发挥其独特的优势,合理使用 std::array
可以提高程序的安全性、性能和代码的可读性。在实际编程中,应根据具体需求选择合适的容器,以达到最佳的编程效果。