Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C _Учебник_МОНУ

.pdf
Скачиваний:
206
Добавлен:
12.05.2015
Размер:
11.12 Mб
Скачать

Функції

279

За обох способів до функції передається адреса параметра, який зазначено при викликанні. При передаванні вказівника всередині функції виникає потреба використовувати явно операції розіменування чи то отримання адреси, що робить менш зрозумілим текст коду функції і більш складним налагодження функції. За передавання за посиланням до функції передається посилання, тобто синонім імені, внаслідок чого всередині функції всі звертання до параметра неявно розіменовуються.

Отже, використовування посилань замість вказівників зоптимізовує читабельність програми, позбавляючи потреби застосовувати операції отримання адреси й розіменування. Використовування посилань замість передавання за значенням є більш ефективне, оскільки не потребує копіювання параметрів, а це є надто важливо при передаванні структур даних великого обсягу.

Розглянемо приклад функції з передаванням параметрів за посиланням: void duplicate(int& a, int& b, int& c)

{

a*=2;

b*=2; c*=2;

}

 

 

int main

()

{int x=1, y=3, z=7; duplicate(x, y, z);

cout << "x=" << x << ", y=" << y << ", z=" << z;

return 0;

}

Результат:

x=2, y=6, z=14

У наведеному фрагменті функція duplicate() отримає три цілих параметри, кожний з яких збільшується вдвічі у тілі функції. Для того щоб ці зміни були відомі у точці виклику (в функції main()), всі параметри передаються за посиланнями. Фактичними параметрами є самі змінні x, y та z, а не копії їхніх значень. Функція підставляє змінну х замість формального параметра а, змінну у – замість формального параметра b, змінну z – замість формального параметра с:

void duplicate(int& a, int& b, int& c)

duplicate( x,

y,

z);

Розглянемо детальніше різницю поміж передаванням параметра за значенням, за посиланням і за вказівником.

У поданому нижче фрагменті програми функція f має три параметри, один з яких передається за значенням, другий – за вказівником, а третій – за посиланням.

Тексти функції та її виклику в основній пр ограмі:

void f(int i, int* j, int& k); int main()

{int i=1, j = 2, k = 3; cout << "i j k\n";

280

Розділ 8

cout <<i<<' '<<j<<' '<<k <<'\n'; f(i,&j,k);

cout<<i<<' '<<j<<' '<<k;

}

void f(int i, int* j, int& k) { i++; (*j)++; k++;

}

Результат роботи програми:

i j k 1 2 3 1 3 4

Перший параметр i передається до функції за значенням. Його змінення у функції не впливає на вихідне значення. Другий параметр j передається за адресою за допомогою вказівника, при цьому для передавання до функції адреси фактичного параметра використовується операція отримання адреси &j, а для одержання його значення у функції потрібна операція розіменування (розадресації) *j. Третій параметр k передається за адресою за допомогою посилання. Це дозволяє передавати фактичний параметр k без операції отримання адреси і використовувати його у функції без розадресації.

Якщо виникає потреба уникнути змінювання параметра всередині функції, використовується специфікатор const:

int f(const char*);

char *t(char *a, const int *b);

На місце параметра типу const& може передаватися константа, а для змінної за потреби – виконуватися перетворення типу. Вихідні дані, які не повинні змінюватися у функції, більш оптимально є передавати їй за допомогою константних посилань.

За замовчуванням параметри будь-якого типу, окрім масиву й функції, передаються до функції за значенням.

Розглянемо ще один приклад функцій з параметрами, які передаються за посиланням і за вказівником:

void swap_values1(float *a, float *b) { float temp = *a;

*a = *b; *b = temp;

}

void swap_values2(float &a, float &b) { float temp = a;

a = b;

b = temp;

}

До функції swap_values1() параметри передаються за вказівником. Виклик цієї функції може мати вигляд

a=3; b=5; swap_values1(&a, &b);

Функції

281

Звертаємо увагу, що фактичними параметрами при передаванні за посиланням і вказівником можуть бути лише змінні.

