C++17新特性个人总结

C++17

编译器版本:GCC 7.1、Clang 5.0

__cplusplus:201703L

编译选项:-std=c++17

1 关键字

1.1 constexpr

扩展constexpr使用范围,可用于if语句中,也可用于lambda表达式中。

例子1:

#include<iostream>

template<bool ok>
constexpr void foo()
{
 //在编译期进行判断,if和else语句不生成代码
 if constexpr (ok == true)
 {
  //当ok为true时,下面的else块不生成汇编代码
  std::cout << "ok" << std::endl;
 }
 else
 {
  //当ok为false时,上面的if块不生成汇编代码
  std::cout << "not ok" << std::endl;
 }
}

int main()
{
 foo<true>();//输出ok,并且汇编代码中只有std::cout << "ok" << std::endl;这一句
 foo<false>();//输出not ok,并且汇编代码中只有std::cout << "not ok" << std::endl;这一句
 return 0;
}

例子2:

int main()
{
 constexpr auto add1 = [](int n, int m){
  auto func1 = [=] { return n; }; //func1 lambda表达式
  auto func2 = [=] { return m; }; //func2 lambda表达式
  return [=] { return func1() + func2(); };
 };
 constexpr auto add2 = [](int n, int m){
  return n + m;
 };
 auto add3 = [](int n, int m){
  return n + m;
 };
 int sum1 = add1(30, 40)( ); //传入常量值,add1在编译期计算,立即返回70
 int sum2 = add2(sum1, 4); //由于传入非constexpr变量,add2的constexpr失效,变成运行期lambda
 constexpr int sum3 = add3(1, 2); //sum3为constexpr变量,传入常量值,add3变成编译期lambda,立即返回3
 int sum4 = add2(10, 2);//传入常量值,add2在编译期计算,立即返回12
 return 0;
}

1.2 static_assert

扩展static_assert用法,静态断言的显示文本可选。

如:

static_assert(true, "");
static_assert(true);//c++17支持

1.3 auto

扩展auto的推断范围

如:

auto x1 = { 1, 2 }; //推断出std::initializer_list<int>类型
auto x2 = { 1, 2.0 }; //错误:类型不统一,无法推断
auto x3{ 1, 2 }; //错误:auto的聚合初始化只能一个元素
auto x4 = { 3 }; //推断出std::initializer_list<int>类型
auto x5{ 3 }; //推断出int类型

1.4 typename

扩展用法,允许出现在模板的模板的参数中。

首先回顾一下typename的用法,①用于模板中,表示模板参数为类型;②用于声明某名字是变量名

如例1:

struct A
{
 typedef int Example;
};
//第一种用法:声明模板参数为类型
template<typename T>
struct B { };

struct C
{
 typedef typename A::Example E;//第二种用法:声明某名字为一种类型
};

int main()
{
 typename A::Example e;//第二种用法:声明某名字为一种类型
 return 0;
}

新特性下的typename用法,

如例2:

#include<iostream>
#include<typeinfo>

template<typename T>
struct A
{
 int num;
 A()
 {
  std::cout << "A Construct" << std::endl;
  std::cout << "template typename is: " << typeid (T).name() << std::endl;
 }
};
//此处的T可省略,X代表模板类型,T和X前的typename可替换成class
template<template<typename T> typename X>
struct B
{
 X<double> e;
 B() { std::cout << "B Construct" << std::endl; }
};

int main()
{
 A<B<A>> a;
 std::cout << "***************************" << std::endl;
 B<A> b;
 return 0;
}

运行结果:

1.5 inline

扩展用法,可用于定义内联变量,功能与内联函数相似。inline可避免函数或变量多重定义的问题,如果已定义相同的函数或变量(且该函数或变量声明为inline),编译器会自动链接到该函数或变量。

如(不发生错误):

// test.h
inline void print()
{
 std::cout << "hello world" << std::endl;
}

