Что такое программа и как она запускается
Первый урок курса «C++ → Unreal Gateway» — с него начинается путь от полного нуля до профессионального C++ для Unreal Engine 5.8. Здесь нет ни одной ранее пройденной темы, на которую можно опереться — поэтому урок начинается с самого начала: с одной простой картинки, которая объясняет вообще всё программирование за один раз.
01Что ты изучишь
- Почему компьютер одновременно самый быстрый и самый бестолковый исполнитель — и почему из этого следует вся профессия программиста
- Путь от исходного кода до запущенной программы: компиляция → исполняемый файл → main()
- Первую полноценную C++ программу — построчно, без пропусков
#include,std::cout, оператор<<, строки,\n,std::endl,return 0;- Что можно вывести на экран и какие ошибки здесь встречаются чаще всего
02Проблема: как объяснить что-либо машине
Представь самого умного человека на свете. Он может за секунду перебрать миллиард вариантов. У него идеальная память. Но есть одна проблема — он не понимает ни единого человеческого слова, пока ты не объяснишь ему всё до последней мелочи, как роботу.
Скажи ему: «Принеси книгу» — и он застынет, потому что не знает, что такое «принеси». А теперь скажи иначе: «Поверни голову на 45 градусов влево. Найди глазами прямоугольный предмет на полке. Вычисли расстояние до него. Сделай три шага вперёд. Вытяни руку. Сожми пальцы...» — и вот теперь он справится.
Это и есть компьютер. Самый быстрый и самый бестолковый исполнитель одновременно. Он не понимает намерений. Он понимает только точные, разложенные по шагам инструкции.
А программист — это человек, который переводит человеческое желание («пусть враг получит урон», «пусть на экране появится сообщение») на язык, который поймёт эта машина. Вот и весь секрет: программирование — не магия и не удел людей с особым складом ума. Это ремесло точной формулировки того, что ты хочешь получить. Дальше в этом уроке мы разберём, что стоит за словом «точно» — по каким именно ступеням твоё желание доходит до экрана.
Забегая на пару стадий курса вперёд: если ты когда-нибудь откроешь чужой код и увидишь что-то вроде std::vector<Enemy*> Enemies; — это не заклинание. Это ровно то же самое явление, что запись прораба «Бетон М300, арматура А400С» на стройке: непонятно постороннему, предельно конкретно для того, кто знает, что означает каждое слово. Программисты сокращают запись по той же причине, по которой прораб не расписывает состав бетонной смеси заново каждый день — не чтобы напустить туману, а чтобы не утонуть в тексте. Правило на будущее: если конструкция выглядит страшно — разверни её по кусочкам, и страх исчезнет. Этим мы и будем заниматься весь курс, конструкция за конструкцией.
03Алгоритм: как желание превращается в текст на экране
Прежде чем писать хоть одну строку C++, распишем словами, что вообще должно произойти, чтобы на экране появилось «Hello, C++!». Ещё без единого символа кода — просто последовательность шагов, которую понял бы человек:
- Записать желание на языке, у которого есть строгие правила и который умеет читать компьютер, — на C++.
- Явно сказать: «программа умеет выводить текст» (эту способность даёт отдельный набор инструментов —
iostream, подключается отдельной строкой). - Указать саму точку, откуда начнётся выполнение, — функцию
main(). - Внутри неё — ровно одна инструкция: взять готовый инструмент для вывода (
std::cout) и отправить в него нужный текст. - Сообщить операционной системе, что всё прошло успешно.
- Перевести весь этот текст в машинный код — этим занимается отдельная программа, компилятор, — и только после этого запустить результат.
Это и есть алгоритм — план действий по шагам, ещё не привязанный к синтаксису конкретного языка. Дальше в уроке каждый из этих шести шагов получит свою C++-конструкцию: шаг 2 — это #include <iostream>, шаг 3 — int main(), шаг 4 — std::cout << "...";, шаг 5 — return 0;. Раздел 8 «Разбор первой программы построчно» покажет, как эти шесть шагов ложатся на шесть строк настоящего кода один к одному.
04Из чего состоит компьютер, который это выполняет
Чтобы дальнейшие темы курса (указатели, память, адреса, а позже — UObject и Garbage Collection в Unreal) не были абстрактными словами, полезно с самого начала представлять, где физически происходит выполнение программы. Память компьютера удобно представлять как большой город: в нём есть дома (ячейки памяти), и у каждого дома есть свой номер — адрес. Когда через несколько уроков появится переменная вроде int Health = 100;, это будет означать буквально «построй в этом городе маленький домик, повесь на него табличку Health и положи внутрь число 100» — просто сам адрес обычно скрыт за удобным именем.
- CPU (процессор) — выполняет инструкции одну за другой. Он не «понимает» C++ — он понимает только машинный код (см. ниже).
- Память (RAM) — тот самый город домов-адресов, временное хранилище для данных, пока программа работает.
- Хранилище (диск) — то, где файлы (в том числе исполняемый файл программы) лежат постоянно, даже когда компьютер выключен.
- Устройства ввода/вывода — клавиатура, экран, консоль: через них программа получает данные извне и показывает результат.
Когда ты запускаешь программу, операционная система загружает её из хранилища в память, а CPU начинает читать и выполнять её инструкции одну за другой — ровно то, что описывал алгоритм из раздела 3, только теперь мы знаем, где физически это происходит.
05Исходный код и язык программирования
Исходный код — это текст программы, написанный человеком в файле (например main.cpp), на определённом языке программирования — C++ в данном случае. Язык программирования — это набор строгих правил: какие слова что означают, где ставить ;, как оформлять функцию. В отличие от обычного языка, здесь нет места двусмысленности — компилятор либо понимает конструкцию строго по правилам, либо выдаёт ошибку. Это прямое продолжение раздела 2: раз компьютер понимает только точные инструкции, у языка, на котором мы их записываем, просто не может быть места для «примерно понятно».
06Машинный код и компилятор
CPU не умеет читать std::cout << "Hello"; — он понимает только машинный код: последовательности нулей и единиц, каждая из которых соответствует одной элементарной операции процессора. Исходный код на C++ нужно перевести в такую форму — этим занимается компилятор, отдельная программа, и другие инструменты сборки рядом с ним. Компилятор читает файл .cpp, проверяет, что код написан по правилам языка (иначе выдаёт ошибку компиляции и ничего не запускает), и превращает его в форму, из которой в итоге получается исполняемая программа. (Внутри этого шага на самом деле несколько этапов — препроцессинг, компиляция, компоновка — но для первого знакомства достаточно видеть его как один блок «компилятор»; подробности будут дальше по курсу.)
Чтобы увидеть этот перевод не абстрактно, возьмём совсем короткую функцию — сложение двух чисел:
int Add(int A, int B)
{
return A + B;
}
Между этим кодом и процессором есть промежуточная ступень — ассемблер, язык, почти напрямую описывающий действия процессора, но всё ещё читаемый человеком. На распространённой архитектуре x86-64 компилятор может сгенерировать для этой функции примерно такой ассемблерный код:
mov eax, edi
add eax, esi
ret
По шагам: mov eax, edi — «положи первое переданное число в ячейку процессора для вычислений (регистр eax)»; add eax, esi — «прибавь к тому, что лежит в этой ячейке, второе число»; ret — «верни результат туда, откуда нас вызвали». А ещё глубже эти три строчки превращаются в чистые машинные байты вроде 89 F8 01 F0 C3 — то есть в конечном счёте в последовательность нулей и единиц, с которой и работает процессор. Точный набор байт не универсален — он зависит от процессора, компилятора и настроек оптимизации; важна не зубрёжка байт, а сама цепочка целиком:
Человеческая задача → Алгоритм → C++ → Компилятор → Ассемблер → Машинный код → Процессор
Для повседневной работы почти никогда не придётся спускаться на этот нижний уровень руками — но знание, что он существует, снимает львиную долю мистики вокруг слов «компилятор» и «машинный код».
07Исполняемый файл и запуск программы
Результат работы компилятора — исполняемый файл (.exe на Windows) — файл, который операционная система умеет запускать напрямую. Когда ты его запускаешь, операционная система загружает его в память и передаёт управление CPU, который начинает выполнять инструкции по порядку.
08main() — точка входа
В обычной hosted C++-программе main() — точка входа, где мы начинаем основной сценарий приложения. Среда исполнения вызывает её после начальной подготовки. Пользовательский код тоже может выполняться до main, например в конструкторах глобальных объектов; пока наши примеры такого кода не содержат. У библиотек нет собственной обязательной main: их функции вызывает приложение. В этом уроке весь исполняемый учебный код мы размещаем внутри main.
Весь путь целиком, от файла на диске до текста на экране, выглядит так:
main.cpp
│
▼
Исходный C++ код
│
▼
Компилятор
│
▼
Машинный код
│
▼
Исполняемый файл
│
▼
Запуск программы
│
▼
main()
│
▼
std::cout
│
▼
Консоль
09Первая программа
Вот минимальная полноценная C++ программа, которая выводит текст на экран — те самые шесть шагов алгоритма из раздела 3, уже на C++:
#include <iostream>
int main()
{
std::cout << "Hello, C++!\n";
return 0;
}
После компиляции и запуска эта программа выводит в консоль ровно одну строку:
Hello, C++!
10Разбор первой программы построчно
#include <iostream> → подключает средства стандартного ввода и вывода
int main() → функция, с которой начинается выполнение обычной C++ программы
{ } → определяют тело функции — всё, что внутри, выполняется по порядку
std::cout → объект, который выводит данные в консоль
<< → передаёт данные в поток вывода
"Hello, C++!" → строковый литерал — данные, которые нужно вывести
\n → переводит курсор на новую строку
; → завершает инструкцию
return 0; → сообщает операционной системе, что программа завершилась успешно
11#include и iostream
C++ сам по себе не знает, как выводить текст на экран — это не встроено в язык напрямую. Эту возможность предоставляет отдельная часть стандартной библиотеки под названием iostream (input-output stream — «поток ввода-вывода»). Строка #include <iostream> в начале файла говорит компилятору: «перед компиляцией этого файла подставь сюда содержимое библиотеки iostream, она мне понадобится». Без этой строки компилятор просто не будет знать, что такое std::cout, и выдаст ошибку.
<iostream> — не один инструмент, а маленький набор из нескольких. В этом уроке нужен только первый пункт таблицы; остальные — просто карта того, что там есть, чтобы позже они не оказывались сюрпризом.
| Имя | Что это | Нужно сейчас? |
|---|---|---|
std::cout | поток вывода — печатает в консоль | да, используется в этом уроке |
std::endl | манипулятор — переводит строку (§14) | да, используется в этом уроке |
std::cin | поток ввода — читает то, что вводит пользователь с клавиатуры | нет — появится в одном из следующих уроков, когда понадобится ввод |
std::cerr | поток вывода для сообщений об ошибках — отдельный от cout, чтобы не путать обычный вывод с диагностикой | нет, не в этом курсе на данном этапе |
std::clog | поток вывода для диагностических логов — похож на cerr, другая техническая настройка буферизации | нет, встречается редко |
То есть <iostream> подключает сразу всю эту группу — но использовать прямо сейчас нужно только std::cout и std::endl; остальное — не магия, которую от тебя скрывают, а просто ещё не понадобившиеся инструменты из того же набора.
12std::cout и пространство имён std
std::cout состоит из двух частей, разделённых :::
| Часть | Что означает |
|---|---|
std | namespace (пространство имён) стандартной библиотеки C++ — механизм, который группирует имена библиотеки и не даёт им конфликтовать с именами, которые придумаешь ты сам |
:: | оператор области видимости — «искать имя справа внутри того, что слева» |
cout | сам объект стандартного потока вывода — то, что физически выводит данные в консоль |
Почему нельзя просто написать cout без std::? Потому что cout — это имя, определённое внутри namespace std, а не в глобальной области. Компилятор ищет имена там, где ты указал, и без std:: он его не найдёт (если явно не написать using namespace std;, что в этом курсе намеренно не используется — так сразу видно, откуда взято каждое имя).
13Оператор <<
<< здесь не имеет отношения к сравнению или математике — в контексте std::cout это оператор, который передаёт данные, стоящие справа, в поток вывода, стоящий слева. Можно представлять его как стрелку: данные «текут» в сторону, куда указывают знаки.
Постепенно усложняя пример, видно, как он работает:
std::cout << "Hello, C++!"; // Hello, C++!
std::cout << 100; // 100
std::cout << 5.5; // 5.5
std::cout << "Health: " << 100; // Health: 100
std::cout << "Health: " << 100 << " / " << 100; // Health: 100 / 100
<< можно повторять сколько угодно раз подряд в одной инструкции — каждое значение справа просто добавляется к выводу сразу за предыдущим, без автоматических пробелов между ними (поэтому в примере выше пробелы после "Health: " и вокруг "/" — часть самих строк, а не что-то, что добавляет <<). Последняя строка примера не случайно выглядит как индикатор здоровья персонажа — вывод именно такой формы будет нужен в живой игре с самого начала, как только появится хоть одна цифра, которую нужно показать игроку.
14Строки, \n и std::endl
Текст в кавычках — "Hello, C++!" — называется строковым литералом: это данные, которые нужно вывести буквально как есть, в отличие от, например, Hello без кавычек — так компилятор искал бы переменную или функцию с именем Hello (см. типичные ошибки ниже).
Чтобы перейти на новую строку, есть два равноправных способа:
| Способ | Что делает | Особенность |
|---|---|---|
"\n" | символ перевода строки внутри строкового литерала | просто вставляет символ, ничего больше не делает |
std::endl | отдельный манипулятор потока — тоже переводит строку | дополнительно принудительно «сбрасывает» буфер вывода на экран — в учебных программах разницы не заметно, но \n в общем случае немного быстрее |
std::cout << "Hello, C++!\n";
std::cout << "Hello, C++!" << std::endl; // тот же результат
15Хуже → лучше: форматируем вывод
Одну и ту же задачу — вывести три строки текста — можно решить с разным качеством. Ниже три версии одной и той же программы, от рабочей-но-плохой до аккуратной.
std::cout << "Hello, C++!";
std::cout << "Alan";
std::cout << "I am learning C++.";
Компилируется без ошибок и технически «работает» — но результат в консоли выглядит так:
Hello, C++!AlanI am learning C++.
Все три строки склеились в одну — ни одного перевода строки нет ни в одном из литералов, поэтому std::cout просто печатает их подряд, ровно как написано.
std::cout << "Hello, C++!\n";
std::cout << "Alan\n";
std::cout << "I am learning C++.\n";
Результат уже читаемый — три отдельные строки:
Hello, C++!
Alan
I am learning C++.
Работает правильно, но три отдельные инструкции std::cout << ...; подряд — по одной на строку — визуально тяжелее, чем могло бы быть, ведь из §13 уже известно, что << можно повторять в одной инструкции сколько угодно раз.
std::cout << "Hello, C++!\n"
<< "Alan\n"
<< "I am learning C++.\n";
Тот же результат, но теперь видно, что это один логический вывод, а не три случайно оказавшихся рядом. Перенос каждого << на новую строку кода (это форматирование самого кода, не имеет отношения к \n) — распространённый способ читать длинные цепочки вывода в реальных проектах, в том числе в Unreal-коде.
16return 0;
main() объявлена как функция, возвращающая int (int main()) — поэтому она должна что-то вернуть операционной системе по завершении. 0 традиционно означает, что программа завершилась успешно; ненулевое значение обычно используется, чтобы сообщить, что программа завершилась с ошибкой или каким-то другим особым статусом — конкретный смысл числа определяет сама программа (или соглашения операционной системы).
return 0; в этом примере технически не обязателен — современный C++ разрешает опустить его именно в main(), и компилятор молча подставит 0 сам. Но здесь ты строишь учебный фундамент, а не пишешь финальный продакшн-код, поэтому return 0; лучше писать явно с самого начала: так сразу закладывается понимание, что функция вообще может что-то возвращать — это пригодится уже в следующих уроках, когда появятся функции с int, bool и другими типами возврата.
17Что можно вывести — таблица
| Значение | Пример | Результат |
|---|---|---|
| Текст | std::cout << "Hello"; | Hello |
| Целое число | std::cout << 100; | 100 |
| Дробное число | std::cout << 5.5; | 5.5 |
| Символ | std::cout << 'A'; | A |
| Несколько значений подряд | std::cout << "HP: " << 100; | HP: 100 |
18Попробуй изменить
Прежде чем переходить к практике ниже, стоит мысленно проследить эти изменения или проверить их в собственном компиляторе — не с чистого листа, а меняя уже готовые строки из примеров выше:
- В
std::cout << "Health: " << 100;замени100на75— результат станетHealth: 75. - Допиши ещё один
<<в ту же строку:std::cout << "Health: " << 75 << " / " << 100;— получитсяHealth: 75 / 100. - Замени
\nнаstd::endlв любой строке — результат на экране не изменится.
После пары таких правок обычно становится видно закономерность: вывод — это просто чередование «текст» + «значение» + «текст» + «значение», и им можно свободно управлять.
19Живая отладка: типичные ошибки компиляции
Точный текст ошибки зависит от компилятора (g++, Clang, MSVC) и его версии — ниже показан характерный вид в стиле g++, чтобы первая реальная ошибка компиляции не была для тебя полной неожиданностью.
; в конце инструкцииstd::cout << "Hello, C++!" // забыт ; в конце инструкции
return 0;
Ошибка указывает на строку 6 (return 0;), а не на ту, где реально пропущен символ, — компилятор понимает это, только дойдя до следующей строки. Если ошибка выглядит странно, полезно смотреть на строку выше указанной.
std:: перед coutcout << "Hello"; // не хватает std:: перед cout
Без using namespace std; (которую этот курс намеренно не использует) компилятор не находит cout в глобальной области — оно определено внутри namespace std. Некоторые компиляторы даже подсказывают исправление прямо в тексте ошибки, как здесь.
Hello без кавычек — это не строкаstd::cout << Hello; // без кавычек — идентификатор, а не строка
Компилятор ищет переменную или функцию с именем Hello и не находит её — кавычки превращают текст из «имени для поиска» в «данные для вывода как есть».
20Проверь себя
Ниже — вопросы разных типов: не только «что это», но и «что произойдёт», «в чём разница», «объясни своими словами». Каждый вопрос сначала стоит попробовать закрыть рукой ответ и подумать самому — ответ открывается отдельно, он никуда не убежит.
std::cout << "Score: " << 10 << "\n"; std::cout << "Score: " << 20;Показать объяснение
Две строки: Score: 10, а на следующей строке Score: 20. Первая инструкция заканчивается символом \n внутри строки — это переводит курсор на новую строку, а вторая инструкция начинает печатать сразу с неё.
std::cout << Score; (при этом нигде в программе нет переменной Score)Показать объяснение
Без кавычек Score — это не текст, а имя, которое компилятор ищет как переменную или функцию (см. §19, третья ошибка). Раз такой переменной не существует, компилятор остановится с ошибкой «Score was not declared in this scope». Если нужно вывести слово «Score» как текст — нужны кавычки: "Score".
"\n" отличается от std::endl — и есть ли ситуация, где это вообще имеет значение?Показать объяснение
Оба переводят строку на экране — с точки зрения того, что ты увидишь в консоли этого урока, разницы нет. Разница в том, что std::endl дополнительно принудительно «сбрасывает» внутренний буфер вывода, а \n — нет. В учебных программах это не заметно; в программах, которые выводят очень много текста очень быстро, лишние принудительные сбросы буфера через std::endl могут ощутимо замедлить вывод — поэтому в реальном коде чаще по умолчанию используют \n, а std::endl — только когда сброс буфера правда нужен прямо сейчас (например, перед аварийным завершением программы).
#include <iostream>, если весь вывод делает std::cout, а не сам #include?Показать объяснение
C++ как язык вообще не содержит встроенного способа вывести текст на экран — эту возможность даёт отдельная библиотека iostream, а не сам язык (§11). #include <iostream> говорит компилятору «подставь сюда описание этой библиотеки, прежде чем читать остальной код» — без этой строки компилятор просто не будет знать, что означает имя std::cout, и остановится с ошибкой ещё до того, как дойдёт до вопроса «а работает ли сам вывод».
21Практика
★ Создай программу, которая выводит: «Hello, C++!». Используй std::cout.
Подумай, какой инструмент из этого урока умеет выводить текст на экран — он уже назван в §12.
Нужен вызов вида std::cout << "..."; — текст внутри кавычек, инструкция заканчивается точкой с запятой.
Не забудь символ перевода строки \n внутри самой строки — иначе если добавишь что-то после, оно приклеится вплотную (см. §15).
Решение: std::cout << "Hello, C++!\n"; — не забудь #include <iostream> и return 0; вокруг неё.
★ Добавь вторую строку: своё имя, отдельной строкой сразу под «Hello, C++!». Используй \n или std::endl.
Добавь имя после перевода строки: отдельной инструкцией std::cout или продолжив строковый литерал в первой инструкции.
Два способа перевести строку равноправны (§14): либо \n внутри строки, либо отдельный std::endl после неё — оба варианта пройдут проверку.
Решение: std::cout << "Alan\n"; (или своё имя вместо «Alan»), второй строкой сразу после первого std::cout.
★★ Измени программу так, чтобы она выводила три строки: «Hello, C++!», своё имя и «I am learning C++.» — каждая строка на новой строке. В этот раз построй все три строки самостоятельно, без готовых заготовок.
Это то же самое действие, что в задании 2, только повторённое ещё раз — сколько всего строк нужно вывести и, значит, сколько всего должно быть вызовов вывода?
Для трёх отдельных строк на экране нужно как минимум два перевода строки — по одному между каждой парой соседних строк вывода.
Решение: три вызова std::cout подряд, каждый со своим \n в конце: "Hello, C++!\n", затем своё имя с \n, затем "I am learning C++.\n".
22Что ты теперь умеешь
До этого урока «программа» была просто словом. Теперь у тебя есть рабочая модель того, что происходит между желанием («пусть на экране появится текст») и результатом на экране — и ты можешь сам, без подсказки перед глазами, написать и объяснить программу, которая выводит текст в несколько строк:
- Ты можешь объяснить своими словами, почему компьютеру нужны предельно точные инструкции, а не общие пожелания — и почему из этого следует сама профессия программиста.
- Ты можешь назвать весь путь от файла с кодом до текста на экране: исходный код → компилятор → машинный код → исполняемый файл → запуск → main().
- Ты можешь самостоятельно написать программу, которая выводит одну или несколько строк текста через
std::cout, с переводом строки через\nилиstd::endl. - Ты можешь объяснить, зачем нужен
#include <iostream>, что означаетstd::передcout, и что делает оператор<<. - Ты можешь узнать и исправить три самые частые ошибки новичка в первой же программе: пропущенная
;, забытыйstd::, текст без кавычек.
В обычной C++ программе ты сам создаёшь main(), и выполнение начинается с неё. В Unreal Engine всё иначе: движок предоставляет собственную систему запуска приложения и игровой framework, поэтому в игровом коде ты обычно не пишешь main() сам — эту точку входа Unreal берёт на себя.
Но всё остальное, что ты изучаешь в этом курсе — функции, переменные, типы, классы, указатели, ссылки, структуры, память — станет прямой основой для работы с Unreal C++. Именно поэтому позже не возникнет вопроса «а где у Unreal main()?» — ты уже будешь знать, что это архитектурная особенность движка, а не то, что ты делаешь неправильно.
23Кратко
- Программа — последовательность точных инструкций; путь от текста до результата: исходный код → компилятор → машинный код → исполняемый файл → запуск → main().
#include <iostream>подключает возможности ввода-вывода — без негоstd::coutне работает.std::— обращение к имени внутри пространства имёнstd, где определёнcout.<<передаёт данные в поток вывода; можно повторять сколько угодно раз подряд.- Строка в кавычках — данные для вывода; без кавычек — это имя, которое компилятор ищет как переменную/функцию.
\nиstd::endl— оба переводят строку.return 0;сообщает об успешном завершении программы — в этом курсе пишем его явно всегда.
Следующий урок разбирает то, что здесь пока принималось на веру: почему строки вроде std::cout << "Hello, C++!\n"; вообще называются «инструкцией» (statement), чем инструкция отличается от выражения (expression) внутри неё — и как из этих двух понятий строится буквально любая программа, которую ты напишешь дальше в этом курсе.