LEARNING · STAGE 00 · PROGRAMMING BASICS

Основные типы данных

До сих пор все переменные в курсе были типа int — целые числа. Но здоровье персонажа с дробным множителем урона, состояние «жив/мёртв», буква оценки, имя игрока — это не целые числа. В этом уроке — оставшиеся базовые типы C++: float/double, bool, char и std::string, и как выбрать нужный.

01Что ты изучишь

  • Дробные числа: float и double, чем они отличаются
  • bool — тип для истина/ложь
  • char — тип для одного символа, и чем он отличается от текста
  • std::string — тип для текста произвольной длины
  • Как выбрать тип под конкретное значение

02Технически этот урок не обязателен

Можно хранить вообще всё через int — здоровье, множитель урона, состояние «жив ли персонаж», ранг, имя. Здоровье, 100 — подходит идеально. Множитель урона, 1.5? int хранит только целые числа — дробная часть при попытке туда что-то дробное засунуть просто исчезнет (подробный разбор — в следующем уроке про операторы, здесь достаточно знать, что эта ловушка существует). Состояние «жив ли персонаж» — не число вообще, а один из двух вариантов: технически влезет как 0 или 1, но ничто не помешает случайно записать туда 42. Буква ранга — один символ, а не количество. Имя игрока — текст неизвестной заранее длины. Ни один из этих четырёх случаев int не описывает честно — отсюда и весь урок: под каждый из них в C++ есть отдельный тип, который не просто «тоже скомпилируется», а не позволит записать туда бессмыслицу.

03float и double — дробные числа

Для множителя урона 1.5 нужен тип, который честно хранит дробную часть, — таких в C++ два: float и double. Разница между ними — не в том, «что можно, а что нельзя», а в размере и точности: double занимает 8 байт памяти и хранит значение точнее, float — 4 байта и грубее. В этом курсе double — выбор по умолчанию; float пригодится позже, когда в Unreal-проекте таких переменных станут тысячи и экономия памяти начнёт что-то реально значить.

main.cppC++
double damageMultiplier = 1.5;   // литерал без суффикса — double по умолчанию
float lightMultiplier = 1.5f;     // суффикс f — явно float

Дробный литерал вроде 1.5 без суффикса компилятор по умолчанию считает double; чтобы получить именно float, к числу приписывается суффикс f. Пока в этом курсе почти везде будет использоваться double — этого достаточно.

04bool — истина или ложь

«Жив ли персонаж» — вопрос всего с двумя возможными ответами, и хранить его в int означало бы разрешить туда записать и 2, и 100, и −7 — притом что ни одно из этих чисел ничего не значит для «жив/мёртв». bool закрывает именно эту дыру: он хранит ровно одно из двух значений, true или false, и больше ничего в него не помещается. Подходит для любого состояния вида «да/нет»: включено/выключено, жив/мёртв, найдено/не найдено.

main.cppC++
bool isAlive = true;
bool hasKey = false;

bool станет по-настоящему рабочим инструментом в следующих уроках: результат сравнения (x > 5) — это bool, и условия (if) работают именно с ним. Здесь достаточно знать, что такой тип есть и что он хранит.

05char — один символ

Буква ранга («A», «B», «F») — это не текст произвольной длины, а ровно один символ, и для этого случая в int или std::string лезть избыточно. char — тип именно под это: хранит ровно один символ, не больше и не меньше. У него и своя, отдельная форма записи — одинарные кавычки, в отличие от текста в двойных, который уже встречался в первом уроке:

main.cppC++
char grade = 'A';       // один символ — одинарные кавычки
std::string name = "Ada";  // текст — двойные кавычки, другой тип

'A' и "A" в C++ — не одно и то же: первое — char (один символ), второе — текст из одного символа (тип const char[2]: символ и завершающий ноль; std::string можно создать из этого литерала — про массивы отдельно в Stage 02). Путать их — частая ошибка новичков, см. раздел «Типичные ошибки» ниже.

06std::string — текст