До функції swap_values2() параметри передаються за посиланням. Виклик цієї функції матиме більш природній вигляд:

a=3; b=5; swap_values2(a, b);

Якщо функція має повернути понад одне значення, можна передати їй за посиланням додаткові параметри, а у тілі функції їм буде присвоєно значення, які треба повернути. Наступна функція prevnext() отримає ціле число х і обчислюватиме попереднє prev та наступне next значення відносно введеного х:

void prevnext (int x, int& prev, int& next) { prev = x-1; next = x+1; }

При використовуванні масиву як параметра функції передається вказівник на його перший елемент, тобто масив завжди передається за адресою (див. п. 5.2.4). При цьому інформація про кількість елементів втрачається і слід передавати його розмірність через окремий параметр. Якщо розмірність масиву є константою, проблем не виникає, оскільки можна зазначити її при оголошенні формального параметра і як верхню межу циклів при опрацюванні масиву всередині функції. У разі передавання до функції масиву символів, тобто рядка, його фактичну довжину можна визначити за розташуванням нуль-символу і передавати кількість елементів немає потреби.

У поданому нижче фрагменті програми оголошується функція sum(), параметрами якої є масив і кількість його елементів. Ця функція обчислює суму елементів масиву.

int sum (int* mas, int n)

{// Варіанти: int sum(int mas[ ], int n) чи int sum(int mas[n]) (якщо n – константа). for(int i = 0, s = 0; i < n; i++) s += mas[i];

return s;

}

void main()

{int marks[] = {3, 4, 5, 4, 4};

cout << "Сума елементів масиву: "<< sum(marks, 5);

}

При передаванні до функції багатовимірних масивів усі розмірності, якщо вони є невідомі на етапі компіляції, мають передаватися як параметри (див. п. 5.3.4). Приміром, заголовок функції, яка обчислює суму елементів динамічного двовимірного масиву, може мати вигляд

int sum(const int **a, const int nr, const int nc);

Виклик цієї функції у програмі може бути таким:

cout << "Сума елементів a: " << sum((const int**)a,nstr,nstb);

Для звичайного статичного двовимірного масиву, коли обидві розмірності є відомі й є константами, заголовок функції матиме вигляд

int sum(int а[4][6]);

282

Розділ 8

Приклад 8.5 Увести масив з 15 цілих чисел та обчислити суму його парних елементів.

Розв‟язок. Функція sum_par() має один параметр – масив А. Оскільки кількість елементів задано константою, вона є відома і передавати її до функції не треба. Результат функції має цілий тип int.

Тексти функції та основної програми: int sum_par(int A[10])

{ for(int i=0, sum=0; i<10; i++) if(A[i]%2==0) sum+=A[i];

return sum;

}

void __fastcall TForm1::Button1Click (TObject *Sender)

{int i, a[10]; if(Memo1->Lines->Count<10)

{ShowMessage("Введіть ще числа"); return;

}

for(i=0; i<10; i++) a[i]=StrToInt(Memo1->Lines->Strings[i]); Edit1->Text=IntToStr(sum_par(a));

}

Приклад 8.6 Увести масив і обчислити за допомогою функції добуток від‟ємних елементів одновимірного масиву.

Розв‟язок. Оскільки в завданні нічого не сказано про кількість елементів, вважатимемо її довільною. Введемо числа у Memo1 і створимо з них масив. Цей масив буде динамічним, оскільки кількість елементів є заздалегідь невідома.

Функція повертатиме дійсне значення і має два параметри: вказівник на початок масиву і кількість елементів. Якщо від‟ємних елементів нема, буде повернуто значення добутка 0.

Тексти функції таі її виклику в основній програмі: double dob(double *a, int n)

{int i, k=0; double p=1; for(i=0; i<n; i++)

if(a[i]<0) { k++; p *= a[i]; } if(k>0) return p; else return 0; }

Функції

283

void __fastcall TForm1::Button1Click (TObject *Sender) { double P; int i, n = Memo1->Lines->Count;

double *a = new double [n];

for(i=0; i<n; i++) a[i]=StrToFloat(Memo1->Lines->Strings[i]); P = dob(a, n);

if(P!=0) Edit1->Text=FloatToStr(P); else ShowMessage("Від’ємних нема");

}

