符号常数的重点是什么?

我无法理解C中符号常量的含义,我确信它们有一个原因,但我似乎无法理解为什么你不会只使用变量。

#include  main() { float fahr, celsius; float lower, upper, step; lower = 0; upper = 300; step = 20; printf("%s\t %s\n", "Fahrenheit", "Celsius"); fahr = lower; while (fahr <= upper) { celsius = (5.0 / 9.0) * (fahr - 32.0); printf("%3.0f\t\t %3.2f\n", fahr, celsius); fahr = fahr + step; } } 

比。

 #include  #define LOWER 0 #define UPPER 300 #define STEP 20 main() { float fahr, celsius; printf("%s\t %s\n", "Fahrenheit", "Celsius"); fahr = LOWER; while (fahr <= UPPER) { celsius = (5.0 / 9.0) * (fahr - 32.0); printf("%3.0f\t\t %3.2f\n", fahr, celsius); fahr = fahr + STEP; } } 

(pre)编译器知道符号常量不会改变。 它在编译时替换常量的值。 如果“常量”在变量中,它通常无法确定变量永远不会改变值。 因此,编译后的代码必须从分配给变量的内存中读取值,这会使程序稍微变慢和变大。

在C ++中,您可以将变量声明为const ,它告诉编译器几乎相同的东西。 这就是为什么符号常量在C ++中不受欢迎的原因。

为什么命名常数是有益的一个很好的例子来自Kernighan和Pike的优秀书籍“编程实践” 。

§1.5幻数

[…]这个摘录自一个程序,用于在24×80光标寻址终端上打印字母频率的直方图,因为有大量的幻数而不必要地不透明:

 ... fac = lim / 20; if (fac < 1) fac = 1; for (i = 0, col = 0; i < 27; i++, j++) { col += 3; k = 21 - (let[i] / fac); star = (let[i] == 0) ? ' ' : '*'; for (j = k; j < 22; j++) draw(j, col, star); } draw(23, 2, ' '); for (i = 'A'; i <= 'Z'; i++) printf("%c ", i); 

该代码包括数字20,21,22,23和27.其中明显相关......或者是它们? 实际上,这个程序只有三个关键数字:24,屏幕上的行数; 80,列数; 26,字母表中的字母数。 但是这些都没有出现在代码中,这使得数字变得更加神奇。

通过在计算中给主要数字命名,我们可以使代码更容易理解。 例如,我们发现数字3来自(80 - 1)/ 26,并且应该有26个条目,而不是27个(一个可以由1个索引的屏幕坐标引起的逐个错误)。 进行其他几个简化,结果如下:

 enum { MINROW = 1, /* top row */ MINCOL = 1, /* left edge */ MAXROW = 24, /* bottom edge (<=) */ MAXCOL = 80, /* right edge (<=) */ LABELROW = 1, /* position of labels */ NLET = 26, /* size of alphabet */ HEIGHT = (MAXROW - 4), /* height of bars */ WIDTH = (MAXCOL - 1)/NLET /* width of bars */ }; ... fac = (lim + HEIGHT - 1) / HEIGHT; if (fac < 1) fac = 1; for (i = 0; i < NLET; i++) { if (let[i] == 0) continue; for (j = HEIGHT - let[i]/fac; j < HEIGHT; j++) draw(j+1 + LABELROW, (i+1)*WIDTH, '*'); } draw(MAXROW-1, MINCOL+1, ' '); for (i = 'A'; i <= 'Z'; i++) printf("%c ", i); 

现在它更清楚主循环的作用; 它是从0到NLET的惯用循环,表明循环遍及数据元素。 draw调用也更容易理解,因为像MAXROW和MINCOL这样的单词提醒我们参数的顺序。 最重要的是,现在可以将程序调整到另一种尺寸的显示器或不同的数据。 这些数字是神秘的,代码也是如此。

修改后的代码实际上并没有使用MINROW,这很有趣; 人们想知道剩下的1中哪一个应该是MINROW。

变量的局部范围是它们声明的结构。当然,你可以使用变量而不是符号常量,但这可能需要做很多工作。 考虑经常使用弧度的应用程序。 符号常量#define TWO_PI 6.28对程序员来说具有很高的价值。

Jonathan明确指出了为什么要在C语言中使用符号常量(以及任何其他编程语言,BTW)。

从语法上讲,在C中,这与C ++和许多其他语言不同,因为它对如何声明这样的符号常量有很大限制。 所谓的const限定变量并没有像在C ++中那样解释这一点。

  • 您可以使用定义为任何常量表达式的宏:整数或浮点常量,静态变量的地址表达式以及从中形成的某些表达forms。 这些仅由编译器的预处理阶段处理,当您在其中使用复杂表达式时必须小心。
  • Yo可以以整数枚举常量的forms声明整数常量表达式,例如enum color { red = 0xFF00, green = 0x00FF00, blue = 0x0000FF }; 。 它们仅受限制使用,因为它们被修复为int类型。 因此,您不会涵盖您可能想要的所有值范围。
  • 如果您愿意,您可能还会看到整数字符常量,'a'L'\x4567'作为预定义的符号常量。 他们将抽象概念(字符值“a”)转换为执行平台的编码(ASCII,EBDIC,等等)。

Jonathan提供了符号常量用户的一个很好的例子。

问题中使用的程序可能不是回答这个问题的最佳程序,但是,考虑到在下列情况下,程序符号常量可能更有意义:

 #include  #define FAHRENHEIT_TO_CELSIUS_CONVERSION_RATIO 5.0 / 9.0 #define FAHRENHEIT_TO_CELSIUS_ZERO_OFFSET 32.0 #define FAHRENHEIT_CELSIUS_COMMON_VALUE -40.0 #define UPPER 300.0 #define STEP 20.0 main() { float fahr, celsius; printf("%s\t %s\n", "Fahrenheit", "Celsius"); fahr = FAHRENHEIT_CELSIUS_COMMON_VALUE; while (fahr <= UPPER) { celsius = (fahr - FAHRENHEIT_TO_CELSIUS_ZERO_OFFSET) * (FAHRENHEIT_TO_CELSIUS_CONVERSION_RATIO); printf("%3.0f\t\t %3.2f\n", fahr, celsius); fahr = fahr + STEP; } } 

可能这使得更容易理解为什么符号常量可能有用。

该程序包括stdio.h ,一个相当常见的包含文件,让我们看一下stdlib.h定义的一些符号常量。 此版本的stdio.h来自Xcode。

 #define BUFSIZ 1024 /* size of buffer used by setbuf */ #define EOF (-1) #define stdin __stdinp #define stdout __stdoutp #define stderr __stderrp 

让我们看一下stdlib.h定义的两个符号常量。

 #define EXIT_FAILURE 1 #define EXIT_SUCCESS 0 

这些值可能因系统而异,但使用它们使得在C中编程变得更加容易和便携。 已知stdinstdoutstderr的符号常量会在各种操作系统实现中发生变化。

使用BUFSIZ为C输入缓冲区定义字符数组通常很有意义。 使用EXIT_FAILURE和EXIT_SUCCESS使代码更具可读性,我不必记住0是失败还是成功。 有人会更喜欢(-1)超过EOF吗?

使用符号常量来定义数组的大小使得在一个地方更改代码变得更加容易,而不必遍历搜索代码中嵌入的特定数字。