变量是 C/C++代码的重要组成部分,几乎所有的 C/C++代码都存在变量的影子,既有内置类型的,也有自定义类型的。虽然变量很常见,但合理和高效地使用它们也有一定的玄机和技巧。掌握这些技巧,在合适的时机把变量定义在合适的位置,会提高代码的可读性和高效性。
C89中规定,在执行任何语句之前,在块的开头声明本语句块使用的所有局部变量,否则此语句块将无法通过编译。而 C99 和 C++则没有这样的规定,即在首次使用之前,可在块的任何位置声明变量。这从一定程度上降低了定义一个不适用的变量的概率。
或许你认为不会愚蠢到定义一个不适用的变量,但话不要说得这么绝对。考虑下面这个函数的实现:
//从字符串指定位置处提取后续字符串
string GetSubStr(const string &str, size t iPos)
{
string strSubStr;
if(str.size()<iPost)
{
throw logic_error( "iPos is too larger!");
}
strSubStr = str.substr(iPos);
return strSubStr;
}
在上述这段代码中,对象 strSubStr 并未完全被使用。如果函数抛出异常,你得付出 strSubStr构造和析构代价。所以最好延迟strSubStr对象定义,直到真正需要 strSubStr时再定义,代码如下:
//从字符串指定位置处提取后续字符串
string GetSubStr(const string &str, size t iPos)
{
if(str.size()<iPost)
{
throw logic_error("iPos is too larger! ");
}
string strSubStur;
strSubStr= str.substr(iPos);
return strSubStr;
}
但是上面的函数实现依然不是最优的,strSubStr 虽然定义了,但是未对它进行任何初始化,也就是说在赋值之前 strSubStr 已经进行了default 构造初始化。
更受欢迎的做法是直接通过初始化的方法实现,跳过毫无意义的default 构造过程,代码如下:
//从字符串指定位置处提取后续字符串
string GetSubStr(const string &str, size_t iPos)
{
if(str.size()<iPost)
{
throw logic_ error("iPos is too larger!");
}
string strSubStr(str.substr(iPos));
returm strSubStr;
}
关于变量的位置,建议变量定义得离使用位置越近越好,尽量避免变量作用域的膨胀。通过这种方式可以有效减少命名污染问题,同时可以提高代码的可读性和执行效率。尽量延后变量的定义,直到非得使用变量前的那一时刻。同样应尽量尝试延后变量定义,直到延迟到可给变量初始化位置。因为这样不仅可以避免构造和析构非必需对象,还可以避免毫无意义的 default构造行为。
命名污染是指来自不同模块的全局变量或外部函数的名称重复,从而导致链接失败,或链接后产生错误的执行结果(链接器在静态函数库查找符号时,将按顺序查找静态函数,找到某个匹配的符号后,就不会查找其他函数库中是否含有相同的符号名)。大型项目一般由多个开发人员并行开发,由命名污染造成的程序运行错误是很难发现的。因此,我们在开发之前就要采取必要的措施来避免此类问题。在 C+中通常采用的措施有:命名空间,以及延迟变量定义的位置。
还有一个特殊的地方需要注意,这就是循环。如果变量定义在循环体内使用,那么把它定义于循环体内还是循环体外呢?也即下面两种应用模式哪个更好呢?
//方法A:变量定义在循环体外
ClassAobj;
for (inti=0; i<n; i++)
{
Obi=与i相关的某个值;
}
//方法B:变量定义在循环体内
for(inti=0;i<n;i++)
{
ClassAobj=与i相关的某个值;
}
方法A和方法B两种应用模式,操作代价可总结如下。方法A:1个ClassA构造+n 个赋值操作+1 个 ClassA 析构;方法 B:n 个 ClassA 构造+n 个 ClassA 析构。可以看出,当n 很大时,如果赋值操作代价较高,则方法B较好;如果构造和析构代价较高,则方法A比较好。
请谨记
● 在定义变量时,请三思而行。掌握变量定义的位置和时机。尽量延迟变量定义的位置,只有到了不得不定义时,才定义变量。
● 减少变量名污染,提高代码的可读性,减少变量的作用域。