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


187

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

int *myVariable;

而不是像这样:

int* myVariable;

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


1
通常,第二种样式看起来更直观,但是前一种样式可以避免代码中与类型相关的错误。如果您真的很喜欢后者的样式,则可以始终使用typedefs,但这会增加不必要的复杂性,恕我直言。

Answers:


250

它们完全等效。但是,在

int *myVariable, myVariable2;

显而易见,myVariable的类型为int *,而myVariable2的类型为int。在

int* myVariable, myVariable2;

看起来两个都是int *类型,但这似乎不正确,因为intmyVariable2类型是不正确的。

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


135
也许但我不会在一个声明中混合使用类型。
BobbyShaftoe,

15
@BobbyShaftoe同意。即使阅读了这里的每个论点,我仍然坚持int* someVar个人项目。这更有意义。
Alyssa Haroldsen

30
@Kupiakos只有在您基于“声明遵循使用”学习C的声明语法之后,它才有意义。声明使用与使用相同类型变量完全相同的语法。当您声明一个整数数组时,它看起来并不像:int[10] x。这根本不是C的语法。语法明确地将解析为:int (*x),而不是(int *) x,因此将星号放在左侧只会产生误导,并且是基于对C声明语法的误解。
Peaker

8
更正:因此,绝对不要在一行上声明多个变量。通常,您不应基于其他一些无关,糟糕和危险的编码风格来激发某种编码风格。
伦丁

6
这就是为什么我坚持每个指针声明一个变量。如果您选择这样做,绝对不会造成混乱int* myVariable; int myVariable2;
帕特里克·罗伯茨

122

如果换一种方式来看,它*myVariable是type int,这很有意义。


6
这是我最喜欢的解释,并且能很好地起作用,因为它通常解释了C的声明怪癖-甚至令人厌恶而粗糙的函数指针语法。
本杰明·波拉克

12
有点整洁,因为您可以想象没有任何实际的指针类型。只有在适当地引用或取消引用后,变量才会为您提供原始类型之一。
生物锌

1
实际上,“ * myVariable”的类型可能为NULL。更糟糕的是,它可能只是一个随机的内存位置。
qonf 2012年

4
qonf:NULL不是类型。myVariable可以为NULL,在这种情况下*myVariable会导致分段错误,但是不存在NULL类型。
Daniel Roethlisberger 2013年

28
这一点在以下情况下可能会产生误导:int x = 5; int *pointer = &x;,因为这表明我们将int设置*pointer为某个值,而不是pointer本身。
rafalcieslak

40

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

int* varA, varB; // This is misleading

正如@Lundin在下面指出的那样,const增加了更多需要考虑的细节。您可以通过声明每行一个变量来完全避免这种情况,这永远不会模棱两可:

int* varA;
int varB;

清晰代码和简洁代码之间的平衡很难实现-十几行多余的代码int a;也不是很好。尽管如此,我还是默认为每行一个声明,并担心稍后再组合代码。


2
好吧,在我看来,第一个令人误解的例子是设计错误。如果可以的话,我将完全从C中删除该声明方式,并使其均为int *类型。
亚当·巴杰

1
“ *与变量的绑定比与类型的绑定更紧密”,这是一个幼稚的参数。考虑一下int *const a, b;。*“绑定”在哪里?类型aint* const,那么当*是类型本身的一部分时,您怎么能说*属于该变量呢?
隆丁

1
针对特定问题,或涵盖所有情况:选择一种。我会做个注释,但这是我最后一个建议的另一个很好的论据:每行一个声明减少了搞砸这个问题的机会。
ojrac

36

到目前为止,这里没有人提到的是该星号实际上是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;,这是一种间接访问*手段间接访问),因此上面的第二个语句比第一个语句更接近标准。


6
感谢您让我免于在此写下另一个答案。顺便说一句,我通常将int a, *b, (*c)();“”声明为“将以下对象声明为int:对象a,对象所指向b的对象以及从函数所指向的对象所返回的对象c”。
Antti Haapala

1
所述*int *a;不操作,并且它不取消引用a(其甚至没有定义还)
MM

