为什么要在一行中声明一个变量,然后在下一行中对其赋值?


101

我经常在C和C ++代码中看到以下约定:

some_type val;
val = something;

some_type *ptr = NULL;
ptr = &something_else;

代替

some_type val = something;
some_type *ptr = &something_else;

最初,我认为这是从必须在范围的顶部声明所有局部变量的日子起遗留下来的习惯。但是我学会了不要这么快就消除资深开发人员的习惯。因此,是否有充分的理由在一行中声明并在事后分配?


12
+1表示“我学会了不要这么快就摒弃资深开发人员的习惯。” 这是一个明智的教训。
2015年

Answers:


92

C

在C89中,所有声明必须在范围({ ... })的开头,但是这一要求很快就被放弃了(首先是使用编译器扩展,然后是标准)。

C ++

这些例子不一样。some_type val = something;调用复制构造函数,同时val = something;调用默认构造函数,然后调用operator=函数。这种差异通常很关键。

习惯

有些人更喜欢先声明变量,然后再定义变量,以防他们稍后重新格式化其代码,使声明在一个位置,而定义在另一个位置。

关于指针,有些人只是习惯于初始化指向NULL或的每个指针nullptr,无论他们使用该指针做什么。


1
非常感谢C ++,谢谢。普通C呢?
乔纳森·斯特林

13
MSVC仍然不支持声明,除非它以C模式进行编译时在块的开头,这一直困扰着我。
迈克尔·伯

5
@Michael Burr:这是因为MSVC完全不支持C99。
2011年

3
“ some_type val = something;调用副本构造函数”:它可以调用副本构造函数,但是标准允许编译器取消临时的默认构造,val的副本构造和临时的销毁,并使用a直接构造val some_type构造函数something作为唯一参数。在C ++中,这是一个非常有趣且不寻常的边缘情况……这意味着对这些操作的语义有一个假设。

2
@Aerovistae:对于内置类型,它们是相同的,但是对于用户定义类型,不一定总是说相同。
orlp 2012年

27

您同时标记了问题C和C ++,而答案在这些语言中却有很大不同。

首先,问题标题的措词不正确(或更确切地说,与问题本身无关)。在两个示例中,变量都在一行中同时声明和定义。您的例子之间的区别是,在第一个变量要么留下未初始化初始化与虚拟值,然后将其分配后,一个有意义的值。在第二个示例中,变量立即被初始化

其次,在C ++语言中,正如@nightcracker在回答中指出的那样,这两种构造在语义上是不同的。第一个依赖于初始化,而第二个则依赖于赋值。在C ++中,这些操作是可重载的,因此有可能导致不同的结果(尽管可以注意到,产生不相等的初始化和赋值重载不是一个好主意)。

在原始标准C语言(C89 / 90)中,在块的中间声明变量是非法的,这就是为什么您可能会在块的开头看到未声明(或用伪值初始化)的变量,然后对其进行有意义分配的原因这些有意义的值可用时,请稍后再输入。

在C99语言中,可以在块的中间声明变量(就像在C ++中一样),这意味着仅在某些特殊情况下,在声明时不知道初始化程序的情况下,才需要第一种方法。(这也适用于C ++)。


2
@乔纳森·斯特林:我读了你的例子。您可能需要复习C和C ++语言的标准术语。具体来说,在这些语言中具有特定含义的术语声明定义。我再重复一遍:在您的两个示例中,变量都在一行中声明和定义。在C / C ++中,该行some_type val;立即声明定义变量val。这就是我在回答中的意思。

1
我在那里明白你的意思。您绝对正确地声明定义我使用它们的方式是毫无意义的。我希望您接受我的歉意,包括措辞不佳和评论欠佳。
乔纳森·斯特林

1
因此,如果一致认为“声明”是错误的词,我建议比我更了解该标准的人来编辑Wikibooks页面。
乔纳森·斯特林

2
在任何其他上下文中,声明都是正确的词,但是由于声明是一个定义良好的概念,因此在C和C ++中,您不能像在其他上下文中那样宽松地使用它。
2011年

2
@ybungalobill:你错了。C / C ++中的声明定义不是互斥的概念。实际上,定义只是声明的一种特定形式。每个定义都同时是一个声明(几乎没有例外)。有定义声明(即定义)和非定义声明。而且,通常情况下,始终使用therm 声明(即使它是一个定义),除非上下文之间的区别至关重要。

