其实这个条款分成两部分介绍会比较好,第一部分是用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. ubuntu下能ping通ssh不通的解决思路

    1.首先看看是否能ping通,如果ping不通可能就要考虑网线.路由等问题了: 2.看防火墙是否关闭,sshd是否开启 3.ssh -v 服务器的ip号,根据提示寻找可能的问题: 我的问题在这一步之后 ...

  2. [No000013D].Net 项目代码风格参考

    1. C#代码风格要求 1.1 注释 类型.属性.事件.方法.方法参数,根据需要添加注释. 如果类型.属性.事件.方法.方法参数的名称已经是自解释了,不需要加注释:否则需要添加注释. 当添加注释时,添 ...

  3. 如何使用IcoMoon字体图标

    如何使用IcoMoon字体图标 一,字体图标工具: 1.登录字体图标网站:https://icomoon.io/app/#/select 2.Svg在线编辑工具:https://c.runoob.co ...

  4. MySQL命令:增删与改

    首先是对数据库的修改: --删除一个叫bbb111的数据库 DROP DATABASE bbb111; 对某数据库里一个表的修改: 重命名一张表 -- 三种效果一样的形式 RENAME TABLE 原 ...

  5. zabbix监控Windows-server

    官网下载客户端 https://www.zabbix.com/download 2.创建自定义文件夹(路径任意),我创建的为C:\zabbix 3.将下载的文件存放至该目录,注意操作系统位数,去对应b ...

  6. URL地址中的#

    1.#的涵义 代表网页中的一个位置.井号后面的字符,就是该位置的标识符.比如, http://www.baidu.com/index.html#one 就代表网页index.html的one位置.浏览 ...

  7. jdbc实现分页,需要前端传当前页码

    1.封装一个公共实体类用于返回:实体数据,当前页,总页数,总条数,每页多少条 public class PageInfo<T> { //一页显示的记录数 private int numPe ...

  8. [UI] UI things

    反正我不懂. 但是很酷. https://facebook.github.io/react/ https://cn.vuejs.org/ https://angular.cn/

  9. Python不使用int()函数把字符串转换为数字

    Python不使用int()函数把字符串转换为数字 2018年05月21日 14:18:45 边缘ob边缘ob 阅读数:1035 https://blog.csdn.net/qq_33192555/a ...

  10. 2018/05/11 PHP 设计模式之 适配器模式

    什么是适配器模式? 简单来说,我想买一根充电线,我买一根安卓的?还是买一根苹果的? 我也不确定,因为我以可能会换手机,对于我的形式我也不确定. 所以,我要买一根可以同时适配 安卓/苹果 的线. 所谓适 ...