《深入理解C++11:C++11新特性解析与应用》笔记七

《深入理解C++11:C++11新特性解析与应用》笔记七_第1张图片

第七章 为改变思考方式而改变

7.1 指针空值--nullptr

7.1.1 指针空值:从0到NULL,再到nullptr

传统C头文件里NULL是一个宏定义:

《深入理解C++11:C++11新特性解析与应用》笔记七_第2张图片

在函数重载同时出现int和char *参数版本的函数时,使用NULL作为参数调用函数会调用int参数版本,而不是我们想要的char*参数版本。 引起该问的元凶是字面常量0的二义性,既可以是一个整型,也可以是一个无类型指针void*。除非对字面常量0进行强制类型转换b=并调用,否则编译器总是会优先把0看作是一个整型常量。

c++出于兼容性考虑,并没有消除字面常量0的二义性。但是引入了作为指针空值类型的常量nullptr,指针空值类型被命名为nullptr_t。nullptr是关键字,nullptr是有类型的,只能被隐式转化为指针类型,所以nullptr做参数可以成功调用char*版本的函数。

7.1.2 nullptr和nullptr_t

c++11标准不仅定义了指针空值常量nullptr,也定义了其指针空值类型nullptr_t。c++11标准严格规定了数据间的关系:

1.所有定义为nullptr_t类型的数据都是等价的,行为也是完全一致的。

2.nullptr_t类型数据可以隐式转换成任意一个指针类型。

3.nullptr_t类型数据不能转换为非指针类型。

4.nullptr_t类型数据不适用于算术运算表达式。

5.nullptr_t类型数据可以用于关系运算表达式,但仅能与nullptr_t类型数据或者指针类型数据进行比较,当且仅当关系运算符为==、<=、>=等时返回true。

7.1.3 一些关于nullptr规则的讨论

nullptr类型数据所占用的内存空间大小跟void*相同。

nullptr到任何指针的转换是隐式的,而(void*)0则必须经过类型转换后才能使用。

nullptr_t对象的地址可以被用户使用。虽然nullptr也是一个nullptr_t的对象,但它被定义为一个右值常量,因此用户不能获得nullptr的地址。

7.2 默认函数的控制

7.2.1 类与默认函数

在c++中声明自定义的类,编译器会默认生成一些被称为默认函数的成员函数,包括:

构造函数、拷贝构造函数、拷贝赋值函数(operator=)、移动构造函数、移动拷贝函数、析构函数。

c++编译器还会为以下这些自定义类型提供全局默认操作符函数:

operator ,,operator &,operator &&,operator *,operator ->,operator ->*,operator new,operator delete。

一旦实现了这些函数的自定义版本,则编译器不会再为该类自动生成默认版本。声明了带参的构造函数版本,必须声明不带参版本以完成无参的变量初始化。声明了自定义版本的构造函数,有可能导致自定义类型不再是POD的。

C++11中,可以在默认函数定义或者声明时加上"= default"来显式地指示编译器生成该函数的默认版本。另一方面,有时候又希望可以限制一些默认函数的生成,譬如有时候需要禁止使用拷贝构造函数,c++11中,可以在默认函数定义或者声明时加上“= delete”来显式地指示编译器不生成函数的缺省版本。

7.2.2 “= default”与“= deleted”

c++11中,也可以在类定义外显式指定缺省版本,这样可以对一个class定义提供多个实现版本,通过选择性编译,从而在提供缺省函数和自定义版本间切换。

c++11并不要求编译器为“operator ==”之类的函数提供缺省实现,但是将其声明为显式缺省的话,编译器会按照某些标准行为为其生成所需的版本。

显式删除可以避免用户使用一些不应该使用的类的成员函数,也可以避免编译器做一些不必要的隐式数据类型转换,例如:

《深入理解C++11:C++11新特性解析与应用》笔记七_第3张图片

隐式删除不应该和explicit同用。在下面的例子中:

《深入理解C++11:C++11新特性解析与应用》笔记七_第4张图片

显式删除导致显式构造的cc变量编译出错,但是Func调用中,编译器会尝试隐式地将char转为int,从而调用一次ConvType(int)构造函数,因而能通过编译。

显式删除并不局限于缺省版本的类成员函数或者全局函数上,对于普通函数,依然可以通过显式删除来禁止类型转换。

7.3 lambda函数

7.3.1 lambda的一些历史

7.3.2 c++11中的lambda函数

lambda函数的语法定义:

捕捉列表由多个捕捉项组成,以逗号分割,有如下形式:

[var]表示值传递方式捕捉变量var,[=]表示值传递方式捕捉所有父作用域的变量,[&var]表示引用传递捕捉变量var。[&]表示引用捕捉所有父作用域的变量。[this]表示值传递方式捕捉当前this指针。还可以进行组合,例如[=,&a,&b]表示引用传递方式捕捉a和b,其他变量以值传递方式捕捉。

默认情况下lambda函数总是一个const函数,mutable可以取消其常量性,使用时参数列表不可省略。

不需要返回值的时候也可以连同符号->一起省略。

c++11标准规定在块作用域以外的lambda函数捕捉列表必须为空,而在块作用域中的lambda函数仅能捕捉父作用域中的自动变量。

7.3.3 lambda与仿函数

在c++11之前,我们在STL中会用到一种特别的对象,称之为函数对象,或者仿函数functor,也就是重定义了成员函数operator()的一种自定义类型对象。在使用它的时候,在代码层面跟函数的使用一样,但本质却是一种对象。仿函数是编译器实现lambda的一种方式。现阶段,通常编译器会把lambda函数转化为一个仿函数对象。

《深入理解C++11:C++11新特性解析与应用》笔记七_第5张图片

7.3.4 lambda的基础使用

lambda可用于实现局部函数。

7.3.5 关于lambda的一些问题及有趣的实验

如果需要捕捉的值称为lambda函数的常量,通常需要使用按值传递的方式捕捉。反之,需要捕捉的值称为lambda函数运行时的变量,则应采用按引用方式进行捕捉。

lambda的类型并非简单的函数指针或者自定义类型,lambda的类型被定义为闭包的类,每个lambda表达式会产生一个闭包类型的临时对象(右值)。不过c++标准允许lambda表达式向函数指针的转换,前提是函数没有捕捉任何变量,且函数指针所示的函数原型,必须跟lambda函数有这相同的调用方式。

lambda函数的常量性及mutable关键字。现有c++11标准中lambda等价的是有常量成员函数operator()的仿函数,捕捉列表中的变量都会成为等价仿函数的成员变量,而常量成员函数中改变其值是不允许的,因而在按值捕捉的变量在没有声明为mutable的lambda函数中改变其会导致编译器报错。可以通过mutable修饰符消除其常量性,不过更推荐使用引用捕捉。

7.3.6 lambda与STL

当循环次数较多时,内联的lambda函数比函数指针性能好。函数指针应用范围相对狭小,特别是需要具备一些运行时才能决定的状态时,以前或许会使用仿函数,现在则可以选用lambda。

7.3.7 更多的一些关于lambda的讨论

在现有c++11中,lambda不是仿函数的完全替代者,这点很大程度上是由lambda的捕捉列表的限制造成的。仿函数可以被定义以后在不同的作用域范围内取得初始值,这使得仿函数天生具有跨作用域共享的特征。lambda函数被设计的目的,就是要就地书写,就地使用。

你可能感兴趣的:(c++,笔记,开发语言)