# Лекция 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();
}
```
Объявления и определения важны при делении программы на файлы.