Переменная — это именованный участок памяти, который хранит значение, доступное вашей программе для чтения и изменения. В C вы сразу заявляете, какого рода значение она хранит, и этот выбор остаётся неизменным на всё время жизни переменной.
Объявление переменной
Форма такова: тип, имя, точка с запятой:
int age;
double price;
char grade;
Каждая строка резервирует достаточно памяти для соответствующего типа данных и привязывает к ней имя. Пока ничего не сохранено — место есть, но его содержимое — это то, что там оставило всё, что выполнялось раньше.
Значение можно задать в тот же момент, и это называется инициализацией:
Обратите внимание на спецификаторы формата: %d для int, %f для double, %c для char. printf не изучает переданные ему значения — он доверяет вашей строке формата, поэтому несоответствие выведет бессмыслицу, а не ошибку.
Присваивание после объявления
Когда переменная существует, вы меняете её с помощью =:
= в C — это присваивание, а не равенство. score = score + 10 читается как «взять то, что лежит в score, прибавить 10, положить результат обратно в score». Оператор сравнения — ==, и их путаница настолько частый баг, что ей посвящён отдельный раздел на странице логического типа.
Сокращение score += 10 делает то же самое меньшим числом символов; составные операторы присваивания покрывают всю арифметику.
Ловушка неинициализированной переменной
Это самый частый баг новичков в C, и он не порождает ошибки:
Запустите. Вы можете увидеть 15 — правильный ответ. Можете увидеть огромное случайное число. Можете увидеть что-то другое при втором запуске или на другой машине. Всё это «корректное» поведение, потому что чтение неинициализированной локальной переменной — это неопределённое поведение: стандарт C не предъявляет никаких требований к тому, что произойдёт.
В отличие от многих языков, C не обнуляет ваши локальные переменные. Локальная переменная — это кусочек стека, а стек полон остатков от предыдущих вызовов функций. Иногда эти остатки случайно оказываются нулями, и именно это делает баг таким опасным: он часто работает при тестировании и ломается в продакшене.
Исправление — один символ:
Сделайте это привычкой: инициализируйте каждую переменную в точке объявления. Если разумного значения у вас пока действительно нет, 0 или NULL лучше мусора. И компилируйте с -Wall, который ловит большинство случаев:
warning: 'total' is used uninitialized in this function
Исключение — глобальные и static-переменные: они действительно обнуляются автоматически. Полагаться на это нормально, но явная запись = 0 документирует ваше намерение.
Объявление нескольких сразу
Переменные одного типа могут делить объявление, с инициализаторами или без:
Это законно и часто используется для тесно связанных значений вроде x, y, z. Здесь же живёт классическое недоразумение:
int a = 0, b = 0, c = 0; /* все три равны 0 */
int d, e, f = 0; /* только f равно 0 - d и e содержат мусор */
Инициализатор привязывается к одному имени, а не ко всей строке. Когда значения различаются по смыслу, давайте каждому свою строку — так читается лучше и diff чище.
Объявление указателей в одну строку содержит ту же ловушку в более острой форме:
int *p, q; /* p - указатель на int; q - обычный int */
* привязывается к имени, а не к типу. Объявляйте указатели по одному на строку.
Правила и соглашения об именах
Правила компилятора:
- Только буквы, цифры и подчёркивания.
- Нельзя начинать с цифры:
total2можно,2totalнельзя. - Нельзя использовать ключевое слово C: никаких переменных с именами
int,return,for,double. - Регистр важен:
count,CountиCOUNT— три разные переменные. - Имена, начинающиеся с подчёркивания или содержащие двойное подчёркивание, зарезервированы для реализации. Не создавайте их.
int itemCount; /* законно */
int item_count; /* законно - обычный стиль C */
int _count; /* законно, но зарезервировано - избегайте */
int item-count; /* ошибка: '-' - оператор вычитания */
int 3rd_place; /* ошибка: начинается с цифры */
int float; /* ошибка: ключевое слово */
Соглашения, которым следует большая часть кода на C: snake_case для переменных и функций, UPPER_CASE для макросов-констант и имена, говорящие о том, что значение означает, а не какого оно типа. elapsed_ms лучше, чем t; customer_count лучше, чем n2.
Короткие имена по-прежнему уместны там, где область видимости крошечная: i как счётчик цикла понятен всем, и никому не нужно for (int loop_iteration_index = 0; ...).
Где живёт переменная
Место объявления переменной определяет, кто её видит и сколько она живёт. Это область видимости, и коротко всё выглядит так:
Переменная видна от своего объявления до закрывающей фигурной скобки содержащего её блока. Глобальные переменные живут всю программу и видны везде, что звучит удобно и именно поэтому доставляет неприятности: изменить такую может любая функция, и поиск виновника превращается в просмотр всего файла. Предпочитайте локальные переменные и передавайте значения функциям параметрами.
Объявления где угодно (C99 и позже)
В исходном C89 все объявления должны были идти до любой инструкции в блоке:
int main(void) {
int i, sum = 0; /* сначала все объявления */
printf("Starting\n");
for (i = 0; i < 10; i++) { sum += i; }
return 0;
}
Начиная с C99 можно объявлять там, где значение впервые нужно, включая заголовок for:
Это лучший стиль: объявление стоит рядом с первым использованием, и i не может просочиться в остальную часть функции. Компилируйте с -std=c17 (или любым режимом C99+), и это работает везде.
Часто задаваемые вопросы
Как объявить переменную в C?
Напишите тип, затем имя, затем точку с запятой: int age;. Можно сразу задать значение — int age = 30;, — это называется инициализацией и почти всегда именно то, что вам нужно.
В чём разница между объявлением и инициализацией переменной?
Объявление резервирует место и даёт ему имя (int count;). Инициализация кладёт в него значение в момент создания (int count = 0;). Объявленная, но неинициализированная локальная переменная содержит те байты, которые уже лежали по этому адресу, — её чтение является неопределённым поведением.
Какие правила действуют для имён переменных в C?
Только буквы, цифры и подчёркивания; имя не может начинаться с цифры; не может быть ключевым словом вроде int или for; регистр важен, поэтому total и Total — разные переменные. Имена, начинающиеся с подчёркивания, зарезервированы — избегайте их.
Можно ли объявлять переменные в любом месте функции C?
Начиная с C99 — да, объявляйте каждую переменную там, где она впервые нужна. В исходном стандарте C89 все объявления должны были стоять в начале блока, поэтому в старом коде перед любой полезной работой идёт стена объявлений.