учебное пособие. Часть1. Информатика
.pdfширяет раздел описаний функции, и формальные параметры наряду с ло- кальными переменными используются в выражениях. Тело функции, в том числе и функции main, заключается в фигурные скобки и содержит описа- ния и операторы.
Вызов функции производится при помощи оператора вызова функции:
<имя >(<список фактических параметров>);
Он записывается в том месте программы, где по логике решения задачи требуется выполнение операторов, определенных в теле функ- ции. После выполнения операторов тела функции управление передает- ся оператору, следующему за оператором вызова.
Следует заметить, что таким образом вызываются функции типа void, т. е. функции, не возвращающие никакого значения. Функции же, возвра- щающие значения типа int, float и т. д., вызываются упоминанием име- ни в выражении аналогично стандартным функциям sin, cos и другим.
Поэтому в теле таких функций присутствует оператор возврата return. После ключевого слова return может записываться выражение, значение которого вставится вместо имени функции в точке вызова. Функция не мо- жет возвращать массив или функцию. В качестве результата, возвращаемого функцией, могут быть значения только простого и ссылочного типа. Опера- тор return приводит к немедленному выходу из функции. Если функция имеет тип void, то оператор return используется без возвращаемого зна- чения.
Пусть имеется следующее описание функции: int max (int a, int b)
{ |
if (a>b) |
return a; |
} |
else |
return b; |
|
|
Функция max может использоваться в выражении c = max(5,3);
m = max (c-d, c+d) + 2*max (c*d, c%d);
либо
printf ( "max=%d", max(c*d, c*d));
В данном примере функция возвращает значение целого типа, фактические параметры − тоже целого типа. Параметры передаются по значению. При первом обращении к функции max формальный пара- метр а заменяется фактическим параметром, которому соответствует целое число 5, а формальный параметр b – числом 3. Возвращает же функция число 5, и это число присваивается переменной с. При втором
и третьем обращениях в качестве фактических параметров передаются выражения. Они сначала вычисляются, и уже значения выражений под-
221
ставляются в качестве формальных параметров. В четвертом случае
функция max вызывается из функции printf.
В примере pr21 треугольник задан координатами вершин. Найти
периметр треугольника. В функции l определяется расстояние между двумя точками.
//Пример pr21 #include <iostream> #include <cmath> using namespace std;
float l (float x1, float y1, float x2, float y2); int main ()
{
float x1, y1, x2, y2, x3, y3;
cout <<" \n Введите координаты точек:"; cin >> x1 >>y1 >>x2 >>y2 >>x3 >>y3; float a = l (x1, y1, x2, y2);
float b = l (x2, y2, x3, y3); float c = l (x1, y1, x3, y3);
cout << "Периметр треугольника равен " << a+b+c; return 0;
}
float l (float x1, float y1, float x2, float y2)
{
return sqrt ((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
К списку параметров предъявляются следующие требования:
количество формальных параметров должно быть равно количеству фактических параметров;
порядок следования фактических и формальных параметров должен быть один и тот же;
тип каждого фактического должен совпадать с типом соответствующе- го формального параметра.
Рассмотрим пример использования функции. Даны вещественные чис- ла a, b и c. Необходимо вычислить значения x и y по формулам:
x = |
a + b |
, |
|
c2 +10 |
|||
|
|
y = bc2 ++a5.
222
Так как формулы для вычисления x и y имеют одинаковую структуру, то напишем общую функцию для вычисления их значений.
//Пример pr22
float f ( float af, float bf, float cf )
{
return (af+bf)/cf;
}
int main()
{
float a, b, c, x, y;
printf ( "\n Введите a,b,c" ); scanf ( "%f%f%f", &a, &b, &c ); x = f ( a, b, c*c+10 );
y = f ( c, a, b*b+5 );
printf ( "\n x =%5.2f y = %5.2f \n", x, y ); return 0;
}
Для вызова функции здесь используется оператор присваивания. Пара- метры a, b, c*c+10 в момент первого обращения к функции − фактиче- ские параметры, а параметры c, a, b*b+5 − фактические параметры в мо- мент второго обращения к функции. Пользователь должен следить за по- рядком следования фактических параметров, смысл их использования зави- сит от порядка их перечисления в списке. Они подставляются вместо фор- мальных параметров af, bf, cf в заголовке функции, а затем над ними выполняются необходимые действия. Параметры в данном примере переда- ются по значению. В этом случае под af, bf, cf выделяется дополни- тельная память, и туда копируются значения соответствующих фактических параметров. Таким образом, в подпрограмме используются копии фактиче- ских параметров, что и позволяет в качестве последних применять выраже- ния. Так как при передаче параметров по значению в функции работаем с ко- пией, то изменение формального параметра не приводит к изменению соот- ветствующего фактического параметра. Если же в изменении фактического параметра возникает необходимость, то параметры передаются по указателю или ссылке.
Для этой же задачи составим программу с использованием параметров,
передаваемых по указателю (pr23).
//Пример pr23
void f ( float af, float bf ,float cf, float *yf )
{
*yf = (af+bf)/cf;
}
223
int main ()
{
float a, b, c, x, y;
printf ( "\n Введите a, b, c" ); scanf ( "%f%f%f", &a, &b, &c ); f ( a, b, c*c+10, &x );
f ( c, a, b*b+5, &y );
printf ( "\n x= %5.2f y= %5.2f \n", x, y); rerurn 0;
}
В этой программе функция имеет четыре параметра: три параметра, пе- редаваемых по значению, и один параметр – по указателю. Параметр yf , пе- редаваемый по указателю, используется для возврата из функции значения, так как в этом случае передается не копия переменной, а её адрес. Таким об- разом, изменение формального параметра приводит к изменению фактиче- ского параметра в вызывающей функции. Параметры, передаваемые по ука- зателю, не требуют дополнительной памяти и дополнительного времени для создания копий параметров. При работе с такими данными, как массивы, структуры, передача параметров возможна только по указателю.
Третий способ передачи параметров – это передача параметров по ссылке. В языке С этого способа передачи параметров не было. Понятие ссылка появилось только в С++. Ссылочные переменные описываются сле- дующим образом [11]:
<тип> &<имя> = <инициализирующее выражение>
Здесь, <тип> − любой тип С++; <имя> − идентификатор перемен- ной ссылочного типа; <инициализирующее выражение> − выражение, определяющее местоположение в памяти. Например:
int y=16; int &x=y;
printf("x=%d\n",x);
y=12;
printf("x=%d\n",x);
Результат работы: x=16
x=12
Ссылочная переменная х определяет местоположение переменной у в памяти в инициализирующем выражении int &x=y. Переменная х обраба- тывается как "нормальная" переменная типа int. При обращении к такой переменной нет необходимости в операции снятия ссылки. Когда перемен- ной y присваивается значение 12, то переменная х принимает это новое зна- чение, поскольку она определяет местоположение переменной y в памяти.
224
Если в этот пример добавить строку х=10, то при этом значение переменной у станет равным 10. Поскольку х – это ссылка на переменную у, то любое изменение переменной х приведет к изменению переменной у. Ссылочная переменная при объявлении должна быть инициализирована. Объявление int &x; недопустимо, поскольку неизвестно на что указывает переменная х. Теперь рассмотрим, как же передается параметр по ссылке на примере той же задачи, что и в программе pr24.
// Пример pr24
void f ( float af, float bf, float cf, float &yf )
{
yf = (af+bf)/cf;
}
int main ()
{
float a, b, c, x, y;
printf ( "\n Введите a, b, c" );
scanf ( "%f%f%f", &a, &b, &c ); f ( a, b, c*c+10, x );
f ( c, a, b*b+5, y );
printf ( "\n x= %5.2f y= %5.2f \n", x, y); return 0;
}
Здесь используется параметр yf, передаваемый по ссылке. Для фор- мального параметра float &yf инициализирующего выражения не требуется. Инициализирующее выражение подставляется при вызове функции. При вызо- ве функции f (a, b, c*c+10, x) инициализирующее выражение прини-
мает вид float &yf = х. При вызове функции f (a, b, c*c+10, у)
инициализирующее выражение принимает вид float &yf = у. В примере pr24 версия функции более "натуральна", нежели версия в примере pr23, так как нет необходимости в операции разыменовывания.
Рассмотрим еще один пример использования функции. Инженеру часто приходится решать алгебраические или трансцендентные уравнения, что мо-
жет представлять самостоятельную задачу или являться составной частью более сложных задач. В обоих случаях практическая ценность численного метода в значительной мере определяется быстротой и эффективностью по- лучения решения. В данном примере решаем трансцендентное уравнение (нелинейное уравнение, содержащее тригонометрические функции или дру- гие специальные функции, например lg x или ex ). Методы решения таких
уравнений делятся на прямые и итерационные [9]. Первые позволяют найти решение непосредственно с помощью формул и всегда обеспечивают полу-
225
чение точного результата. В итерационных методах задается процедура ре- шения в виде многократного применения некоторого алгоритма. Полученное решение всегда является приближенным, хотя может быть сколько угодно близким к точному. Итерационные методы наиболее удобны для реализации на компьютере. Пусть необходимо найти корень уравнения f (x) = 0 на ин-
тервале [a, b] с точностью е. Известно, что функция f(x) на этом ин- тервале непрерывна и монотонна, f(x) = x - sin(x)/cos(x), a < b, е>0. Графическая схема алгоритма решения уравнения методом поло- винного деления представлена на рис. 23, программа − в pr25.
|
Hачало |
|
|
|
Bвод |
|
|
|
a, b, e |
|
|
|
Да |
|
|
|
f (a)× f (b)>0 |
|
|
|
Нет |
|
|
Bывод |
x1 := a |
|
|
"Корней |
x2 := b |
|
|
нет" |
|
|
|
|
|
|
|
|
x1− x2 > e |
Да |
|
|
Нет |
x = |
x1 + x2 |
|
|
||
|
|
|
2 |
|
|
|
Нет |
|
Bывод |
f(x)×f(x1)>0 |
|
|
|
Да |
|
|
x1 |
|
|
|
Kонец |
|
x1:=x |
|
|
|
|
|
Рис. 23. Алгоритм PR25 |
||
// Пример pr25 |
|
|
|
226
#include <iostream> #include <cmath> using namespace std; float f ( float xf )
{
return xf-sin (xf)-0.25;
}
int main ()
{
float a, b, e, x, x1, x2;
printf ( "\n Введите a, b, e" ); scanf ( "%f%f%f", &a, &b, &e );
if ( f (a)*f (b)>0 ) printf("\nКорней нет\n"); else
{
x1 = a;
x2 = b;
while ( fabs (x2-x1) > e )
{
x = (x1+x2)/2;
if ( f(x1)*f(x) > 0 ) x1=x;
else
x2 = x;
}
printf("\nКореньуравнения=%f \nf (x)=%f",x1,f(x1));
}
return 0;
}
Сначала проверяется, есть ли корень на заданном интервале, т. е. ме- няются ли знаки на концах интервала f (a) × f (b) > 0 . Так как функция на ин- тервале монотонна и непрерывна, то смена знака указывает на существова- ние корня. Если корень существует, то обозначаем концы интервала новыми идентификаторами х1 и х2. Проверяем, достаточно ли мал интервал, на ко- тором находится корень fabs(x2-x1)>e. Если интервал больше точности, то находим среднее значение х на интервале. В зависимости от того, на какой
половине [x1,x] или [x,x2], происходит смена знака, интервал соответ- ствующим образом сужается. Как только длина интервала становится равной или меньшей е, процесс завершается и любой из концов интервала можно считать корнем уравнения с точностью е.
227
В языке С++ функции можно размещать в отдельном файле. В этом случае файл, из которого функция вызывается, должен содержать директиву
препроцессора #include "имя_файла.cpp".
5.1. Функции и массивы
При передаче массивов в функцию учитывается, что имя одномерного массива является указателем на первый элемент массива. Поэтому, указывая в качестве фактического параметра имя одномерного массива, происходит передача адреса первого элемента массива. Так как элементы одномерного массива располагаются в непрерывной области памяти, то легко можно обра- титься к любому элементу массива, зная адрес первого элемента.
Впримере pr26 дано два массива a[n] и b[m]. Требуется определить,
вкаком массиве сумма элементов наибольшая. На рис. 24 представлена схе- ма алгоритма главной функции (main), а на рис. 25 – схема функции sum, вычисляющей сумму элементов одномерного массива.
Рис. 24. лодлододлдо
228
//Пример 26 #include <iostream> #include <cstdlib> using namespace std;
float sum (int *x, int nx)
{
int i;
float s = 0;
Рис. 25. рншнгнщ8н
for ( i=0; i<nx; i++ ) s=s+x[i];
return s;
}
// Основная программа int main()
{
int n, m; //Количество элементов в массивах
229
int *a, *b; //Описываем переменные типа указатель int i,
sa, sb;//Суммы элементов массивов
printf ( "\nВведите размерность массива а n=" ); scanf ( "%d", &n);
printf ( "\n Введите размерность массива b m=" ); scanf ( "%d", &m);
//Выделение памяти под массивы a = new int[n];
b = new int[m]; randomize();
for ( i=0; i<n; i++ ) // Формируем массив a a[i] = random(10);
for ( i=0; i<m; i++ ) // Формируем массив b b[i] = random(10);
//Контрольный вывод элементов массивов printf ( "Массив а:");
for ( i=0; i<n; i++ ) printf ( "%5d", a[i]); printf ( "\nМассив b:" ); for ( i=0; i<m; i++ ) printf ( "%5d", b[i]); sa = sum (a, n);
sb = sum (b, m); if ( sa>sb )
printf ( "\n Сумма элементов массива а больше суммы элементов массива b");
else
if ( sa == sb )
printf ( "\n Сумма элементов массива а равна
сумме элементов массива b" );
else
printf ( "\n Сумма элементов массива а
меньше суммы элементов массива b" );
return 0;
}
Впримере в качестве параметра функции передается указатель на мас- сив. В этом случае не создается копия массива, поэтому нет затрат времени на копирование и затрат памяти под копию. Использование функций не только сокращает текст программы, но и улучшает читаемость программы.
Вследующем примере осуществляется циклический сдвиг одномерно- го массива из n элементов на k позиций. Например, если n = 7, а k = 3, то мас-
230
