其实这个条款分成两部分介绍会比较好,第一部分是用const和enum替换不带参的宏,第二部分是用inline替换带参的宏。

第一部分:用const和enum替换不带参宏

宏定义#define发生在预编译期,而const,enum定义的常量发生在编译期,两者的重要差别在于编译期里的变量是进符号表的,而预编译期的宏是简单的替换,不进符号表。因此,const, enum定义的常量具有以下优势:

(1)支持类型检查

(2)支持访问权限

第(1)条优势,其实在Visual Studio编译器也已经对宏也引入了类型检查了,但不是所有的编译器都这样;第(2)条优势是指可以把这些常量放在类中,从而避免了全局的作用域,而宏定义只能是全局的(全局变量在多线程中容易出问题,一份优秀的代码里是几乎不出现全局变量的),所以这条优势其实是const和enum替换宏定义最好的理由。在书中还提到了,用const和enum定义的常量名还会出现在编译器的提示信息里,而宏定义在编译器中显示的就不是宏定义名了,而直接是一个数字量了,这样就不方便调试了。

那么什么时候用const,什么时候用enum呢?const适合于单个常量,比如double const PI = 3.1415926,而enum适合于一组相关的常量,比如星期:

enum DayOfWeek
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
};

这段枚举定义了Sunday = 0, Monday = 1, …, Saturday = 6(去掉DayOfWeek也行,这时就定义了匿名的枚举类型了)。

第二部分:用inline替换带参的宏

不带参的宏还说得过,带参的宏本身就是一个坑,可谓是bug四伏,一个不小心,就掉坑里了。举个最简单的例子:

#define square(a) a * a

在main函数中调用时,可能会这样square(3),计算的是3的平方,这是OK的,但如果换成是square(1+2),计算的还是3的平方吗?注意这时编译器解释成1 + 2 * 1 + 2,这样2 * 1的优先级高一些,所以先做了,这就出问题了。

好的,这是常见的错误,有些程序员可能认为多加括号就行,比如:

#define square(a) (a) * (a)

这的确可以避免上面所说的优先级的问题,但万一调用时是这样写的呢?

int main()
{
    int v = 3;
    squre(++v);
}

本意是想求4的平方,但编译器会翻译成(++v)*(++v),这样v就被加了两次,这个结果肯定不是你想要的!

一句话,带参的宏很容易出问题,特别是针对复合操作(一句话做了不止一件事情)时,bug频出。

解决这个问题的方法是:用函数替换!因为函数调用时,若实参是表达式,总是会先计算这个表达式的值,再去进行调用的,不会出现宏展开时的bug。

template <class T>
T square(const T& v)
{
    return v * v;
}

就是一种比较好的解决方案(注意这里v不用加括号了,也不用担心参数被求值多次,而且提供了可靠的作用域限制),但函数调用有一个保存现场和恢复现场的过程(本质是栈的压入和弹出),频繁地调用会导致性能低下,解决方法是在函数前面加上inline关键字,像这样:

template <class T>
inline T square(const T& v)
{
    return v * v;
}

这样就告诉编译器了,我想牺牲空间换时间——把这段函数体部分直接替换到原代码中,就不要保存现场和恢复现场了。但这里注意,inline并不是强制的,就算你用了inline,编译器也不一定100%地进行代码替换,比如递归函数,编译器会直接忽略掉你加的inline。所以,inline只是向编译器建议进行代码内联而已,inline适合于函数体本身短小(无递归)且频繁调用的场景。

