Главная / Курсы / C++ по спирали / Базовый синтаксис
# Глава 2. Базовый синтаксис Вы познакомитесь с несколькими типами данных, переменными, операторами и простыми функциями. Это позволит сразу же приступить к практике. Подробнее каждая из этих тем будет раскрыта в посвященной ей главе. ## Правила именования Правил именования в C++ всего несколько. Они распространяются на переменные, функции, классы и другие сущности в программе. Имя _должно начинаться_ с буквы латинского алфавита или символа подчеркивания `_`: `i`, `SearchEngine`, `connect_to_db`, `_abs_val`. Символ подчёркивания в начале имени использовать [не рекомендуется:](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#nl8-use-a-consistent-naming-style) такие имена могут оказаться зарезервированными. Имя _может содержать_ только буквы, цифры и символы подчеркивания: `API_v3`, `isValid`, `catch2`. Имя _не должно совпадать_ с [ключевыми словами](https://en.cppreference.com/w/cpp/keyword) языка: `int`, `if`, `union` и другими. C++ — регистрозависимый язык. Поэтому `count`, `Count` и `COUNT` — это разные имена. Какие имена переменных составлены правильно? Перечислите номера строк через пробел. {.task_text} ``` 1 $total_volume 2 codek.meta 3 loop 4 MDFormatter 5 %TOKEN% 6 hex_val ``` ```consoleoutput {.task_source #cpp_chapter_0020_task_0010} ``` Имя `$total_volume` содержит недопустимый символ `$`. Имя `codek.meta` содержит недопустимую в названии точку. Имя `%TOKEN%` содержит недопустимый символ `%`. {.task_hint} ```cpp {.task_answer} 3 4 6 ``` ## Правила форматирования В C++ отсутствуют общепринятые правила форматирования. Например, нет разницы между пробелами и табуляцией, а наличие отступов опционально. Фигурные скобки можно ставить на любой строке. Перед вами два разных подхода к форматированию: ```c++ {.example_for_playground} int main() { int x = 5 + (2 - 1); } ``` ```c++ {.example_for_playground} int main() { int x=5+(2-1); } ``` Более того, вся программа может быть записана в одну строку, оставаясь при этом корректной. Хоть и не читабельной. ## Точка входа в программу Функция с именем `main` — это точка входа в программу (entry point). Ее наличие обязательно: после запуска программы управление передается именно ей. Так выглядит минимальная программа на C++, которая ничего не делает: ```c++ {.example_for_playground} int main() { } ``` Функция `main()` возвращает [целое число](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-main) типа `int` вызвавшему программу окружению. Это статус завершения: - 0 в случае успеха, - другое значение в случае ошибки. В нашем примере тело функции пустое: `{ }`. Но как же тогда формируется статус завершения? Функция `main()` — особая: при отсутствии явно возвращаемого значения она возвращает 0. Для наглядности мы можем вернуть его явно: ```c++ {.example_for_playground} int main() { return 0; } ``` Чтобы обеспечить выполнение кода, удостоверьтесь, что он вызывается из функции `main()`. ## Функции При объявлении функции сначала указывается тип возвращаемого значения, потом имя функции, после него параметры. А затем тело функции, обрамленное фигурными скобками: ![Функции в C++](https://raw.githubusercontent.com/senjun-team/senjun-courses/refs/heads/main/illustrations/cpp/function.jpg) {.illustration} Напомним, что **параметр** — это имя в определении функции. А **аргумент** — это фактическое значение, переданное функции при вызове. Рассмотрим реализацию функции `is_error()` и ее вызов: ```c++ {.example_for_playground} import std; bool is_error(int http_code) { return http_code >= 300; } int main() { bool res = is_error(404); std::println("404 is error code? {}", res); return 0; } ``` ``` 404 is error code? true ``` Для возврата из функции значения мы использовали [оператор](https://en.wikipedia.org/wiki/Operator_(computer_programming)) `return`. А для вывода `res` в консоль мы сделали две вещи: - Импортировали стандартную библиотеку `std`. В ней содержится функция [println()](https://en.cppreference.com/w/cpp/io/println), отвечающая за форматированный вывод. - Вызвали `println()`. Она находится в пространстве имен (namespace) `std`, и мы указали его при вызове: `std::println()`. Вы обратили внимание, что некоторые строки в программе заканчиваются точкой с запятой? Это **инструкции** (statements) — фрагменты кода, выполняемые последовательно. {#block-statements} Напишите функцию `to_fahrenheit()`, которая: {.task_text} - Принимает вещественное число типа `double` — температуру в градусах по Цельсию. - Возвращает градусы по шкале Фаренгейта (`double`). Формула: `°F = °C × 9.0/5.0 + 32.0`. Чтобы ее реализовать, воспользуйтесь операторами для сложения `+`, умножения `*` и деления `/`. {.task_text} ```c++ {.task_source #cpp_chapter_0020_task_0020} ``` Возвращаемое функцией значение, если параметр называется `celsius`: `celsius * 9.0 / 5.0 + 32.0`. {.task_hint} ```c++ {.task_answer} double to_fahrenheit(double celsius) { return celsius * 9.0 / 5.0 + 32.0; } ``` ## Переменные Чтобы создать переменную, укажите ее тип и имя. А затем через оператор `=` проинициализируйте значением: ```c++ int request_count = 0; ``` После типа можно перечислять несколько переменных, разделенных запятой: ```c++ int left = -100, right = 100; ``` Однако делать так [не рекомендуется:](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-name-one) такой код сложно читать. Лучше заводите по одной переменной на одну строку: ```c++ int left = -100; int right = 100; ``` В некоторых языках действует правило: если переменной не задано значение явно, то она инициализируется значением по умолчанию. C++ к таким языкам не относится: ```c++ int request_count; // Здесь может быть что угодно! ``` Поэтому при создании переменной [обязательно задавайте ей значение.](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es20-always-initialize-an-object) {#block-initialization} Чтобы изменить значение переменной, применяется уже знакомый вам **оператор присваивания** (assignment operator): ```c++ double default_len = 6.7; double len = default_len; len = len + 2; // 8.7 ``` Чему равны значения `a` и `b`? Введите их через пробел. {.task_text} ```c++ {.example_for_playground .example_for_playground_004} int a = -1 int b = 4; int c = a; a = b; b = c; ``` ```consoleoutput {.task_source #cpp_chapter_0020_task_0030} ``` В этом коде значения переменных `a` и `b` меняются местами с использованием переменной `c`. {.task_hint} ```cpp {.task_answer} 4 -1 ``` ## Константы Делать константами все переменные, которые не требуется изменять — это [отличная практика.](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rconst-immutable) Она предотвращает случайную перезапись переменной. Константы помечаются квалификатором типа [const](https://en.cppreference.com/w/cpp/language/cv). Попытка перезаписи константы приведет к ошибке компиляции. Квалификатор `const` может стоять как слева от типа, так и справа: ```c++ const int equator_len_km = 40075; int const winter_avg_temp = -5; ``` ## Знакомство с фундаментальными типами [Фундаментальные типы](https://en.cppreference.com/w/cpp/language/types#Standard_floating-point_types) (fundamental types) — это типы, встроенные в язык. Их имена являются ключевыми словами (keywords). Рассмотрим некоторые из них: - `int` — знаковое целое: `93`, `-3`, `0`, `9'100`. - `double` — число с плавающей точкой [двойной точности:](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) `-45.5`, `1e6`, `0.0`, `NAN` (not a number), `INFINITY`. - `bool` — логическое значение: `true`, `false`. - `char` — ASCII-символ: `'a'`, `'9'`, `'\t'`, `50`. - `void` — отсутствие значения. ### Типы int и double Большие числовые значения удобно разбивать по разрядам символом штриха `'`: ```c++ int avg_dist_to_moon_km = 384'400; ``` В [литералах](https://en.wikipedia.org/wiki/Literal_(computer_programming)) типа `double` целая часть отделяется от дробной точкой. ```c++ double weight = 1008.9; ``` Тип `double` поддерживает экспоненциальную запись числа. Она удобна для компактного представления длинных значений. ```c++ double a = 3e6; // 3x10^6 = 3'000'000.0 double b = -7e-2; // -7x10^-2 = -0.07 ``` Напишите экспоненциальное представление числа 0.00002. {.task_text} Если вы раньше не работали с экспоненциальной записью, самое время [разобраться в ней.](https://urvanov.ru/2021/12/08/%D0%BD%D0%B0%D1%83%D1%87%D0%BD%D0%B0%D1%8F-%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C-%D1%87%D0%B8%D1%81%D0%BB%D0%B0/) {.task_text} ```consoleoutput {.task_source #cpp_chapter_0020_task_0040} ``` Представим число 0.00002 в виде мантиссы и порядка. Мантисса: 2. Порядок: -5. {.task_hint} ```cpp {.task_answer} 2e-5 ``` ### Тип bool Логический тип `bool` может принимать два значения: `true` и `false`. ```c++ bool is_eq = false; bool has_open_connections = true; ``` ### Тип char Переменную символьного типа `char` можно инициализировать символом в одинарных кавычках: ```c++ char letter = 'S'; ``` А можно кодом символа из [ASCII-таблицы:](https://www.asciitable.com/) ```c++ char letter = 83; ``` Тип `char` представляет собой целое число, которое _можно_ трактовать как ASCII-код. Поэтому в обоих примерах переменная `letter` содержит одно и то же значение — число 83, в ASCII-таблице соответствующее заглавной букве S латинского алфавита. ### Тип void Используйте тип `void` в качестве типа возвращаемого значения функции, если она ничего не возвращает: ```c++ {.example_for_playground} void show_warning() { std::println("Something went wrong"); } ``` Кстати, вызывать `return` в конце такой функции не обязательно. Но его можно использовать для раннего выхода (early exit): ```c++ if (!is_connection_opened) { return; } ``` ## Знакомство с библиотечными типами Итак, мы обсудили несколько встроенных в язык типов. А теперь взглянем на два типа из стандартной библиотеки C++. Они пригодятся вам уже в следующей главе: - `std::size_t` — беззнаковое целое. - `std::string` — класс, реализующий строку. Класс — это пользовательский тип данных, призванный объединять данные (поля класса) и методы по работе с ними. ### Тип std::size_t Тип [std::size_t](https://en.cppreference.com/w/cpp/types/size_t) может хранить: - Индекс элемента в контейнере. [Контейнер](https://en.cppreference.com/w/cpp/container) — это коллекция элементов. Например, переменная типа `std::size_t` может хранить индекс символа строки. - Длину контейнера. - Размер объекта в байтах. - Счетчик цикла. Под капотом `std::size_t` — псевдоним (alias) для одного из фундаментальных беззнаковых целых типов. ```c++ import std; int main() { const std::size_t i = 9; std::println("{}", i); } ``` ``` 9 ``` ### Класс std::string Тип [std::string](https://en.cppreference.com/w/cpp/string/basic_string) реализует строку, не привязанную к кодировке. Она представляет собой последовательность символов типа `char`. Если `std::size_t` — всего лишь псевдоним фундаментального типа, то `std::string` — полноценный класс, содержащий методы для работы со строкой. ```c++ import std; int main() { std::string s = "The standard string class"; // Получение символа по его индексу: const char c = s[1]; // Длина строки: const std::size_t n = s.size(); // Запись символа по индексу: s[n-1] = 'S'; std::println("{}", s); std::println("{} {}", c, n); } ``` ``` The standard string clasS h 25 ``` Из примера видно, что строковый литерал заключается в двойные кавычки. А для обращения к символу строки по индексу используется оператор `[]`. Индексация начинается с нуля. Чтобы получить размер строки, мы вызвали метод `size()`. Для вызова метода между объектом класса и именем метода ставится точка: `s.find("st")`. Точка — это тоже оператор, и он нужен для доступа к членам класса (то есть его полям и методам). У класса `std::string` есть [множество](https://en.cppreference.com/w/cpp/string/basic_string) полезных методов. Вот некоторые из них с примерами для строки `s="example"`: - `size()` возвращает длину строки: `s.size() // 7`. - `empty()` возвращает `true`, если строка пустая: `s.empty() // false`. - `insert()` вставляет заданное количество символов по указанному индексу: `s.insert(1, 2, '8') // e88xample`. - `contains()` проверяет, присутствует ли в строке подстрока или символ: `s.contains("am") // true`, `s.contains('y') // false`. Этот метод появился в C++23. ## Операторы {#block-operators} Вы уже познакомились с [оператором присваивания](https://en.cppreference.com/w/cpp/language/operator_assignment) `=`, оператором взятия элемента по индексу `[]` и оператором доступа к члену класса `.`. Рассмотрим еще несколько категорий операторов, которые пригодятся в первую очередь. Чтобы поменять приоритет выполнения операторов, они группируются скобками. ### Операторы сравнения [Операторы сравнения](https://en.cppreference.com/w/cpp/language/operator_comparison) (comparison operators) применимы к большинству фундаментальных типов: - `==` — равенство. - `!=` — неравенство. - `<`, `>` — меньше, больше. - `<=`, `>=` — меньше или равно, больше или равно. Выражения сравнения приводятся к типу `bool`. **Выражение** (expression) — это последовательность операторов и операндов. ```c++ bool a = 8.1 < 16; // true bool b = -5 != -5; // false std::string s = ""; bool c = s.empty(); // true bool d = s.size() == 4; // false ``` Чему равно значение `b`? {.task_text} ```c++ {.example_for_playground .example_for_playground_005} std::string text = "Operator"; bool b = text[text.size() - 1] == text[3]; ``` ```consoleoutput {.task_source #cpp_chapter_0020_task_0090} ``` `text.size()` вернет 8. Символ по индексу 7 равен `r`. Символ по индексу 3 тоже равен `r`. {.task_hint} ```cpp {.task_answer} true ``` ### Логические операторы [Логические операторы](https://en.cppreference.com/w/cpp/language/operator_logical) применимы ко всем выражениям, которые приводятся к `bool`: - `&&` — «И»: `is_filled && is_valid`. - `||` — «ИЛИ»: `has_gps_location || connected_to_wifi`. - `!` — «НЕ» (отрицание): `!is_valid`. ```c++ {.example_for_playground .example_for_playground_001} bool is_online = true; bool is_updated = false; std::println("{}", is_online || is_updated); // true std::println("{}", !(is_online && is_updated)); // true ``` XOR — это булева функция, также известная как [исключающее «ИЛИ».](https://ru.wikipedia.org/wiki/%D0%98%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B0%D1%8E%D1%89%D0%B5%D0%B5_%C2%AB%D0%B8%D0%BB%D0%B8%C2%BB) Она принимает два флага и возвращает `true`, если один из них истинен, а другой — ложен. В остальных случаях она возвращает `false`. {.task_text} Напишите свою реализацию `hello_xor()`. {.task_text} ```c++ {.task_source #cpp_chapter_0020_task_0050} ``` Функция возвращает `true` тогда и только тогда, когда один из аргументов равен `true`, а другой — `false`. {.task_hint} ```c++ {.task_answer} bool hello_xor(bool a, bool b) { return (!a && b) || (a && !b); } ``` ### Арифметические операторы [Арифметические операторы](https://en.cppreference.com/w/cpp/language/operator_arithmetic) (arithmetic operators) применимы к любым выражениям, которые приводятся к числам. Они позволяют осуществлять: - `+` — сложение: `5 + 6 == 11`. - `-` — вычитание. `8 - 9 == -1`. - `*` — умножение. `3 * 7 == 21`. - `/` — деление. `10 / 4 == 2`. - `%` — деление по модулю, то есть получение остатка от деления. `11 % 3 == 2`. Перечисленные операторы называются бинарными. Они применяются к двум операндам: `a + b`. В C++ есть и унарные операторы — унарные плюс и минус: `+a`, `-a`. ### Составное присваивание {#block-compound-assignment} [Операторы составного присваивания](https://en.cppreference.com/w/cpp/language/operator_assignment) (compound assignment) объединяют присваивание переменной с арифметическим действием над ней. Их ввели в язык, чтобы записывать простые арифметические действия более кратко: ```c++ x += 5; // x = x + 5 x -= y; // x = x - y x *= 10; // x = x * 10; x /= y; // x = x / y; x %= 2; // x = x % 2; ``` ### Инкремент и декремент Увеличение или уменьшение значения на единицу можно записывать еще короче! [Оператор инкремента](https://en.cppreference.com/w/cpp/language/operator_incdec) `++` увеличивает значение на 1, а оператор декремента `--` уменьшает. Эти операторы применимы _только_ к целым числам. ```c++ ++x; // Эквивалентно x+=1 --x; // Эквивалентно x-=1 ``` В этом примере приведена префиксная форма операторов: пре-инкремент и пре-декремент. Есть и постфиксная форма: в ней `++` и `--` указываются после переменной. Это называется пост-инкрементом и пост-декрементом: ```c++ x++; x--; ``` Обе формы изменяют переменную и возвращают ее значение. Разница в _очередности_ этих действий. Префиксный оператор сначала изменяет переменную на 1, а потом возвращает значение: ```c++ a = 2; b = ++a; // a=3, b=3 ``` Постфиксный оператор сначала возвращает значение переменной, и лишь затем увеличивает ее на 1: ```c++ a = 2; b = a++; // a=3, b=2 ``` Постфиксные операторы инкремента и декремента имеют более высокий приоритет, чем префиксные. И еще одно важное отличие: префиксные формы возвращают саму переменную, а постфиксные — ее неизменяемую копию. Поэтому такой код не скомпилируется: ```c++ --i++; ``` Вначале выполнится постфиксный оператор и вернет неизменяемую копию переменной: `--(i++)`. А так как неизменяемое значение нельзя уменьшить, компилятор прервет сборку программы с ошибкой: ``` error: expression is not assignable --i++; ^ ~~~ ``` ### Приоритет операторов Операторы с равным приоритетом применяются слева направо. Порядок вычисления можно изменять с помощью скобок. Перечислим уже знакомые вам операторы по убыванию приоритета. Если в строке несколько операторов, то приоритет у них одинаковый. - `a::b` — разрешение области видимости, например `std::println()`. Это оператор с наивысшим приоритетом. - `a++`, `a--`, `a[b]`, `a.b` — постфиксные инкремент и декремент, взятие по индексу, доступ к члену класса. - `!a`, `+a`, `-a`, `++a`, `--a` — логическое отрицание, унарные плюс и минус, префиксные инкремент и декремент. - `a * b`, `a / b`, `a % b` — умножение, деление, модуль числа. - `a + b`, `a - b` — сложение, вычитание. - `a < b`, `a <= b`, `a > b`, `a >= b` — больше, меньше. - `a == b`, `a != b` — равенство, неравенство. - `a && b` — логическое «И». - `a || b` — логическое «ИЛИ». - `a = b`, `a += b`, `a -= b`, `a *= b`, `a /= b`, `a %= b` — присваивание, составное присваивание. Таблицу с приоритетом _всех_ операторов C++ вы можете [посмотреть на cppreference.](https://en.cppreference.com/w/cpp/language/operator_precedence) Нужны ли скобки, чтобы это выражение вычислилось как ожидается? `y/n`. {.task_text} ```c++ width < 0 || volume / length <= max_val ``` ```consoleoutput {.task_source #cpp_chapter_0020_task_0060} ``` Приоритет деления `/` выше, чем сравнения `<=`. А приоритет `||` меньше, чем сравнения. {.task_hint} ```cpp {.task_answer} n ``` Как быть, если вы сомневаетесь, нужны ли в выражении скобки? [Если без скобок код трудно читать, то ставьте их!](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-parens) Например, выражение из задачи выше со скобками выглядит проще: `(width < 0) || (volume / length <= max_val)`. Какое значение у переменной `x`? {.task_text} В случае ошибки напишите `err`. {.task_text} Ремарка: это пример плохого кода. В реальных проектах избегайте подобных трудночитаемых конструкций. Однако они встречаются на собеседованиях. {.task_text} ```c++ {.example_for_playground .example_for_playground_002} int a = 1, b = 2, c = 3; int x = a-- - b++ - c--; ``` ```consoleoutput {.task_source #cpp_chapter_0020_task_0070} ``` Приоритет постфиксных операторов выше, чем у оператора вычитания `-`. Постфиксный оператор сначала возвращает значение переменной, а потом изменяет его. Поэтому `x` равен `1 - 2 - 3`. {.task_hint} ```cpp {.task_answer} -4 ``` Что будет выведено в консоль? {.task_text} В случае ошибки напишите `err`. {.task_text} ```c++ {.example_for_playground .example_for_playground_003} int c = 2; int C = 5; std::print("{}", c++ * ++C); ``` ```consoleoutput {.task_source #cpp_chapter_0020_task_0080} ``` В этом выражении у постфиксного оператора максимальный приоритет. Следующим по приоритету выполнится префиксный оператор. И лишь затем — оператор умножения. Мы получим `2 * 6`. {.task_hint} ```cpp {.task_answer} 12 ``` ---------- ## Резюме - Функция `main()` — это точка входа в программу. - Если функция ничего не возвращает, то тип ее возвращаемого значения `void`. - При создании переменных всегда инициализируйте их значением. - Неизменяемые переменные помечаются квалификатором типа `const`. - Несколько фундаментальных типов: `bool`, `int`, `double`, `char`, `void`. - Пара библиотечных типов: `std::size_t`, `std::string`. - Операторы сравнения: `==`, `!=`, `<`, `>`, `<=`, `>=`. - Логические операторы: `&&`, `||`, `!`. - Арифметические операторы: `+`, `-`, `*`, `/`, `%`. - Операторы составного присваивания — это краткая форма выполнения над переменной арифметического действия и присваивания ей. Например, `x *= 2`. - Операторы инкремента `++` и декремента `--` бывают префиксными и постфиксными.
Отправка...
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!