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

Зубенко, Омельчук - Програмування. Поглиблений курс

.pdf
Скачиваний:
49
Добавлен:
07.03.2016
Размер:
4.72 Mб
Скачать

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

подібній оптимізації, указуючи компілятору, що значення змінної мо- же змінитися незалежно від програми.

Приклад 3.7. Опис змінної з кваліфікатором volatile:

volatile int contDay=7;

Кваліфікатор restrict пов'язаний із покажчиками й буде розгляну- тий у підрозд. 3.5.1.

C-файли. Складаються з прототипів і описів функцій, декларацій і описів зовнішніх змінних, а також директив препроцесора. Оператор опису функції має таку структуру:

<опис_функції>::=<заголовок_функції><тіло_функції> <заголовок_функції>::=[<клас_пам'яті>]<скалярний-тип>\ <специфікатор-функції> <скалярний_тип>::=<базовий-тип>|<тип-покажчиків>|<тип-void> <тіло_функції>::=<блок> <блок>::={<оператор>{,<оператор>}}

Опис функції складається із заголовка й тіла функції. Останнє описує дію функції. Заголовок розпочинається з класу пам'яті функції як об'єк- та (extern або static), потім описується тип значення, яке повертає фу- нкція. Це може бути один зі скалярних типів, наприклад цілий int або тип покажчиків на ціле int*. Якщо цей тип є типом void, то кажуть, що функція не повертає значення, а діє як певний оператор.

Потім іде специфікатор функції. У найпростішому варіанті це просто певний ідентифікатор ім'я функції, за яким у круглих дуж- ках іде список декларацій параметрів.

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

strlen: long strlen (const char *s) та long strlen (const char *)

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

Повний синтаксис специфікатора функцій наведено в підрозд. 3.6.1. Тіло складається з послідовності операторів (можливо порожньої), розміщеної у фігурних дужках. Така конструкція називається блоком (див. підрозд. 3.3.2). Серед операторів тіла можуть бути й оператори

опису даних, що визначають власні дані функції.

З погляду семантики функція як програмна одиниця задає алго- ритм для обчислення певної X -арної U -функції або X Y -оператора, де специфікатор скалярного типу визначає множину U значень фун- кції, а склад фреймів X та Y визначається структурою програми.

281

ПРОГРАМУВАННЯ

Дія алгоритму полягає у виконанні модифікованого тіла функції. Як саме відбувається модифікація тіла функції перед його виконанням і як обчислюється результат функції, буде сказано нижче. Наразі пого- воримо про структуру і склад X -фрейму, який обробляється тілом фу- нкції. Його складають дані й об'єкти, описані у функції й поза нею.

Опис змінних та інших об'єктів. Дані, з якими має справу про-

грама, розподіляються на проблемні та службові. Проблемні це дані, описані в програмі, над якими безпосередньо проводяться обчислен- ня, а службові це об'єкти, що використовуються для опису проблем- них даних і організації обчислень. До службових даних належать: ти- пи, теги типів, мітки операторів, макроси препроцесора, регістр ста- ну процесора РS, лічильник команд PC, параметри ОС тощо.

Системні дані, доступні в програмі й на які вона може впливати, залежать від компілятора й не описуються у стандарті мови. Усі реш- та мають опис.

Змінна визначена в підрозд. 2.1.2 як інформаційний об'єкт, що може під час інформаційного процесу набувати різних значень. У мо- вах програмування поняття змінної набуває подальшої конкретизації. По-перше, воно типізується (див. підрозд. 2.2.2), по-друге, з ним по- в'язується певний операційний (машинний) аспект. Нагадаємо, що кожній змінній програми на машинному рівні відповідає об'єкт ОП, в якому зберігається його значення. Інтерфейсні функції за іменем да- ного здійснюють доступ до цього об'єкта (його частини). Розрізняють п'ять рівнів доступу в програмах:

0відсутність доступу;

1доступ для читання (для констант);

2доступ для запису (для змінних);

3повний доступ (для читання й запису);

4повний доступ до копії даного.

Востанньому випадку доступ до самого даного блокується.

Увага! У мові C поняття змінної й константи об'єднуються в одне змінної, а константами вважаються змінні з доступом тільки для чи- тання (змінні з кваліфікатором типу const)

Фрагмент програми, в якому змінна має ненульовий рівень досту- пу, називається її областю існування в програмі.

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

Оператор опису змінних має вигляд:

282

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

<опис-змінної>::=[<клас_пам'яті>][<кваліфікатор_типу>]<тип>\ <специфікатор-імені>[<ініціатор>]\ {,<специфікатор-імені>[<ініціатор>]};

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

<специфікатор-імені>::=<прямий-специфікатор>| <специфікатор-покажчик> <прямий-специфікатор>::=<простий-специфікатор>| (<специфікатор-імені>)| <специфікатор-функції>| <специфікатор-масиву>

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

Приклад 3.8. Декларації й опис цілого масиву:

int array[]; /*декларація масиву array із цілими компонентами*/ int array[5]; /*опис масиву array з п'ятьма цілими компонентами*/

У декларації специфікатор array[] тільки засвідчив, що змінна array є цілим масивом, у той час як array[5] довершив визначення масиву array як цілого з п'ятьма компонентами ■

Детальніше про специфікатори масиву див. у підрозд. 3.4.1. Оператор опису змінної за допомогою ініціатора може надати по-

чаткове значення її об'єкту в момент зв'язування. Приклад 3.9. Ініціалізація змінних:

{

char ch='#'; int n=256;

int array[5]={10,20,30,40,50};

}

Після виконання операторів опису змінна ch набуде значення 35, змінна n значення 256, а компоненти масиву array відповідно значень 10, 20, 30, 40 та 50

283

ПРОГРАМУВАННЯ

Про специфікатори імен та ініціатори змінних будемо говорити де- тальніше при розгляді відповідних типів.

Описи змінних і функцій можуть мати досить складну внутрішню будову. Наприклад, специфікатори імен можуть містити інші (вкладе- ні) специфікатори імен тощо. Щоб спростити подібні описи й додати виразності програмам, у мові С існує оператор typedef, який дозволяє вводити синоніми для будь-яких типів. Синтаксично він збігається з описом змінної, якщо в ньому елемент <клас-пам'яті> замінити на службове слово typedef. Тоді ім'я у специфікаторі імені буде не іменем змінної, а новим іменем типу, який описується в даному операторі. Нові імена повністю замінюють типи у відповідних контекстах.

Приклад 3.10. Оператор typedef:

typedef unsigned char ascii; /*ascii – символьний тип для подання ASCII-кодів*/

typedef const char *arg; /*arg – тип константних символьних покажчиків для аргументів функцій*/

typedef double (matr[80])[2];/*matr – тип двовимірних масивів для подання прямокутних матриць розміром 80×2*/

ascii ch; /*ch : беззнакова символьна змінна*/ matr aa; /*aa : двовимірний дійсний масив*/

long strlen (arg s); /*s – константний символьний покажчик*/

double maltyply (matr x,matr y);*/x,y – двовимірні дійсні масиви*/

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

1) Кожну зовнішню змінну в програмі описувати тільки в одному файлі. У визначальному описі не вказувати специфікатор extern та ініціалізувати змінну: наприклад, сhar c2='0'.

2) У всіх інших файлах описана зовнішня змінна обов'язково декла- рується зі специфікатором extern: наприклад, extern сhar c2.

