1 std::array 简介
std::array 是 C++11 标准库提供的一个固定大小的容器,用于存储特定类型的元素序列。该类型作为 C 风格数组的改进和封装,具有更多的功能和安全性。使用 std::array 需要引入头文件 <array> 。
std::array 的大小在创建时就已经确定,并且之后不能再改变。这种固定大小的特性使得 std::array 在内存使用上是高效的,因为它不需要动态分配内存或管理内存大小的变化。同时,由于其大小在编译时就已知,编译器可以进行一些优化,提高代码的执行效率。
与 C 风格数组相比,std::array 提供了更多的成员函数和迭代器支持,使得对数组的操作更加便捷和安全。例如,std::array 提供了 size() 函数来获取数组的大小,at() 函数来访问元素(同时提供边界检查以避免越界错误),以及 begin() 和 end() 函数来获取迭代器,从而可以方便地使用 STL 算法对数组进行操作。
此外,std::array 还支持像其他 STL 容器一样使用范围 for 循环进行遍历,使得代码更加简洁易读。同时,由于 std::array 是标准库的一部分,因此它具有跨平台性和可移植性,可以在不同的编译器和操作系统上使用。
需要注意的是,由于 std::array 的大小是固定的,因此在需要动态调整数组大小的情况下,可能需要考虑使用其他容器,如 std::vector。然而,在已知数组大小且不需要动态调整的情况下,std::array 通常是一个更好的选择,因为它提供了更多的功能和安全性,同时避免了动态内存分配的开销。
2 std::array 与 C风格数组的区别
std::array 与 C 风格数组在多个方面存在显著的差异。如下是主要区别:
(1)固定大小与动态大小:
std::array 是一个固定大小的数组,其大小在创建时就已经确定,之后不能再改变。这种固定大小的特性使得 std::array 在内存使用上更为高效,并且编译器可以针对其大小进行特定的优化。
相比之下,C 风格数组的大小可以是动态的,这意味着在运行时可以根据需要分配和调整数组的大小。然而,这也带来了动态内存分配和管理的复杂性。
(2)内存分配:
std::array 的对象通常是在栈上分配的,这意味着其生命周期与创建它的作用域相同。这种分配方式使得 std::array 在性能上更为高效,因为栈上的内存分配和释放通常比堆上更快。
C 风格数组可以在栈上或堆上分配,这取决于数组是如何声明的。在函数内部声明的局部数组通常位于栈上,而使用 malloc 或 new 动态创建的数组则位于堆上。
(3)安全性和边界检查:
std::array 提供了边界检查,这有助于避免访问数组时的越界错误,从而提高了代码的安全性。例如,使用 at() 成员函数访问元素时,如果索引超出范围,程序会抛出异常。
C 风格数组不提供这样的边界检查,因此程序员必须自己确保不会访问数组的非法区域。这增加了出错的可能性,尤其是在处理复杂逻辑或大量数据时。
(4)使用便捷性:
std::array 提供了许多便利的成员函数和迭代器,使得对数组的操作更加简单和直观。例如,可以使用 size() 函数获取数组大小,使用 begin() 和 end() 函数获取迭代器以进行范围操作,还可以使用 fill() 函数一次性设置所有元素的值。
C 风格数组的操作相对较为基础,没有提供这些便利的函数和迭代器。对数组元素的访问和操作通常需要使用索引和指针,这在某些情况下可能会使代码变得复杂和难以维护。
(5)与 STL 算法的兼容性:
std::array 作为 STL 容器,与 STL 算法完美兼容。这意味着你可以使用 STL 算法库中的函数对 std::array 进行各种操作,如排序、查找等。
C 风格数组虽然也可以与一些 STL 算法结合使用,但通常需要额外的处理或转换,不如 std::array 直接和方便。
综上所述,std::array 在安全性、使用便捷性以及与 STL 算法的兼容性等方面相比C风格数组具有显著优势。因此,在 C++ 编程中,当需要使用固定大小的数组时,建议使用 std::array 替代 C 风格数组。
3 声明与初始化
3.1 声明
声明一个 std::array 对象时,需要指定数组中的元素类型和数组的大小。声明的基本语法如下:
std::array<元素类型, 数组大小> 数组名;
其中,元素类型是数组中存储的数据类型,数组大小是一个常量表达式,指定了数组包含的元素个数。数组名这个 std::array 对象的名字。
例如,声明一个包含 5 个整数的 std::array 可以这样写:
std::array<int, 5> myArray;
3.2 初始化
初始化 std::array 有多种方式,下面列举几种常见的初始化方法:
(1)默认值初始化
如果不提供任何初始值,std::array 中的元素将使用其类型的默认值进行初始化。
std::array<int, 5> myArray; // 所有元素将被初始化为随机值
(2)列表初始化
可以使用花括号 {} 内的元素列表来初始化 std::array。元素的数量必须与数组大小相匹配。
std::array<int, 5> myArray1 = {1, 2, 3, 4, 5};
std::array<int, 5> myArray2{1, 2, 3, 4, 5};
(3)使用 std::fill
虽然这不是初始化的方式,但可以使用 std::fill 函数来设置 std::array 中所有元素的值。
std::array<int, 5> myArray;
std::fill(myArray.begin(), myArray.end(), 0); // 所有元素设置为0
(4)注意事项
std::array 的大小在编译时就必须是已知的,因此不能使用变量来指定其大小。
初始化列表中的元素数量必须与 std::array 声明时指定的大小一致,否则会导致编译错误。
初始化时提供的值必须与 std::array 的元素类型兼容。
4 成员函数和基本操作
4.1 size()
功能:返回 std::array 的大小(即元素数量)。
std::array<int, 5> arr;
std::cout << "Size of array: " << arr.size() << std::endl; // 输出: Size of array: 5
4.2 at(size_t idx)
功能:通过索引访问数组中的元素,并进行边界检查。如果索引超出范围,将抛出 std::out_of_range 异常。
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::cout << "Element at index 2: " << arr.at(2) << std::endl; // 输出: Element at index 2: 3
4.3 front() 和 back()
功能:分别返回数组中第一个和最后一个元素的引用。
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::cout << "First element: " << arr.front() << std::endl; // 输出: First element: 1
std::cout << "Last element: " << arr.back() << std::endl; // 输出: Last element: 5
4.4 begin() 和 end()
功能:返回指向数组中第一个元素和“尾后元素”的迭代器。尾后元素并不指向一个有效的元素,而是用作一个哨兵值,表示数组的结束。
std::array<int, 5> arr = {1, 2, 3, 4, 5};
for (auto it = arr.begin(); it != arr.end(); ++it) {
std::cout << *it << " "; // 输出: 1 2 3 4 5
}
4.5 fill(const T& value)
功能:用给定的值填充整个数组。
std::array<int, 5> arr;
arr.fill(0); // 所有元素都设置为0
4.6 operator[]
功能:通过索引访问数组中的元素,不进行边界检查。如果索引超出范围,将不会抛出异常,但可能导致未定义行为。
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::cout << "Element at index 2: " << arr[2] << std::endl; // 输出: Element at index 2: 3
4.7 swap
功能:使用 std::swap 函数或 swap 成员函数来交换两个 std::array 的内容。
std::array<int, 5> arr1 = { 1, 2, 3, 4, 5 };
std::array<int, 5> arr2 = { 6, 7, 8, 9, 10 };
arr1.swap(arr2); // 现在 arr1 包含 {6, 7, 8, 9, 10},arr2 包含 {1, 2, 3, 4, 5}
5 比较和运算符
5.1 相等运算符 == 和 不等运算符 !=
== 运算符会检查两个 std::array 对象的所有元素是否都相等。只有当两个 std::array 对象具有相同大小和所有对应位置的元素都相等时,它们才被认为是相等的。
!= 运算符则正好相反,只要两个 std::array 对象在大小或元素上有任何不同,它们就被认为是不等的。
示例:
#include <iostream>
#include <array>
int main()
{
std::array<int, 3> arr1 = {1, 2, 3};
std::array<int, 3> arr2 = {1, 2, 3};
std::array<int, 3> arr3 = {1, 2, 4};
if (arr1 == arr2) {
std::cout << "arr1 is equal to arr2" << std::endl; // 输出这个信息
} else {
std::cout << "arr1 is not equal to arr2" << std::endl;
}
if (arr1 != arr3) {
std::cout << "arr1 is not equal to arr3" << std::endl; // 输出这个信息
} else {
std::cout << "arr1 is equal to arr3" << std::endl;
}
return 0;
}
上面代码的输出为:
arr1 is equal to arr2
arr1 is not equal to arr3
5.2 关系运算符 <、<=、> 和 >=
对于关系运算符,std::array 使用字典序(lexicographical order)来比较两个数组。这意味着比较从第一个元素开始,如果第一个元素相等,则比较第二个元素,以此类推,直到找到不相等的元素或遍历完所有元素。
<:如果第一个不匹配的元素在第一个 std::array 中较小,则第一个 std::array 小于第二个。
<=:如果第一个 std::array 小于或等于第二个(即 < 或 ==),则满足条件。
>:如果第一个不匹配的元素在第一个 std::array 中较大,则第一个 std::array 大于第二个。
>=:如果第一个 std::array 大于或等于第二个(即 > 或 ==),则满足条件。
示例:
#include <iostream>
#include <array>
int main()
{
std::array<int, 3> arr1 = {1, 2, 3};
std::array<int, 3> arr2 = {1, 2, 4};
std::array<int, 3> arr3 = {1, 3, 2};
if (arr1 < arr2) {
std::cout << "arr1 is less than arr2" << std::endl; // 输出这个信息
}
if (arr2 > arr1) {
std::cout << "arr2 is greater than arr1" << std::endl; // 输出这个信息
}
if (arr1 <= arr2) {
std::cout << "arr1 is less than or equal to arr2" << std::endl; // 输出这个信息
}
if (arr3 >= arr1) {
std::cout << "arr3 is greater than or equal to arr1" << std::endl; // 输出这个信息
}
return 0;
}
上面代码的输出为:
arr1 is less than arr2
arr2 is greater than arr1
arr1 is less than or equal to arr2
arr3 is greater than or equal to arr1
在使用这些比较运算符时,需要确保两个 std::array 对象的大小相同,因为不同大小的 std::array 对象是不能直接进行比较的。如果尝试比较不同大小的 std::array 对象,编译器会报错。
注意:这些比较运算符的性能通常是线性的,因为它们需要遍历整个数组来执行比较。对于非常大的数组,这可能会成为性能瓶颈,因此在实际应用中需要根据具体情况进行权衡。在某些情况下,可能需要实现自定义的比较逻辑或使用其他数据结构来优化性能。
6 与标准库算法的结合使用
std::array 与标准库算法的结合使用是一种非常强大和灵活的方式,能够处理数组中的元素并执行各种操作。标准库算法,如 std::find、std::sort、std::for_each 等,都可以直接应用于 std::array,因为 std::array 提供了类似于指针的迭代器接口。
6.1 使用 std::find 查找元素
#include <iostream>
#include <array>
#include <algorithm>
int main()
{
std::array<int, 5> arr = {1, 2, 3, 4, 5};
auto it = std::find(arr.begin(), arr.end(), 3); // 查找元素3
if (it != arr.end()) {
std::cout << "Found element: " << *it << std::endl; // 输出找到的元素
} else {
std::cout << "Element not found" << std::endl;
}
return 0;
}
上面代码的输出为:
Found element: 3
6.2 使用 std::sort 对数组进行排序
#include <iostream>
#include <array>
#include <algorithm>
int main()
{
std::array<int, 5> arr = {5, 3, 1, 4, 2};
std::sort(arr.begin(), arr.end()); // 对数组进行排序
for (const auto& elem : arr) {
std::cout << elem << " "; // 输出排序后的数组: 1 2 3 4 5
}
std::cout << std::endl;
return 0;
}
上面代码的输出为:
1 2 3 4 5
6.3 使用 std::for_each 遍历并处理数组中的每个元素
#include <iostream>
#include <array>
#include <algorithm>
void print_element(int elem) {
std::cout << elem << " ";
}
int main()
{
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::for_each(arr.begin(), arr.end(), print_element); // 遍历并打印每个元素
std::cout << std::endl; // 输出: 1 2 3 4 5
return 0;
}
上面代码的输出为:
1 2 3 4 5
6.4 使用 std::transform 对数组中的元素进行转换
#include <iostream>
#include <array>
#include <algorithm>
#include <iterator>
#include <vector>
int main()
{
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::vector<int> result(arr.size()); // 创建一个与arr大小相同的vector来存储结果
// 将arr中的每个元素乘以2,并将结果存储在result中
std::transform(arr.begin(), arr.end(), result.begin(), [](int x) { return x * 2; });
// 打印结果
for (const auto& elem : result) {
std::cout << elem << " "; // 输出: 2 4 6 8 10
}
std::cout << std::endl;
return 0;
}
上面代码的输出为:
2 4 6 8 10
6.5 使用 std::partition 对数组进行分区
#include <iostream>
#include <array>
#include <algorithm>
bool is_even(int n) {
return n % 2 == 0;
}
int main()
{
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::partition(arr.begin(), arr.end(), is_even); // 将偶数放在数组的前面,奇数放在后面
// 打印分区后的数组
for (const auto& elem : arr) {
std::cout << elem << " "; // 输出可能是: 4 2 3 1 5(顺序可能因实现而异)
}
std::cout << std::endl;
return 0;
}
上面代码的输出为:
4 2 3 1 5
6.6 总结
这些例子只是冰山一角,标准库提供了大量的算法,可以与 std::array 一起使用,以执行各种复杂的操作。通过使用这些算法,你可以避免编写冗余的循环代码,并利用算法提供的优化和通用性。同时,由于 std::array 提供了与原生数组类似的性能特性,因此这些操作通常也是非常高效的。