C++之C++新特性
目录
- C++11
- auto
- for_each
- 就地初始化
- 非静态成员的sizeof
- final
- override
- initialize_list
- 提高类型安全
- C++14
- C++17
- 基本语言特性
- 结构化绑定
- 新属性和属性特性
- [[nodiscard]]
- [[maybe_unused]]
- [[fallthrough]]
- 模板特性
- 新的标准库组件
- std::optional<>
- std::variant<>
- std::any #include<any>
- std::byte #include <cstddef>
- 文件系统库
C++11
连续两个右尖括号>中间空格隔开,C++98会优先解析为右移,C++11会优先解析为模板参数界定符
auto
- auto声明变量的类型必须由编译器在编译时期推导而得;auto声明的变量必须被初始化;优势是可以匹配返回类型,因为有时候不确定返回类型,就会声明错误
- 自动类型推导的一种新写法:
auto func(T params) -> type
;可以配合decltype关键字:template<typename U, typename V>auto add(U a, V b) -> decltype(a+b)
for_each
- C++11中新增long long类型(其实在之前就支持了),标准要求long long类型可以在不同的平台上有不同的长度,但至少64位
就地初始化
只能使用"=“或”{}"进行初始化;作用先于列表初始化;如果既有列表初始化又有就地初始化,最终值取决于列表初始化
非静态成员的sizeof
在C++98中对非静态成员变量使用sizeof是不能通过编译的,需要对象的实例才能对其成员进行sizeof操作;C++11可以——有什么用?
struct People{ int hand; static People *all; }
People p;
cout << sizeof(p.hand) << endl; // 都支持
cout << sizeof(People::all) << endl; // 都支持
cout << sizeof(People::hand) << endl; // C++11才支持
final
- 禁止继承:将类标记为final,方法是直接在类名后使用关键字final,这样继承该类将导致编译出错
- 禁止重写:在成员函数的参数列表后使用final关键字,使派生类不可覆盖它所修饰的虚函数(防止重载和重写)
override
虚函数描述符,如果派生类在虚函数声明时使用了override,该函数必须重写其基类中的同名函数,用在子类重写的函数后面!!声明和定义在一起的时候不能使用?
initialize_list
- 标准库中容器对初始化列表的支持源自
#include<initializer_list>
这个头文件中initialize_list类模板的支持 - 自定义类型使用列表初始化需要包含该头文件,并且声明一个initialize_list模板类为参数的构造函数
- 函数的参数列表也可以使用初始化列表,即以initializer_list作为模板参数
- 初始化列表可以用于函数返回的情况。返回一个初始化列表通常会导致构造一个临时变量,当然列表初始化构造成什么类型依据返回类
- 使用初始化列表的一大优势防止类型收窄——类型收窄一般是一些使得数据变化或者精度丢失的隐式类型转换
提高类型安全
- 强类型枚举,在enum后加上关键字class,具有以下优势:
强作用域:强类型枚举成员的名称不会被输出到其父作用域空间
转换限制:不可与整型隐式的相互转换
可以指定顶层类型:具体方法是在枚举名称后面加上“:type”,如 enum class Type: char { … }
匿名的enum class可能什么都做不了 - nullptr: 是一个“指针空值类型”的常量,指针空值类型被命名为 nullptr_t,所以可以通过nullptr_t来声明一个指针空值类型的变量(使用nullptr_t的头文件
#include<cstddef>
)
所有定义为nullptr_t类型的数据都是等价的,行为也是完全一致
nullptr_t类型数据可以隐式转换为任意一个指针类型
nullptr_t类型数据不能转换为非指针类型,即使是使用reinterpret_cast<nullptr_t>()的方式
nullptr_t类型数据不适用于算术运算表达式
C++14
前缀0b允许定义二进制字面量
数字分隔符可以增强数字字面量的可读性
变量模板
C++17
基本语言特性
结构化绑定
概念:允许用一个对象的元素或成员同时实例化多个实体,形如:
void struct_bind() {
// 绑定到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;
使用修饰符
结构化绑定适用的场景
a. 对于所有非静态成员都是public的结构体和类,可以把每个成员绑定到一个新的变量名上
b. 对于原生数组,可以把数组的每个元素绑定到新的变量名上
c. 对于任何类型,可以使用tuple-like API来绑定新的名称,std::tuple, std::pair, std::array
// 带初始化的if和switch语句
int b = 20;
if(int a = 10; b != a) {
cout << a << endl;
return;
}
if语句的条件表达式里定义的变量将在整个if语句中有效
可以在加锁的地方使用
内联变量
可以在头文件中以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 };
C++17中聚合体的定义:
是一个数组,或者,
满足如下条件的类类型:
新属性和属性特性
[[nodiscard]]
鼓励编译器在某个函数的返回值未被使用时给出警告,但并不意味着编译器必须这么做
[[maybe_unused]]
可以避免编译器在某个变量未被使用时发出警告。新的属性可以应用于类的声明,使用typedef,using定义的类型,变量等场景
void foo(int val, [[maybe_unused]] std::string str) {
...
}
class MyClass {
char c;
int i;
[[maybe_unused]] char xxx[32];
};
注意:不能在一条语句上应用[[maybe_unused]]
[[fallthrough]]
可以避免编译器在switch语句中某一个标签缺少break语句时发出警告
模板特性
constexpr if: 编译器if语句
新的标准库组件
std::optional<>
头文件:#include<optional>
, 模拟了一个可以为空的任意类型的实例,可以被用作成员,参数,返回值等
同时定义了以下:std::nullopt
, 从 std::exception
派生的 std::bad_optional_access
异常类,当无值的时候访问值将会出现异常。可选对象也使用了头文件中定义的 std::in_place
对象来初始化多个参数的可选对象
操作:
• 构造/析构/=
• make_optional<>():创建一个用参数初始化的可选对象
std::variant<>
头文件:#include <variant>
说明:提供了一个新的联合类型,最大的优势是提供了一种新的具有多态性的处理异质集合的方法,也就是说可以处理不同类型的数据,并且不需要公共基类和指针
如果第一个类型没有默认构造函数,那么调用variant的默认构造函数将会导致编译期错误,为支持第一个参数没有默认构造的情况,C++标准库提供了std::monostate,可以作为第一个选项来保证variant能默认构造
可以从variant派生
std::any #include
说明:是一种在保证类型安全的基础上还能改变自身类型的值类型。也就是说,它可以持有任意类型的值,并且它知道当前持有的值是什么类型
std::any对象同时包含了值和值的类型。
为了将当前值转换为真实的类型,必须要使用any_cast<>,如果转换失败,可能是因为对象为空或者与内部值的类型不匹配,会抛出一个std::bad_any_cast
异常。
拷贝std::any的开销一般都很大,推荐以引用传递对象,或者move值,std::any支持部分move语义
std::byte #include
这个类型代表内存的最小单位,std::byte本质上代表一个字节的值,但不能进行数字或字符的操作,也不对每一位进行解释
注意:std::byte实现和unsigned char类似,不能保证8位;底层实现的类型是unsigned char,所以大小总为1
列表初始化是唯一可以直接初始化std::byte对象的方法
没有定义输入和输出运算符,因此不得不把它转换为整数类型再进行IO
文件系统库
#include, 命名空间:std::filesystem,一个很常见的操作时定义:namespace fs = std::filesystem
异常:std::filesystem::filesystem_error