Лекция 1. Типы и операторы
Идентификаторы и стиль кода, объявления и определения, встроенные типы и их размеры, беззнаковая арифметика, представление чисел, литералы, перечисления, операторы и приоритет, преобразования типов.
Первая содержательная лекция о языке. Здесь собраны «кирпичики» любой программы: как назвать переменную, какой тип ей выбрать, как записать число, что делают операторы и почему выражение a + b * c понимается не так, как его читает глаз. Почти все ошибки начинающих в C++ рождаются именно на этом уровне, поэтому разбираем подробно.
Читая любое выражение, сначала расставьте скобки по приоритету, и только потом считайте. Если сомневаетесь, добавьте скобки в свой код: компилятор от этого не станет медленнее, а читателю станет проще.
1. Программа и её первые строки
Классическая программа «Hello, world!» выводит строку в стандартный поток вывода:
#include <iostream>
int main() {
std::cout << "Hello world!\n";
return 0;
}Здесь важны три вещи: директива #include подключает объявления из стандартной библиотеки, std::cout — поток вывода, а main — точка входа, которую вызывает система при запуске.
2. Идентификаторы и стиль
Идентификатор — это имя переменной, типа, функции или другого элемента программы. Правила такие:
- может содержать буквы (в том числе допустимый Unicode), цифры и символ
_; - не начинается с цифры;
- регистр важен:
valueиValue— разные имена; - не совпадает с ключевым словом языка, например
intилиreturn.
Правила стиля на курсе такие же, как в проекте: snake_case для переменных и пространств имён, PascalCase для функций и типов, kPascalCase для констант. Отступ — четыре пробела, фигурная скобка остаётся на той же строке, фигурные скобки ставим даже у однострочных if и циклов. Форматирует clang-format, за основу взят Google C++ Style Guide.
Стиль не влияет на результат программы, но влияет на то, насколько быстро вы и ваш преподаватель поймёте код. Одинаковые имена и отступы экономят время на каждом ревью.
3. Объявление и определение
Объявление сообщает компилятору имя и тип. Определение дополнительно создаёт объект и выделяет под него память:
extern int global_count; // объявление: объект где-то в другом месте
int global_count = 0; // определение
double radius = 1.23; // определение
char grade = 'A'; // определениеКаждое определение является объявлением, но не наоборот.
3.1. Переменная без инициализатора
Если написать int value; внутри функции, переменная создастся, но её значение будет мусором — тем, что оказалось в памяти до этого. Чтение такого значения — неопределённое поведение (undefined behavior): программа может сделать что угодно, включая «работать правильно» на одном запуске и ломаться на другом.
Пишите int value{}; или int value = 0;. Неинициализированная локальная переменная — самый частый источник «плавающих» багов у новичков.
4. Встроенные типы
Основные типы языка делятся на группы:
- Целочисленные:
bool,char,short,int,long,long long. - С плавающей точкой:
float,double,long double. - Специальные:
void(нет значения) иstd::nullptr_t(тип литералаnullptr). - Символьные:
wchar_t,char8_t,char16_t,char32_tдля разных кодировок.
Обратите внимание: char формально целочисленный тип. Мы используем его для символов, но арифметика с ним работает как с числом.
4.1. Модификаторы
Модификаторы меняют диапазон целочисленного типа:
short int small_value = -10;
long int large_value = 1'000'000L;
long long very_large_value = 9'000'000'000LL;
signed int temperature = -20;
unsigned int student_count = 30U;
unsigned long file_size = 4'000'000UL;signedразрешает отрицательные значения (по умолчанию для большинства целых типов);unsignedхранит только неотрицательные;shortиlongзадают минимальный гарантированный диапазон, а не точный размер;- слово
intчасто можно опустить:unsignedозначаетunsigned int.
4.2. Беззнаковая арифметика
Беззнаковые числа «заворачиваются»: арифметика идёт по модулю , где — число бит типа. Поэтому:
unsigned int zero = 0U;
unsigned int wrapped = zero - 1U; // максимальное значение типа, а не -1Смешивание знаковых и беззнаковых типов в сравнении коварно. Знаковое значение приводится к беззнаковому, и -1 становится огромным положительным числом:
bool result = -1 < 10U; // falseБеззнаковый тип не спасает от отрицательных значений: он просто меняет правила их интерпретации. Для счётчиков и размеров используйте std::size_t, но помните про преобразования при сравнении с int.
4.3. Переполнение при вычислении середины
Классическая ошибка в бинарном поиске:
int middle = (left + right) / 2; // может переполнить int
int safe_middle = left + (right - left) / 2;Сумма left + right при больших индексах превышает диапазон int. Для знаковых типов переполнение — неопределённое поведение. Именно такой баг много лет жил в стандартной реализации бинарного поиска в библиотеке Java.
5. Размеры типов
Размеры встроенных типов зависят от платформы. Типичная раскладка для 64-битной Linux-системы с моделью данных LP64:
| Тип | Размер |
|---|---|
char | 1 байт |
short | 2 байта |
int | 4 байта |
long | 8 байт |
long long | 8 байт |
float | 4 байта |
double | 8 байт |
На другой архитектуре, в другой ОС или у другого компилятора цифры могут отличаться. Гарантировано только то, что sizeof(char) == 1.
Узнать размер можно оператором sizeof. Результат имеет тип std::size_t:
std::cout << sizeof(int) << '\n';Диапазоны значений типа доступны через std::numeric_limits из заголовка <limits>: методы lowest() и max() дают крайние конечные значения. Для double метод min() возвращает наименьшее положительное нормализованное число, а не самое отрицательное, поэтому не путайте их.
5.1. Типы фиксированной ширины
Заголовок <cstdint> объявляет типы с точно заданным размером: std::int8_t, std::uint16_t, std::int32_t, std::uint64_t и т. д. Число в имени — ширина в битах.
Используйте их, когда размер является частью контракта: формат файла, сетевой протокол, регистр устройства. Для обычных счётчиков и вычислений часто достаточно int.
6. Как хранятся числа
6.1. Знаковые целые
Для знаковых целых в современном C++ используется дополнительный код (two's complement). Чтобы понять, зачем он нужен, сравним три подхода на восьми битах.
- Прямой код: старший бит хранит знак, остальные — модуль. Сложение разных знаков не работает напрямую, а ноль имеет два представления.
- Обратный код: для отрицательного числа инвертируются все биты. Сложение требует циклического переноса, а ноль по-прежнему двойной.
- Дополнительный код: для отрицательного берётся . Для 8 бит записывается как
1111 1011. Сложение работает одной и той же схемой для любых знаков, ноль один, диапазон от до .
Переполнение знакового целого всё равно остаётся неопределённым поведением. Например, -INT_MIN не имеет допустимого результата.
6.2. Числа с плавающей точкой
Стандартный формат float — IEEE 754 binary32: 1 бит знака, 8 бит смещённой экспоненты и 23 бита мантиссы. Для нормализованного числа значение равно
Посмотрим на . В двоичной записи это . Значит, знак , экспонента , мантисса (ведущая единица не хранится).
Число в двоичной системе — бесконечная периодическая дробь. Поэтому 0.1 + 0.2 == 0.3 в типах с плавающей точкой ложно. Сравнивайте вещественные числа с допуском или используйте целые величины там, где это возможно.
7. Литералы
7.1. Целочисленные
int decimal = 162;
int octal = 0242; // ведущий ноль: восьмеричная система
int hexadecimal = 0xA2; // префикс 0x: шестнадцатеричная
int binary = 0b1010'0010; // префикс 0b: двоичная
unsigned long population = 1'000'000UL;Апостроф разделяет разряды и на значение не влияет. Суффиксы: U — беззнаковый, L — long, LL — long long. Ведущий ноль меняет систему счисления: 010 — это восемь, а не десять.
7.2. Вещественные
Без суффикса литерал имеет тип double. Суффикс F делает float, L — long double. Запись 1.5E6 означает .
7.3. Символы и строки
Символьный литерал записывается в одинарных кавычках. Экранирующие последовательности начинаются с обратной косой черты:
\n— перевод строки,\t— табуляция,\0— нулевой символ;\\— сама обратная косая черта,\'и\"— кавычки.
'\n' — один символ, а не два. Строковый литерал вида "Hello" имеет тип массива const char[6]: пять символов и завершающий нулевой символ.
const char message[] = "Hello, world!\n";7.4. Логический тип
bool принимает два значения: true и false. Результаты сравнений имеют именно этот тип:
bool is_positive = value > 0;8. Перечисления
Перечисление задаёт набор именованных констант. Бывают два вида:
enum Color { kRed, kGreen, kBlue }; // обычное
enum class Direction { kLeft, kRight }; // «сильное»| Свойство | enum | enum class |
|---|---|---|
| Имена элементов | Видны в окружающей области | Только через Direction::kLeft |
Неявное преобразование в int | Есть | Нет, только явное |
Рекомендуем enum class: меньше конфликтов имён и случайных преобразований.
9. Операторы
Основные группы операторов:
- арифметические:
+,-,*,/,%; - сравнения:
==,!=,<,<=,>,>=; - логические:
&&,||,!; - инкремент и декремент:
++,--(префиксная и постфиксная формы); - побитовые:
&,|,^,~,<<,>>; - присваивание:
=,+=,-=,*=,/=,%=; - условный:
?:; - прочие:
sizeof, явные приведения типов.
9.1. Приоритет
Приоритет определяет, какие операторы объединяются первыми. Сокращённая таблица, от высокого к низкому:
- постфиксные:
a[k],f(),.,->,a++,a--; - унарные:
++a,--a,!,~,+,-,*,&,sizeof; - умножение, деление, остаток;
- сложение и вычитание;
- сдвиги;
- отношения
<,<=,>,>=; - равенство
==,!=; - побитовое И;
- побитовое исключающее ИЛИ;
- побитовое ИЛИ;
- логическое И;
- логическое ИЛИ;
- условный оператор;
- присваивание;
- запятая.
Приоритет не задаёт порядок вычисления операндов. Для выражения f() + g() порядок вызовов не определён стандартом. Если выражение трудно читать, ставьте скобки.
9.2. Разбор выражения
Пусть a = 1, b = 2, c = 3, d = 1, все типа int. Разберём выражение
a + b * c << d || 25 != 32 && !c++По приоритету оно эквивалентно
((a + (b * c)) << d) || ((25 != 32) && (!(c++)))Левая часть даёт . Поскольку || вычисляется с коротким замыканием и левая часть ненулевая, правая часть не вычисляется, и c++ не происходит. Итоговое значение — true.
Этот пример полезен как упражнение, но в реальном коде такие выражения не пишут. Побочные эффекты внутри сложного условия (вроде c++ здесь) ломают чтение и легко ведут к ошибкам.
10. Преобразования типов
Компилятор сам приводит операнды к общему типу. Правила такие:
charиshortсначала превращаются вint;- при смешивании целых и вещественных результат обычно вещественный;
- при смешивании знаковых и беззнаковых значения приводятся к беззнаковому, что часто удивляет.
Явное преобразование пока записываем в C-стиле: (тип) выражение. В следующих лекциях появятся static_cast и другие более строгие формы.
int total = 7;
int count = 2;
double average = static_cast<double>(total) / count; // 3.57 / 2 даёт 3, а не 3.5: оба операнда целые, поэтому и результат целый. Чтобы получить вещественный результат, приведите хотя бы один операнд к double.
11. Оператор sizeof
sizeof применяется к типу или к выражению и возвращает размер в байтах:
sizeof(int)
sizeof(double)
sizeof(variable)Результат имеет тип std::size_t. Количество бит в байте задано константой CHAR_BIT из заголовка <climits>.
Частые ошибки
- Чтение неинициализированной переменной.
- Беззнаковая арифметика, которая «уходит в минус» и становится огромным числом.
- Деление целых там, где ожидался вещественный результат.
- Полагаться на порядок вычисления операндов в одном выражении.
- Сравнение вещественных чисел через
==.
Мини-тренажёр
Ответы
int x; std::cout << x;— неопределённое поведение:xне инициализирован.unsigned int a = 0; a -= 1;—aстановится максимальным значением типа, а не .-1 < 10U—false, потому что преобразуется в очень большое беззнаковое значение.7 / 2—3;7 / 2.0—3.5.0.1 + 0.2 == 0.3—falseиз-за точности двоичного представления.sizeof(char)— всегда1.
Шпаргалка
| Понятие | Суть |
|---|---|
| Объявление | Сообщает имя и тип |
| Определение | Объявление, которое создаёт объект |
| Беззнаковый тип | Арифметика по модулю |
| Дополнительный код | Представление знаковых целых в современном C++ |
| IEEE 754 | Формат float: знак, экспонента, мантисса |
enum class | Перечисление без неявного преобразования в int |
| Приоритет | Порядок группировки операторов, не порядок вычисления |
sizeof | Размер в байтах, тип std::size_t |
Актуальная версия: https://m3105.ru/notes/osnovy-programmirovaniya/lektsiya-1-tipy-i-operatory

Комментарии0
Пока никто ничего не написал.
Войдите, чтобы оставить комментарий