13

我认为这是一个古老的习惯,是“本地声明”时代遗留下来的。因此,作为对您问题的回答:不,我不认为有充分的理由。我从来没有自己做过。


4

我说了一些关于在我的答案,以通过Helium3问题

基本上,我说这是视觉帮助,可以轻松地看到更改。

if (a == 0) {
    struct whatever *myobject = 0;
    /* did `myobject` (the pointer) get assigned?
    ** or was it `*myobject` (the struct)? */
}

if (a == 0) {
    struct whatever *myobject;
    myobject = 0;
    /* `myobject` (the pointer) got assigned */
}

4

其他答案都很好。在C中有一些与此相关的历史。在C ++中,构造函数和赋值运算符之间是有区别的。

令我惊讶的是,没有人提到这一点:有时将声明与变量的使用区分开来可能更具可读性。

从视觉上讲,在阅读代码时,更普通的工件(例如变量的类型和名称)并不是您的选择。这是您通常最感兴趣,最花时间盯着别人看的陈述,因此有一种趋势可以浏览其余部分。

如果我的某些类型,名称和赋值都在同一个狭窄的空间中进行,则可能会导致信息过载。此外,这意味着在我通常浏览的空间中发生了重要的事情。

听起来似乎有点违反直觉,但这是使您的源占用更多垂直空间可以使其更好的一种情况。我认为这类似于为什么您不应该编写拥挤的行,这些行在狭窄的垂直空间中进行疯狂的指针算术和赋值操作-仅因为该语言让您摆脱了这些事情,并不意味着您应该这样做一直如此。:-)


2

在C中,这是标准做法,因为必须在函数的开头声明变量,这与C ++不同,在C ++中,可以在函数主体中的任何地方声明变量以供以后使用。指针被设置为0或NULL,因为它只是确保指针指向无垃圾。否则,我没有想到的显着优势,那就是强迫任何人这样做。


2

本地化变量定义及其有意义的初始化的优点:

  • 如果在变量首次出现在代码中时便习惯性地为其分配了有意义的值(另一种观点:您将它们的出现延迟到有意义的值可用之前),那么就不可能无意地它们与无意义或未初始化的值一起使用(由于条件语句,短路评估,异常等意外地跳过了一些初始化,因此很容易发生)

  • 可以更有效率

    • 避免了设置初始值的开销(默认构造或初始化为某些哨兵值,例如NULL)
    • operator= 有时效率较低,需要一个临时对象
    • 有时(尤其是对于内联函数),优化器可以消除某些/所有效率低下的问题

  • 最小化的反过来变量的范围最小化平均的数目同时在范围内的变量:本

    • 使您更容易从心理上跟踪范围内的变量,可能影响这些变量的执行流和语句以及其值的导入
    • 至少对于某些复杂且不透明的对象,这减少了程序的资源使用(堆,线程,共享内存,描述符)
  • 有时更加简洁,因为您没有在定义中重复变量名,而是在初始的有意义的赋值中

  • 对于某些类型(例如引用)以及当您希望对象是 const

分组变量定义的参数:

  • 有时,将一些变量的类型排除在外是方便和/或简洁的:

    the_same_type v1, v2, v3;

    (如果原因仅仅是类型名称太长或太复杂,typedef则有时可能会更好)

  • 有时,最好将变量与变量的使用无关地进行分组,以强调某些操作中涉及的一组变量(和类型):

    type v1;
    type v2; type v3;

    这强调了类型的通用性,使其更容易更改,同时仍然坚持每行的变量,从而便于复制粘贴,//注释等。

在编程中通常会遇到这种情况,尽管在大多数情况下一个实践可以带来明显的经验利益,但在少数情况下,另一种实践确实可以压倒性地更好。


我希望更多的语言能够区分代码声明和设置永远不会写在其他地方的变量的值的情况,尽管新变量可以使用相同的名称[即,无论后面的语句使用相同的变量还是相同的行为,行为都相同。 [与其他代码不同],与代码创建必须在多个位置可写的变量的代码不同。尽管两个用例将以相同的方式执行,但是在尝试查找错误时,了解变量何时可能更改非常有帮助。
2014年
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.