Урок №24. Заголовкові файли

  Юрій  | 

  Оновл. 18 Кві 2020  | 

 259

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

Файли .cpp не є єдиними файлами в проектах. Є ще один тип файлів — заголовкові файли (або ще “заголовки“), які мають розширення .h. Метою заголовків є зручне зберігання набору оголошень об’єктів для їх подальшого використання в інших програмах.

Заголовкові файли зі Стандартної бібліотеки С++

Розглянемо наступну програму:

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

Hello, world!

У цій програмі ми використовуємо об’єкт std::cout, який ніде не визначаємо. Як компілятор знає, що це таке? Справа в тому, що std::cout оголошений в заголовку iostream. Коли ми пишемо #include <iostream>, ми робимо запит, щоб весь вміст заголовка iostream було скопійовано в наш файл. Таким чином, весь вміст iostream стає доступним для використання.

Як правило, в заголовкових файлах записуються тільки оголошення, без визначень. Отже, якщо std::cout тільки оголошений в заголовку iostream, де ж він визначається? Відповідь: “В Стандартній бібліотеці С++, яка автоматично підключається до вашого проекту на етапі лінкінга”.

Подумайте, що б сталося, якби заголовкового файла iostream не було б? Кожен раз, при використанні std::cout, вам доводилося би вручну копіювати всі попередні оголошення, пов’язані з std::cout, в верхню частину вашого файлу! Добре ж, що можна просто #include <iostream>, чи не так?

Пишемо свої власні заголовкові файли

Тепер давайте повернемося до прикладу, який ми обговорювали в попередньому уроці. У нас було два файли: add.cpp і main.cpp.

add.cpp:

main.cpp:

(Якщо ви все робите з нуля, то не забудьте додати add.cpp в свій проект, щоб він був підключений до компіляції)

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

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

Написати свій власний заголовок не так вже й складно. Заголовкові файли складаються з двох частин:

   Директиви препроцесора (зокрема, header guards), які запобігають викликам заголовка більше одного разу з одного і того ж файлу (про це детальніше в наступних уроках).

   Вміст заголовкового файлу: набір оголошень.

Всі ваші заголовки (які ви написали самостійно) повинні мати розширення .h.

add.h:

Щоб використовувати цей файл в main.cpp, вам спочатку потрібно буде підключити його до проекту.

main.cpp, в якому ми підключаємо add.h:

add.cpp залишається без змін:

Коли компілятор зустрічає #include "add.h", то він копіює весь вміст add.h в поточний файл. Таким чином, ми отримуємо попереднє оголошення функції add().

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

Якщо ви отримали помилку від компілятора, що add.h не знайдений, то переконайтеся, що ім’я вашого файлу точно add.h. Цілком можливо, що ви могли зробити помилку, наприклад: просто add (без .h) або add.h.txt або add.hpp.

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

Кутові дужки (<>) vs. Подвійні лапки (“”)

Ви, напевно, хочете дізнатися, чому використовуються кутові дужки для iostream і подвійні лапки для add.h. Справа в тому, що, використовуючи кутові дужки, ми повідомляємо компілятору, що заголовковий файл, який підключається, написаний не нами (він є “системним”, тобто тим, який надається Стандартною бібліотекою С++), так що шукати цей заголовок слід в системних директоріях. Подвійні лапки повідомляють компілятору, що ми підключаємо наш власний заголовок, який ми написали самостійно, тому шукати його слід в поточній директорії нашого проекту. Якщо файла там не виявиться, то компілятор почне перевіряти інші шляхи, у тому числі і системні директорії.

Правило: Використовуйте кутові дужки для підключення “системних” заголовкових файлів і подвійні лапки для всього іншого (ваших власних заголовків).

Варто відзначити, що одні заголовки можуть підключати інші заголовки. Проте, ми не рекомендуємо цього робити.

Чому iostream пишеться без закінчення .h?

Ще одне часте питання: “Чому iostream (або будь-який інший зі стандартних заголовкових файлів) при підключенні пишеться без закінчення .h?”. Справа в тому, що є два окремих файла: iostream.h (заголовок) і просто iostream! Для пояснення потрібен короткий екскурс в історію.

Коли C++ тільки створювався, всі файли бібліотеки Runtime мали закінчення .h. Оригінальні версії cout і cin оголошені в iostream.h. При стандартизації мови С++ комітетом ANSI, вирішили перенести всі функції з бібліотеки Runtime в простір імен std, щоб запобігти можливості виникнення конфліктів імен з ідентифікаторами користувачів (що, між іншим, є хорошою ідеєю). Проте, виникла проблема: якщо всі функції перемістити в простір імен std, то старі програми переставали працювати!

Для забезпечення зворотної сумісності ввели новий набір заголовків з тими ж іменами, але без закінчення .h. Весь їх функціонал знаходився в просторі імен std. Таким чином, старі програми з #include <iostream.h> не потрібно було переписувати, а нові програми вже могли використовувати #include <iostream>.

Коли ви підключаєте заголовки зі Cтандартної бібліотеки C++, переконайтеся, що ви використовуєте версію без .h (якщо вона існує). В іншому випадку ви будете використовувати застарілу версію заголовкового файлу, який вже більше не підтримується.

Крім того, багато бібліотек, успадковані від мови Cі, які до сих пір використовуються в C++, також були продубльовані з додаванням префікса c (наприклад, stdlib.h став cstdlib). Функціонал цих бібліотек також був переміщений в простір імен std, щоб уникнути можливості виникнення конфліктів імен з користувацькими ідентифікаторами.

Правило: При підключенні заголовкових файлів зі Стандартної бібліотеки С++, використовуйте версії без “.h” (якщо вони існують). Користувацькі заголовки ж повинні мати закінчення “.h”.

Чи можна записувати визначення в заголовкових файлах?

C++ не буде скаржитися, якщо ви це зробите, але так робити не прийнято.

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

Для невеликих проектів, це, ймовірно, не буде проблемою. Але для більш масштабних проектів це може сприяти збільшенню часу компіляції (так як код буде повторно компілюватися) і розміру виконуваного файлу. Якщо внести зміни до визначень, які знаходяться в файлі .cpp, то перекомпілювати доведеться тільки цей файл. Якщо ж внести зміни до визначень, які записані в заголовку, то перекомпілювати доведеться кожен файл, який #include цей заголовок. Через одну незначну правку, вам доведеться перекомпілювати весь проект!

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

Поради

Ось декілька порад щодо написання своїх власних заголовкових файлів:

   Завжди використовуйте директиви препроцесора.

   Не визначайте змінні в заголовкових файлах, якщо це не константи. Заголовкові файли слід використовувати тільки для оголошень.

   Не визначайте функції в заголовкових файлах.

   Кожен заголовок повинен виконувати свою певну роботу і бути якомога більш незалежним. Наприклад, ви можете помістити всі ваші оголошення, пов’язані з файлом А.cpp в файл A.h, а всі ваші оголошення, пов’язані з B.cpp в файл B.h. Таким чином, якщо ви будете працювати тільки з А.cpp, то вам достатньо буде підключити тільки A.h і навпаки.

   Використовуйте імена ваших робочих файлів в якості імен для ваших заголовків (наприклад: grades.h працює з grades.cpp).

   Не підключайте одні заголовки з других заголовків.

   Не #include файли .cpp.

На цьому все. В наступному уроці ми розглянемо директиви препроцесора.

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (Немає Оцінок)
Loading...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *