读书笔记 effective c++ Item 2 尽量使用const,枚举(enums),内联(inlines),不要使用宏定义(define)

小编 2026-06-22 阅读:1318 评论:0
这个条目叫做,尽量使用编译器而不要使用预处理器更好。#define并没有当作语言本身的一部分。例...

这个条目叫做,尽量使用编译器而不要使用预处理器更好。#define并没有当作语言本身的一部分。

例如下面的例子:

1 #define ASPECT_RATIO 1.653

符号名称永远不会被编译器看到。它可能在源码到达编译器之前被预处理器移除。ASPECT_RATIO 最终不会进入符号表,如果因为这个常量的使用而导致编译错误,会使你非常迷惑,因为错误信息会指向1.653而不是ASPECT_RATIO。如果ASPECT_RATIO被定义在一个不是你自己写的头文件中,你会不知道1.,653来自哪里。,

解决方法是将宏替换成常量:

1 Const double AspectRatio = 1.653

作为一个语言常量,AspectRatio能够被编译器看到,编译器也肯定能进入到AspectRatio的符号表中。此外,对于浮点常量来说,使用常量比使用宏定义会产生更少的代码。因为预处理器会盲目的将宏定义名称ASPECT_RATIO替换成1.653,这会造成在目标码中1.653的多份拷贝,而常量的使用最多产生一份拷贝。

当用常量替换宏定义的时候,有两种特殊情况值得提一下:

第一种是定义常量指针,因为常量定义会被放到头文件中,很多文件会包含这个头文件,将指针声明成常量,同时将指针指向的内容也声明成常量。为了在头文件中定义一个基于char*的字符串,必须写const两次:

1 Const char* const authorname = “Scott”

在这里有必要提醒一下使用string对象要优于基于char*的字符串,所以将authorname定义成如下方式更好:

1 const std::string authorname(“Scott”);

第二种特殊的情况是关于类中指定的常量。为了将常量的作用域限制在类中,必须将其声明成一个成员,为了保证至多只有一份常量的拷贝,你必须将其声明成static 成员:

1 Class GamePlayer{2 3 Private:4 5 Static const int NumTurns = 5;6 7 Int scores[NumTurns];8 9 }

上面看到的是NumTurns的声明而非定义,c++需要你为你所使用的任何东西(anything)提供一份定义,但是类专属的静态整型常量(intergers,chars,bools)是一个例外,只要你不使用他们的地址,你可以声明并且使用他们而不用提供一个定义。如果你需要取得类专属常量的地址或者你所使用的编译器错误的坚持类专属常量需要一个定义(即使不需要获取地址),你需要提供一个单独的定义:

1 Const int GamePlayer::NumTurns;

你需要把定义放到实现文件而不是头文件中。因为类专属对象的初始值是在声明时提供的,不允许在定义的时候对其进行初始化。

顺便说一句,不可以使用宏定义为类定义专属常量,因为宏定义没有作用域。一旦一个宏定义被定义,它就在余下的编译中有效(只要它没有被undefed)。这意味者宏定义不能当作类专属常量,它们也不能用来提供任何类型的封装,例如,没有私有的#define.

旧的编译器也许不会接受上面的语法,因为在过去,为静态类成员在声明处提供初始值是非法的,此外,只允许整型和常量进行类内部的初始化。一旦上面的语法不能用了,你需要把初始化值放在定义处。

1 Class costEstimate{2 3 Private:4 5 Static const double FudgeFactor;6 7 }8 9 Const double costEstimate::FudgeFactor=1.35;

这是你任何时候需要做的,唯一的例外是在类编译过程中你需要一个类常量值,例如在类中声明一个数组,在编译过程中需要知道数组的大小。这时候在类内部为静态整型常量值指定初始值是被禁止的(这是不正确的),补偿的做法是使用”enum hack”.这种技术利用了一个事实:枚举类型的值可被用在需要整型值的地方,所以可以如下定义:

 1 Class Gameplayer 2  3 { 4  5 Private: 6  7 Enum{NumTurns=5}; 8  9 Int scores[NumTurns];10 }

Enum hack技术值得被了解,有以下几个原因:

第一,  enum hack的行为在一些情况下更像宏定义而不是const,有时候这也是你所需要的。例如:取得const的地址是合法的,但获取枚举的地址是不合法的,同样的,获取宏定义的地址是不合法的。如果你不想让其他人获取指向整型常量的指针或者引用,枚举是进行这种约束的一个好的方法。同样,虽然好的编译器不会为整型常量分配额外的空间,一个草率的编译器可能会这么做,这是你不愿意看到的。像宏定义一样,枚举永远不会产生这样的不必要的内存分配。

