Что такое точка символических констант?

У меня возникли проблемы с пониманием того, что является пунктом 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?

Использование символической константы для определения размера массивов значительно упрощает изменение кода в одном месте, вместо того, чтобы искать весь конкретный номер, встроенный в код.