284

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

3) Якщо в зовнішньої змінної немає ініціатора, то компілятор іні- ціалізує її за умовчанням зі значенням 0.

Приклад 3.11. Функції та змінні в різних файлах програми.

Перший файл:

Другий файл:

int i=1, j=1;

extern int i,j;

char c='a';

static char c='b';

void int f1(void);

void int f1(void)

void int f2(void);

 

{i=j+2;}

int main(void)

void int f2(void)

{

f1();

{j=j+c;}

f2();

 

}

 

 

 

Зовнішні імена функцій f1 та f2 декларуються в першому файлі й описуються в другому. У другому файлі специфікатор extern декла- рує змінні i та j як зовнішні, а визначальним є опис їх у першому файлі. На етапі компонування об'єктних модулів після роздільної ком- піляції файлів до функцій і змінних буде застосоване зовнішнє зв'язу- вання вони будуть зв'язані з об'єктами своїх тезок, описаними у ві- дповідних файлах. Специфікатор static оголошує символьну змінну с у другому файлі як статичну й обмежує її область існування тільки цим файлом. Це означає, що зовнішня змінна с у першому файлі й дана статична змінна с різні й до них буде застосоване внутрішнє зв'язування ■

Глобальні й локальні дані. Х-фрейм даних, що обробляється фун- кцією, складають: 1) параметри функції; 2) змінні й константи, опи- сані в тілі функції; 3) глобальні дані (тобто описані за межами функ- ції), які в тілі функції мають непорожню область існування.

