《Effective C++》:尽量用const和inline取代#define(1)

来源:互联网 发布:医院用软件 编辑:程序博客网 时间:2024/04/29 17:51

     Shifting from C to C++!!
     学习C++也有一段时间了,写此笔记,主要是一是激励监督自己不间断的学习,而是加深知识的理解与记忆!
     C++的难学,不仅在其广博的语法,以及语法背后的语意,以及语意背后的深层思考,它不仅仅是一门语言,而且是用语言创造智慧的语言。 C++的难学,还在于它提供了四种不同但是相辅相成的程序设计模型:procedural-based 基于过程、object-based 基于对象、object-oriented 面向对象和generic paradigm--通用模式。

      C基本上只是C++的一个子集,所有在C中的技巧在C++中都可以使用。举个例子,所有熟悉C的程序员都知道C中没有引用&,是一个pointer to pointer,这在C++程序员眼里是比较滑稽的,为什么不用reference to pointer取而代之呢?多以C++继承了C后还有许多优点。
       Efficient 1:尽量用const和inline取代#define
       还有一种说法是:“尽量以编译器(complier)取代预处理器(preprocessor)”。
       首先注意的是:
      #define的用法  #define 第一位置 第二位置
      a、只能是三部分,任何一部分不能用空格隔开,否则会出现一些问题!
      b、不替换程序里字符串里的东西;
      c、第一位置必须是合法的标识符;(字母或者下划线开头,中间可以有数字,不能是关键字);
      d、第二位置如果有字符串,必须“”配对;
      e、只替换与第一位置完全相同的标识符;
      使用宏#define的优缺点:
      优点:因为在函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序内同执行完以后,再返回到转去执行该函数前的地方。这种转移操作要求在转去执行前要保存现场并记忆执行的地方,转会后要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。而宏只是在预处理的地方把代码展开,不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个函数更有效。
    但宏也有它的缺点:
    (1)宏不能访问对象的私有成员;

    (2)宏的定义很容易产生二义性(单纯的替换引起的),没有类型的检测

   例如:

#include<iostream>using namespace  std;#define LEN 12#define NAME "Tom" #define NAME1(a) "a"//字符串里的东西不会被替换#define NAME2(a) a  //#define 12an 456   //第一位置必须合法的标识符;#define MAX(a,b) ((a)>(b)?(a):(b))#define TABLE_MULTI1(x) x*x #define TABLE_MULTI(x) ((x)*(x)) int main(){int num=2;cout<<NAME<<endl;cout<<NAME2(123)<<endl;        cout<<TABLE_MULTI1(2+2)<<endl;//希望得到16,可结果却是2+2*2+2=8;就算加上括号还是会有下面的二义性;cout<<TABLE_MULTI(++num)<<endl;        //希望得到3*3=9,事实上VS6.0是3*4=12;VS2005是4*4=16;system("pause");return 0;}

但是使用inline函数,你就可以获得预期的结果和参数型别检验;
内联函数和宏很类似,而本质区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
    (3)就是本书讲到的,我总结为不好找错除Bug!
      例如#define p 3.14
      因为宏是单纯的文本替换,在预处理(文本替换,宏展开,删除注释)就完成了替换,编译器可能没有机会看见p,他可能在编译之前就被预处理器移走了。结果p并没有进入符号表(symbol table),这可能回造成困惑,当你在编译时涉及到此常量的错误时,报错可能会出在3.14,而不是p,如果p定义于一个头文件,该头文件又恰好不是自己写的,那将会是件比较头疼的事,无缘无故出现一个3.14,会蒙圈的。找错的过程中,符号除错器(symbolic debugger)也可能遇到同样的问题,原因相同:程序内所使用的名称并未出现在符号表之中。你将花费大量的时间去追寻该头疼的bug!
      a、什么是符号表?符号表有哪些重要作用?
           符号表是用来记录编译过程中的各种信息的表格。
            符号表的作用:
           >登记编译过程输入和输出信息
           >在语义分析过程中用于语义检查和中间代码生成
           >作为目标代码生成阶段地址分配的依据
      b、 符号表的表项常包括哪些部分?各描述什么?
           >符号表的表项包含两大栏,即名字栏和信息栏;
           >名字栏也叫主栏,存放名字的标示符,称为关键字;
           >信息栏包含许多子栏和标志位,用来记录相应名字的各种不同属性。
改进的方法就是使用const或inline来替换#define;
   (1)定义一般常量和指向常量的指针常量:
      例如:const int *const p=&a;
                 const int num=10;
    (2)class的专属常量:
     如果想让类中之多只有一份,则:
             static const int num=5;
      常量需要再初始化列表里进行初始化;
     当你在class编译期间需要一个常量时,一般编译器会阻止在类中直接赋初值。但是用枚举enum可以完成该功能:

class{   private:   enum{num=5};//enum特殊技法,令num成为5的一个符号名称;  int score[num]; //过关;}

inline函数的好处:

a、获得宏带来的效率(宏没有函数调用带来的额外开销);

b、一般函数的所有可预料行为和类型安全性(type safety);


总结:
   (1)对于单纯常量,最好以const对象或enum替换#defines;

    (2)对于形似函数的宏(macros),最好改用inline函数替换#defines

      有了const和inline两项武器,我们对预处理器(processor)的依赖性就降低了,但任未能消除,当我们能够舍弃#include和#ifdef/#ifndef在编译器控制过程中扮演的重要角色时,这一天才会到来,目前还不是可以让预处理器(preprocessor)全面隐退的时候,但是我们应该明确的给予它更长的假期。

2 0
原创粉丝点击