nullptr
nullptr出现的目的是为了替代NULL。在某种意义上来说,传统会把NULL,0视为同一种东 西,这取决于编译器如何定义NULL,有些编译器会将定义为((void*)0),有些则会直接将其定义 为0。
C++不允许直接将void*隐式转换到其他类型。但如果编译器尝试把NULL定义为((void*)0), 那么在下面这句代码中:
char *ch = NULL;
没有了void*隐式转换的C++只好将定义为0。而这依然会产生新的问题,将NULL定义成0,将导致C++中重载特性发生混乱。考虑下面这两个函数:
void foo(char*);
void foo(int);
那么foo(NULL)这个语句将会去调用foo(int),从而导致代码违反直觉。
为了解决这个问题,C++引入了关键字nullptr,专门用来区分空指针、0。而nullptr的类型 为nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。 你可以尝试使用clang++编译下面的代码:
#include <iostream>#include <type_traits>//*******1. nullptr******/
// C++17 引入了 std::nullptr_t 类型,它是一个空指针类型。
// 在 C++ 中,NULL 是一个宏,通常定义为 0 或 (void*)0。
/*int main(){if(std::is_same<decltype(NULL), decltype(0)>::value){std::cout << "NULL is the same type as 0" << std::endl;}else{std::cout << "NULL is NOT the same type as 0" << std::endl;//NULL 和 0 在 C++ 中是不同的类型。}if(std::is_same<decltype(NULL), decltype((void*)0)>::value){std::cout << "NULL is the same type as (void*)0" << std::endl;}else{std::cout << "NULL is NOT the same type as (void*)0" << std::endl;//NULL 和 (void*)0 在 C++ 中是不同的类型。}if(std::is_same<decltype(NULL), std::nullptr_t>::value){std::cout << "NULL is the same type as std::nullptr_t" << std::endl;}else{std::cout << "NULL is NOT the same type as std::nullptr_t" << std::endl;//NULL 和 std::nullptr_t 在 C++ 中是不同的类型。}return 0;
}
*/
从输出中我们可以看出,NULL不同于与nullptr,0。所以,请养成直接使用的习惯。 此外,在上面的代码中,我们使用了decltype和std::is_same这两个属于现代C++的语法,简 单来说decltype用于类型推导,而std::is_same用于比较两个类型是否相同,我们会在后面 一节中详细讨论。
constexpr
C++本身已经具备了常量表达式的概念,比如1+2, 3*4这种表达式总是会产生相同的结果并且没 有任何副作用。如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序 的性能。一个非常明显的例子就是在数组的定义阶段:
// C++17 引入了 constexpr 关键字,它允许在编译时计算常量表达式。
#include <iostream>
#define LEN 10/*
int len_foo(){int i = 2;return i;
}constexpr int len_foo_constexpr() {return 5;
}constexpr int fibonacci(const int n) {return n==1 || n == 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
}int main() {char arr_1[10]; //合法char arr_2[LEN]; //非法 int len = 10;//char arr_3[len]; //非法const int len_2 = len + 1;constexpr int len_2_constexpr = 1+2+3;//char arr_4[len_2_constexpr]; //合法char arr_4[len_2]; //非法//char arr_5[len_foo()]; //非法char arr_6[len_foo_constexpr() + 1]; //合法std::cout << "fibonacci(10) = " << fibonacci(10) << std::endl; // 输出 55//1, 1, 2, 3, 5, 8, 13, 21, 34, 55std::cout << "fibonacci(10) = " << fibonacci(10) << std::endl; // return 0;}
*/
上面的例子中char arr_4[len_2]可能比较令人困惑,因为len_2已经被定义为了常量
char arr_4[len_2]为什么仍然是非法的呢?这是因为C++标准中数组的长度必须是一个常量表达式,而对len_2于而言,这是一个const常数,而不是一个常量表达式,因此(即便这种行为在大部分编译器中都支持,但是)它是一个非法的行为,我们需要使用接下来即将介绍的C++11引入的constexpr特性来解决这个问题;而对于arr_5来说,C++98之前的编译器无法得知len_foo()在运行期实际上是返回一个常数,这也就导致了非法的产生。
注意,现在大部分编译器其实都带有自身编译优化,很多非法行为在编译器优化的加持下会变得合法,若需重现编译报错的现象需要使用老版本的编译器。
C++11提供了constexpr让用户显式的声明函数或对象构造函数在编译期会成为常量表达式,这个关键字明确的告诉编译器应该去验证len_foo()在编译期就应该是一个常量表达式。此外,修饰的函数可以使用递归:
constexpr int fibonacci(const int n) {return n==1 || n == 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
}