1
@MM请命名任何ISO C标准的页面和行号,在该标准中,星号可以是除乘法或间接形式之外的其他名称。它仅以示例形式显示“指针声明”,在任何地方都没有定义星号的第三个含义(并且没有定义含义,即不会是运算符)。哦,我无处声称任何东西都是“定义的”,你把它弄了。或者像乔纳森·莱夫勒(Jonathan Leffler)所说的那样,在C标准中,*始终是“语法”,它不是列出的声明说明符的一部分(因此,它不是声明的一部分,因此它必须是运算符)
Mecki

1
@MM 6.7.6.1仅通过示例的声明,完全像我说的,它没有给意义*(它仅提供了含义的完整表达,而不是在*表达式中!)。它说“ int a;” 声明一个指针,它确实做了,但从未声明过,但是没有给出任何含义*,将其读为* in的取消引用值a仍然是int仍然完全有效,因为它具有相同的事实含义。没什么,真的没有什么可以与6.7.6.1中的语句相矛盾。
Mecki '17

2
没有“总表达”。 int *a;是声明,而不是表达式。a没有被取消引用int *a;a甚至还不存在*。您是否认为int *a = NULL;是因为它取消引用了空指针而导致错误?
MM

17

我将在这里走出去,说这个问题有一个直接的答案,无论是对于变量声明还是对于参数和返回类型,这都应在名称旁边加上星号:int *myVariable;。要了解原因,请查看如何在C中声明其他类型的符号:

int my_function(int arg); 为了功能

float my_array[3] 用于数组。

通用模式(在use之后称为声明)是将符号的类型分为名称之前的部分,名称周围的部分以及名称周围的这些部分,这些模仿了您用来获取符号的语法。左侧类型的值:

int a_return_value = my_function(729);

float an_element = my_array[2];

和:int copy_of_value = *myVariable;

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


12

这只是一个偏好问题。

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

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

int* pMyPointer;

3
问题是关于C,那里没有参考。
Antti Haapala'5

感谢您指出这一点,尽管问题本质上不是关于指针或引用,而是关于代码格式。
macbirdie

8

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

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技术的道歉,用于解析的AST简介int *a大致如下:

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

如已清楚显示的那样,由于它们的共同祖先是,因此*绑定更紧密,而您需要一直向上走到树上,以找到涉及的共同祖先。aDeclaratorDeclarationint


不,编译器绝对将类型读取为(int*) a
隆丁

@Lundin这是大多数现代C ++程序员所犯的一个巨大误解。解析变量声明是这样的。步骤1.读取第一个标记,该标记成为声明的“基本类型”。 int在这种情况下。步骤2.阅读声明,包括任何类型的修饰。 *a在这种情况下。步骤3读取下一个字符。如果是逗号,请使用它,然后返回到步骤2。如果还有其他抛出语法错误。...
dgnuff

@Lundin ...如果解析器按照您的建议阅读它,那么我们将能够编写int* a, b;并获得一对指针。我要说的是*绑定到变量并被解析,而不是用于形成声明的“基本类型”的类型。这也是引入typedef来允许typedef int *iptr; iptr a, b;创建几个指针的部分原因。通过使用typedef,您可以将绑定*int
dgnuff

1
...当然,编译器将的基本类型Declaration-Specifier与中的“装饰” “组合” Declarator以得出每个变量的最终类型。但是,它不会将装饰“移动”到声明说明符,否则int a[10], b;将产生完全可笑的结果,它将解析int *a, b[10];int *a , b[10] ;。没有其他方法可以描述它了。
dgnuff

1
是的,这里的重要部分实际上并不是编译器解析的语法或顺序,而是int*a最终由编译器读取的a类型为:“ 具有类型int*”。这就是我最初的评论的意思。
隆丁

3

因为当您有如下声明时,它更有意义:

int *a, *b;

5
从字面上看,这是一个乞求问题的例子。不,这样做没有任何意义。“ int * a,b”也可以使它们都是指针。
MichaelGG 2015年

