为什么大多数C程序员这样命名变量:

int *myVariable;

而不是像这样:

int* myVariable;

两者都成立。在我看来,星号是类型的一部分,而不是变量名的一部分。有人能解释一下这个逻辑吗?


当前回答

这个主题中的许多论点都是非常主观的,关于“星号绑定到变量名”的论点是幼稚的。以下是一些不只是观点的争论:


被遗忘的指针类型限定符

形式上,“星号”既不属于类型,也不属于变量名,它是它自己的语法项pointer的一部分。正式的C语法(ISO 9899:2018)是:

(6.7)声明: 声明说明符init-declarator-listopt;

其中declarations -specifiers包含类型(和存储),init-declarator-list包含指针和变量名。如果我们进一步分析这个声明器列表语法,我们就会看到:

(6.7.6)说明符: pointeropt direct-declarator ... (6.7.6)指针: * type-qualifier-listopt * type-qualifier-listopt指针

其中,声明器是整个声明,直接声明器是标识符(变量名),指针是星号后面跟着一个属于指针本身的可选类型限定符列表。

关于“星号属于变量”的各种样式参数之所以不一致,是因为它们忘记了这些指针类型限定符。Int *const x, Int *const x还是Int *const x?

考虑int *const a, b;, a和b的类型是什么?“星属于变量”的说法不再那么明显了。相反,人们会开始思考const属于哪里。

您可以明确地提出一个合理的参数,说明星号属于指针类型限定符,但除此之外别无其他。

指针的类型限定符列表可能会给使用int *a样式的人带来问题。那些在类型定义中使用指针(我们不应该这样做,这是非常糟糕的做法!)并认为“星号属于变量名”的人往往会写出这样一个非常微妙的错误:

    /*** bad code, don't do this ***/
    typedef int *bad_idea_t; 
    ...
    void func (const bad_idea_t *foo);

这样编译起来很干净。现在你可能认为代码是const正确的。不是这样的!这段代码意外地伪造了const正确性。

foo的类型实际上是int*const*——最外层的指针是只读的,而不是指向数据的指针。在这个函数中,我们可以写**foo = n;它会改变调用者的变量值。

这是因为在表达式const bad_idea_t *foo中,*不属于这里的变量名!在伪代码中,这个参数声明将被读取为const (bad_idea_t *) foo,而不是(const bad_idea_t) *foo。在这种情况下,星号属于隐藏指针类型——类型是指针,限定const的指针被写入*const。

但是上面例子中问题的根源是将指针隐藏在typedef后面而不是*样式后面。


关于在一行中声明多个变量

在一行上声明多个变量被普遍认为是一种糟糕的做法。CERT-C总结得很好:

DCL04-C。不要在每个声明中声明多个变量

只看英文,常识认为一份声明应该是一份声明。

变量是不是指针并不重要。在单行中声明每个变量几乎在每种情况下都能使代码更加清晰。

所以关于程序员对int* a, b感到困惑的争论是不好的。问题的根源在于使用了多个声明器,而不是*的位置。不管哪种风格,你应该这样写:

    int* a; // or int *a
    int b;

另一个合理但主观的论点是,给定int* a, a的类型毫无疑问是int*,因此星号属于类型限定符。

但基本上我的结论是,这里张贴的许多论点只是主观和天真的。你真的不能为任何一种风格提出一个有效的论点——这确实是个人主观偏好的问题。


1) CERT-C DCL04-C。

其他回答

如果你从另一个角度来看,*myVariable是int类型,这是有意义的。

这个主题中的许多论点都是非常主观的,关于“星号绑定到变量名”的论点是幼稚的。以下是一些不只是观点的争论:


被遗忘的指针类型限定符

形式上,“星号”既不属于类型,也不属于变量名,它是它自己的语法项pointer的一部分。正式的C语法(ISO 9899:2018)是:

(6.7)声明: 声明说明符init-declarator-listopt;

其中declarations -specifiers包含类型(和存储),init-declarator-list包含指针和变量名。如果我们进一步分析这个声明器列表语法,我们就会看到:

