目录
- 基本语言特性
- 结构化绑定
- if和switch初始化器
- std::string_view
- 属性
- [[nodiscard]]
- [[maybe_unused]]
- [[fallthrough]]
- 模板特性
- 新的标准库组件
- std::optional<>
- std::variant<>
- std::any
- std::byte
- std::as_const
- 文件系统库
- 零星新特性
基本语言特性
结构化绑定
概念:允许用一个对象的元素或成员同时实例化多个实体,形如:
绑定到map这个对象
map<int, string> m;
m.insert({1, "hello"});
m.insert({2, "world"});
m.insert({3, "good"});
for(const auto& [key, val]: m) {
std::cout << key << ": " << val << "\n";
}
绑定到struct对象
struct MyStruct {
int i = 0;
std::string s;
};
MyStruct ms;
auto [u, v] = ms;
为了理解结构化绑定,必须意识到这里面其实有一个隐藏的匿名对象,结构化绑定时新引入的局部变量名其实都指向这个匿名对象的成员
auto [u, v] = ms;
auto e = ms;
aliasname u = e.i;
aliasname v = e.s;
使用修饰符
结构化绑定适用的场景
- 对于所有非静态成员都是public的结构体和类,可以把每个成员绑定到一个新的变量名上
- 对于原生数组,可以把数组的每个元素绑定到新的变量名上
- 对于任何类型,可以使用tuple-like API来绑定新的名称,std::tuple, std::pair, std::array
注意:
- 必须为结构化绑定使用auto关键字,例如,不能用int代替auto
- 使用结构化绑定声明的变量数量必须与右侧表达式中的值数量匹配
- 通常使用auto& 或者const auto&代替auto
if和switch初始化器
C++允许在if语句中包括一个初始化器,语法如下:
if(<initializer>; <conditional_expression>) { ... }
switch(<initializer>; <expression>) { ... }
if语句的条件表达式<initializer>
里定义的变量将在整个if语句中有效,此类变量在if语句之外不可用
// 带初始化的if和switch语句
int b = 20;
if(int a = 10; b != a) {
cout << a << endl;
return;
}
可以在加锁的地方使用
内联变量
可以在头文件中以inline的方式定义全局变量
内联变量产生的动机:C++不允许在类里初始化非常量静态成员,可以在类定义的外部定义并初始化非常量静态成员,但如果被多个cpp文件同时包含的话又会引发新的错误,根据一次性定义原则,一个变量或实体的定义只能出现在一个编译单元内,除非该变量或实体被定义为inline
对于静态成员,在C++17中constexpr修饰符现在隐含着inline
static constexpr int n = 5; // 等价于
inline static constexpr int n = 5;
// 聚合体
struct Data {
std::string name;
double value;
};
// 聚合体初始化
Data x = {"test1", 6.778};
// 在C++11中起可以忽略等号
Data x {"test1", 6.778};
// 自C++17起聚合体可以拥有基类
struct MoreData : Data {
bool done;
};
// 初始化时可以用如下两种方式:
MoreData y {{"test1", 6.778}, false };
MoreData y { "test1", 6.778, false };
std::string_view
头文件包含:#include<string_view>
应用场景:针对接收只读字符串的函数形参而言,如果是const char*的话,如果使用std::string,则必须调用其上的c_str()和data()来获取,但这样将失去std::string良好的面向对象的方面及其方法;如果改用std::string& 始终需要传入std::string。例如,传递一个字符串变量,编译器将默认创建一个临时字符串对象并将该对象传递给函数。所以有时需要重载多个版本,但这并不是一个很好的解决方案。
C++17中引入string_view解决了这类问题。
string_view基本就是const string&的简单替代品,但并不会复制字符串,不会产生开销。
属性
[[nodiscard]]
鼓励编译器在某个函数的返回值未被使用时给出警告,但并不意味着编译器必须这么做
[[nodiscard]] int func() { return 42; }
int main() { func(); }
以上代码编译器会发出告警
从C++20开始,可以以字符串的形式为[[nodiscard]]提供一个原因,例如:
[[nodiscard("Some explanation")]] int func();
[[maybe_unused]]
int func(int param1, int param2) { return 20; }
如果编译器告警级别设置的足够高,会报变量没有使用的警告。通过[[maybe_unused]]可以避免编译器在某个变量未被使用时发出警告。
void foo(int val, [[maybe_unused]] std::string str) {
...
}
class MyClass {
char c;
int i;
[[maybe_unused]] char xxx[32];
};
[[maybe_unused]]属性可用于类和结构体,非静态数据成员,联合,typedef,类型别名,变量,函数,枚举以及枚举值。
注意:不能在一条语句上应用[[maybe_unused]]。
这个写法可以代替C或者以前通过 (void)var; 这种方式避免未使用的变量报告警的方法。
[[fallthrough]]
可以避免编译器在switch语句中某一个标签缺少break语句时发出警告
模板特性
constexpr if: 编译器if语句
新的标准库组件
std::optional<>
头文件:#include<optional>
, 模拟了一个可以为空的任意类型的实例,可以被用作成员,参数,返回值等。如果想要允许值是可选的,则可以将optional用作函数的参数;如果函数可能返回也可能不返回某些内容,可以将optional用作函数的返回类型。这消除了从函数中返回”特殊“值的需要,如nullptr,end(), -1, EOF等。
同时定义了以下:
std::nullopt
,- 异常类
std::bad_optional_access
:派生自std::exception
,当无值的时候访问值将会出现异常。 std::in_place
:可选对象也使用了<utility>
头文件中定义的std::in_place
对象来初始化多个参数的可选对象
操作:
- 构造/析构/=
- make_optional<>():创建一个用参数初始化的可选对象
- has_value():判断一个optional是否有值,或者也可以简单的将optional用在if语句中。
- value() :如果optional有值,可以用value或者解引用运算符访问
optional<int> getData(bool qiveIt) {
if(qiveIt) { return 32; }
return nullopt;
}
int main() {
optional<int> data1 { getData(true) };
optional<int> data2 { getData(false) };
if(data1.has_value()) { std::cout << "data1: " << data1.value() << std::endl; }
if(data2.has_value()) { std::cout << "data2: " << data2.value() << std::endl; }
}
std::variant<>
头文件:#include <variant>
说明:提供了一个新的联合类型,最大的优势是提供了一种新的具有多态性的处理异质集合的方法,也就是说可以处理不同类型的数据,并且不需要公共基类和指针
如果第一个类型没有默认构造函数,那么调用variant的默认构造函数将会导致编译期错误,为支持第一个参数没有默认构造的情况,C++标准库提供了std::monostate,可以作为第一个选项来保证variant能默认构造
可以从variant派生
std::any
头文件:#include<any>
说明:是一种在保证类型安全的基础上还能改变自身类型的值类型。也就是说,它可以持有任意类型的值,并且它知道当前持有的值是什么类型
std::any对象同时包含了值和值的类型。
为了将当前值转换为真实的类型,必须要使用any_cast<>,如果转换失败,可能是因为对象为空或者与内部值的类型不匹配,会抛出一个std::bad_any_cast
异常。
拷贝std::any的开销一般都很大,推荐以引用传递对象,或者move值,std::any支持部分move语义
std::byte
头文件:#include <cstddef>
在C++17之前使用char或者unsigned char来表示一个字节,但这些类型使得像是在处理字符。C++17提供了std::byte。这个类型代表内存的最小单位,std::byte本质上代表一个字节的值,但不能进行数字或字符的操作,也不对每一位进行解释
注意:std::byte实现和unsigned char类似,不能保证8位;底层实现的类型是unsigned char,所以大小总为1
列表初始化是唯一可以直接初始化std::byte对象的方法
没有定义输入和输出运算符,因此不得不把它转换为整数类型再进行IO
std::as_const
include<utility>
,该方法接收一个引用参数,返回他的const引用版本。等价于 const_cast<const T&>(obj)
文件系统库
#include, 命名空间:std::filesystem,一个很常见的操作时定义:namespace fs = std::filesystem
异常:std::filesystem::filesystem_error
零星新特性
- 支持嵌套命名空间,如
namespace A {
namespace B {
namespace C {
}
}
}
可以写成:
namespace A::B::C {}
- std::size() 计算基于C风格数组的大小
int myArray[3] { 2 };
std::size(myArray);