Effective C++学习笔记(Item2)

2021-08-17  本文已影响0人  懒生活

Item2 Prefer consts, enums, and inlines to #defines.

使用预编译的缺点(#define语句的缺点)

缺点1

当你使用如下语句#define PI (3.14),预编译器会把全文出现PI的地方都替换成3.14。对编译器而言,他看不到PI,他看到的只是3.14。如果编译器在编译3.14所在语句发生了错误,编译器的提示语句也只会显示3.1,这对于定位问题很麻烦。上述是作者的意思。但实际情况已经不是这样了,用最新的g++5.4.0尝试下,会发现现在的编译器在发出编译告警中,明确的告诉你发生错误时的语句发生过宏替换,并且会告诉你宏定义在什么地方。先暂时认为作者用的可能是太旧的编译环境,才会有这个提示不全面的问题。

#define PI ("abc")
int main()
{
    int a = PI; return 0;
}

使用g++ main.cpp -std=c++11编译报错,报错信息很详细,并没有作者说的找不着北的情况。

缺点2:

类似缺点1,因为编译器不知道宏定义的符号,所以在调试的时候,调试器不认识宏定义。这个是确实存在的,宏定义是没有办法调试。

#define SWAP(x,y) \
    x= x+y;\
    y=x-y;\
    x=x-y;
int main()
{
    int a = 1,b =2;
    SWAP(a,b); return 0;
}

针对上述的代码,你是没有办法单步进入宏定义内部进行进一步调试的。

缺点3:

有些宏替换会潜藏着风险,尤其是带表达式如++,--之类的宏会让人不省心,经典的例子如下:

#define MAX(a,b) a>b?a:b
int main()
{
    int a= 1, b =2;
int maxone = MAX(a++,b);
return 0;
}

这个例子中预编译器在替换的时候,替换出的结果是a++>b?a++:b,这个应该不是编程人员的本意。

使用const取代宏定义常量

使用const常量取代宏定义的好处

  1. 使用const常量就省去了预处理器进行替换的工作。就不会存在编译器不认识常量符号的问题。
  2. const定义的常量一般被编译器放到text区,特殊情况下也会放到data区,但不管放在哪里,只会放一份。反观宏定义,经过预处理器替换之后,在源代码中会出现多份该常量,编译器会在text区存放多份该常量。编译器可没有足够聪明能把这些重复的常量提炼成一个。至于什么是data区,text,bss区这个在引申章节中说明。
  3. const常量可以定义在类里面让该常量只在这个类里面生效,这个是宏定义不具有的封装性。

使用const常量需要注意的地方

归纳起来主要有两点

定义指向常量的指针为什么需要两个const

正确定义指向指针的常量方式如下const char * const authorName = "Scott Meyers"; 这种写法有两个const限定,一方面限定authorName是常量,编译器会禁止对他的赋值操作。另一方面限定了authorName指向的内容是常量,编译器会禁止诸如authorName[0] = 'x'通过指针篡改字符串的操作。 少任何一个const通常都是你不希望的限定缺失。

const 变量的初始化时机

这块作者直接上来介绍static const的初始化。对于一般人是有些难度的。这里这样子总结或许会更好理解:

  1. 对于non static const变量,初始化需要通过初始化列表进行。(一般网上查的资料都这么说,但是如果用c++11,实际上是支持在类声明中直接进行对const成员进行初始化的。而且对于non static const成员不管是什么类型都是可以直接在类中声明时赋值的。并不需要初始化列表的方式。举例如下
class A
{
    public:
    const string str = "123";
}
int main()
{
    A a;
    cout<<a.str<<endl;
}

如果用g++ main.cpp的方式编译,编译器会编译失败,并提示不支持在这种初始化方式。 如果用g++ main.cpp -std=c++11编译是正常通过的。

  1. 对于static const变量如果是整形,支持在类声明时直接初始化。除此之外必须在类外面定义并初始化。通用的编写方法是这样的
static const string A::CONSTSTR = "hello123";
class A
{
    public:
    static const string CONSTSTR;
}
int main()
{
cout<<A::CONSTSTR<<endl;
return 0;
}

使用enum取代宏定义

enum的使用类似于static const的使用。唯一不同的是编译器保证碰到enum的时候不会分配内存,而是一定只放在text区。
作者还讲到enum是模板元编程的基础技术。这个在Item48中有讲述。

使用inline取代宏定义

inline函数在编译的时候会展开,这样在运行期间也能减少函数的调用开销。相对于宏来说,还更容易调试。

item2的引申

可执行文件的结构

对于任何可执行的文件exe也好,.o文件也好,.out文件也好,在linux下你可以用size a.out的类似命令查看可执行文件a.out的结构组成。
一个可执行文件至少包括3个区段。text区,data区和bss区。

可执行文件的加载过程

在PC机上,可执行文件存放在硬盘,要执行的时候,必须先加载到内存中才能执行。(注意这和单片机系统不一样,单片机的可执行文件存放在ROM中,程序执行的时候可以从ROM中直接开始执行,不需要把所有的可执行代码放到RAM中。)这里只针对PC系统描述。
加载大致过程:

上一篇下一篇

猜你喜欢

热点阅读