1
@MichaelGG你的尾巴在那儿摇着狗。当然K&R 可能已指定int* a, b;b一个指针int。但是他们没有。并且有充分的理由。在您建议的系统下b,以下声明的类型是什么int* a[10], b;
dgnuff

2

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


2

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

我说“虚构”是因为:

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

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

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

  • void (*fn)(int)Mean fn是指向带且不int返回任何内容的函数的指针。

  • int a[10]声明“ a”为10 int秒的数组。

  • pixel bitmap[height][width]

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

此声明语法源自K&R希望声明能够反映用法的愿望。读取简单的声明很直观,可以通过学习左右规则(有时称为螺旋规则或仅称为左右规则)来掌握更复杂的声明。

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

在C ++中,语法变得有点复杂(带有类,引用,模板,枚举类),并且作为对这种复杂性的反应,您会发现在许多声明中要花更多的精力将类型与标识符分离。换句话说,int* p如果您检查了大量的C ++代码,则可能会看到更多的-style声明。

无论使用哪种语言,都可以始终在变量的左侧输入类型声明:(1)永不在同typedef一条语句中声明多个变量;(2)使用s(或别名声明,具有讽刺意味的是,将别名放在类型左侧的标识符)。例如:

typedef int array_of_10_ints[10];
array_of_10_ints a;

一个小问题,为什么void(* fn)(int)不能表示“ fn是一个接受int并返回(void *)的函数?”
Soham Dongargaonkar,

1
@ a3y3:因为括号中的内容(*fn)使指针与fn而不是返回类型相关联。
阿德里安·麦卡锡

得到它了。我认为将其添加到您的答案中非常重要,我建议您尽快进行修改。
苏厄姆·东加冈卡

1
我认为答案很混乱,却没有增加太多价值。这只是该语言的语法。多数人询问为什么使用这种语法的原因可能已经知道了规则。任何对此有疑问的人都可以在这些评论中看到澄清。
阿德里安·麦卡锡

1

我已经在CP中回答了类似的问题,并且因为没有人提到,在这里我还必须指出C是一种自由格式语言,无论您选择哪种样式都可以,而解析器可以区分每个标记。C的这种特殊性导致了一种非常特殊的竞赛,称为C模糊竞赛


1

本主题中的许多参数都是主观的,而“星号绑定到变量名”的参数是幼稚的。这里有一些不只是观点的论点:


被遗忘的指针类型限定符

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

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

其中的声明说明符包含类型(和存储),而init-declarator-list包含指针和变量名。如果进一步剖析该声明符列表语法,可以看到以下内容:

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

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

使有关“星星属于变量”的各种样式参数不一致的原因是,它们忘记了这些指针类型限定符。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);

这样可以干净地编译。现在您可能会认为代码是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而不是*样式。


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

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

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

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

变量是否为指针都没有关系。在一行中声明每个变量几乎可以使代码更清晰。

因此,关于程序员感到困惑的争论int* a, b是不好的。问题的根源是使用多个声明符,而不是的放置*。无论样式如何,您都应该编写以下代码:

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

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

但基本上我的结论是,此处发表的许多论点只是主观的和幼稚的。对于这两种风格,您都无法真正提出有效的论据-这确实是个人主观偏好的问题。


1) CERT-C DCL04-C


有趣的是,如果您阅读了我链接的文章,则有一个简短的主题,typedef int *bad_idea_t; void func(const bad_idea_t bar); 正如伟大的先知丹·萨克斯(Dan Saks)所教导的:“如果您始终将其const尽可能地靠右放置,而又不改变其语义,”这将完全停止。成为一个问题。这也使您的const声明更一致阅读。“ const单词右边的所有内容都是const,左边的所有内容都是其类型。” 这将适用于声明中的所有const。试试int const * * const x;
dgnuff

-1

考虑

int *x = new int();

这不会转化为

int *x;
*x = new int();

它实际上转化为

int *x;
x = new int();

这使int *x表示法有些不一致。


new是C ++运算符。他的问题被标记为“ C”而不是“ C ++”。
Sapphire_Brick
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.