【C++知识点】C++17 常用新特性总结(一)

简介: 【C++知识点】C++17 常用新特性总结(一)

C++17 新特性

折叠表达式

C++17 中引入了折叠表达式,主要是方便模板编程,分为左右折叠。

语法

(形参包 运算符 ...) (1)
(... 运算符 形参包) (2)
(形参包 运算符 ... 运算符 初值) (3)
(初值 运算符 ... 运算符 形参包) (4)

折叠表达式的实例化按以下方式展开成表达式 e:


1.一元右折叠 (E 运算符 …) 成为 (E1 运算符 (… 运算符 (EN-1 运算符 EN)))

2.一元左折叠 (… 运算符 E) 成为 (((E1 运算符 E2) 运算符 …) 运算符 EN)

3.二元右折叠 (E 运算符 … 运算符 I) 成为 (E1 运算符 (… 运算符 (EN−1 运算符 (EN 运算符 I))))

4.二元左折叠 (I 运算符 … 运算符 E) 成为 ((((I 运算符 E1) 运算符 E2) 运算符 …) 运算符 EN)


(其中 N 是包展开中的元素数量)

template<typename... Args>
bool all(Args... args) { return (... && args); }
bool b = all(true, true, true, false);
//在 all() 中,一元左折叠展开成
//return ((true && true) && true) && false;
//b 是 false

C++标准设置

解决方案下面的项目名上右键->属性->配置属性->C/C+±>语言->C++语言标准,更改成自己的想要的版本:

类模板参数推导

类模板实例化时,可以不必显式指定类型,前提是保证类型可以推导:

#include <iostream>
using namespace std;
template<class T>
class ClassTest
{
public:
    ClassTest(T, T) {};
};
int main() { 
    auto y = new ClassTest{ 100, 200 }; //分配的类型是 ClassTest<int>
    return 0;
}

auto 占位的非类型模板形参

#include <iostream>
using namespace std;
template <auto T> void func1() { 
    cout << T << endl; 
}
int main() {
    func1<100>();
    //func1<int>();
    return 0;
}

编译期 constexpr if 语句

#include <iostream>
using namespace std;
template <bool ok> constexpr void func2() {
    //在编译期进行判断,if和else语句不生成代码
    if constexpr (ok == true) {
        //当ok为true时,下面的else块不生成汇编代码
        cout << "ok" << endl;
    }
    else {
        //当ok为false时,上面的if块不生成汇编代码
        cout << "not ok" << endl;
    }
}
int main() {
    func2<true>(); //输出ok,并且汇编代码中只有 cout << "ok" << endl;
    func2<false>(); //输出not ok,并且汇编代码中只有 cout << "not ok" << endl;
    return 0;
}

inline 变量

扩展的 inline 用法,使得可以在头文件或者类内初始化静态成员变量。

//mycode.h
inline int value = 100;
//mycode.cpp
class AAA {
    inline static int value2 = 200;
};

结构化绑定

在 C++11 中,如果需要获取 tuple 中元素,需要使用 get<>() 函数或者 tie<> 函数,这个函数可以把 tuple 中的元素值转换为可以绑定到 tie<>() 左值的集合,也就是说需要已分配好的内存去接收,用起来不方便。

int main() {
    auto student = make_tuple(string{ "Zhangsan" }, 19, string{ "man" });
    string name;
    size_t age;
    string gender;
    tie(name, age, gender) = student;
    cout << name << ", " << age << ", " << gender << endl;
    //Zhangsan, 19, man
    return 0;
}

C++17 中的结构化绑定,大大方便了类似操作,而且使用引用捕获时,还可以修改捕获对象里面的值,代码也会简洁很多。

int main() {
    auto student = make_tuple(string{ "Zhangsan" }, 19, string{ "man" });
    auto [name, age, gender] = student;
    cout << name << ", " << age << ", " << gender << endl;
    return 0;
}

if switch 初始化

使用迭代器操作时,可以使代码更紧凑。

