Перейти к содержимому
Topos Uranos

Краткое содержание

Этот урок изучает, какими путями данные входят в функцию и выходят из неё её результаты. По умолчанию C++ передаёт аргументы по значению: параметр есть копия, и ничто из того, что функция с ним делает, не достигает оригинала. Ссылка, напротив, есть второе имя для уже существующей ячейки, и она позволяет функции изменить переменную того, кто её вызывает; константная ссылка позволяет прочитать большие данные, не копируя их и не имея возможности их изменить. Чтобы вернуть несколько результатов, библиотека предлагает std::pair и std::tuple, которые принимаются структурными привязками. Затем рассматриваются область видимости имени (где оно видно) и время жизни переменной (когда она существует), вместе со скрытием имени и локальными переменными static, а завершает урок ссылка, которая переживает свою переменную. Понятие, объединяющее всё это, есть поток данных между частями программы: копирование против совместного использования и видимость имён как механизм изоляции.

Цели обучения

  1. Предсказывать действие функции на её аргументы в зависимости от того, передаются они по значению или по ссылке.
  2. Выбирать способ передачи каждого параметра (по значению для небольших типов, по константной ссылке для больших данных, по ссылке для того, что изменяется) и обосновывать выбор.
  3. Возвращать несколько результатов с помощью std::pair или std::tuple и принимать их структурными привязками.
  4. Определять область видимости и время жизни каждой переменной программы, включая локальные переменные static и те, что скрыты другой переменной.
  5. Распознавать ссылку на локальную переменную или на временное значение, которого уже нет, вместе с предупреждением компилятора, которое о ней сообщает, и исправлять её.

Передача по значению

Параметр есть копия

Урок Функции показал, что каждый вызов создаёт кадр в стеке, с параметрами и локальными переменными функции, и что кадр исчезает при возврате. Остаётся уточнить, как параметр связан с аргументом, который его инициализирует. Ответ C++ по умолчанию самый простой: параметр есть новая локальная переменная, которая инициализируется копией значения аргумента. Если это так, функция, которая изменяет свои параметры, изменяет свои копии, и переменные того, кто её вызывает, не меняются. Программа ниже испытывает это на функции, которая пытается обменять значениями два целых числа.

Программа на C++
#include <print>​// Пытается обменять a и b; обменивает только их копии.void swapValues(int a, int b){    const int saved{a};    a = b;    b = saved;    std::println("Внутри: a = {}, b = {}", a, b);}​int main(){    int x{1};    int y{2};    swapValues(x, y);    std::println("После: x = {}, y = {}", x, y);}
Вывод
Внутри: a = 2, b = 1После: x = 1, y = 2

Внутри функции обмен выполняется правильно; после вызова x и y сохраняют свои значения. Компилятор не предупреждает, потому что предупреждать не о чем: код обменивает две локальные переменные, которые исчезают по его завершении. Рисунок прослеживает кадры стека во время вызова.

ПримерДва кадра, четыре ячейки

Кадр main содержит x и y; вызов создаёт над ним кадр swapValues с собственными ячейками.

Доказательство

  1. До вызова в кадре main две ячейки: x со значением 1 и y со значением 2.

  2. Вызов создаёт кадр swapValues. Его параметры a и b суть новые ячейки, и в них копируются значения аргументов: 1 и 2. С этого момента между a и x не остаётся никакой связи, кроме того, что они начали с одного и того же значения.

  3. Локальная переменная saved создаётся в том же кадре и хранит копию a.

  4. Два присваивания обменивают a и b. Все изменяющиеся ячейки находятся в верхнем кадре; ячейки main не затрагиваются.

  5. При возврате кадр swapValues исчезает целиком, вместе с обменом, который в нём был. x и y по-прежнему равны 1 и 2.

Что защищает копия

Передача по значению не есть недостаток, а есть правило, которое делает функции разумными. Во-первых, аргументом может быть любое выражение, а не только переменная: swapValues(x + 1, 7) допустим, потому что параметр инициализируется значением, откуда бы оно ни пришло. Во-вторых, вызывающий знает из объявления, не читая тела, что его переменные не изменятся. В-третьих, внутри функции параметр есть ещё одна локальная переменная, которая может служить рабочей переменной без последствий снаружи.

Программа на C++
#include <print>​// Количество десятичных цифр числа n.// Предусловие: n >= 0.// Постусловие: возвращает количество цифр числа n (1 для 0).int digitCount(int n){    int count{1};    while (n >= 10) {        n = n / 10;        ++count;    }    return count;}​int main(){    const int population{48'210};    std::println("Число {} содержит {} цифр", population, digitCount(population));}
Вывод
Число 48210 содержит 5 цифр

Функция разрушает свой параметр, деля его на 10, пока в нём не останется одна цифра; так как это копия, population остаётся нетронутой и может быть const. Отсюда следует правило чтения, действующее во всём курсе: если у параметра нет знака &, который изучается в следующем параграфе, функция не может изменить переменную того, кто её вызывает, что бы она с ним ни делала.