天天看点

C++11 nullptr与常量表达式constexpr记录

1、nullptr

  • (1) nullptr是一个关键字,而nullptr_t是一个类型
    • typedef decltype(nullptr) nullptr_t
    • 使用nullptr_t类型必须包含#include <cstddef>,而nullptr不需要包含任何头文件。
    • nullptr_t类型可以隐式转换为任意一种指针类型;如:std::nullptr_t test;char* s = test;
    • nullptr_t类型不适用于算数表达式,但是适用于关系表达式。
  • (2) nullptr是有类型的,仅可以被隐式转换为指针类型
    • 和NULL的不同,NULL的定义有二义性,有时其定义为0,有时定义为void*(0)
  • (3) 模板只能将nullptr_t作为一个普通类型来推导,而不会将其视为T*指针,因此要让编译器成功的推导出nullptr_t的类型,必须做显式的类型转换。
  • (4) nullptr类型所占用的内存空间大小和void*是相同的。即:(sizeof(nullptr) == sizeof(void*))
  • (5) nullptr与void*的区别
    • nullptr是编译时期的常量,是编译时期的一个关键字,可以被编译器识别。void*(0)是一个强制转换表达式,返回值也是void*类型
    • nullptr到任何指针的转换都是隐式的,而void*要转换为其他类型必须强制转换。【注意C语言void*可以隐式转换为任意指针,而C++不行】

2、constexpr

2.1 constexpr常量表达式函数

2.1.1 常量表达式要解决的问题

​ 在编码的过程中,我们会遇到下面的情况:

const int test1() {
    return 1;
}

int main() {
    int a[test1()] = {0}; // 编译错误,test1()返回值是运行时常量,所以在编译期间不能确定a的大小,这个时候需要使用编译时常量
}
           

​ 上面的代码是编译不过的,原因很简单,就是定义数组a的时候,a的大小由test1()返回值决定,由于test1()返回的值是一个运行时常量,而不是编译时常量,所以在编译时期,编译器不知道要给数组a分配多大的空间,所以就报错了。要解决这个问题,C++11引入了常量表达式函数,即在函数的返回值类型之前添加constexpr关键字,这个时候改函数的返回值即为一个编译时常量,可以放心使用了。

constexpr int test1() {
  return 1;
}

int mani() {
  int a[test1()] = {0};
}
           

2.1.2常量表达式限制

常量表达式在使用的时候需要注意下面四个问题:

  • (1) 函数体必须只有一个单一的return返回语句
    • 如:

      constexpr int test() {int i = 1; return i;}

      这样常量表达式定义是不允许的,会导致编译错误。
  • (2) 函数必须返回值
    • 显而易见,常量表达式函数必须返回值,即不允许有

      constexpr void fun() {}

      这种常量表达式函数
  • (3) 常量表达式函数使用前必须定义
  • (4) return返回语句表达式中不能使用非常量表达式函数、全局数据并且必须是一个常量表达式。

下面对这几个进行逐个讲解。

(1)函数体必须只有一个单一的return返回语句

// 常量表达式函数中只允许有一个return语句,不能有其他语句,否则会产生编译错误,(不产生代码的语句除外)
constexpr int func() {
  int i = 1;
  return i;
}
           

(2)函数必须返回值

// 禁止出现这种没有返回值的常量表达式,也没有什么意义,编译错误
constexpr viod fun() {
}
           

(3)常量表达式函数使用前必须定义

constexpr int fun();

int main() {
  constexpr int a = fun();	// 编译错误,在定义前使用,使用前必须先定义
  int b = fun();						// 编译通过,因为此时转换为普通的函数调用
}

constexpr int fun() {
  return 1;
}
           

(4) return返回语句表达式中不能使用非常量表达式函数、全局数据并且必须是一个常量表达式。

return语句中不能使用非常量表达式,因为如果使用非常量表达式,可能会导致常量表达式的结果在编译时期不能确定,同样的,也不能使用全局变量,因为全局变量在运行的过程中可能会被修改而导致编译时期不确定。

int a = 0;
// 编译错误,不允许使用全局变量
constexpr int func() {
  return a;
}
           

2.2 常量表达式值

2.2.1 const与constexpr区别

​ 常量表达式值很容易理解,即为在变量前添加constexpr修饰符,与const类似。如:

const int a = 0;
constexpr b = 1;
           

那上面a和b是否有区别呢?

  • 对于a来说,编译器总是为a生成数据,即编译器总是为变量a分配一块内存。
  • 对于b来说,只有显式的使用b的地址的情况下,编译器会为b分配内存;否则编译器不为变量b分配内存。

2.2.2 浮点型表达式常量

​ 由于编译时环境和运行时环境可能有差别,在使用浮点型常量表达式的时候,可能导致编译时期的常量值和运行时的常量值不一致而导致程序出现为定义行为,所以C++11标准要求,编译时的浮点常量的精度要高于或等于运行时的精度,只有这样才能保证为定义行为的出现。

2.2.3 自定义类型的常量表达式

​ 默认情况下constexpr修饰符不能修饰自定义类型,若在使用的时候需要修饰自定义类型,需要为为自定义类型定义常量表达式构造函数。常量表达式构造函数有下面两个限制:

  • 构造函数的函数体必须为空
  • 初始化列表中只能由常量表达式来赋值
class Test {
public:
    constexpr Test(int a):_a(a) {}
    constexpr int getA() {return _a;}
private:
    int _a;
};

int main() {
    constexpr Test t(1);
    std::cout << t.getA() << std::endl;

}
           
注意:程序函数的虚函数成员不允许声明为constexpr,因为virtual虚函数式运行时动态绑定,与编译时常量相悖。

继续阅读