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

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

.pdf
Скачиваний:
210
Добавлен:
12.05.2015
Размер:
11 Мб
Скачать
☆

Функції

299

Функції не можуть бути перевантажені, якщо опис їхніх параметрів відрізняється лише специфікатором const чи використанням посилання, наприклад int та const int чи int та int&.

Приклад 8.21 Написати дві функції для обчислення суми сімох елементів цілого й дійсного масивів відповідно. Використати ці функції для обчислення сум двох масивів.

Тексти функцій та їх викликів в основній пр ограмі: int adder(int iarray[7])

{int isum = 0;

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

isum += iarray[i]; return(isum);

}

float adder (float farray[7])

{float fsum = 0;

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

fsum += farray[i]; return (fsum) ;

}

void main() {

int iarray[7] = {5,1,6,20,15,0,12};

float farray[7] = {3.3,5.2,0.05,1.49,3.12345,31.0,2.007}; int isum; float fsum;

isum = adder (iarray); // Виклик для масиву цілих чисел fsum = adder (farray); // Виклик для масиву дійсних чисел

cout << "Сума масиву цілих чисел дорівнює " << isum << "\n"; cout << "Сума масиву дійсних чисел дорівнює "<< fsum << "\n"; getch ();

}

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

Сума масиву цілих чисел дорівнює 59 Сума масиву дійсних чисел дорівнює 46.1705

Приклад 8.22 Написати три функції для обчислення периметра трикутника, чотирикутника й п‟ятикутника відповідно.

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

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

//Визначення всіх потрібних версій функції

//Версія функції для трикутника

int Perimetr(int Len1, int Len2, int Len3) { return Len1 + Len2 + Len3; }

300

Розділ 8

// Версія функції для чотирикутника

int Perimetr(int Len1, int Len2, int Len3, int Len4)

{return Len1 + Len2 + Len3 + Len4; } // Версія функції для п‟ятикутника

int Perimetr(int Len1, int Len2, int Len3, int Len4, int Len5)

{return Len1 + Len2 + Len3 + Len4 + Len5; }

int main()

{int AB = 5, BC = 3, CA = 7;

int CD = 8, DA = 11, DE = 2, EA = 9;

cout << "Perimetr ABC = " << Perimetr(AB, BC, CA) << "\n"; cout << "Perimetr ABCD = " << Perimetr(AB,BC,CD,DA) << "\n"; cout << "Perimetr ABCDE = " << Perimetr(AB, BC, CD, DE, EA);

getch();

}

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

Perimetr ABC = 15

Perimetr ABCD = 27

Perimetr ABCDE = 27

Питання та завдання для самоконтролю

1)У чому полягає різниця поміж оголошенням прототипу й визначенням

функції?

2)Які дії виконує у функції оператор return?

3)Для яких функцій при їхньому визначенні не використовується ключове слово return?

4)Який тип має функція, яка не повертає значення?

5)Яку інформацію про функцію надає такий її заголовок: int fun(double x)

6)В який спосіб можна передавати з функції декілька результатів?

7)Які параметри функції називають формальними, а які – фактичними?

8)Чи повинні збігатися імена відповідних формальних і фактичних параметрів функції?

9)Що таке локальна змінна?

10)Поясніть різницю поміж локальними та глобальними змінними.

11)В яких випадках слід використовувати глобальні змінні?

12)Що буде виведено на екран після виконання такої програми?

#include <iostream.h>

void swap(int i, int j)

{int temp = i; i = j; j = temp;} int main()

{int a = 10, b = 3; swap (a, b);

cout << "а = " << a << " and b = " << b; return 0; }

Функції

301

13)Що таке область видимості змінної?

14)Чи можна у С++ оголошувати вкладені функції?

15)Які є способи передавання параметрів до функції?

16)Що буде виведено на екран після виконання програми у завданні 12,

якщо замінити функцію swap() на таку: void swap(int &i, int &j)

{int temp = i; i = j; j = temp; }

17)Що таке перевантаження функцій?

18)Яка проблема виникне з такими перевантаженими функціями? void inc(int &i)

{i = i + 1; }

void inc(int &i, int diff = 1)

{i += diff; }

19)Знайдіть помилку у функції

