Константа — это значение с именем и обещанием, что оно не изменится. C предлагает для этого три механизма, и внутри они устроены совершенно по-разному: один — это переменная, другой — текстовая подстановка, третий — целочисленный тип.
Зачем вообще давать значению имя
Сравните две строки:
double area = 3.14159 * r * r;
double area = PI * r * r;
Вторая лучше по трём причинам, и все они проявятся позже. Она говорит, что означает число. Оно встречается ровно в одном месте, поэтому изменить точность — это одна правка, а не поиск по всему коду. И опечатка в PI — ошибка компиляции, тогда как опечатка в 3.14195 — просто неправильный ответ.
Безымянные числа, разбросанные по коду, называют магическими числами, и избавление от них — одно из самых дешёвых улучшений, которые можно внести в программу на C.
const
Ключевое слово const помечает переменную как доступную только для чтения:
Раскомментируйте присваивание — и компилятор откажется: assignment of read-only variable 'PI'. Эта ошибка на этапе сборки и есть весь смысл.
Переменная const — настоящая переменная. У неё есть тип, поэтому компилятор проверяет, как вы её используете. Она подчиняется области видимости, так что const внутри функции наружу не утекает. У неё есть адрес, поэтому на неё можно взять указатель. И отладчик может показать её имя и значение.
const обязана быть инициализирована прямо в объявлении — второго шанса не будет:
const int limit; /* ошибка (или мусор навсегда): присвоить ей позже уже ничего нельзя */
const int limit = 50; /* правильно */
#define
Альтернатива от препроцессора — макрос:
Это не переменная. Ещё до запуска компилятора препроцессор заменяет каждое вхождение PI в исходнике на символы 3.14159265358979. Компилятор вообще не видит этого имени — именно поэтому у #define нет типа и он не может появиться в отладчике.
Обратите внимание: ни точки с запятой, ни =. Макрос — это сырой текст, поэтому вот такая частая ошибка:
#define MAX_USERS 100; /* обратите внимание на точку с запятой */
int limit = MAX_USERS + 1; /* разворачивается в: int limit = 100; + 1; -> ошибка */
выдаёт ошибку в строке, которая совсем не похожа на определение. Соглашение писать макросы заглавными буквами существует как раз для того, чтобы читатель понимал: здесь происходит подстановка.
Макросы по традиции пишут в UPPER_CASE; переменные const обычно следуют тому же стилю, когда выражают ту же идею.
Константы enum
Третья форма даёт вам набор именованных целых чисел:
Значения начинаются с 0 и растут на единицу, если вы не задали их явно, поэтому MON равно 0, а FRI — 4. Анонимный enum { ... } — распространённая идиома для обычных целочисленных констант.
У констант enum есть реальное преимущество перед обеими альтернативами: это целочисленные константы времени компиляции, понятные компилятору, и они видны отладчику. Подробный рассказ — на странице про перечисления в C.
Что выбрать
| Что нужно | Что использовать |
|---|---|
| Типизированная константа с областью видимости, видимая в отладчике | const |
Размер массива, метка case, ширина битового поля | enum или #define |
| Строковая константа | #define или const char * |
| Константа с плавающей точкой | const double |
| Набор связанных целочисленных значений | enum |
Всё, что должен видеть препроцессор (#if, защита от повторного включения) | #define |
По умолчанию стоит брать const. Используйте #define, когда значение должно существовать до компиляции, и enum, когда нужна целочисленная константа времени компиляции или именованная группа.
По-настоящему это бьёт в одном месте — на размерах массивов:
const int в C — это переменная только для чтения, а не константное выражение времени компиляции. Внутри функции C99 принимает int local[const_size], делая его массивом переменной длины; на уровне файла, в struct или в качестве метки case он отвергается сразу. enum и #define работают везде.
(Это одно из реальных отличий от C++, где const int является константным выражением. Код, переезжающий между языками, на этом спотыкается.)
const и указатели
const и указатели сочетаются тремя способами, и смыслы у них разные. Читайте объявление от имени наружу:
const char *p; /* p указывает на символы, которые нельзя изменять */
char * const q; /* сам q нельзя перенаправить; символы менять можно */
const char * const r; /* нельзя менять ни указатель, ни символы */
Форму const char * вы будете писать чаще всего, потому что это правильный тип для параметра функции, которая читает строку, не изменяя её:
Добавление const к параметру, доступному только для чтения, ничего не стоит и документирует контракт так, что компилятор его соблюдает. Это стоит делать везде.
Строковые литералы фактически константны
Тонкая ловушка, ради которой const и существует:
char *s = "hello"; /* легальный C, но s указывает в память только для чтения */
s[0] = 'H'; /* неопределённое поведение - обычно падение */
Строковые литералы живут в секции исполняемого файла, доступной только для чтения. Запись через такой указатель обычно приводит к segfault. Объявление указателя как const char *s = "hello"; заставляет компилятор отвергнуть присваивание, вместо того чтобы дать программе упасть во время выполнения.
Если нужна изменяемая строка, скопируйте её в массив:
Часто задаваемые вопросы
Как объявить константу в C?
Добавьте const в объявление и сразу задайте значение: const double PI = 3.14159;. После этого компилятор отвергнет любую попытку присвоить ей что-то новое. Более старая альтернатива — макрос препроцессора #define PI 3.14159, который вместо этого выполняет текстовую подстановку.
В чём разница между const и #define в C?
const создаёт настоящую типизированную переменную, о которой знает компилятор: она проверяется по типу, видна в отладчике и подчиняется области видимости. #define — это чистая замена текста ещё до компиляции: без типа, без области видимости и без возможности посмотреть на неё при отладке. Предпочитайте const, если только значение не нужно в контексте, требующем константы времени компиляции.
Можно ли использовать const-переменную как размер массива в C?
Для массива фиксированного размера на уровне файла — нет. const int N = 10; int arr[N]; — это массив переменной длины из C99 (допустим внутри функции, но не на уровне файла и не в структуре). Для настоящего размера времени компиляции используйте #define N 10 или enum { N = 10 };.
Что означает const char *p?
Это указатель на константный char: менять то, куда указывает p, можно, а сам символ по этому адресу — нет. char * const p — противоположность: константный указатель на изменяемый char. const char * const p фиксирует и то, и другое. Читайте объявление от имени наружу.