Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекцій по С.doc
Скачиваний:
7
Добавлен:
16.11.2019
Размер:
393.73 Кб
Скачать

4.5 Рекурсія

4.5. Рекурсія

Функції можуть викликатися рекурсивно

int gcd (int v1, int v2)

{

return v2 ? gcd (v2, v1%v2): v1;

}

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

int BadFib(int n)

{

// Так не варто рахувати!!!

switch (n)

{

case 0:

return 0; break;

case 1:

return 1; break;

default:

return BadFib(n-1)+BadFib(n-2);

}

}

Але це не проблема рекурсії, а проблема неефективної схеми обчислень. Дійсно, підраховуючи n-1-е число Фібоначчі корисно пам’ятати обчислене перед цим n-2-е:

void fib(int &f1, int &f2, int n)

{

int f;

if (n>=2)

{

f=f2; f2+=f1; f1=f;

fib(f1, f2, n-1);

}

}

int Fibonacci (int n)

{

int f0=0, f1=1;

switch (n)

{

case 0:

return f0;break;

case 1:

return f1; break;

default:

fib(f0, f1, n);

return f1;

}

}

4.6 Довизначення (overloading) функцій

4.6. Довизначення (overloading) функцій

Візьмемо дві функції

void DisplayInt( int intParam )

{

cout << "The integer is: " << intParam << "\n";

}

void DisplayString( char *text )

{

cout << "The text is: " << text << "\n";

}

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

void Display( int intParam )

{

cout << "The integer is: " << intParam << "\n";

}

void Display( char *text )

{

cout << "The text is: " << text << "\n";

}

Їх виклик

Display(a)

буде розпізнаватися за типом параметру. Ясно, що для використання цих функцій нам потрібні будуть обидва прототипи

void Display( int );

void Display( char * );

При необхідності список функцій з одним і тим же іменем може бути розширений шляхом довизначення (overloading) цієї функції для іншого типу параметрів, наприклад,

void Display( double );

Можна визначити декілька функцій для обчислення максимуму

int max (int x, int y) { return x>y? x: y; }

double max (double x, double y) { return x>y? x: y; }

int max (int x, int y, int z) { return max(x, max(y,z));}

Особливо популярним є довизначення стандартних операцій. Наприклад, визначимо структуру точки площини

struct Point{ double x; double y;}

Point operator+(Point u, Point v)

{

Point w;

w.x = u.x + v.x;

w.y = u.y + v.y;

return w;

}

Можна уявити собі декілька сигнатур додавання:

Point operator+(Point, Point);

Point operator+(Point &, Point &);

Point operator+(const Point &, const Point &);

Подумайте, на якому з них зупинитися.

Можна також довизначити порівняння точок на рівність

bool operator==(Point u, Point v)

{

return (u.x == v.x) && (u.y == v.y);

}

нерівність і багато чого іншого.

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