В языке С++ данные в подпрограмму можно передавать тремя способами: по значению, по адресу и по ссылке. В языке С допустимы только два способа: по значению и по адресу.
Этот способ передачи данных в подпрограмму является основным и действует по-умолчанию. Фактический параметр вычисляется в вызывающей функции и его значение передаётся на место формального параметра в вызываемой функции. На этом связь между фактическим и формальным параметрами прекращается.
В качестве фактического параметра можно использовать константу, переменную или более сложное выражение. Передача данных по значению пригодна только для простых данных, которые являются входными параметрами. Если параметр является выходным данным или массивом, то передача его в функцию по значению не приемлема.
Пример 1. Вычислить сумму ряда с заданной точностью ε=10-5:
Для вычисления суммы ряда используем функцию. В неё передадим по значению x и eps. Результат вернём через имя функции оператором return.
Возможный вариант реализации программы:
#include <iostream>
using namespace std;
#include <cmath>
double fsum(double x, double eps);
int main()
{
double x, s, eps = 1.0e-5;
cout << "x = ";
cin >> x;
s = fsum(x, eps);
cout << "s = " << s << endl;
return 0;
}
double fsum(double x, double eps)
{
double s = x, p = x, i, t = x * x;
for(i = 3; fabs(p) > eps; i += 2)
{
p = -p * t / (i * (i - 1));
s += p;
}
return s;
}
Не сложно убедиться, что всё нормально работает. Но что делать, когда выходных параметров два или более? Через имя функции можно вернуть только один объект, остальные придётся возвращать через список. Позволит ли этот способ (передача по значению) вернуть через список параметров изменённые значения? Нет, не позволит. Давайте проверим это на несложном примере.
Пример 2. Даны два числа, хранящиеся в переменных a и b. Используя подпрограмму, выполнить обмен содержимого ячеек этих переменных.
Данные передадим по значению. Они будут в этой задаче и входными, и выходными данными. Для контроля изменения содержимого ячеек памяти будем выводить на экран монитора промежуточные данные.
Возможный вариант реализации программы:
#include <iostream>
using namespace std;
void Obmen(double x, double y);
int main()
{
double a = 2.5, b = 3.1;
cout << "Do Obmen: a=" << a << " b=" << b << endl;
Obmen(a, b);
cout << "Posle Obmen: a=" << a << " b=" << b << endl;
return 0;
}
void Obmen(double x, double y)
{
double c;
cout << "Function Obmen start:\n x=" << x << " y=" << y << endl;
c = x;
x = y;
y = c;
cout << "Function Obmen end:\n x=" << x << " y=" << y << endl;
}
Результаты выполнения программы:
Do Obmen: a=2.5 b=3.1
Function Obmen start:
x=2.5 y=3.1
Function Obmen end:
x=3.1 y=2.5
Posle Obmen: a=2.5 b=3.1
Вывод на экран значений переменных показывает, что данные в функцию переданы правильно, перестановка в функции произведена, но это ни как не отразилось на значениях исходных переменных a и b после выхода из функции Obmen().
Этот пример наглядно показывает, что через параметры, передаваемые по значению, нельзя вернуть результаты работы функции.
По адресу в функцию всегда передаются массивы (рассмотрим это в следующих темах). Для массива это вообще единственный способ передачи данных в языках С/С++. Так же по адресу можно передать те простые объекты, которые являются выходными данными (или входными и выходными одновременно).
В случае передачи данных по адресу фактический параметр может быть только переменной (константа или выражение не имеют адреса!).
Вернёмся к предыдущему примеру.
Пример 3. Даны два числа, хранящиеся в переменных a и b. Используя подпрограмму, выполнить обмен содержимого ячеек этих переменных.
Данные передадим по адресу. Они будут в этой задаче и входными, и выходными данными. Для контроля изменения содержимого ячеек памяти будем выводить на экран монитора промежуточные данные.
Возможный вариант реализации программы:
#include <iostream>
using namespace std;
void Obmen(double *x, double *y);
int main()
{
double a = 2.5, b = 3.1;
cout << "Do Obmen: a=" << a << " b=" << b << endl;
Obmen(&a, &b);
cout << "Posle Obmen: a=" << a << " b=" << b << endl;
return 0;
}
void Obmen(double *x, double *y)
{
double c;
cout << "Function Obmen start:\n *x=" << *x << " *y=" << *y << endl;
c = *x;
*x = *y;
*y = c;
cout << "Function Obmen end:\n *x=" << *x << " *y=" << *y << endl;
}
Результаты выполнения программы:
Do Obmen: a=2.5 b=3.1
Function Obmen start:
*x=2.5 *y=3.1
Function Obmen end:
*x=3.1 *y=2.5
Posle Obmen: a=3.1 b=2.5
Печать на экране монитора наглядно показывает, что обмен произведён, и исходные переменные теперь имеют новые значения, т.е. передача данных по адресу действительно позволяет вернуть в вызывающую функцию результат работы вызываемой подпрограммы.
Как это работает? Рассмотрим данный вопрос подробнее, используя пример с обменом данных. Для наглядности приведём рисунок:
В вызывающей функции (в нашем случае — в main()) вычисляются адреса объектов, передаваемых по адресу ( у нас — адреса переменных a и b. Пусть это будут числа 1000 и 1008), и затем эти адреса копируются в ячейки памяти — указатели, память под которые выделена в функции Obmen() (это x и y). Зная адрес переменной, например, адрес переменной a, который теперь хранится в указателе x, можно, пользуясь операцией разыменование, не только прочитать, но и изменить значение исходной переменной.
Ни какой реальной передачи данных (в смысле копирования) из подпрограммы Obmen() назад в main() не делается. Мы на самом деле через указатели работаем с исходными объектами! Поэтому после выхода из функции Obmen() имеем изменённые переменные a и b (если быть точнее, переменные изменятся ещё до выхода из функции,то есть в момент перестановки в самой функции Obmen()).
Это ещё один из способов вернуть результат работы функции через список параметров. Напомним, что применяется только для С++. В языке С такого варианта нет.
При передаче данных по ссылке в функцию, куда передаются данные, создаются синонимы исходных объектов. Поэтому работа в подпрограмме ведётся именно с исходными объектами. Если в подпрограмме ссылочная переменная изменит значение, то это сразу отразится на исходной переменной.
В вызывающей функции параметр, передаваемый по ссылке, может быть только простой переменной любого известного типа.
Вернёмся снова к примеру обмена, только данные передадим по ссылке.
Пример 4. Даны два числа, хранящиеся в переменных a и b. Используя подпрограмму, выполнить обмен содержимого ячеек этих переменных.
Возможный вариант реализации программы:
#include <iostream>
using namespace std;
void Obmen(double &x, double &y);
int main()
{
double a = 2.5, b = 3.1;
cout << "Do Obmen: a=" << a << " b=" << b << endl;
Obmen(a, b);
cout << "Posle Obmen: a=" << a << " b=" << b << endl;
return 0;
}
void Obmen(double &x, double &y)
{
double c;
cout << "Function Obmen start:\n x=" << x << " y=" << y << endl;
c = x;
x = y;
y = c;
cout << "Function Obmen end:\n x=" << x << " y=" << y << endl;
}
Результаты выполнения программы:
Do Obmen: a=2.5 b=3.1
Function Obmen start:
x=2.5 y=3.1
Function Obmen end:
x=3.1 y=2.5
Posle Obmen: a=3.1 b=2.5
Результаты, выводимые на экран монитора, подтверждают правильность работы.
Обратите внимание на то, что для объекта, передаваемого по ссылке, в списке формальных параметров указывается значок & перед именем переменной (работаем с ссылкой на объект), а далее в теле подпрограммы используется просто имя этой переменной. При вызове в списке фактических параметров задаётся имя нужной переменной.
Для простых объектов передача данных по ссылке предпочтительнее, чем передача по адресу, так как в этом случае текст функции проще, легче читается, не требуется выполнять операции разыменования.
К недостатку способа (передача данных по ссылке) можно отнести то, что по вызову функции нельзя понять, что параметр передаётся именно по ссылке, а не по значению, и как следствие, этот параметр скорее всего изменяется функцией. Чтобы не было сомнений, всегда смотрите на запись прототипов функций, использованных в программе. Прототипы всегда доступны, даже если сами функции имеются только в виде объектных модулей.
|
|
|