读书笔记_Effective_C++_条款二:尽量以const, enum, inline替换#define的更多相关文章

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

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

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

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

  3. 读书笔记_Effective_C++_条款二十七:尽量少做转型动作

    有关转型的几种做法,已经在早些的博客中写过了.这里先简单回顾一下,再讲一讲effective中对之更深入的阐述. 转型可以按风格可以分成C风格转型和C++风格转型两大类,C风格转型很容易看到,因为我们 ...

  4. 读书笔记_Effective_C++_条款二十四: 若所有参数皆需类型转换,请为此采用non-member函数

    class A { private: int a; public: A(int x) :a(x){} A operator*(const A& x) { return A(a*x.a); } ...

  5. 读书笔记_Effective_C++_条款二十九:为“异常安全”而努力是值得的

    还是举书上的例子: void PrettyMenu::changeBackground(std::istream& imgSrc) { lock(&mutex); delete bgI ...

  6. 读书笔记_Effective_C++_条款二十八:避免返回handlers指向对象内部成分

    举个例子: class Student { private: int ID; string name; public: string& GetName() { return name; } } ...

  7. 读书笔记_Effective_C++_条款二十六:尽可能延后变量定义式的出现时间

    这个条款从字面意思还是很好理解的,就是在使用这个变量前才去定义,而不是很早就定义了它,而在很后面的时候才去使用.这个条款只适用于对变量声明位置没有要求的语言,比如C++.对于像C或者一些脚本语言,语法 ...

  8. 读书笔记_Effective_C++_条款二十五: 考虑写出一个不抛出异常的swap函数

    在之前的理论上调用对象的operator=是这样做的 void swap(A& x) { std::swap(a, x.a); } A& operator=(const A& ...

  9. 读书笔记_Effective_C++_条款二十三:宁以non-member、non-friend替换member函数

    有下面一种情况 class A { private: int a; int b; public: A(int x, int y) :a(x), b(y){} void a_display(){ cou ...

随机推荐

  1. [No0000EB]C# 数组(Array)

    数组是一个存储相同类型元素的固定大小的顺序集合.数组是用来存储数据的集合,通常认为数组是一个同一类型变量的集合. 声明数组变量并不是声明 number0.number1.....number99 一个 ...

  2. C和C指针小记(五)-指针类型

    1.指针常量(pointer constant) 一般是没有这个概念的,指针类型的常量理解起来可以看着指针类型的常量,常用 0xff123456 表示,我们一般不会这么做.因为程序员一般无法事先知道计 ...

  3. “段错误(segment fault)”、“非法操作,该内存地址不能read/write” 非法指针解引用造成的错误。

    小结: 1. “段错误(segment fault)”.“非法操作,该内存地址不能read/write”非法指针解引用造成的错误. <程序员的自我修养 : 链接.装载与库> Q 我写的程序 ...

  4. omitting directory何意

    使用cp命令拷贝目录时,若该目录下有子目录 需加参数 cp -r 加目标目录到路径下

  5. Appium入门(5)__ Appium测试用例(1)

    步骤为:启动AVD.启动Appium.写用例(python).执行 一.启动Android模拟器                   二.启动Appium Server 双击appium图标启动,配置 ...

  6. Ubuntu 下Anaconda3出现 conda:command not found(未找到命令)

    问题:anaconda: command not found解决方案:打开Terminal 1.使用命令:sudo apt install vim 安装vim文本编辑器2.使用命令:vim ~/.ba ...

  7. python摸爬滚打之day14----内置函数,递归函数

    1.匿名函数 用一句话实现的简单函数. ret = lambda x : x ** 2      即 函数名 = lambda 形参 : 返回值 print(ret(5))  ----> 25 ...

  8. python摸爬滚打之day11----函数闭包,迭代器

    1.函数名 函数名就是一个变量名, 函数名存储的是该函数的内存地址.    函数名都可以进行哪些应用? 函数名可以赋值给其他的变量; 函数名可以作容器里的元素使用; 函数名可以当做形参传进另一函数; ...

  9. FastDFS的使用

    1.FastDFS 1.1. 什么是FastDFS? FastDFS是用c语言编写的一款开源的分布式文件系统.FastDFS为互联网量身定制,充分考虑了冗余备份.负载均衡.线性扩容等机制,并注重高可用 ...

  10. basename 命令

    basename命令的作用是获取脚本的名字 basename 作用:从给定的包含绝对路径的文件名中去除路径,然后返回剩下的文件名 [root@salt-client- sh1]# basename / ...