diff --git a/cpp/cpp_chapter_0020/examples/example_for_playground_001/wrapper_playground b/cpp/cpp_chapter_0020/examples/example_for_playground_001/wrapper_playground new file mode 100644 index 00000000..7c875bb8 --- /dev/null +++ b/cpp/cpp_chapter_0020/examples/example_for_playground_001/wrapper_playground @@ -0,0 +1,5 @@ +import std; + +int main() { + #INJECT-b585472fa +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/examples/example_for_playground_002/wrapper_playground b/cpp/cpp_chapter_0020/examples/example_for_playground_002/wrapper_playground new file mode 100644 index 00000000..7c875bb8 --- /dev/null +++ b/cpp/cpp_chapter_0020/examples/example_for_playground_002/wrapper_playground @@ -0,0 +1,5 @@ +import std; + +int main() { + #INJECT-b585472fa +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/examples/example_for_playground_003/wrapper_playground b/cpp/cpp_chapter_0020/examples/example_for_playground_003/wrapper_playground new file mode 100644 index 00000000..7c875bb8 --- /dev/null +++ b/cpp/cpp_chapter_0020/examples/example_for_playground_003/wrapper_playground @@ -0,0 +1,5 @@ +import std; + +int main() { + #INJECT-b585472fa +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/examples/example_for_playground_004/wrapper_playground b/cpp/cpp_chapter_0020/examples/example_for_playground_004/wrapper_playground new file mode 100644 index 00000000..7c875bb8 --- /dev/null +++ b/cpp/cpp_chapter_0020/examples/example_for_playground_004/wrapper_playground @@ -0,0 +1,5 @@ +import std; + +int main() { + #INJECT-b585472fa +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0010/wrapper_test b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0010/wrapper_test new file mode 100644 index 00000000..5709ca04 --- /dev/null +++ b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0010/wrapper_test @@ -0,0 +1,17 @@ +#include "ut.hpp" + +#include +#include + +namespace ut = boost::ut; + +int main() { + using namespace ut; + + "check user input"_test = [] { + std::string str = R"(#INJECT-b585472fa)"; + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + std::sort(str.begin(), str.end()); + expect(str == "346"); + }; +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0020/wrapper_playground b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0020/wrapper_playground new file mode 100644 index 00000000..86cd89bc --- /dev/null +++ b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0020/wrapper_playground @@ -0,0 +1,7 @@ +import std; + +#INJECT-b585472fa + +int main() { + std::println("{}", to_fahrenheit(100.0)); +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0020/wrapper_run b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0020/wrapper_run new file mode 100644 index 00000000..b8e0912e --- /dev/null +++ b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0020/wrapper_run @@ -0,0 +1,8 @@ +import std; + +#INJECT-b585472fa + +int main() { + std::println("to_fahrenheit(100.0) = {}", to_fahrenheit(100.0)); + std::println("to_fahrenheit(-50.0) = {}", to_fahrenheit(-50.0)); +} diff --git a/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0020/wrapper_test b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0020/wrapper_test new file mode 100644 index 00000000..1b2ae608 --- /dev/null +++ b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0020/wrapper_test @@ -0,0 +1,19 @@ +#include "ut.hpp" + +#INJECT-b585472fa + +namespace ut = boost::ut; + +int main() { + using namespace ut; + + "zero temperature"_test = [] { + expect(32.0_d == to_fahrenheit(0.0)) << "epsilon=0.001"; + expect(0.0_d == to_fahrenheit(-17.7778)) << "epsilon=0.001"; + }; + + "non-zero temperature"_test = [] { + expect(113.0_d == to_fahrenheit(45.0)) << "epsilon=0.001"; + expect(15.8_d == to_fahrenheit(-9.0)) << "epsilon=0.001"; + }; +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0030/wrapper_test b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0030/wrapper_test new file mode 100644 index 00000000..e7956501 --- /dev/null +++ b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0030/wrapper_test @@ -0,0 +1,16 @@ +#include "ut.hpp" + +#include +#include + +namespace ut = boost::ut; + +int main() { + using namespace ut; + + "check user input"_test = [] { + std::string str = R"(#INJECT-b585472fa)"; + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + expect(str == "4-1"); + }; +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0040/wrapper_test b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0040/wrapper_test new file mode 100644 index 00000000..16c0eee7 --- /dev/null +++ b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0040/wrapper_test @@ -0,0 +1,18 @@ +#include "ut.hpp" + +#include +#include + +namespace ut = boost::ut; + +int main() { + using namespace ut; + + "check user input"_test = [] { + std::string str = R"(#INJECT-b585472fa)"; + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c){ return std::tolower(c); }); + expect(str == "2e-5"); + }; +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0050/wrapper_playground b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0050/wrapper_playground new file mode 100644 index 00000000..ef76fead --- /dev/null +++ b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0050/wrapper_playground @@ -0,0 +1,10 @@ +import std; + +#INJECT-b585472fa + +int main() { + std::println("{}", hello_xor(true, false)); // true + std::println("{}", hello_xor(false, true)); // true + std::println("{}", hello_xor(false, false)); // false + std::println("{}", hello_xor(true, true)); // false +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0050/wrapper_run b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0050/wrapper_run new file mode 100644 index 00000000..fee9e377 --- /dev/null +++ b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0050/wrapper_run @@ -0,0 +1,10 @@ +import std; + +#INJECT-b585472fa + +int main() { + std::println("{}", hello_xor(true, false)); // true + std::println("{}", hello_xor(false, true)); // true + std::println("{}", hello_xor(false, false)); // false + std::println("{}", hello_xor(true, true)); // false +} diff --git a/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0050/wrapper_test b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0050/wrapper_test new file mode 100644 index 00000000..b248a13a --- /dev/null +++ b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0050/wrapper_test @@ -0,0 +1,16 @@ +#include "ut.hpp" + +#INJECT-b585472fa + +namespace ut = boost::ut; + +int main() { + using namespace ut; + + "all a, b combinations"_test = [] { + expect(hello_xor(true, false) == true); + expect(hello_xor(false, true) == true); + expect(hello_xor(true, true) == false); + expect(hello_xor(false, false) == false); + }; +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0060/wrapper_test b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0060/wrapper_test new file mode 100644 index 00000000..2baff028 --- /dev/null +++ b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0060/wrapper_test @@ -0,0 +1,19 @@ +#include "ut.hpp" + +#include +#include + +namespace ut = boost::ut; + +int main() { + using namespace ut; + + "check user input"_test = [] { + std::string str = R"(#INJECT-b585472fa)"; + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c){ return std::tolower(c); }); + + expect(str == "y"); + }; +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0070/wrapper_test b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0070/wrapper_test new file mode 100644 index 00000000..f443fcf1 --- /dev/null +++ b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0070/wrapper_test @@ -0,0 +1,17 @@ +#include "ut.hpp" + +#include +#include + +namespace ut = boost::ut; + +int main() { + using namespace ut; + + "check user input"_test = [] { + std::string str = R"(#INJECT-b585472fa)"; + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + + expect(str == "-4"); + }; +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0080/wrapper_test b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0080/wrapper_test new file mode 100644 index 00000000..321212e3 --- /dev/null +++ b/cpp/cpp_chapter_0020/tasks/cpp_chapter_0020_task_0080/wrapper_test @@ -0,0 +1,17 @@ +#include "ut.hpp" + +#include +#include + +namespace ut = boost::ut; + +int main() { + using namespace ut; + + "check user input"_test = [] { + std::string str = R"(#INJECT-b585472fa)"; + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + + expect(str == "12"); + }; +} \ No newline at end of file diff --git a/cpp/cpp_chapter_0020/text.md b/cpp/cpp_chapter_0020/text.md new file mode 100644 index 00000000..b57f6a59 --- /dev/null +++ b/cpp/cpp_chapter_0020/text.md @@ -0,0 +1,569 @@ +# Глава 2. Базовый синтаксис + +Вы познакомитесь с несколькими типами данных, переменными, операторами и простыми функциями. Это позволит сразу же приступить к практике. Подробнее каждая из этих тем будет раскрыта в посвященной ей главе. + +## Правила именования + +Правил именования в C++ всего несколько. Они распространяются на переменные, функции, классы и другие сущности в программе. + +Имя _должно начинаться_ с буквы латинского алфавита или символа подчеркивания `_`: `i`, `SearchEngine`, `connect_to_db`, `_abs_val`. + +Имя _может содержать_ только буквы, цифры и символы подчеркивания: `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/cpp_course_chapter_func/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) - фрагменты кода, выполняемые последовательно. + +Напишите функцию `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::printlin("{}", i); +} +``` +``` +9 +``` + +### Класс std::string + +Тип [std::string](https://en.cppreference.com/w/cpp/string/basic_string) реализует строку, не привязанную к кодировке. Каждый ее символ занимает ровно 1 байт. + +Если `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. + +## Операторы + +Вы уже познакомились с [оператором присваивания](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`. + +### Составное присваивание + +[Операторы составного присваивания](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++ +a < 0 || a / b <= max +``` + +```consoleoutput {.task_source #cpp_chapter_0020_task_0060} +``` +Приоритет деления `/` выше, чем сравнения `<=`. А приоритет `||` меньше, чем сравнения. {.task_hint} +```cpp {.task_answer} +n +``` + +Какое значение у переменной `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`. +- Операторы инкремента `++` и декремента `--` бывают префиксными и постфиксными.