Effective C++读书笔记(2) 条款02 尽量用const, enum, inline 代替 #define

“这个条款或许应该改为, 尽量使用编译器代替预编译器, 比较好, 因为define或许不被视为语言的一部分.”

​ 为什么这么讲呢? 因为#define的变量几乎不能叫做变量. #define只是一种出现在预处理阶段非常简单粗暴的替换, 对于处理器来讲, 可能根本不知道define存在过.

​ 如:

#define SPACE " "

在这里插入图片描述

​ SPACE 有可能根本没进入记号表(变量记号表), 而是在预处理阶段就已经被移走了. 这时会出现几个问题, 一是如果是对于" "的使用出现了问题, 报的编译错误可能很难理解, 因为并不是对 SPACE 进行报错, 而是对 " " 进行报错, 如果程序规模较大, 那么追踪该错误是非常困难的. 二是, 你没法对 SPACE 进行 DEBUG, 因为它根本就没有进入记号表.

​ 解决的方法就是用一个常量去替换宏.

const char* Space = " ";   // 全大写一般用于宏 这里是变量所以采用大驼峰

作为一个语言上的常量, Space 会理所应当地进入记号表, 就肯定可以被编译器看到, 此时如果报错也是报关于 Space 的错误, 同时 DEBUG 也会更加方便. 并且, 由于预处理器处理 define 是简单粗暴的替换, 所以可能会出现多份 " ", 而使用变量则不会出现这种问题.

​用常量替换 #define 有两种比较特殊的情况需要说明.

  1. 对于常量指针, 一般有必要对指针和指针的解引用进行 const 修饰, 比如:

    const char* const Space = " ";
    

    第一个 const 修饰的是, *Space, 表示 *Space 不可被改变, 即指针指向的内容不能被改变.

    第二个 const 修饰的是, Space, 表示 Space 自身不可被改变, 即该指针不允许再指向别的空间.

  2. 对类内的 const 成员, 推荐加上 static 修饰, 比如:

class test
{
public:
    static const int x = 6;

private:
    static const int num = 5;
};

因为 const 本身表示不可被修改, 如果不加上 static , 那么每个类创建实体时, 都需要不必要地去为一个常量开辟空间, 这是不可取的, 而 static 会保证, 不管有多少实体, 这个常量都只会有一份.

这里还有一些问题, 你是否认为, x 和 num 在类内都是被定义式定义的? 但是其实这些都是声明式, 通常 C++ 会要求你给所有东西都提供一个定义式, 但是对于类内的 static 成员例外, 你可以值提供声明式, 不提供定义式, 但是, 你不可以进行一些特殊操作, 如取地址. 但是如果你坚持要取地址, 或者你的编译器非要你提供一个定义式, 那么你就需要提供定义式. 如下:

const int test::x;

你可能会感到好奇, 为什么没有值, 这是因为你在声明的时候已经提供了值, 所以在定义的时候, 你不可以提供值.

​顺带说一句, define 是无法为类创建专属常量的, 因为 define 不重视作用域(scope), 除非在某处进行了条件编译. 也就是说, 没有 private #define 这种东西, #define 也不能提供任何封装性.

​老的编译器或许不支持上面的在声明时给值, 这时你可以把值放到定义式上.

​但是, 我们仍旧会碰到问题, 比如开辟数组这个问题上, 我们写出如下代码:

#define NUM 30

int main()
{
    int num[NUM] = {0};
    return 0;
}

​ 这样写当然是没有问题, 但是如果我们换成常量呢,

const int Num = 30;
int main()
{
    int num[Num] = {0};
    return 0;
}
// 或者以下代码
class test
{
private:
    static const int Total = 50;
    int num[Total];
};

​ 此时, 有些编译器就会(错误地)报出编译错误, 认为必须要知道初始值, 也就是必须要知道数组开了多大. 我们来分析一下为什么会这样, #define 的处理在预处理阶段, 编译器工作在预处理后, 代表如果粗暴替换了 30, 那么编译器会很明显地知道需要开多大, 而如果是一个变量, 那么就认为不知道要开多大了. 我认为, 这样做不正确, 这样就导致了, 我们没办法去让程序自己根据情况去开多大, 而是必须要提前设置好.

​ 并不是所以的编译器都会这样, 有的编译器是可以这样做的. 对于不支持的编译器, 我们可以用 enum (枚举)的方法达到效果, 如:

private:
    enum
    {
        Total = 50
    };
    int num[Total];
};

​ 因为 enum 更像 #define 而不是 const, 所以可以达到目的, 而且取一个 enum 值的地址是不合法的, 就像取 #define 的值的地址不合法一样, 而取 const 的是合法的. 这表明, 如果你并不想别人拿到这个常量的地址, 你可以使用 enum. 而且, enum 其实是模板编程的基础技术.

​ 接着看预处理阶段, 如果你用 #define 写了一个函数, 如:

#define FUNC(a) a * 3

​ 如果你踩过 #define 的坑, 你看到这个就会感到头痛, 因为它让你想起了那些不好的回忆, 它给你收获就是让你知道, 这样一定会出错的. 如写出以下代码:

int main()
{
    std::cout << FUNC(1 + 5) << std::endl;   // 16
    return 0;
}

​ 你或许认为结果非常简单, 就是 18, 但是这是错误, 因为#define的简单粗暴替换, 导致 a = 1 + 5, 而替换后, 就是 1 + 5 * 3, 由于 * 的优先级更高, 导致, 最后结果不能达到预期, 这就是坑. 我们可以用括号解决这个坑:

#define FUNC(a) ((a) * 3)

​ 但是接下来这个, 就是没法用括号解决的了:

#define MAX(a, b) ((a) > (b) ? (b) : (b))

​ 写出如下代码:

int main()
{
    int a = 5;
    int b = 0;
    MAX(++a, b);
    std::cout << a << " " << b << std::endl;  // a 累加两次
    MAX(++a, b + 10);
    std::cout << a << " " << b << std::endl;  // a 累加一次

    return 0;
}

​ 你发现, a 累加多少次竟然和与谁比较有关, 这是由于单纯的替换导致表达式执行了多次. 而单独为其携程一个函数又非常浪费, 幸运的是, C++ 为我们提供了方法, 让我们既能享受宏的快, 又有函数的可预料行为和类型安全性. 那就是 inline.

​ inline 会为函数提供优化, 使其变得类似于宏, 你不需要加上一堆括号, 也不需要担心参数被计算多次:

inline Max(a, b)
{
    return a > b ? a : b;
}

​ 而且类内的函数, 如果编译器认为需要优化成 inline, 那么他会自己帮你加上 inline.

​ 有了 const ,enum, inline, 你就可以给 #define 放个假了, 但是预编译阶段的 #include , #ifdef/#ifndef 仍被需要.

提醒:

  • 对于单纯常量, 最好使用 const, enum 代替 #define.
  • 对于类似函数的宏, 最好用 inline 代替 #define.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

roseisbule

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值