编写高质量代码:改善C++程序的150个建议(七)

建议13:掌握变量定义的位置与时机

  在C/C++代码中,变量是一个不得不提的关键词。变量在程序中起着不同寻常的作用。所有的代码中肯定离不开各种类型变量的影子,既有内置类型的,又有自定义类型的。虽然常见,但使用它也是有一定的技巧与玄机的。掌握了这些技巧与玄机,在合适的时机将变量定义在合适的位置上,会使代码变得更具可读性与高效性。

  C++规则允许在函数的任何位置定义变量,当程序执行到变量定义的位置,并接收到这一变量的定义时,就会调用相应的构造函数,完成变量的构造。当程序控制点超出变量的作用域时,析构函数就会被调用,完成对该变量的清理。而对象的构造和析构不可避免地会带来一定的开销,无论该变量在程序中有没有发挥作用,所以建议在需要使用变量时再去定义。

  分析下面代码片段中定义的函数:

  1. std::string ChangToUpper(const std::string& str)  
  2. {  
  3.  using namespace std;  
  4.  string upperStr;  
  5.  if (str.length() <= 0 )  
  6.  {  
  7.    throw error("String to be changed is null");  
  8.  }  
  9.  ...    // 将字符变为大写  
  10.  return upperStr;  
  11. }

  在上面的代码中,变量upperStr定义的时机有点早。如果输入字符串为空,函数抛出异常,这个变量就不会被使用。所以,如果函数抛出了异常,就要为upperStr的构造与析构付出代价,而这些代价完全完全是可以避免的。所以,变得精明些,把握变量定义的时机:尽量晚地去定义变量,直到不得不定义时。代码如下所示。

  1. std::string ChangToUpper(const std::string& str)  
  2. {  
  3.  using namespace std;  
  4.  if (str.length() <= 0 )  
  5.  {  
  6.    throw error("String to be changed is null");  
  7.  }  
  8.  string upperStr;  
  9.  ...    // 将字符变为大写  
  10.  return upperStr;  
  11. }

  关于变量定义的位置,建议变量定义得越“local”越好,尽量避免变量作用域的膨胀。这样做不仅可以有效地减少变量名污染,还有利于代码阅读者尽快找到变量定义,获悉变量类型与初始值,使阅读代码更容易。

  针对“变量名污染”,最臭名昭著的例子就是在VC 6.0环境的for语句中声明变量i:

  1. for( int i=0; i<N; i++)  
  2. {  
  3.   ...// do something  
  4. }  
  5. ... // some code  
  6. for( int i=0; i<M; i++)  
  7. {  
  8.   ...// do another thing  
  9. }

  上述代码在VC 6.0中是不能通过编译的,编译器会提示变量i重复定义。不熟悉VC 6.0环境的人肯定会很诧异。这是因为在VC 6.0中,i的作用域超出了本身的循环。幸好,微软意识到了这个问题,在其后续的VC++系列产品中,i的作用域重新被限定在了for循环体中。

  不过在这条规则中,还有一个小小的例外,如下所示:

  1. for (int i = 0; i < 1000000; ++i)  
  2. {  
  3.     ClassName obj;  
  4.     obj.DoSomething();  
  5. }

  以上变量的定义遵循了“尽可能晚,尽可能local”的规则,但是ClassName的构造和析构却因此被调用了1 000 000次。更高效的方式就是将obj的定义放在循环之外,构造函数和析构函数的调用次数则会减少到1次:

  1. ClassName obj;  
  2. for (int i = 0; i < 1000000; ++i)  
  3. {  
  4.     obj.DoSomething();  
  5. }

  请记住:

  在定义变量时,要三思而后行,掌握变量定义的时机与位置,在合适的时机于合适的位置上定义变量。尽可能推迟变量的定义,直到不得不需要该变量为止;同时,为了减少变量名污染,提高程序可读性,尽量缩小变量的作用域。

建议14:小心typedef使用中的陷阱

  typedef本来是很好理解的一个概念,但是因为与宏并存,理解起来就有点困难了。再加上部分教材以偏概全,更是助长了错误认识的产生。某些教材介绍typedef时会给出类似以下的形式,但是缺少进一步的解释:

  1. typedef string NAME;  
  2. typedef int AGE;

  这种形式让我不由地想起C语言中著名的宏定义:

  1. #define MAC_NAME string  
  2. #define MAC_AGE   int

  因为二者的声明方式太相似了,所以很多人习惯用#define的思维方式来看待typedef,认为应当把int与AGE看成独立的两部分。实际情况是怎样的呢?首先分析下面的代码片段:

  1. typedef int* PTR_INT1;  
  2. #define int* PTR_INT2  
  3. int main()  
  4. {  
  5.      PTR_INT1 pNum1, pNum2;  
  6.      PTR_INT2 pNum3, pNum4;  
  7.      int year = 2011;  
  8.      pNum1 = &year;  
  9.      pNum2 = &year;  
  10.      pNum3 = &year;  
  11.      pNum4 = &year;  
  12.      cout<<pNum1<<" "<<pNum2<<" "<<pNum3<<" "<<pNum4;  
  13.      return 0;  
  14. }

  输出为:2011 2011 2011 0E8951241。通过程序执行结果可以看出typedef与#define的不同:typedef后面是一个整体声明,是不能分割的部分,就像整型变量声明int i;,只不过typedef声明的是一个别名。宏定义只是简单的字符串替换,不过,typedef并不是原地扩展,它的新名称具有一定的封装性,更易于定义变量,它可以同时声明指针类型的多个对象,而宏则不能。使用typedef声明多个指针对象,形式直观,方便省事:

  1. char *pa, *pb, *pc, *pd;  //方式1  
  2. typedef char* PTR_CHAR;  
  3. PTR_CHAR pa, pb, pc, pd;  //方式2,直观省事

  除此之外,typedef还有多种用途,下面来看看。

  (1)在部分较老的C代码中,声明struct对象时,必须带上struct关键字,即采用“struct 结构体类型 结构体对象”的声明格式。例如:

  1. struct tagRect  
  2. {  
  3.      int width;  
  4.      int length;  
  5. };  
  6. strcut tagRect rect;

  为了在结构体使用过程中,少写声明头部的struct,于是就有人使用了typedef:

  1. typedef struct tagRect  
  2. {  
  3.       int width;  
  4.       int length;  
  5. }RECT;  
  6. RECT rect;

在现在的C++代码中,这种方式已经不常见,因为对于结构体对象的声明已经不需要使用struct了,可以采用“结构体类型 结构体对象”的形式。

  (2)用typedef定义一些与平台无关的类型。例如在标准库中广泛使用的size_t的定义:

  1. #ifndef _SIZE_T_DEFINED  
  2. #ifdef  _WIN64  
  3. typedef unsigned __int64    size_t;  
  4. #else  
  5. typedef _W64 unsigned int   size_t;  
  6. #endif  
  7. #define _SIZE_T_DEFINED  
  8. #endif

  (3)为复杂的声明定义一个简单的别名。这一点将在建议93中详细介绍。它可以增强程序的可读性和标识符的灵活性,这也是它最突出的作用。

  在typedef的使用过程中,还必须记住:typedef在语法上是一个存储类的关键字,类似于auto、extern、mutable、static、register等,虽然它并不会真正影响对象的存储特性,如:

typedef static int INT2; //不可行,编译将失败

  编译器会提示“指定了一个以上的存储类型”。

  请记住:

  区分typedef与#define之间的不同;不要用理解宏的思维方式对待typedef,typedef声明的新名称具有一定的封装性,更易定义变量。同时还要注意它是一个无“现实意义”的存储类关键字。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值