《现代C++语言核心特性解析》笔记草稿

仅供学习记录之用,谢绝转发
在这里插入图片描述

第1章 新基础类型(C++11~C++20)

1.1 整数类型long long

更多笔记
“在C++中应该尽量少使用宏,用模板取而代之是明智的选择。C++标准中对标准库头文件做了扩展,特化了long long和unsigned long long版本的numeric_ limits类模板。这使我们能够更便捷地获取这些类型的最大值和最小值”。

1.2 新字符类型char16_t和char32_t

更多笔记

1.2.1 字符集和编码方法

1.2.2 使用新字符类型char16_t和char32_t

“char utf8c = u8’a’在C++11标准中实际上是无法编译成功的,因为在C++11标准中u8只能作为字符串字面量的前缀,而无法作为字符的前缀。这个问题直到C++17标准才得以解决”。

“char utf8c = u8’好’是无法通过编译的,因为存储“好”需要3字节,显然utf8c只能存储1字节,所以会编译失败。”

1.2.3 wchar_t存在的问题

1.2.4 新字符串连接

1.2.5 库对新字符类型的支持

“C11在中增加了4个字符的转换函数,在C++17标准中已经不被推荐使用了,所以应该尽量避免使用它们。”

1.3 char8_t字符类型 【c++20】

“C++20标准新引入的类型char8_t可以解决以上问题,它可以代替char作为UTF-8的字符类型。char8_t具有和unsigned char相同的符号属性、存储大小、对齐方式以及整数转换等级。引入char8_t类型后,在C++17环境下可以编译的UTF-8字符相关的代码会出现问题”

1.4 总结

第2章 内联和嵌套命名空间(C++11~C++20)

2.1 内联命名空间的定义和使用

更多笔记

2.2 嵌套命名空间的简化语法 【c++17】

C++17标准允许使用一种更简洁的形式描述嵌套命名空间:
“在C++20中,我们可以这样定义内联命名空间:
在这里插入图片描述

2.3 总结

第3章 auto占位符(C++11~C++17)

3.1 重新定义的auto关键字
“在C++11中静态成员变量是可以用auto声明并且初始化的,不过前提是auto必须使用const限定符”

“遗憾的是,const限定符会导致i常量化,显然这不是我们想要的结果。幸运的是,在C++17标准中,对于静态成员变量,auto可以在没有const的情况下使用“。

“按照C++20之前的标准,无法在函数形参列表中使用auto声明形参(注意,在C++14中,auto可以为lambda表达式声明形参)”

3.2 推导规则

“1.如果auto声明的变量是按值初始化,则推导出的类型会忽略cv限定符。”
“2.使用auto声明变量初始化时,目标对象如果是引用,则引用属性会被忽略”
“3.使用auto和万能引用声明变量时(见第6章),对于左值会将auto推导为引用类型”
“4.使用auto声明变量,如果目标对象是一个数组或者函数,则auto会被推导为对应的指针类型”
“5.当auto关键字与列表初始化组合时,这里的规则有新老两个版本,这里只介绍新规则(C++17标准)。
(1)直接使用列表初始化,列表中必须为单元素,否则无法编译,auto类型被推导为单元素的类型。
(2)用等号加列表初始化,列表中可以包含单个或者多个元素,auto类型被推导为std::initializer_list,其中T是元素类型。请注意,在列表中包含多个元素的时候,元素的类型必须相同,否则编译器会报错”

3.3 什么时候使用auto

简单归纳auto的使用规则。
1.当一眼就能看出声明变量的初始化类型的时候可以使用auto。
2.对于复杂的类型,例如lambda表达式、bind等直接使用auto。”

3.4 返回类型推导【c++14】

“C++14标准支持对返回类型声明为auto的推导,例如:”
在这里插入图片描述

3.5 lambda表达式中使用auto类型推导

“在C++14标准中我们还可以把auto写到lambda表达式的形参中,这样就得到了一个泛型的lambda表达式”

“起初在后置返回类型中使用auto是不允许的,但是后来人们发现,这是唯一让lambda表达式通过推导返回引用类型的方法了”

3.6 非类型模板形参占位符【c++17】

“C++17标准对auto关键字又一次进行了扩展,使它可以作为非类型模板形参的占位符。”

3.7 总结

第4章 decltype说明符(C++11~C++17)

4.1 回顾typeof和typeid