double volume(double length, double width = 1, double height) { return length * width * height; }

20)Поясніть способ передавання параметрів до функції void inc (int &i, int diff = 1)

{i += diff; }

21)Яка помилку припущено у такій програмі? Як її виправити?

# include <iostream.h> int main()

{double x = 5.2;

cout << x << " ^ 2 = " << sqr(x); return 0;

}

double sqr( double х)

{return x * x; }

22)Що таке рекурсія?

23)Чи є наступна рекурсивна функція правильною? Поясніть. double F(double х)

{return F(x-1); }

24)Напишіть програму із застосуванням перевантаження функцій обчислення суми двох чисел цілого і дійсного типів відповідно.

Розділ 9

Багатофайлові програми

9.1 Міжфайлова взаємодія

Проект С++ Builder складається з багатьох файлів, але власне програма дотепер містилася в єдиному файлі Unit1.cpp. Існує багато ситуацій у програмуванні, коли мати лише один файл програми незручно.

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

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

У багатофайлових програмах елементи програми (змінні, константи, функції тощо), які зберігаються у різних файлах, повинні взаємодіяти один з одним. Розглянемо спочатку окремі cpp-файли. Зазначимо, що область видимості – це частина програми, всередині якої до певної змінної чи іншого елемента програми є доступ (детальніше див. підрозд. 9.5). Приміром, елементи, оголошені всередині функції, мають локальну область видимості, тобто доступ до них існує лише з даної функції. Компоненти класу є видимі лише всередині класу (докладніше про класи див. розд. 14), окрім певних окремих випадків.

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

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

int a;

// Оголошення і визначення

extern int b;

// Лише оголошення

Багатофайлові програми

303

Глобальну змінну може бути визначено у програмі лише одноразово, незалежно від того, скільки файлів міститься у програмі. Локальні змінні з однаковими іменами може бути визначено у різних областях видимості. Але використовувати змінні з однаковими іменами, навіть у різних блоках програми, не рекомендовано, оскільки це може призвести до помилок.

// Файл А

int X; // Визначення змінної Х у файлі А // Файл В

int X; // Так робити не можна: є визначення такої самої змінної Х у файлі А

Але й наступний запис буде неправильним, оскільки у файлі В змінна Х є невідома:

// Файл А

int X; // Визначення змінної Х у файлі А // Файл В

X=3; // Так робити не можна, оскільки змінна Х у файлі В є невідома

Щоб глобальна змінна, визначена в одному файлі, була видимою у всіх файлах програми, слід оголосити її із зарезервованим словом extern:

// Файл А

int X; // Визначення змінної Х у файлі А // Файл В

extern int X; // Визначення змінної Х у файлі B

X=3;

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

Слід пам‟ятати про одне обмеження: змінну не можна ініціалізувати при оголошенні з extern. Вираз

extern int X=27;

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

Якщо треба мати дві глобальні змінні з однаковими іменами у двох різних файлах, їх слід визначати із зарезервованим словом static. У такий спосіб область видимості змінної звужується до файла, в якому її визначено. Решта змінних з таким ім‟ям можуть в інших файлах використовуватися без обмежень.

// Файл А

 

static int X;

// Визначення: змінна Х є видима лише у файлі А

// Файл В

 

static int X;

// Визначення: змінна Х є видима лише у файлі В

Хоча тут визначено дві змінні з однаковими іменами, конфлікту це не спричинює. Код, до якого входить звертання до Х, звертатиметься лише до змінної, ви-

304

Розділ 9

значеної у даному файлі. У такому разі вважають, що статична змінна має вну-

трішнє зв‟язування. Нестатичні глобальні змінні мають зовнішнє зв‟язування.

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

Звернімо увагу на те, що зарезервоване слово static має декілька значень залежно від контексту. У контексті глобальних змінних слово static звужує область видимості змінної до одного файла.

Змінна-константа, яка визначається за допомогою const, у загальному випадку є невидимою за межами одного файла. В цьому вона є подібна до static. Але її можна зробити видимою з кожного файла програми і для визначення, і для оголошення за допомогою слова extern:

// Файл А

extern const int X=99; // Визначення Х

// Файл В

 

extern const int X;