(6.7.6)说明符: pointeropt direct-declarator ... (6.7.6)指针: * type-qualifier-listopt * type-qualifier-listopt指针

其中,声明器是整个声明,直接声明器是标识符(变量名),指针是星号后面跟着一个属于指针本身的可选类型限定符列表。

关于“星号属于变量”的各种样式参数之所以不一致,是因为它们忘记了这些指针类型限定符。Int *const x, Int *const x还是Int *const x?

考虑int *const a, b;, a和b的类型是什么?“星属于变量”的说法不再那么明显了。相反,人们会开始思考const属于哪里。

您可以明确地提出一个合理的参数,说明星号属于指针类型限定符,但除此之外别无其他。

指针的类型限定符列表可能会给使用int *a样式的人带来问题。那些在类型定义中使用指针(我们不应该这样做,这是非常糟糕的做法!)并认为“星号属于变量名”的人往往会写出这样一个非常微妙的错误:

    /*** bad code, don't do this ***/
    typedef int *bad_idea_t; 
    ...
    void func (const bad_idea_t *foo);

这样编译起来很干净。现在你可能认为代码是const正确的。不是这样的!这段代码意外地伪造了const正确性。

foo的类型实际上是int*const*——最外层的指针是只读的,而不是指向数据的指针。在这个函数中,我们可以写**foo = n;它会改变调用者的变量值。

这是因为在表达式const bad_idea_t *foo中,*不属于这里的变量名!在伪代码中,这个参数声明将被读取为const (bad_idea_t *) foo,而不是(const bad_idea_t) *foo。在这种情况下,星号属于隐藏指针类型——类型是指针,限定const的指针被写入*const。

但是上面例子中问题的根源是将指针隐藏在typedef后面而不是*样式后面。


关于在一行中声明多个变量

在一行上声明多个变量被普遍认为是一种糟糕的做法。CERT-C总结得很好:

DCL04-C。不要在每个声明中声明多个变量

只看英文,常识认为一份声明应该是一份声明。

变量是不是指针并不重要。在单行中声明每个变量几乎在每种情况下都能使代码更加清晰。

所以关于程序员对int* a, b感到困惑的争论是不好的。问题的根源在于使用了多个声明器,而不是*的位置。不管哪种风格,你应该这样写:

    int* a; // or int *a
    int b;

另一个合理但主观的论点是,给定int* a, a的类型毫无疑问是int*,因此星号属于类型限定符。

但基本上我的结论是,这里张贴的许多论点只是主观和天真的。你真的不能为任何一种风格提出一个有效的论点——这确实是个人主观偏好的问题。


1) CERT-C DCL04-C。

在这里,我要大胆地说,对于变量声明以及参数和返回类型,这个问题都有一个直接的答案,那就是名称后面应该有星号:int *myVariable;。要理解其中的原因,看看如何在C语言中声明其他类型的符号:

Int my_function(Int arg);对于一个函数;

为数组浮动my_array[3]。

一般的模式,被称为声明遵循使用,是一个符号的类型被分为名称前的部分和名称周围的部分,而名称周围的这些部分模仿了你用来获得左侧类型值的语法:

Int a_return_value = my_function(729);

浮动an_element = my_array[2];

and: int copy_of_value = *myVariable;

c++在引用方面遇到了麻烦,因为使用引用的语法与值类型的语法是相同的,所以你可能会说c++采取了与C不同的方法。另一方面,c++在指针的情况下保留了与C相同的行为,所以引用在这方面确实是一个奇怪的例子。

因为这一行中的*与变量的绑定比与类型的绑定更紧密:

int* varA, varB; // This is misleading

正如@Lundin在下面指出的,const增加了更多需要考虑的微妙之处。你可以通过每行声明一个变量来完全避免这个问题,这永远不会有歧义:

int* varA;
int varB;

清晰的代码和简洁的代码之间的平衡是很难实现的——十几行冗余的int a;也不好。不过,我还是默认每行声明一次,并担心以后组合代码的问题。

当你在一个语句中初始化和赋值一个变量时,例如:

int *a = xyz;

将xyz的值赋给a,而不是*a。这使得

int* a = xyz;

一个更一致的符号。