Что такое точка символических констант?
У меня возникли проблемы с пониманием того, что является пунктом Symbolic Constants в 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;
}
}
Ответы
Ответ 1
Компилятор (pre) знает, что символические константы не изменятся. Он заменяет значение константы во время компиляции. Если "константа" находится в переменной, она обычно не может понять, что переменная никогда не изменит значение. В результате, скомпилированный код должен прочитать значение из памяти, выделенной для переменной, что может сделать программу немного медленнее и больше.
В С++ вы можете объявить переменную как const
, которая сообщает компилятору почти то же самое. Вот почему символические константы недоверчивы в С++.
Ответ 2
Один хороший пример того, почему именованные константы полезны, можно найти в превосходной книге Кернигана и Пайка "Практика программирования " (1999).
§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 должен быть МИНРОУ.
Ответ 3
Переменные локально привязаны к структуре, в которой они объявлены. Конечно, вы можете использовать переменные вместо символических констант, но это может занять много работы. Рассмотрим приложение, которое часто использует радианы. Символьная константа #define TWO_PI 6.28
будет иметь большое значение для программиста.
Ответ 4
Джонатан сделал хороший аргумент в том, почему вы хотели бы использовать символические константы в C (и на любом другом языке программирования, BTW).
Синтаксически, в C это отличается от С++ и многих других языков, потому что он сильно ограничивает то, как вы можете объявить такую символическую константу. Так называемые const
квалифицированные переменные не учитывают это, как в С++.
- Вы можете использовать макрос, который определяется для любого константного выражения: целочисленные или постоянные с плавающей запятой, выражения адресов статических переменных и некоторые формы выражения, которые вы формируете из них. Они обрабатываются только на этапе предварительной обработки компилятора, и вы должны быть осторожны при использовании в них сложных выражений.
- Yo может объявлять целочисленные константные выражения в виде целочисленных констант перечисления, таких как
enum color { red = 0xFF00, green = 0x00FF00, blue = 0x0000FF };
. Они используются только для ограниченного использования, поскольку они имеют тип int
. Таким образом, вы не будете охватывать все диапазоны значений, которые вы могли бы захотеть с ними.
- Вы также можете увидеть целые символьные константы, такие как
'a'
или L'\x4567'
, в качестве предопределенных символических констант, если хотите. Они переводят абстрактную концепцию (значение символа "a" ) в кодировку исполняющей платформы (ASCII, EBDIC, что угодно).
Ответ 5
Джонатан представляет отличный пример пользователя символических констант.
Возможно, что программа, использованная в вопросе, не самая лучшая для ответа на этот вопрос. Однако, учитывая программу, символические константы могут иметь больше смысла в следующем случае:
#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?
Использование символической константы для определения размера массивов значительно упрощает изменение кода в одном месте, вместо того, чтобы искать весь конкретный номер, встроенный в код.