Дані 1)–2) називаються локальними. Локальні змінні є автоматич- ними, тобто мають клас пам'яті auto і зберігаються в автоматичній пам'яті (на стеку) або в купі. Їхня область існування блок, в якому вони декларовані або описані. В іншому блоці може визначатися нова змінна з таким самим іменем. Зазвичай локальні змінні оголошуються на початку блока, одразу після фігурної дужки, але за С99 їх можна оголошувати й усередині блока 25.

25 У стандарті С89 усі локальні змінні мають бути описані обовязково на початку блока.

285

ПРОГРАМУВАННЯ

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

Приклад 3.12. Дані різних рівнів:

{int n,m; /*змінні першого рівня*/

{char ch; /*змінна другого рівня – родич змінних n,m*/ int n; /*змінна другого рівня – родич змінних n,m*/

}

{int k; /*змінна другого рівня – родич n,m, але не родич змінної сh*/

}

}

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

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

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

Приклад 3.13. Область існування змінних у програмі.

Літери і' , ‘c' та ‘f', що розпочинають ідентифікатори, указують на тип змінної (відповідно int, char, float).

Лістинг variable1.c:

286

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

1: #include <stdio.h> 2:

3: int Var=100; 4:

5:void Func1(float fX, int iX)

6:{

7:

int i=1, j=10;

/*локальні змінні*/

8:

 

 

9:printf("\ni=%d", i);

10:printf("\nj*Var=%d\niX+fX=%f", j*Var, iX+fX);

11:}

12:

 

/*iX – формальний параметр*/

13: void Func2(int iX)

14: {

int i;

 

15:

 

16:

 

 

17:printf("\nInput i:");

18:

scanf("%d", &i);

/*введення i*/

19:

 

 

20:if(i==1)

21:{

22:char cS=‘a';

23:int i=10;

24:printf("\ncS=%c\ni=%d", cS, i);

25:}

26:

27:printf("\ni=%d\niX*i=%d", i, iX*i);

28:}

29:int main(void)

30:{

31:int iX=10;

32:

33:Func1(2.5, iX);

34:Func2(Var);

36:return 0;

37:}

Урядку 3 описано й проініціалізовано цілочислову глобальну змінну Var з областю існування від рядка 3 до рядка 37.

Урядку 5 описана функція Func1, що містить формальні параметри fX та iX. Формальні параметри існують у межах функції (від рядка 5 до 11).

У7-му рядку описані цілочислові локальні зміні i та j функції Func1, що існують у межах тіла функції (від рядка 6 до 11). Змінній j присвоєне початкове значення 10.

287

ПРОГРАМУВАННЯ

У рядку 10 окрім локальних змінних використовується й глобальна змінна Var.

Рядок 13 містить опис функції Func2 із формальним параметром iX і областю існування рядками 13-28. Зверніть увагу, що параметр iX присутній в обох функціях Func1 та Func2, але це різні змінні.

У15-му рядку описана локальна змінна i. Область її існування ті- ло функції Func2. Змінна i також присутня у функції Func1, але це різні змінні.

Урядку 22 описується локальна змінна cS, яка демонструє можли-

вість опису змінних усередині блока. Область існування цієї змінної блок (рядки 22-25).

У рядку 23 описується локальна змінна i з областю існування ря- дками 23-25. Глобальна змінна з таким іменем уже існує у функції Func2, тому з її області існування (рядки 15-28) вилучаються рядки 23-25, що складають область існування однойменної змінної, яка її перекриває ■

