为什么大多数 C 程序员都这样命名变量:
int *myVariable;
而不是这样:
int* myVariable;
两者都是有效的。在我看来,星号是类型的一部分,而不是变量名的一部分。谁能解释一下这个逻辑?
为什么大多数 C 程序员都这样命名变量:
int *myVariable;
而不是这样:
int* myVariable;
两者都是有效的。在我看来,星号是类型的一部分,而不是变量名的一部分。谁能解释一下这个逻辑?
它们是完全等价的。然而,在
int *myVariable, myVariable2;
很明显 myVariable 的类型为int*,而 myVariable2 的类型为int。在
int* myVariable, myVariable2;
两者都是int*类型似乎很明显,但这与intmyVariable2
类型不正确。
因此,第一种编程风格更直观。
如果换个角度看,*myVariable
is 是 type int
,这是有道理的。
到目前为止没有人在这里提到的是这个星号实际上是 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;
中也是间接访问(*
意味着间接访问),因此上面的第二个语句比第一个语句更接近标准。
因为该行中的 * 与变量的绑定比与类型的绑定更紧密:
int* varA, varB; // This is misleading
正如@Lundin 在下面指出的那样, const 增加了更多需要考虑的微妙之处。您可以通过每行声明一个变量来完全回避这一点,这绝不是模棱两可的:
int* varA;
int varB;
清晰代码和简洁代码之间的平衡很难达到——十几行冗余代码int a;
也不好。尽管如此,我还是默认每行一个声明,并担心以后合并代码。
我要在这里冒个险,说这个问题有一个直接的答案,对于变量声明以及参数和返回类型,星号应该放在名称旁边: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 的行为,所以在这方面引用确实是奇怪的。
一位伟大的大师曾经说过“必须按照编译器的方式阅读它”。
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"
正如清楚地表明的那样,由于它们的共同祖先是,因此*
绑定更紧密,而您需要一直向上查找涉及 的共同祖先。a
Declarator
Declaration
int
这只是一个偏好问题。
当您阅读代码时,在第二种情况下区分变量和指针更容易,但是当您将通用类型的变量和指针放在一行中时可能会导致混淆(项目指南通常不鼓励这样做,因为降低了可读性)。
我更喜欢在类型名称旁边声明带有相应符号的指针,例如
int* pMyPointer;
喜欢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;
这个主题中的很多论点都是主观的,关于“星号绑定到变量名”的论点是幼稚的。这里有一些不仅仅是意见的论点:
被遗忘的指针类型限定符
形式上,“星”既不属于类型也不属于变量名,它是它自己的语法项的一部分,名为指针。正式的 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 x
或int*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
.
但是上面示例中问题的根源在于将指针隐藏在 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。
因为当你有这样的声明时它更有意义:
int *a, *b;
对于在一行中声明多个指针,我更喜欢int* a, * b;
更直观地将“a”声明为指向整数的指针,并且在同样声明“b”时不混合样式。就像有人说的那样,无论如何我都不会在同一个语句中声明两种不同的类型。
当您在一个语句中初始化和分配变量时,例如
int *a = xyz;
您将值分配xyz
给a
,而不是*a
。这使得
int* a = xyz;
更一致的符号。