这两天在看《CLR via C#》一书,发现C#在语法上区分了常量和只读两个概念,使用const
关键字表示常量,使用readonly
关键字表示只读。我当时转念一想,C++中对应的const
如何表达这两种概念呢?细思之下发现,C++里面这个问题比我想象中的要复杂很多,特意写一篇文章记录一下。
常量不等于常量表达式
我觉得弄清楚这个问题的关键点在于区分常量和常量表达式这个概念。我们可以用下面这个例子来说明:
1 | const int size = 7; |
上面这段代码在C++
中是合法的(虽然你可能更应该使用std::array
),但是在C99之前是不合法的(C99允许变长数组,和这里讨论的问题不属于同一个范畴)。也就是说C++中的const
和C语言中的const
含义并不相同。
常量
要解释清楚这个问题,我们需要先解释清楚常量这个概念,常量这个概念其实包含两种完全不一样的含义:
- 在编译期间计算对象的值
- 这个对象在作用域内部不会更改
第一个含义实际上说的是常量表达式,而第二个含义实际上说的是只读属性。
C++ 中的常量表达式
C++中常量表达式定义如下:
Defines an expression that can be evaluated at compile time
一个可以在编译期间确定值的表达式是由整型值、浮点值或者枚举值开始,组合操作符和常量函数而来的值。在语法上,morden c++
使用constexpr
来表达常量表达式这个概念,比如下面的例子(我没有写;是为了强调它是一个表达式):
1 | constexpr int x = 7 |
C++ 中的只读属性
只读属性实际上表达的含义是我保证在这个对象在作用域内部不会更改它的值,这种属主要用在约定接口上面。在语法上,C++使用const
表达只读这个概念,比如下面的例子。
1 | int foo(const std::vector<int>& ia); |
这个函数签名约定无论foo
如何实现,它不会更改ia
的内容。
C 语言中的常量表达式
C 语言中的常量表达式的定义如下:
An expression that involves only constants.
C语言中没有特殊的语法表示这个概念,通常会使用宏来替代
1 | #define PI (3.14) |
C 语言中的只读属性
C 语言中同样使用 const 关键字表示只读。
为什么上面的代码在C++中合法,在C中不合法呢
前面提到,数组的长度必须是一个常量表达式,这在C89和C++中都是一致的。不同地方在于C和C++的常量表达式定义不通。
我们说C++中使用consexpr
表示常量表达式,所以在C++中,上面的代码应该像下面这样写:
1 | // morden cpp |
但是因为在C++11之前,并不存在consexpr
这个关键字,所以const
关键字就兼任了表示常量表达式的功能,所以上面的代码和下面这一段是等价的:
1 | // old cpp |
C89中常量表达式只允许包含常量,所以在C89中,size
并不是常量表达式(size
是一个只读变量而不是常量)所以上面这段代码在C89中不合法,合法的写法如下:
1 |
|
【1】文中如果使用morden cpp
,表示C++11以后的版本,old cpp
表示C++11以前的版本,如果只用了C++或者CPP则表示所有的版本都适用。
【2】C++中的常量请参考《C++程序设计语言》第四版,10.4 小节
【3】C中的常量请参考《C程序设计语言》第二版,2.3 小节