为什么"const T"和"T const"都是有效的,应该使用哪一个呢?

191
首先,你可能知道const可以用来使对象的数据或指针不可修改,或者两者都不可修改。
const Object* obj; // can't change data
Object* const obj; // can't change pointer
const Object* const obj; // can't change data or pointer

然而,您也可以使用以下语法:

Object const *obj; // same as const Object* obj;

唯一似乎重要的事情是星号的哪一边放置`const`关键字。个人而言,我更喜欢将`const`放在类型的左侧以指定其数据不可修改,因为在我的从左到右思维方式中读起来更好,但哪种语法先出现呢?
更重要的是,为什么有两种正确的指定`const`数据的方式?在什么情况下你会更喜欢或需要其中一种方法?
编辑:
听起来这似乎是一个任意的决定,在编译器解释事物的标准起草之前就已经存在了,而那时我还未出生。由于`const`应用于关键字左侧(默认情况下?),我猜他们认为在添加"快捷方式"来以其他方式应用关键字和类型限定符时不会造成任何伤害,至少直到声明通过解析`*`或`&`进行更改...
C语言也是这样吗?

13
在宏定义中,始终将 const 关键字放在类型之后,例如 #define MAKE_CONST(T) T const 而不是 #define MAKE_CONST(T) const T,这样 MAKE_CONST(int *) 将正确展开为 int * const,而不是 const int * - jotik
14
我曾看到这两种风格被称为“东方风格”和“西方风格”。 - Tom Anderson
34
@TomAnderson 但实际上应该是“东常数”和“常数西”。 - YSC
2
我会选择中西部建筑公司,因为我来自威斯康辛州。 - C. Tewalt
9个回答

137
为什么指定 const 数据有两种正确的方法?在什么情况下,如果有的话,您会更喜欢或需要其中的一种?
本质上,const 在指针前的说明符中位置的任意性是由 Kernighan 和 Ritchie 定义 C 语法规则而来的。
他们定义语法规则的原因可能是他们的 C 编译器从左到右解析输入,并在消耗每个标记时完成处理。消耗 * 标记将当前声明的状态更改为指针类型。在 * 之后遇到 const 表示将该修饰符应用于指针声明;而在 * 之前遇到表示该修饰符应用于所指向的数据。
由于语义含义不会因为 const 修饰符出现在类型说明符之前还是之后而改变,因此这两种写法都被接受。
当声明函数指针时也会遇到类似的情况,其中:
- void * function1(void) 声明了返回 void * 的函数, - void (* function2)(void) 声明了一个返回 void 的函数指针。
同样要注意的是,语言语法支持从左到右的解析器。

10
Kernighan参与撰写了这本书,但并没有参与C语言的设计,只有Ritchie。 - Tom Zych
18
我从来记不住哪个是哪个。感谢您的解释,我终于有了记忆技巧来记住它。谢谢!在 * 编译器解析器之前,它不知道它是指针,因此对于数据值而言它是常量。在 * 之后,它与常量指针相关。太棒了。最后,它解释了为什么我可以使用 const charchar const - Vit Bernatik
3
对我来说,对于为什么要这样做的猜测似乎相当薄弱/自相矛盾。也就是说,如果我正在定义一种语言并编写编译器,并且希望保持简单,“从左到右解析输入并完成处理每个标记”,正如您所说,那么在我看来,我应该要求 const 总是在其修饰的内容之后...正好这样,我可以在消耗const之后立即完成其处理。因此,这似乎是一个禁止使用西const的论点,而不是允许它。 - Don Hatch
3
因为如果const限定符出现在类型说明符之前或之后,语义含义不会改变,所以两种方式都被接受。但这难道不是循环论证吗?问题在于为什么要定义语义含义如此,所以我认为这句话没有任何贡献。 - Don Hatch
1
@donhatch 你必须记住,相对于今天和我们基于熟悉良好的编程语言设计所做出的假设,编程语言在那个时候还是相当新的事物。此外,一个语言是否具有宽容或限制性是一个价值判断。例如,Python是否应该有一个 ++ 运算符?"这句话",在我看来,帮助我意识到除了因为他们可以之外,没有任何特定的原因。也许他们今天会做出不同的选择/也可能不会。 - interestedparty333
显示剩余3条评论