Имя игрока не помещается в char — букв там обычно больше одной, и заранее неизвестно, сколько именно. Нужен тип, который сам подстраивается под длину текста, — std::string, тоже из стандартной библиотеки (как и std::cout), и тоже требующий свой собственный #include <string>.

main.cppC++
#include <string>

std::string playerName = "Ada";
std::cout << "Hello, " << playerName << "!";

Строку в кавычках ты уже выводил в самом первом уроке ("Hello, C++!\n") — тогда это был string-литерал, использованный напрямую внутри std::cout, без переменной. Теперь то же самое значение можно сохранить в переменную типа std::string и использовать по имени — точно так же, как с int или double.

07Собираем статы персонажа шаг за шагом

Это продолжение программы-персонажа из прошлого урока: там появилась первая переменная, hp (сокращение от playerHealth — см. прошлый урок). Ниже — та же программа, которая на каждом шаге получает ещё одну переменную нового типа. Это не пять разных примеров, а один и тот же main(), который постепенно обрастает деталями — примерно так же выглядит реальная разработка: сначала простейшая версия, потом к ней добавляются поля одно за другим.

Шаг 1 — только intC++
int hp = 100;
std::cout << "HP: " << hp << "\n";

Знакомо по прошлому уроку — одна переменная, один вывод.

Шаг 2 — добавили doubleC++
int hp = 100;
double damageMultiplier = 1.5;
std::cout << "HP: " << hp << ", Multiplier: " << damageMultiplier << "\n";

Множитель урона дробный — int сюда не подходит (см. раздел «Живая отладка» ниже), поэтому это double.

Шаг 3 — добавили boolC++
int hp = 100;
double damageMultiplier = 1.5;
bool isAlive = true;
std::cout << "HP: " << hp << ", Multiplier: " << damageMultiplier
          << ", Alive: " << isAlive << "\n";

«Жив ли персонаж» — не число и не дробь, а один из двух вариантов, поэтому bool.

Шаг 4 — добавили char и std::stringC++
#include <string>

int hp = 100;
double damageMultiplier = 1.5;
bool isAlive = true;
char grade = 'A';
std::string playerName = "Ada";
std::cout << playerName << " — HP: " << hp
          << ", Multiplier: " << damageMultiplier
          << ", Alive: " << isAlive
          << ", Grade: " << grade << "\n";

Финальная версия использует все пять типов из этого урока в одной программе, каждый — под своё значение. Это же построение пригодится в задании 3 ниже.

08Типы — таблица

ТипХранитПримерКогда использовать
intцелое числоint hp = 100;счёт, количество, HP, индексы — всё, что не бывает дробным
doubleдробное число (по умолчанию)double multiplier = 1.5;множители, координаты, деньги с копейками — дробные значения по умолчанию
floatдробное число, меньше точности и памятиfloat multiplier = 1.5f;то же, что double, но когда важна экономия памяти (например, тысячи однотипных значений)
booltrue или falsebool isAlive = true;состояния «да/нет»: жив/мёртв, включено/выключено, найдено/не найдено
charодин символ (одинарные кавычки)char grade = 'A';ровно один символ: буква ранга, разделитель, код клавиши
std::stringтекст произвольной длины (двойные кавычки)std::string name = "Ada";текст произвольной длины: имя, сообщение, название предмета

09Хуже → лучше: тип, который описывает смысл

Задача — хранить «жив ли персонаж» и «буквенную оценку». Оба значения технически помещаются в int — но это не значит, что так стоит делать.

Хуже — всё в int, смысл спрятан в комментарияхC++
int isAlive = 1;   // задумано как true/false
int grade = 65;      // задумано как символ 'A' (65 — его ASCII-код)
std::cout << isAlive << " " << grade;

Компилируется и «работает» — выведет 1 65. Но тип int ничего не мешает записать isAlive = 42; — компилятор не увидит здесь ничего плохого, хотя по смыслу такое значение бессмысленно для «жив/мёртв». Смысл переменной держится только на комментарии, а комментарии никто не проверяет.

