From 6c14501bc680e055cf4bfaef29a780c572fea696 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=BE?=
 =?UTF-8?q?=D0=B7=D0=BB=D1=8E=D0=BA?= <KozliukDA@mpei.ru>
Date: Mon, 10 Apr 2023 02:46:42 +0300
Subject: [PATCH] =?UTF-8?q?lecture03:=20=D1=84=D1=83=D0=BD=D0=BA=D1=86?=
 =?UTF-8?q?=D0=B8=D0=B8,=20=D1=83=D0=BA=D0=B0=D0=B7=D0=B0=D1=82=D0=B5?=
 =?UTF-8?q?=D0=BB=D0=B8,=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B8,=20=D1=81?=
 =?UTF-8?q?=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D1=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                    |   2 +-
 lectures/lecture03/README.md | 554 +++++++++++++++++++++++++++++++++++
 2 files changed, 555 insertions(+), 1 deletion(-)
 create mode 100644 lectures/lecture03/README.md

diff --git a/README.md b/README.md
index 5535491..d29830e 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 
 1. [Основы языка C++](lectures/lecture01)
 2. [Системы контроля версий](lectures/lecture02)
-3. Структурирование кода и данных (функции, указатели)
+3. [Структурирование кода и данных (функции, указатели)](lectures/lecture03)
 4. Сборка программ из нескольких файлов
 5. Ввод-вывод, модульное тестирование
 6. Библиотеки
