231

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

int *myVariable;

而不是这样:

int* myVariable;

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

4

12 回答 12

295

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

int *myVariable, myVariable2;

很明显 myVariable 的类型为int*,而 myVariable2 的类型为int。在

int* myVariable, myVariable2;

两者都是int*类型似乎很明显,但这与intmyVariable2类型不正确。

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

于 2008-12-29T19:28:47.973 回答
146

如果换个角度看,*myVariableis 是 type int,这是有道理的。

于 2008-12-29T19:25:26.687 回答
60

到目前为止没有人在这里提到的是这个星号实际上是 C 中的“取消引用运算符”。

*a = 10;

上面的行并不意味着我要分配10a,这意味着我要分配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;中也是间接访问*意味着间接访问),因此上面的第二个语句比第一个语句更接近标准。

于 2015-07-22T11:23:20.500 回答
41

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

int* varA, varB; // This is misleading

正如@Lundin 在下面指出的那样, const 增加了更多需要考虑的微妙之处。您可以通过每行声明一个变量来完全回避这一点,这绝不是模棱两可的:

int* varA;
int varB;

清晰代码和简洁代码之间的平衡很难达到——十几行冗余代码int a;也不好。尽管如此,我还是默认每行一个声明,并担心以后合并代码。

于 2008-12-29T19:35:23.547 回答
20

我要在这里冒个险,说这个问题有一个直接的答案,对于变量声明以及参数和返回类型,星号应该放在名称旁边:int *myVariable;。要了解原因,请查看如何在 C 中声明其他类型的符号:

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

float my_array[3]对于一个数组。

一般的模式,称为声明后使用,是符号的类型被分成名称之前的部分和名称周围的部分,并且名称周围的这些部分模仿您用来获取左侧类型的值:

int a_return_value = my_function(729);

float an_element = my_array[2];

和:int copy_of_value = *myVariable;

C++ 在使用引用的工作中抛出了一个扳手,因为您使用引用的语法与值类型的语法相同,因此您可能会争辩说 C++ 对 C 采取了不同的方法。另一方面,C++ 保留了相同的在指针的情况下 C 的行为,所以在这方面引用确实是奇怪的。

于 2011-10-14T15:30:55.243 回答
12

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

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

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

编译器将其读取为:

int (*a);

不是:

(int*) a;

如果您养成将星号放在变量旁边的习惯,它将使您的声明更易于阅读。它还避免了诸如:

int* a[10];

- 编辑 -

当我说它被解析为 时,要准确解释我的意思int (*a),这意味着它与 的*绑定比它对 的绑定更紧密,a这与表达式中的绑定比它更紧密地绑定int的方式非常相似,因为 的优先级更高。4 + 3 * 7 374*

为 ascii 艺术道歉,用于解析的 AST 概要int *a大致如下所示:

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

正如清楚地表明的那样,由于它们的共同祖先是,因此*绑定更紧密,而您需要一直向上查找涉及 的共同祖先。aDeclaratorDeclarationint

于 2016-03-08T19:33:48.397 回答
11

这只是一个偏好问题。

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

我更喜欢在类型名称旁边声明带有相应符号的指针,例如

int* pMyPointer;
于 2008-12-29T19:35:37.133 回答
10

喜欢int* x;的人试图将他们的代码强制进入一个虚构的世界,其中类型在左侧,标识符(名称)在右侧。

我说“虚构”是因为:

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

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

  • int main(int argc, char *argv[])意思是“main是一个函数,它接受一个int和一个指针数组char并返回一个int。” 换句话说,大多数类型信息都在右侧。有些人认为函数声明不算数,因为它们在某种程度上是“特殊的”。好的,让我们尝试一个变量。

  • void (*fn)(int)meanfn是一个指向一个函数的指针,它接受一个int并且什么都不返回。

  • int a[10]int将 'a' 声明为 10秒的数组。

  • pixel bitmap[height][width].

  • 显然,我挑选了一些在右边有很多类型信息的例子来说明我的观点。有很多声明,其中大多数(如果不是全部)类型位于左侧,例如struct { int x; int y; } center.

这种声明语法源于 K&R 希望让声明反映使用情况。阅读简单的声明很直观,阅读更复杂的声明可以通过学习左右规则(有时称为螺旋规则或仅称为左右规则)来掌握。

C 语言非常简单,以至于许多 C 程序员都接受这种风格并将简单的声明编写为int *p.

在 C++ 中,语法变得更加复杂(包括类、引用、模板、枚举类),并且作为对这种复杂性的反应,您将看到在许多声明中将类型与标识符分开的努力。换句话说,int* p如果您查看大量 C++ 代码,您可能会看到更多 - 样式的声明。

在任何一种语言中,您始终可以通过以下方式在变量声明typedef的左侧拥有类型类型左侧的标识符)。例如:

typedef int array_of_10_ints[10];
array_of_10_ints a;
于 2019-07-11T17:52:00.523 回答
8

这个主题中的很多论点都是主观的,关于“星号绑定到变量名”的论点是幼稚的。这里有一些不仅仅是意见的论点:


被遗忘的指针类型限定符

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

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

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

(6.7.6)声明器:
指针opt直接声明
器 ...
(6.7.6)指针:
* type-qualifier-list opt
* type-qualifier-list opt 指针

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

是什么让关于“星属于变量”的各种风格参数不一致,是他们忘记了这些指针类型限定符。int* const x,int *const xint*const x?

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

您绝对可以提出一个合理的论点,即星号属于指针类型限定符,但仅此而已。

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

    /*** 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.

但是上面示例中问题的根源在于将指针隐藏在 atypedef而不是*样式后面的做法。


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

在一行上声明多个变量被广泛认为是不好的做法1)。CERT-C 很好地总结为:

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

只是阅读英文,然后常识同意一个声明应该是一个声明。

变量是否是指针并不重要。在单行上声明每个变量可以使代码在几乎所有情况下都更加清晰。

因此,关于程序员感到困惑的论点int* a, b是不好的。问题的根源在于使用了多个声明符,而不是*. 不管风格如何,你都应该写这个:

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

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

但基本上我的结论是,这里发布的许多论点都是主观和幼稚的。你不能真正为任何一种风格做出有效的论据——这确实是一个主观个人偏好的问题。


1) CERT-C DCL04-C

于 2019-06-04T11:07:01.153 回答
3

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

int *a, *b;
于 2008-12-29T19:27:25.153 回答
1

对于在一行中声明多个指针,我更喜欢int* a, * b;更直观地将“a”声明为指向整数的指针,并且在同样声明“b”时不混合样式。就像有人说的那样,无论如何我都不会在同一个语句中声明两种不同的类型。

于 2012-10-18T05:25:34.587 回答
0

当您在一个语句中初始化和分配变量时,例如

int *a = xyz;

您将值分配xyza,而不是*a。这使得

int* a = xyz;

更一致的符号。

于 2022-02-10T10:57:13.980 回答