1. 背景
有一种值不会改变并且在编译过程就能得到计算结果的表达式我们称为常量表达式。
字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式:
const int MAX = 100;//常量表达式
const int MAX_TIME = MAX * 60;//常量表达式
int size = 30;//不是常量表达式
const int size2 = getSize();//不是常量表达式
一个对象或者表达式是不是常量表达式由它的数据类型和初始值共同决定,但是在一些复杂的系统中,我们难以分辨一个初始值是不是常量表达式。如何让编译器帮助我们判断是否是常量表达式呢?
2. constexpr
C++11中,引入了关键字constexpr,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是常量表达式。
声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化:
constexpr int i = 200;
constexpr int j = i + 100;
constexpr int size = getSize();//getSize必须是constexpr函数
constexpr除了修饰变量还可以修饰函数,这种函数必须是编译时就可以计算到结果。
2.1 constexpr限制
常量表达式的值需要在编译时就得到计算,所以对声明constexpr时用到的类型必须有所限制。
把比较简单,值显而易见,容易计算的类型成为“字面值类型”。算术类型,引用类型和指针都属于字面值类型。
指针和引用都能定义成constexpr,但是它们的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。
函数体内定义的变量一般不是存放在固定地址中,因此constexpr指针不能指向函数体内定义的指针。
constexpr声明中如果定义了指针,限定符constexpr仅对指针有效,与指针所指的对象无关。
3. decltype类型指示符
当我们希望从表达式的类型推断出要定义的变量的类型,但是不想用这个表达式的值初始化变量,这个时候就要用到了C++11引入的类型说明符decltype,它的作用是选择并返回操作数的数据类型:
decltype(fun()) i = x;//i的类型就是函数fun的返回类型
编译器分析fun表达式并得到它的类型,但是不进行实际调用。
3.1 引用在decltype的特殊之处
如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。有时有些表达式向decltype返回一个引用类型。这时意味着表达式的结果对象能作为一条赋值语句的左值。
如果表达式的内容是解引用操作,decltype将得到引用类型。
引用和指针本身是C++比Java多的引用和指针已经加大了编程语言的复杂度,现在为它们增加了新特性更是让人眼花缭乱,但是如果不弄明白一是遇到实际问题不知道用什么办法解决;二是看别人代码的时候容易一头雾水。
4. 类型别名
C/C++还有为类型定义别名的能力。比如我们用到的uint_8之类跨平台类型都是基于typedef方式命名的。
传统的别名定义是使用typedef:typedef char uint_8
C++11中引入了一种新的方法,使用using方式:using Str = std::string
使用typedef给复合类型起名容易踩坑,比如:
typedef char *str;
const str cstr = 0;//cstr是指向char的常量指针
const str *s;//s是一个指针,它的对象是指向char的常量指针
const是对给定类型的修饰,str是指向char的指针,因此const str就是指向char的常量指针,而不是指向常量字符的指针。
我们往往直接做替换:
const char *cstr = 0;
理解成指向常量字符的指针。
5. 总结
本文作为const内容的延续,介绍了constexpr和decltype两种修饰符,以及类型别名的两种方式,以及typedef别名使用容易遇到的问题。