第二,  enum hack是很实用的技术,很多代码都会使用到它,因此当你看到它你应给能够识别出来。事实上,enum hack是模板元编程的基本技术。

回到预处理器,#define的另外一个用法是实现一个看上去像函数的宏,但对其调用不会招致额外开销。下面是一个取最大值的例子:

1 #define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

这样的宏有许多缺点,想想都头疼。

当你实现这类宏时,你必须记住对宏定义体中的所有参数都要加上括号,否则别人在表达式中调用宏的时候会遇到 麻烦。但是即使你那么做了,你仍然会遇到奇怪的事情

1 int a = 5, b = 0;2 CALL_WITH_MAX(++a, b); // a is incremented twice3 CALL_WITH_MAX(++a, b+10); // a is incremented once

这里a加一的次数取决于a和一个多大的数进行比较。

幸运的是,你可以不必忍受这么无聊的事情。你可以通过定义一个内联函数模板来获得宏定义函数所有的效率并且可预知函数的所有行为,函数也是类型安全的。

1 template<typename T>                                                // because we don’t2 inline void callWithMax(const T& a, const T& b) // know what T is, we3 4 {5 6   F(a>b?a:b);7 }

这个宏定义会产生一个函数族,每个函数将相同类型两个对象作为参数,其中较大的调用f,不必给函数体内部的参数加括号,也不必担心参数会被求值多次。并且因为callWithMax是一个函数,它遵循作用域和访问规则。比如,你可以写出一个类的私有内联函数。宏定义却不能够做到。

鉴于consts,enums和inlines的实用性,你可以减少预处理器的使用,但是它并没有被清除,#inlcude仍然是必要的,#ifdef和#ifndef在编译控制上仍然发挥重要作用,还没有到让预处理器退休的时候,但是你绝对可以给它放一个长长的假期。

 


作者:HarlanC

博客地址:http://www.cnblogs.com/harlanc/
个人博客: http://www.harlancn.me/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接

如果觉的博主写的可以,收到您的赞会是很大的动力,如果您觉的不好,您可以投反对票,但麻烦您留言写下问题在哪里,这样才能共同进步。谢谢!

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

热门文章
  • 机房智能化温湿度解决方式之POE供电以太网温湿度传感器

    机房智能化温湿度解决方式之POE供电以太网温湿度传感器
    机房智能化温湿度解决方式之POE供电以太网温湿度传感器 北京盈创力和电子科技有限公司 智能型TCP网口温湿度记录仪 北京IP网络温湿度记录仪厂家,北京盈创力和 北京智能型TCP网口温湿度记录仪IP网络温湿度记录仪是一种新型的基于TCP/IP协议双绞线以太网标准温湿度采集模块,利用它可以实现现场温度值、相对湿度值的采集,同时利用其自身的RJ45通信接口可以方便地和机房监控主机或交换机集线器进行联网。 工作于-40℃~85℃工业级带...
  • Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering

    Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering
    Problem Statement 我们考虑一个具有马尔可夫性质、非线性、非高斯的状态空间模型(State Space Model):对于一个时间序列上的观测结果{yt,t∈N}\\{ y_t , t \\in N \\}{yt​,t∈N},我们认为每个观测结果yty_tyt​的生成依赖于一个无法直接观察的隐变量xt∈{xt,t∈N}x_t \\in \\{x_t , t \\in N \\}xt​∈{xt​,t∈N},即:p(...
  • HTTP状态保持的原理

    HTTP状态保持的原理
    a)在用户登录之后,浏览器返回响应的时候会在响应中添加上cookieb)浏览器接收到cookie之后会自动保存c)当用户再次请求同一服务器中的其他网页的时候,浏览器会自动带上之前保存的cookied)服务接收到请求之后可以请 request 对象中取到cookie 判断当前用户是否登录  Http是无状态的,就是连接时数据互通,关闭后...
  • Hive 系统函数及示例

    Hive 系统函数及示例
    查看所有系统函数 show functions; 函数分类 内置函数【系统函数】 数学函数: floor、round、ceil、cos、log2等 字符串函数: length、reverse、trim、lower、get_json_object、repeat等 收集函数: size 转换函数: cast 日期函数: year、month、datediff、date、date_add等 条件函数: coalesce、case…w...
  • CSRF的原理和防范措施

    CSRF的原理和防范措施
    a)攻击原理:i.用户C访问正常网站A时进行登录,浏览器保存A的cookieii.用户C再访问攻击网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数iii.而攻击网站B在访问网站A的时候,浏览器会自动带上网站A的cookieiv.所以网站A在接收到请求之后可判断当前用户是登录状态,所以...
标签列表