我无法理解C中符号常量的含义,我确信它们有一个原因,但我似乎无法理解为什么你不会只使用变量。
#include <stdio.h>
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;
}
}
Vs以上。
#include <stdio.h>
#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;
}
}
答案 0 :(得分:16)
(pre)编译器知道符号常量不会改变。它在编译时替换常量的值。如果“常量”在变量中,通常无法确定变量永远不会改变值。因此,编译后的代码必须从分配给变量的内存中读取值,这会使程序稍微变慢一些。
在C ++中,您可以将变量声明为const
,它告诉编译器几乎相同的东西。这就是为什么符号常量在C ++中不受欢迎。
但请注意,在C(与C ++相对)中,const int
变量不是常量表达式。因此,尝试做这样的事情:
const int a = 5;
int b[a] = {1, 2, 3, 4, 5};
将在C ++中工作,但会在C中遇到编译错误(假设b
应该是一个静态绑定数组)。
答案 1 :(得分:7)
为什么命名常数是有益的一个很好的例子来自Kernighan和Pike(1999)的优秀书The Practice of Programming。
§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。
答案 2 :(得分:2)
变量的作用域是它们声明的结构。当然你可以使用变量而不是符号常量,但这可能需要很多工作。考虑一个经常使用弧度的应用程序。符号常量#define TWO_PI 6.28
对程序员来说具有很高的价值。
答案 3 :(得分:2)
Jonathan在为什么中提出了一个很好的观点,你想在C语言中使用符号常量(以及任何其他编程语言,BTW)。
在语法上,在C中,这与C ++和许多其他语言不同,因为它对如何的限制很大,你可以声明这样的符号常量。所谓的const
限定变量并不像在C ++中那样解释这一点。
enum color { red = 0xFF00, green = 0x00FF00, blue = 0x0000FF };
中。它们仅受限制使用,因为它们被固定为类型int
。因此,您不会涵盖您可能想要的所有值范围。'a'
或L'\x4567'
作为预定义的符号常量。他们将抽象概念(字符值“a”)转换为执行平台的编码(ASCII,EBDIC,等等)。 答案 4 :(得分:0)
Jonathan提供了符号常量用户的一个很好的例子。
问题中使用的程序可能不是回答此问题的最佳程序。但是,给定程序,在下列情况下,符号常量可能更有意义:
#include <stdio.h>
#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
int 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编程变得更加容易和便携。已知stdin
,stdout
和stderr
的符号常量会在各种操作系统实现中发生变化。
使用BUFSIZ为C输入缓冲区定义字符数组通常很有意义。 使用EXIT_FAILURE和EXIT_SUCCESS使代码更具可读性,我不必记住0是失败还是成功。 有人会更喜欢(-1)而不是EOF吗?
使用符号常量来定义数组的大小可以更容易地在一个地方更改代码,而不必遍历搜索代码中嵌入的特定数字。