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

int *myVariable;

而不是像这样:

int* myVariable;

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


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


因为当你有这样的声明时更有意义:

int *a, *b;

它们是完全相等的。 然而,在

int *myVariable, myVariable2;

显然,myVariable的类型是int*,而myVariable2的类型是int。 在

int* myVariable, myVariable2;

很明显,两者都是int*类型,但这是不正确的,因为myVariable2的类型是int。

因此,第一种编程风格更直观。


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

int* varA, varB; // This is misleading

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

int* varA;
int varB;

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


这只是个人喜好的问题。

当您阅读代码时,在第二种情况下,区分变量和指针更容易,但是当您将同一类型的变量和指针放在一行中时,可能会导致混淆(项目指南通常不鼓励这样做,因为这会降低可读性)。

我更喜欢在类型名旁边声明指针的对应符号,例如。

int* pMyPointer;

在这里,我要大胆地说,对于变量声明以及参数和返回类型,这个问题都有一个直接的答案,那就是名称后面应该有星号: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* a, * b;它更直观地将“a”声明为指向整数的指针,并且在同样声明“b”时不会混合样式。就像有人说的,我不会在同一个语句中声明两种不同的类型。


到目前为止还没有人提到的是,这个星号实际上是C语言中的“解引用操作符”。

*a = 10;

上面这行并不是说我要把10赋值给a,而是说我要把10赋值给a所指向的任何内存位置。我从没见过有人在写作

* a = 10;

有你吗?所以解引用操作符总是没有空格。这可能是为了将它与跨多行分解的乘法区分开来:

x = a * b * c * d
  * e * f * g;

这里*e会误导人,不是吗?

好吧,下面这行到底是什么意思:

int *a;

大多数人会说:

这意味着a是一个指向int值的指针。

这在技术上是正确的,大多数人喜欢这样看/读它,这就是现代C标准定义它的方式(注意,C语言本身早于所有ANSI和ISO标准)。但这并不是看待它的唯一方式。你也可以这样读这句话:

a的解引用值是int类型的。

所以事实上,这个声明中的星号也可以被看作是一个解引用操作符,这也解释了它的位置。a是一个指针其实并没有声明,它是一个隐含的事实,你唯一可以解引用的是一个指针。

C标准只定义了*操作符的两个含义:

间接操作符 乘法运算符

间接只有一个含义,声明指针没有额外的含义,只有间接,这就是解引用操作的作用,它执行间接访问,所以在像int *a这样的语句中;这是一种间接访问(*表示间接访问),因此上面的第二个语句比第一个语句更接近标准。


一位伟大的大师曾经说过:“你必须按照编译器的方式来阅读它。”

http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835

虽然这是关于const放置的主题,但同样的规则也适用于这里。

编译器将其解读为:

int (*a);

而不是:

(int*) a;

如果您养成了将星号放在变量旁边的习惯,它将使您的声明更容易阅读。它还避免了一些碍眼的东西,比如:

int* a[10];

——编辑——

为了准确解释我说它被解析为int (*a)时的意思,这意味着*与a的绑定比与int的绑定更紧密,就像在表达式4 + 3 * 7中,3与7的绑定比与4的绑定更紧密,因为*的优先级更高。

为了对ascii艺术表示歉意,解析int *a的A.S.T.大致如下所示:

      Declaration
      /         \
     /           \
Declaration-      Init-
Secifiers       Declarator-
    |             List
    |               |
    |              ...
  "int"             |
                Declarator
                /       \
               /        ...
           Pointer        \
              |        Identifier
              |            |
             "*"           |
                          "a"

如图所示,*与a的绑定更紧密,因为它们的共同祖先是Declarator,而您需要沿着树一直向上走到Declaration才能找到涉及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* x的人;试图将他们的代码强行带入一个虚构的世界,在这个世界中,类型在左边,标识符(名称)在右边。

我说“虚构的”是因为:

在C和c++中,一般情况下,声明的标识符被类型信息包围。

这听起来可能很疯狂,但你知道这是真的。下面是一些例子:

int main(int argc, char *argv[]) means "main is a function that takes an int and an array of pointers to char and returns an int." In other words, most of the type information is on the right. Some people think function declarations don't count because they're somehow "special." OK, let's try a variable. void (*fn)(int) means fn is a pointer to a function that takes an int and returns nothing. int a[10] declares 'a' as an array of 10 ints. pixel bitmap[height][width]. Clearly, I've cherry-picked examples that have a lot of type info on the right to make my point. There are lots of declarations where most--if not all--of the type is on the left, like struct { int x; int y; } center.

这种声明语法源于K&R希望声明能够反映用法。阅读简单的声明是凭直觉的,而阅读更复杂的声明可以通过学习右-左-右规则(有时称为螺旋规则或只是右-左规则)来掌握。

C语言非常简单,许多C程序员都接受这种风格,并将简单的声明写成int *p。

在c++中,语法变得有点复杂(有类、引用、模板、枚举类),作为对这种复杂性的反应,您将在许多声明中看到将类型与标识符分离的更多努力。换句话说,如果你检查了大量的c++代码,你可能会看到更多的int* p风格的声明。

在任何一种语言中,你都可以将类型放在变量声明的左边,方法是(1)永远不要在同一个语句中声明多个变量,以及(2)使用typedefs(或别名声明,讽刺的是,它将别名标识符放在类型的左边)。例如:

typedef int array_of_10_ints[10];
array_of_10_ints a;

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

int *a = xyz;

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

int* a = xyz;

一个更一致的符号。