LEARNING · STAGE 00 · PROGRAMMING BASICS

Что такое программа и как она запускается

Первый урок курса «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++!». Ещё без единого символа кода — просто последовательность шагов, которую понял бы человек:

  1. Записать желание на языке, у которого есть строгие правила и который умеет читать компьютер, — на C++.
  2. Явно сказать: «программа умеет выводить текст» (эту способность даёт отдельный набор инструментов — iostream, подключается отдельной строкой).
  3. Указать саму точку, откуда начнётся выполнение, — функцию main().
  4. Внутри неё — ровно одна инструкция: взять готовый инструмент для вывода (std::cout) и отправить в него нужный текст.
  5. Сообщить операционной системе, что всё прошло успешно.
  6. Перевести весь этот текст в машинный код — этим занимается отдельная программа, компилятор, — и только после этого запустить результат.

Это и есть алгоритм — план действий по шагам, ещё не привязанный к синтаксису конкретного языка. Дальше в уроке каждый из этих шести шагов получит свою 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++:

main.cppC++
#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 состоит из двух частей, разделённых :::

ЧастьЧто означает
stdnamespace (пространство имён) стандартной библиотеки C++ — механизм, который группирует имена библиотеки и не даёт им конфликтовать с именами, которые придумаешь ты сам
::оператор области видимости — «искать имя справа внутри того, что слева»
coutсам объект стандартного потока вывода — то, что физически выводит данные в консоль

Почему нельзя просто написать cout без std::? Потому что cout — это имя, определённое внутри namespace std, а не в глобальной области. Компилятор ищет имена там, где ты указал, и без std:: он его не найдёт (если явно не написать using namespace std;, что в этом курсе намеренно не используется — так сразу видно, откуда взято каждое имя).

13Оператор <<

<< здесь не имеет отношения к сравнению или математике — в контексте std::cout это оператор, который передаёт данные, стоящие справа, в поток вывода, стоящий слева. Можно представлять его как стрелку: данные «текут» в сторону, куда указывают знаки.

Постепенно усложняя пример, видно, как он работает:

Progressive examplesC++
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Хуже → лучше: форматируем вывод

Одну и ту же задачу — вывести три строки текста — можно решить с разным качеством. Ниже три версии одной и той же программы, от рабочей-но-плохой до аккуратной.

Хуже — работает, но нечитаемоC++
std::cout << "Hello, C++!";
std::cout << "Alan";
std::cout << "I am learning C++.";

Компилируется без ошибок и технически «работает» — но результат в консоли выглядит так:

Hello, C++!AlanI am learning C++.

Все три строки склеились в одну — ни одного перевода строки нет ни в одном из литералов, поэтому std::cout просто печатает их подряд, ровно как написано.

Лучше — с \n в конце каждой строкиC++
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 уже известно, что << можно повторять в одной инструкции сколько угодно раз.

Лучше всего — одна инструкция, цепочка <<C++
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;
консоль (иллюстрация)
g++ main.cpp -o main main.cpp: In function 'int main()': main.cpp:6:31: error: expected ';' before 'return' 6 | std::cout << "Hello, C++!" | ^ | ; Компиляция не удалась.

Ошибка указывает на строку 6 (return 0;), а не на ту, где реально пропущен символ, — компилятор понимает это, только дойдя до следующей строки. Если ошибка выглядит странно, полезно смотреть на строку выше указанной.

Не указано std:: перед cout
cout << "Hello";   // не хватает std:: перед cout
консоль (иллюстрация)
g++ main.cpp -o main main.cpp: In function 'int main()': main.cpp:6:5: error: 'cout' was not declared in this scope 6 | cout << "Hello"; | ^~~~ | std::cout Компиляция не удалась.

Без using namespace std; (которую этот курс намеренно не использует) компилятор не находит cout в глобальной области — оно определено внутри namespace std. Некоторые компиляторы даже подсказывают исправление прямо в тексте ошибки, как здесь.

Hello без кавычек — это не строка
std::cout << Hello;   // без кавычек — идентификатор, а не строка
консоль (иллюстрация)
g++ main.cpp -o main main.cpp: In function 'int main()': main.cpp:6:18: error: 'Hello' was not declared in this scope 6 | 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.

★ Добавь вторую строку: своё имя, отдельной строкой сразу под «Hello, C++!». Используй \n или std::endl.

★★ Измени программу так, чтобы она выводила три строки: «Hello, C++!», своё имя и «I am learning C++.» — каждая строка на новой строке. В этот раз построй все три строки самостоятельно, без готовых заготовок.

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

До этого урока «программа» была просто словом. Теперь у тебя есть рабочая модель того, что происходит между желанием («пусть на экране появится текст») и результатом на экране — и ты можешь сам, без подсказки перед глазами, написать и объяснить программу, которая выводит текст в несколько строк:

  • Ты можешь объяснить своими словами, почему компьютеру нужны предельно точные инструкции, а не общие пожелания — и почему из этого следует сама профессия программиста.
  • Ты можешь назвать весь путь от файла с кодом до текста на экране: исходный код → компилятор → машинный код → исполняемый файл → запуск → main().
  • Ты можешь самостоятельно написать программу, которая выводит одну или несколько строк текста через std::cout, с переводом строки через \n или std::endl.
  • Ты можешь объяснить, зачем нужен #include <iostream>, что означает std:: перед cout, и что делает оператор <<.
  • Ты можешь узнать и исправить три самые частые ошибки новичка в первой же программе: пропущенная ;, забытый std::, текст без кавычек.
🎮 А как это связано с Unreal Engine?

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