// Оголошення Х

Тут файл В матиме доступ до константи Х, визначеної в А. Компілятор розрізнює оголошення й визначення константи за наявністю чи то відсутністю ініціалізації.

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

Коли компілятор генерує виклик функції, йому не треба знати, в який спосіб вона працює. Все, що потрібно знати, – це її ім‟я, тип результату і типи параметрів (аргументів). Усі дані стосовно цього містяться в оголошенні функції. Тому можна легко визначити функцію в одному файлі, а викликати її з іншого. Жодних додаткових дозволів (як-от слова extern) не вимагається. Слід лише оголосити функцію в тому файлі, звідки ця функція викликатиметься.

// Файл А

 

int add(int a, int b)

// Визначення функції

{ return a+b; }

// з тілом функції

// Файл В

 

int add(int, int);

// Оголошення функції (без тіла)

. . .

 

int answer=add(2, 3);

// Виклик функції

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

Можна оголосити (не визначити!) функцію чи інший елемент програми кількаразово. Якщо оголошення не суперечать одне одному, компілятор не видасть помилку:

Багатофайлові програми

305

// Файл А

 

 

int add(int, int);

// Оголошення функції (без тіла);

 

int add(int, int);

// друге оголошення – помилки немає

 

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

// Файл А

 

static int add(int a, int b)

// Визначення функції

{ return a+b; }

 

// Файл В

 

static int add(int a, int b)

// Інша функція

{ return a+b; }

 

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

9.2 Заголовні файли

Заголовні файли мають розширення h чи hpp і містять заголовки (прототипи) функцій, а також оголошення типів, констант та змінних з ключовим словом extern. Перевага заголовних файлів полягає у тому, що, коли створено заголовний файл для одного модуля (одного чи декількох cpp-файлів) програми, можна переносити всі зроблені оголошення, які були зроблені, до іншої програми С++. Для цього слід просто залучити заголовний файл за допомогою директиви #include. Зазвичай для кожного cpp-файла створюється власний заголовний файл з таким самим ім‟ям і розширенням h. І, навпаки, здебільшого, для кожного заголовного файла створюється cpp-файл з реалізацією функцій, оголошених у h-файлі.

При залученні до програми заголовного файла його текст (а з ним і текст відповідного cpp-файла) автоматично вставляється до програми замість рядка з відповідною директивою #include. При компіляції програми всі залучені заголовні файли з файлами їхньої реалізації перекомпільовуються, тому залучення надмірної кількості заголовних файлів уповільнює компіляцію програми.

Заголовні файли можна розділити на стандартні й створювані програмістом. Стандартні заголовні файли зберігаються у спеціальній теці INCLUDE, яка є підтекою теки, в яку було встановлено Borland C++. Імена стандартних заголовних файлів пишуться у кутових дужках “<” і “>”, наприклад:

#include <math.h>

/*Залучення заголовного файла

 

з математичними функціями */

Окрім того, для таких файлів можна не зазначати розширення .h. Якщо стандартний заголовний файл розміщено в іншій теці, слід зазначити його місцезнаходження, наприклад, щоб залучити до програми заголовний файл types.h, який міститься у теці INCLUDE\SYS, слід написати:

#include <include\sys\types.h>

306

Розділ 9

Якщо cpp-файл і h-файл мають однакові імена, редактор коду Borland C++ Builder дозволяє перемикатися поміж цими файлами. Для цього слід клацнути правою кнопкою миші на імені вкладки, в якій відкрито один з файлів, і в контекстному меню обрати Open Source / Header File (Відкрити файл коду / Заголовний файл).

Заголовні файли, створені програмістом, зазвичай розташовують у теці проекту. Імена цих файлів у директиві #include пишуться у подвійних лапках і завжди з розширенням h або hpp, наприклад, #include "Myfile.h".

Для створення нового заголовного файла слід виконати команду File / New / Unit. Буде створено новий модуль, який складатиметься з двох файлів: заголовного файла, наприклад з ім‟ям Unit2.h (рис. 9.1), і відповідного до нього файла реалізації Unit2.cpp (рис. 9.2). При зберіганні буде запитано ім‟я тільки cpp-файла. Ім‟я h-файла буде створено автоматично.

