为什么大多数C程序员这样命名变量:
int *myVariable;
而不是像这样:
int* myVariable;
两者都成立。在我看来,星号是类型的一部分,而不是变量名的一部分。有人能解释一下这个逻辑吗?
如果你从另一个角度来看,*myVariable的类型是int,这是有意义的。
*myVariable
int
因为当你有这样的声明时更有意义:
int *a, *b;
它们完全相等。 然而,在< / p >
int *myVariable, myVariable2;
int* myVariable, myVariable2;
很明显,两者都是int *类型,但这是不正确的,因为myVariable2的类型是int。
myVariable2
因此,第一种编程风格更直观。
因为这一行中的*与变量的绑定比与类型的绑定更紧密:
int* varA, varB; // This is misleading
正如@Lundin在下面指出的,const增加了更多需要考虑的微妙之处。你可以通过每行声明一个变量来完全避免这个问题,这永远不会有歧义:
int* varA; int varB;
在清晰的代码和简洁的代码之间取得平衡是很难的。十几行多余的int a;也不好。不过,我还是默认每行声明一次,并担心以后组合代码的问题。
int a;
这只是个人喜好的问题。
当您阅读代码时,在第二种情况下,区分变量和指针更容易,但是当您将同一类型的变量和指针放在一行中时,可能会导致混淆(项目指南通常不鼓励这样做,因为这会降低可读性)。
我更喜欢在类型名旁边声明指针的对应符号,例如。
int* pMyPointer;
我将在这里大胆地说这个问题有一个直接的答案,无论是对于变量声明,还是对于参数和返回类型,也就是应该在名称int *myVariable;后面加上星号。要理解其中的原因,看看如何在C语言中声明其他类型的符号:
int my_function(int arg);用于函数;
int my_function(int arg);
float my_array[3]用于数组。
float my_array[3]
一般的模式被称为使用后声明,即符号的类型被分为名称前面的部分,周围部分是名称,名称周围的这些部分模拟了用于获取左侧类型值的语法:
int a_return_value = my_function(729);
float an_element = my_array[2];
和:int copy_of_value = *myVariable;。
int copy_of_value = *myVariable;
c++在引用方面遇到了麻烦,因为使用引用的语法与值类型的语法是相同的,所以你可能会说c++采取了与C不同的方法。另一方面,c++在指针的情况下保留了与C相同的行为,所以引用在这方面确实是一个奇怪的例子。
对于在一行中声明多个指针,我更喜欢int* a, * b;,它更直观地将“a”声明为指向整数的指针,并且在同样声明“b”时不会混合样式。就像有人说的,我不会在同一个语句中声明两种不同的类型。
int* a, * b;
到目前为止还没有人提到的是,这个星号实际上是C语言中的“引用操作符”。
*a = 10;
上面这行并不是说我想把10赋值给a,而是说我想把10赋值给a指向的任何内存位置。我从没见过有人在写作
10
a
* a = 10;
有你吗?所以引用操作符几乎总是没有空格。这可能是为了将它与跨多行分解的乘法区分开来:
x = a * b * c * d * e * f * g;
这里*e会误导,不是吗?
*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的绑定更紧密。
int (*a)
4 + 3 * 7
3
7
4
为了对ascii艺术表示歉意,解析int *a的A.S.T.大致如下所示:
int *a
Declaration / \ / \ Declaration- Init- Secifiers Declarator- | List | | | ... "int" | Declarator / \ / ... Pointer \ | Identifier | | "*" | "a"
如图所示,*与a绑定得更紧密,因为它们的共同祖先是Declarator,而你需要沿着树一直向上走到Declaration才能找到涉及int的共同祖先。
Declarator
Declaration
这个话题中的很多争论都是非常主观的,关于“星号绑定到变量名”的争论。是天真的。以下是一些不只是观点的争论:
被遗忘的指针类型限定符
形式上,“星”;它既不属于类型,也不属于变量名,它是它自己的语法项指针的一部分。正式的C语法(ISO 9899:2018)是:
(6.7)声明: declare -specifiers init-declarator-list选择 ; . xml
;
其中declaration-specifiers包含类型(和存储),而init-declarator-list包含指针和变量名。如果我们进一步分析这个声明器列表语法,我们就会看到:
(6.7.6) 说明符: 指针<子>选择< /子> direct-declarator ... (6.7.6) 指针: * type-qualifier-list选择 * type-qualifier-list选择 指针
其中,声明器是整个声明,直接声明器是标识符(变量名),指针是星号后面跟着一个属于指针本身的可选类型限定符列表。
是什么导致了关于“星星属于变量”的各种风格争论?不一致的,是他们忘记了这些指针类型限定符。int* const x, int *const x还是int*const x?
int* const x
int *const x
int*const x
考虑int *const a, b;, a和b的类型是什么?“星星属于变量”这一点不是很明显;任何更长的时间。相反,人们会开始思考const属于哪里。
int *const a, b;
b
const
您可以明确地提出一个合理的参数,说明星号属于指针类型限定符,但除此之外别无其他。
指针的类型限定符列表可能会给那些使用int *a样式的人带来问题。那些在typedef中使用指针(我们不应该这样做,非常糟糕的做法!)并认为“星号属于变量名”的人;倾向于写这样一个非常微妙的错误:
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;,它将改变调用者中的变量值。
foo
int*const*
**foo = n;
这是因为在表达式const bad_idea_t *foo中,*不属于这里的变量名!在伪代码中,这个参数声明将被读取为const (bad_idea_t *) foo,而不将被读取为(const bad_idea_t) *foo。在这种情况下,星号属于隐藏指针类型——类型是指针,而const限定的指针被写入*const。
const bad_idea_t *foo
const (bad_idea_t *) foo
(const bad_idea_t) *foo
*const
但是上面例子中问题的根源是将指针隐藏在typedef而不是*样式后面。
关于在单行中声明多个变量
在一行上声明多个变量被广泛认为是坏习惯__abc0。CERT-C总结得很好:
DCL04-C。不要在每个声明中声明多个变量
只看英文,常识认为一个声明应该是一个声明。
变量是不是指针并不重要。在单行中声明每个变量几乎在每种情况下都能使代码更加清晰。
因此,关于程序员对int* a, b感到困惑的争论是不好的。问题的根源是使用了多个声明器,而不是*的位置。不管哪种风格,你应该这样写:
int* a, b
int* a; // or int *a int b;
另一个合理但主观的论点是,给定int* a, a的类型毫无疑问是int*,因此星号属于类型限定符。
int* a
int*
但基本上我的结论是,这里张贴的许多论点只是主观和天真的。你真的不能为任何一种风格提出一个有效的论点——这确实是个人主观偏好的问题。
1) CERT-C DCL04-C。
喜欢int* x;的人试图将他们的代码强行带入一个虚构的世界,在这个世界中,类型在左边,标识符(名称)在右边。
int* x;
我说“虚构的”是因为:
在C和c++中,一般情况下,声明的标识符被类型信息包围。
这听起来可能很疯狂,但你知道这是真的。下面是一些例子:
int main(int argc, char *argv[])表示“main是一个函数,它接受int和指向char的指针数组,并返回int。”也就是说,most的类型信息在右边。有些人认为函数声明不算数,因为它们在某种程度上是“特殊的”。好的,让我们试试一个变量。
int main(int argc, char *argv[])
main
char
void (*fn)(int)意味着fn是指向一个函数的指针,该函数接受int参数,但不返回任何值。
void (*fn)(int)
fn
int a[10]声明'a'为一个包含10个__abc1的数组。
int a[10]
pixel bitmap[height][width]。
pixel bitmap[height][width]
显然,我挑选了一些右边有很多类型信息的例子来说明我的观点。有很多声明,其中大多数(如果不是全部)类型都在左边,比如struct { int x; int y; } center。
struct { int x; int y; } center
这种声明语法源于K&R希望声明反映用法。阅读简单的声明是凭直觉的,而阅读更复杂的声明可以通过学习右-左-右规则(有时称为螺旋规则或只是右-左规则)来掌握。
C语言非常简单,许多C程序员都接受这种风格,并将简单的声明编写为int *p。
int *p
在c++中,语法变得有点复杂(有类、引用、模板、枚举类),作为对这种复杂性的反应,您将在许多声明中看到将类型与标识符分离的更多努力。换句话说,如果你检查了大量的c++代码,你可能会看到更多__abc0风格的声明。
在任何一种语言中,你可以的类型总是在变量声明的左边,通过(1)从不在同一语句中声明多个变量,(2)使用__abc0(或别名声明,具有讽刺意味的是,它将别名标识符放在类型的左边)。例如:
typedef int array_of_10_ints[10]; array_of_10_ints a;
当你在一个语句中初始化和赋值一个变量时,例如:
int *a = xyz;
将xyz的值赋给a,而不是*a。这使得
xyz
*a
int* a = xyz;
一个更一致的符号。