学习C++14的那些新特性
为了方便指定使用C++14来编译代码,本文的测试都是在linux下进行的,g++版本如下
如果你和我一样,也是使用VSC来链接linux进行代码编写,那一定要记得修改C++插件里面的CPP版本,否则默认以C++11来进行语法高亮的话,会把C++11不支持的语法标红,影响我们学习
本文参考 https://zhuanlan.zhihu.com/p/588826142 进行学习;
官方文档 https://zh.cppreference.com/w/cpp/14
1.lambda新特性
C++14给lambda表达式添加了两个新功能
- 参数推断(auto)
- 参数初始化后捕获(可以在
[]
对某个新参数进行赋值)
先来复习一下C++11中学习的lambda捕获的基本方式
在C++14中,新增的是下面的这种情况
运行测试,可以看到成功输出了结果
修改一下类型,也能正常调用
如果想将赋值参数和原本的捕获方式一起使用,则需要将赋值参数放在[]
的最后面
初始化捕获的好处是可以支持移动捕获了;不然在C++11中,lambda就只能使用赋值捕获和引用捕获
这个新特性的提出,也让lambda成功有了和bind比拼的能力。在C++11中,bind的优势就是在于移动捕获的支持;如今lambda也有了这份能力了,我们可以更灵活地根据场景选用lambda或者bind,而不是只能使用bind了。
2.变量模板
2.1 示例
看清楚这个名字啊!是变量模板,可不是什么函数模板哈!
如上就是一个最最最简单的变量模板,我们在传入对应的类型后,他就会转成我们需要的类型
2.2 类中使用
当你需要在类中使用模板变量的时候,这个变量必须定义为static
;
因为它是模板,我们还可以接用模板本身就有的特性,将这个模板针对某一个类型进行特化
2.3 和类型转换的区别
这里我又直接定义了一个变量,使用static_cast
直接转换变量,看看结果会不会有什么区别
看上去二者的结果完全相同,那么既然可以直接使用变量类型转换,为什么还要新增一个模板变量呢?
以下内容来自GPT,我觉得它说的很对
定义一个变量并使用数据转换(类型转换)是一种常见的编程方式,但与变量模板有一些区别:
- 通用性: 变量模板允许你通过模板参数来生成多个不同类型的变量,从而在不同的上下文中使用。这使得代码更具通用性和可扩展性,因为你可以为多个类型生成相应的变量。相比之下,直接定义变量并使用数据转换通常只适用于特定的一种数据类型。
- 模板化: 变量模板是一种模板化的方式来生成变量,它遵循 C++ 的模板机制,这意味着你可以使用模板特化、部分特化等技术来定制化生成的变量,以满足不同的需求。而使用数据转换时,你必须显式地执行类型转换,这可能会在代码中引入不必要的重复。
- 编译时计算: 变量模板通常用于在编译时生成值,因此可以在编译阶段进行类型检查和计算。这有助于提高代码的性能和安全性。而数据转换可能在运行时进行,可能会引入一些运行时开销和类型错误的风险。
- 抽象性: 变量模板可以在更高的抽象层次上操作数据,使代码更具表达力和可读性。它允许你以更自然的方式描述某个值与特定类型之间的关系,而不必显式进行类型转换。
总之,变量模板提供了一种更灵活、通用和模板化的方式来生成变量,适用于需要在不同类型上工作的情况。当你需要为多个类型生成特定的变量或值时,变量模板是一种更优雅和强大的选择。
3.constexpr限制放宽
在C++11中被引入的constexpr,可以让编译器在编译程序的期间,就将一部分工作完成,不必等到运行期间再做;在C++11中,constexpr的限制很严格,这导致它并不好用:
- constexpr修饰变量,要求变量必须可以在编译器推导出来
- constexpr修饰函数(返回值),函数内除了可以包含using和typedef指令以及
static_asssert
断言外,只能包含一条return
语句 - constexpr同时可以修饰构造函数,但也会要求使用这个构造函数的时候,可以在编译器就把相关的内容全推导出来
以下是一个比较基础的C++11中的用例,给该函数设置了constexpr
关键字后,该函数就可以在编译期间被计算出结果,再用static_assert
在编译期间断言结果是否正确;
如果在C++11中的constexpr函数内包含其他语句,编译的时候会报错,翻译过来是该函数内部不是一个return返回语句
c++14中,对constexpr的限制放宽了,允许使用循环、if、switch等等语句,但是主旨还是一样的,需要在编译期间就可以计算出全部内容;限制放宽之后,这个关键字便可以更灵活的使用了。
4.二进制变量
可以使用0b
或者0B
开头直接定义二进制变量。
运行结果如下
我在测试中发现,当我用C++11编译此代码的时候,似乎也没有引发编译错误,难道说0b是在C++11里面就支持了吗?
GPT给出了0B这种二进制变量是在C++14中引入的确认,并提到了为什么会出现上述情况;虽然C++11看上去编译和运行都没有问题,但我们还是得遵循版本,选用正确的版本进行编译,才能根本上避免错误
C++标准通常是向后兼容的,这意味着较新版本的编译器通常会继续支持较旧版本的标准。例如,如果你在使用支持C++11标准的编译器(如g++)时,使用了C++14或更高版本的特性,通常不会引发编译错误,因为这些编译器会尽量向后兼容,以保持现有代码的可编译性。
在你提到的情况下,即使你使用g++编译器以C++11标准编译,它仍然可以理解和接受C++14引入的二进制字面量特性。这是编译器开发者的一种设计选择,以便使代码的迁移更加平滑。但是,为了遵循最佳实践和保持代码的可读性,当你在使用特定C++标准的功能时,最好将编译器选项设置为该标准的版本,以确保代码的可移植性。
5.数字分隔符
在日常生活中使用数字的时候,为了更好的可读性,我们会以3个数组或者4个数组为分割,打一个点
C++14中,也支持了这样的打点,以方便我们更好的看出大数字的位数
需要注意,这样的操作不会对数字本身有任何影响
在C++11中这种语法是不支持的
6.返回值auto推导
c++14新增了函数返回值的推导,当返回值声明为auto时,编译器会根据你的return语句推导出你的返回值类型。
这个推导是有限制条件的
1、如果有多个推导语句,那么多个推导的结果必须一致
2、如果没有return或者return为void类型,那么auto会被推导为void。
3、一旦在函数中看到return语句,从该语句推导出的返回类型就可以在函数的其余部分中使用,包括在其他return语句中。
但是如果还没被推导出来,那就不能使用。
4、不能推导初始化列表。
5、虚函数不能使用返回值推导
7.[[deprecated]]
标记
这个标记的作用是告知其他人,某个函数被弃用了,不允许继续调用该函数;该字段的好处在于,如果一个方法已经在后续不需要使用了,你可以先给他加上这个关键字,然后再进行其他的代码检查,确认无误后,再将这个函数整体清除;
别人也不需要去检查函数的实现,因为在编译过程中编译器就会告诉你这个函数被弃用;但是编译依旧是成功的!
在编译的时候,编译器会警告你,这个函数已经被弃用了;但这里只是警告,编译依旧成功了,所以最终还是需要程序猿去瞅一眼各个警告到底是什么意思。
std库的新特性
以下是STD库的新增内容!
8.std::make_unique
这个东西在cplusplus网站上找不到释义,所以就去 cpp的官网上找了
https://zh.cppreference.com/w/cpp/memory/unique_ptr/make_unique
该函数定义在<memory>
头文件中
作用是构造 T
类型对象并将其包装进 std::unique_ptr;
参数 | 说明 |
args | 将要构造的 |
size | 要构造的数组大小 |
- 构造非数组类型
T
对象。传递参数args
给T
的构造函数。此重载只有在T
不是数组类型时才会参与重载决议。函数等价于:
- 构造拥有动态大小的数组。 值初始化数组元素。此重载只有在
T
是未知边界数组时才会参与重载决议。函数等价于:
使用示例
9.std::shared_timed_mutex与std::shared_lock
c++11引入了多线程线程的一些库,但是是没有读写锁的,因此在c++14引入了读写锁的相关实现(头文件shared_mutex),其实c++14读写锁也还不够完善,直到c++17读写锁这块才算是完备起来。
std::shared_timed_mutex
是带超时的读写锁对象,接口还算比较简洁易懂,和之前接触过的其他锁基本一致;内部成员中lock()
是写锁,lock_shared()
是读锁;
https://zh.cppreference.com/w/cpp/thread/shared_timed_mutex
std::shared_lock
是加锁的RAII实现,即构造时加锁,析构时解锁;我们使用shared_lock/unique_lock
来从shared_timed_mutex
中获取锁的时候,就会自动获取读锁和写锁;
10.std::exchange
c++14新增了一个接口std::exchange
(头文件utility),其实这个也并不算是新增的,因为这个接口其实在c++11的时候就有了,只不过在c++11中作为一个内部函数,不暴露给用户使用,在c++14中才把它暴露出来给用户使用。使用方法也很简单。
我们可以看到,exchange会把第二个值赋值给第一个值,但是不会改变第二个值。我们来看下它的实现吧。
通过注释我们可以明白含义,它的作用是把第二个值赋值给第一个值,同时返回第一个值的旧值。
除此之外,我们这里说明一个关键的点。exchange的第二个参数是万能引用,所以说他是既可以接收左值,也可以接收右值的,所以我们可以这样来使用。
11.std::integer_sequence
类模板 std::integer_sequence
表示一个编译时的整数序列。在用作 函数模板的实参时,能推导 参数包 Ints
并将它用于包展开。
https://zh.cppreference.com/w/cpp/utility/integer_sequence
这个实在是太难懂了,搞不明白是干嘛的,放弃了😥
12.std::quoted
https://zh.cppreference.com/w/cpp/io/manip/quoted
该函数模板位于 <iomanip>
头文件中,用于在输入输出流中处理被引号包围的字符串。它通常用于处理 CSV(逗号分隔值)文件或其他格式,其中字段被引号括起来以处理包含特殊字符(如逗号、换行符等)的情况。
对于cout
而言,quoted会将字符串包围在双引号中输出
以下是官方给的一个示例
输出
在给定的代码中,delim
和 escape
是用于指定自定义的分隔符和转义字符的参数。这些参数是用于 std::quoted
函数的重载形式,允许你指定不同于默认引号的字符来包围字符串,并指定一个不同于默认转义字符的字符来转义引号字符。以下是关于这两个参数的详细解释:
-
delim
: 分隔符 在第一个用法中,std::quoted
函数使用了三个参数的重载形式:std::quoted(in, delim, escape)
。delim
参数用于指定包围字符串的分隔符。通常情况下,std::quoted
使用双引号作为默认分隔符,但在某些情况下,你可能想要使用其他字符来包围字符串,以避免与字符串本身的字符冲突。在你的代码示例中,分隔符delim
被设置为$
,这意味着字符串会被包围在$
字符内。 -
escape
: 转义字符escape
参数允许你指定一个字符,用于转义分隔符字符本身。在默认情况下,std::quoted
使用双引号"
作为转义字符,以确保在字符串中嵌入的引号不会被解释为结束引号。但如果你选择了自定义的分隔符,你可能还需要指定一个不同于默认转义字符的字符来进行转义。在你的代码示例中,转义字符escape
被设置为%
,这意味着在字符串中,如果你想要表示分隔符$
本身,你需要使用%$
。
这部分也不是很容易搞明白它是干嘛的,如果面试官问道了就说我不会吧😭