【C++】:C++17新特性

std::variant

类模板 std::variant 表示一个类型安全的 union。std::variant 的实例在任何给定时间都持有一个其替代类型的值(它也可以是无值的)

cpp 复制代码
std::variant<int, double> v{ 12 };
std::get<int>(v); // == 12
std::get<0>(v); // == 12
v = 12.0;
std::get<double>(v); // == 12.0
std::get<1>(v); // == 12.0

std::optional

类模板 std::optional 管理一个可选的包含值,即一个可能存在也可能不存在的值。optional 的常见用例是函数的返回值,该函数可能会失败

cpp 复制代码
std::optional<std::string> create(bool b) {
  if (b) {
    return "Godzilla";
  } else {
    return {};
  }
}

create(false).value_or("empty"); // == "empty"
create(true).value(); // == "Godzilla"
// 返回 optional 的工厂函数可以用作 while 和 if 的条件
if (auto str = create(true)) {
  // ...
}

std::any

一个类型安全的容器,用于存储任何类型的单个值

cpp 复制代码
std::any x {5};
x.has_value() // == true
std::any_cast<int>(x) // == 5
std::any_cast<int&>(x) = 10;
std::any_cast<int>(x) // == 10

std::string_view

对字符串的非拥有引用。它适用于在字符串上提供抽象(例如用于解析)

cpp 复制代码
// 普通字符串。
std::string_view cppstr {"foo"};
// 宽字符串。
std::wstring_view wcstr_v {L"baz"};
// 字符数组。
char array[3] = {'b', 'a', 'r'};
std::string_view array_v(array, std::size(array));
std::string str {"   trim me"};
std::string_view v {str};
v.remove_prefix(std::min(v.find_first_not_of(" "), v.size()));
str; //  == "   trim me"
v; // == "trim me"

std::invoke

调用一个 Callable 对象及其参数。可调用 对象的示例包括 std::function 或 lambda;可以像普通函数一样调用的对象

cpp 复制代码
template <typename Callable>
class Proxy {
  Callable c_;

public:
  Proxy(Callable c) : c_{ std::move(c) } {}

  template <typename... Args>
  decltype(auto) operator()(Args&&... args) {
    // ...
    return std::invoke(c_, std::forward<Args>(args)...);
  }
};

const auto add = [](int x, int y) { return x + y; };
Proxy p{ add };
p(1, 2); // == 3

std::apply

使用元组的参数调用一个 Callable 对象

cpp 复制代码
auto add = [](int x, int y) {
  return x + y;
};
std::apply(add, std::make_tuple(1, 2)); // == 3

std::filesystem

新的 std::filesystem 库提供了一种标准的方式来操作文件、目录和文件系统中的路径。

以下是一个大文件被复制到临时路径的示例,前提是存在足够的空间:

cpp 复制代码
const auto bigFilePath {"bigFileToCopy"};
if (std::filesystem::exists(bigFilePath)) {
  const auto bigFileSize {std::filesystem::file_size(bigFilePath)};
  std::filesystem::path tmpPath {"/tmp"};
  if (std::filesystem::space(tmpPath).available > bigFileSize) {
    std::filesystem::create_directory(tmpPath.append("example"));
    std::filesystem::copy_file(bigFilePath, tmpPath.append("newFile"));
  }
}

std::byte

新的 std::byte 类型提供了一种标准的方式来表示数据为字节。使用 std::byte 而不是 char 或 unsigned char 的好处是它不是字符类型,也不是算术类型;唯一可用的运算符重载是按位运算

cpp 复制代码
std::byte a {0};
std::byte b {0xFF};
int i = std::to_integer<int>(b); // 0xFF
std::byte c = a & b;
int j = std::to_integer<int>(c); // 0

注意,std::byte 只是一个枚举类型,而枚举的直接列表初始化成为可能,这得益于枚举的直接列表初始化

映射和集合的拼接

在没有昂贵的拷贝、移动或堆分配/释放开销的情况下,移动节点和合并容器。

从一个映射移动元素到另一个映射:

cpp 复制代码
std::map<int, string> src {{1, "one"}, {2, "two"}, {3, "buckle my shoe"}};
std::map<int, string> dst {{3, "three"}};
dst.insert(src.extract(src.find(1))); // 从 `src` 到 `dst` 廉价地移除并插入 { 1, "one" }
dst.insert(src.extract(2)); // 从 `src` 到 `dst` 廉价地移除并插入 { 2, "two" }
// dst == { { 1, "one" }, { 2, "two" }, { 3, "three" } };

