读书笔记_Effective_C++_条款二:尽量以const, enum, inline替换#define
其实这个条款分成两部分介绍会比较好,第一部分是用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的更多相关文章
- Effective C++学习笔记 条款02:尽量以const,enum,inline替换 #define
尽量使用const替换 #define定义常量的原因: #define 不被视为语言的一部分 宏定义的常量,预处理器只是盲目的将宏名称替换为其的常量值,导致目标码中出现多分对应的常量,而const定义 ...
- NO.2: 尽量以const,enum,inline 替换 #define
1.首先#define 定义不重视作用域(scope),虽然可以#undef控制,但是不美观,还存在多次替换的问题,以及没有任何封装性. 2.const XXX_XX,保证其常量性以及可控的作用域,如 ...
- 读书笔记_Effective_C++_条款二十七:尽量少做转型动作
有关转型的几种做法,已经在早些的博客中写过了.这里先简单回顾一下,再讲一讲effective中对之更深入的阐述. 转型可以按风格可以分成C风格转型和C++风格转型两大类,C风格转型很容易看到,因为我们 ...
- 读书笔记_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); } ...
- 读书笔记_Effective_C++_条款二十九:为“异常安全”而努力是值得的
还是举书上的例子: void PrettyMenu::changeBackground(std::istream& imgSrc) { lock(&mutex); delete bgI ...
- 读书笔记_Effective_C++_条款二十八:避免返回handlers指向对象内部成分
举个例子: class Student { private: int ID; string name; public: string& GetName() { return name; } } ...
- 读书笔记_Effective_C++_条款二十六:尽可能延后变量定义式的出现时间
这个条款从字面意思还是很好理解的,就是在使用这个变量前才去定义,而不是很早就定义了它,而在很后面的时候才去使用.这个条款只适用于对变量声明位置没有要求的语言,比如C++.对于像C或者一些脚本语言,语法 ...
- 读书笔记_Effective_C++_条款二十五: 考虑写出一个不抛出异常的swap函数
在之前的理论上调用对象的operator=是这样做的 void swap(A& x) { std::swap(a, x.a); } A& operator=(const A& ...
- 读书笔记_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 ...
随机推荐
- eclipse安装Hadoop-0.20.2插件
因为在使用Hadoop-0.20.2这个古董,需要使用它自带的eclipse插件,而我最初安装的是现代的eclipse4.10.0. 在经历两天,以及以下种种尝试之后,均以失败告终: 1.网上找适合的 ...
- 配置zsh
.zshrc export ZSH="/root/.oh-my-zsh" ZSH_THEME="robbyrussell" plugins=(git zsh-s ...
- mysql root看不到mysql表
1.首先停止mysql服务:service mysqld stop2.加参数启动mysql:/usr/bin/mysqld_safe --skip-grant-tables & 然后就可以无任 ...
- windows SysinternalsSuite
procdump -ma -i c\dumps 捕获系统所有程序的崩溃 SysinternalsSuite autoruns是个什么鬼
- LeetCode 892 Surface Area of 3D Shapes 解题报告
题目要求 On a N * N grid, we place some 1 * 1 * 1 cubes. Each value v = grid[i][j] represents a tower of ...
- synchronized使用
在一个方法内部使用如下代码: public void m5() { synchronized (Test1.class) { System.out.println("m5"); t ...
- java中使用jdbc配置连接串时mysql 5.6与5.7版本“编码”参数有区别!
在mysql5.6中 java程序使用jdbc时链接字符串应该使用?characterEncoding=utf-8,而5.7版本可以省略,否则可能会有相关的语句执行结果出错! String drive ...
- python time模块总结
常用函数: import time --------------------------------------------------time.time() ...
- java JDBC (二) 防止注入/参数化
package cn.sasa.demo2; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Pr ...
- ubuntu上第一个hello程序
1.终端中输入gedit hello.c ,然后输入程序: 2.使用gcc编译器,编译出在PC上运行的hello可执行程序:gcc ./hello.c -o hello-pc; 3.使用ar ...