Зубенко, Омельчук - Програмування. Поглиблений курс
.pdf
|
|
|
|
|
Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++ |
||||||||
|
|
|
Таблиця 3.1. Ключові слова мови C (С99) |
|
|
|
|
||||||
|
auto |
|
double |
|
int |
|
struct |
|
break |
|
else |
|
|
|
|
|
|
|
|
|
|||||||
|
register |
|
typedef |
|
char |
|
extern |
|
return |
|
void |
|
|
|
unsigned |
|
default |
|
for |
|
signed |
|
union |
|
do |
|
|
|
volatile |
|
continue |
|
enum |
|
short |
|
while |
|
static |
|
|
|
_Bool |
|
_Imaginary |
|
restrict |
|
_Complex |
|
inline |
|
long |
|
|
|
const |
|
switch |
|
float |
|
sizeof |
|
goto |
|
case |
|
|
|
if |
|
|
|
|
|
|
|
|
|
|
|
|
Константи. У C виділяють чотири типи констант: цілі, константи з рухомою точкою, символьні й літерали.
Увага! Розпочинаючи з визначення символьних констант і далі, для опису синтаксичних конструкцій будемо користуватися розширеними БНФ (див. підрозд. 1.4.4). Для перенесення продовження конструкції на наступний рядок будемо використовувати символ бек-слеш \ ►
Символьні константи формуються з літер алфавіту й записуються за допомогою апострофа:
<символьна-константа>::=‘<вхідний-символ-кодової-таблиці>' | ‘<керівна-послідовність>' | L‘<широка-символьна-константа>'
Керівні послідовності наведено в табл. 3.2. Вони подають деякі ке- рівні символи, а також дозволяють задавати всі літери кодової табли- ці за допомогою їхніх числових кодів (вісімкових і шістнадцяткових). При цьому довжина вісімкової послідовності обмежена трьома літе- рами, а шістнадцяткової – довільна. С99 забороняє всі коди, що ви- ходять за межі беззнакового символьного типу (unsigned char).
Таким чином, щоб отримати символьну константу, необхідно взяти в лапки потрібну вхідну літеру або керівну послідовність.
Префікс L перед символьною константою свідчить про запис широ- кої константи. Широка символьна константа зображується або за допо- могою універсального коду певної широкої літери, або послідовністю си- мволів, серед яких можуть бути й керівні, які формують один багатобай- тний символ. Спосіб такого формування залежить від реалізації мови.
Таблиця 3.2. Керівні послідовності
Керівна по- |
Значення |
Десяткова |
Шістнадцят- |
Символьна |
|
слідовність |
|
|
кова |
|
|
\a |
Дзвоник |
7 |
0x07 |
BEL |
|
\b |
Крок_назад |
8 |
0x08 |
BS |
|
\t |
Горизонталь- |
9 |
0x09 |
HT |
|
|
на_табуляція |
|
|
|
271
ПРОГРАМУВАННЯ
Закінчення табл. 3.2.
Керівна по- |
Значення |
Десяткова |
Шістнадцят- |
Символьна |
|
слідовність |
|
|
кова |
|
|
\n |
Новий_рядок |
10 |
0x0a |
LF |
|
\v |
Вертикаль- |
11 |
0x0b |
VT |
|
\r |
на_табуляція |
|
|
CR |
|
Повернен- |
13 |
0x0d |
|||
\f |
ня_каретки |
|
|
FF |
|
Нова_сторінка |
12 |
0x0c |
|||
\" |
Подвійна_лапка |
34 |
0x22 |
" |
|
\' |
Апостроф |
39 |
0x27 |
‘ |
|
\0 |
Нуль-символ |
0 |
0x00 |
|
|
\\ |
Бек-слеш |
92 |
0x5c |
\ |
|
\? |
Знак_питання |
63 |
0x3f |
? |
|
\ццц8 |
Символ із вісімко- |
ццц(8) |
( ццц(8) ) (16) |
Ідеограма |
|
|
вим кодом ццц(8) |
|
|
символу |
|
\xц…ц16 |
Символ із шістнадця- |
ц…ц(16) |
ц…ц16 |
Ідеограма |
|
|
тковим кодом ц…ц(16) |
|
|
символу |
Приклад 3.2. Символьні константи:
1)символи в апострофах: ‘a', ‘А' , ‘/', ‘9';
2)вісімкові й шістнадцяткові коди: ‘\007', ‘\x7' – символ Дзвоник;
3)у вхідному рядку \0007 препроцесор розпізнає два символи: нуль-символ і ‘7', а в рядку abc\025\xFF 125 – дев'ять cимволів – ‘a',
‘b', ‘c', ‘\025', ‘\xFF',‘ ‘,'1', ‘2', ‘5' ■
Літерали – це послідовність літер, розміщених у подвійних лапках: "…" (не плутати символ подвійних лапок із двома апострофами). Підкре- слюємо, що до складу літерала входять саме літери, а не символьні конс- танти в апострофах. Наприклад, "факультет кібернетики". Лапки не входять до складу літерала. Для включення в літерал подвійних лапок і літер \ та LF використовуються керівні послідовності – відповідно \",\\ та \n. Наприклад, "\"Гайдамаки\"\n" – правильний літерал.
Зберігаються літерали у статичній пам'яті. Якщо n – довжина літе- рала, то для нього буде виділено n +1 байтів пам'яті – по одному для коду кожної літери й додаткового символу ‘\0' із кодом 0. Останній додається обов'язково – як ознака закінчення поля літерала.
Приклад 3.3. Літерали в пам'яті
Літерал |
|
|
|
|
Подання в пам'яті |
|
|
|
|
||||
"" |
0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
"UEFA- |
85 |
69 |
70 |
65 |
|
45 |
50 |
48 |
|
49 |
50 |
10 |
0 |
2012\n" |
|
|
|
|
|
|
|
|
|
|
|
|
|
"25/*int*/" |
50 |
53 |
47 |
42 |
|
105 |
110 |
116 |
|
42 |
47 |
0 |
|
"\"Дніпро\"" |
34 |
E4 |
ED |
B3 |
|
EF |
F0 |
EE |
|
34 |
0 |
|
|
272
Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++
Упершому рядку подано порожній літерал. Довжина його нульова,
адовжина поля в пам'яті становить 1. У другому рядку літерал міс- тить керівний символ LF – новий_рядок із кодом 10, у четвертому –
усередині лапки " ■ Операції й роздільники. Лексеми операцій і пунктуатори наведе-
ні в табл. 3.3.
Таблиця 3.3. Лексеми операцій і пунктуатори
Підклас лексем |
Лексеми |
Прості операції |
!|%|^|&|*|-|+|=|~|||.|,|<|>|/|?|, |
Cкладені присвоювання |
+=|-=|*=|/=|%=|<<=|>>=|&=|^=||= |
Cкладені операції |
->|++|--|<<|>>|<=|>=|==|!=|&& |
Пунктуатори |
(|)|[|]|{|}|;|:|#|'|"|\ |
Коментар – це довільна послідовність символів, розташована між парами символів /* та */, що не містить усередині іншої пари */. У стандарті С99 коментарями є також підрядки, що розпочинаються двома символами слеш // і закінчуються символом LF. Коментарі не розпізнаються як лексеми в символьних константах і літералах (див. прикл. 3.3), не задають жодних дій і замінюються препроцесором на один пробіл. Їхнє основне призначення – пояснити в тексті програми окремі її конструкції (див. прикл. 3.5).
Увага! У програмах будь-які сусідні константи, ідентифікатори та службові слова мають розділятися одним або кількома порожніми си- мволами. Пробіли між сусідніми лексемами необов'язкові, якщо од- нією з них є роздільник або символ операції ►
3.1.3. СТРУКТУРА ПРОГРАМ
Основними елементами структури С-програм є функції й директиви препроцесора. Функції здійснюють обробку даних у С-програмах, а пре- процесор готує програму до компіляції, зокрема здійснює синтаксичні перетворення тексту програми згідно з директивами препроцесора.
C-програма – це послідовність функцій, директив препроцесора й операторів опису й декларації даних, яка обов'язково містить функцію з іменем main.
Підключення файлів. Кожна директива розпочинається літерою #. Щоб не писати в програмах кожний раз деякі стандартні оголо- шення, їх можна розмістити один раз в окремих заголовних файлах і підключати (вставляти) за необхідності у C-файли під час препроце-
273
ПРОГРАМУВАННЯ
сування. Це, окрім інших переваг, сприяє скороченню текстів про- грам. Заголовні файли можуть самі використовувати інші заголовні файли. Подібні підключення здійснює директива #іnclude:
<директива_підключення>::= #іnclude \<file1\>| #іnclude "file2" 23
Файли в обох варіантах директиви відрізняються лише місцем їх- нього розташування. Перший файл file1 препроцесор буде шукати тільки у стандартних бібліотеках, наприклад у папці INCLUDE, а файл file2 – там само, але після того, як його не буде знайдено в поточно- му каталозі. Зазвичай це файли, підготовлені самим програмістом. Дія директиви полягає у вилученні самої директиви з тексту програ- ми і вставленні (підключенні) на її місце всього вмісту текстових фай-
лів file1 або file2.
Препроцесорні константи. Препроцесор дозволяє вводити в
текст програм власні (препроцесорні ) константи:
#define <ІМ'Я-КОНСТАНТИ> <слово>
Ім'я та слово обов'язково мають бути розділені в директиві при- наймні одним пробілом. У слові праворуч немає якоїсь спеціальної ознаки кінця. Щоб виділити імена препроцесорних констант у тексті програм, їх краще писати великими літерами. Директива наказує препроцесору замінити всі наступні входження в програмі ідентифі- катора константи на відповідне слово.
Приклад 3.4. Наведені директиви підключають до програми заголов- ний файл <stdio.h>, що підтримує стандартні функції консольного вве- дення-виведення, і визначають препроцесорні константи N та ALPHA:
# include <stdio.h> /*необхідний для забезпечення в/в*/ #define N 10000
#define N=10000 /*невірна директива: між N та 10000 відсутній пробіл*/
#define ALPHA "abcdefgheijklmnopqrstuvwxyz" ■
Роздільна компіляція С-програми. Складові елементи C-програми розміщуються в текстових файлах, що належать певній одиниці компі- ляції. Окремий файл може містити кілька елементів із набору, а вся про- грама – складатися з кількох одиниць компіляції (роздільна компіляція).
23 У формулі праворуч літери ‘<’ ,‘>’ є термінальними, а ліворуч – метасимволами.
274
Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++
Кожна з одиниць компілюється окремо й об'єднується в один образ за- дачі вже на етапі компонування програми.
Файли C-програм бувають двох типів: заголовні, або h-файли (ма- ють розширення .h), і C-файли (мають розширення .c). Імена С++- файлів мають розширення .cpp, .cxx або .cc.
Щоб отримати уявлення про C-програму, розглянемо приклад про- стої програми з кількома одиницями компіляції, а потім повернемося до загальної структури програм.
Приклад 3.5. Роздільна компіляція. Нехай необхідно створити про- граму prog, що знаходить куб суми й суму кубів двох введених із кла- віатури чисел і виводить їх на екран.
Програма складається з кількох файлів. У першому – funcs.c – роз- міщуються описи функцій для обчислення куба суми й суми кубів двох чисел, у другому – start.c – забезпечується введення двох чисел, знаходження від них куба суми й суми кубів і виведення їх на екран.
Зауважимо, що у С-програмах діє правило: першій появі виклику будь-якої функції (у тому числі й стандартної) має передувати її про- тотип – заголовок із символом ‘;' у кінці. Прототипи стандартних фун- кцій розташовані в заголовних файлах, прототипи решти – безпосе- редньо в текстах С-програм. Щоб прототип потрібної стандартної фу- нкції потрапив у текст С-програми, необхідно до неї підключити від- повідний заголовний файл. Таке підключення здійснюється директи- вою препроцесора #include і зводиться до текстової заміни директи- ви на текст заголовного файла, що підключається.
Лістинг funcs.c:
#include <math.h> /*підключення заголовного файла math.h, що містить прототип стандартної функції pow для обчислення степеня*/24
/*опис функції обчислення куба суми x та y*/ int cube_sum(int x, int y)
{
return pow(x+y,3);
}
/*опис функції обчислення суми кубів x та y*/ int sum_cube(int x, int y)
{
return pow(x,3)+pow(y,3);
}
24 pow(x,y)= xy .
275
ПРОГРАМУВАННЯ
Файл start.c містить основну частину програми, в якій вводяться з клавіатури два дійсні числа й за допомогою функцій cube_sum і sum_cube знаходяться потрібні числа, які, у свою чергу, виводяться на екран. Для того, щоб програмно об'єднати файли funcs.c та start.c, в останній вводять прототипи функцій cube_sum та sum_cube з інформацією про те, що їхні описи містяться в іншому файлі (кваліфікатор extern).
Лістинг start.c:
#include <stdio.h> /*підключення файла stdio.h із прототипами стандартних функцій введення scanf і виведення printf*/
/*прототипи зовнішніх функцій cube_sum та sum_cube*/ extern int cube_sum(int x, int y);
extern int sum_cube(int x, int y);
/*maim – головна функція*/ int main()
{int x,y;
/*читання з клавіатури значень і присвоювання їх змінним x,y*/ scanf("%d%d", &x,&y); /*про функцію scanf див. підрозд.
3.8.2*/
/*виведення |
на екран значень |
функцій |
cube_sum(x,y) та |
sum_cube(x,y)*/ |
cube_sum=%d\nsum_cube=%d", |
cube_sum(x,y), |
|
printf("\n |
|||
sum_cube(x,y)); /*про функцію printf |
див. підрозд. 3.8.2*/ |
||
} |
|
|
|
Програма складається з двох одиниць компіляції. Перша – файл funcs.c, друга – файл start.c. Можна було б вибрати простішу структу- ру програми й одразу описати функції у файлі start.c та отримати про- граму у вигляді однієї одиниці компіляції. Однак запропонована струк- тура гнучкіша. Вона дозволяє мінімальними зусиллями модифікувати програму, не змінюючи її загальної структури, а за необхідності додати нові функції для обробки чисел або замінити існуючі на інші.
У системі UNІX для препроцесування, компілювання, компонуван- ня й виконання програми prog потрібно видати дві команди мовного процесора:
%cc –o prog func.c start.c
%prog
276
Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++
Перша команда компілює й компонує два вхідні файли в образ за- дачі з іменем prog, друга – завантажує програму prog і виконує її. Якщо ввести числа 2.0 та 3.0, то на екрані отримаємо результат:
2.0 3.0 cube_sum=125 sum_cube=35
У сучасних ОС замість явного застосування інструкцій командного процесора використовують відповідні кнопки інтегрованого середо- вища розробки IDE ■
Заголовні файли. Прикл. 3.5 показує, як заголовні файли викори- стовуються для організації взаємодії різних частин програми. Основ- на задача h-файлів – описати або попередньо задекларувати спільні для кількох одиниць трансляції (або програм) дані й функції з метою їх використання в програмі ще до повного опису.
Задають такі визначення оператори опису й декларації. Скорочено будемо називати їх описом і декларацією даних. Декларації функцій мають спеціальну назву – прототипи.
Декларації даних (функцій) надають інформацію, достатню, щоб ними можна було оперувати в програмі. Ця інформація обов'язково містить ім'я даних (не стосується параметрів у заголовках функцій), клас пам'яті й тип значень. Інформація про тип, зокрема, визначає можливості доступу до даних і коло операцій над їхніми значеннями. При цьому може залишатися відкритим питання про розмір об'єктів типу, а у випадку функцій – про конкретну дію функції тощо.
Увага! Оператори декларації не пов'язують з іменем конкретний об'єкт у ОП ►
Загальний вигляд оператора декларації:
<оператор-декларації>::=[<клас_пам'яті>] [<кваліфікатор_типу>] <тип>\
<специфікатор-імені>{,<специфікатор-імені>}; <клас_пам'яті>::=extern| static|auto|register <кваліфікатор_типу>::=const | volatile | restrict <тип>::=<специфікатор_типу>
Специфікатори імені подають імена змінних (функцій) і додаткову інформацію про їхній тип:
<специфікатор-імені>::=<прямий-специфікатор> | <специфікатор-покажчик>
277
ПРОГРАМУВАННЯ
<прямий-специфікатор>::=<простий-специфікатор>| (<специфікатор-імені>) | <неповний-специфікатор-масиву>| <cпецифікатор-функції>
Прості специфікатори використовують для декларації тих змінних, специфікатори типу яких повністю визначають тип: це стосується арифметичних і зліченних типів, структур і об'єднань, типу void. Роз- глянемо приклад:
int n;
struct S {int counter; float a} x;
В обох випадках специфікатори цілого типу int і типу структури struct S {int counter; float a} повністю визначають тип змінних, тому для декларації відповідних змінних достатньо простих описува- чів – ідентифікаторів n та x. Конкретна будова специфікаторів змін- них залежить від типу змінних і буде розглядатися окремо для кожно- го з відповідних типів.
Специфікатори типу надають інформацію про тип змінної. Як уже зазначалося, ця інформація може доповнюватися специфікато- ром змінної:
<специфікатор_типу>::=<специфікатор-зліченного-типу> <специфікатор_цілого_типу> <специфікатор_дійсного_типу> <специфікатор_типу_структур> <специфікатор_типу_об'єднання> <специфікатор_імені_typedef> <специфікатор_типу_void>
Специфікатори типу розглядатимемо в підрозділах, присвячених конкретним типам.
Мова C є мовою із сильною типізацією – це означає, що перед ви- користанням змінна в програмі має бути принаймні задекларована.
Увага! Діє правило:
Кожна константа, змінна чи функція програми:
1)мають бути описані оператором опису;
2)якщо вони не описані перед своїм першим застосуванням у тек- сті програми, то обов'язково мають бути перед цим задекларовані ►
278
Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++
Перший пункт обумовлений тим, що тільки при описі даного чи функції під них фактично виділяється пам'ять, тобто вони зв'язують- ся з реальним об'єктом в ОП і розпочинають діяти інтерфейсні функ- ції доступу до них.
Клас пам'яті й час існування даних. Компілятор, аналізуючи ін-
формацію про програмні дані, розподіляє їх за класами пам'яті. Ста- ндарт C містить чотири класи пам'яті:
•зовнішня (extern);
•статична (static);
•автоматична (auto);
•регістрова (register).
Зовнішні дані розміщуються в глобальній пам'яті й доступні в усіх функціях програми протягом усього часу її виконання. Це означає також, що до них буде застосовано зовнішнє зв'язування, тобто на етапі компонування в різних об'єктних модулях однойменні дані бу- дуть зв'язані з одним об'єктом. Це стосується насамперед функцій, які за умовчанням мають клас пам'яті extern.
З поняттям даного пов'язане таке поняття, як час існування в про- цесі виконання програми. Час існування даного – це час існування в ОП об'єкта, що зберігає значення даного, тобто відрізки часу з момен- ту створення об'єкта в ОП до його руйнації.
Автоматичні дані розміщуються на стеку під час виклику функції й доступні тільки всередині неї, де вони описані. Після завершення виклику доступ до них припиняється. Тому час існування автоматич- них даних – виклики функцій.
Регістрові змінні розміщуються на регістрах.
Статичні дані розміщуються в купі один раз і не змінюють свою адресу до кінця виконання програми. Якщо вони описані в тілі фун- кції, то доступні тільки всередині функції та зберігають свою адресу й значення між викликами функції.
Час існування зовнішніх і статичних даних – весь час виконання програми. У C дозволяється також створювати й знищувати об'єкти в процесі виконання програми програмними засобами. Час існування таких об'єктів визначається в ручному режимі й регулюється програ- містом. Подібні об'єкти зберігаються в купі, іменуються за допомогою покажчиків і називаються динамічними.
У програмах можуть бути також присутні об'єкти, що існують тіль- ки в процесі їхньої компіляції. До них належать типи, що визначені специфікатором типу typedef, теги типів і порядкові типи.
279
ПРОГРАМУВАННЯ
В оголошенні може бути задано не більше одного специфікатора класу пам'яті. Якщо в оголошенні відсутній специфікатор класу па- м'яті, то спрацьовує правило умовчання (табл. 3.4), яке враховує кон- текст декларації.
Таблиця 3.4. Правило умовчання для класів пам'яті
Розташування |
Вид |
Клас пам'яті за умовчанням |
Верхній рівень |
Усі |
Extern |
Заголовок функції |
Усі |
Відсутній |
Усередині блока |
Функції |
Extern |
Усередині блока |
Решта |
Auto |
Кваліфікатори типу. Впливають на засоби доступу до змінної. Стандарт С89 визначає два кваліфікатори: const та volatile. У C99 з'являється третій – restrict. Класифікатор типу передує специфіка- тору типу у визначенні даного.
Змінні з кваліфікатором const доступні тільки для читання. Класифі- катор const часто використовується, наприклад, для того, щоб запобігти модифікації функцією об'єкта, з яким зв'язується параметр функції в процесі її виклику. Без нього функція може змінити цей об'єкт.
Приклад 3.6. Опис константи й декларації параметра з класифіка- тором const:
const int a=10; /*ціла константа зі значенням 10*/
сhar *strcat (сhar *s1, const сhar *s2); /*символьний параметр
– константа*/
Дія функції strcat полягає в додаванні до рядка символів першого аргументу символів рядка другого аргументу. Зрозуміло, що перший рядок мусить змінитися, а от кваліфікатор const перед другим гаран- тує збереження його значення на виході з функції ■
Значення змінної з кваліфікатором const не може змінюватися в програмі, але може змінюватися внаслідок зовнішнього впливу. На- приклад, адресу глобальної змінної-константи можна передати у фу- нкцію ОС, що стежить за часом, і тоді змінна буде відображати сис- темний час. У цьому випадку її значення буде змінюватися примусо- во без участі операторів програми.
Такі подробиці важливі для компіляторів, тому що більшість із них автоматично оптимізує вирази, які не змінюють свої значення. Якщо у виразі присутня змінна, що не змінюється явно операціями при- своювання, проте змінюється поза програмою, то така оптимізація коду може виявитись некоректною. Кваліфікатор volatile запобігає
280