C++ 标准库 valarray
下面对 <valarray>
头文件中提供的数值数组类及相关操作做一次系统、深入的梳理,包括类型定义、元素级运算、切片与掩码视图、成员函数、非成员函数,以及使用示例与实践建议。
一、概述
<valarray>
定义了一个专门用于数值计算的数组模板类std::valarray<T>
,以及一系列针对该类型的高效元素级(element‑wise)运算工具。- 与通用容器(如
std::vector
)不同,valarray
旨在支持对整组元素进行向量化操作(包括加减乘除、数学函数、切片、掩码、滚动等),在实现上便于编译器或底层库做 SIMD/多线程优化。
二、核心类型与表达式模板
1. std::valarray<T>
- 定义:
template<typename T> class valarray { public: // 构造 valarray(); // 空数组 explicit valarray(std::size_t n); // 大小为 n,元素值为 T() valarray(std::size_t n, const T& v); // 大小为 n,所有元素初始化为 v valarray(const T* ptr, std::size_t n); // 从原始数组拷贝 n 个元素 valarray(std::initializer_list<T>); // 列表初始化 // 元素访问 T& operator[](std::size_t i) noexcept; const T& operator[](std::size_t i) const noexcept; // 大小与维度 std::size_t size() const noexcept; void resize(std::size_t n); // 视图(见下节) // …… };
- 特点:
- 支持按标量或另一
valarray
对象做元素级算术运算,返回一个延迟求值的表达式模板,让多步运算合并执行。 - 内置成员函数如
sum()
,min()
,max()
,apply()
,shift()/cshift()
,sort()
等,可对整个数组快速操作。
- 支持按标量或另一
2. 表达式模板(Expression Template)
当对 valarray
做加、减、乘、除等运算时,并不立即执行,而是返回一个临时的表达式对象。这样,诸如
std::valarray<double> a, b, c;
// …
auto d = (a + b) * c - 1.0;
可在一次遍历中完成 d[i] = (a[i] + b[i]) * c[i] - 1.0
,减少中间临时数组、提高性能。
三、视图:切片(Slice)与掩码(Mask)
1. std::slice
与 slice_array<T>
std::slice(start, size, stride)
:描述从下标start
开始,每次跳过stride
,取size
个元素的切片。valarray<T>::operator[](slice s)
返回slice_array<T>
,是对原数组的引用视图,可读写。
#include <valarray>
#include <iostream>
int main() {
std::valarray<int> v{0,1,2,3,4,5,6,7,8};
// 取从下标 1 开始,每隔 2 取一个,共 4 个元素
auto sv = v[std::slice(1,4,2)]; // 对应原 v[1],v[3],v[5],v[7]
sv = std::valarray<int>{10,11,12,13}; // 写回到 v
for (auto x : v) std::cout << x << ' '; // 0 10 2 11 4 12 6 13 8
}
2. std::gslice
与 gslice_array<T>
- 多维切片:用一个
std::gslice
(起始下标 + 各维大小数组 + 各维步长数组)描述多维数组在一维valarray
中的映射。
3. std::mask_array<T>
- 掩码切片:先用
std::valarray<bool>
构造掩码数组,再对原valarray
做operator[](mask)
,返回mask_array<T>
视图。
std::valarray<int> v{0,1,2,3,4,5};
std::valarray<bool> m = (v % 2 == 0); // 偶数元素掩码
auto mv = v[m]; // 包含 v[0],v[2],v[4]
mv = 100; // 将对应位置写成 100
4. std::indirect_array<T>
- 间接索引视图:以
std::valarray<std::size_t>
作为下标数组,通过operator[](std::valarray<size_t>)
获取指定位置的元素视图。
四、成员函数
函数 | 功能 | 复杂度 |
---|---|---|
sum() | 返回所有元素之和 | O(N) |
min() / max() | 返回最小/最大元素 | O(N) |
apply(Func) | 对每个元素调用无参数可调用对象 Func(T)->T | O(N) |
shift(int n) | 算术右移/左移(移出填零) | O(N) |
cshift(int n) | 循环右移/左移 | O(N) |
sort() | 就地升序排序 | O(N log N) |
resize(std::size_t n) | 改变大小(新元素初始化为 T()) | O(max(old,n)) |
operator[](slice/gslice/… | 切片与视图 | O(1) 构造,后续访问按视图规则 |
五、非成员函数与运算符
1. 元素级算术运算
对任意 valarray<T>
与标量或另一 valarray<T>
,支持下列运算,均返回表达式模板:
operator+,-,*,/,%,^,&,|,<<,>>
operator+=,-=,*=,/=
常见示例:
std::valarray<double> a, b;
// 计算 a[i] = 3*a[i] + b[i]/2
auto c = a * 3.0 + b / 2.0;
2. 数学函数
以模板方式对 valarray
元素调用 C 数学库函数,返回新的 valarray
:
std::sin(a), std::exp(a), std::log(a), std::sqrt(a), std::abs(a) // 逐元素调用
3. 其他算法
std::pow(a, b)
:元素 i 做pow(a[i], b[i])
或pow(a[i], scalar)
std::cshift(a,n)
、std::shift(a,n)
:与成员同名版本等价std::sort(a)
:非成员版排序std::swap(a,b)
:交换两个 valarray 的所有元素
六、综合示例
#include <valarray>
#include <iostream>
#include <cmath>
int main() {
// 构造 0–9 的序列
std::valarray<double> v(10);
for (size_t i = 0; i < 10; ++i) v[i] = i;
// 计算 sin(π * i / 9)
auto angles = v * M_PI / 9.0;
auto sines = std::sin(angles);
// 对偶数位置做循环左移 1,将奇数位置设为 0
auto even_mask = (v % 2 == 0);
auto evens = v[std::slice(0,5,2)]; // 下标 0,2,4,6,8
evens = std::cshift(evens, 1);
auto odds = v[std::slice(1,5,2)];
odds = 0;
// 结果合并后排序
std::sort(v);
// 输出
for (auto x : v) std::cout << x << ' ';
std::cout << '\n';
// 求和与最值
std::cout << "sum=" << v.sum()
<< ", min=" << v.min()
<< ", max=" << v.max() << "\n";
}
七、实践建议
- 数值计算优先选
valarray
- 当需要对整组数据做批量算术或数学函数时,
valarray
语义清晰,易于向量化。
- 当需要对整组数据做批量算术或数学函数时,
- 注意表达式延迟求值
- 多重算术合并在一次遍历中完成;若要显式将结果存储到已有
valarray
,可用赋值或std::valarray<T> tmp = expr;
。
- 多重算术合并在一次遍历中完成;若要显式将结果存储到已有
- 切片与掩码慎用递归视图
- 连续多次对视图再做视图操作可能导致性能下降,必要时可先
std::valarray<T> sub = view;
拷贝到新数组。
- 连续多次对视图再做视图操作可能导致性能下降,必要时可先
- 尽量用成员函数
- 比如用
v.cshift(n)
、v.apply(f)
、v.sum()
而非手动循环,既简洁又高效。
- 比如用
- 与并行算法权衡
- 标准库并未在
<valarray>
明确支持并行策略。如需并行,可手动分块并行或考虑使用第三方库(如 Intel TBB、OpenMP)。
- 标准库并未在
通过以上对 <valarray>
类型、视图、运算和算法的全面梳理与示例演示,相信能帮助你在科学计算、信号处理、数值模拟等场景中高效地利用 C++ 标准库的向量化功能。祝编码顺利!