Глобальна змінна може бути використана в будь-якому блоці програ- ми. Визначати глобальні змінні рекомендується у верхній частині про- грами. При цьому слід ураховувати, що програма може складатися з кількох одиниць трансляції, а статичні зовнішні дані (з класом пам'яті static) доступні тільки в межах файла, де вони описані. Щоб зберегти значення локальної змінної між викликами функції, її потрібно описати з класом пам'яті static. Статична змінна після зв'язування з полем в ОП не змінює своєї адреси до кінця роботи програми.

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

T func (T1 x1,…,Tn xn) {O1… Om}

опис певної функції з іменем func, де T тип її значення, який не збігається з типом void; x1,…,xn параметри з типами T1,…,Tn; O1… Om

оператори тіла функції, серед яких є обов'язково оператор(и) повер-

нення значення return e;. Нехай Y={y1,…,yк} та Z={z1,…,zl} сукуп-

ності всіх глобальних і локальних змінних функції.

Фрейм даних, на станах якого визначена функція func, складаєть- ся з її параметрів і глобальних змінних, тобто збігається із сукупністю Х={x1,…,xn, y1,…,yк}. Сформувати конкретний початковий стан X -фрейму й обчислити на ньому значення функції дозволяє виклик функції вираз вигляду

func (е1,…,еn),

288

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

де е1,…,еn вирази типів, узгоджених із типами T1,…,Tn, які назива- ються фактичними параметрами функції 26. Про узгодженість типів див. у підрозд. 3.2.4.

Нехай a1,K,an значення виразів e1,K,en , а b1,K,bk поточні

значення глобальних змінних. Тоді початковим станом X -фрейму для даного виклику функції буде стан {(x1,a1),…, (xn,an), (y1,b1),…, (yk,bk)}. Розпочинається виконання виклику функції з ініціалізації параметрів і локальних даних, під час якої параметри й локальні дані зв'язуються з певними об'єктами в динамічній пам'яті, після чого тіло функції стає модифікованим і готовим до виконання.

Результатом виклику функції є значення виразу e в операторі return e;, який здійснює вихід із функції в процесі виконання моди- фікованого тіла.

Якщо типом значення функції T є void, то її модифіковане тіло за- вершує роботу як звичайний складений оператор-блок і результатом його роботи буде оновлення значень глобальних змінних функції. Та- ким чином, у цьому випадку функція діє як X Y -оператор. Якщо звернутись до функцій f1 та f2 із прикл. 3.11, то перша діє як {j}- {i}-оператор, а друга як {j,c}-{j}-оператор.

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

Приклад 3.14. Функція red_func має побічний ефект:

int k=10; /*зовнішня глобальна змінна к*/ int red_func()

{…

k=k+1; /* збільшення глобальної змінної k на 1*/

return 0;

}

Детальніше про виклики функцій йтиметься в підрозд. 3.3.2, про синтаксис і семантику функцій у підрозд. 3.6.

Функція main(). Як уже зазначалось, серед функцій C-програми обов'язково має бути спеціальна функція зі стандартним іменем main(). Вона називається точкою входження у C-програму. Саме з неї

26У літературі вираз виклик функції іноді називають покажчиком функції. Ми не будемо застосовувати цю практику.

27В Алгол-60 такі функції називалися червоними.

289

ПРОГРАМУВАННЯ

розпочинається й нею закінчується виконання програм. Найменша C-програма не є винятком вона також містить функцію main:

/*Найменша C-програма*/ int main()

{}

Дія програми зводиться до виконання порожнього тіла (оператора) функції.

Прототип функції main без параметрів має вигляд

int main([void]);

Функція main() повертає ціле число процесу, який ініціював її ви- клик. Зазвичай таким процесом є операційна система. Якщо функція main() нe повертає значення явно (тобто оператором return), то про- цес отримує формально невизначене значення. Більшість компілято- рів C у цьому випадку передбачають повернення 0.

Функція maіn може мати також параметри (див. підрозд. 3.6.5).

*Література для CР: стандарти C89 – [55]; C99 – [133, 136];

структура С-програм – [55, 133, 136].

Контрольні запитання та вправи

1.Які символи називають вхідними та як вони класифікуються?

2.Що таке широкі символи?

3.Дати визначення основних класів лексем мови C.

4.Провести лексичний аналіз рядків:

а) "string ""FFF""" \r б) "\"F\xFF\?""" \n

в) "word 07ff\125 \?"\n г) abs \x25\n\XFF

д) \t\t C 1982 Denis Ritchi \n

е) x+=a+b;printf("\vlength=%d\n", sqr(x));\n є) _int\t\t\tx,y;

ж) _int_main(void){} з) x==(a+++b*2)

и) q=a->b->p--;

5.Провести лексичний аналіз опису функцій: а) cube_sum, sum_cube та main (див. прикл. 3.5); б) Func1 та Func2 (див.

прикл. 3.13).

6.Який рядок поверне препроцесор після обробки такого фра- гмента C-програми:

290