unordered_map<string, int> stu1{ {"zhangsan", 18}, {"wangwu", 19} };
//C++11
auto iter = stu1.find("wangwu");
if (iter != stu1.end()) {
    cout << iter->second << endl;
}
//C++17
if (auto iter = stu1.find("wangwu"); iter != stu1.end()) {
    cout << iter->second << endl;
}

简化的嵌套命名空间

//C++17之前
namespace A {
    namespace B {
        namespace C {
            void func1() {}
        } //namespace C
    } //namespace B
} //namespace A
//C++17
namespace A::B::C {
    void func1() {}
} //namespace A::B::C

using 声明语句可以声明多个名称

using std::cout, std::cin;
• 1

lambda 表达式捕获 *this

一般情况下,lambda 表达式访问类成员变量时需要捕获 this 指针,这个 this 指针指向原对象,即相当于一个引用,在多线程情况下,有可能 lambda 的生命周期超过了对象的生命周期,此时对成员变量的访问是未定义的。

因此 C++17 中增加捕获 *this,此时捕获的是对象的副本,也可以理解为只能对原对象进行读操作,没有写权限。

#include <iostream>
using namespace std;
class ClassTest {
public:
    int num;
    void func1() {
        auto lamfunc = [*this]() { cout << num << endl; };
        lamfunc();
    }
};
int main() {
    ClassTest a;
    a.num = 100;
    a.func1();
    return 0;
}

简化重复命名空间的属性列表

为类型、对象、代码等引入由实现定义的属性。

[[属性]] [[属性1, 属性2, 属性3(实参)]] [[命名空间::属性(实参)]] alignas说明符

正式而言,语法是:

[[属性列表]] (C++11起)
[[using 属性命名空间 : 属性列表]] (C++17起)

其中属性列表是由逗号分隔的零或更多个属性的序列(可以以指示包展开的省略号 … 结束)。

标识符
属性命名空间 :: 标识符
标识符 (实参列表)
属性命名空间 :: 标识符 (实参列表)
  1. 1.简单属性,例如 [[noreturn]]
  2. 2.有命名空间的属性,例如 [[gnu::unused]]
  3. 3.有实参的属性,例如 [[deprecated(“because”)]]
  4. 4.既有命名空间又有实参列表的属性

举个例子:

[[gnu::always_inline]] [[gnu::hot]] [[gnu::const]] [[nodiscard]]
inline int f(); //声明 f 带四个属性
//C++11
[[gnu::always_inline, gnu::const, gnu::hot, nodiscard]]
int f(); //同上,但使用含有四个属性的单个属性说明符
//C++17:
[[using gnu:const, always_inline, hot]] [[nodiscard]]
int f [[gnu::always_inline]] (); //属性可出现于多个说明符中
int f() { return 0; }

__has_include

跨平台项目需要考虑不同平台编译器的实现,使用 __has_include 可以判断当前环境下是否存在某个头文件。

int main() {
#if __has_include("iostream")
    cout << "iostream exist." << endl;
#endif
#if __has_include(<cmath>)
    cout << "<cmath> exist." << endl;
#endif
    return 0;
}

新增属性

[[fallthrough]]

switch 语句中跳到下一条语句,不需要 break,让编译器忽略告警。

int i = 1;
int result;
switch (i) {
    case 0:
        result = 1; //warning
    case 1:
        result = 2;
        [[fallthrough]]; //no warning
    default:
        result = 0;
        break;
}

[[nodiscard]]

所修饰的内容不可被忽略,主要用于修饰函数返回值。

当用于描述函数的返回值时,如果调用函数的地方没有获取返回值时,编译器会给予警告。

[[nodiscard]] auto func(int a, int b) { return a + b; }
int main() {
    func(2, 3); //警告
    return 0;
}

[[maybe_unused]]

用于描述暂时没有被使用的函数或变量,以避免编译器对此发出警告。

[[maybe_unused]] void func() //没有被使用的函数
{
    cout << "test" << endl;
}
int main()
{
    [[maybe_unused]] int num = 0; //没有被使用的变量
    return 0;
}

charconv

<charconv> 是 C++17 新的标准库头文件,包含了相关类和两个转换函数。


可以完成传统的整数/浮点和字符串互相转换的功能(atoi、itoa、atof、sprintf等),同时支持输出格式控制、整数基底设置并且将整数和浮点类型对字符串的转换整合了起来。

