
C _Учебник_МОНУ
.pdf
Багатофайлові програми |
329 |
поточний каталог;
каталоги, вказані опцією компілятора /I;
каталоги, задані змінною оточення INCLUDE.
Якщо ім‟я файла зазначено зі шляхом, то препроцесор ніде більше цей файл не буде шукати.
Третя форма директиви #include припускає наявність макросу, який визначає файл, що долучається. Ідентифікатор макроса не повинен починатися з символів “<” та “"”.
Наприклад, для долучення файла vcl.h, який шукається у стандартному каталозі, директива має вигляд:
#include <vcl.h>
Наведемо поширений приклад директиви долучення файла Unit1.h, який шукається передусім у каталозі, де розташований файл, який містить цю директиву:
#include "Unit1.h"
Покажемо директиви, які долучають файл C:\Test\My.h, що шукатиметься лише у каталозі C:\Test:
#define myincl "C:\Test\My.h" #include myincl
Директива #undef використовується для відмінення чинності поточного визначення директиви #define для зазначеного ідентифікатора. Не є помилкою використання директиви #undef для ідентифікатора, який не було визначено директивою #define. Синтаксис цієї директиви є таким:
#undef <ідентифікатор>
Як було зазначено вище, заголовний файл також може містити директиви #include. Тому іноді складно зрозуміти, які саме заголовні файли долучено до програмного коду, і деякі заголовні файли може бути долучено декілька разів. Уникати цього дозволяють умовні директиви препроцесора.
Розглянемо приклад:
#ifndef BIBL_H #define BIBL_H
//Вміст файла bibl.h
#endif
Умовна директива #ifndef перевіряє, чи не було значення BIBL_H визначено раніше (BIBL_H – це константа препроцесора; такі константи узвичаєно писати великими літерами). Препроцесор опрацьовує наступні рядки аж до директиви #endif. Інакше він пропускає рядки від #ifndef до #endif. Директива #define BIBL_H визначає константу препроцесора BIBL_H. Розмістивши цю директиву безпосередньо після директиви #ifndef, можна гарантувати, що змістовну частину заголовного файла bibl.h буде включено до початкового тексту лише один раз, скільки б разів не долучався до тексту сам цей файл.