inline int num = 0;
// func.h
include "test.h"
inline void add(int arg)
{
 num += arg;
 print();
}
// main.cpp
include "func.h"
int main()
{
 num = 0;
 print();
 add(10);
 return 0;
}

2 语法

2.1 折叠表达式

用于变长参数模板的解包,只支持各种运算符(和操作符),分左、右折叠

如:

#include<string>

template<typename ... T>
auto sum(T ... arg)
{
 return (arg + ...);//右折叠
}

template<typename ... T>
double sum_strong(T ... arg)
{
 return (arg + ... + 0);//右折叠
}

template<typename ... T>
double sub1(T ... arg)
{
 return (arg - ...);//右折叠
}

template<typename ... T>
double sub2(T ... arg)
{
 return (... - arg);//左折叠
}

int main()
{
 int s1 = sum(1, 2, 2, 4, 5);//解包:((((1+)2+)3+)4+)5 = 15
 double s2 = sum(1.1, 2.2, 3.3, 4.4, 5.5, 6.6);
 double s3 = sum(1, 2.2, 3, 4.4, 5);

 double s4 = sub1(5, 2, 1, 1);//解包:((((5-)2-)1-)1) = 1
 double s5 = sub2(5, 2, 1, 1);//解包:(5-(2-(1-(1)))) = 3

 double s6 = sum_strong();//s6 = 0

 std::string str1("he");
 std::string str2("ll");
 std::string str3("o ");
 std::string str4("world");
 std::string str5 = sum(str1, str2, str3, str4);//str5 = "hello world"
 return 0;
}

2.2 结构化绑定

用一对包含一个或多个变量的中括号,表示结构化绑定,但是使用结构化绑定时,须用auto关键字,即绑定时声明变量

例子1:

/*
 * 例子:多值返回
 */
struct S
{
 double num1;
 long num2;
};

S foo(int arg1, double arg2)
{
 double result1 = arg1 * arg2;
 long result2 = arg2 / arg1;
 return {result1, result2};//返回结构体S对象
};

int main()
{
 auto [num1, num2] = foo(10, 20.2);//自动推导num1为double,num2为long
 return 0;
}

例子2:

#include<list>
#include<map>

/*
 * 例子:循环遍历
 */
template<typename T, typename U>
struct MyStruct
{
 T key;
 U value;
};

int main()
{
 std::list<MyStruct<int, double>> Container1;
 std::map<int, MyStruct<long long, char>> Container2;
 for(auto [key, value] : Container1)
 {
  //key为int类型,value为double类型
 }
 for(auto [key, value] : Container2)
 {
  //key为int类型,value为MyStruct<long long, char>类型
  //value1为long long类型,value2为char类型
  auto [value1, value2] = value;
 }
 return 0;
}

2.3 允许非类型模板参数进行常量计算

非类型模板参数可传入类的静态成员

如:

class MyClass
{
public:
 static int a;
};

template<int *arg>
void foo() {}

int main()
{
 foo<&MyClass::a>();
 return 0;
}

2.4 条件分支语句初始化

在if和switch中可进行初始化

如:

template<long value>
void foo(int &ok)
{
 if constexpr (ok = 10; value > 0)
 {

 }
}

int main()
{
 int num = 0;
 if(int i = 0; i == 0)
 {

 }
 foo<10>(num);
 switch(int k = 10; k)
 {
  case 0:break;
  case 1:break;
  default:break;
 }
 return 0;
}

2.5 聚合初始化

在初始化对象时,可用花括号进行对其成员进行赋值

如:

struct MyStruct1
{
 int a;
 int b;
};

struct MyStruct2
{
 int a;
 MyStruct1 ms;
};

int main()
{
 MyStruct1 a{10};
 MyStruct2 b{10, 20};
 MyStruct2 c{1, {}};
 MyStruct2 d{{}, {}};
 MyStruct2 e{{}, {1, 2}};
 return 0;
}

2.6 嵌套命名空间

