
Переменные используются для хранения данных в программе. Они требуют объявления перед использованием. Сперва указывается тип переменной (в нашем случае это int — целое), затем её имя (как всякое другое имя, оно должно состоять только из латинских символов, цифр и знаков подчёркивания, причём оно не может начинаться с цифры).
int foo;
Чему сейчас равно значение переменной foo? Многие отвечают, что нулю. Но на деле это не так. Оперативную память компьютера можно сравнить с меловой доской: если вы намереваетесь что-то написать на ней, это ещё не значит, что доска чистая. Получается, что значение переменной может быть любым. Поэтому считается хорошим тоном всегда инициализировать переменные, то есть задавать их начальное значение.
int foo = 0;
Затем значение переменной можно изменить.
foo = 42;
Примечание. Числа можно задавать не только в десятичной системе, а также в восьмеричной (приписывая 0), например, 052, и шестнадцатеричной (приписывая 0x), например 0x2a.
Конечно, если вы не объявили константу. В этом случае изменить значение нельзя.
const int foo = 42;
Слово const может занимать и другое положение.
int const foo = 42;
Примечание. Если мы попытаемся изменить значение константы, то получим от компилятора сообщение наподобие такого: «Выражение должно быть изменяемым lvalue». За строгими определениями lvalue и rvalue обращайтесь к стандарту и справочникам. Мы лишь скажем, что lvalue — это объект, который имеет адрес и как правило может находиться слева от знака присваивания. Но константа из последнего примера имеет адрес, а поставить её слева от знака равно нельзя (неизменяемое lvalue). Всё остальное считается rvalue.
Конечно, одним целым типом int дело не ограничивается.
Примечание. Конкретный размер типа (следовательно, и его диапазон) зависит от платформы. В качестве примера приведены значения, с которыми вы скорее всего столкнётесь.
Самостоятельно изучите, как работают целочисленные типы (прямой, обратный, дополнительный код) и типы с плавающей запятой (float и double). Тип long double также существует, однако варианты его реализации слишком различны, что выходит за рамки этого материала. Можно использовать любые разумные сочетания типов и их модификаторов, к примеру, unsigned short int.
Как рассчитать диапазон типа? Пусть это будет short int, 16-битное знаковое целое (в нашем случае). Поскольку один бит отведён под знак (0 — положительное, 1 — отрицательное), на само число остаётся 15 бит. Количество вариантов равно
Старайтесь избегать переполнения типов (выходить за пределы диапазонов). Оно может приводить к неопределённому поведению (в случае знаковых типов), хотя обычно получается так, что диапазоны типов зациклены: самое большое число плюс один равно самое маленькое и т. п.
Звёздочка в таблице стоит неспроста. На разных платформах размеры типов могут различаться. Чтобы узнать точный размер типа в байтах, используйте оператор sizeof. В скобках можно указывать имя типа или переменной.
printf("%zu", sizeof(int));
Примечание. Я использую %zu для типа size_t. Далее будет очень подробно рассмотрено, как использовать ввод и вывод.
Название переменной можно не брать в скобки.
printf("%zu", sizeof(foo));
printf("%zu", sizeof foo);
Гарантируется, к примеру, что sizeof(short int) ≤ sizeof(int) ≤ sizeof(long int). А ещё sizeof(char) всегда 1 байт (но никто не сказал, что в этом байте 8 бит).
С оператором sizeof есть интересный трюк. Код, приведённый ниже, скомпилированный как код на языке C, выведет «C», а скомпилированный как код на языке C++ — выведет «C++». Проверка основывается на факте, что в C размер символа равен размеру типа int (в коде используется тернарный условный оператор, который рассматривается позднее).
printf("%s", (sizeof('a') == sizeof(int) ? "C" : "C++"));
Компилятор умеет разрешать ситуации, когда в выражении присутствуют переменные разных типов. Если складываются (вычитаются, умножаются и так далее) операнды типов short int и int, то операнд типа short int будет неявно приведён к типу int (если тот имеет больший размер). То есть меньший по размеру приводится к большему по размеру. Если один операнд типа int, а другой — float, то первый операнд тоже будет приведён к типу с плавающей запятой. Между unsigned int и signed int приоритет у типа unsigned int (наверное, потому, что самое большое положительное число у него больше). Последний случай легко приводит к странному поведению программы, а ошибку обнаружить трудно. Вот поэтому в коде должно быть как можно меньше неявных приведений типа.
Попробуйте угадать, чему равен результат (/ означает деление).
int foo = 180, bar = 360;
int result = foo / bar;
Результат будет 0. Целое 180 делим на целое 360, получаем целое число 0 (и остаток 180).
А вот если указать, что 180 и 360 нужно считать не целыми, а действительными числами, то результат нас порадует.
int foo = 180, bar = 360;
float result = (float)foo / (float)bar;
Оператор приведения типа можно было указать и у одного операнда. Тогда второй был бы неявно приведён к нему.
Для сокращения записи и удобства восприятия можно создавать для типов псевдонимы с помощью слова typedef.
typedef unsigned int uint;
uint foo; // То же, что unsigned int foo;