Приклад 8.7 Увести матрицю дійсних чисел розмірності 4 3 й обчислити за допомогою функції мінімальний елемент матриці.

Тексти функції та її виклику в основній програмі: double matrix_min(double a[4][3])

{double min=a[0][0]; for(int i=0; i<4; i++) for(int j=0; j<3; j++) if(a[i][j]<min)

min=a[i][j]; return min;

}

void __fastcall TForm1::BitBtn1Click(TObject *Sender)

{double a[4][3], m; for(int i=0; i<4; i++) for(int j=0; j<3; j++)

a[i][j]=StrToFloat(StringGrid1->Cells[j][i]); m=matrix_min(a);

Edit1->Text=FloatToStr(m);

}

Приклад 8.8 Увести матрицю дійсних чисел розмірності 4 3 й обчислити за допомогою функції мінімальний елемент матриці та його індекси.

Розв‟язок. Реалізацію подібного завдання без створення функції вже було розглянуто в підрозд. 5.3. До того ж цей приклад є продовженням попереднього. Змінимо функцію в такий спосіб, щоб вона обчислювала і повертала три значення. У цьому разі слід передати до функції два додаткових параметри – ind_i та ind_j, – в які буде записано обидва індекси мінімального елемента. Ці параметри мають передаватися за посиланням (чи за вказівником), щоб у тілі функції можна було їх змінити і повернути ці зміни у точку виклику.

Тексти функції та її виклику в основній пр ограмі:

double matrix_min(double a[4][3], int& ind_i, int& ind_j)

{double min=a[0][0]; ind_i=0; ind_j=0; for(int i=0; i<4; i++)

for(int j=0; j<3; j++)

if(a[i][j]<min) { min=a[i][j]; ind_i=i; ind_j=j; } return min; }

284

Розділ 8

void __fastcall TForm1::Button1Click(TObject *Sender) { double a[4][3], m; int i, j, ind_i=0, ind_j=0;

for(i=0;i<4;i++)

for(j=0;j<3;j++) a[i][j]=StrToInt(StringGrid1->Cells[j][i]); m = matrix_min(a, ind_i, ind_j);

Edit1->Text = FloatToStr(m) + "(" + IntToStr(ind_i) + ", " + IntToStr(ind_j) + ")";

}

Приклад 8.9 Увести рядок і перетворити його на верхній регістр.

Розв‟язок. Створимо функцію для перетворення будь-яких літер (у тому числі й українських) на верхній регістр. З попереднього розділу відомо, що функції isupper() та islower() не працюють з літерами кирилиці. Коди більшості малих російських літер є більші, аніж коди відповідних великих літер, на 32. Але в українській абетці є кілька специфічних літер, місцеположення яких у таблиці не підпадає під цю залежність при обчислюванні великої літери, а тому їх слід перевіряти окремо.

Оскільки, на відміну від звичайних масивів, кінець рядка можна визначити за розміщенням нуль-символу, немає потреби передавати до функції довжину рядка, тобто рядок можна передавати як вказівник на його перший символ. При такому способі передаванні будь-які змінення рядка всередині функції автоматично змінюють вихідний рядок.

Тексти функції та її виклику в основній пр ограмі: void toUp(char *c)

{int n=strlen(c); for(int i=0; i<n; i++)

{if(isalpha(c[i])) c[i] = toupper(c[i]); if(c[i] >= 'а' && c[i] <= 'я') c[i] -= 32;

Функції

285

if(c[i] == 'ї' || c[i] == 'є') c[i] = c[i] - 16; if(c[i] == 'ґ') c[i] = 'Ґ';

if(c[i] == 'і') c[i] = 'І';

}

}

void __fastcall TForm1::Button1Click(TObject *Sender)

{char s[50];

strcpy(s, Edit1->Text.c_str()); toUp(s);

Edit2->Text=AnsiString(s);

}