97
规则是:
const关键词适用于其左侧的东西。如果左侧没有任何东西,则它适用于其右侧的东西。
我更喜欢在需要使用const的东西右侧使用const,因为这是“原始”定义const的方式。
但我认为这是一个非常主观的观点。

22
我更喜欢把它放在左边,但我认为把它放在右边更有道理。一般来说,在C++中,你是从右到左阅读类型,例如Object const *是一个指向常量Object的指针。如果你把const放在左边,它会被读作一个指向一个常量Object的指针,这样并不是很流畅。 - Collin Dauphinee
1
我认为左侧是为了与其他种类的C声明保持人类风格的一致性(从计算机角度来看,这并不正确,因为“const”不是存储类,但人们不是解析器)。 - geekosaur
2
@Heath 我认为这更像是一种指导方针而不是规则,我经常听到它作为记住编译器将如何解释它的方式...我理解它的工作原理,所以我只是好奇支持两种方式背后的思考过程。 - AJG85
6
@HeathHunnicutt 这个规则确实存在,但稍微有些复杂:http://c-faq.com/decl/spiral.anderson.html - geometrian
3
螺旋法则是第一个评论者"你通常从右到左阅读[C/]C++中的类型"的扩展版本。我认为你当时是在反驳这个观点,但现在我认为你可能是在提到答案本身。 - geometrian
显示剩余6条评论

89

我更喜欢第二种语法。通过从右往左阅读类型声明,它帮助我跟踪“什么”是常量:

Object * const obj;        // read right-to-left:  const pointer to Object
Object const * obj;        // read right-to-left:  pointer to const Object
Object const * const obj;  // read right-to-left:  const pointer to const Object

4
没错。"常量指针指向常量对象"应该是Object const* const,而不是const const Object*。除非有很多人绝对喜欢它(参见上面的Heath),否则"const"不能在左边。 - cdunn2001

52

在声明中关键字的顺序并不是固定不变的。有很多替代方案可以使用,不一定要遵循“唯一正确的顺序”。例如:

int long const long unsigned volatile i = 0;

或者应该是什么?
volatile unsigned long long int const i = 0;

??


36
对于一个简单变量,这个定义太过混乱了,我给你点赞。 :) - Xeo
4
是的,unsigned是一种类型,与unsigned intint unsigned相同。 unsigned long是另一种类型,与unsigned long intint long unsigned相同。看到规律了吗? - Bo Persson
2
@Bo:我看到这个混乱了,需要三个才能看出规律。;) 好的,谢谢。 - rubenvb
1
以前你可以在一堆单词中添加“static”,但最近编译器抱怨需要先加上“static”。 - Mark Lakata
1
@rubenvb 的意思是,unsigned 本身只是 unsigned int 的简写(如果不清楚的话),就像 short 本身与 short int 相同一样。(尽管要小心,使用 charunsigned/signed char 有所不同...它不是自己的类型,但它有自己的含义,因为 char 可以在内部是有符号或无符号的,而且无论哪种方式都没有关系,但基本上要保持一致,并且对于标准库函数,只需使用 plain char 并让它使用自己的内部表示。) - RastaJedi
显示剩余2条评论

10

首要规则是使用符合本地编码标准的格式。在此之后:若涉及到typedef,将const放在前面会导致无尽的混乱。

typedef int* IntPtr;
const IntPtr p1;   // same as int* const p1;
如果您的编码标准允许typedef指针,则确实应该坚持在类型之后放置const。除非应用于类型,否则在每种情况下都必须将const放在其所适用的内容之后,因此一致性也支持将const置于类型之后。但是本地编码准则优先于所有这些;通常不重要到足以返回并更改所有现有代码的差异。

