Effective C++_笔记_条款02_尽量以const、enum、inline替换#define
(整理自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被累加两次
1: template<typename T>
2: inline void callWithMax(const T& a , const T& b)
3: {
4: f(a > b ? a : b) ;
5: }