是独立于本地环境、不分配、不抛出的。目的是在常见的高吞吐量环境,例如基于文本的交换( JSON 或 XML )中,允许尽可能快的实现。

chars_format

chars_format 是作为格式控制的类定义在 头文件中。

enum class chars_format {
    scientific = /*unspecified*/,
    fixed = /*unspecified*/,
    hex = /*unspecified*/,
    general = fixed | scientific
};

from_chars

first, last - 要分析的合法字符范围。

value - 存储被分析值的输出参数,若分析成功。.

base - 使用的整数基底:2 与 36 间的值(含上下限)。

fmt - 使用的浮点格式,std::chars_format 类型的位掩码。

struct from_chars_result {
    const char* ptr;
    std::errc ec;
};
std::from_chars_result from_chars(const char* first, const char* last,
                                  /*see below*/& value, int base = 10);
std::from_chars_result from_chars(const char* first, const char* last, float& value,
                                  std::chars_format fmt = std::chars_format::general);
std::from_chars_result from_chars(const char* first, const char* last, double& value,
                                  std::chars_format fmt = std::chars_format::general);
std::from_chars_result from_chars(const char* first, const char* last, long double& value,
                                  std::chars_format fmt = std::chars_format::general);

to_chars

struct to_chars_result {
    char* ptr;
    std::errc ec;
};
std::to_chars_result to_chars(char* first, char* last, value, int base = 10);
目录
相关文章
|
3月前
|
编译器 C++ 开发者
C++一分钟之-C++20新特性:模块化编程
【6月更文挑战第27天】C++20引入模块化编程,缓解`#include`带来的编译时间长和头文件管理难题。模块由接口(`.cppm`)和实现(`.cpp`)组成,使用`import`导入。常见问题包括兼容性、设计不当、暴露私有细节和编译器支持。避免这些问题需分阶段迁移、合理设计、明确接口和关注编译器更新。示例展示了模块定义和使用,提升代码组织和维护性。随着编译器支持加强,模块化将成为C++标准的关键特性。
142 3
|
3月前
|
编译器 C语言 C++
C++一分钟之-C++11新特性:初始化列表
【6月更文挑战第21天】C++11的初始化列表增强语言表现力,简化对象构造,特别是在处理容器和数组时。它允许直接初始化成员变量,提升代码清晰度和性能。使用时要注意无默认构造函数可能导致编译错误,成员初始化顺序应与声明顺序一致,且在重载构造函数时避免歧义。利用编译器警告能帮助避免陷阱。初始化列表是高效编程的关键,但需谨慎使用。
48 2
|
1月前
|
安全 NoSQL Redis
C++新特性-智能指针
C++新特性-智能指针
|
3月前
|
存储 网络协议 编译器
【干货总结】Linux C/C++面试知识点
Linux C/C++基础与进阶知识点,不仅用于面试,平时开发也用得上!
519 13
|
2月前
|
数据安全/隐私保护 C++
|
3月前
|
安全 JavaScript 前端开发
C++一分钟之-C++17特性:结构化绑定
【6月更文挑战第26天】C++17引入了结构化绑定,简化了从聚合类型如`std::tuple`、`std::array`和自定义结构体中解构数据。它允许直接将复合数据类型的元素绑定到单独变量,提高代码可读性。例如,可以从`std::tuple`中直接解构并绑定到变量,无需`std::get`。结构化绑定适用于处理`std::tuple`、`std::pair`,自定义结构体,甚至在范围for循环中解构容器元素。注意,绑定顺序必须与元素顺序匹配,考虑是否使用`const`和`&`,以及谨慎处理匿名类型。通过实例展示了如何解构嵌套结构体和元组,结构化绑定提升了代码的简洁性和效率。
60 5
|
2月前
|
存储 安全 编译器
|
3月前
|
C++
C++ 是一种面向对象的编程语言,它支持对象、类、继承、多态等面向对象的特性
C++ 是一种面向对象的编程语言,它支持对象、类、继承、多态等面向对象的特性
|
1天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
41 30
|
16天前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)