简化多层命名空间的写法

如:

//传统写法
namespace A
{
 namespace B
 {
  namespace C
  {

  };
 };
};
//新写法
namespace A::B::C
{

};

2.7 lambda表达式捕获*this的值

lambda表达式可捕获*this的值,但this及其成员为只读

如:

struct MyStruct {
 double ohseven = 100.7;
 auto f() {
  return [this] {
   return [*this] {
    this->ohseven = 200.2;//错误,只读变量不可赋值
    return ohseven;//正确
   };
  }();
 }
 auto g() {
  return []{
   return [*this]{};//错误,外层lambda表达式没有捕获this
  }();
 }
};

2.8 枚举[类]对象的构造

可以给枚举[类]对象赋值

如:

enum MyEnum { value };
MyEnum me {10};//错误:不能用int右值初始化MyEnum类型对象

enum byte : unsigned char { };
byte b { 42 }; //正确
byte c = { 42 }; //错误:不能用int右值初始化byte类型对象
byte d = byte{ 42 }; //正确,其值与b相等
byte e { -1 }; //错误:常量表达式-1不能缩小范围为byte类型

struct A { byte b; };
A a1 = { { 42 } }; //错误:不能用int右值初始化byte类型对象
A a2 = { byte{ 42 } }; //正确

void f(byte);
f({ 42 }); //错误:无类型说明符

enum class Handle : unsigned int { value = 0 };
Handle h { 42 }; //正确

2.9 十六进制单精度浮点数字面值

以0x前缀开头的十六进制数,以f后缀的单精度浮点数,合并,就有了十六进制的单精度浮点数

如:

int main()
{
 float value = 0x1111f;
 return 0;
}

2.10 基于对齐内存的动态内存分配

谈到动态内存分配,少不了new和delete运算符,新标准中的new和delete运算符新增了按照对齐内存值来分配、释放内存空间的功能(即一个新的带对齐内存值的new、delete运算符重载)

函数原型:

void* operator new(std::size_t size, std::align_val_t alignment);
void* operator new[](std::size_t size, std::align_val_t alignment);

void operator delete(void*, std::size_t size, std::align_val_t alignment);
void operator delete[](void*, std::size_t size, std::align_val_t alignment);

参数说明:

size —— 分配的字节数。必须为alignment的整数倍。

alignment —— 指定的对齐内存值。必须是实现支持的合法对齐。

new的返回值:

成功,返回指向新分配内存起始地址的指针。

用法例子:

#include<new>

struct alignas(8) A {};

int main()
{
 A *a = static_cast<A *>(::operator new(sizeof(A), static_cast<std::align_val_t>(alignof (A))));
 ::operator delete(a, sizeof(A), static_cast<std::align_val_t>(alignof (A)));
 return 0;
}

2.11 细化表达式的计算顺序

为了支持泛型编程和重载运算符的广泛使用,新特性将计算顺序进行的细化

如以下争议代码段:

#include<map>

int main()
{
 std::map<int, int> tmp;
 //对于std::map的[]运算符重载函数,在使用[]新增key时,std::map就已经插入了一个新的键值对
 tmp[0] = tmp.size();//此处不知道插入的是{0, 0}还是{0, 1}
 return 0;
}

为了解决该情况,新计算顺序规则为:

①后缀表达式从左到右求值。这包括函数调用和成员选择表达式。

②赋值表达式从右向左求值。这包括复合赋值。

③从左到右计算移位操作符的操作数。

2.12 模板类的模板参数自动推导

定义模板类的对象时,可以不指定模板参数,但必须要在构造函数中能推导出模板参数

如:

template<class T> struct A {
 explicit A(const T&, ...) noexcept {} // #1
 A(T&&, ...){} // #2
};

int i;