Лучше — тип соответствует смыслу значенияC++
bool isAlive = true;
char grade = 'A';
std::cout << isAlive << " " << grade;

Тот же результат по смыслу (выведет 1 A — bool печатается как 0/1, см. раздел «Попробуй изменить» ниже), но теперь сам тип не даёт присвоить isAlive что-то кроме true/false, а grade — что-то длиннее одного символа. Комментарий про смысл значения больше не нужен — тип уже его выражает.

Лучше всего — вывод тоже читаем без контекстаC++
bool isAlive = true;
char grade = 'A';
std::cout << "Alive: " << isAlive << ", Grade: " << grade << "\n";

Теперь даже вывод 1 вместо слова «true» не сбивает с толку — рядом стоит подпись «Alive:», объясняющая, что это за число.

10Попробуй изменить

  • Замени bool isAlive = true; на false и выведи значение через std::cout — увидишь, что C++ печатает 1 для true и 0 для false, а не слово «true».
  • Замени 1.5 на 1.5f у переменной типа float — поведение не изменится, но так явно видно, что это float-литерал.
  • Попробуй написать char grade = 'AB'; (два символа в одинарных кавычках) — это многосимвольный литерал типа int с зависящим от реализации значением. Компилятор может выдать предупреждение и принять обычное присваивание с преобразованием к char; гарантированной ошибки компиляции здесь нет. Для отдельного символа используй 'A'.

11Живая отладка: типичные ошибки

Точный текст ошибки зависит от компилятора и его версии — ниже характерный вид в стиле g++.

Двойные кавычки вместо одинарных у char
char grade = "A";   // двойные кавычки — это строка, а не символ
консоль (иллюстрация)
g++ main.cpp -o main main.cpp:4:18: error: invalid conversion from 'const char*' to 'char' [-fpermissive] 4 | char grade = "A"; | ^~~ | | | const char* Компиляция не удалась.

"A" имеет тип const char[2]; в этом выражении массив преобразуется в const char* (отсюда тип в диагностике), а не в отдельный символ — типы не совпадают, компилятор отказывается неявно преобразовать одно в другое. Для char нужны одинарные кавычки: 'A'.

std::string без #include <string>
std::string name = "Ada";   // #include <string> выше отсутствует
консоль (иллюстрация)
g++ main.cpp -o main main.cpp:6:10: error: 'string' is not a member of 'std' 6 | std::string name = "Ada"; | ^~~~~~ Компиляция не удалась.

Точно так же, как std::cout без #include <iostream> в первом уроке. (На некоторых компиляторах код может неожиданно скомпилироваться и без явного #include <string>, потому что его подключил какой-то другой заголовок случайно — полагаться на это нельзя, поведение не гарантировано стандартом.)

Дробное значение потеряно при сохранении в int
int score = 9.99;   // дробная часть тихо отбрасывается, score станет 9

Это не ошибка компиляции — программа успешно соберётся (возможно, с предупреждением о потере точности). Опаснее ошибки компиляции именно потому, что ничего не «падает» — код молча делает не то, что задумано. Если значение дробное, тип переменной должен быть double (или float), а не int.

12Проверь себя

Три вопроса: выбор типа по смыслу значения, предсказание неочевидного вывода и повторная встреча со «скрытым багом» из живой отладки.

Выбери тип
Какой тип лучше всего подходит для значения «первая буква фамилии игрока»?
Показать объяснение

char — ровно один символ, не текст произвольной длины (для этого был бы std::string). int тут не подходит — буква не число, а bool хранит только два состояния.

Предскажи результат
Что напечатает эта программа — слово «true» или что-то другое?
bool hasKey = true;
std::cout << hasKey;
Показать объяснение

Напечатает 1, а не слово «true». std::cout по умолчанию выводит bool как 1/0 — это касается всех значений типа bool, слово появляется, только если явно попросить об этом отдельной настройкой потока (за пределами этого урока).

Скрытый баг
Компилятор пропустит эту строку без единой ошибки. Чему будет равен score?
int score = 9.99;
Показать объяснение