Приклад 8.10 Заповнити матрицю розмірності M N (значення M та N увести) випадковими числами з проміжку [–15, 10) за допомогою функції.

Тексти функції та її виклику в основній пр ограмі:

void fill_random(int** a, int M, int N) { randomize();

for(int i=0; i<M; i++)

for(int j=0; j<N; j++) a[i][j]=random(25)-15;

}

void __fastcall TForm1::Button1Click(TObject *Sender)

{int M, N, i, j;

if(Edit1->Text=="" || Edit2->Text=="")

{ShowMessage("Введіть M та N"); return; } M=StrToInt(Edit1->Text); N=StrToInt(Edit2->Text); StringGrid1->RowCount=M; StringGrid1->ColCount=N;

int **a = new int* [M]; for(i=0; i<M; i++)

a[i]=new int [N]; fill_random(a, M, N);

for(i=0; i<M; i++) for(j=0; j<N; j++) StringGrid1->Cells[j][i]=

IntToStr(a[i][j]); for(i=0; i<M; i++) delete []a[i]; delete []a;

}

8.3 Параметри зі значеннями за замовчуванням

Щоб спростити виклик функції, в її заголовку можна задати значення параметрів за замовчуванням. Ці параметри мають бути останніми у списку й можуть опускатися при виклику функції. Якщо при виклику параметр є опущений, має бути опущено й усі параметри, що стоять за ним. Як значення параметрів за замовчуванням можуть використовуватися константи, глобальні змінні й

286

Розділ 8

вирази. Розглянемо приклади прототипів функцій з параметрами за замовчуванням.

int f(int a, int b=0); // Параметр b має значення за замовчуванням 0. void f1(int, int=100, char* = 0);// Зверніть увагу на пробіл між * й =,

//без нього б вийшла операція складного присвоювання *= void err(int errValue = errno); //errno – глобальна змінна.

Варіанти виклику цих функцій:

f(100); // Виклик функції з першим параметром 100, другий за замовчуванням – 0. f(А,1); // Виклик функції з першим параметром – значенням змінної А, другим – 1. f1(А); // Виклик функції з другим та третім параметрами за замовчуванням. f1(a,10); //Виклик функції зі значенням третього параметра за замовчуванням. f1(a,10,"Hello"); // Виклик без значень параметрів за замовчуванням.

Приклад 8.11 Написати функцію, яка виконує ділення одного цілого числа на друге. Якщо друге число не зазначено, то виконується ділення на 2.

Тексти функції та її виклику в основній пр ограмі:

double divide(int a, int b=2) { return (double)a/b; }

void main()

{ cout << divide(12); //12 : 2 cout << endl;

cout << divide(20,4); //20 : 4 getch();

}

Результати виконання програми:

6

5

8.4 Функції зі змінною кількістю параметрів

Якщо список формальних параметрів функції завершується трьома крапками, це означає, що при її виклику на цьому місці можна зазначити ще кілька параметрів. Перевірка відповідності типів для цих параметрів не виконується, char та short передаються як int, а float – як double. За приклад стандартної функції зі змінним числом параметрів можна навести функцію printf(), прототип якої має вигляд

int printf (const char*, ...)

Це означає, що виклик функції має містити принаймні один параметр типу char* й може чи то містити, чи не містити інші параметри:

printf("Уведіть вихідні дані"); printf("Сума: %5.2f рублів", sum); printf("%d %d %d %d", a, b, c, d);

//Один параметр

//Два параметри

//П'ять параметрів

Функції

287

Для роботи з функціями зі змінною кількістю аргументів використовуються величини va_list, va_start, va_arg і va_end, описані в заголовному файлі <stdarg.h>.

Тип va_list призначено для зберігання вказівника на черговий аргумент. Для роботи з функціями зі змінною кількістю аргументів використовуються декілька стандартних макросів (дещо подібних до функцій наборів ко-

манд).

Макрос va_start ініціює вказівник на черговий елемент, він встановлює аргумент arg_ptr на початок списку необов‟язкових параметрів і має вигляд функції з двома параметрами:

