6.3 清除定义块

在C中,总是要在一个程序块的左括号一开始就定义好所有的变量,这在程序设计语言中不算少见,其理由无非是因为“这是一种好的编程风格”。在这点上,我有自己的看法。我认为它总是带来不方便。作为一个程序员,每当需要增加一个变量时我都得跳到块的开始,我发现如果变量定义紧靠着变量的使用点时,程序的可读性更强。

也许这些争论仅限于格式。在C++中,是否一定要在块的开头就定义所有变量成了一个很突出的问题。如果存在构造函数,那么当对象产生时它必须首先被调用,如果构造函数带有一个或者更多个初始化参数,那么怎么知道在块的开头定义这些初始化信息呢?在一般的编程情况下将做不到这点,因为C中没有私有成员的概念。这样很容易将定义与初始化部分分开,然而C++要保证在一个对象产生时,它同时被初始化。这可以保证系统中没有未初始化的对象。C并不关心这些。事实上,C要求在块的开头定义变量,而这时还不知道一些必要的初始化信息[1],这样就鼓励了不初始化变量的习惯。

通常,在C++中,在还不拥有构造函数的初始化信息时不能创建一个对象,所以不必在块的开头定义所有变量。事实上,这种语言风格似乎鼓励把对象的定义放得离使用点处尽可能近一点。在C++中,对一个对象适用的所有规则,对内建类型的对象也同样适用。这意味着任何类的对象或者内建类型的变量都可以在块的任何地方定义。这也意味着可以等到已经知道一个变量的必要信息时再去定义它,所以总是可以同时定义和初始化一个变量。

6.3 清除定义块 - 图1

6.3 清除定义块 - 图2

上例中可以看到先是执行一些代码,然后retval被定义和初始化,接着是一条用来接受客户程序员输入的语句,最后定义y和g。然而,在C中这些变量都只能在块的开始处定义。

一般说来,应该在尽可能靠近变量的使用点处定义变量,并在定义时就初始化(这是对内建类型的一种格式上的建议,而内建变量的初始化是可选的)。这是出于安全性的考虑,通过减少变量在块中的生命周期,就可以减少该变量在块的其他地方被误用的机会。另外,程序的可读性也增强了,因为读者不需要跳到块的开头去确定变量的类型。

6.3.1 for循环

在C++中,经常看到for循环的计数器直接在for表达式中定义:

6.3 清除定义块 - 图3

上述这些语句是一种重要的特殊情况,这可能使那些刚接触C++的程序员感到迷惑不解。

变量i和j都是在for表达式中直接定义的(在C中不能这样做),然后它们就可以作为一个变量在for循环中使用。这给程序员带来很大的方便,因为从上下文中我们可以清楚地知道变量i、j的作用,所以不必再用诸如i_loop_counter之类的名字来定义一个变量,以清晰地表示这一变量的作用。

然而,如果想把变量i、j的生命期扩展到for循环之外,就会有一些问题[2]

在第3章中指出,while语句和switch语句也允许在它们的表达式内定义变量,尽管这种用法远没有for循环重要。

要注意局部变量会屏蔽其封闭块内的其他同名变量。通常,使用与全局变量同名的局部变量会使人产生误解,并且也易于产生错误[3]

小作用域是良好设计的指标。如果一个函数有好几页,也许正在试图让这个函数完成太多的工作。如果用更多细化的函数,不仅更有用,而且更容易发现错误。

[1]在标准C的升级版本C99中,可以像C++一样,在某一块的任意地方定义变量。

[2]C++标准草案一个更早版本中,允许变量生命期扩展到包含for循环块的作用域。有些编译器仍旧这样实现,但它是不恰当的,所以我们的代码只有我们把块限制在for循环中时才可移植。

[3]Java语言认为这种做法不妥,将其认为是出错。