为什么变量名之前的星号,而不是类型之后?

WBl*_*sko 177 c variables pointers naming-conventions

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

int *myVariable;
Run Code Online (Sandbox Code Playgroud)

而不是像这样:

int* myVariable;
Run Code Online (Sandbox Code Playgroud)

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

lui*_*bal 235

它们完全等同.但是,在

int *myVariable, myVariable2;
Run Code Online (Sandbox Code Playgroud)

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

int* myVariable, myVariable2;
Run Code Online (Sandbox Code Playgroud)

似乎很明显两者都是int*类型,但是因为intmyVariable2类型不正确.

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

  • 也许,但我不会在一个声明中混合和匹配类型. (127认同)
  • @Kupiakos只有在你根据"声明跟随使用"学习C的声明语法之后才会更有意义.声明使用与使用相同类型变量完全相同的语法.声明一个int数组时,它看起来不像:`int [10] x`.这根本不是C的语法.语法显式解析为:`int(*x)`,而不是`(int*)x`,因此在左边放置星号只是误导并且基于对C声明语法的误解. (28认同)
  • @BobbyShaftoe同意.即使在阅读了这里的每一个论点之后,我仍然坚持使用`int*someVar`进行个人项目.这更有意义. (13认同)
  • 更正:因此,您不应该在一行上声明多个变量.一般来说,你不应该根据其他一些不相关的,坏的和危险的编码风格来激发某种编码风格. (8认同)
  • 这就是为什么我坚持每个指针声明一个变量。如果您执行`int * myVariable,则绝对不会造成混淆。int myVariable2;`代替。 (4认同)

bio*_*inc 115

如果你以另一种方式看待它,那*myVariable就是类型int,这是有道理的.

  • 在这种情况下,这一点可能会产生误导:`int x = 5; int*pointer =&x;`,因为它建议我们将int`*pointer`设置为某个值,而不是`pointer`本身. (25认同)
  • 它有点整洁,因为你可以想象没有任何实际的指针类型.只有在适当引用或取消引用时,才会为您提供一种原始类型. (11认同)
  • 这是我最喜欢的解释,效果很好,因为它解释了C的一般声明怪癖 - 甚至是恶心和粗糙的函数指针语法. (6认同)
  • qonf:NULL不是一个类型.`myVariable`可以为NULL,在这种情况下`*myVariable`会导致分段错误,但没有类型NULL. (3认同)

ojr*_*rac 40

因为*更接近变量而不是类型:

int* varA, varB; // This is misleading
Run Code Online (Sandbox Code Playgroud)

然而,即使是最好的单行声明对我来说也是违反直觉的,因为*是该类型的一部分.我喜欢这样做:

int* varA;
int varB;
Run Code Online (Sandbox Code Playgroud)

像往常一样,代码越紧凑,可读性越强.;)

  • 好吧,误导性的第一个例子在我看来是一个设计错误。如果可以的话,我会完全从 C 中删除这种声明方式,并使其成为 int* 类型。 (3认同)
  • “* 与变量的绑定比与类型的绑定更紧密”这是一个幼稚的论点。考虑“int *const a, b;”。* 在哪里“绑定”?`a` 的类型是 `int* const`,那么当 * 是类型本身的一部分时,怎么能说它属于变量呢? (2认同)

Mec*_*cki 32

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

*a = 10;
Run Code Online (Sandbox Code Playgroud)

该线之上并不意味着我要分配10a,这意味着我要分配10到任何内存位置a点.我从未见过有人在写作

* a = 10;
Run Code Online (Sandbox Code Playgroud)

你呢?因此,取消引用运算符几乎总是在没有空格的情况下编写.这可能是为了将它与跨越多行的乘法区分开来:

x = a * b * c * d
  * e * f * g;
Run Code Online (Sandbox Code Playgroud)

*e会误导,不是吗?

好的,现在以下行实际意味着什么:

int *a;
Run Code Online (Sandbox Code Playgroud)

大多数人会说:

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

这在技术上是正确的,大多数人喜欢以这种方式查看/阅读它,这就是现代C标准定义它的方式(注意语言C本身早于所有ANSI和ISO标准).但这不是看待它的唯一方式.您还可以按如下方式阅读此行:

取消引用的值a是类型int.

所以事实上,这个声明中的星号也可以看作是一个解引用运算符,它也解释了它的位置.这a是一个指针根本没有真正声明,它隐含的事实是,你唯一可以解除引用的是指针.

