Основные типы данных
До сих пор все переменные в курсе были типа 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-проекте таких переменных станут тысячи и экономия памяти начнёт что-то реально значить.
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, и больше ничего в него не помещается. Подходит для любого состояния вида «да/нет»: включено/выключено, жив/мёртв, найдено/не найдено.
bool isAlive = true;
bool hasKey = false;
bool станет по-настоящему рабочим инструментом в следующих уроках: результат сравнения (x > 5) — это bool, и условия (if) работают именно с ним. Здесь достаточно знать, что такой тип есть и что он хранит.
05char — один символ
Буква ранга («A», «B», «F») — это не текст произвольной длины, а ровно один символ, и для этого случая в int или std::string лезть избыточно. char — тип именно под это: хранит ровно один символ, не больше и не меньше. У него и своя, отдельная форма записи — одинарные кавычки, в отличие от текста в двойных, который уже встречался в первом уроке:
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>.
#include <string>
std::string playerName = "Ada";
std::cout << "Hello, " << playerName << "!";
Строку в кавычках ты уже выводил в самом первом уроке ("Hello, C++!\n") — тогда это был string-литерал, использованный напрямую внутри std::cout, без переменной. Теперь то же самое значение можно сохранить в переменную типа std::string и использовать по имени — точно так же, как с int или double.
07Собираем статы персонажа шаг за шагом
Это продолжение программы-персонажа из прошлого урока: там появилась первая переменная, hp (сокращение от playerHealth — см. прошлый урок). Ниже — та же программа, которая на каждом шаге получает ещё одну переменную нового типа. Это не пять разных примеров, а один и тот же main(), который постепенно обрастает деталями — примерно так же выглядит реальная разработка: сначала простейшая версия, потом к ней добавляются поля одно за другим.
int hp = 100;
std::cout << "HP: " << hp << "\n";
Знакомо по прошлому уроку — одна переменная, один вывод.
int hp = 100;
double damageMultiplier = 1.5;
std::cout << "HP: " << hp << ", Multiplier: " << damageMultiplier << "\n";
Множитель урона дробный — int сюда не подходит (см. раздел «Живая отладка» ниже), поэтому это double.
int hp = 100;
double damageMultiplier = 1.5;
bool isAlive = true;
std::cout << "HP: " << hp << ", Multiplier: " << damageMultiplier
<< ", Alive: " << isAlive << "\n";
«Жив ли персонаж» — не число и не дробь, а один из двух вариантов, поэтому bool.
#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, но когда важна экономия памяти (например, тысячи однотипных значений) |
bool | true или false | bool isAlive = true; | состояния «да/нет»: жив/мёртв, включено/выключено, найдено/не найдено |
char | один символ (одинарные кавычки) | char grade = 'A'; | ровно один символ: буква ранга, разделитель, код клавиши |
std::string | текст произвольной длины (двойные кавычки) | std::string name = "Ada"; | текст произвольной длины: имя, сообщение, название предмета |
09Хуже → лучше: тип, который описывает смысл
Задача — хранить «жив ли персонаж» и «буквенную оценку». Оба значения технически помещаются в int — но это не значит, что так стоит делать.
int isAlive = 1; // задумано как true/false
int grade = 65; // задумано как символ 'A' (65 — его ASCII-код)
std::cout << isAlive << " " << grade;
Компилируется и «работает» — выведет 1 65. Но тип int ничего не мешает записать isAlive = 42; — компилятор не увидит здесь ничего плохого, хотя по смыслу такое значение бессмысленно для «жив/мёртв». Смысл переменной держится только на комментарии, а комментарии никто не проверяет.
bool isAlive = true;
char grade = 'A';
std::cout << isAlive << " " << grade;
Тот же результат по смыслу (выведет 1 A — bool печатается как 0/1, см. раздел «Попробуй изменить» ниже), но теперь сам тип не даёт присвоить isAlive что-то кроме true/false, а grade — что-то длиннее одного символа. Комментарий про смысл значения больше не нужен — тип уже его выражает.
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 grade = "A"; // двойные кавычки — это строка, а не символ
"A" имеет тип const char[2]; в этом выражении массив преобразуется в const char* (отсюда тип в диагностике), а не в отдельный символ — типы не совпадают, компилятор отказывается неявно преобразовать одно в другое. Для char нужны одинарные кавычки: 'A'.
std::string name = "Ada"; // #include <string> выше отсутствует
Точно так же, как std::cout без #include <iostream> в первом уроке. (На некоторых компиляторах код может неожиданно скомпилироваться и без явного #include <string>, потому что его подключил какой-то другой заголовок случайно — полагаться на это нельзя, поведение не гарантировано стандартом.)
int score = 9.99; // дробная часть тихо отбрасывается, score станет 9
Это не ошибка компиляции — программа успешно соберётся (возможно, с предупреждением о потере точности). Опаснее ошибки компиляции именно потому, что ничего не «падает» — код молча делает не то, что задумано. Если значение дробное, тип переменной должен быть double (или float), а не int.
12Проверь себя
Три вопроса: выбор типа по смыслу значения, предсказание неочевидного вывода и повторная встреча со «скрытым багом» из живой отладки.
Показать объяснение
char — ровно один символ, не текст произвольной длины (для этого был бы std::string). int тут не подходит — буква не число, а bool хранит только два состояния.
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.
Три отдельных объявления, каждое своего типа — bool, char (одинарные кавычки!) и double — и одна или три инструкции std::cout, выводящие все три.
Решение: bool isAlive = true; char grade = 'A'; double damageMultiplier = 1.5; — затем std::cout << isAlive << grade << damageMultiplier; (можно в одной инструкции или в трёх разных).
★ Подключи <string>, объяви std::string со своим именем (текстом), и выведи приветствие вида «Hello, Ada!» через std::cout, используя эту переменную.
#include <string> нужен отдельной строкой наверху файла, до int main() — без него std::string не существует для компилятора (§06).
Решение: #include <string> наверху, std::string playerName = "Ada";, затем std::cout << "Hello, " << playerName << "!";
★★★ Собери одну программу «статы персонажа», используя все пять типов из этого урока: int (например, hp), double (например, damageMultiplier), bool (например, isAlive), char (например, grade) и std::string (например, playerName). Выведи все пять значений одной строкой через std::cout, как в шаге 4 раздела «Собираем статы персонажа» выше.
Пять отдельных объявлений, по одному каждого типа — порядок объявления не важен, важно, чтобы каждое было инициализировано значением подходящего вида (целое, дробное, true/false, символ в одинарных, текст в двойных кавычках).
Вывод — одна цепочка std::cout с пятью << подряд, как в шаге 4 раздела «Собираем статы персонажа» — каждая переменная должна реально встретиться после <<, а не просто существовать где-то выше в коде.
Решение: int hp = 100; double damageMultiplier = 1.5; bool isAlive = true; char grade = 'A'; std::string playerName = "Ada"; — затем одним 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 C++ ровно так же — просто под другими именами. Unreal предпочитает свои типы фиксированного размера вроде int32 вместо голого int (гарантия того, что переменная занимает ровно 32 бита на любой платформе — сам int формально этого не гарантирует), а вместо std::string — собственный тип FString (по той же причине, по какой у движка своя система памяти и сборки). Принцип «выбрать тип под значение» переносится без изменений:
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+.