A a1 = { i, i }; //错误,不能根据#1推导为右值引用,也不能通过#1实现复制初始化
A a2{i, i}; //正确,调用#1初始化成功,a2推导为A<int>类型
A a3{0, i}; //正确,调用#2初始化成功,a2推导为A<int>类型
A a4 = {0, i}; //正确,调用#2初始化成功,a2推导为A<int>类型

template<class T> A(const T&, const T&) -> A<T&>; // #3
template<class T> explicit A(T&&, T&&) -> A<T>; // #4

A a5 = {0, 1}; //错误,#1和#2构造函数结果相同(即冲突)。根据#3推导为A<int&>类型
A a6{0, 1}; //正确,通过#2推断为A<int>类型
A a7 = {0, i}; //错误,不能将非静态左值引用绑定到右值。根据#3推导为A<int&>类型
A a8{0, i}; //错误,不能将非静态左值引用绑定到右值。根据#3推导为A<int&>类型

template<class T>
struct B {

 template<class U>
 using TA = T;//定义别名

 template<class U>
 B(U, TA<U>);//构造函数
};

B b{(int*)0, (char*)0}; //正确,推导为B<char *>类型
2.13 简化重复命名空间的属性列表
如:

[[ using CC: opt(1), debug ]] void f() {}
//作用相同于 [[ CC::opt(1), CC::debug ]] void f() {}

2.14 不支持、非标准的属性

在添加属性列表时,编译器会忽略不支持的非标准的属性,不会发出警告和错误。

2.15 改写与继承构造函数

在类的继承体系中,构造函数的自动调用是一个令人头疼的问题。新特性引入继承与改写构造函数的用法。

例子1:

#include<iostream>

struct B1
{
 B1(int) { std::cout << "B1" << std::endl; }
};

struct D1 : B1 {
 using B1::B1;//表示继承B1的构造函数
};

D1 d1(0); //正确,委托基类构造函数进行初始化,调用B1::B1(int)

例子2:

#include<iostream>

struct B1
{
 B1(int) { std::cout << "B1" << std::endl; }
};

struct B2
{
 B2(int) { std::cout << "B2" << std::endl; }
};

struct D1 : B1, B2 {
 using B1::B1;//表示继承B1的构造函数
 using B2::B2;//表示继承B2的构造函数
};
D1 d1(0); //错误:函数冲突,

struct D2 : B1, B2
{
 using B1::B1;
 using B2::B2;
 //正确,D2::D2(int)隐藏了B1::B1(int)和B2::B2(int)。另外由于B1和B2没有默认的构造函数,因此必须显式调用B1和B2的构造函数
 D2(int) : B1(1), B2(0)
 { std::cout << "D2" << std::endl; }
};

struct D3 : B1
{
 using B1::B1;
};
D3 d3(0);//正确,继承B1的构造函数,即利用B1的构造函数来初始化,输出B1

// 程序入口
int main()
{
 D2 d(100);//编译通过,输出B1 B2 D2
 return 0;
}

例子3:

#include<iostream>

struct B1
{
 B1() { std::cout << "B1 default" << std::endl; }
 B1(int) { std::cout << "B1" << std::endl; }
};

struct B2
{
 B2() { std::cout << "B2 default" << std::endl; }
 B2(int) { std::cout << "B2" << std::endl; }
};

struct D1 : B1, B2
{
 using B1::B1;
 using B2::B2;
 //正确,D2::D2(int)隐藏了B1::B1(int)和B2::B2(int),但必须要显示调用B1和B2的构造函数
 D1(int) : B1(1), B2(0)
 { std::cout << "D2" << std::endl; }
 //有默认构造函数,在不显示调用基类的构造函数时自动调用基类的默认构造函数
 D1() { std::cout << "D2 default" << std::endl; }
};
// 程序入口
int main()
{
 D1 d(100);//编译通过,输出B1 B2 D2
 D1 dd;
 //输出
 //B1 default
 //B2 default
 //D2 default
 return 0;
}

2.16 内联变量

见1.5

2.17 用auto作为非类型模板参数