C标准仅为*运营商定义了两个含义:

  • 间接运算符
  • 乘法运算符

间接只是一个单一的含义,没有额外的意义来声明指针,只有间接,这是取消引用操作所做的,它执行间接访问,因此在这样的语句中int *a;也是间接访问(*均值)间接访问)因此上面的第二个陈述比第一个陈述更接近标准.

  • 谢谢你让我免于在这里写下另一个答案.顺便说一下,我通常读取`int a,*b,(*c)();`就像"将以下对象声明为`int`:对象`a`,`b`指向的对象,以及从`c`"指向的函数返回的对象. (6认同)
  • `int *a;` 中的 `*` 不是运算符,也不会解引用 `a`(它甚至还没有定义) (3认同)
  • 没有“完全表达”。`int *a;` 是一个声明,而不是一个表达式。`a` 不会被 `int *a;` 解引用。在处理 `*` 时,`a` 甚至还不存在。您是否认为 `int *a = NULL;` 是一个错误,因为它取消了对空指针的引用? (3认同)

Inj*_*ilo 17

我将在这里说明这个问题的答案是直接的,无论是变量声明还是参数和返回类型,都应该是星号旁边的名字: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的行为,所以在这方面引用确实是奇怪的.


mac*_*die 11

这只是一个偏好问题.

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

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

int* pMyPointer;
Run Code Online (Sandbox Code Playgroud)

  • 问题是关于C,没有参考. (3认同)

The*_*nus 7

在K&R中,它们将指针运算符放在变量名称旁边而不是类型.就个人而言,我认为这本书是最终的C权威/圣经.同样来自Objective-C背景,我遵循*name约定.


dgn*_*uff 7

一位伟大的大师曾经说过"你必须按照编译器的方式阅读它."

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

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

编译器将其读取为:

int (*a);
Run Code Online (Sandbox Code Playgroud)

不是:

(int*) a;
Run Code Online (Sandbox Code Playgroud)

如果您养成将星形放在变量旁边的习惯,它将使您的声明更容易阅读.它还避免了眼睛,如:

int* a[10];
Run Code Online (Sandbox Code Playgroud)

  • 不,编译器绝对将类型读取为`(int*) a`。 (2认同)
  • 是的,这里的重要部分并不是编译器解析的语法或顺序,而是 `int*a` 的类型最终被编译器读取为:“`a` 的类型为 `int*`” . 这就是我最初评论的意思。 (2认同)

Adr*_*thy 7

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

我说“虚构”是因为:

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

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

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

  • void (*fn)(int)meanfn是一个指向函数的指针,该函数接受 anint并且不返回任何内容。

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

  • pixel bitmap[height][width].

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

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

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

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

在任何一种语言,你可以总是有左边的类型变量的声明(1)从来没有定义多个变量在同一语句,和(2)利用typedefS(或别名声明,这些声明,讽刺的是,把别名类型左侧的标识符)。例如:

typedef int array_of_10_ints[10];
array_of_10_ints a;
Run Code Online (Sandbox Code Playgroud)

  • 我认为这会使答案变得混乱而没有增加太多价值。这只是该语言的语法。大多数询问语法为何如此的人可能已经知道规则。其他对此感到困惑的人可以看到这些评论中的澄清。 (3认同)
  • “喜欢 `int* x;` 的人试图将他们的代码强制带入一个虚构的世界,其中类型位于左侧,标识符(名称)位于右侧。” 像 Bjarne Stroustrup 这样的人?https://www.stroustrup.com/bs_faq2.html#whitespace (3认同)
  • @a3y3:因为`(*fn)`中的括号保留与`fn`关联的指针而不是返回类型。 (2认同)

Lun*_*din 6

本主题中的许多论点都是主观的,关于“星号绑定到变量名”的论点是幼稚的。以下是一些不仅仅是意见的论点:


被遗忘的指针类型限定符

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

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

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

(6.7.6)声明
指针选择直接声明
...
(6.7.6)指针:
* 类型限定符列表选择
* 类型限定符列表选择 指针

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

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

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

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

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

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

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

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

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

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


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

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

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

刚读英语,然后常识同意一个声明应该是一个声明。

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

所以关于程序员混淆的争论int* a, b是不好的。问题的根源在于使用了多个声明符,而不是*. 不管风格如何,你都应该这样写:

    int* a; // or int *a
    int b;
Run Code Online (Sandbox Code Playgroud)

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

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


1) CERT-C DCL04-C