赞
踩
在C++17前构造一个模板类对象需要指明类型:
pair<int, double> p(1, 2.2); // before c++17
std::tuple<int, double, std::string> tq(64, 128.0, "Caroline");
C++17就不需要特殊指定,直接可以推导出类型,代码如下:std::tuple eee(64, 128.0, "Caroline");
- pair p(1, 2.2); // c++17 自动推导
- vector v = {1, 2, 3}; // c++17
如果是tuple, 则是 std::tuple eee(64, 128.0, "Caroline");
复习:
tuple是C++11新标准里的类型。它是一个类似pair类型的模板。pair类型是每个成员变量各自可以是任意类型,但是只能有俩个成员,而tuple与pair不同的是它可以有任意数量的成员。但是每个确定的tuple类型的成员数目是固定的.
可以通过构造函数和make_tuple进行定义初始化
std::tuple<std::string, std::string, int> t2 = std::make_tuple("Caroline", "Wendy", 1992);
get(t) | 返回t的第i个数据成员 |
具体见:c++ pair 、make_pair、tuple_baidu_16370559的博客-CSDN博客
结构化绑定提供了类似其他语言中提供的多返回值的功能。到目前为止,我们可以通过 std::tuple 来构造一个元组,囊括多个返回值。但缺陷是显而易见的,我们没有一种简单的方法直接从元组中拿到并定义元组中的元素,尽管我们可以使用 std::tie 对元组进行拆包,但我们依然必须非常清楚这个元组包含多少个对象,各个对象是什么类型。
用一对包含一个或多个变量的中括号,表示结构化绑定,但是使用结构化绑定时,须用auto关键字,即绑定时声明变量
std::tuple<int,double,std::string> f() {
return std::make_tuple(1,2.3,"456");
}
auto [x,y,z] = f(); // x,y,z 分别被推导为int,double,std::string
结构化绑定还可以改变对象的值,使用引用即可:
- // 进化,可以通过结构化绑定改变对象的值
- int main() {
- std::pair a(1, 2.3f);
- auto& [i, f] = a;
- i = 2;
- cout << a.first << endl; // 2
- }
注意结构化绑定不能应用于constexpr
constexpr auto[x, y] = std::pair(1, 2.3f); // compile error, C++20可以
结构化绑定不止可以绑定pair和tuple,还可以绑定数组和结构体等
- int array[3] = {1, 2, 3};
- auto [a, b, c] = array;
- cout << a << " " << b << " " << c << endl;
-
- // 注意这里的struct的成员一定要是public的
- struct Point {
- int x;
- int y;
- };
- Point func() {
- return {1, 2};
- }
- const auto [x, y] = func();
C++17前if语句需要这样写代码:
- int a = GetValue();
- if (a < 101) {
- cout << a;
- }
- // if (init; condition)
-
- if (int a = GetValue()); a < 101) {
- cout << a;
- }
C++17前只有内联函数,现在有了内联变量,我们印象中C++类的静态成员变量在头文件中是不能初始化的,但是有了内联变量,就可以达到此目的:
- // header file
- struct A {
- static int value;
- };
- inline int A::value = 10;
-
- // ==========或者========
- struct A {
- inline static int value = 10;
- }
延伸:内联函数,
如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方 。
1)内联含函数比一般函数在前面多一个inline修饰符
2)内联函数是直接复制“镶嵌”到主函数中去的,就是将内联函数的代码直接放在内联函数的位置上,这与一般函数不同,主函数在调用一般函数的时候,是指令跳转到被调用函数的入口地址,执行完被调用函数后,指令再跳转回主函数上继续执行后面的代码;而由于内联函数是将函数的代码直接放在了函数的位置上,所以没有指令跳转,指令按顺序执行
3)一般函数的代码段只有一份,放在内存中的某个位置上,当程序调用它是,指令就跳转过来;当下一次程序调用它是,指令又跳转过来;而内联函数是程序中调用几次内联函数,内联函数的代码就会复制几份放在对应的位置上.
利:避免了指令的来回跳转,加快程序执行速度
弊:代码被多次复制,增加了代码量,占用更多的内存空间
递归函数不能使用内联函数
inline可避免函数或变量多重定义的问题,如果已定义相同的函数或变量(且该函数或变量声明为inline),编译器会自动链接到该函数或变量。如在一个头文件使用inline定义过变量,只要其他的头文件包含该头文件,就可以直接使用该变量。
具体见:c++ inline 函数及变量_baidu_16370559的博客-CSDN博客
C++17引入了折叠表达式使可变参数模板编程更方便:
用于变长参数模板的解包,只支持各种运算符(和操作符),分左、右折叠
- template <typename ... Ts>
- auto sum(Ts ... ts) {
- return (ts + ...);
- }
- int a {sum(1, 2, 3, 4, 5)}; // 15
- std::string a{"hello "};
- std::string b{"world"};
- cout << sum(a, b) << endl; // hello world
C++17前lambda表达式只能在运行时使用,C++17引入了constexpr lambda表达式,可以用于在编译期进行计算。
- int main() { // c++17可编译
- constexpr auto lamb = [] (int n) { return n * n; };
- static_assert(lamb(3) == 9, "a");
- }
注意:constexpr函数有如下限制:
函数体不能包含汇编语句、goto语句、label、try块、静态变量、线程局部存储、没有初始化的普通变量,不能动态分配内存,不能有new delete等,不能虚函数。
扩展constexpr使用范围,可用于if语句中,也可用于lambda表达式中。
if constexpr (ok == true)
{
//当ok为true时,下面的else块不生成汇编代码
std::cout << "ok" << std::endl;
}
- namespace A {
- namespace B {
- namespace C {
- void func();
- }
- }
- }
-
- // c++17,更方便更舒适
- namespace A::B::C {
- void func();)
- }
可以判断是否有某个头文件,代码可能会在不同编译器下工作,不同编译器的可用头文件有可能不同,所以可以使用此来判断。
int main()
{
#if __has_include(<cstdio>)
printf("hehe");
#endif
#if __has_include("iostream")
std::cout << "hehe" << std::endl;
#endif
return 0;
}
正常情况下,lambda表达式中访问类的对象成员变量需要捕获this,但是这里捕获的是this指针,指向的是对象的引用,正常情况下可能没问题,但是如果多线程情况下,函数的作用域超过了对象的作用域,对象已经被析构了,还访问了成员变量,就会有问题。
- struct A {
- int a;
- void func() {
- auto f = [this] {
- cout << a << endl;
- };
- f();
- }
- };
- int main() {
- A a;
- a.func();
- return 0;
- }
所以C++17增加了新特性,捕获*this,不持有this指针,而是持有对象的拷贝,这样生命周期就与对象的生命周期不相关啦。
- struct A {
- int a;
- void func() {
- auto f = [*this] { // 这里
- cout << a << endl;
- };
- f();
- }
- };
- int main() {
- A a;
- a.func();
- return 0;
- }
我们可能平时在项目中见过declspec, __attribute , #pragma指示符,使用它们来给编译器提供一些额外的信息,来产生一些优化或特定的代码,也可以给其它开发者一些提示信息。
例如:
- struct A { short f[3]; } __attribute__((aligned(8)));
-
- void fatal() __attribute__((noreturn));
[[fallthrough]],用在switch中提示可以直接落下去,不需要break,让编译期忽略警告
[[nodiscard]] :表示修饰的内容不能被忽略,可用于修饰函数,标明返回值一定要被处理
[[maybe_unused]] :提示编译器修饰的内容可能暂时没有使用,避免产生警告
新增from_chars函数和to_chars函数
const std::string str{ "123456098" };
int value = 0;
const auto res = std::from_chars(str.data(), str.data() + 4, value);
则 :value = 1234;
res.ptr= 56098;
具体见:
https://mp.csdn.net/mp_blog/creation/editor/120419967
C++17增加std::variant实现类似union的功能,但却比union更高级,举个例子union里面不能有string这种类型,但std::variant却可以,还可以支持更多复杂类型,如map等。
注意,不允许使用空变量、具有引用成员的变量、具有c样式数组成员的变量和具有不完整类型(如void)的变量。没有空的状态:这意味着对于每个构建的对象,必须至少调用一个构造函数。默认构造函数初始化第一个类型(通过第一个类型的默认构造函数):
1.默认情况下,变量的默认构造函数调用第一个备选项的默认构造函数:
2.成员函数index()可用于查明当前设置了哪个选项(第一个选项的索引为0)。
3.访问值的通常方法是调用get<>()获取对应的选项值。可以传递它的索引或者类型
#include <variant>
std::variant<int, std::string> var{"hi"}; // initialized with string alternative
std::cout << var.index(); // prints 1
var = 42; // now holds int alternative
std::cout << var.index(); // prints 0
var = "world";
...
try {
std::string s = std::get<std::string>(var); // access by type
std::cout << s << std::endl; //world
var = 3;
int i = std::get<0>(var); // access by index
std::cout << i << std::endl; //3
}
catch (const std::bad_variant_access& e) { // in case a wrong type/index is used
如果没有为第一个类型定义默认构造函数,则调用该变量的默认构造函数会在编译时错误:
#include <iostream>
#include <variant>
struct NoDefConstr
{
NoDefConstr(int i)
{
std::cout << "NoDefConstr::NoDefConstr(int) called\n";
}
};
int main()
{
std::variant<NoDefConstr, int> v1; // ERROR: can’t default construct first type
return 0;
不过辅助类型std::monostate提供了处理这种情况的能力,还提供了模拟空状态的能力。
std::monostate
为了支持第一个类型没有默认构造函数的variant对象,提供了一个特殊的helper类型:std::monostate。类型std::monostate的对象总是具有相同的状态,因此,它们总是相等的。它自己的目的是表示另一种类型,这样variant就没有任何其他类型的值。也就是说,std::monostate可以作为第一种替代类型,使变体类型默认为可构造。
std::variant<std::monostate, NoDefConstr> v2; // OK
std::cout << "index: " << v2.index() << '\n'; // prints 0
4.赋值和emplace()操作对应于初始化:
std::variant<int, int, std::string> var; // sets first int to 0, index()==0
var = "hello"; // sets string, index()==2
var.emplace<1>(42); // sets second int, index()==1
这个函数定义在tuple
头文件中,函数签名如下:
- template <class F, class Tuple>
- constexpr decltype(auto) apply(F&& f, Tuple&& t);
该函数接受两个参数,第一个是一个函数对象,第二个是一个Tuple对象
使用std::apply可以将tuple展开作为函数的参数传入,见代码:
int add(int first, int second) { return first + second; }
auto add_lambda = [](auto first, auto second) { return first + second; };
int main() {
std::cout << std::apply(add, std::pair(1, 2)) << '\n';
std::cout << add(std::pair(1, 2)) << "\n"; // error
std::cout << std::apply(add_lambda, std::tuple(2.0f, 3.0f)) << '\n'; 输出为5
}
std::apply
可以解决函数调用时,tuple
转参数列表,但是如果希望调用的是构造函数怎么办?构造函数毕竟没办法直接获取函数指针。
使用make_from_tuple可以将tuple展开作为构造函数参数。
template <class T, class Tuple>
constexpr T make_from_tuple(Tuple&& t);
struct Foo {
Foo(int first, float second, int third) {
std::cout << first << ", " << second << ", " << third << "\n";
}
};
int main() {
auto tuple = std::make_tuple(42, 3.14f, 0);
std::make_from_tuple<Foo>(std::move(tuple));
}
我们传递一个string时会触发对象的拷贝操作,大字符串的拷贝赋值操作会触发堆内存分配,很影响运行效率,有了string_view就可以避免拷贝操作,平时传递过程中传递string_view即可
它只有两个成员变量,指向内存的指针和可以访问到的长度。它对它指向的内存是只读的,std::string_view的任何方法都不会改变执行的内存。
- void func(std::string_view stv) { cout << stv.data() << endl; }
-
- int main(void) {
- std::string str = "Hello World";
- std::cout << str << std::endl;
-
- std::string_view stv(str.c_str(), str.size());
- cout << stv.data() << endl;
- func(stv);
- return 0;
- }
std::string_view对应的是string,std::string_view背后是basic_string_view模板,因此它有几种字符串变种:
using string_view = basic_string_view<char>;
using u16string_view = basic_string_view<char16_t>;
using u32string_view = basic_string_view<char32_t>;
using wstring_view = basic_string_view<wchar_t>;
C++17使用as_const可以将左值转成const类型
- std::string str = "str";
- const std::string& constStr = std::as_const(str);
C++17正式将file_system纳入标准中,提供了关于文件的大多数功能,基本上应有尽有,这里简单举几个例子:
命名空间及头文件
- #include<filesystem>
- using namespace std::filesystem;
- namespace fs = std::filesystem;
- fs::create_directory(dir_path);
- fs::copy_file(src, dst, fs::copy_options::skip_existing);
- fs::exists(filename);
- fs::current_path(err_code);
path 类:说白了该类只是对字符串(路径)进行一些处理,这也是文件系统的基石。
directory_entry 类:功如其名,文件入口,这个类才真正接触文件。
directory_iterator 类:获取文件系统目录中文件的迭代器容器,其元素为 directory_entry对象(可用于遍历目录)
file_status 类:用于获取和修改文件(或目录)的属性(需要了解C++11的强枚举类型(即枚举类))
常用函数
void copy(const path& from, const path& to) :目录复制
path absolute(const path& pval, const path& base = current_path()) :获取相对于base的绝对路径
bool create_directory(const path& pval) :当目录不存在时创建目录
bool create_directories(const path& pval) :形如/a/b/c这样的,如果都不存在,创建目录结构
bool exists(const path& pval) :用于判断path是否存在
uintmax_t file_size(const path& pval) :返回目录的大小
file_time_type last_write_time(const path& pval) :返回目录最后修改日期的file_time_type对象
bool remove(const path& pval) :删除目录
uintmax_t remove_all(const path& pval) :递归删除目录下所有文件,返回被成功删除的文件个数
void rename(const path& from, const path& to) :移动文件或者重命名
作用:
1.可以替代遍历文件,更快速,更简单。
扩展static_assert用法,静态断言的显示文本可选。
static_assert(true, "");
static_assert(true);//c++17支持
扩展auto的推断范围。
在c++ 11 中,auto不能定义数组。现在c++ 17 可以。
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类型
扩展用法,允许出现在模板的模板的参数中。
回顾一下typename的用法,①用于模板中,表示模板参数为类型;
//第一种用法:声明模板参数为类型
template<typename T>
struct B { };
②用于声明某名字是变量名
struct A
{
typedef int Example;
};
int main()
{
typename A::Example e;//第二种用法:声明某名字为一种类型
return 0;
}
在模板的模板的参数中
template<template<typename T> typename X>
struct B
{
X<double> e;
B() { std::cout << "B Construct" << std::endl; }
};
非类型模板参数可传入类的静态成员
class MyClass
{
public:
static int a;
};
template<int *arg>
void foo() {}
int main()
{
foo<&MyClass::a>();
return 0;
}
对于C++17标准,if和switch语句有以下新形式:
对于初始语句定义的变量,在if-else及switch-case部分都可见,而在if和switch的外层作用域不可见,如此能够缩小变量作用域。
if(int i = 0; i == 0){
}
switch(int k = 10; k)
{
case 0:break;
case 1:break;
default:break;
}
在初始化对象时,可用花括号进行对其成员进行赋值。
具体见:CSDN
可以给枚举[类]对象赋值.
enum class Handle : unsigned int { value = 0 };
Handle h { 42 };
以0x前缀开头的十六进制数,以f后缀的单精度浮点数,合并,就有了十六进制的单精度浮点数
float value = 0x1111f;
谈到动态内存分配,少不了new和delete运算符,新标准中的new和delete运算符新增了按照对齐内存值来分配、释放内存空间的功能(即一个新的带对齐内存值的new、delete运算符重载)
void* operator new(std::size_t size, std::align_val_t alignment);
参数说明:
size —— 分配的字节数。必须为alignment的整数倍。
alignment —— 指定的对齐内存值。必须是实现支持的合法对齐。
new的返回值:
成功,返回指向新分配内存起始地址的指针。
[[ using CC: opt(1), debug ]] void f() {}
//作用相同于 [[ CC::opt(1), CC::debug ]] void f() {}
在类的继承体系中,构造函数的自动调用是一个令人头疼的问题。新特性引入继承与改写构造函数的用法。
#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;
}
当模板参数为非类型时,可用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;
}
30.fallthrough
该属性主要用于switch
语句中。在C++的switch语句中,如果当前case
分支中不加break
, 便会执行下一个case
分支的代码。
switch (n) {
case 1: {
cout << "work through one \n";
[[fallthrough]];
}
case 2: {
cout << "work through two \n";
[[fallthrough]];
}
case 3: {
cout << "work through three \n";
[[fallthrough]];
}
default: {
cout << "work through default \n";
}
}
31.nodiscard
nodiscard是c++17引入的一种标记符,其语法一般为[[nodiscard]]或[[nodiscard("string")]](c++20引入),含义可以理解为“不应舍弃”。nodiscard一般用于标记函数的返回值或者某个类,当使用某个弃值表达式而不是cast to void 来调用相关函数时,编译器会发出相关warning
[[nodiscard]] int func(){return 1;}; // C++17
[[nodiscard("nodiscard_func_1")]] int func_1(){return 2;}; // C++20
func(); // warning
func_1(); // warning
保存函数返回值,或者使用cast to void
[[nodiscard]] int func(){return 1;}; // C++17
int a = func(); // no warning
static_cast<void>(func()); // no warning
32.maybe_unused
可用于类、typedef、变量、非静态数据成员、函数、枚举或枚举值中。用于抑制编译器对没用实体的警告。即加上该属性后,对某一实体不会发出“没有用”的警告。
[[maybe_unused]] int C;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。