§ 2. Переменные и типы

§ 2.1 Объявления, диапазоны, основы ввода и вывода

Переменные используются для хранения данных в программе. Они требуют объявления перед использованием. Сперва указывается тип переменной (в нашем случае это 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) дело не ограничивается.

Тип

Другие названия

Пример

Размер*

Диапазон*

int signed int, signed 1515

4 байта

−2147483648...2147483647

short int short 1515

2 байта

−32768...32767

long int long 1515L, 1515l

4 байта

−2147483648...2147483647

long long int long long 1515LL, 1515ll

8 байтов

−36893488147419103232...36893488147419103231

unsigned int unsigned 1515U, 1515u

4 байта

0...4294967295

char signed char

'7' (код 55), 55

1 байт

−128...127 либо 0...255, если интерпретировано как unsigned char (некоторые платформы)

unsigned char  

'7' (код 55), 55u

1 байт

0...255

float   3.1415f

4 байта

±(3,4×10−38...3,4×1038)

double long float 3.1415

8 байтов

±(3,4×10−4932...3,4×104932)

Примечание. Конкретный размер типа (следовательно, и его диапазон) зависит от платформы. В качестве примера приведены значения, с которыми вы скорее всего столкнётесь.

Самостоятельно изучите, как работают целочисленные типы (прямой, обратный, дополнительный код) и типы с плавающей запятой (float и double). Тип long double также существует, однако варианты его реализации слишком различны, что выходит за рамки этого материала. Можно использовать любые разумные сочетания типов и их модификаторов, к примеру, unsigned short int.

Как рассчитать диапазон типа? Пусть это будет short int, 16-битное знаковое целое (в нашем случае). Поскольку один бит отведён под знак (0 — положительное, 1 — отрицательное), на само число остаётся 15 бит. Количество вариантов равно 215 = 32768. Тогда самое маленькое число равно −32768, а самое большое — 32767 (на 1 меньше, чем 215, поскольку учитывается 0). Но стоит отметить, что стандарт гарантирует ещё более узкий диапазон: −32767...32767.

Старайтесь избегать переполнения типов (выходить за пределы диапазонов). Оно может приводить к неопределённому поведению (в случае знаковых типов), хотя обычно получается так, что диапазоны типов зациклены: самое большое число плюс один равно самое маленькое и т. п.

Звёздочка в таблице стоит неспроста. На разных платформах размеры типов могут различаться. Чтобы узнать точный размер типа в байтах, используйте оператор 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++"));

§ 2.2 Неявное приведение типов

Компилятор умеет разрешать ситуации, когда в выражении присутствуют переменные разных типов. Если складываются (вычитаются, умножаются и так далее) операнды типов short int и int, то операнд типа short int будет неявно приведён к типу int (если тот имеет больший размер). То есть меньший по размеру приводится к большему по размеру. Если один операнд типа int, а другой — float, то первый операнд тоже будет приведён к типу с плавающей запятой. Между unsinged int и signed int приоритет у типа unsigned int (наверное, потому, что самое большое положительное число у него больше). Последний случай легко приводит к странному поведению программы, а ошибку обнаружить трудно. Вот поэтому в коде должно быть как можно меньше неявных приведений типа.

§ 2.3 Явное приведение типов

Попробуйте угадать, чему равен результат (/ означает деление).

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;

Оператор приведения типа можно было указать и у одного операнда. Тогда второй был бы неявно приведён к нему.

§ 2.4 Псевдонимы для типов

Для сокращения записи и удобства восприятия можно создавать для типов псевдонимы с помощью слова typedef.

typedef unsigned int uint;
uint foo; // То же, что unsinged int foo;
14 июня