В чем смысл символических констант? - PullRequest
13 голосов
/ 21 февраля 2011

Мне сложно понять, в чем смысл символических констант в 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;
    }

}

Ответы [ 5 ]

16 голосов
/ 21 февраля 2011

Компилятор (pre) знает, что символические константы не изменятся.Он подставляет значение для константы во время компиляции.Если «константа» находится в переменной, она обычно не может понять, что переменная никогда не изменит значение.В результате скомпилированный код должен прочитать значение из памяти, выделенной для переменной, что может сделать программу немного медленнее и больше.

В C ++ вы можете объявить переменную равной const, чтоговорит компилятору почти то же самое.Вот почему символические константы не одобряются в C ++.

Обратите внимание, однако, что в C (в отличие от C ++) переменная const int является , а не константным выражением.Поэтому попытка сделать что-то вроде этого:

const int a = 5;
int b[a] = {1, 2, 3, 4, 5};

будет работать в C ++, но при этом вы получите ошибку компиляции в C (предполагая, что b должен быть статически связанным массивом).

7 голосов
/ 21 февраля 2011

Один хороший пример того, почему именованные константы полезны, можно найти в превосходной книге Kernighan and Pike (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 должен быть МИНРОУ.

2 голосов
/ 21 февраля 2011

Джонатан высказал хорошую мысль в , почему вы хотели бы использовать символические константы в C (и на любом другом языке программирования, BTW).

Синтаксически, в C это отличается отC ++ и многие другие языки, потому что это очень ограничивает как , вы можете объявить такую ​​символическую константу.Так называемые const квалифицированные переменные не учитывают это, как в C ++.

  • Вы можете использовать макрос, определенный для любого константного выражения: целочисленные константы или константы с плавающей запятой, адресные выражения статических переменных и некоторые формы выражения, которые вы формируете из них,Они обрабатываются только на этапе предварительной обработки компилятора, и вы должны быть осторожны, когда используете в них сложные выражения.
  • Yo может объявлять целочисленные константы в виде целочисленных констант перечисления , таких как enum color { red = 0xFF00, green = 0x00FF00, blue = 0x0000FF };.Они имеют ограниченное использование, так как имеют тип int.Таким образом, вы не охватите все диапазоны значений, которые вам могут понадобиться.
  • Вы также можете увидеть целочисленные символьные константы , такие как 'a' или L'\x4567', в качестве предопределенных символических констант, если хочешь.Они переводят абстрактное понятие (символьное значение «а») в кодировку исполняющей платформы (ASCII, EBDIC и т. Д.).
2 голосов
/ 21 февраля 2011

Переменные локально ограничиваются структурой, в которой они объявлены. Конечно, вы можете использовать переменные вместо символических констант, но это может занять много работы. Рассмотрим приложение, которое часто использует радианы. Символическая константа #define TWO_PI 6.28 будет иметь большое значение для программиста.

0 голосов
/ 16 мая 2018

Джонатан приводит отличный пример пользователя символических констант.

Возможно, что программа, использованная в вопросе, не является лучшей для ответа на этот вопрос.Однако, учитывая программу, символические константы могут иметь больше смысла в следующем случае:

#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?

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...