2.3 – Введение в параметры и аргументы функций
На предыдущем уроке мы узнали, что функция может возвращать значение обратно в вызывающую функцию. Мы использовали это для создания модульной функции getValueFromUser
, которую мы использовали в этой программе:
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
int main()
{
int num { getValueFromUser() };
std::cout << num << " doubled is: " << num * 2 << '\n';
return 0;
}
Однако что, если мы также захотим поместить строку вывода в отдельную функцию? Вы можете попробовать что-то вроде этого:
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
// Эта функция не будет компилироваться
void printDouble()
{
std::cout << num << " doubled is: " << num * 2 << '\n';
}
int main()
{
int num { getValueFromUser() };
printDouble();
return 0;
}
Этот код не скомпилируется, потому что функция printDouble
не знает, что такое идентификатор num
. Вы можете попробовать определить num
как переменную внутри функции printDouble()
:
void printDouble()
{
int num{}; // мы добавили эту строку
std::cout << num << " doubled is: " << num * 2 << '\n';
}
Хотя это устраняет ошибку компиляции и делает программу пригодной для компиляции, программа по-прежнему работает некорректно (всегда выводит «0 doubled is: 0»). Суть проблемы здесь в том, что у функции printDouble
нет способа получить доступ к значению, введенному пользователем.
Нам нужен способ передать значение переменной num
в функцию printDouble
, чтобы printDouble
могла использовать это значение в теле функции.
Параметры и аргументы функции
Во многих случаях полезно иметь возможность передавать информацию вызываемой функции, чтобы у этой функции были данные для работы. Например, если мы хотим написать функцию для сложения двух чисел, нам нужен способ сообщить этой функции, какие два числа нужно складывать при ее вызове. Иначе как функция узнает, что складывать? Мы делаем это с помощью параметров и аргументов функции.
Параметр функции – это переменная, используемая в функции. Параметры функции работают почти так же, как переменные, определенные внутри функции, но с одним отличием: они всегда инициализируются значением, предоставленным вызывающей функцией.
Параметры функции определяются в объявлении функции путем помещения их в скобки после идентификатора функции, при этом несколько параметров разделяются запятыми.
Вот несколько примеров функций с разным количеством параметров:
// Эта функция не принимает параметров
// Она никак не зависит от вызывающей стороны
void doPrint()
{
std::cout << "In doPrint()\n";
}
// Эта функция принимает один целочисленный параметр с именем x
// Вызывающая сторона предоставит значение x
void printValue(int x)
{
std::cout << x << '\n';
}
// Эта функция имеет два целочисленных параметра, один с именем x, а другой с именем y
// Вызывающая сторона предоставит как x, так и y
int add(int x, int y)
{
return x + y;
}
Аргумент – это значение, которое передается от вызывающей стороны к функции при ее вызове:
doPrint(); // у этого вызова нет аргументов
printValue(6); // 6 - это аргумент, переданный в функцию printValue()
add(2, 3); // 2 и 3 - аргументы, переданные в функцию add()
Обратите внимание, что несколько аргументов также разделяются запятыми.
Как параметры и аргументы работают вместе
Когда функция вызывается, все параметры функции создаются как переменные, а значение каждого из аргументов копируется в соответствующий параметр. Этот процесс называется передачей по значению.
Например:
#include <iostream>
// Эта функция имеет два целочисленных параметра, один с именем x, а другой с именем y
// Значения x и y передаются вызывающей стороной
void printValues(int x, int y)
{
std::cout << x << '\n';
std::cout << y << '\n';
}
int main()
{
printValues(6, 7); // Этот вызов функции имеет два аргумента, 6 и 7
return 0;
}
Когда функция printValues
вызывается с аргументами 6 и 7, параметр x
в printValues
создается и инициализируется значением 6, а параметр y
в printValues
создается и инициализируется значением 7.
Это дает в результате следующий вывод:
6
7
Обратите внимание, что количество аргументов обычно должно соответствовать количеству параметров функции, иначе компилятор выдаст ошибку. Аргумент, переданный функции, может быть любым допустимым выражением (поскольку аргумент является, по сути, просто инициализатором для параметра, а инициализатор может быть любым допустимым выражением).
Исправляем нашу тестовую программу
Теперь у нас есть инструмент, необходимый для исправления программы, представленной в начале урока:
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
void printDouble(int value) // Эта функция теперь имеет целочисленный параметр
{
std::cout << value << " doubled is: " << value * 2 << '\n';
}
int main()
{
int num { getValueFromUser() };
printDouble(num);
return 0;
}
В этой программе переменная num
сначала инициализируется значением, введенным пользователем. Затем вызывается функция printDouble
, и значение аргумента num
копируется в параметр value
функции printDouble
. Затем функция printDouble
использует значение параметра value
.
Использование возвращаемых значений в качестве аргументов
В приведенной выше задаче мы видим, что переменная num
используется только один раз, чтобы передать возвращаемое из функции getValueFromUser
значение аргументу вызова функции printDouble
.
Мы можем немного упростить приведенный выше пример следующим образом:
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
void printDouble(int value)
{
std::cout << value << " doubled is: " << value * 2 << '\n';
}
int main()
{
printDouble(getValueFromUser());
return 0;
}
Теперь мы используем возвращаемое значение функции getValueFromUser
непосредственно в качестве аргумента функции printDouble
!
Хотя эта программа более лаконична (и дает понять, что значение, прочитанное пользователем, не будет использоваться ни для чего другого), вы также можете решить, что этот «компактный синтаксис» немного труднее для чтения. Если вам удобнее придерживаться версии, в которой вместо этого используется переменная, ничего страшного.
Предупреждение о порядке вычисления аргументов функции
Спецификация C++ не определяет, сопоставляются ли аргументы с параметрами в порядке слева направо или справа налево. При копировании значений порядок не имеет значения. Однако если аргументы являются вызовами функций, это может быть проблематично:
someFunction(a(), b()); // a() или b() могут быть вызваны первыми
Если архитектура вычисляет слева направо, a()
будет вызываться перед b()
. Если архитектура вычисляет справа налево, b()
будет вызываться перед a()
. Это может иметь или не иметь последствий, в зависимости от того, что делают a()
и b()
.
Если важно, чтобы какой-либо аргумент вычислялся первым, вы должны явно определить порядок выполнения, например:
int avar{ a() }; // a() всегда будет вызываться первой
int bvar{ b() }; // b() всегда будет вызываться второй
someFunction(avar, bvar); // не имеет значения, будет ли сначала скопировано avar или bvar,
// потому что это просто значения
Предупреждение
Спецификация C++ не определяет, будут ли вызовы функций вычислять аргументы слева направо или справа налево. Позаботьтесь о том, чтобы не использовать функции в качестве аргументов, где порядок их вызовов имеет значение.
Как параметры и возвращаемые значения работают вместе
Используя оба параметра и возвращаемое значение, мы можем создавать функции, которые принимают входные данные, выполняют с ними какие-либо вычисления и возвращают значение вызывающей стороне.
Вот пример очень простой функции, которая складывает два числа и возвращает результат вызывающей функции:
#include <iostream>
// add() принимает два целых числа в качестве параметров и возвращает результат их суммы
// Значения x и y определяются функцией, которая вызывает add()
int add(int x, int y)
{
return x + y;
}
// main не принимает параметров
int main()
{
std::cout << add(4, 5) << '\n'; // Аргументы 4 и 5 передаются в функцию add()
return 0;
}
Выполнение начинается с вершины main
. Когда вычисляется add(4, 5)
, вызывается функция add
, при этом параметр x
инициализируется значением 4, а параметр y
инициализируется значением 5.
Инструкция return
в функции add
вычисляет x + y
для получения значения 9, которое затем возвращается обратно в main
. Это значение 9 затем отправляется в std::cout
для печати в консоли.
Вывод приложения:
9
В графическом виде это выглядит так:
Еще примеры
Давайте взглянем еще на несколько вызовов функций:
#include <iostream>
int add(int x, int y)
{
return x + y;
}
int multiply(int z, int w)
{
return z * w;
}
int main()
{
std::cout << add(4, 5) << '\n'; // внутри add() x=4, y=5, поэтому x+y=9
std::cout << add(1 + 2, 3 * 4) << '\n'; // внутриadd() x=3, y=12, поэтому x+y=15
int a{ 5 };
std::cout << add(a, a) << '\n'; // вычисляет (5 + 5)
std::cout << add(1, multiply(2, 3)) << '\n'; // вычисляет 1 + (2 * 3)
std::cout << add(1, add(2, 3)) << '\n'; // вычисляет 1 + (2 + 3)
return 0;
}
Эта программа создает в консоли следующий вывод:
9
15
10
7
6
Первая инструкция простая.
Во второй инструкции аргументы – это выражения, которые вычисляются перед передачей. В этом случае 1 + 2 вычисляется как 3, поэтому 3 копируется в параметр x
. 3 * 4 вычисляется как 12, поэтому 12 копируется в параметр y
. add(3, 12)
решается в 15.
Следующая пара инструкций также относительно проста:
int a{ 5 };
std::cout << add(a, a) << '\n'; // вычисляет (5 + 5)
В этом случае вызывается add()
, где значение a
копируется в оба параметра, x
и y
. Поскольку a
имеет значение 5, add(a, a)
= add (5, 5)
, что решается до значения 10.
Давайте посмотрим на первую сложную инструкцию в этой связке:
std::cout << add(1, multiply(2, 3)) << '\n'; // вычисляет 1 + (2 * 3)
При выполнении функции add
программе необходимо определить значения параметров x
и y
. С x
всё просто, поскольку мы просто передали ему целое число 1. Чтобы получить значение для параметра y
, сначала нужно вычислить multiply(2, 3)
. Программа вызывает multiply
и инициализирует z
= 2 и w
= 3, поэтому multiply(2, 3)
возвращает целочисленное значение 6. Это возвращенное значение 6 теперь можно использовать для инициализации параметра y
функции add
. add(1, 6)
возвращает целое число 7, которое затем передается в std::cout
для печати.
Менее подробно: add(1, multiply(2, 3))
вычисляется как add(1, 6)
, что вычисляется как 7
Следующая инструкция выглядит запутанной, потому что один из аргументов для add()
– это еще один вызов add()
.
std::cout << add(1, add(2, 3)) << '\n'; // вычисляет 1 + (2 + 3)
Но этот случай работает точно так же, как и предыдущий. Сначала вычисляется add(2, 3)
, в результате чего возвращается значение 5. Теперь можно вычислить вызов add(1, 5)
, который вычисляет значение 6, которое передается в std::cout
для печати.
Менее подробно: add(1, add(2, 3))
вычисляется как add(1, 5)
=> вычисляется как 6
Заключение
Параметры функций и возвращаемые значения являются ключевыми механизмами, с помощью которых функции могут быть написаны для повторного использования, поскольку это позволяет нам писать функции, которые могут выполнять задачи и возвращать извлеченные или вычисленные результаты обратно вызывающей стороне, не зная заранее конкретных входных и выходных данных.
Небольшой тест
Вопрос 1
Что не так с этим фрагментом программы?
#include <iostream>
void multiply(int x, int y)
{
return x * y;
}
int main()
{
std::cout << multiply(4, 5) << '\n';
return 0;
}
Ответ
multiply()
определяется как возвращающая void
, что означает, что она не может возвращать значение. Поскольку функция пытается вернуть значение, эта функция вызовет ошибку компиляции. Эта функция должна возвращать int
.
Вопрос 2
Какие две вещи неправильны в этом фрагменте программы?
#include <iostream>
int multiply(int x, int y)
{
int product{ x * y };
}
int main()
{
std::cout << multiply(4) << '\n';
return 0;
}
Ответ
Проблема 1: main()
передает один аргумент в multiply()
, а multiply()
требует двух параметров. Проблема 2: в multiply()
нет инструкции return
.
Вопрос 3
Какое значение выводит следующая программа?
#include <iostream>
int add(int x, int y, int z)
{
return x + y + z;
}
int multiply(int x, int y)
{
return x * y;
}
int main()
{
std::cout << multiply(add(1, 2, 3), 4) << '\n';
return 0;
}
Ответ
multiply()
вызывается, где x
= add(1, 2, 3)
и y
= 4. Сначала процессор вычисляет x
= add(1, 2, 3)
, что возвращает 1 + 2 + 3, или x
= 6. multiply(6, 4)
= 24, и это есть ответ.
Вопрос 4
Напишите функцию с именем doubleNumber()
, которая принимает один целочисленный параметр. Функция должна возвращать удвоенное значение параметра.
Ответ
int doubleNumber(int x)
{
return 2 * x;
}
Вопрос 5
Напишите полную программу, которая считывает целое число, вводимое пользователем на клавиатуре, удваивает его, используя функцию doubleNumber()
, которую вы написали в предыдущем вопросе, а затем выводит удвоенное значение в консоль.
Ответ
#include <iostream>
int doubleNumber(int x)
{
return 2 * x;
}
int main()
{
int x{};
std::cin >> x;
std::cout << doubleNumber(x) << '\n';
return 0;
}
Примечание: вы можете предложить другие (похожие) решения. В C++ часто есть много способов сделать одно и то же.