C++11(六)nullptr 与 Lambda
原图
nullptr
nullptr、NULL 和 0
般情况下,NULL 是一个宏定义。在传统的 C 头文件( stddef.h)里我们可以找到如下代码
1 |
可以看到,NULL 可能被定义为字面常量 0,或者是定义为无类型指针(void*)常量。不过无论采用什么样的定义,我们在使用空值的指针时,都不可避免地会遇到一些麻烦。
1 | void func(int i) |
以上的代码中 func(NULL); 本意是调用指针函数,实际却调用了 int 函数,因为 NULL 被定义成了 0。虽然在前面加上 (void*) 强制转换能解决这个问题,但是某些编译器缺会在产生这种二义性时报错,多以对于代码移植来说会有一定限制。于是 C++11 中新增了 nullptr 来代表指针空值,以上问题就得到了解决:
1 | int main() |
nullptr 和 nullptr_t
同时,C++11 还定义了指针空值类型 nullptr_t,就是说指针空值并非只有 nullptr 一个实例,也可以通过 nullptr_t 来声明一个空指针类型的变量,虽然好像没什么用。另外,nullptr_t 也规定了一些规则:
- 所有定义为 nullptr_t 类型的数据都是等价的,行为也完全一致。
- nullptr_t 类型数据可以隐式转换成任意一个指针类型。
- nullptr_t 类型数据不能转换成非指针类型。
- nullptr_t 类型数据不适用于算术运算表达式。
- nullptr_t 类型数据可以用于关系运算表达式,但是只能和 nullptr_t 类型或者指针类型进行比较。
nullptr_t 类型还是比较有意思的,它的定义是从 nullptr 反推而来,从这点又能看出 C++11 用法的灵活性:
1 | typedef decltype(nullptr) nullptr_t; |
默认函数的控制
在 C++中声明自定义的类之后,编译器会默认生成一些成员函数,这些函数被称为默认函数。包括构造函数、拷贝构造函数、拷贝赋值构造函数、移动构造函数、移动拷贝函数、析构函数。另外,编译器还会默认生成一些操作符函数,包括 operator ,、operator &、operator &&、operator 、operator ->、operator ->、operator new、operator delete。
显式缺省函数 (= default)
但是如果实现了这些函数的自定义版本后,编译器就不会去生成默认的版本。有时候我们需要声明带参数的构造函数,此时就不会生成默认的构造函数,这样会导致类不再是 POD 类型,从而影响类的优化:
1 | class Example { |
C++11 中提供了新的机制来控制默认函数生成来避免这个问题,我们在声明时在函数末尾加上”= default”来显式地指示编译器去生成该函数的默认版本,这样就保证了类还是 POD 类型:
1 | class Example { |
显式删除函数 (= delete)
另一方面,有时候可能需要限制一些默认函数的生成,例如需要禁止拷贝构造函数的使用。原来,通过把拷贝构造函数声明为 private 成员,这样一旦使用编译器就会报错。而在 C++11 中,在函数的定义或者声明后面加上”= delete”就能实现这个效果:
1 | class Example { |
其他用法
= default 和= delete 能够更加精准的控制类的默认函数版本。其中,= default 同样也能在类外的定义中修饰成员函数:
1 | class Example { |
这样的好处是,能够为一个类实现多个版本,只要我们在头文件里声明同样的函数,而在不同的 cpp 文件中用不同的方法实现,当选择不同的 cpp 编译时产生的版本就不同。
关于= delete,它不仅局限于限制生成默认函数,还能避免编译器做一些不必要的隐式数据转换:
1 | class Example { |
有时候我们不想要这种隐式转换:
1 | class Example { |
这个方法也能用于普通函数:
1 | void func(int i) {} |
另外,还有很多用法,例如显示删除 new 操作符来避免在堆上分配对象、显示删除析构函数用于构建单例模式等等。
Lambda
什么是 Lambda 表达式?
Lambda 表达式是 C++11 提出的新特性,主要用来实现代码中函数的内嵌,简化了编程,提高了效率。
它的基本格式如下:
1 | auto fun = [捕获参数](函数参数){函数体}; |
例如这个 Hello Lambda:
1 | auto fun = [](){ std::cout << "Hello Lambda" << std::endl; }; |
Lambda`本质上是一个内联函数,只是定义和使用的方式与普通的函数有些不同,下面来具体介绍下它的基本语法。
基本语法
最简单的 Lambda
后面不加 () 的 Lambda 表达式相当于函数指针,加上 ()`就相当于调用这个 Lambda 函数。
1 | int main(void) { |
Lambda 可以赋值
可以将 Lambda 表达式赋值给一个变量,之后这个变量可以当作函数指针来调用,需要加上 ()。
1 | int main(void) { |
为 Lambda 传递参数
可以为 Lambda 指定函数参数,该参数也具有副本机制。
1 | int main(void) { |
获取 Lambda 的返回值
Lambda 的返回值类型可以进行类型转换或者使用 decltype 自动推导。
1 | int main(void) { |
[ ] 的使用方法
Lambda 表达式的 [] 用来确定捕获参数:
- [=]:捕获的局部变量只可读不可写,捕获范围是当前 Lambda 表达式之前的作用域。
- [&]:捕获的局部变量可读可写。
1 | int main(void) { |
Lambda 是 const 函数
Lambda 默认是 const 函数,不能修改引用的变量,使用 mutable 可以取消该属性,但是 mutable 修改的只是副本。
1 | int main(void) { |
Lambda 没有地址
Lambda 表达式是内联展开的,没有实际的地址,这是与普通函数的一个很大的区别。
1 | int main(void) |
Lambda 在 class 中的使用
Lambda 在 C++ class 中的使用需要知道如何捕获 this。
1 | void MyClass::function() { |
Lambda 与仿函数
Lambda 与仿函数有些类似,仿函数是编译器实现 Lambda 的一种方式,编译器会将 Lambda 转换为一个仿函数对象,Lambda 可以视为仿函数的等价形式,编译器在发现 Lambda 函数出现语法错误的时候,会报出一些与构造函数相关的信息。
下面是一个仿函数例子:
1 | class TestLambda { |
可以看到调用仿函数和 Lambda 是很类似的。
1 | int main() { |
Lambda 是 C++11 提出的新特性,简化了我们的代码,提高了编程的效率,应该了解并学会使用它。
参考文献
https://blog.csdn.net/WizardtoH/article/details/81164740
https://blog.csdn.net/WizardtoH/article/details/81169570
https://www.jianshu.com/p/a200a2dab960
《深入理解 C++11:C++11 新特性解析与应用》