一直以来对 C++ 中的 const 说明符理解不够清晰,尤其是在变量声明时处于何种位置起到何种作用,分辨不清。
现在花费一些时间专门理清其中的关系,明白其中的道理之后,就再也不会混淆了。文中内容主要参考了这篇译文。
1. 几个概念
以 static unsigned long int *x[N];
为例,说明几个概念
- 声明符:一个声明符就是被声明的名称,可能伴有操作符和标识符,这里为
*x[N]
; - 操作符:如
*
,[]
,()
, 和(C++中的)&
. 正如你所知的,声明中的符号*
表示“指针”,[]
表示 “序列”; - 标识符:一个声明符可能包含不止一个标识符。声明符
*x[N]
包含两个标识符,x
和N
。只有其中一个标识符是被声明的,而且被称为是声明符ID,其余的必须在这之前就被声明过。举例,*x[N]
中的声明符ID是x
。 - 说明符:可以包括类型说明符,如
int
,unsigned
,他们也可以是存储类说明符,如extern
或static
。在C++中,他们也可以是函数说明符,如inline
或virtual
,const
和volat
关键词都是类型说明符。
现在可以说,变量声明语句 static unsigned long int *x[N];
是由声明符和说明符来组成的。其中声明符由操作符和标识符组成,说明符可以同时含有类型说明符、存储类说明符等。
2. 解析顺序
所谓的解析顺序,我理解分为两个部分,声明符说明了是一个什么东西,说明符给出了这个东西的一些性质。对声明符来说,解析顺序决定了这个东西属于什么大类,什么小类,说明符不区分顺序,给出了这个东西在不同方面的属性。
2.1 操作符解析顺序
可以按照这个规则来决定解析顺序:离标识符ID越近,越决定了目标先是什么,距离相等情况下操作符优先级越高越先解析。
举例来说,*x[N]
中 *
和 []
离标识符ID x
的距离相等,按照 C++ 符号优先级图表进行解析, []
的优先级比 *
更高,因此声明符 *x[N]
表明 x
是一个优先于指针的序列,也就是说,它首先是一个序列,其次序列中的每个元素是一个指针。如果想要变换解析顺序,可以应用 ()
来提高解析级别,例如 (*x)[N]
表示为一个指向序列为 N
的指针。
2.2 说明符解析顺序
声明说明符在一个声明中出现的顺序并不重要。比如 const unsigned static int
、static unsigned int const
、int const unsigned static
、const int static unsigned
,只不过通常大家有一套默认的书写顺序。
2.3 const
和 volatile
能出现在声明符中的声明说明符只有 const
和 volatile
。当出现在声明符中时,可以认为其修饰的对象变成了操作符,并且不能交换 const
或 volatile
在声明中的顺序(不能交换const
或 volatile
与操作符*
的顺序)。例如 int const *a
把 a
声明为指向 const int
的指针,而 int *const a
把 a
声明为指向 int
的 const 指针。
2.4 最终的解析顺序
参考文章中总结到:
C++基本上是按从头到尾、从左到右的顺序来读,但是指针的声明,从某种意义来讲却是倒着的。指针的声明是从右到左来看。把
const
放在其他类型说明符的右边,可以严格的从右到左来看指针声明,还可以把const
从“右边的”位置提出来,如:T const *p
;
把p
声明为“指向const T
的指针”,非常准确,同样:T *const p
;
把p
声明为“指向T
的 const 指针”,也能正确的理解。
按照我自己的理解,可以按照先确定是什么东西(声明符),再看这个东西的性质(说明符)顺序来确定。
以上面例子来说,T const *p
先确定是一个指针,再看这个指针是关于 const T
类型的,也就是“指向 const T
的指针”;T *const p
先确定时一个 const 指针,再看这个 const 指针是关于 T
类型的,也就是“指向 T
的 const 指针”。
3. 声明风格
- 当把变量声明语句分成说明符和声明符两大部分之后,可能比较好理解文中给出的陈述:
const int* p
;
而不是const int *p
;
- 依照参考文章中对声明顺序的理解,给出的另一个建议:
const void *vectorTable[]
(3)void const *vectorTable[]
(4)
大多数C和C++程序员更喜欢把const和volatile写在其他类型的说明符的左边,同(3)。而我更喜欢把const和volatile写在右边,如(4),而且强烈推荐这样写。