“在C++11标准发布以前,GCC的扩展提供了一个名为typeof的运算符。通过该运算符可以获取操作数的具体类型。”

“C++标准还提供了一个typeid运算符来获取与目标操作数类型有关的信息。获取的类型信息会包含在一个类型为std::type_info的对象里。我们可以调用成员函数name获取其类型名,成员函数name返回的类型名在C++标准中并没有明确的规范,所以输出的类型名会因编译器而异。”
在这里插入图片描述“另外,还有3点也需要注意。”
“1.typeid的返回值是一个左值,且其生命周期一直被扩展到程序生命周期结束。”

“2.typeid返回的std::type_info删除了复制构造函数,若想保存std::type_info,只能获取其引用或者指针”

“3.typeid的返回值总是忽略类型的 cv 限定符,也就是typeid(const T)== typeid(T))。”

“虽然typeid可以获取类型信息并帮助我们判断类型之间的关系,但遗憾的是,它并不能像typeof那样在编译期就确定对象类型。”

4.2 使用decltype说明符

“C++11标准引入了decltype说明符,使用decltype说明符可以获取对象或者表达式的类型,其语法与typeof类似”

“auto是返回类型的占位符,参数类型分别是T1和T2,我们利用decltype说明符能推断表达式的类型特性,在函数尾部对auto的类型进行说明,如此一来,在实例化sum函数的时候,编译器就能够知道sum的返回类型了。”

“上述用法只推荐在C++11标准的编译环境中使用,因为C++14标准已经支持对auto声明的返回类型进行推导了”。“auto作为返回类型的占位符还存在一些问题,下面的例子中,auto被推导为值类型,而不是需要的引用类型。
在这里插入图片描述“如果想正确地返回引用类型,则需要用到decltype说明符:
在这里插入图片描述

4.3 推导规则

“decltype(e)(其中e的类型为T)的推导规则有5条。

1.如果e是一个未加括号的标识符表达式(结构化绑定除外)或者未加括号的类成员访问,则decltype(e)推断出的类型是e的类型T。如果并不存在这样的类型,或者e是一组重载函数,则无法进行推导。

2.如果e是一个函数调用或者仿函数调用,那么decltype(e)推断出的类型是其返回值的类型。

3.如果e是一个类型为T的左值,则decltype(e)是T&。

4.如果e是一个类型为T的将亡值,则decltype(e)是T&&。

5.除去以上情况,则decltype(e)是T。”

4.4 cv限定符的推导

“通常情况下,decltype(e)所推导的类型会同步e的cv限定符”

“当e是未加括号的成员变量时,父对象表达式的cv限定符会被忽略,不能同步到推导结果。当e是加括号的数据成员时,父对象表达式的cv限定符会同步到推断结果。”

4.5 decltype(auto) 【c++14】

“在C++14标准中出现了decltype和auto两个关键字的结合体:decltype(auto)。它的作用简单来说,就是告诉编译器用decltype的推导表达式规则来推导auto。另外需要注意的是,decltype(auto)必须单独声明,也就是它不能结合指针、引用以及cv限定符。”

“使用decltype(auto)消除返回类型后置的语法”
在这里插入图片描述

4.6 decltype(auto)作为非类型模板形参占位符 【c++17】

“在C++17标准中decltype(auto)也能作为非类型模板形参的占位符,其推导规则和上面介绍的保持一致”
在这里插入图片描述

4.7 总结

第5章 函数返回类型后置(C++11)

5.1 使用函数返回类型后置声明函数

“使用传统函数声明语法的foo1无法将函数指针类型作为返回类型直接使用,所以需要使用typedef给函数指针类型创建别名bar,再使用别名作为函数foo1的返回类型。而使用函数返回类型后置语法的foo2则没有这个问题。同样,auto作为返回类型占位符,在->后声明返回的函数指针类型int(*)(int)即可。”
在这里插入图片描述

5.2 推导函数模板返回类型

“C++11标准中函数返回类型后置的作用之一是推导函数模板的返回类型,当然前提是需要用到auto和decltype说明符”
在这里插入图片描述

5.3 总结

第6章 右值引用(C++11 C++17 C++20)

6.1 左值和右值

在C++中所谓的左值一般是指一个指向特定内存的具有名称的值(具名对象),它有一个相对稳定的内存地址,并且有一段较长的生命周期。而右值则是不指向稳定内存地址的匿名值(不具名对象),它的生命周期很短,通常是暂时性的。基于这一特征,我们可以用取地址符&来判断左值和右值,能取到内存地址的值为左值,否则为右值。