当模板参数为非类型时,可用auto自动推导类型

如:

#include<iostream>

template<auto T>
void foo()
{
 std::cout << T << std::endl;
}

int main()
{
 foo<100>();//输出100
 foo<int>();//no matching function for call to "foo<int>()"
 return 0;
}

3 宏

3.1 __has_include

判断有没有包含某文件

如:

int main()
{
#if __has_include(<cstdio>)
 printf("hehe");
#endif
#if __has_include("iostream")
 std::cout << "hehe" << std::endl;
#endif
return 0;
}

4 属性

4.1 fallthrough

用于switch语句块内,表示会执行下一个case或default

如:

int main()
{
 int ok1, ok2;
 switch (0)
 {
  case 0:
  ok1 = 0;
  [[fallthrough]];
  case 1:
  ok2 = 1;
  [[fallthrough]];
 }
 return 0;
}

4.2 nodiscard

可用于类声明、函数声明、枚举声明中,表示函数的返回值没有被接收,在编译时会出现警告。

如:

[[nodiscard]] class A {}; //该属性在这其实没用
[[nodiscard]] enum class B {}; //该属性在这其实没用
class C {};

[[nodiscard]] int foo()
{ return 10; }

[[nodiscard]] A func1() { return A(); }
[[nodiscard]] B func2() { return B(); }
[[nodiscard]] C func3() { return C(); }

int main()
{
 foo();//warning: ignoring return value
 func1();//warning: ignoring return value
 func2();//warning: ignoring return value
 func3();//warning: ignoring return value
 return 0;
}

4.3 maybe_unused

可用于类、typedef、变量、非静态数据成员、函数、枚举或枚举值中。用于抑制编译器对没用实体的警告。即加上该属性后,对某一实体不会发出“没有用”的警告。

用法例子:

[[maybe_unused]] class A {};
[[maybe_unused]] enum B {};
[[maybe_unused]] int C;
[[maybe_unused]] void fun();

结语

本次检验C++17新特性使用了GCC编译器,对于Clang的支持性方面没有做出差异测试。若有问题,欢迎指出

