我自己就很少写宏定义,一般都是改别人写好的宏定义,一个原因是我无法掌控宏定义。本书的第一个建议就是少写宏定义,看来我的习惯是正确的?书中建议:在我们想要使用宏时可以想想能不能使用常用(const)、枚举(enum)、内联(inline)等替代。
目录
宏定义似乎不被视为语言的一部分,编译器处理宏定义域与处理代码区的方式不一致,宏定义在程序编译的早期就被处理了,在某些环境下,找不到宏定义的符号。如果宏定义被定义在一个非你所写的头文件内,那对它来自何处感到更加懵逼了,于是你将因为追踪这个错误而浪费时间。我们可以使用 const 来定义常量。
- // const.h
- #ifndef __CONST_H__
- #define __CONST_H__
-
- #include
-
- #define ASPECT_RATIO 1.653
- const double AspectRatio = 1.653;
- const int ArrayCount = 10;
-
- #endif // __CONST_H__
- // const.cpp
- #include "const.h"
-
- int main()
- {
- std::cout << "ASPECT_RATIO = " << ASPECT_RATIO << std::endl;
- std::cout << "AspectRatio = " << ASPECT_RATIO << std::endl;
-
- int a[ArrayCount] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
-
- for(int i = 0; i < ArrayCount; i++)
- std::cout << a[i] << " ";
- std::cout << std::endl;
-
- return 0;
- }
作为一个语言常量,AspectRatio 肯定会被编译器看到,当然就会进入记号表内。此外对浮点常量(floating point constant)而言,使用常量可能比使用宏导致较小量的码,因为预处理器盲目地将宏名称 ASPECT_RATIO 替换为 1.653 可能导致目标码出现多份 1.653,若改用常量 AspectRatio 绝不会出现这种情况。
需要说明的是 const char* const authorName_c 是定义一个常量指针,const std::string authorName_s 才是定义一个字符串常量。
- // const.h
- #ifndef __CONST_H__
- #define __CONST_H__
-
- #include
-
- #define AUTHORNAME "Scott Meyers"
- const char* const authorName_c = "Scott Meyers";
- const std::string authorName_s("Scott Meyers");
-
- #endif // __CONST_H__
- // const.cpp
- #include "const.h"
-
- int main()
- {
- std::cout << "AUTHORNAME = " << AUTHORNAME << std::endl;
- std::cout << "authorName_c = " << authorName_c << std::endl;
- std::cout << "authorName_s = " << authorName_s << std::endl;
-
- return 0;
- }
常量定义在同文件中以便被不同的文件引入,因此有必要将指针声明为常量。若要在头文件内定义一个常量的指针字符串,必须包含两次const,前面的const表示这个指针的地址是常量不可改变,也就是里面存的地址值不能变,第二个const表示指针所指的字符串不可改变。
使用 string 来定义字符串常量会更加的合适,string 是 C++ 中的字符串类,可看作是一种基础数据类型(实际不是基础数据类型,是一个类)。
为了将常量的作用域限制于类中,你必须让它成为类的一个成员;而为确保此常量至多只有一份实体,你必须使用 static 修饰使之成为静态成员。
- // const.h
- #ifndef __CONST_H__
- #define __CONST_H__
-
- #include
-
- class GamePlayer
- {
- public:
- GamePlayer();
- ~GamePlayer();
- private:
- static const int NumTurns = 5; // 定义常量
- int scores[NumTurns]; // 使用常量
- // ...
- };
-
- #endif // __CONST_H__
- // const.cpp
- #include "const.h"
-
- const int GamePlayer::NumTurns;
-
- GamePlayer::GamePlayer()
- {
- }
-
- GamePlayer::~GamePlayer()
- {
- }
-
- int main()
- {
- GamePlayer gp;
- return 0;
- }
然而在 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;。
- // enum.h
- #ifndef __ENUM_H__
- #define __ENUM_H__
-
- #include
-
- class CostEstimate
- {
- public:
- CostEstimate();
- ~CostEstimate();
- private:
- static const double FudgeFactor; // 定义常量
- };
-
- #endif // __ENUM_H__
- // enum.cpp
- #include "enum.h"
-
- const double CostEstimate::FudgeFactor = 1.35;
-
- CostEstimate::CostEstimate()
- {
- }
-
- CostEstimate::~CostEstimate()
- {
- }
-
- int main()
- {
- CostEstimate ce;
- return 0;
- }
不设初值的报错代码:
- class GamePlayer
- {
- public:
- GamePlayer();
- ~GamePlayer();
- private:
- static const int NumTurns; // 定义常量
- int scores[NumTurns]; // 使用常量
- // ...
- };
报错信息:
- In file included from enum.cpp:1:
- enum.h:22:16: error: size of array ‘scores’ is not an integral constant-expression
- 22 | int scores[NumTurns]; // 使用常量
- | ^~~~~~~~
那么这个时候就该枚举出场了,一个属于枚举类型的数值可权充 int 类型被使用。
- // enum.h
- #ifndef __ENUM_H__
- #define __ENUM_H__
-
- #include
-
- class GamePlayer
- {
- public:
- GamePlayer();
- ~GamePlayer();
- private:
- enum { NumTurns = 5 }; // 定义枚举
- int scores[NumTurns]; // 使用常量
- // ...
- };
-
- #endif // __ENUM_H__
- // enum.cpp
- #include "enum.h"
-
- GamePlayer::GamePlayer()
- {
- }
-
- GamePlayer::~GamePlayer()
- {
- }
-
- int main()
- {
- GamePlayer gp;
- return 0;
- }
如示例代码,我们最终是用枚举代替了宏的使用。
枚举的行为某方面说比较像宏而不像常量,有时候这正是你想要的。例如取一个常量类型的地址是合法的,但取一个枚举的地址就不合法,而取一个 宏的地址通常也不合法。如果你不想让别人获得一个指针或引用指向你的某个整数常量,枚举可以实现这个约束。
此外虽然优秀的编译器不会为整数型常量对象设定另外的存储空间(除非你创建一个指针或引用指向该对象),而有的编译器却可能如此,而这可能是你不想要的。枚举和宏一样绝不会导致非必要的内存分配。
去年我写过的一篇内联与宏的区别:
示例代码:
- // inline.h
- #ifndef __INLINE_H__
- #define __INLINE_H__
-
- #include
-
- #define MAX(a, b) ((a) > (b) ? (a) : (b))
-
- template<typename T> // 由于我们不知道模板中 T 的类型时说明,所以使用 常量引用传递的方式定义
- inline T Max(const T& a, const T& b)
- {
- // 按值传递
- return a > b ? a : b;
- }
-
- #endif // __INLINE_H__
这篇更像是对宏定义的批判,因为大部分人对规则模糊,加之宏定义带来的问题很少能顺利解决,所以本书有一条建议就是少用宏定义。同时可以多使用const、枚举、内联来替代宏定义。但是,宏定义也有很大的用武之地,如windows api的新版本接口为了与老版本兼容,会用#define来链接二者。还有什么地方必须用宏定义呢?
我去看荷兰VS美国了,祝大家周末愉快!