[Effective C++]解读20211116

[Effective C++]解读20211116,第1张

[Effective C++]解读2021/11/16

二·尽可能使用const,enum,inline,替换#define

        从标题可以看出,C语言与C++的优质写法上的区别,在C语言的工程中,我们经常可以看到大量的#define出的宏定义,但是在C++上,相较于用于盲目替换的#define,更提倡开发者使用限定更为便捷、清晰的const关键字。其实这样解释听起来虽然直接,但是不如”尽量用编译器替代预处理器“这种说法更全面。

        因为预处理器其实并不属于代码的一部分,所以例如这样的宏定义,也许从未被编译器看见.

#define    CUTEGIRL    1.123

在介绍C语言的文章中提到过, 宏定义的内容被称之为记号。而这段定义的记号名称其实在代码编译之前,就被预处理器调离代码,进行另外一套处理流程。所以在代码进行编译时,其实上面的记号并没有进入到记号表中。至于它到底去了哪里,其中对我们来讲并不算很重要,只需要它很有可能并不会进入到你想要它进入的位置。

        所以,本书更推荐在定义一个常量时,尽量用下面的方法:

const double CuteGirl = 1.123

可以见到,其实除了命名上面的差别,二者其他功能并没有差别,一样的不可改变的常量,相同的命名内容。后者的命名规约符合于一个变量的命名,前者全部大写的命名方式则是标准的宏定义的命名。

        在后者的写法中,开发者是声明了一个不可变的“变量”并定义了一个常量值,既然它成为了一个“变量”,那它自然是由编译器进行编译,也就会进入记号表内。并且这样的定义一定会出现在你在用到它时的附近位置,而不会出现用#define定义时,有可能出现的定义在其他可能非你编写的头文件内并且你需要耗费时间去追踪这一项定义的情况。同时,预处理器的特点时盲目的将宏名称替换为值,也就是每当出现一个CUTEGIRL预处理器就会将它替换为1.123,这时可能导致你的目标码出现过多的1.123,而如果你使用CuteGirl则只会在其作用域内产生作用,不会造成大面积盲目替换的情况。       

        对于const的妙用有很多,当const和指针联系起来时,也会产生一个有些难懂并容易混淆的情况——指针常量和常量指针。

 一般常量的定义是会放在头文件中以便于在不同的源码使用,因此有必要将指针本身声明为一个const型,即指针的地址可以视为一个常量,这个地址不可以被修改,开发者不能通过修改指针地址的方式来修改指针所指的内容。例如:当你想在头文件(地址为常量)中定义一个常量的字符串,你必须写两次const去限定它:

const char* const authorName

 当然,在C++中有更适合用于字符串的关键字,你只需要运用string对象更加方便也更加适宜。例如:

const std::string authorName("Hello World")

但运用指针去定义一个字符串的方式在C语言中更加重要,因为尽管C语言加入了的头文件,但它对字符串的运用依然局限。

        另一个需要注意的是class(类)专属常量。在一个类中,要使一个常量的作用域仅限制在class中,必须让它成为class中的一个成员常量,而确保这个常量最多只有一份实体,即它只需要开辟一次内存空间并在后续使用时只需要通过传递来使用,我们必须用static关键字去限制他。例如

class GamePlayer{
private:
static const int NumTurns = 5;    //常量声明
int scores[NumTurns];              //常量的使用
...
};

注:static const int NumTurns = 5 其实是一个声明式,这种写法并没有什么问题,虽然C++要求你对你使用的任何东西提供一个定义式,但如果它是class专属常量且又时static和整型,则可以特殊处理。只要不取它们地址,你可以直接声明并使用它们,但如果你想取它们的地址(&NumTurns)或你的编译器坚持要看到一个定义式,你必须另外提供一个定义式给编译器:

const int  GamePlayer::NumTurns; //NumTurns的定义式 在声明式中已经赋值5 定义式中不需赋值

注:C++中不可使用#define去创建一个class的专属常量,因为#define并不重视作用域,它只会在定义时开始到代码结尾都作用,而不会在某个特定范围产生作用,除非在某处被#undef掉,同时#define并不具备封装性(关键字private),而const成员变量时可以封装的。若对此处有疑问,可以去学习参考一下虚函数、纯虚函数,接口类的相关内容。

这样的写法适合于较新的编译器,如果你使用的编译器较为老旧,则会出现报错,因为旧的编译器不允许static成员变量在其声明式上获得初值, 所以不得以会将初值放到定义式里面。而如果你将上面的代码写成下面的形式:

class GamePlayer {
private:
    static const int GameTurn;
    int scores[GameTurn];
};

const int Game::GameTurn = 5;

如果没有int scores[GameTurn];,这段代码就可以用不支持类内初始化的编译器通过了。

但因为 int scores[GameTurn]; 用到了GameTurn,而GameTurn的值不能确定。所以会报如下错误。

enum_hack.cpp:5: error: array bound is not an integer constant

 当你面对这种进退两难的境况时,你就需要换一种方法去声明这个常量,这就是另一个比较重要的内容——enum hack 值。enum hack 的行为在某些方面更接近#define,比如enum hack的地址和#define同样不能被取得。

在这种特点下,当你不想让别人获得一个指针指向你的某个整数常量,enum就可以帮你实现这个目的。

class Game {
private:
    enum {GameTurn = 10};
    int scores[GameTurn];
};

回归标题,尽量避免使用#define的另一个原因,就是在夹带着实参的类函数宏的使用上,会产生很多你不想要的错误。

//以a和b的较大值为参数调用函数f

#define THEMAX(a,b)    f((a)>(b)?(a):(b))

像这样的丑陋的宏我在C语言编写的车载系统中见过不要太多次,这样的东西让我看到就极为头痛,更有甚者会写成这样:

#define TEST    a>f(e)?(f(a)||f(b)):(f(c)&&f(d))

更更有甚者,还会在其中给你套上几个移位和更多的逻辑运算,再调试他们逻辑时,几乎会让人疯掉。

所以再C++中,我们尽量避免再预处理器中实现这种复杂的运算,如果你一定想要使用这种方法,你还会再接下来的编写中不得不解决括号带来的各种问题。不过幸运的是,我们可以选择更好的方式去实现THEMAX的宏。,且同时还具备宏的效率及一般函数的所有可预料行为和类型安全性——template inline函数(模板内联函数)。

template

inline void TheMax(const T&a,const T&b)
{
    f(a>b?a:b);
}

通过const , inline函数,和enum枚举,可以大大降低我们对#define的需求,但这并不能完全替代#define的功用,但是我们应选择尽量必要的时候才去使用#define

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5503094.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-12
下一篇 2022-12-13

发表评论

登录后才能评论

评论列表(0条)

保存