void va_start(arg_ptr, prav_param);

де параметр prav_param має бути останнім обов‟язковим параметром викликуваної функції, а вказівник arg_ptr має бути оголошено у списку змінних типу va_list у вигляді

va_list arg_pt;

Макрос va_start має бути використано до першого використання макросу va_arg. Макрос va_arg повертає значення чергового аргументу, кожен його виклик призводить до просування вказівника, який зберігається в va_list. Макрокоманда va_arg забезпечує доступ до поточного параметра викликуваної функції й теж має вигляд функції із двома параметрами:

type_arg va_arg (arg_ptr, type);

Ця макрокоманда бере значення типу type за адресою, заданою вказівником arg_ptr, збільшує значення вказівника arg_ptr на довжину використаного параметра (довжина type) і в такий спосіб параметр arg_ptr вказуватиме на наступний параметр викликуваної функції. Макрокоманда va_arg використається стільки разів, скільки треба для отримання всіх параметрів викликуваної функції.

Після перебирання аргументів, але до виходу з функції зі змінним числом аргументів, необхідно звернутися до макросу va_end. Він встановлює вказівник списку необов‟язкових параметрів на NULL.

Приклад 8.12 Увести послідовність цілих чисел і обчислити за допомогою функції зі змінною кількістю параметрів середнє значення цієї послідовності. За завершення послідовності слід вважати значення, яке дорівнює –1.

Розв‟язок. Оскільки у списку має бути принаймні один елемент, у функції буде один обов‟язковий параметр х, після якого слід поставити три крапки.

Тексти функції та її виклику в основній пр ограмі: float sred_znach (int x, ...)

{int i=0, j=0, sum=0; va_list uk_arg;

va_start(uk_arg, x);

// Встановлення вказівника uk_arg на перший

 

// необов‟язковий параметр.

if(x!=-1) sum=x;

// Перевірка наявності чисел у списку.

else return 0;

 

288

Розділ 8

j++;

while((i=va_arg(uk_arg, int))!=-1) // Вибірка чергового параметра

{sum+=i; j++;}

// й перевірка завершення списку

va_end(uk_arg);

//Закриття списку параметрів

return (float)sum/j;

 

 

}

 

 

void main()

 

 

{ float sr=sred_znach(2,3,4,-1);

// Виклик з чотирма параметрами

cout << "sr=" << sr << endl;

 

sr=sred_znach(5,6,7,8,9,-1);

// Виклик з шістьма параметрами

cout << "sr=" << sr;

 

 

getch();

 

 

}

 

 

8.5 Рекурсивні функції

Рекурсивною називається функція, яка викликає сама себе. Така рекурсія називається прямою. Існує ще непряма рекурсія, коли дві чи більше функцій викликають одна одну.

Несерйозні приклади, які добре ілюструють принцип рекурсії, можна знайти в дитячих лічилках, наприклад:

Упопа був собака, він її любив Вона з'їла шматок м'яса, він її убив

Уземлю закопав,

Напис написав:

“У попа був собака, він її любив Вона з'їла шматок м'яса, він її убив У землю закопав, Напис написав:

“У попа був собака, він її любив Вона з'їла шматок м'яса, він її убив У землю закопав, Напис написав:

... (лапки цитат ніколи не закриються)

Загальновідомий приклад рекурсивного зображення – предмет між двома дзеркалами – у кожному з них видно нескінченний ряд відображень.

Окрім того, поняття рекурсії добре ілюструється матрьошками. Це означає, що рекурсія дозволяє звести складне завдання до розв‟язку його зменшених копій. Послідовно зменшуючи розмір цих завдань, процес рекурсії в решті решт приведе до відомого розв‟язку, використовуючи який можна легко здобути розв‟язок початкового завдання.

Більш серйозні приклади рекурсії можна віднайти у математиці: рекурентні співвідношення визначають певний елемент послідовності через один чи кілька попередніх. Наприклад, числа Фібоначчі :

F(n) = F(n–1) + F(n–2), де F(0)=1, F(1)=1.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]