C++并发编程与数值函数实用指南
立即解锁
发布时间: 2025-08-22 00:43:55 阅读量: 3 订阅数: 16 


深入解析C++标准库:从入门到精通
### C++ 并发编程与数值函数实用指南
#### 1. 全局数值函数
在 C++ 里,`<cmath>` 和 `<cstdlib>` 头文件提供了从 C 语言继承而来的全局数值函数。这些函数能满足各种数值计算需求,下面是它们的详细介绍。
##### 1.1 `<cmath>` 头文件中的函数
| 函数 | 作用 |
| --- | --- |
| `pow()` | 幂函数 |
| `exp()` | 指数函数 |
| `sqrt()` | 平方根函数 |
| `log()` | 自然对数函数 |
| `log10()` | 以 10 为底的对数函数 |
| `sin()` | 正弦函数 |
| `cos()` | 余弦函数 |
| `tan()` | 正切函数 |
| `sinh()` | 双曲正弦函数 |
| `cosh()` | 双曲余弦函数 |
| `tanh()` | 双曲正切函数 |
| `asin()` | 反正弦函数 |
| `acos()` | 反余弦函数 |
| `atan()` | 反正切函数 |
| `atan2()` | 商的反正切函数 |
| `asinh()` | 反双曲正弦函数(C++11 起) |
| `acosh()` | 反双曲余弦函数(C++11 起) |
| `atanh()` | 反双曲正切函数(C++11 起) |
| `ceil()` | 将浮点值向上舍入为下一个整数值 |
| `floor()` | 将浮点值向下舍入为下一个整数值 |
| `fabs()` | 浮点值的绝对值 |
| `fmod()` | 浮点值除法的余数(取模) |
| `frexp()` | 将浮点值转换为小数和整数部分 |
| `ldexp()` | 将浮点值乘以 2 的整数次幂 |
| `modf()` | 从浮点值中提取有符号整数和小数部分 |
与 C 语言不同,C++ 对部分操作进行了不同类型的重载,这使得 C 语言里的一些数值函数变得多余。例如,在 C 语言中,处理不同类型的绝对值需要使用 `abs()`、`labs()`、`llabs()`、`fabs()`、`fabsf()` 和 `fabsl()` 等函数,而在 C++ 里,`abs()` 函数被重载,可用于所有这些数据类型。
##### 1.2 `<cstdlib>` 头文件中的数值函数
| 函数 | 作用 |
| --- | --- |
| `abs()` | 整数的绝对值 |
| `labs()` | 长整数的绝对值 |
| `llabs()` | 长长整数的绝对值(C++11 起) |
| `div()` | 整数除法的商和余数 |
| `ldiv()` | 长整数除法的商和余数 |
| `lldiv()` | 长长整数除法的商和余数(C++11 起) |
| `srand()` | 随机值生成器(设置新序列的种子) |
| `rand()` | 随机值生成器(生成序列中的下一个数) |
所有用于整数的数值函数都针对 `int`、`long` 和 `long long` 类型进行了重载,而所有用于浮点值的数值函数则针对 `float`、`double` 和 `long double` 类型进行了重载。不过,这会带来一个问题:当你传递一个整数值,而只有多个浮点重载存在时,表达式会产生歧义。例如:
```cpp
std::sqrt(7) // 有歧义: sqrt(float), sqrt(double), 还是 sqrt(long double)?
```
为避免这种情况,你需要这样写:
```cpp
std::sqrt(7.0) // 正确
```
或者,如果你使用变量,必须写成:
```cpp
int x;
// ...
std::sqrt(float(x)) // 正确
```
不同的库供应商对这个问题的处理方式各不相同。为编写可移植的代码,你应该始终确保代码中的参数精确匹配。
#### 2. Valarrays
自 C++98 起,C++ 标准库就提供了 `valarray` 类,用于处理数值数组。
##### 2.1 Valarrays 的用途
`valarray` 是数学概念中值的线性序列的一种表示。它是一维的,但通过特殊的计算索引技术和强大的子集选取功能,你可以获得更高维度的错觉。因此,`valarray` 可作为向量和矩阵运算的基础,也可用于处理多项式方程组的数学系统,且性能良好。
从技术上讲,`valarray` 是一维数组,元素从 0 开始顺序编号。它能对一个或多个值数组中的所有或部分值进行数值处理。例如,你可以处理如下语句:
```cpp
z = a*x*x + b*x + c
```
其中 `a`、`b`、`c`、`x` 和 `z` 是包含数百个数值的数组。这样做的好处是表示简单。而且,处理性能也不错,因为这些类提供了一些优化,避免在处理整个语句时创建临时对象。这是因为 `valarray` 保证无别名,即非常量 `valarray` 的任何值都通过唯一路径访问,编译器在优化操作时无需考虑数据可能通过其他路径访问的情况。
此外,特殊的接口和辅助类提供了处理值数组特定子集或进行多维处理的能力,有助于实现向量和矩阵运算及相关类。
##### 2.2 Valarrays 的问题
`valarray` 类的设计并不理想。实际上,没人去验证最终规范是否可行,这是因为没人对这些类负责。将 `valarray` 引入 C++ 标准库的人在标准完成前很久就离开了委员会,导致 `valarray` 很少被使用。
#### 3. 并发编程基础
现代系统架构通常支持同时运行多个任务和线程。特别是在有多个处理器核心的情况下,使用多线程能显著提高程序的执行时间。然而,并行执行也会带来新的挑战,比如多个线程同时访问相同资源,可能导致创建、读取、写入和删除操作无法按预期顺序进行,从而产生意外结果。在 C++11 之前,语言和标准库都不支持并发编程,不过实现可以自行提供一些保证。C++11 改变了这一状况,核心语言和库都得到改进以支持并发编程。
核心语言定义了一个内存模型,确保两个不同线程使用的两个不同对象的更新相互独立,并引入了新关键字 `thread_local` 来定义具有线程特定值的变量。标准库提供了启动多个线程的支持,包括跨线程边界传递参数、返回值和异常,以及同步多个线程的方法,从而实现控制流和数据访问的同步。
#### 4. 高级接口:`async()` 和 `Futures`
对于初学者来说,使用 C++ 标准库的高级接口 `std::async()` 和 `std::future<>` 是开启多线程编程的最佳起点。
##### 4.1 `async()` 和 `Futures` 的第一个示例
假设要计算两个函数调用返回的操作数之和,通常的编程方式是:
```cpp
func1() + func2()
```
这种方式下,操作数的处理是顺序进行的,程序会先调用 `func1()`,再调用 `func2()`,或者反之,整体处理时间是 `func1()` 的执行时间加上 `func2()` 的执行时间再加上计算和的时间。
现在,借助几乎随处可见的多处理器硬件,我们可以尝试并行运行 `func1()` 和 `func2()`,这样整体时间只需 `func1()` 和 `func2()` 执行时间的最大值加上计算和的时间。以下是实现该功能的示例代码:
```cpp
// concurrency/async1.cpp
#include <future>
#include <thread>
#include <chrono>
#include <random>
#include <iostream>
#include <exception>
using namespace std;
int doSomething (char c)
{
// 随机数生成器 (使用 c 作为种子以获得不同序列)
std::default_random_engine dre(c);
std::uniform_int_distribution<int> id(10,1000);
// 循环在随机时间段后打印字符
for (int i=0; i<10; ++i) {
this_thread::sleep_for(chrono::milliseconds(id(dre)));
cout.put(c).flush();
}
return c;
}
int func1 ()
{
return doSomething('.');
}
int func2 ()
{
return doSomething('+');
}
int main()
{
std::cout << "starting func1() in background"
<< " and func2() in foreground:" << std::endl;
// 异步启动 func1() (现在、稍后或不启动):
std::future<int> result1(std::async(func1));
int result2 = func2(); // 同步调用 func2()
// 打印结果 (等待 func1() 完成并将其结果与 result2 相加)
int result = result1.get() + result2;
std::cout << "\nresult of func1()+func2(): " << result
<< std::endl;
}
```
这里,我们通过调用 `doSomething()` 函数模拟 `func1()` 和 `func2()` 中的复杂处理,该函数会不时打印传入的字符,并最终返回该字符的整数值。“不时” 是通过随机数生成器指定间隔实现的,`std::this_thread::sleep_for()` 会使用这些间隔作为当前线程的超时时间。
我们没有直接调用 `int result = func1() + func2();`,而是按如下方式调用:
```cpp
std::future<int> result1(std::async(func1));
int result2 = func2();
int result = result1.get() + result2;
```
首先,使用 `std::async()` 尝试在后台启动 `func1()`,并将结果赋值给 `std::future` 对象。`async()` 会尝试在单独的线程中异步立即启动传入的功能,理想情况下,`func1()` 会在此处启动,而不会阻塞 `main()` 函数。返回的 `future` 对象有两个重要作用:
1. 它允许访问传递给 `async()` 的功能的 “未来” 结果,这个结果可能是返回值或异常。`future` 对象根据启动的功能的返回类型进行了特化,如果启动的后台任务没有返回值,则应为 `std::future<void>`。
2. 它确保传入的功能迟早会被调用。如果 `async()` 未能立即启动该功能,当我们需要结果或确保功能已执行时,`future` 对象会强制启动该功能。
接着,在前台同步调用 `func2()`,如果 `func1()` 成功启动且尚未结束,此时 `func1()` 和 `func2()` 会并行运行。最后,调用 `result1.get()` 获取 `func1()` 的结果并与 `result2` 相加。调用 `get()` 时,可能会出现以下三种情况:
1. 如果 `func1()` 在单独的线程中启动且已完成,会立即得到其结果。
2. 如果 `func1()` 已启动但尚未完成,`get()` 会阻塞并等待其结束,然后返回结果。
3. 如果 `func1()` 尚未启动,它将被强制启动,并且 `get()` 会像同步函数调用一样阻塞,直到返回结果。
这种行为确保了程序在单线程环境或因其他原因 `async()` 无法启动新线程的情况下仍能正常工作。`async()` 调用并不保证传入的功能一定会启动并完成,如果有线程可用,它会启动;否则,调用会被推迟,直到我们显式请求其结果(调用 `get()`)或希望该功能完成(调用 `wait()`)。
因此,`std::future<int> result1(std::async(func1));` 和 `result1.get()` 的组合能优化程序,使 `func1()` 尽可能与主线程中的后续语句并行运行。如果无法并行运行,在调用 `get()` 时会顺序调用 `func1()`。
根据这个示例,我们可以总结出让程序更快的通用方法:
1. 包含 `<future>` 头文件。
2. 将可以独立并行运行的功能作为可调用对象传递给 `std::async()`。
3. 将结果赋值给 `future<ReturnType>` 对象。
4. 当需要结果或确保启动的功能已完成时,调用 `future<>` 对象的 `get()` 方法。
需要注意的是,这仅适用于没有数据竞争的情况,即两个线程同时使用相同数据会导致未定义行为。而且,不调用 `get()` 就无法保证 `func1()` 会被调用。另外,要确保在必要时才请求 `async()` 启动的功能的结果,否则可能会导致顺序处理。一般来说,目标是最大化调用 `async()` 和调用 `get()` 之间的时间间隔。
如果传递给 `async()` 的操作没有返回值,`async()` 会返回 `future<void>`,此时 `get()` 不返回任何值。最后,传递给 `async()` 的对象可以是任何类型的可调用对象,如函数、成员函数、函数对象或 lambda 表达式。
##### 4.2 使用启动策略
你可以通过显式传递启动策略来强制 `async()` 不推迟传入的功能。例如:
```cpp
// 强制 func1() 立即异步启动,否则抛出 std::system_error
std::future<long> result1 = std::async(std::launch::async, func1);
```
如果无法进行异步调用,程序会抛出 `std::system_error` 异常,错误代码为 `resource_un
0
0
复制全文
相关推荐










