本章涵盖更多数值处理类与算法,主要内容包括:
- 使用 std::valarray
- 使用 std::slice
- 内积
- 归约运算
与前一章类似,本章部分讨论假定读者已具备某些数学学科的基础知识。您可以根据编程兴趣选择略读或跳过无关章节。
类 std::valarray
模板类 std::valarray 是专为数值运算设计的一维类数组结构。与 std::array 和 std::vector 等类似容器不同,std::valarray 类排除了某些形式的别名使用,这使得编译器能够进行更激进的优化。通过结合 std::valarray 类和 std::slice 类,可以构建多维数组模型。后续章节将对此进行更详细的讲解。
算术运算函数
代码清单 19-1-1 展示了示例函数 Ch19_01_ex1() 的源代码。该示例演示了使用 std::valarray 类实例进行基础运算的几种方法。
//-------------------------------------------------------------------------
// Ch19_01_ex.cpp
//-------------------------------------------------------------------------
#include <algorithm>
#include <numeric>
#include <valarray>
#include "Ch19_01.h"
#include "MT.h"
#include "RN.h"
void Ch19_01_ex1()
{
const char* fmt1 = "{:7d}";
const char* fmt2 = "{:7.1f}";
constexpr size_t epl_max {10};
constexpr size_t n {25};
constexpr int rng_min {1};
constexpr int rng_max {500};
constexpr unsigned int rng_seed {19011};
// using std::valarray<int>
std::valarray va1 = RN::get_valarray<int>(n, rng_min, rng_max, rng_seed);
MT::print_ctr("\nva1:\n", va1, fmt1, epl_max);
std::println("\nva1 - sum: {:d}, min: {:d}, max: {:d}",
va1.sum(), va1.min(), va1.max());
// using std::valarray<> operator+=
va1 += 5;
MT::print_ctr("\nva1 (after operator+=):\n", va1, fmt1, epl_max);
// using std::valarray<double>
std::valarray va2 = RN::get_valarray<double>(n, rng_min, rng_max, rng_seed + 1);
MT::print_ctr("\nva2:\n", va2, fmt2, epl_max);
std::println("\nva2 - sum: {:.1f}, min: {:.1f}, max: {:.1f}",
va2.sum(), va2.min(), va2.max());
// using std::valarray<> operator-=
va2 -= 0.5;
MT::print_ctr("\nva2 (after operator-=):\n", va2, fmt2, epl_max);
// using operator[]
for (size_t i = 0; i < n; ++i)
va2[i] = static_cast<double>(va1[i] % 3);
MT::print_ctr("\nva2 (after operator[] calculations):\n",
va2, fmt2, epl_max);
}
在 Ch19_01_ex1() 开头的代码块中,执行 std::valarray va1 = RN::get_valarray<int>(n, rng_min, rng_max, rng_seed) 会初始化 va1,使其包含 n 个介于[rng_min, rng_max]之间的随机整数。函数 RN::get_valarray() 是 std::valarray 版本的 RN::get_vector()(参见代码清单 18-5-1-1 和文件 Common/RN.h)。执行 va1.sum() 会计算 va1 中所有元素的总和,而 va1.min() 和 v1.max() 则分别计算最小值和最大值。在接下来的代码块中,执行 va1 += 5 会给 va1 中的每个元素加 5。
清单 19-1-1 中的下一个 std::valarray 示例使用 std::valarray va2 = RN::get_valarray<double>(n, rng_min, rng_max, rng_seed) 来初始化 va2 的随机值 。与之前示例类似,执行 va2.sum() 将对 va2 的元素求和,而 va2.min() 和 va2.max() 则分别确定 va2 的最小值与最大值。语句 va2 -= 0.5 会从 va2 的每个元素中减去 0.5。其他常见算术运算符同样可用于调整 std::valarray 中的元素。
Ch19_01_ex1() 中的最后一个代码块展示了如何使用 operator[] 访问 std::valarray 中的单个元素。与 std::array 和 std::vector 类似,std::valarray 的 operator[] 不会检查无效索引;若使用无效索引,其执行行为是未定义的。std::valarray 类也没有定义边界检查成员函数 at()。
void Ch19_01_ex2()
{
const char* fmt = "{:7d}";
constexpr size_t epl_max {10};
// create test valarray<> objects
std::valarray<int> va1 {10, 20, 30, 40, 50};
std::valarray<int> va2 {100, 200, 300, 400, 500, 600, 700, 800};
std::valarray<long long> va3 {1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000};
MT::print_ctr("\nva1:\n", va1, fmt, epl_max);
MT::print_ctr("\nva2:\n", va2, fmt, epl_max);
MT::print_ctr("\nva3:\n", va3, fmt, epl_max);
// using valarray<>::operator=
va2 = va1;
MT::print_ctr("\nva2 (after operator=):\n", va1, fmt, epl_max);
// va3 = va1; // illegal - different element types
// using valarray<>::apply
auto apply_op = [](int x) { return x * x - 1; };
std::valarray<int> va4 = va1.apply(apply_op);
MT::print_ctr("\nva4:\n", va4, fmt, epl_max);
}
在调用 print_ctr() 之后,执行 va2 = va1 将 va1 赋值给 va2。类 std::valarray 的赋值运算符仅支持使用持有相同元素类型的对象进行赋值。语句 va3 = va1 被注释掉,因为它是无效的;对象 va3 持有 long long 类型,而 va1 包含 int 类型。Ch19_01_ex2() 的最后代码块演示了 std::valarray::apply() 的使用。执行 std::valarray<int> va4 = va1.apply(apply_op) 会返回一个新的 std::valarray,其值对应于 va4[i] = apply_op(va1[i])。
在代码清单 19-1-3 中,示例函数 Ch19_01_ex3() 使用了 std::iota(std::begin(va1), std::end(va1), fp_t {1}) 来初始化 std::valarray<double> va1(n)。与大多数其他 STL 容器不同,类 std::valarray 没有定义迭代器成员函数 begin() 和 end(),必须使用全局函数 std::begin() 和 std::end() 来获取 std::valarray 实例的迭代器。
void Ch19_01_ex3()
{
const char* fmt = "{:12.4f}";
constexpr size_t epl_max {5};
// create test valarray<>
// must use std::begin() and std::end() for std::valarray iterators
constexpr size_t n {20};
std::valarray<double> va1(n);
std::iota(std::begin(va1), std::end(va1), 1.0);
MT::print_ctr("\nva1:\n", va1, fmt, epl_max);
// using std::valarray<> math overloads
std::valarray<double> va2 = std::sqrt(va1);
MT::print_ctr("\nva2 (after sqrt):\n", va2, fmt, epl_max);
va2 = std::pow(va2, 3.0);
MT::print_ctr("\nva2 (after pow):\n", va2, fmt, epl_max);
va2 = std::log10(va1);
MT::print_ctr("\nva2 (after log10):\n", va2, fmt, epl_max);
}
Ch19_01_ex3() 中的剩余代码展示了 std::valarray 非成员函数 std::sqrt()、std::pow() 和 std::log10() 的用法。这些函数都会对指定 std::valarray 中的每个元素执行数学运算。STL 还为常见的三角函数和双曲函数运算定义了 std::valarray 重载。
在代码清单 19-1-4 中,示例函数 Ch19_01_ex4() 演示了 operator<、operator== 和 operator> 的使用。这些运算符返回类型为 std::valarray<bool> 的对象。例如,执行 std::valarray<bool> va_lt = va1 < va2 会返回一个 std::valarray<bool>,其第 i 个元素等于 va1[i] < va2[i]。使用 std::valarray 进行比较时,两个数组必须包含相同数量的元素。
void Ch19_01_ex4()
{
const char* fmt = "{:7.1f}";
constexpr size_t epl_max {11};
constexpr size_t n {10};
// create test valarray<> objects
std::valarray<float> va1(n);
std::iota(std::begin(va1), std::end(va1), 1.0f);
std::valarray<float> va2(va1);
va2[0] += 1.0f;
va2[n / 4] -= 2.0f;
va2[n / 2] *= 3.0f;
va2[n - 1] /= 4.0f;
MT::print_ctr("\nva1:\n", va1, fmt, epl_max);
MT::print_ctr("\nva2:\n", va2, fmt, epl_max);
// using operator< (returns std::valarray<bool>)
std::valarray<bool> va_lt = va1 < va2;
MT::print_ctr("\nva_lt:\n", va_lt, "{:>7s}", epl_max);
// using operator== (returns std::valarray<bool>)
std::valarray<bool> va_eq = va1 == va2;
MT::print_ctr("\nva_cmp:\n", va_eq, "{:>7s}", epl_max);
// using operator> (returns std::valarray<bool>)
std::valarray<bool> va_gt = va1 > va2;
MT::print_ctr("\nva_gt:\n", va_gt, "{:>7s}", epl_max);
}
以下是示例 Ch19_01 的运行结果:
----- Results for example Ch19_01 -----
----- Ch19_01_ex1() -----
va1:
16 454 341 466 394 276 327 115 203 373
450 260 219 491 18 487 187 84 166 64
22 131 222 210 129
va1 - sum: 6105, min: 16, max: 491
va1 (after operator+=):
21 459 346 471 399 281 332 120 208 378
455 265 224 496 23 492 192 89 171 69
27 136 227 215 134
va2:
70.0 238.0 206.0 77.0 330.0 210.0 473.0 453.0 210.0 135.0
82.0 222.0 211.0 217.0 86.0 72.0 330.0 425.0 241.0 408.0
124.0 60.0 461.0 335.0 259.0
va2 - sum: 5935.0, min: 60.0, max: 473.0
va2 (after operator-=):
69.5 237.5 205.5 76.5 329.5 209.5 472.5 452.5 209.5 134.5
81.5 221.5 210.5 216.5 85.5 71.5 329.5 424.5 240.5 407.5
123.5 59.5 460.5 334.5 258.5
va2 (after operator[] calculations):
0.0 0.0 1.0 0.0 0.0 2.0 2.0 0.0 1.0 0.0
2.0 1.0 2.0 1.0 2.0 0.0 0.0 2.0 0.0 0.0
0.0 1.0 2.0 2.0 2.0
----- Ch19_01_ex2() -----
va1:
10 20 30 40 50
va2:
100 200 300 400 500 600 700 800
va3:
1000 2000 3000 4000 5000 6000 7000 8000
va2 (after operator=):
10 20 30 40 50
va4:
99 399 899 1599 2499
----- Ch19_01_ex3() -----
va1:
1.0000 2.0000 3.0000 4.0000 5.0000
6.0000 7.0000 8.0000 9.0000 10.0000
11.0000 12.0000 13.0000 14.