330 |
Розділ 9 |
Директива #if та її модифікації #ifdef, #ifndef разом з директивами #else, #endif, #elif дозволяють організувати умовне опрацювання тексту програми. При використовуванні цих засобів компілюється не весь текст, а лише ті його частини, які обираються за допомогою вищенаведених директив. Головна ідея полягає у тому, що, якщо вираз, який розміщено після директив #if, #ifdef, #ifndef виявиться істинним, то буде скомпільовано код, розташований поміж однією з цих трьох директив та директивою #endif, інакше цей код буде опущений. Директива #endif використовується для позначення завершення блока #if. Директиву #else можна використовувати з кожною із наведених вище директив для надання альтернативного варіанта компіляції.
Загальна форма запису директиви #if:
#if <константний_вираз>
Якщо константний вираз є істинним, буде скомпільовано код, розташований безпосередньо за цією директивою.
Директива #line дозволяє зазначати ім‟я файла і бажаний початковий номер рядка. Базова форма запису цієї команди є такою:
#line <номер> * <ім‟я_файла> *
Тут номер – це певне додатне число, а ім‟я файла – допустимий ідентифікатор файла. Значення параметру номер стає номером поточного початкового рядка, а значення параметру ім‟я файла – ім‟ям початкового файла. Параметр ім‟я файла є необов‟язковий.
Директива #line переважно використовується з метою налагодження програм і в спеціальних додатках.
Директива #error дозволяє задавати текст діагностичного повідомлення, яке виводиться при виявленні помилок. За наявності цієї директиви відображується задане повідомлення і номер рядка.
Дія директиви #pragma залежить від конкретної реалізації компілятора. Директива дозволяє видавати компіляторові різні інструкції.
Директива # є порожньою директивою і завжди ігнорується.
9.5 Область дії та простір імен
Реальний програмний проект розробляється як кілька вихідних файлів, які компілюються окремо один від одного, а потім поєднуються редактором зв‟язків. Як було зазначено у підрозд. 8.1, змінні, оголошені всередині тіла функції, називають локальними. Такі змінні діють лише всередині своєї функції. Функції з різних файлів можуть використовувати глобально доступні зовнішні змінні. Самі функції зазвичай є зовнішніми і доступними з будь-яких файлів. Кожна змінна характеризується областю дії, областю видимості і тривалістю життя. Схеми розподілу пам‟яті С++ визначають, як довго змінні лишаються в пам‟яті, та які частини програми мають до них доступ.
Область видимості змінної – це частина тексту програми, в якій може бути використано змінну. Змінна вважається за видиму, якщо відомі її тип та ім‟я. Змінна може бути видимою у межах блока, файла чи всієї програми. Якщо
Багатофайлові програми |
331 |
змінна оголошена у файлі на зовнішньому рівні, вона є видима від оголошення і до кінця цього файла. Змінну можна оголосити глобально видимою за допомогою відповідних специфікаторів у вихідних файлах програми. Якщо змінна оголошена у блоці, вона є видимою всередині цього блока і блоків, вкладених у нього.
Нагадаємо, що блоком у програмі є складений оператор чи послідовність операторів, обмежених операторними дужками {}. При цьому з точки зору синтаксису блок вважається єдиним оператором. Окрім того, блоки можуть містити складені оператори, але не можуть містити визначення функцій, тобто всередині функції не можна визначити іншу функцію.
Тривалість життя змінної – це інтервал виконання програми, під час якого змінна існує в пам‟яті. Змінна з глобальним часом існування має відведену для неї пам‟ять упродовж всього часу виконання програми, а змінні з локальним часом життя – лише під час виконання відповідного блока програми. Функції у програмі на С++ мають глобальний час життя.
Як відомо, синтаксис оголошення змінної у програмі є такий:
[<клас пам‟яті][ <тип>] <ім‟я> =[<ініціалізатор>];
Існують такі специфікатори класів пам‟яті:
1) |
auto; |
3) |
extern; |
5) volatile; |
2) |
register; |
4) |
static; |
6) mutable. |
Специфікатор auto (так звана автоматична змінна) використовується для оголошування лише локальних змінних, які є видимі лише в тому блоці, в якому вони оголошені. Цей специфікатор призначається за замовчуванням, а тому його можна явно не зазначати:
auto int X=10; // Те саме, що int X=10;
Специфікатор register вказує компілятору по можливості розмістити змінну в регістрах процесора. Оскільки кількість регістрів процесора є обмежена, кількість таких змінних має бути невеликою. За невдалої спроби такого розміщення змінна буде вести себе подібно до локальної змінної типу auto. Розміщення змінних у регістрах оптимізує програмний код за швидкістю, оскільки процесор оперує зі змінними, розміщеними у регістрах, набагато швидше, ніж з пам‟яттю. Регістрова пам‟ять, якщо вона є, може бути виділена лише для змінної типу int і вказівників:
register int Y;
Специфікатори auto та register застосовуються лише для локальних змінних. Спроба застосування цих специфікаторів для оголошення глобальних змінних призведе до помилки.
Специфікатор extern (зовнішній) використовують для посилання на змінну, оголошену в іншому файлі (див. підрозд. 9.1). Якщо визначення зовнішньої змінної розміщено у тому ж самому файлі, але пізніше, ніж її використання, слід теж застосовувати оголошення з специфікатором extern.
Специфікатор static (статичний) застосовують для оголошення статичних змінних, які зберігають власні значення від одного входження до функції (блока) до наступного входження до цієї функції (блока) протягом усього часу виконання програми. Статичну змінну не можна оголосити в іншому файлі як
Багатофайлові програми |
333 |
оголошено мітку). У межах однієї функції всі мітки мають бути різними, але імена міток у різних функціях можуть збігатися.
Прототип функції. Ідентифікатори, які зазначено у переліку параметрів прототипу (заголовка, оголошення) функції, мають область дії лише прототип функції.
Клас. Елементи структур, об‟єднань і класів (за винятком статичних елементів) є видимими лише у межах класу. Вони утворюються при створюванні змінної зазначеного типу і руйнуються при її знищенні.
Іменована область. С++ дозволяє явно задавати область визначення імен як частину глобальної області за допомогою оператора namespace.
Область видимості співпадає з областю дії, за винятком ситуації, коли у вкладеному блоці оголошено змінну з таким самим ім‟ям. У цьому разі зовнішня змінна у вкладеному блоці є невидима, хоча цей блок і входить в її область дії. До цієї змінної, якщо вона є глобальна, можна звертатися, використовуючи операцію визначення діапазону доступу (::).
Простори імен
У кожній області дії розрізняють так звані простори імен (namespace). Простори імен надають можливість створювати іменовані області пам‟яті, в яких можна оголошувати ідентифікатори. Вони призначені для зниження вірогідності конфліктів імен, коли імена з одного простору пам‟яті не конфліктують з тими самими іменами, оголошеними в інших просторах пам‟яті. Це особливо важливо для великих програм, які використовують програмні коди, розроблені різними постачальниками програмних продуктів. Ідентифікатори у просторах імен можуть стати доступними при застосуванні операції доступу (::) або директиви using.
У різних просторах імена можуть збігатися, наприклад:
struct Node
{ int Node; int i; } Node;
Тут протиріч немає, оскільки імена типу, змінної та елементи структури належать до різних просторів.
У С++ визначено чотири класи ідентифікаторів, у межах кожного з них імена мають бути унікальними:
до першого класу належать імена змінних, функцій, типів користувача (визначених за допомогою typedef) і перераховних констант у межах однієї області видимості. Всі вони, окрім імен функцій, можуть бути перевизначені у вкладених блоках;
другий клас імен утворюють імена типів перерахувань, структур, класів і об‟єднань. Кожне ім‟я має відрізнятися від імен інших типів у тій самій області видимості;
третій клас становлять елементи структури, класу й об‟єднання. Ім‟я елемента має бути унікальним усередині кожної структури, але може збігатися
зіменами елементів інших структур;
мітки операторів утворюють окремий клас імен.
Багатофайлові програми |
335 |
Після цього можна використовувати ім‟я без явного зазначання області. Якщо є потреба зробити доступними всі імена з певної області, викорис-
товується оператор using namespace: using namespace demo;
Оператори using та using namespace можна використовувати всередині іменованої області, щоб зробити в ній доступними оголошення з іншої області:
namespace Department
{using demo::i;
. . .
}
Імена, оголошені в іменованій області явно чи за допомогою оператора using мають пріоритет щодо імен, оголошених за допомогою оператора using namespace (це має значення при долученні кількох іменованих областей, які містять збіжні імена).
Короткі імена просторів імен можуть увійти у конфлікт один з одним, а довгі є непрактичні при написанні реального коду, тому дозволяється вводити синоніми імен:
namespace DIT = Department_of_Informational_Technologies;
Об‟єкти стандартної бібліотеки визначено у просторі імен std.
Механізм просторів імен разом з директивою #include забезпечує потрібну при написанні великих програм гнучкість завдяки логічному групуванню пов‟язаних величин разом з обмеженням доступу.
Здебільшого, у кожному завершеному фрагменті програми можна виокремити інтерфейсну частину (наприклад заголовки функцій, опис типів), потрібну для використання цього фрагмента, і частину реалізації, тобто допоміжні змінні, функції та інші засоби, доступ до яких зовні не потрібний. Простори імен дозволяють приховати деталі реалізації і в такий спосіб спростити структуру програми та зменшити кількість потенційних помилок. Добре помірковане розбиття програми на модулі, чітка специфікація інтерфейсів й обмеження доступу дозволяють організувати ефективну роботу над проектом групи програмістів.
Створимо багатофайловий проект у консолі, в якому проілюструємо деякі властивості простору імен.
Перший файл – заголовний; він містить деякі з елементів, які зазвичай розміщуються у заголовних файлах, – константи, визначення структур і прототипи функцій. У цьому прикладі елементи розміщено у двох просторах імен. Перший простір з ім‟ям pers містить визначення структури Person і прототипи двох функцій: перша вносить до структури ім‟я людини, а друга – відбиває вміст структури. Другий простір імен, debts, визначає структуру для зберігання імені людини і суми грошей, яку має ця людина. У цій структурі використовується структура Person, тобто простір імен debts містить директиву using, щоб зробити імена з pers доступними у просторі імен debts. Простір імен debts містить також деякі прототипи.
Багатофайлові програми |
337 |
cout<<"Enter last name: "; cin>>rp.lname;
}
void showPerson(const Person & rp) { cout<<rp.lname<<", "<<rp.fname; }
}
namespace debts
{void getDebt(Debt & rd)
{getPerson(rd.name); cout<<"Enter debt: ";
cin>>rd.amount;
}
void showDebt(const Debt & rd) { showPerson(rd.name);
cout<<": $"<<rd.amount<<endl;
}
double sumDebts(const Debt ar[], int n)
{double total=0;
for(int i=0; i<n; i++) total+=ar[i].amount; return total;
}
}
Файл Unit1.h
#include <iostream.h> #include <conio.h> #include <vcl.h> #pragma hdrstop #pragma argsused #include "namesp.h" void other();
void another(); int main()
{ using debts::Debt; // Робить доступним визначення структури Debt using debts::showDebt; // Робить доступною функцію showDebt
Debt golf={{"Benny","Goatsniff"},120.0}; showDebt(golf);
other();
another(); getch(); return 0;
}
void other()
{ using namespace debts;// Робить доступними для функції other() всі імена // з просторів debts та pers.
Person dg={"Doodles","Glister"}; showPerson(dg);
cout<<endl;
Debt zippy[3]; int i;
for(i=0; i<3; i++) getDebt(zippy[i]);
