二、尽量以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;
}是不是好理解多了! 类似的还可以在类中定义私有的内联函数,而宏不能。
本文详细阐述了在C语言编程中,使用#define的潜在问题及改进方法,强调了采用const和enum的重要性,以提高代码的可读性和减少调试困难。文章深入分析了指针和引用的const用法,类专属常量的应用,以及宏定义的合理使用,提供了实用的编程技巧。
2133

被折叠的 条评论
为什么被折叠?