插入整个集合:

cpp 复制代码
std::set<int> src {1, 3, 5};
std::set<int> dst {2, 4, 5};
dst.merge(src);
// src == { 5 }
// dst == { 1, 2, 3, 4, 5 }

插入超出容器生命周期的元素:

cpp 复制代码
auto elementFactory() {
  std::set<...> s;
  s.emplace(...);
  return s.extract(s.begin());
}
s2.insert(elementFactory());

更改映射元素的键:

cpp 复制代码
std::map<int, string> m {{1, "one"}, {2, "two"}, {3, "three"}};
auto e = m.extract(2);
e.key() = 4;
m.insert(std::move(e));
// m == { { 1, "one" }, { 3, "three" }, { 4, "two" } }

并行算法

许多 STL 算法(如 copy、find 和 sort)开始支持 并行执行策略:seq、par 和 par_unseq,分别表示"顺序"、"并行"和"并行无序"。

cpp 复制代码
std::vector<int> longVector;
// 使用并行执行策略查找元素
auto result1 = std::find(std::execution::par, std::begin(longVector), std::end(longVector), 2);
// 使用顺序执行策略排序元素
auto result2 = std::sort(std::execution::seq, std::begin(longVector), std::end(longVector));

std::sample

从给定序列中随机抽样 n 个元素(不放回),每个元素被选中的概率相等。

cpp 复制代码
const std::string ALLOWED_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
std::string guid;
// 从 ALLOWED_CHARS 中随机抽样 5 个字符。
std::sample(ALLOWED_CHARS.begin(), ALLOWED_CHARS.end(), std::back_inserter(guid),
  5, std::mt19937{ std::random_device{}() });

std::cout << guid; // 例如:G1fW2

std::clamp

将给定值限制在上下界之间。

cpp 复制代码
std::clamp(42, -1, 1); // == 1
std::clamp(-42, -1, 1); // == -1
std::clamp(0, -1, 1); // == 0

// `std::clamp` 也接受自定义比较器:
std::clamp(0, -1, 1, std::less<>{}); // == 0

std::reduce

对给定范围的元素进行折叠操作。它与 std::accumulate 概念上类似,但 std::reduce 会并行执行折叠操作。由于折叠是并行执行的,如果指定了二元运算符,则要求它是结合的和交换的。给定的二元运算符也不应修改范围内的任何元素或使任何迭代器失效。

默认的二元运算是 std::plus,初始值为 0

cpp 复制代码
const std::array<int, 3> a{ 1, 2, 3 };
std::reduce(std::cbegin(a), std::cend(a)); // == 6
// 使用自定义二元运算符:
std::reduce(std::cbegin(a), std::cend(a), 1, std::multiplies<>{}); // == 6

此外,还可以为归约器指定转换操作:

cpp 复制代码
std::transform_reduce(std::cbegin(a), std::cend(a), 0, std::plus<>{}, times_ten); // == 60

const std::array<int, 3> b{ 1, 2, 3 };
const auto product_times_ten = [](const auto a, const auto b) { return a * b * 10; };

std::transform_reduce(std::cbegin(a), std::cend(a), std::cbegin(b), 0, std::plus<>{}, product_times_ten); // == 140

前缀和算法

支持前缀和(包括包含扫描和排除扫描)以及转换。

cpp 复制代码
const std::array<int, 3> a{ 1, 2, 3 };

std::inclusive_scan(std::cbegin(a), std::cend(a),
    std::ostream_iterator<int>{ std::cout, " " }, std::plus<>{}); // 1 3 6

std::exclusive_scan(std::cbegin(a), std::cend(a),
    std::ostream_iterator<int>{ std::cout, " " }, 0, std::plus<>{}); // 0 1 3

const auto times_ten = [](const auto n) { return n * 10; };

std::transform_inclusive_scan(std::cbegin(a), std::cend(a),
    std::ostream_iterator<int>{ std::cout, " " }, std::plus<>{}, times_ten); // 10 30 60

