目录
1、结构化绑定
2、constexpr扩展
2.1、constexpr lambda
2.2、constexpr if
2.3、constexpr string
4、if with initializer
5、std::optional
6、使用inline定义内联变量
7、std::filesystem库
8、折叠表达式
9、模板的模板参数推导
9.1、从构造函数参数推导模板参数
9.2、从函数参数推导模板参数
10、std::variant
11、std::byte
12、并行算法
12.1、std::for_each和std::for_each_n
12.2、std::transform和std::transform_reduce
12.3、std::reduce和std::inclusive_scan
12.4、std::sort和std::partial_sort
C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战进阶(专栏文章已更新380多篇,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.htmlWindows C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html C++17是C++标准委员会于2017年发布,其官方名称为ISO/IEC 14882:2017。C++17在C++14的基础上进行了大量改进和扩展,旨在提高编程效率、简化代码以及增强标准库的功能。下面介绍一些C++17的一些主要特性。
- 很多C++开源库会大规模地使用C++11及以上的新特性,比如WebRTC开源库,所以在阅读开源代码时需要了解C++11及以上新特性。我们在日常编码中会用到部分新特性,比如一些新的关键字(auto、nullptr、override、final等)、lambda表达式(匿名函数)、智能指针等。此外,现在很多C++开发岗位在面试时都会问到C++11或以上的新特性方面的内容。所以学习C++11及以上的新特性很有必要!
- C++新特性的推出,使得C++变得更丰富更灵活,但也使得C++变得更臃肿,更加难以驾驭!
1、结构化绑定
可以通过一行代码将结构体或元组中的成员绑定到变量上,从而方便地访问这些成员。
struct Point
{int x;int y;
};Point pt{1, 2};
auto [x, y] = pt;
在这个例子中,我们定义了一个Point结构体,并创建了一个名为pt的实例,它包含了两个成员变量x和y。接着,我们使用auto关键字和一对中括号,将x和y变量绑定到了pt的成员变量上。在这之后,我们就可以直接使用x和y变量来访问pt的成员变量了,而不需要通过pt.x和pt.y来访问。
需要注意的是,只能在函数内部使用,不能在全局作用域中使用,结构化绑定仅适用于具有公共成员的结构体和元组类型。 此外,结构化绑定还可以在for循环中使用,从而方便地遍历数组、容器等数据结构中的元素。
std::vector<std::pair<int, std::string>> v{{1, "one"}, {2, "two"}, {3, "three"}};
for (auto [key, value] : v)
{std::cout << "key: " << key << ", value: " << value << std::endl;
}
在这个例子中,我们定义了一个vector容器,其中存储了多个pair类型的元素。在for循环中,我们使用auto关键字和一对中括号,将key和value变量绑定到了pair元素的first和second成员上。在循环体中,我们就可以直接使用key和value变量来访问pair元素的成员了。
需要注意的是,结构化绑定在for循环中使用时,变量名的顺序应该与元素成员的顺序一致,否则会导致绑定错误。此外,需要确保绑定的变量类型与元素成员的类型一致。
2、constexpr扩展
C++17中支持将constexpr与其他东西结合起来使用,使用起来会更加灵活。
2.1、constexpr lambda
C++17支持将constexpr与Lambda表达式结合起来使用,主要体现在以下两点:
- Lambda表达式支持constexpr:在C++17中,lambda表达式可以被声明为constexpr,这意味着它们可以在编译时被求值。这允许lambda表达式在需要编译时常量表达式的场合中使用,例如作为模板参数或初始化constexpr变量。
- 条件编译时的Lambda表达式:即使lambda表达式没有被显式声明为constexpr,只要它满足constexpr的要求(如捕获的变量是字面量类型),它在编译时常量表达式中也能被隐式地视为constexpr。
C++17中的constexpr lambda可以用于编译时计算,可以在编译时执行lambda表达式,而不需要运行时执行。
#include <iostream>int main()
{constexpr auto square = [](int x) { return x * x; };constexpr int result = square(5);std::cout << result << std::endl;return 0;
}
在上面的示例中,我们定义了一个constexpr lambda表达式,它接受一个整数参数并返回该参数的平方。我们还定义了一个constexpr整数变量result,它将square(5)的结果赋值给它。由于lambda表达式是constexpr的,因此square(5)将在编译时计算,而不是在运行时计算。
需要注意的是,constexpr lambda表达式的参数和返回类型必须是字面类型,否则无法在编译时计算。
在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)
专栏1:(该精品技术专栏的订阅量已达到500多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!欢迎订阅!)
C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!
专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:
C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795
常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!
专栏3:(本专栏涵盖了多方面的内容,是当前重点打造的专栏,专栏文章已经更新到390多篇,持续更新中...)
C/C++实战进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、C++11及以上新特性(不仅看开源代码会用到,日常编码中也会用到部分新特性,面试时也会涉及到)、常用C++开源库的介绍与使用、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(排查软件异常的手段与方法、分析C++软件异常的基础知识、常用软件分析工具使用、实战问题分析案例等)、设计模式、网络基础知识与网络问题分析进阶内容等。
专栏4:
VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/124272585
将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。
专栏5:
Windows C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html
根据多年C++软件开发实践,详细地总结了Windows C++ 应用软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。
2.2、constexpr if
C++17引入了constexpr if语句,允许在编译时根据常量表达式的结果来选择性地编译代码块。这极大地改善了模板元编程中的条件编译,使得原本需要在运行时进行的选择可以在编译时完成,从而优化代码并提高执行效率。
使用constexpr if,编译器可以只编译满足条件的代码路径,忽略不满足条件的代码路径,这有助于减少编译后的代码大小并提高运行效率。
C++17中的constexpr if是一种条件编译语句,用于在编译时进行条件判断,从而在编译期间选择不同的代码路径。
1)在if语句中使用constexpr关键字,如果条件表达式是constexpr,则编译器在编译期间进行判断。
2)如果条件表达式为真,则编译器编译if语句中的代码块;否则,编译器忽略if语句中的代码块。
3)可以在if语句中使用else关键字,来指定条件为假时要编译的代码块。
比如下面一段代码:
template<typename T>
void print(const T& t)
{if constexpr (std::is_integral_v<T>){std::cout << "Integral type: " << t << std::endl;}else if constexpr (std::is_floating_point_v<T>){std::cout << "Floating point type: " << t << std::endl;}else{std::cout << "Unknown type: " << t << std::endl;}
}int main()
{int i = 8;float f = 3.5f;std::string s = "Hello World";print(i); // 输出 "Integral type: 8"print(f); // 输出 "Floating point type: 3.5"print(s); // 输出 "Unknown type: Hello World"return 0;
}
在上面的示例代码中,我们使用了constexpr if语句来根据传入的类型不同,选择不同的输出方式。'std::is_integral_v’是C++17中的一个模板元编程工具,用于判断一个类型是否为整数类型。它是一个类型特征(type trait),返回一个布尔值,表示传入的类型是否为整数类型。'std::is_floating_point_v’同理。
2.3、constexpr string
C++17中引入了std::string_view和constexpr std::string这两个新特性,可以更方便地处理字符串。
constexpr std::string是一个编译时常量字符串,可以在编译时计算,不需要在运行时再次计算。可以使用以下方式创建:
constexpr std::string str = "Hello World";
std::string_view是一个非拥有式的字符串视图,可以用于访问字符串的子串。可以使用以下方式创建:
std::string_view str_view = "Hello World";
非拥有式的字符串视图: 它不拥有底层字符串的内存,而是对底层字符串的一个引用或视图 。因此,它不会在自身生命周期结束时释放底层字符串的内存。使用非拥有式的字符串视图可以避免复制字符串,提高程序的性能。例如,在函数调用中传递字符串参数时,使用非拥有式的字符串视图可以避免不必要的内存复制。需要注意的是,当使用非拥有式的字符串视图时,需要确保底层字符串的生命周期不短于视图的生命周期,否则可能会导致悬垂指针的问题。
4、if with initializer
C++17中的if with initializer是一种新的语法结构,允许在if语句中声明和初始化变量。这种语法结构的好处是可以使代码更简洁,因为变量声明和初始化可以在if语句中完成。
if (int x = some_function())
{// 如果some_function返回的值不为0,则进入if语句块// 变量x的作用域仅限于if语句块内部// 变量x的类型为int,值为some_function()的返回值
}
在上面的示例中,if语句中声明了一个变量x,并将其初始化为some_function()的返回值。如果some_function()的返回值不为0,则进入if语句块。在if语句块之外,变量x不再可见。
使用if with initializer可以避免在if语句之前声明变量并进行初始化的冗余代码。同时,这种语法结构还可以使代码更加清晰和简洁。
5、std::optional
std::optional是C++17中引入的一个新特性,用于表示可以存在或不存在的值。它类似于指针,但提供了更好的语义和安全性。
#include <iostream>
#include <optional>std::optional<int> divide(int a, int b)
{if (b == 0) {return std::nullopt; // 表示不存在值} else {return a / b;}
}int main() {auto result = divide(10, 2);if (result) {std::cout << "Result: " << *result << std::endl;} else {std::cout << "Error: Division by zero" << std::endl;}result = divide(10, 0);if (result) {std::cout << "Result: " << *result << std::endl;} else {std::cout << "Error: Division by zero" << std::endl;}return 0;
}
在这个例子中,divide函数返回一个std::optional。如果除数为0,则返回std::nullopt表示不存在值。否则返回a/b的结果。在main函数中,我们使用if语句检查result是否存在值。如果存在,则使用*运算符获取值并输出。否则输出错误信息。
std::optional还提供了其他一些有用的函数,例如value_or函数,用于获取值或默认值。例如:
auto result = divide(10, 0);
int value = result.value_or(-1); // 如果不存在值,则返回-1
std::optional是一个非常有用的工具,可以避免许多指针相关的问题,并提供更好的语义和安全性。
6、使用inline定义内联变量
在 C++17 中,可以使用 inline 关键字来定义内联变量。内联变量的定义必须在头文件中,并且不能有初始化器。
// 头文件 example.h
inline int x; // 声明内联变量// 源文件 example.cpp
#include "example.h"
int x = 42; // 定义内联变量// 在使用内联变量时,可以直接使用其名称,就像使用普通变量一样:
#include "example.h"
int main()
{x = 10; // 直接使用内联变量 xreturn 0;
}
需要注意的是,内联变量的使用和内联函数的使用有些不同。内联函数的定义必须在每个使用它的编译单元中都可见,否则会导致链接错误。而内联变量的定义只需要在任意一个编译单元中可见即可,因为它们不会导致多个实例的生成。
7、std::filesystem库
C++17中引入了std::filesystem库,用于处理文件系统操作。文件操作的函数很多,在此举例,可以自行查找文档。说一下相比于以前的文件操作的优势:
- 更加简洁易用:std::filesystem库提供了一组简洁易用的函数和类,能够方便地完成常见的文件系统操作,使代码更加简洁易读。
- 跨平台支持:std::filesystem库能够跨平台支持各种操作系统,包括Windows、Linux、macOS等操作系统,因此可以在不同的平台上使用相同的代码。
- 更好的性能:std::filesystem库的实现使用了现代操作系统的一些高效API,能够更好地利用操作系统的缓存和异步I/O机制,从而提高文件操作的性能。
- 更好的错误处理:std::filesystem库提供了一组异常类,能够更好地处理文件操作中可能出现的错误,从而使代码更加健壮。
8、折叠表达式
折叠表达式是C++17中引入的新特性,用于简化模板元编程和可变参数模板的实现。折叠表达式允许在编译时对一系列参数进行折叠操作,最终得到一个值。折叠表达式的语法如下:
( pack op ... op init )
( init op ... op pack )
( ... op pack )
( pack op ... op )
其中,pack是可变参数列表中的参数,op是操作符,init是初始值。其中第三种和第四种语法需要至少有一个参数。
例如,以下代码使用折叠表达式计算可变参数列表中的所有值之和:
template<typename... Args>
auto sum(Args... args)
{return (args + ...); // 折叠表达式
}int main()
{int s = sum(1, 2, 3, 4, 5); // s = 15return 0;
}
在这个例子中,(args + ...)表示对所有可变参数进行求和操作。折叠表达式会将所有参数依次展开,然后通过加法运算符对它们进行累加,最终得到结果。
9、模板的模板参数推导
在C++17中,类模板的模板参数推导被引入,允许我们在使用类模板时省略模板参数列表中的一些参数,而让编译器根据上下文自动推导。
9.1、从构造函数参数推导模板参数
template<typename T>
class MyVector
{
public:MyVector(std::initializer_list<T> list) {// ...}
};MyVector vec = {1, 2, 3};
在这个例子中,我们没有显式指定MyVector的模板参数,但编译器可以从std::initializer_list中的元素类型T推导出它的类型为MyVector。
9.2、从函数参数推导模板参数
template<typename T>
class MyArray
{
public:T& operator[](std::size_t index) {// ...}
};void foo(MyArray<int> arr)
{// ...
}MyArray arr = {1, 2, 3};
foo(arr);
在这个例子中,我们也没有显式指定MyArray的模板参数,但编译器可以从foo函数的参数类型MyArray推导出它的类型为MyArray。
需要注意的是,类模板的模板参数推导只能用于构造函数和函数参数,不能用于类模板的成员函数或者静态成员变量。另外,模板参数推导只能用于单一的模板参数,不能同时推导多个模板参数。
10、std::variant
学过QT的对这个关键字想必很熟悉,C++17中的std::variant是一种类型安全的联合类型,它可以存储多个不同的类型值。在使用std::variant时,需要包含头文件。std::variant的声明方式如下:
std::variant<int, double, std::string> v;
这里声明了一个std::variant对象v,它可以存储int、double和std::string类型的值。初始时,v中没有值。
可以通过std::get函数从std::variant对象中获取值,例如:
v = 3.55;
double d = std::get<double>(v);
这里将3.55赋值给v,然后通过std::get函数获取v中的double值,并将其赋值给变量d。
当std::variant对象中存储的值类型与std::get函数传入的类型不匹配时,将抛出std::bad_variant_access异常。
可以使用std::visit函数访问std::variant对象中的值。std::visit函数接受一个lambda表达式作为参数,该lambda表达式的参数类型是std::variant对象中存储的所有类型。例如:
std::visit([](auto&& arg)
{std::cout << arg << std::endl;
}, v);
这里访问v中存储的值,并将其输出到控制台。lambda表达式的参数类型是auto&&,表示可以接受任意类型的参数。
std::variant还提供了一些其他的方法,例如std::holds_alternative函数可以判断std::variant对象中是否存储了指定类型的值,std::index_sequence_for函数可以获取std::variant模板参数中类型的数量等。
总的来说,std::variant提供了一种灵活、类型安全的联合类型实现方式,可以帮助我们更方便地处理多种不同类型的值。
11、std::byte
C++17中的std::byte是一个新类型,用于表示字节数据。它是一种无符号整数类型,有8个比特位,可以表示0到255之间的值。与其他整数类型不同,std::byte类型没有定义任何算术运算符,因为它们不是数学上的对象,而是表示二进制数据的字节。
使用std::byte类型可以更好地处理二进制数据,因为它提供了更直观和类型安全的方式来表示字节。例如,可以使用std::byte类型来读写二进制文件、网络数据包等:
#include <iostream>
#include <cstddef>int main()
{std::byte b1{0x12};std::byte b2{0xff};// 比较两个std::byte类型的值if (b1 == b2) {std::cout << "b1 and b2 are equal" << std::endl;} else {std::cout << "b1 and b2 are not equal" << std::endl;}// 将std::byte类型转换为整数类型std::size_t n = static_cast<std::size_t>(b1);std::cout << "n = " << n << std::endl;// 使用std::byte类型处理二进制数据std::byte buffer[1024];// 从文件中读取二进制数据到缓冲区// ...// 将缓冲区中的数据发送到网络// ...
}
12、并行算法
C++17中提供了一些新的并行算法,可以使用这些算法来实现并行化的计算。这些算法都在头文件== < execution > ==中定义,使用前需要包含该头文件。以下是使用C++17并行算法的一些示例。
12.1、std::for_each和std::for_each_n
std::for_each和std::for_each_n可以用于并行地遍历一个序列,对每个元素进行操作。
#include <algorithm>
#include <execution>
#include <vector>int main()
{std::vector<int> v{1, 2, 3, 4, 5};// 并行遍历std::for_each(std::execution::par, v.begin(), v.end(), [](int& x) {x *= 2;});// 并行遍历前3个元素std::for_each_n(std::execution::par, v.begin(), 3, [](int& x) {x *= 2;});
}
12.2、std::transform和std::transform_reduce
std::transform可以用于并行地对一个序列进行变换操作,std::transform_reduce可以用于并行地对一个序列进行变换操作并求和。
#include <algorithm>
#include <execution>
#include <vector>int main()
{std::vector<int> v{1, 2, 3, 4, 5};// 并行变换std::vector<int> result(v.size());std::transform(std::execution::par, v.begin(), v.end(), result.begin(), [](int x) {return x * 2;});// 并行变换并求和int sum = std::transform_reduce(std::execution::par, v.begin(), v.end(), 0, std::plus<int>{}, [](int x) {return x * 2;});
}
12.3、std::reduce和std::inclusive_scan
std::reduce可以用于并行地对一个序列求和,std::inclusive_scan可以用于并行地对一个序列进行前缀和。
#include <algorithm>
#include <execution>
#include <vector>int main()
{std::vector<int> v{1, 2, 3, 4, 5};// 并行求和int sum = std::reduce(std::execution::par, v.begin(), v.end());// 并行前缀和std::vector<int> result(v.size());std::inclusive_scan(std::execution::par, v.begin(), v.end(), result.begin());
}
12.4、std::sort和std::partial_sort
std::sort可以用于并行地对一个序列进行排序,std::partial_sort可以用于并行地对一个序列的前N个元素进行排序。
#include <algorithm>
#include <execution>
#include <vector>int main()
{std::vector<int> v{5, 3, 1, 4, 2};// 并行排序std::sort(std::execution::par, v.begin(), v.end());// 并行部分排序std::partial_sort(std::execution::par, v.begin(), v.begin() + 3, v.end());
}