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)->TO(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";
}

七、实践建议

  1. 数值计算优先选 valarray
    • 当需要对整组数据做批量算术或数学函数时,valarray 语义清晰,易于向量化。
  2. 注意表达式延迟求值
    • 多重算术合并在一次遍历中完成;若要显式将结果存储到已有 valarray,可用赋值或 std::valarray<T> tmp = expr;
  3. 切片与掩码慎用递归视图
    • 连续多次对视图再做视图操作可能导致性能下降,必要时可先 std::valarray<T> sub = view; 拷贝到新数组。
  4. 尽量用成员函数
    • 比如用 v.cshift(n)v.apply(f)v.sum() 而非手动循环,既简洁又高效。
  5. 与并行算法权衡
    • 标准库并未在 <valarray> 明确支持并行策略。如需并行,可手动分块并行或考虑使用第三方库(如 Intel TBB、OpenMP)。

通过以上对 <valarray> 类型、视图、运算和算法的全面梳理与示例演示,相信能帮助你在科学计算、信号处理、数值模拟等场景中高效地利用 C++ 标准库的向量化功能。祝编码顺利!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注