diff --git a/lectures/lecture03/README.md b/lectures/lecture03/README.md
new file mode 100644
index 0000000..98939da
--- /dev/null
+++ b/lectures/lecture03/README.md
@@ -0,0 +1,554 @@
+# Лекция 3. Функции, указатели, ссылки, структуры
+
+## Функции (повторение)
+
+Функция — это именованный блок кода с формальными параметрами
+и возвращаемым значением.
+
+Пример:
+
+```cpp
+double
+multiply(double x, int y) {
+    return x * y;
+}
+```
+
+Здесь `double` — тип возвращаемого значения (результата),
+`multiply` — имя функции,
+`(double x, int y)` — список формальных параметров,
+а в фигурных скобках — тело функции.
+
+Если вместо типа возвращаемого значения указать `void`,
+это значит, что функция значения не возвращает.
+
+Тип возвращаемого значения, имя функции и список формальных параметров
+могут быть разнесены по строкам в соответствии со стилем и читаемостью.
+Например, мы в лабораторных работах предпочитаем тип возвращаемого значения
+писать на отдельной строке.
+С точки зрения C++ это безразлично.
+
+Типы ее формальных параметров составляют *сигнатуру* функции.
+Отметим, что тип возвращаемого значения, имя функции и имена параметров
+в сигнатуру не входят.
+Например, сигнатура `multiply`: `(double, int)`.
+
+Оператор `return` определяет возвращаемое значение
+и немедленно завершает функцию.
+В `void`-функциях тоже можно использовать `return` без указания результата,
+чтобы немедленно выйти из функции.
+
+**Внимание.**
+Выполнение не-`void` функции всегда должно заканчиваться `return`,
+хотя C++ не отслеживает это жестко (но может выдать предупреждение).
+
+Внутри тела функции её формальные параметры являются локальными переменными.
+Они независимы от переменных в месте вызова функции,
+даже если у них одинаковые имена:
+
+```cpp
+void func(int x) {
+    x = 66;
+}
+...
+int x = 42;
+func(x);
+// x == 42
+```
+
+При вызове функции значения, переданные ей в качестве аргументов,
+*копируются* в её переменные-параметры.
+
+## Указатели (повторение)
+
+**Примечание.**
+В этой лекции не рассматривается динамическое выделение памяти,
+а только сами указатели как тип данных и их применение в связи с функциями.
+
+Всю память компьютера можно представить как массив байтов.
+Тогда индекс в этом массиве, то есть номер ячейки памяти,
+называется *адресом,* а переменная, содержащая адрес, называется *указателем.*
+
+При объявлении указателей перед именем переменной ставится звездочка.
+Например, так объявляется указатель на действительное число:
+
+```cpp
+double* r1;
+```
+
+Часто звездочку прижимают к имени типа, а не переменной, как в примере.
+Есть известная «ловушка»:
+
+```cpp
+double* x, y;
+```
+
+Здесь только `x` является указателем (имеет тип `double*`),
+а `y` является обычной переменной (имеет тип `double`).
+Надо либо ставить звездочку перед каждой переменной-указателем,
+либо объявлять каждую переменную отдельно (это почти всегда нагляднее).
+
+В указатель записывается не значение переменной, а ее адрес.
+Адрес берется *оператором взятия адреса* в виде амперсанда (`&`):
+
+```cpp
+double x = 3.14;
+double* p = &x;
+```
+
+Вот как расположены при этом данные в памяти:
+
+```
+адреса:        0   1       8   9   10  11  12      42  43  44  45  46
+            +---+-     -+---+---+---+---+-     -+---+---+---+---+-
+ячейки:     |   | ..... |     3.14      | ..... |       8       | ...
+            +---+-     -+---+---+---+---+-     -+---+-.'+---+---+-
+                        ↑\_____________/         \__.'_________/
+                        |       x                 .'    p
+                        |                       .'
+                       &x = 8 = ...............'
+```
+
+Чтобы, имея указатель, обратиться к тем данным, адрес которых он хранит,
+используется оператор *разыменования* в виде звездочки:
+
+```cpp
+*p = 2.71;  // x = 2.71
+```
+
+Есть специальное значение указателя — нулевой: `NULL`, `0` или `nullptr`.
+Указатель, хранящий такой адрес, запрещено разыменовывать.
+
+Начальное значение указателя, если оно не присвоено явно, не определено,
+как и для любых других переменных встроенных типов.
+Таким образом, переменной-указателем нельзя корректно пользоваться,
+пока ей что-нибудь не присвоено.
+
+### Висячие указатели (dangling pointers)
+
+К сожалению, C++ не отслеживает, что значение указателя всегда корректно.
+Рассмотрим пример:
+
+```cpp
+int* p = nullptr;
+if (...) {
+    int x;
+    p = &x;
+    ...
+}
+cout << *p;
+```
+
+В последней строке `p` указывает на переменную, которая объявлена внутри `if`
+и уже не существует после выхода из фигурных скобок.
+Поэтому, хотя указатель и хранит не `nullptr`, разыменовывать его нельзя.
+Такие указатели на данные, которых уже нет, называются *висячими (dangling).*
+
+Другой пример:
+
+```cpp
+int* func() {
+    int x = 42;
+    return &x;
+}
+...
+auto p = func();
+cout << *p;
+```
+
+Здесь функция возвращает адрес локальной переменной.
+Однако локальные время жизни локальных переменных ограничено функцией,
+поэтому пользоваться таким возвращаемым значением нельзя.
+
+При работе с указателями надо всегда думать о том,
+чтобы время жизни указателя не превышало время жизни данных,
+адрес которых указатели хранят.
+
+## Ссылки
+
+Ссылка (reference) — это новое имя для существующего объекта.
+Объект может быть переменной или её частью, такой как элемент массива.
+
+Ссылки объявляются с использованием амперсанда:
+
+```cpp
+int var = 42;
+int& ref = var;
+```
+
+Не следует путать амперсанд при объявлении ссылок
+с амперсандом-оператором взятия адреса!
+
+Обращение к ссылке эквивалентно обращению к тому, на что она ссылается:
+
+```cpp
+cout << ref;  // 42
+ref = 66;
+cout << var;  // 66
+```
+
+В частности, так как ссылка не является самостоятельной переменной,
+её адрес — это адрес того, на что она ссылается,
+а явное разыменование не нужно (если это не ссылка на указатель, конечно):
+
+```cpp
+if (&var == &ref) { ... }   // истинно
+
+*ref = 66;                  // ОШИБКА: обращение к ref — обращение к var,
+                            // а var не указатель, разыменовать её нельзя.
+```
+
+Поскольку ссылка — новое имя для *существующего* объекта,
+у нее всегда должно быть начальное значение:
+
+```cpp
+int& ref;  // ОШИБКА: новое имя для чего?
+```
+
+Не бывает «нулевой ссылки», подобной нулевому указателю.
+
+Даже вне связи с функциями ссылки могут применяться,
+чтобы дать более короткие или понятные имена в коде:
+
+```cpp
+double& first = some_vector[0];
+// ...
+fisrt = 0;
+```
+
+### Передача входных параметров функций по ссылкам
+
+Рассмотрим функцию, суммирующую элементы вектора:
+
+```cpp
+double sum(vector<double> xs) {
+    double total{};
+    for (double x : xs) {
+        total += x;
+    }
+    return total;
+}
+```
+
+Вспомним, что при вызове функции значения аргументов *копируются*
+в переменные-формальные параметры, то есть в `xs` будет помещена копия вектора,
+который передан функции.
+Если этот вектор большой, будет потрачено много лишней памяти,
+кроме того, это копирование бесполезно — функция не меняет `xs` даже внутри.
+
+Можно передавать `xs` по ссылке, чтобы не копировать вектор,
+а работать с той переменной, которую передали функции, напрямую:
+
+```cpp
+double sum(vector<double>& xs) { ... }
+...
+vector<double> xs;
+double s = sum(xs);
+```
+
+Однако есть две проблемы:
+
+* Нельзя вызвать `sum({1, 2, 3})`,
+    потому что `{1, 2, 3}` — выражение, а не переменная.
+    Это запрещено, потому что с помощью ссылки возможно поменять то,
+    на что она ссылается, однако выражение поменять нельзя в принципе.
+    (Можно изменить значение переменной, содержащей `5 = 3 + 2`,
+    но нельзя поменять саму `5`, «пятерку как таковую».)
+
+* При чтении кода непонятно, не меняет ли `sum()` свой аргумент,
+    и нет гарантий, что она этого не начнет делать в будущем.
+
+Итак, нужно сослаться на аргумент, но сделать этот так,
+чтобы с точки зрения `sum()` эта переменная была неизменяемой,
+даже если в месте вызова менять ее можно.
+Это делается с помощью константной ссылки:
+
+```cpp
+double sum(const vector<double>& xs) { ... }
+```
+
+При передаче параметров нетривиального типа (не `int`, `double` и т. п.),
+в том числе при передаче `std::vector<T>` и `std::string`,
+рекомендуется по умолчанию использовать константную ссылку.
+
+## Выходные параметры функций через указатели и ссылки
+
+Составим функцию для решения квадратного уравнения в действительных числах.
+Очевидно, что она принимает коэффициенты уравнения.
+Возвращает она три значения:
+* признак, что действительные решения есть;
+* корень `x1`;
+* корень `x2`.
+
+Однако у функции возвращаемое значение только одно, допустим, признак.
+Как вернуть корни?
+
+Можно сделать это через ссылки:
+
+```cpp
+bool solve(double a, double b, double c, double& x1, double& x2) {
+    auto d = b*b - 4*a*c;
+    if (d < 0) {
+        return false;
+    }
+    x1 = (-b + sqrt(d)) / 2*a;
+    x2 = (-b - sqrt(d)) / 2*a;
+    return true;
+}
+```
+
+Вызов функции будет выглядеть так:
+
+```cpp
+double x1, x2;
+if (solve(3, 2, 1, x1, x2)) {
+    cout << "x1 = " << x1 << "\n"
+         << "x2 = " << x2 << "\n";
+} else {
+    cout << "Нет действительных корней.\n";
+}
+```
+
+Можно было бы использовать указатели:
+
+```cpp
+bool solve(double a, double b, double c, double* x1, double* x2) {
+    auto d = b*b - 4*a*c;
+    if (d < 0) {
+        return false;
+    }
+    *x1 = (-b + sqrt(d)) / 2*a;
+    *x2 = (-b - sqrt(d)) / 2*a;
+    return true;
+}
+```
+
+Вызов функции будет выглядеть так:
+
+```cpp
+double x1, x2;
+if (solve(3, 2, 1, &x1, &x2)) {
+    cout << "x1 = " << x1 << "\n"
+         << "x2 = " << x2 << "\n";
+} else {
+    cout << "Нет действительных корней.\n";
+}
+```
+
+Какой вариант лучше и почему?
+
+В случае с указателями в функцию мог бы быть передан нулевой указатель:
+
+```cpp
+solve(3, 2, 1, nullptr, &x2);
+```
+
+Программа успешно компилировалась бы, но при запуске аварийно завершилась,
+поскольку в функции `solve()` был бы разыменован нулевой указатель `x1`.
+
+Может показаться, что из-за этого вариант с указателями хуже:
+функция должна проверять, что ей не передали нулевой указатель,
+а со ссылками этого не потребовалось бы — ведь «нулевых ссылок» нет.
+Однако наличие особого значения у указателя — не только проблема,
+но и возможность связать с этим значением особую логику.
+Например, функция могла бы быть реализована так:
+
+```cpp
+bool solve(double a, double b, double c, double* x1, double* x2) {
+    auto d = b*b - 4*a*c;
+    if (d < 0) {
+        return false;
+    }
+    if (x1) {
+        *x1 = (-b + sqrt(d)) / 2*a;
+    }
+    if (x2) {
+        *x2 = (-b - sqrt(d)) / 2*a;
+    }
+    return true;
+}
+```
+
+Теперь, если передать в функцию `nullptr` в качестве `x1` или `x2`,
+она не будет вычислять соответствующий корень.
+Таким образом программа может сэкономить вычисления,
+если оба корня ей заведомо не нужны.
+Можно даже передать `nullptr` в качестве и `x1`, и `x2`,
+тогда функция просто проверит, есть ли действительные решения —
+возможно, конкретной программе, которая использует `solve()`,
+только это и нужно.
+
+Вывод: использовать для передачи выходных параметров указатели или ссылки
+зависит от того, нужна ли дополнительная гибкость логики,
+которую дает наличие особого значения — нулевого указателя.
+
+Заметим, что некоторые проекты предпочитают всегда использовать указатели,
+даже если предполагается, что они обязаны не быть `nullptr` никогда.
+Причина в том, что в случае ссылок по вызову `solve(a, b, c, x1, x2)`
+невозможно определить, какие из переменных после этой строки могут поменяться.
+Вызов же `solve(a, b, c, &x1, &x2)` ясно показывает,
+что `solve()` может поменять `x1` и `x2`.
+
+## Структуры
+
+Структура — это пользовательский тип данных,
+представляющий собой совокупность именованных полей различных типов.
+
+Структуры удобны для того, чтобы сгруппировать несколько переменных,
+которые используются в программе совместно.
+Например, в задаче ЛР № 1 можно было бы объединить входные данные в структуру:
+
+```cpp
+struct Input {
+    std::vector<double> numbers;
+    size_t bin_count;
+};
+```
+
+Здесь `Input` — имя структуры, а `numbers` и `bin_count` — её поля.
+
+Важно понять, что определение выше описывает тип данных (аналог `std::vector`
+или `std::string`), а не переменную, то есть код выше не описывает переменные
+`numbers` и `bin_count`, куда можно сохранить значения. Он описывает,
+что каждая переменная типа `Input` содержит поля `numbers` и `bin_count`,
+в которые уже можно сохранить конкретные данные.
+
+Переменные типа структур объявляются так же, как переменные других типов;
+именем типа выступает имя структуры:
+
+```cpp
+Input x;
+```
+
+Говорят, что переменная `x` — экземпляр структуры.
+
+К полям структуры обращаются через точку:
+
+```cpp
+cout << x.numbers.size(); // 0
+cin >> x.bin_count;
+if (x.bin_count == 0) { ... }
+```
+
+Можно объявить несколько переменных типа структуры:
+
+```cpp
+Input y;
+Input z;
+
+y.bin_count = 3;
+z.bin_count = 4;
+```
+
+Значения, хранимые в `x`, `y` и `z` будут независимы друг от друга.
+
+### Инициализация полей
+
+Вернемся к объявлению `x`, чему было равно `x.bin_count` до её ввода?
+Так как в определении структуры
+для поля `bin_count` не было указано значения по умолчанию,
+то и конкретное начальное значение `x.bin_count` не определено.
+
+Удобно, чтобы у всех полей были начальные значения.
+Например, так можно инициализировать нулем `bin_count`
+любой новой переменной типа `Input`:
+
+```cpp
+struct Input {
+    vector<double> numbers;
+    size_t bin_count{}; // или size_t bin_count = 0;
+};
+```
+
+Как известно, переменные типа `vector<T>` по умолчанию содержат пустой вектор,
+поэтому для поля `numbers` о начальном значении заботиться не нужно.
+
+### Оператор «стрелка» (`->`)
+
+На переменные типа структуры могут быть указатели:
+
+```cpp
+Input* p = new Input;
+```
+
+Чтобы использовать оператор `.` для доступа к полям структуры,
+на которую указывает `p`, нужно сначала разыменовать `p`.
+Оператор `.` имеет наивысший приоритет, поэтому нужны скобки: `(*p).bin_count`.
+Это громоздко, поэтому в C++ введено оператор «стрелки» `->`,
+чтобы записать то же самое проще: `p->bin_count`.
+
+## Перегрузка функций
+
+Можно объявить набор функций с одинаковыми именами, но разными сигнатурами:
+
+```cpp
+// print(3.14) -> 3.14
+void print(double x) {
+    cout << x;
+}
+
+// print({1, 2, 3}) -> {1, 2, 3};
+void print(const std::vector<double>& xs) {
+    cout << "{";
+    bool need_comma = false;
+    for (auto x : xs) {
+        if (need_comma) {
+            cout << ", ";
+        } else {
+            need_comma = true;
+        }
+        print(x);  // вызов print(double), а не рекурсия
+    }
+    cout << "}";
+}
+```
+
+Говорят, что функция `print()` *перегружена (overloaded),*
+а каждая из конкретных функций называется её *перегрузкой.*
+Когда компилятор встречает вызов `print()`, он анализирует типы параметров
+и вызывает соответствующую перегрузку.
+
+Перегрузки бывают полезны в обобщенном коде,
+то есть таком, который готов работать с различными типами данных.
+Об это будет рассказано на последующих лекциях.
+
+## Объявление и определение функции
+
+Будет ли компилироваться программа такого вида?
+
+```cpp
+void foo() {
+    bar();
+}
+
+void bar() {
+    foo();
+}
+```
+
+Функция `foo()` вызывает функцию `bar()` раньше, чем она описана,
+что вызовет ошибку.
+Однако перенести описание `bar()` выше описания `foo()` нельзя:
+`bar()` содержит вызов `foo()`, который тогда окажется выше,
+чем описана `foo()`.
+
+Рассуждая логически, для того, чтобы скомпилировать `foo()`,
+компилятору не нужно тело функции `bar()` — достаточно знать,
+что такая функция есть, и какие у нее параметры (нет параметров).
+Сообщить компилятору о сигнатуре функции можно с помощью
+*объявления функции (declaration)*.
+В отличие от уже знакомого *определения функции (definition),*
+объявление не содержит тела, а кончается точкой с запятой:
+
+```cpp
+void bar();     // объявление bar()
+
+void foo() {
+    bar();      // вызов bar()
+}
+
+void bar() {    // определение bar()
+    foo();
+}
+```
+Объявления и определения важны при делении программы на файлы.