Краткое содержание
Этот урок изучает, какими путями данные входят в функцию и выходят из неё её результаты. По умолчанию C++ передаёт аргументы по значению: параметр есть копия, и ничто из того, что функция с ним делает, не достигает оригинала. Ссылка, напротив, есть второе имя для уже существующей ячейки, и она позволяет функции изменить переменную того, кто её вызывает; константная ссылка позволяет прочитать большие данные, не копируя их и не имея возможности их изменить. Чтобы вернуть несколько результатов, библиотека предлагает std::pair и std::tuple, которые принимаются структурными привязками. Затем рассматриваются область видимости имени (где оно видно) и время жизни переменной (когда она существует), вместе со скрытием имени и локальными переменными static, а завершает урок ссылка, которая переживает свою переменную. Понятие, объединяющее всё это, есть поток данных между частями программы: копирование против совместного использования и видимость имён как механизм изоляции.
Цели обучения
- Предсказывать действие функции на её аргументы в зависимости от того, передаются они по значению или по ссылке.
- Выбирать способ передачи каждого параметра (по значению для небольших типов, по константной ссылке для больших данных, по ссылке для того, что изменяется) и обосновывать выбор.
- Возвращать несколько результатов с помощью
std::pairилиstd::tupleи принимать их структурными привязками. - Определять область видимости и время жизни каждой переменной программы, включая локальные переменные
staticи те, что скрыты другой переменной. - Распознавать ссылку на локальную переменную или на временное значение, которого уже нет, вместе с предупреждением компилятора, которое о ней сообщает, и исправлять её.
Передача по значению
Параметр есть копия
Урок Функции показал, что каждый вызов создаёт кадр в стеке, с параметрами и локальными переменными функции, и что кадр исчезает при возврате. Остаётся уточнить, как параметр связан с аргументом, который его инициализирует. Ответ 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 с собственными ячейками.
Доказательство
До вызова в кадре
mainдве ячейки:xсо значением 1 иyсо значением 2.Вызов создаёт кадр
swapValues. Его параметрыaиbсуть новые ячейки, и в них копируются значения аргументов: 1 и 2. С этого момента междуaиxне остаётся никакой связи, кроме того, что они начали с одного и того же значения.Локальная переменная
savedсоздаётся в том же кадре и хранит копиюa.Два присваивания обменивают
aиb. Все изменяющиеся ячейки находятся в верхнем кадре; ячейкиmainне затрагиваются.При возврате кадр
swapValuesисчезает целиком, вместе с обменом, который в нём был.xиyпо-прежнему равны 1 и 2.
Что защищает копия
Передача по значению не есть недостаток, а есть правило, которое делает функции разумными. Во-первых, аргументом может быть любое выражение, а не только переменная: swapValues(x + 1, 7) допустим, потому что параметр инициализируется значением, откуда бы оно ни пришло. Во-вторых, вызывающий знает из объявления, не читая тела, что его переменные не изменятся. В-третьих, внутри функции параметр есть ещё одна локальная переменная, которая может служить рабочей переменной без последствий снаружи.
#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. Отсюда следует правило чтения, действующее во всём курсе: если у параметра нет знака &, который изучается в следующем параграфе, функция не может изменить переменную того, кто её вызывает, что бы она с ним ни делала.
Загрузка содержимого…