(整理自Effctive C++,转载请注明。整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/

这个条款或许改为“宁可以编译器替换预处理器”比较好,因为或许#define不被视为语言的一部分,那正是它的问题所在。#define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。#define的基本用法有两种,都容易出现问题,C++也分别采用不同的方法进行解决。

1. 简单的宏定义

1.1 核心内容

   1: #define PI 3.14

记号名称PI也许从未被编译器看见,在编译器处理源码之前就被预处理器移走了。即记号名称PI根本就没进入记号表内。当你运用此常量但获得一个编译错误时,可能会带来困惑,因为这个错误信息也许会提到3.14而不是PI,当这个宏被定义在非你所写的头文件中,你肯定对3.14以及它来自何处毫无概念。

怎么解决这个问题呢?以一个常量替换上述的宏(#define)就可以了:

   1: const double Pi = 3.14  //大写名称通常用于宏,这里用改变名称写法

作为一个语言常量,Pi肯定会被编译器看到,当然就会就会进入到记号表内。

用常量替换#define,有两种特殊情况。

(1)定义常量指针。由于常量定义式通常被放在头文件内,因此有必要将指针声明为const。

(2)class内的专属常量。为了将常量的作用域限制于class内,必须让它成为class的一个成员;而为确保此常量至多只有一份实体,必须让它成为一个static成员。

1.2 概念补充

为了能够真正理解#define的作用,让我们来了解一下对C语言源程序的处理过程。C++程序从源代码到可执行的二进制文件经历了预处理、编译、汇编和连接几个过程。其中预处理器产生编译器的输入,它实现以下的功能:
(1)文件包含

可以把源程序中的#include 扩展为文件正文,即把包含的.h文件找到并展开到#include 所在处。

(2)条件编译

预处理器根据#ifdef和#ifndef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外,通常把排除在外的语句转换成空行。

(3)宏展开

预处理器将源程序文件中出现的对宏的引用展开成相应的宏 定义,即本文所说的#define的功能,由预处理器来完成。

经过预处理器处理的源程序与之前的源程序有所有不同,在这个阶段所进行的工作只是纯粹的替换与展开,没有任何计算功能,所以在学习#define命令时只要能真正理解这一点,这样才不会对此命令引起误解并误用。

2. 带参数的宏定义

   1: #define N 2+2

   2: void main()

   3: {

   4:    int a=N*N;

   5:    cout << a << endl ; 

   6: }

在此程序中存在着宏定义,宏N代表的字符串是2+2,在程序中有对宏N的使用,一般同学在读该程序时,容易产生的问题是先求解N为2+2=4,然后在程序中计算a时使用乘法,即N*N=4*4=16。但其实结果为8,原来宏展开是在预处理阶段完成的,这个阶段把替换文本只是看作一个字符串,并不会有任何的计算发生,在展开时是在宏N出现的地方 只是简单地使用串2+2来代替N,并不会增添任何的符号,所以对该程序展开后的结果是a=2+2*2+2,计算后=8。若要计算结果为16:

   1: #define N (2+2)

你以为加上括号,问题就能得到圆满解决?在原书上举了一个更加极端的例子:下面这个宏夹着带宏实参,调用函数f,

   1: #define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))

   2: int a = 5 , b = 0 ;

   3: CALL_WITH_MAX(++a,b);//a被累加一次

   4: CALL_WITH_MAX(++a,b+10);//a被累加两次

    在这里,调用f之前,a的递增次数竟然取决于“它和谁比较”!是不是很无聊。有另外一种更好的方法让你可以获得宏带来的效率以及一般函数的所有可预料行为和类型安全性—采用template inline函数。
   1: template<typename T>

   2: inline void callWithMax(const T& a , const T& b)

   3: {

   4:     f(a > b ? a : b) ;

   5: }

这个template产生一整群函数,每个函数都接受两个同类型的对象,并以其中较大者调用f。这里不需要在函数本体中为参数加上括号,也不需要操心参数被核算多次。此外由于callWithMax是个真正的函数,遵守作用域和访问规则。
    总结:
(1)对于单纯常量,最好以const对象或enums替换#defines。
(2)对于形似函数的宏,最好改用inline函数替换#defines。

Effective C++_笔记_条款02_尽量以const、enum、inline替换#define的更多相关文章

  1. 读书笔记_Effective_C++_条款二:尽量以const, enum, inline替换#define

    其实这个条款分成两部分介绍会比较好,第一部分是用const和enum替换不带参的宏,第二部分是用inline替换带参的宏. 第一部分:用const和enum替换不带参宏 宏定义#define发生在预编 ...

  2. Effective C++学习笔记 条款02:尽量以const,enum,inline替换 #define

    尽量使用const替换 #define定义常量的原因: #define 不被视为语言的一部分 宏定义的常量,预处理器只是盲目的将宏名称替换为其的常量值,导致目标码中出现多分对应的常量,而const定义 ...

  3. NO.2: 尽量以const,enum,inline 替换 #define

    1.首先#define 定义不重视作用域(scope),虽然可以#undef控制,但是不美观,还存在多次替换的问题,以及没有任何封装性. 2.const XXX_XX,保证其常量性以及可控的作用域,如 ...

  4. Effective C++阅读笔记_条款2:尽量以const,enum,inline替换#define

    1.#define缺点1 #define NUM 1.2 记号NUM可能没有进入记号表,在调试或者错误信息中,无法知道1.2的含义. 改善:通过const int NUM = 1.2; 2.#dein ...

  5. Effective C++ -----条款02:尽量以const, enum, inline替换 #define

    class GamePlayer{private: static const int NumTurns = 5; int scores[NumTurns]; ...}; 万一你的编译器(错误地)不允许 ...

  6. Effective C++之条款2:尽量以const enum inline替换 #define

    本文的标题也可以改成“用编译器替换预处理器”: const double AspectRatio = 1.653; //最好使用上述代码替换下述代码: #define ASPECT_RATIO 1.6 ...

  7. 条款2:尽量以const, enum, inline替换#define

    原因: 1. 追踪困难,由于在编译期已经替换,在记号表中没有. 2. 由于编译期多处替换,可能导致目标代码体积稍大. 3. define没有作用域,如在类中定义一个常量不行. 做法: 可以用const ...

  8. 条款2:尽量使用const ,enum,inline替换define

    宁可使用编译器而不用预处理器 假设我们使用预处理器: #define ABC 1.56 这标识符ABC也许编译器没看到,也许它在编译器处理源码前就被预处理器移走了,于是“标识符”ABC没有进入标识符列 ...

  9. 条款02:尽量以const,enum,inline替换#define

    目录 1. 总结 2. 使用const常量或enum替换宏常量 class外部的常量指针 class专属常量 1. 总结 对于单纯常量,最好以const常量或enum替换#define 对于宏代码段, ...

  10. Book. Effective C++ item2-尽量使用const, enum, inline替换#define

    ##常规变量 c++里面的#define后面的定义部分,是不算代码的一部分的.所以如果你使用#define: #define ASPECT_RATIO 1.653 你希望这个代号ASPECT RATI ...

随机推荐

  1. Android中弹出对话框,AlertDialog关键代码

    写在这里便于以后查看. Android中弹出对话框的关键代码: btn01.setOnClickListener(new OnClickListener() { @Override public vo ...

  2. 我的Python成长之路---第四天---Python基础(14)---2016年1月23日(寒风刺骨)

    一.生成器和迭代器 1.迭代器 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退. ...

  3. php 登陆动作详解

    <?php class LoginAction extends Action { function index(){ $this->display(); } function do_log ...

  4. 基于visual Studio2013解决C语言竞赛题之0604二维数组置换

     题目

  5. 基于visual Studio2013解决C语言竞赛题之0518回文数

     题目

  6. 全栈project师的悲与欢

    从小米辞职出来创业的两个多月里,通过猎头或自己投简历,先后面试了知乎,今日头条,豌豆荚,美团,百度,App Annie,去哪儿,滴滴打车等技术团队,一二面(技术面)差点儿都轻松的过了,三面却没有毕业那 ...

  7. ant_0105

    在projectA中执行projectB的构件文件.projectA的构件文件内容如下 <?xml version="1.0"?> <!-- 在projectA中 ...

  8. 高级UIKit-02(文件操作)

    [day3_1_Sandbox]:沙箱的介绍 snadbox沙箱沙盒 沙箱根目录下的几个文件夹: 1.应用名称.app存放应用程序的素材 2.Documents:存放应用运行时需要用到的数据(关键性数 ...

  9. 2个NASM开发环境下载以及30篇教程(lostspeed)

    http://download.csdn.net/detail/lostspeed/8958175http://download.csdn.net/detail/lostspeed/8954263 h ...

  10. cocos2d-x游戏开发系列教程-中国象棋05-开始游戏

    前情回顾 通过CCMainMenu的init函数,已经把所有的按钮,棋子都摆放完毕了,但是这个时候,棋子是不能走动的,只有在开始游戏之后才能移动棋子. 点击