【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);
目录
相关文章
|
2月前
|
编译器 程序员 定位技术
C++ 20新特性之Concepts
在C++ 20之前,我们在编写泛型代码时,模板参数的约束往往通过复杂的SFINAE(Substitution Failure Is Not An Error)策略或繁琐的Traits类来实现。这不仅难以阅读,也非常容易出错,导致很多程序员在提及泛型编程时,总是心有余悸、脊背发凉。 在没有引入Concepts之前,我们只能依靠经验和技巧来解读编译器给出的错误信息,很容易陷入“类型迷路”。这就好比在没有GPS导航的年代,我们依靠复杂的地图和模糊的方向指示去一个陌生的地点,很容易迷路。而Concepts的引入,就像是给C++的模板系统安装了一个GPS导航仪
128 59
|
27天前
|
安全 编译器 C++
【C++11】新特性
`C++11`是2011年发布的`C++`重要版本,引入了约140个新特性和600个缺陷修复。其中,列表初始化(List Initialization)提供了一种更统一、更灵活和更安全的初始化方式,支持内置类型和满足特定条件的自定义类型。此外,`C++11`还引入了`auto`关键字用于自动类型推导,简化了复杂类型的声明,提高了代码的可读性和可维护性。`decltype`则用于根据表达式推导类型,增强了编译时类型检查的能力,特别适用于模板和泛型编程。
22 2
|
2月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(三)
【C++】面向对象编程的三大特性:深入解析多态机制
|
2月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(二)
【C++】面向对象编程的三大特性:深入解析多态机制
|
2月前
|
编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(一)
【C++】面向对象编程的三大特性:深入解析多态机制
|
2月前
|
C++
C++ 20新特性之结构化绑定
在C++ 20出现之前,当我们需要访问一个结构体或类的多个成员时,通常使用.或->操作符。对于复杂的数据结构,这种访问方式往往会显得冗长,也难以理解。C++ 20中引入的结构化绑定允许我们直接从一个聚合类型(比如:tuple、struct、class等)中提取出多个成员,并为它们分别命名。这一特性大大简化了对复杂数据结构的访问方式,使代码更加清晰、易读。
43 0
|
2月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析继承机制(三)
【C++】面向对象编程的三大特性:深入解析继承机制
|
2月前
|
编译器 C++
【C++】面向对象编程的三大特性:深入解析继承机制(二)
【C++】面向对象编程的三大特性:深入解析继承机制
|
2月前
|
安全 程序员 编译器
【C++】面向对象编程的三大特性:深入解析继承机制(一)
【C++】面向对象编程的三大特性:深入解析继承机制
|
2月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值