读书笔记 effective c++ Item 2 尽量使用const,枚举(enums),内联(inlines),不要使用宏定义(define)
这个条目叫做,尽量使用编译器而不要使用预处理器更好。#define并没有当作语言本身的一部分。
例如下面的例子:
#define ASPECT_RATIO 1.653
符号名称永远不会被编译器看到。它可能在源码到达编译器之前被预处理器移除。ASPECT_RATIO 最终不会进入符号表,如果因为这个常量的使用而导致编译错误,会使你非常迷惑,因为错误信息会指向1.653而不是ASPECT_RATIO。如果ASPECT_RATIO被定义在一个不是你自己写的头文件中,你会不知道1.,653来自哪里。,
解决方法是将宏替换成常量:
Const double AspectRatio = 1.653
作为一个语言常量,AspectRatio能够被编译器看到,编译器也肯定能进入到AspectRatio的符号表中。此外,对于浮点常量来说,使用常量比使用宏定义会产生更少的代码。因为预处理器会盲目的将宏定义名称ASPECT_RATIO替换成1.653,这会造成在目标码中1.653的多份拷贝,而常量的使用最多产生一份拷贝。
当用常量替换宏定义的时候,有两种特殊情况值得提一下:
第一种是定义常量指针,因为常量定义会被放到头文件中,很多文件会包含这个头文件,将指针声明成常量,同时将指针指向的内容也声明成常量。为了在头文件中定义一个基于char*的字符串,必须写const两次:
Const char* const authorname = “Scott”
在这里有必要提醒一下使用string对象要优于基于char*的字符串,所以将authorname定义成如下方式更好:
const std::string authorname(“Scott”);
第二种特殊的情况是关于类中指定的常量。为了将常量的作用域限制在类中,必须将其声明成一个成员,为了保证至多只有一份常量的拷贝,你必须将其声明成static 成员:
Class GamePlayer{
Private:
Static const int NumTurns = 5;
Int scores[NumTurns];
}
上面看到的是NumTurns的声明而非定义,c++需要你为你所使用的任何东西(anything)提供一份定义,但是类专属的静态整型常量(intergers,chars,bools)是一个例外,只要你不使用他们的地址,你可以声明并且使用他们而不用提供一个定义。如果你需要取得类专属常量的地址或者你所使用的编译器错误的坚持类专属常量需要一个定义(即使不需要获取地址),你需要提供一个单独的定义:
Const int GamePlayer::NumTurns;
你需要把定义放到实现文件而不是头文件中。因为类专属对象的初始值是在声明时提供的,不允许在定义的时候对其进行初始化。
顺便说一句,不可以使用宏定义为类定义专属常量,因为宏定义没有作用域。一旦一个宏定义被定义,它就在余下的编译中有效(只要它没有被undefed)。这意味者宏定义不能当作类专属常量,它们也不能用来提供任何类型的封装,例如,没有私有的#define.
旧的编译器也许不会接受上面的语法,因为在过去,为静态类成员在声明处提供初始值是非法的,此外,只允许整型和常量进行类内部的初始化。一旦上面的语法不能用了,你需要把初始化值放在定义处。
Class costEstimate{
Private:
Static const double FudgeFactor;
}
Const double costEstimate::FudgeFactor=1.35;
这是你任何时候需要做的,唯一的例外是在类编译过程中你需要一个类常量值,例如在类中声明一个数组,在编译过程中需要知道数组的大小。这时候在类内部为静态整型常量值指定初始值是被禁止的(这是不正确的),补偿的做法是使用”enum hack”.这种技术利用了一个事实:枚举类型的值可被用在需要整型值的地方,所以可以如下定义:
Class Gameplayer
{
Private:
Enum{NumTurns=5};
Int scores[NumTurns];
}
Enum hack技术值得被了解,有以下几个原因:
第一, enum hack的行为在一些情况下更像宏定义而不是const,有时候这也是你所需要的。例如:取得const的地址是合法的,但获取枚举的地址是不合法的,同样的,获取宏定义的地址是不合法的。如果你不想让其他人获取指向整型常量的指针或者引用,枚举是进行这种约束的一个好的方法。同样,虽然好的编译器不会为整型常量分配额外的空间,一个草率的编译器可能会这么做,这是你不愿意看到的。像宏定义一样,枚举永远不会产生这样的不必要的内存分配。
第二, enum hack是很实用的技术,很多代码都会使用到它,因此当你看到它你应给能够识别出来。事实上,enum hack是模板元编程的基本技术。
回到预处理器,#define的另外一个用法是实现一个看上去像函数的宏,但对其调用不会招致额外开销。下面是一个取最大值的例子:
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
这样的宏有许多缺点,想想都头疼。
当你实现这类宏时,你必须记住对宏定义体中的所有参数都要加上括号,否则别人在表达式中调用宏的时候会遇到 麻烦。但是即使你那么做了,你仍然会遇到奇怪的事情
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a is incremented twice
CALL_WITH_MAX(++a, b+10); // a is incremented once
这里a加一的次数取决于a和一个多大的数进行比较。
幸运的是,你可以不必忍受这么无聊的事情。你可以通过定义一个内联函数模板来获得宏定义函数所有的效率并且可预知函数的所有行为,函数也是类型安全的。
template<typename T> // because we don’t
inline void callWithMax(const T& a, const T& b) // know what T is, we
{
F(a>b?a:b);
}
这个宏定义会产生一个函数族,每个函数将相同类型两个对象作为参数,其中较大的调用f,不必给函数体内部的参数加括号,也不必担心参数会被求值多次。并且因为callWithMax是一个函数,它遵循作用域和访问规则。比如,你可以写出一个类的私有内联函数。宏定义却不能够做到。
鉴于consts,enums和inlines的实用性,你可以减少预处理器的使用,但是它并没有被清除,#inlcude仍然是必要的,#ifdef和#ifndef在编译控制上仍然发挥重要作用,还没有到让预处理器退休的时候,但是你绝对可以给它放一个长长的假期。
读书笔记 effective c++ Item 2 尽量使用const,枚举(enums),内联(inlines),不要使用宏定义(define)的更多相关文章
- 读书笔记 effective c++ Item 26 尽量推迟变量的定义
1. 定义变量会引发构造和析构开销 每当你定义一种类型的变量时:当控制流到达变量的定义点时,你引入了调用构造函数的开销,当离开变量的作用域之后,你引入了调用析构函数的开销.对未使用到的变量同样会产生开 ...
- 读书笔记 effective c++ Item 27 尽量少使用转型(casting)
C++设计的规则是用来保证使类型相关的错误不再可能出现.理论上来说,如果你的程序能够很干净的通过编译,它就不会尝试在任何对象上执行任何不安全或无意义的操作.这个保证很有价值,不要轻易放弃它. 不幸的是 ...
- 读书笔记 effective c++ Item 22 将数据成员声明成private
我们首先看一下为什么数据成员不应该是public的,然后我们将会看到应用在public数据成员上的论证同样适用于protected成员.最后够得出结论:数据成员应该是private的. 1. 为什么数 ...
- 读书笔记 effective c++ Item 30 理解内联的里里外外 (大师入场啦)
最近北京房价蹭蹭猛涨,买了房子的人心花怒放,没买的人心惊肉跳,咬牙切齿,楼主作为北漂无房一族,着实又亚历山大了一把,这些天晚上睡觉总是很难入睡,即使入睡,也是浮梦连篇,即使亚历山大,对C++的热情和追 ...
- 读书笔记 effective c++ Item 38 通过组合(composition)为 “has-a”或者“is-implemented-in-terms-of”建模
1. 什么是组合(composition)? 组合(composition)是一种类型之间的关系,这种关系当一种类型的对象包含另外一种类型的对象时就会产生.举个例子: class Address { ...
- 读书笔记 effective c++ Item 4 确保对象被使用前进行初始化
C++在对象的初始化上是变化无常的,例如看下面的例子: int x; 在一些上下文中,x保证会被初始化成0,在其他一些情况下却不能够保证.看下面的例子: class Point { int x,y; ...
- 【Effective C++ 读书笔记】条款02: 尽量以 const, enum, inline 替换 #define
条款02: 尽量以 const, enum, inline 替换 #define 这个条款或许可以改为“宁可以编译器替换预处理器”. 编译过程: .c文件--预处理-->.i文件--编译--&g ...
- 读书笔记 effective c++ Item 24 如果函数的所有参数都需要类型转换,将其声明成非成员函数
1. 将需要隐式类型转换的函数声明为成员函数会出现问题 使类支持隐式转换是一个坏的想法.当然也有例外的情况,最常见的一个例子就是数值类型.举个例子,如果你设计一个表示有理数的类,允许从整型到有理数的隐 ...
- 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库
1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...
随机推荐
- (简单) POJ 1961 Period,扩展KMP。
Description For each prefix of a given string S with N characters (each character has an ASCII code ...
- python--字符串操作(删除,替换)
示例: 替换字符串开头和结尾处的空格 1. [代码][Python]代码 跳至 [1] [全屏预览] view source print? 01 # -*- coding: utf-8 -*- ...
- 9、手把手教你Extjs5(九)使用MVVM特性控制菜单样式
菜单的样式多了,怎么可以灵活的切换是个问题. 在使用标准菜单的时候,在菜单最前面有二个按钮,可以切换到树状菜单和按钮菜单. 在树状菜单的显示区,可以切换换到标准菜单,以及折叠式菜单. 切换到按钮菜单之 ...
- IOS开发之IOS8.0最新UIAlertController 分类: ios技术 2015-01-20 14:24 144人阅读 评论(1) 收藏
最近苹果更新的IOS8 对以前进行了很大的修改, 更新的API也让人捉急,据说iOS 8的新特性之一就是让接口更有适应性.更灵活,因此许多视图控制器的实现方式发生了巨大的变化.比如全新的UIPrese ...
- uboot移植前奏
Tiny4412开发板硬件版本为: 底板: Tiny4412/Super4412SDK 1506 核心板:Tiny4412 - 1412 1.下载u-boot源代码,建立u ...
- chorme加减乘除浮点数处理
在处理简单的1-0.7的时候发现chorme给的结果竟然是0.30000000000000004,瞬间蛋疼了,这数据能用?! 然后去百度找到了简单的两个数的加减乘除,然后修改了下,除了除法都可以多个数 ...
- 安卓 canvas
[转]http://blog.sina.com.cn/s/blog_61ef49250100qw9x.html(easy) [转]http://blog.csdn.net/rhljiayou/arti ...
- CCF 201612-1 中间数
试题编号:201612-1 试题名称:中间数 时间限制:1.0s 内存限制:256.0MB 问题描述 在一个整数序列a1, a2, -, an中,如果存在某个数,大于它的整数数量等于小于它的整数数量, ...
- iOS 之 关闭键盘
//方法一:关闭整个系统的键盘 [[[UIApplication sharedApplication] keyWindow] endEditing:YES]; //方法二:关闭当前页的键盘 [[sel ...
- XML 字符串解析
微信红包发送完成后返回xml字符串,解析过程如下: 1.调用解析: public ActionResult GetEntityFromXml() { string str = @"<x ...