Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
lektsii_OP / T12.doc
Скачиваний:
96
Добавлен:
17.03.2016
Размер:
272.38 Кб
Скачать

Бібліотеки визначень

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

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

Підключення бібліотек до вихідного коду програм виконується відповідно до синтаксису мови програмування. Так у Pascal для цього використовується директива uses, у С/С++ - директива препроцесора #include. Наприклад,

#include <stdio.h> // підключення засобів форматованого введення-виведення С/С++

У системах програмування можуть використовуватися два види бібліотек: стандартні та власні, наприклад,

#include <stdio.h> // підключення стандартної бібліотеки С/С++

#include “mylib.h“ // підключення власної бібліотеки С/С++

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

Інтерфейс та реалізація бібліотек визначень можуть міститися як в одному файлі, так і у різних файлах. Наприклад, у Pascal інтерфейсний розділ та розділ реалізації є складовими єдиного бібліотечного модуля. У С/С++ визначення даних і прототипи функцій задаються у заголовних файлах (.h-файлах), а коди функцій - у срр-файлах.

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

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

Як зазначалося вище, заголовні файли (.h-файли) у С/С++ містять інтерфейсну частину визначень. Реалізацію ж функцій подають у файлах .cpp. Тобто, для кожного cpp-файла створюється власний заголовний файл з таким самим ім’ям і розширенням h і, навпаки, здебільшого, для кожного заголовного файла створюється cpp-файл з реалізацією функцій, оголошених у h-файлі.

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

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

#include <math.h> // підключення заголовного файла з математичними функціями

Окрім того, для таких файлів іноді можна не зазначати розширення .h (наприклад, для <iostream>).

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

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

  • оголошення типів (наприклад, typedef double arr [14]; );

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

  • визначення вбудованих функцій (наприклад, inline char get() { return *p++; } );

  • оголошення даних (наприклад, extern int a; );

  • визначення констант (наприклад, const float pi = 3.141593; );

  • перерахування (наприклад, enum bool { false, true}; );

  • команди підключення файлів (наприклад, #include <math.h> );

  • макровизначення (наприклад, #define n 7 );

  • коментарі (наприклад, /* Перевірка на кінець файла */ ).

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

З іншого боку, в заголовному файлі ніколи не повинно бути:

  • визначення функцій (наприклад, char get() { return *p++; } );

  • визначення даних (наприклад, int a; );

  • визначення складених констант (наприклад, const b[i] = { /* ... */ }; ).

Повернемося до попереднього прикладу.

У файлі tabular.h зробимо потрібні оголошення змінних і функцій.

// --------------------------------------- tabular.h - заголовний файл -------------------------------------------

#include<iostream>

#include<stdio.h>

#include<math.h> // підключення математичної бібліотеки

using namespace std;

//=================== визначення глобальних змінних================

float pi=3.14159;

//======================== прототипи функцій================

void Print(float, float, float, float (*)(float), char []);

У файлі tabular.cpp пропишемо визначення функцій, прототипи яких розміщено у файлі tabular.h.

//------------------------------- tabular.cpp. - файл реалізації функцій --------------------------------

#include<iostream>

#include<stdio.h>

#include<math.h> // підключення математичної бібліотеки

using namespace std;

//============== табулювання функції *func ============

void Print(float a, float b, float h, float (*func)(float), char s[]) /* a, b - межі відрізка,

h -відстань між точками, s - текстова назва функції*/

{ float x; //значення аргументу функції *func

cout<<"=================="<<endl; //виведення заголовка таблиці

cout<<" x | "<<s<<endl;

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

x=a;

while (x<b) //поки не досягнуто правої межі відрізка

{ printf("%6.2f |",x);

printf("%8.4f \n", (*func)(x)); //обчислити і вивести значення функції

x+=h;

}

}

Головний файл проекту арр.cpp:

//-------------------- арр.cpp - файл головної функції, містить виклики функцій ---------------------

#include "tabular.h" // підключення власної бібліотеки

using namespace std;

float lower, upper, step; // межі відрізка, крок

int main()

{ cout<<"Enter lower, upper bounds and step: ";

cin>>lower>>upper>>step;

lower*=pi; upper*=pi; step*=pi;

Print(lower, upper, step, sin, "sin(x)"); //табулювання стандартної функції sin

Print(lower, upper, step, sin, "cos(x)"); //табулювання стандартної функції cos

system("pause");

}

Отже, маємо три файли. Заголовний файл tabular.h містить визначення величини  і заголовок функції Print, яка табулює значення вказаних функцій на заданому відрізку, файл tabular.cpp — реалізацію даної функції та необхідні «стандартні» директиви, а файл головна програма арр.cpp — включення tabular.h та головну функцію.

Стандартний заголовний файл в директиві include вказується в кутових дужках, як, наприклад, <iostream>, а файл, створений програмістом — у лапках, як "tabular.h". Кутові дужки говорять про те, що препроцесор має шукати заголовний файл, починаючи з підкаталогу include каталогу з IDE та його підкаталогів, лапки — з каталогу з поточним cpp-файлом. Отже, файли tabular.h та арр.cpp краще розмістити в одному й тому самому каталозі.

Тексти у файлах tabular.cpp та арр.cpp є одиницями трансляції (translation unit), або програмними одиницями. Кожна така одиниця містить послідовність функцій, директив препроцесора та інструкцій оголошень імен. Як правило, С/С++-програма складається з кількох одиниць трансляції.

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

В IDE MS Visual Studio засобами меню створимо новий проект. Типом проекту виберемо Win32 Console Application — консольна програма на платформі Win32. За допомогою меню додамо до проекту вхідні файли tabular.cpp та арр.cpp, а також tabular.h. Далі залишається побудувати виконуваний код (він матиме ім’я проекту з розширенням ".exe").

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

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

// ----------Файл app.cpp-----------

#include "headone.h"

#include "headone.h"

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

// -----------Файл headtwo.h---------

int х;

// -----------Файл headone.h---------

#include "headtwo.h"

// -----------Файл app.cpp-------------

#include "headone.h"

#include "headtwo.h" // повторне підключення заголовного файла

Оскільки директивою #include заголовні файли підключаються до програми з усім їхнім вмістом, файл app.cpp буде таким:

// -----------Файл app.cpp-------------

. . .

int х; // з headtwo.h через headone.h

. . .

int х; // безпосередньо з headtwo.h

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

Для попередження помилок повторних включень визначення у заголовному файлі слід розпочинати з директиви препроцесора:

#if!defined(HEADCOM)

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

#if!defined (HEADCOM) // якщо змінну HEADCOM ще не визначено

#define HEADCOM // визначити ї

int х; // визначити змінну

int func (int a, int b) // визначити функцію func

{ return a+b;

}

#endif // директива, яка закриває умову

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

Раніше використовувалась директива #ifndef, це є те ж саме, що й #if!defined. Зрозуміло, що цей “захист від дурня” з використанням #if!defined спрацює лише у тому разі, коли визначення змінної х (чи іншої змінної або функції) може випадково бути включеним кількаразово до одного й того самого файла реалізації. Вона не спрацює, якщо х визначено у h-файлі, і його підключають до різних файлів F1 і F2. Препроцесор у цьому випадку є безсилий: він не в змозі визначити наявність однакових виразів у окремих файлах.

Соседние файлы в папке lektsii_OP