一、魔数的来源
在源代码编写中,有这么一种情况:编码者在写源代码的时候,使用了一个数字,比如0x2123,0.021f等,他当时是明白这个数字的意思的,但是别的程序员看他的代码,可能很难理解,甚至,过了一段时间,代码的作者自己再看代码的时候也忘记了这个数字代表的含义。于是感叹,虽然不知道这个数字是干什么用的,究竟代表什么,但是编译后的程序可以正常运行,真是“魔术般的数字”,魔数即源于此。
二、魔数的用法
“魔数”有贬义词、中性词、褒义词三种用法,默认为贬义词。
1、贬义词“魔数”
指的是代码中出现的没有说明的数字。代码中突然出现一个没说明用途的数字会让其它阅读代码、维护代码的的人非常难受。例如:const int N = 2073600;
for (int i=0; i<N; i++)
{ ……
看代码的人需要猜2073600是什么意思,而且特别难猜。改成这样就清楚多了:
const int WIDTH = 1920;
const int HEIGHT = 1080;
int totalPixels = WIDTH * HEIGHT;
这里的“魔数”就是指代码中直接出现的数字。现代编程规范比较忌讳这样写代码,一方面看不懂意思,另一方面如果2073600这个数字多次出现,一旦需要修改的时候就需要全部找出来改掉,一旦少改一处就会产生BUG,非常麻烦。我们在编程中要尽可能避免使用“魔数”,例如写3.1416这种数字,也应该改为数学库中的π常数,例如Unity中的Mathf.PI。
2、褒义词“魔数”
最典型的例子就是现代3D游戏之父约翰·卡马克在雷神之锤中的那个魔数:
i = 0x5f3759df – ( i >> 1 );
配合前后的代码,这句代码可以快速计算一个数字的平方根的倒数。具体推导过程比较复杂,涉及到浮点数的原理。
3、中性词“魔数”
某些具有特定格式的文件,喜欢在文件开头写几个特殊的字符以表明自己的身份,以便验明正身。例如常见的几种图片格式的文件:
JPEG (jpg),文件头:FFD8FF
PNG (png),文件头:89504E47
GIF (gif),文件头:47494638
Windows Bitmap (bmp),文件头:424D
如果你用16进制编辑器打开一个文件,它的开头不是FFD8FF,那就不是jpg文件。这个魔数一般会在相关文件标准中进行规定,所有人都要遵守。
三、魔数的弊端
1、代码可读性差
例如float time=1.0f;//小数类型时间=1.0f
float speed=time*2.13f;//小数类型速度=时间*2.13f
如果没有说明,很难猜到那个2.13f的含义,假如它代表加速度,那么修改如下:
#define ACCELERATION (2.13f);/*#定义加速(2.13f)前等于后*/
float speed=time*ACCELERATION;//小数类型速度=时间*加速
这样对于代码阅读者来说更好理解。
2、修改不方便
例如setfontcolor(string,0xFFFFFFFF);/*设置字体颜色(字符串,0xffffffff);设此函数设置一个字符串的颜色*//*等等……*/
setbackcolor(widget,0xFFFFFFFF);/*设置背景色(小部件,0xffffffff);设此函数设置控件背景色*//*等等……*/暂且不说0xFFFFFFFF代表的含义,如果程序中很多地方使用了统一的一个常量,如果要修改值的时候很麻烦,也容易出错。可能有遗漏等等诸多问题。
同样可以改为如下:
static const int WHITE=0xFFFFFFFF;/*静态常量整数类型WHITE=0xFFFFFFFF;WHITE是白色*/
setfontcolor(string,WHITE);/*设置字体颜色(字符串、WHITE);*//*等等……*/
setbackcolor(widget,WHITE);//设置背景色(小部件、WHITE);/*等等……*/
这样程序代码不仅便于阅读,而且要替换他的值,只需要替换一次就好了。
解决魔术数字的方法主要是将这些数字定义为常量,或者枚举类型,或者使用编译器的宏定义(如C/C++的#define)