Для використання заголовного файла у проекті його слід додати до проекту командою Project / Add To Project. У діалоговому вікні слід обрати ім‟я заголовного файла. Окрім того, для використання в інших модулях проекту функцій, типів, констант, оголошених у заголовному файлі, слід залучити заголовний файл до модуля за допомогою директиви #include чи то виконати команду File / Include Unit Hdr і обрати відповідний файл.

Рис. 9.1. Вікно заголовного файла

Рис. 9.2. Вікно файла реалізації

 

Багатофайлові програми

307

Заголовний файл може містити:

 

оголошення типів

typedef double arr [14];

 

шаблони типів

template <class T>

 

 

class V { /* ... */ }

 

оголошення (прототипи) функцій extern int strlen(const char*);

визначення функцій-підстановок inline char get()

{return *p++; }

оголошення даних

extern int a;

визначення констант

const float pi = 3.141593;

перерахування

enum bool { false, true };

оголошення імен

class Matrix;

команди долучення файлів

#include <signal.h>

макровизначення

#define Case break;case

коментарі

/* Перевірка на кінець файла */

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

визначення функцій

char get() { return *p++; }

визначення даних

int a;

визначення складених констант

const tb[i] = { /* ... */ };

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

// Файл app.cpp з помилкою

#include "headone.h" #include "headone.h"

Припустімо, що є файл з кодом програми app.cpp і два заголовних файли headone.h та headtwo.h. До того ж headone.h долучає до себе headtwo.h. Як-

що є потреба залучити обидва заголовні файли до app.cpp, слід уважно стежити за тим, щоб не залучити один і той самий файл двічі. Наприклад:

//Файл headtwo.h extern int X;

//Файл headone.h

#include "headtwo.h"

//Файл app.cpp

#include "headone.h"

#include "headtwo.h" // Повторне залучення заголовного файла

Оскільки директивою

#include заголовні файли долучаються до програми

з усім їхнім умістом, файл app.cpp буде таким:

// Файл app.cpp

. . .

 

int X;

// З headtwo.h через headone.h

. . .

 

int X;

// Безпосередньо з headtwo.h

308

Розділ 9

Як наслідок, компілятор виведе повідомлення, що змінну Х оголошено двічі. Для попередження помилок повторних включень визначення у заголов-

ному файлі слід розпочинати з директиви препроцесора:

#ifndef HEADCOM

чи директиви

#if!defined(HEADCOM)

На місці HEADCOM може бути який завгодно ідентифікатор. Цей вираз говорить про те, що якщо HEADCOM ще не було визначено, то весь текст звідси і до директиви #endif просто вставлятиметься до файла реалізації. В іншому разі (якщо HEADCOM вже було визначено раніш, в чому можна впевнитися за допомогою директиви #define HEADCOM) наступний за #if текст не буде долучено до початкового коду. Оскільки змінну HEADCOM не було визначено до того як ця директива зустрілася вперше, але одразу ж після #if!defined() вона стала визначеною, увесь текст, розміщений поміж #if і #endif, буде долучено один раз, але це буде перший і останній раз. Наприклад:

#if!defined (HEADCOM)

// Якщо змінну HEADCOM ще не визначено,

#define HEADCOM

// визначити її,

extern int X;

// визначити змінну Х,

int func(int a, int b);

// визначити функцію func().

#endif

// Директива, яка закриває умову.

Цей підхід слід використовувати завжди, коли існує можливість випадково долучити заголовний файл до початкового понад одного разу. Раніше використовувалась директива #ifndef, це є те ж саме, що й #if!defined.

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

Приклад 9.1 Створити заголовний файл, який містить прототипи таких функцій:

1) обчислення елементів матриці а розмірністю 8 8 за формулою

 

 

 

 

 

i j 2

 

4

3

 

 

 

j

 

 

 

ai, j

7i 1

 

 

sin3 i j ,

 

 

 

 

 

 

3.7 * i j , за

sin i j 0

за

sin i j 0

2)обчислення вектора (одновимірного масиву), елементи якого є сумами елементів відповідних рядків матриці а;

3)пошук елемента матриці а, найбільш наближеного до значення середнього арифметичного елементів матриці а.

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