您的位置:首页 > 编程语言 > C语言/C++

effective C++ 读后感(二) 尽量以const, enum, inline替换 #define

2014-04-01 16:21 477 查看

二、尽量以const, enum, inline替换 #define

#define在C语言中是常见的,用于定义常量。如:

#define PI 3.14

但PI会在编译之前就被预处理了,代码中的PI会被3.14替代。所以PI并没有进入记号表内。因此如果代码出现问题的话,错误信息里可能会提到3.14,而没有PI,这会增大查找问题的难度。而且这也会给调试带来一些麻烦。

比如说下面的例子:

#include <iostream>
using namespace std;
#define PI 3.14
double getCircleArea(double r) {
return PI * r * r;
}

int main() {
int r = 1;
cout << getCircleArea(r) << endl;
return 0;
}


例子很简单,就是计算圆面积。定义圆周率为常量。
我们用gdb进行调试,在第10行设置一个断点,进入getCircleArea函数中。如下图:



如果我们想看一下PI倒底是多少,于是输入print PI,结果却查不到:



原因也是PI没有进入记号表中。

说到底,其实就是预处理器在做怪。为了避免这类问题,应该尽量少用或不用预处理器的功能,让编译器替换预处理器。

而如果将PI的定义改成

const double PI = 3.14;
将不会出现上面的问题了。

用#define也很容易出错,往往这些错误难以被发现。比如下面的代码本来要计算两个数的乘积:

#include <iostream>
using namespace std;
#define M 10
#define N M + 100

int main() {
cout << M * N << endl;
}
编译器看到的只是10 * 10 + 100,所以最后结果为200. 如果仍要用#define而得到正确结果,需要将N定义为:
#define N (M + 100)


而使用const就完全不用担心这类问题。


用const代替#define有几点是要注意的

一、关于指针和引用的const

const作用于指针时,有两种方式,一是常量指针,如:

const int *p 或
int const *p
表示指针指向的对象为const,但指针还是可以改变指向的地址。
二是指针常量,如:

int * const p


表示指针指向的地址不能变,但指向的对象内容可以变。

这里教大家一个记忆的方法:如果把const翻译成“常量”,把*号翻译成“指针”,那么const int *(或int const *)表类型去掉就是“常量指针”,int *const就是“指针常量”了。而且常量指针顾名思意就是“指向一个常量的指针”,指针常量就是“是一个指针的常量”,这样意思和写法就都记住了。

于是如果我们要定义一个既指向常量,指向地址又不能变的指针那就可以这样:

const char* const name = "warrenfws";

引用也可以加const修饰,但由于引用本身指向的地址是不能改变的,所以只有一种用法:

int b;
const int &a = b;


这样能保证a不会修改b的值。这种用法在很多方法传参中经常用到。

二、类的专属常量

常量可以定义在类里面,作为类的专属常量(而#define不可以),如下面的例子:

#include <iostream>
using namespace std;

class Apple {
static int count;
const int id;
public:
Apple(): id(count ++) {}
void print() {
cout << id << endl;
}
};
int Apple::count = 0;

int main() {
Apple apple[5];
for(int i=0; i<5; i++)
apple[i].print();
return 0;
}


在这个例子中,id中常量,在对象初始化之后被赋值了,之后就不能再修改了。所以const能提供更好的封装性。

但是如果在编译期就需要用到一个类的常量值(比如申明一个大小由一个常量定义的数组),用const就可能行不通了(因为有些编译器不允许在属性定义的时候对其进行申明,也就是说下面的代码在有些编译器下可能不能通过)

class Person {
const int nameLen = 10;
char name[nameLen];
};


幸运的是,我们可以通过"enum hack"来解决:
class Person {
enum { nameLen = 10 };
char name[nameLen];
};


虽然enum更像#define, 我们不能取一个enum的地址。但它相比于#define能提供更好的封装性。

如何解决宏定义

#define还有一个很大的用处就是定义宏。它的缺点在前面提到过,如果合理加上小括号,宏的运行结果很有可能出人意料。而用内联函数或内联的函数模板可以绕过这个问题,并且同时还与宏同样高效。

如定义一个取最大值的宏:

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


注意小括号。是不是觉得很麻烦?而且太容易出错了。
而用内联函数模板来实现的话:

template<typename T>
inline T max(const T &a, const T &b) {
return a > b ? a : b;
}
是不是好理解多了! 类似的还可以在类中定义私有的内联函数,而宏不能。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