score станет равен 9 — дробная часть тихо отбрасывается при сохранении в int (см. §11), округления к ближайшему целому не происходит. Компилятор не обязан считать это ошибкой: технически такое преобразование разрешено языком (может быть выдано предупреждение, но не ошибка) — именно поэтому такой баг легко пропустить, в отличие от ошибок вроде двойных кавычек у char, которые компиляция сразу отклоняет.

13Практика

★ Объяви три переменные: bool (например, isAlive), char (например, grade) и double (например, damageMultiplier) — каждую с осмысленным именем и значением. Выведи все три через std::cout.

★ Подключи <string>, объяви std::string со своим именем (текстом), и выведи приветствие вида «Hello, Ada!» через std::cout, используя эту переменную.

★★★ Собери одну программу «статы персонажа», используя все пять типов из этого урока: int (например, hp), double (например, damageMultiplier), bool (например, isAlive), char (например, grade) и std::string (например, playerName). Выведи все пять значений одной строкой через std::cout, как в шаге 4 раздела «Собираем статы персонажа» выше.

14Термины

ТерминЗначит
Дробное число (floating-point)число с дробной частью — представлено типами double/float
Логическое значение (boolean)значение true или false — тип bool
Символьный литералодиночный символ в одинарных кавычках, например 'A' — значение типа char
Строковый литералтекст в двойных кавычках, например "Ada" — исходное значение для std::string
Суффикс типабуква после числового литерала, уточняющая его тип, например f в 1.5f (float)

15Что ты теперь умеешь

До этого урока единственным инструментом был int. Теперь у тебя есть выбор — и понимание, чем он оправдан:

  • Ты можешь для конкретного значения (количество, состояние да/нет, один символ, текст) выбрать подходящий тип, а не запихивать всё в int «потому что скомпилируется».
  • Ты можешь объяснить разницу между 'A' и "A" и предсказать, как отреагирует компилятор, если перепутать кавычки местами.
  • Ты можешь предсказать, что именно выведет std::cout для значения типа bool — не слово, а 0 или 1.
  • Ты можешь объяснить, почему присваивание дробного значения переменной типа int не вызывает ошибки компиляции, но приводит к молчаливой потере данных.
  • Ты можешь собрать одну программу, использующую несколько разных типов одновременно, каждый — под своё значение, а не для количества строк.

16Кратко

  • double (и реже float) — дробные числа; double — выбор по умолчанию в этом курсе.
  • bool — true/false, печатается как 1/0 через std::cout.
  • char — один символ, одинарные кавычки ('A'), не путать со строкой в двойных.
  • std::string — текст произвольной длины, двойные кавычки, требует #include <string>.
  • Сохранение дробного значения в int обрезает дробную часть без ошибки компиляции — источник тихих багов.

Все пять типов на месте — но пока с ними можно только объявлять и выводить значения. Следующий урок добавляет операторы: арифметику, сравнение и логику, без которых типы остаются просто контейнерами для чисел и текста.

🎮 А как это связано с Unreal Engine?

Эти типы будут использоваться в Unreal C++ ровно так же — просто под другими именами. Unreal предпочитает свои типы фиксированного размера вроде int32 вместо голого int (гарантия того, что переменная занимает ровно 32 бита на любой платформе — сам int формально этого не гарантирует), а вместо std::string — собственный тип FString (по той же причине, по какой у движка своя система памяти и сборки). Принцип «выбрать тип под значение» переносится без изменений:

Player.h (Unreal C++, для примера)C++
int32 Health = 100;              // вместо int
float DamageMultiplier = 1.5f;   // вместо double — Unreal чаще использует float
bool bIsAlive = true;            // bool остаётся bool, но с префиксом b
FString PlayerName = "Ada";      // вместо std::string

Заметь: bool-переменные в Unreal принято называть с префиксом b (bIsAlive, а не isAlive) — это соглашение об именовании движка, а не требование языка C++. Подробности про FString и другие Unreal-типы текста — в Stage 07+.