• 《Effective C++》系列之(1)用常量、枚举、内联替代宏


    前言

            我自己就很少写宏定义,一般都是改别人写好的宏定义,一个原因是我无法掌控宏定义。本书的第一个建议就是少写宏定义,看来我的习惯是正确的?书中建议:在我们想要使用宏时可以想想能不能使用常用(const)、枚举(enum)、内联(inline)等替代。

    目录

    前言

    一、常量替换宏定义

    1.1 定义基础数据类型常量

    1.2 定义字符串常量

    1.3 定义类成员常量

    二、枚举替换宏

    三、内联代替宏

    四、总结


    一、常量替换宏定义

    1.1 定义基础数据类型常量

            宏定义似乎不被视为语言的一部分,编译器处理宏定义域与处理代码区的方式不一致,宏定义在程序编译的早期就被处理了,在某些环境下,找不到宏定义的符号。如果宏定义被定义在一个非你所写的头文件内,那对它来自何处感到更加懵逼了,于是你将因为追踪这个错误而浪费时间。我们可以使用 const 来定义常量。

    1. // const.h
    2. #ifndef __CONST_H__
    3. #define __CONST_H__
    4. #include
    5. #define ASPECT_RATIO 1.653
    6. const double AspectRatio = 1.653;
    7. const int ArrayCount = 10;
    8. #endif // __CONST_H__

    1. // const.cpp
    2. #include "const.h"
    3. int main()
    4. {
    5. std::cout << "ASPECT_RATIO = " << ASPECT_RATIO << std::endl;
    6. std::cout << "AspectRatio = " << ASPECT_RATIO << std::endl;
    7. int a[ArrayCount] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    8. for(int i = 0; i < ArrayCount; i++)
    9. std::cout << a[i] << " ";
    10. std::cout << std::endl;
    11. return 0;
    12. }

            作为一个语言常量,AspectRatio 肯定会被编译器看到,当然就会进入记号表内。此外对浮点常量(floating point constant)而言,使用常量可能比使用宏导致较小量的码,因为预处理器盲目地将宏名称 ASPECT_RATIO 替换为 1.653 可能导致目标码出现多份 1.653,若改用常量 AspectRatio 绝不会出现这种情况。

    1.2 定义字符串常量

            需要说明的是 const char* const authorName_c 是定义一个常量指针,const std::string authorName_s 才是定义一个字符串常量。

    1. // const.h
    2. #ifndef __CONST_H__
    3. #define __CONST_H__
    4. #include
    5. #define AUTHORNAME "Scott Meyers"
    6. const char* const authorName_c = "Scott Meyers";
    7. const std::string authorName_s("Scott Meyers");
    8. #endif // __CONST_H__
    1. // const.cpp
    2. #include "const.h"
    3. int main()
    4. {
    5. std::cout << "AUTHORNAME = " << AUTHORNAME << std::endl;
    6. std::cout << "authorName_c = " << authorName_c << std::endl;
    7. std::cout << "authorName_s = " << authorName_s << std::endl;
    8. return 0;
    9. }

             常量定义在同文件中以便被不同的文件引入,因此有必要将指针声明为常量。若要在头文件内定义一个常量的指针字符串,必须包含两次const,前面的const表示这个指针的地址是常量不可改变,也就是里面存的地址值不能变,第二个const表示指针所指的字符串不可改变。

            使用 string 来定义字符串常量会更加的合适,string 是 C++ 中的字符串类,可看作是一种基础数据类型(实际不是基础数据类型,是一个类)。

    1.3 定义类成员常量

            为了将常量的作用域限制于类中,你必须让它成为类的一个成员;而为确保此常量至多只有一份实体,你必须使用 static 修饰使之成为静态成员。  

    1. // const.h
    2. #ifndef __CONST_H__
    3. #define __CONST_H__
    4. #include
    5. class GamePlayer
    6. {
    7. public:
    8. GamePlayer();
    9. ~GamePlayer();
    10. private:
    11. static const int NumTurns = 5; // 定义常量
    12. int scores[NumTurns]; // 使用常量
    13. // ...
    14. };
    15. #endif // __CONST_H__
    1. // const.cpp
    2. #include "const.h"
    3. const int GamePlayer::NumTurns;
    4. GamePlayer::GamePlayer()
    5. {
    6. }
    7. GamePlayer::~GamePlayer()
    8. {
    9. }
    10. int main()
    11. {
    12. GamePlayer gp;
    13. return 0;
    14. }

            然而在 const.h 头文件中的 static const int NumTurns = 5; 是声明式而非定义式,const.cpp 文件中 const int GamePlayer::NumTurns; 是定义式。通常 C++ 要求你对你所使用的任何东西提供一个定义式,但如果它是个类专属常量又是 static 且为整数类型(例如 integers, chars, bools),则需特殊处理。只要不取它们的地址,你可以声明并使用它们而无须提供定义式。 

            定义式需要放到实现文件(const.cpp)而非头文件(const.h)。由于类常量已在声明时获得初值(例如先前声明 NumTurns 时为它设初值 5),因此定义时不可以再设初值。

            注意:宏无法创建一个类成员常量,因为宏并不重视作用域,也就是说没有所谓私有宏这样的东西。而当然常量成员变量是可以被封装的,如示例中NumTurns 就是。

    二、枚举替换宏

            “类内初值设定” 也只允许对整数常量进行。如果你的编译器不支持上述语法可以在定义式中设定初值。 如下面示例代码中 const double CostEstimate::FudgeFactor = 1.35;

    1. // enum.h
    2. #ifndef __ENUM_H__
    3. #define __ENUM_H__
    4. #include
    5. class CostEstimate
    6. {
    7. public:
    8. CostEstimate();
    9. ~CostEstimate();
    10. private:
    11. static const double FudgeFactor; // 定义常量
    12. };
    13. #endif // __ENUM_H__

       

    1. // enum.cpp
    2. #include "enum.h"
    3. const double CostEstimate::FudgeFactor = 1.35;
    4. CostEstimate::CostEstimate()
    5. {
    6. }
    7. CostEstimate::~CostEstimate()
    8. {
    9. }
    10. int main()
    11. {
    12. CostEstimate ce;
    13. return 0;
    14. }

            不设初值的报错代码:

    1. class GamePlayer
    2. {
    3. public:
    4. GamePlayer();
    5. ~GamePlayer();
    6. private:
    7. static const int NumTurns; // 定义常量
    8. int scores[NumTurns]; // 使用常量
    9. // ...
    10. };

            报错信息:

    1. In file included from enum.cpp:1:
    2. enum.h:22:16: error: size of array ‘scores’ is not an integral constant-expression
    3. 22 | int scores[NumTurns]; // 使用常量
    4. | ^~~~~~~~

            那么这个时候就该枚举出场了,一个属于枚举类型的数值可权充 int 类型被使用。

    1. // enum.h
    2. #ifndef __ENUM_H__
    3. #define __ENUM_H__
    4. #include
    5. class GamePlayer
    6. {
    7. public:
    8. GamePlayer();
    9. ~GamePlayer();
    10. private:
    11. enum { NumTurns = 5 }; // 定义枚举
    12. int scores[NumTurns]; // 使用常量
    13. // ...
    14. };
    15. #endif // __ENUM_H__
    1. // enum.cpp
    2. #include "enum.h"
    3. GamePlayer::GamePlayer()
    4. {
    5. }
    6. GamePlayer::~GamePlayer()
    7. {
    8. }
    9. int main()
    10. {
    11. GamePlayer gp;
    12. return 0;
    13. }

            如示例代码,我们最终是用枚举代替了宏的使用。

            枚举的行为某方面说比较像宏而不像常量,有时候这正是你想要的。例如取一个常量类型的地址是合法的,但取一个枚举的地址就不合法,而取一个 宏的地址通常也不合法。如果你不想让别人获得一个指针或引用指向你的某个整数常量,枚举可以实现这个约束。

            此外虽然优秀的编译器不会为整数型常量对象设定另外的存储空间(除非你创建一个指针或引用指向该对象),而有的编译器却可能如此,而这可能是你不想要的。枚举和宏一样绝不会导致非必要的内存分配。

    三、内联代替宏

            去年我写过的一篇内联与宏的区别:

            宏定义与内联函数区别_Thomas_Lbw的博客-CSDN博客_宏定义和内联函数的区别

             示例代码:

    1. // inline.h
    2. #ifndef __INLINE_H__
    3. #define __INLINE_H__
    4. #include
    5. #define MAX(a, b) ((a) > (b) ? (a) : (b))
    6. template<typename T> // 由于我们不知道模板中 T 的类型时说明,所以使用 常量引用传递的方式定义
    7. inline T Max(const T& a, const T& b)
    8. {
    9. // 按值传递
    10. return a > b ? a : b;
    11. }
    12. #endif // __INLINE_H__

    四、总结

            这篇更像是对宏定义的批判,因为大部分人对规则模糊,加之宏定义带来的问题很少能顺利解决,所以本书有一条建议就是少用宏定义。同时可以多使用const、枚举、内联来替代宏定义。但是,宏定义也有很大的用武之地,如windows api的新版本接口为了与老版本兼容,会用#define来链接二者。还有什么地方必须用宏定义呢?

            我去看荷兰VS美国了,祝大家周末愉快!

  • 相关阅读:
    计算机组成原理---第四章指令系统---指令的寻址方式---选择题
    电阻值的优先值
    C++ 中的 enum关键字
    华为云DevCloud平台部署bootdo博客论坛实战【开发者专属集市】
    js+css实现的数据统计类网页ui设计,html页面前端源码
    P27 含并行连结的网络 GoogLeNet / Inception V3
    【牛客 11259H】Scholomance Academy——生成函数、分治NTT、Bostan-mori 算法
    网络安全—综合渗透测试-CVE-2019-15107-Webmin远程代码执行
    数据管理框架以及数据战略制定方法
    挂件板死机刷固件
  • 原文地址:https://blog.csdn.net/weixin_44120785/article/details/128164154