1
我认为这可能是我们在这家公司的相对宽松的标准中没有指针typedef的原因。 - AJG85
2
我的政策(当我独自决定时)是将const放在后面(为了一致性),并且不使用typedef指针(或者总体上不使用typedef):-)。顺便说一句,string::iterator与string::const_iterator也应该考虑进你的决定中。(只是为了让事情更加混乱:-)。没有正确答案。) - James Kanze
这是一个很好的例子,我同意James Kanze的观点。然而,在typedef指针时有其用途:当你可能以后需要使用智能指针时。 - cdunn2001
3
@JamesKanze — 等一下,帮我看看这个例子...我没有看到发布的例子中存在什么混淆。const IntPtr p1除了表示“常量整数指针”(即“指向整数的常量指针”)之外,还可能有什么其他含义吗?即使不知道IntPtr的定义,任何理智的人都不会认为p1是可变的。而且同样地,为什么会有人错误地假设*p1是不可变的呢?而且,将const放在任何其他位置(例如IntPtr const p1)也不会改变语义。 - Todd Lehman
3
@ToddLehman,你可能没有意识到混淆的问题,但是大多数C++程序员会,而且通常会犯错(毫无疑问是由于std::vector<T>::const_iterator等事物的帮助,其中不是迭代器是const,而是它指向的内容是const)。 - James Kanze
显示剩余2条评论

9

历史原因导致左右顺序都可以被接受。 Stroustrup在1983年将const添加到C++中,但它直到C89/C90才出现在C语言中。

在C++中,始终在右侧使用const有一个好理由。您将在所有地方保持一致,因为const成员函数必须以这种方式声明:

int getInt() const;

1
...嗯,“好的理由”并不是那么令人信服,因为“const”的其他可能位置意义不同。const int& getInt(); int& const getInt(); - Maestro
1
@Maestro:我建议使用int const& getInt();比等价的const int& getInt();更好,而你所比较的int& const getInt();是多余的(引用已经是const),虽然合法,但通常会发出警告。无论如何,在成员函数上使用const会将函数中的“this”指针从“Foo* const”更改为“Foo const* const”。 - Nick Westgate
在成员函数上使用 const 并不意味着完全相同的事情——或者 void set(int)&; 是某种函数的引用吗? - Davis Herring

9

C语言采用从右到左的语法。只需从右向左阅读声明:

int var = 0;

// one is a pointer to a const int
int const * one = &var; 
// two is a pointer to an int const (same as "const int")
const int * two = &var; 

// three is a constant pointer to an int
int * const three = &var;

"const" 影响的是留给它的第一项。

要了解更多信息,请阅读此指南: http://cseweb.ucsd.edu/~ricko/rt_lt.rule.html


2

当我使用现有代码时,我会遵循已经使用的方式,大多数情况下是左边使用const,例如const int a = 10;,但如果我有机会从头开始工作,我会选择“正确的方式”,即右边使用const,例如int const a = 10;,这是一种更通用和直观的方法。

如前所述,规则是:

const应用于其左侧的事物。如果左侧没有任何内容,则应用于其右侧的事物。

以下是基本用例的示例代码。

int main() {
  int const a = 10; // Constant integer
  const int b = 20; // Constant integer (same as above)

  int c = 30; // Integer (changeable)
  int * const d = &c; // Constant pointer to changeable integer

  int const * e = &a; // Changeable pointer to constant integer

  int const * const f = &a; // Constant pointer to constant integer

  return 0;
}

说到底,如果没有遵循的指导方针,这是主观的,使用任何一种方法都是无害的。


1

随着

using P_Int = int *;

P_Int const a = nullptr;
int * const b = nullptr;

const P_Int c = nullptr;
const int * d = nullptr;

变量 a b 的类型相同,但是有点令人困惑的是,变量 c d 的类型不同。 我更喜欢第一种情况,避免混淆:将 const 放在类型的右边。请注意,对于 a b c ,指针是 const ; 但是对于 d ,整数是 const

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接