x++是右值,因为在后置++操作中编译器首先会生成一份x值的临时复制,然后才对x递增,最后返回临时复制内容。而++x则不同,它是直接对x递增后马上返回其自身,所以++x是一个左值。

“对于值类型x,在函数返回的时候编译器并不会返回x本身,而是返回x的临时复制,此时返回的是右值。”

“通常字面量都是一个右值,但字符串字面量是左值,因为编译器会将字符串字面量存储到程序的数据段中,程序加载的时候也会为其开辟内存空间,所以我们可以使用取地址符&来获取字符串字面量的内存地址”

6.2 左值引用

“左值引用传参时,可以免去创建临时对象的操作”
“常量左值引用除了能引用左值,还能够引用右值”

“虽然在结果上const int &x = 11和const int x = 11是一样的,但是从语法上来说,前者是被引用了,所以语句结束后11的生命周期被延长,而后者当语句结束后右值11应该被销毁。”

6.3 右值引用

“右值引用是一种引用右值且只能引用右值的方法。右值引用是在类型后添加&&”

“右值引用的特点之一是可以延长右值的生命周期,从而减少对象复制,提升程序性能。”

6.4 右值的性能优化空间

“很多情况下右值都存储在临时对象中,当右值被使用之后程序会马上销毁对象并释放内存。这个过程可能会引发一个性能问题”
在这里插入图片描述“以上代码同样需要加上编译参数-fno-elide-constructors,编译运行程序会在屏幕上输出字符串:”
“copy big memory pool.
copy big memory pool.
copy big memory pool.”

“可以看到BigMemoryPool my_pool = make_pool();调用了3次复制构造函数。
1.get_pool返回的BigMemoryPool临时对象调用复制构造函数复制了pool对象。
2.make_pool返回的BigMemoryPool临时对象调用复制构造函数复制了get_pool返回的临时对象。
3.main函数中my_pool调用其复制构造函数复制make_pool返回的临时对象。”

6.5 移动语义

“如果有办法能将临时对象的内存直接转移到my_pool对象中,不就能消除内存复制对性能的消耗吗?好消息是在C++11标准中引入了移动语义,它可以帮助我们将临时对象的内存移动到my_pool对象中,以避免内存数据的复制。”
在这里插入图片描述
“在上面的代码中增加了一个类BigMemoryPool的构造函数BigMemoryPool (BigMemoryPool&& other),它的形参是一个右值引用类型,称为移动构造函数。它接受的是一个右值,其核心思想是通过转移实参对象的数据以达成构造目标对象的目的,也就是说实参对象是会被修改的。在移动构造函数中没有了复制构造中的内存复制,取而代之的是简单的指针替换操作。它将实参对象的pool_赋值到当前对象,然后置空实参对象以保证实参对象析构的时候不会影响这片内存的生命周期。”

“编译运行这段代码,其输出结果如下:
copy big memory pool.
move big memory pool.
move big memory pool.

可以看到后面两次的构造函数变成了移动构造函数,因为这两次操作中源对象都是右值(临时对象),对于右值编译器会优先选择使用移动构造函数去构造目标对象。当移动构造函数不存在的时候才会退而求其次地使用复制构造函数。在移动构造函数中使用了指针转移的方式构造目标对象,所以整个程序的运行效率得到大幅提升。”

“除移动构造函数能实现移动语义以外,移动赋值运算符函数也能完成移动操作,继续以BigMemoryPool为例,在这个类中添加移动赋值运算符函数:”
在这里插入图片描述
这段代码编译运行的结果是:
copy big memory pool.
move big memory pool.
move(operator=) big memory pool.”

“可以看到赋值操作my_pool = make_pool()调用了移动赋值运算符函数,这里的规则和构造函数一样,即编译器对于赋值源对象是右值的情况会优先调用移动赋值运算符函数,如果该函数不存在,则调用复制赋值运算符函数”

最后有两点需要说明一下。
1.同复制构造函数一样,编译器在一些条件下会生成一份移动构造函数,这些条件包括:没有任何的复制函数,包括复制构造函数和复制赋值函数;没有任何的移动函数,包括移动构造函数和移动赋值函数;也没有析构函数。编译器生成的移动构造函数和复制构造函数并没有什么区别。
2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值