到此这篇关于C++17新特性个人总结的文章就介绍到这了,更多相关C++17新特性内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++17结构化绑定的实现

    动机 std::map<K, V>的insert方法返回std::pair<iterator, bool>,两个元素分别是指向所插入键值对的迭代器与指示是否新插入元素的布尔值,而std::map<K, V>::iterator解引用又得到键值对std::pair<const K, V>.在一个涉及std::map的算法中,有可能出现大量的first和second,让人不知所措. #include <iostream> #include <m

  • visual studio 2019编译c++17的方法

    右键点击你的项目打开 属性 > C/C++ > Language > C++ Language Standard,选择c++ 17 或者 c++ latest即可. 步骤1:右键点击项目如下图: 步骤二,选择最下面的"属性"项,如下图: 步骤三,点击"配置属性",选择"通用",然后在右侧选择"C++语言标准",后面的下拉列表中选择自己需要的C++版本编译器即可. 到此这篇关于visual studio 201

  • C++17新特性个人总结

    C++17 编译器版本:GCC 7.1.Clang 5.0 __cplusplus:201703L 编译选项:-std=c++17 1 关键字 1.1 constexpr 扩展constexpr使用范围,可用于if语句中,也可用于lambda表达式中. 例子1: #include<iostream> template<bool ok> constexpr void foo() { //在编译期进行判断,if和else语句不生成代码 if constexpr (ok == true)

  • Java 17的一些新特性介绍

    目录 前言 Java 17中的Sealed 密封类 Java 17提供了更好的随机生成器 Java对增强安全性的关注 Pattern Matching For Switch预览 前言 Java17将是一个长期支持的LTS版本. Java采用了6个月的发布周期.也就是说,它将每6个月发布一个新版本的Java.每隔3年,LTS版本就会发布一次.目前,Java 11是LTS版本,于2018年9月发布.但在Java17发布后,它将是最新的LTS支持. 许多组织依赖LTS版本,所以他们使用的是Java11

  • C++14新特性的所有知识点全在这

    前面程序喵介绍过C++11的新特性,在这里(),这篇文章介绍下C++14的新特性. 函数返回值类型推导 C++14对函数返回类型推导规则做了优化,先看一段代码: #include <iostream> using namespace std; auto func(int i) { return i; } int main() { cout << func(4) << endl; return 0; } 使用C++11编译: ~/test$ g++ test.cc -st

  • JAVA8 十大新特性详解

    "Java is still not dead-and people are starting to figure that out." 本教程将用带注释的简单代码来描述新特性,你将看不到大片吓人的文字. 一.接口的默认方法 Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下: 复制代码 代码如下: interface Formula {    double calculate(int a); default do

  • 110.iOS10新特性适配教程XCode8新特性解析

    iOS10 新特性 SiriKit SiriKit的功能非常强大,支持音频.视频.消息发送接收.搜索照片.预订行程.管理锻炼等等.在用到此服务时,siri会发送Intent对象,里面包括用户的请求和各种数据,可以对这个intent处理选择适当的响应. 这个功能主要是看这两个头文件(#import Proactive Suggestions 系统预先建议 背景就是iOS9的时候系统给予的主动建议会通过:Spolight搜索,Safari搜索,Handoff,或者siri建议. 在iOS10之后新增

  • 浅析C++11新特性的Lambda表达式

    lambda简介 熟悉Python的程序员应该对lambda不陌生.简单来说,lambda就是一个匿名的可调用代码块.在C++11新标准中,lambda具有如下格式: [capture list] (parameter list) -> return type { function body } 可以看到,他有四个组成部分: 1.capture list: 捕获列表 2.parameter list: 参数列表 3.return type: 返回类型 4.function body: 执行代码

  • C# 7.0 新特性1之基于Tuple的“多”返回值方法

    原文链接:http://www.cnblogs.com/ylvict/p/5573094.html 回顾  首先,提出一个问题,C#中,如何使一个方法可返回"多个"返回值?  我们先来回顾一下C#6.0 及更早版本的做法.   在C#中,通常我们有以下4种方式使一个方法返回多条数据. •使用 KeyValue 组合 • static void Main(string[] args) { int int1 = 15; int int2 = 25; var result = Add_Mul

  • 详解Java8 新特性之日期API

    Java 8 在包java.time下包含了一组全新的时间日期API.下面的例子展示了这组新API里最重要的一些部分: 1.Clock 时钟 Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数.某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象. Clock clock = Clock.systemDefaultZone();

  • java8新特性将List中按指定属性排序过滤重复数据的方法

    在java中常常会遇到这样一个问题,在实际应用中,总会碰到对List排序并过滤重复的问题,如果List中放的只是简单的String类型过滤so easy,但是实际应用中并不会这么easy,往往List中放的是一个类,类中有多个属性,要过滤重复数据,而且这个重复数据要按自己指定的属性过滤,但是要想按照其它属性排序顺序过滤,所以要先排序一下,然后按照某个属性过滤. 实体类如下所示,大家只要创建下面的实体类,无需继承父类,大家不会注解式风格的话,请自行加上getter/setter方法. 首先看看gr

  • Oracle 12c 新特性之多线程数据库操作

    之前我们学习Oracle基础的时候,有一个概念,叫多进程和多线程.在Unix/Linux等环境下面.数据库是以多进程的方式运行的,当一个会话连接进来,就会通过监听,然后在服务器上创建一个进程.而在Windows上面它是以多线程的方式来运行的.一个进程有很多个thread线程.而在12c这个版本上面,Oracle在Unix/Linux平台上做出了一些改变,引入了多线程的方式.通过参数threaded_execution,我们可以控制数据库是以多进程方式运行还是以多线程方式运行,默认该参数是fals

随机推荐