std::transform_exclusive_scan(std::cbegin(a), std::cend(a),
    std::ostream_iterator<int>{ std::cout, " " }, 0, std::plus<>{}, times_ten); // 0 10 30

最大公约数和最小公倍数

最大公约数(GCD)和最小公倍数(LCM)

cpp 复制代码
const int p = 9;
const int q = 3;
std::gcd(p, q); // == 3
std::lcm(p, q); // == 9

std::not_fn

实用函数,返回给定函数结果的否定

cpp 复制代码
const std::ostream_iterator<int> ostream_it{ std::cout, " " };
const auto is_even = [](const auto n) { return n % 2 == 0; };
std::vector<int> v{ 0, 1, 2, 3, 4 };

// 打印所有偶数。
std::copy_if(std::cbegin(v), std::cend(v), ostream_it, is_even); // 0 2 4
// 打印所有奇数(非偶数)。
std::copy_if(std::cbegin(v), std::cend(v), ostream_it, std::not_fn(is_even)); // 1 3

字符串与数字的相互转换

将整数和浮点数转换为字符串,反之亦然。这些转换是不抛出异常的,不会进行分配,并且比 C 标准库中的等效函数更安全。

用户负责为 std::to_chars 分配足够的存储空间,否则函数将通过在其返回值中设置错误代码对象来失败。

这些函数允许您可选地传递基数(默认为 10 进制)或浮点输入的格式说明符。

std::to_chars 返回一个(非 const)字符指针,指向函数在给定缓冲区内写入的字符串的末尾,以及一个错误代码对象。

std::from_chars 返回一个 const 字符指针,成功时等于传递给函数的结束指针,以及一个错误代码对象。

这两个函数返回的错误代码对象在成功时都等于默认初始化的错误代码对象。

将数字 123 转换为 std::string:

cpp 复制代码
const int n = 123;

// 可以使用任何容器,字符串,数组等。
std::string str;
str.resize(3); // 为 `n` 的每个数字分配足够的存储空间

const auto [ ptr, ec ] = std::to_chars(str.data(), str.data() + str.size(), n);

if (ec == std::errc{}) { std::cout << str << std::endl; } // 123
else { /* 处理失败 */ }

从值为 "123" 的 std::string 转换为整数:

cpp 复制代码
const std::string str{ "123" };
int n;

const auto [ ptr, ec ] = std::from_chars(str.data(), str.data() + str.size(), n);

if (ec == std::errc{}) { std::cout << n << std::endl; } // 123
else { /* 处理失败 */ }

chrono 持续时间和时间点的舍入函数

为 std::chrono::duration 和 std::chrono::time_point 提供 abs、round、ceil 和 floor 辅助函数

cpp 复制代码
using seconds = std::chrono::seconds;
std::chrono::milliseconds d{ 5500 };
std::chrono::abs(d); // == 5s
std::chrono::round<seconds>(d); // == 6s
std::chrono::ceil<seconds>(d); // == 6s
std::chrono::floor<seconds>(d); // == 5s
相关推荐
keep intensify20 分钟前
【数据结构】--- 双向链表的增删查改
c语言·数据结构·算法·链表
mahuifa25 分钟前
(34)VTK C++开发示例 ---将图片映射到平面
c++·平面·3d·vtk·cmake
薛慕昭1 小时前
《ESP32无线网络编程全攻略:从STA/AP模式到NTP时间同步》
开发语言·单片机·嵌入式硬件
小杨升级打怪中1 小时前
前端面经-VUE3篇(二)--vue3组件知识(二)依赖注入、异步组件、生命周期、组合式函数、插件
开发语言·前端·javascript
催眠大树1 小时前
适配器模式(Adapter Pattern)
java·开发语言·适配器模式
zl_dfq2 小时前
C语言 之 【栈的简介、栈的实现(初始化、销毁、入栈、出栈、判空、栈的大小、访问栈顶元素、打印)】
c语言·数据结构
geneculture2 小时前
融智学数学符号体系的系统解读(之一)
人工智能·算法·机器学习
Despacito0o2 小时前
C语言发展史:从Unix起源到现代标准演进
c语言·stm32·unix·嵌入式实时数据库
巷9552 小时前
DBSCAN对比K-means
算法·机器学习·kmeans
博哥爱学习3 小时前
《Java高级编程:从原理到实战 - 进阶知识篇四》
java·开发语言