Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Posibnyk_C_sum.doc
Скачиваний:
9
Добавлен:
29.08.2019
Размер:
1.63 Mб
Скачать

7 Складні типи даних

7.1 Масиви та вказівники

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

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

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

Так же, як і прості, дані складних типів підлягають оголошенню в програмі. Ознакою масиву пiд час його оголошення є квадратнi дужки, якi ставляться пiсля iдентифiкатора масиву, в дужках записується кiлькiсть елементiв масиву. Мiнiмальне значення iндекса (порядкового номера) елемента масиву завжди дорiвнює нулю. Це відрізняє С від інших мов програмування таких, наприклад, як Паскаль, Бейсик, де початковий номер елемента можна змінювати. Пiд час оголошення масив може бути iнiцiалiзований. Наведемо декілька прикладів.

char h[]="Система", t[25], s[9]="Комп'ютер";

float a[20], b[5][3];

float c[2][3]={{2, 3, 7}, {6, , 1.2}}; або

float c[2][3]={2, 3, 7, 6, 1e-4, 1.2}; – задані всі елементи.

Тут для масиву h буде зарезервовано в пам’ятi мiсце для зберiгання 8 букв типу char – на одну більше, ніж це потрібно для слова Система, в цьому випадку компiлятор автоматично додасть до масиву нуль '\0' – машинний нуль. Під час операцій вводу-виводу та деяких інших компілятор не підраховує кількість елементів масиву, а використовує цей нуль, як ознаку його кінця. Масив h можна було оголосити інакше так: char h[8]; та ініціалізувати шляхом присвоєння: h[0]='С'; h[1]='и'; h[2]='с'; ... h[6]='а';

Масиви t i s займуть вiдповiдно 25 i 9 комiрок пам’ятi типу char. Подібно до масиву h, вектор s буде iнiцiалiзований, проте вiн не буде доповнений нульовим елементом, оскільки під час оголошення для нього не було зарезервовано місце – всі 9 елементів будуть зайнятими буквами слова Комп'ютер. В будь-якому місці програми машинний нуль можна примусово встановити наприкінці масиву s, наприклад, так: s[8]='\0';, але тоді його остання буква р буде стертою.

Особливо треба наголосити на тому, що під час оголошення для масиву s відведено 9 комірок типу char, а порядковий номер його останнього індекса дорівнює лише 8, оскільки нумерація елементів починається з 0. Неврахування цього часто спричиняє виникнення помилок у програмі.

Для масиву a буде видiлено 20, масиву b – 15, а масиву c – 6 комiрок пам’ятi типу float. Пiд час оголошення вiдбудеться iнiцiалiзацiя масиву c п’ятьма елементами, тобто c[0][0]=2, c[0][1]=3, c[0][2]=7, c[1][0]=6 та c[1][2]=1.2. Елемент c[1][1] не буде ініціалізованим, але йому буде зарезервовано відповідне місце. Зауважимо лише, що не всі компілятори забезпечують можливість пропуску елементів.

Рядки багатовимірного масиву розташовуються в пам’яті один за другим у порядку оголошення.

Звернення до елемента масиву в програмi вiдбувається шляхом вказування iдентифiкатора масиву, пiсля якого в квадратних дужках задаються значення всiх його iндексiв за допомогою констант або виразiв цiлого типу. Зрозуміло, що у випадку використання виразів усі значення його змінних повинні бути задані до їх застосування. Наприклад, якщо маємо звертання до елемента масиву a[2*i+1], то спочатку повинно бути задано значення змінної i.

Використання масивів у програмі покажемо на прикладі обчислення суми рядків цілочислової матриці m порядку 3×4. Результати обчислень представимо у вигляді вектора p. Нехай матриця m має такий вигляд:

Алгоритм розв’зування поставленої тут задачі показаний на рисунку 7.1. Він являє собою два вкладені цикли з накопиченням суми.

Зовнішній цикл (блоки 2 – 6) служить для перебору рядків матриці m, його параметр i змінюється від 0 до 2 – за кількістю рядків. У ньому відбувається присвоєння черговому елементу pi нульового значення елемента i-го рядка, тобто pi=mi0 (блок 3), виконання внутрішнього циклу (блоки 4–5) та вивід чергового значення pi (блок 6). У внутрішньому циклі з параметром j – переглядаються стовпчики від 1-го до 3-го та накопичується сума pi. Його параметр змінюється від 1 до 3, а не від 0, нульовий елемент враховується перед цим циклом.

Він реалізований програмою прикладу 7.1.1, її ідентифікатори називаються так же, як і в графічному алгоритмі. На початку програми оголошені дві змінні: i та j, які служать параметрами циклів, а також матриця m і вектор p. Всі вони мають цілий тип, оскільки елементи заданої матриці – цілі числа, а змінні i та j використовуються ще й як індекси елементів.

Приклад 7.1.1 – Обробка двовимірного масиву

#include<stdio.h> /* masyv */

int main(void)

{

int i, j, p[3], m[3][4]={{2, 3, 1, 6},

{2, 2, 1, 2},

{3, 2, 3, 3}};

for(i=0;i<3;i++)

{

p[i]=m[i][0];

for(j=1; j<4; j++)p[i]+=m[i][j];

printf("p(%d)=%2d\n", i, p[i]);

}

getch();

return(0);

}

Результати, видані програмою, матимуть такий вигляд:

p(0)=12

p(1)= 7

p(2)=11

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

У цій програмі матрицю m ініціалізовано під час оголошення. Її елементи можна було вводити й під час виконання програми за допомогою такої конструкції:

for(i=0; i<3; i++)for(j=0; j<4; j++)scanf("%d", &m[i][j]);

Якщо значення масиву не регламентовані, наприклад, якщо програма має навчальний характер подібно до прикладу 7.1.1, то можна використати функцію random(n), яка генерує цілі псевдовипадкові числа від 0 до n-1. Псевдовипадковими називають числа, які поводять себе як випадкові, але насправді не є випадковими, бо отримані програмним способом за жорстким алгоритмом. Тоді цю програму слід доповнити такими операторами:

#include<stdlib.h>

randomize();

for(i=0; i<3; i++)for(j=0; j<4; j++)m[i][j]=random(12);

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

Вказівник являє собою комірку пам’яті цілого типу, в яку можна записати адресу об’єкта. Для роботи із вказівниками використовуються операції адресації (&) та непрямої адресації (*), розглянені в розділі 2.6, де наводилися приклади їх застосування для обробки простих змінних.

Вказівники знайшли широке застосування для роботи з масивами, більше того, ідентифiкатор масиву сприймається компiлятором, як адреса його першого елемента, тому фрагменти

float m[7], *p; p=&m[0]; та

float m[7], *p; p=m; або float m[7], *p=m;

виконаються однаково. Тут p – змінна адресного типу, вона приймає значення адреси першого елемента масиву m.

Оскільки p=m, то ці змінні можна й використовувати однаково – p, як m, а m – замість p. Крім того, вказівник дозволяє накладати масиви один на інший, якщо вони розташовані поряд подібно до того, як це було зроблено з простими змінними у прикладі розділу 2.6. Програма прикладу 7.1.2 демонструє ці можливості, вона видасть усі 8 чисел масивів m та k, тобто числа: 66 77 88 11 22 33 44 55 у 4 стовпчики (4-ма різними способами).

Приклад 7.1.2 – Застосування вказівника на масив

#include<stdio.h> /* Вказівник */

int main(void)

{int m[3]={66, 77, 88}, k[5]={11, 22, 33, 44, 55}, i, *p=m;

for(i=0; i<8; i++)printf(" %d %d %d %d\n", p[i], k[i-3], *(p+i), *(m+i));

getch();

return(0);

}

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

Приклад 7.1.3 – Масиви вказівників

#include<stdio.h> /* Масиви вказівників */

int main(void)

{

int i,j,p[3],m[3][4]={{2, 3, 1, 6},

{2, 2, 1, 2},

{3, 2, 3, 3}};

for(i=0; i<3; i++)

{

*(p+i)=*m[i];

for(j=1; j<4; j++)*(p+i)+=*(m[i]+j);

printf("p(%d)=%2d\n", i, *(p+i));

}

getch();

return(0);

}

Тут ім’я масиву p застосовується як вказівник на масив p, а ім’я m[3] як масив 3-х вказівників на одновимірні масиви довжиною по 4 елементи. Порівнявши використання p і m[i], можна переконатися в тому, що в обох випадках воно однакове, різниця полягає лише в тому, що p – адреса, а m[i] – елемент масиву типу адреси (масиву адрес).

Масив вказівників програми прикладу 7.1.3 можна було й оголосити, наприклад, так: int i, j, *p, *m[3]; Але тоді неможливою стає ініціалізація масиву m під час оголошення, бо це вже не масив елементів матриці, а масив адрес, її прийшлося б виконувати за допомогою операцій присвоєння, наприклад, так: *m[0]=2; *(m[0]+1)=3; *(m[0]+2)=1; i т.д.

Аналізуючи цей приклад, стає зрозумілим чому мова С оперує лише таким поняттям, як одновимірниий масив даних, це дозволяє спростити роботу з вказівниками.

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

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

Приклад 7.1.4 – Динамічні масиви

#include<stdio.h> /* Функція malloc() */

#include<stdlib.h>

int main(void)

{int m, i, *q;

while(puts("Введіть кільк. ел. масиву ") && scanf("%d", &m))

{

q=malloc(m*sizeof(int));

printf("Адреса масиву=%p\n", q);

if(q==NULL){puts("Немає пам’яті \n"); exit(1);}

printf("Введіть %d елементи масиву: ", m);

i=0;

while(i<m && scanf("%d",&q[i])==1)++i;

printf("Введено %d елементи: ", i);

for(i=0; i<m; i++)printf("%d ", q[i]);

free(q);

}

getch();

return(0);

}

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

Введіть кільк. ел. масиву

2

Адреса масиву=094E

Введіть 2 елементи масиву:

22 33

Введено 2 елементи: 22 33

Введіть кільк. ел. масиву

3

Адреса масиву=094E

Введіть 3 елементи масиву:

44 55 66

Введено 3 елементи: 44 55 66

Введіть кільк. ел. масиву

k

Результати запуску програми при m=0.

Введіть кільк. ел. масиву

0

Адреса масиву=0000

Немає пам’яті

На початку програми за допомогою директиви #include під’єднується файл stdlib.h, що дозволяє використовувати функцію malloc(). Всередині програмного блоку оголошено три змінні типу int: m – кількість елементів масиву, i – лічильник (параметр циклу) та вказівник *q.

Програма зациклена оператором

while(puts("Введіть кільк. ел. масиву ") && scanf("%d",&m))

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

Оператор q=malloc(m*sizeof(int)); є головним у програмі, саме він виділяє пам’ять для m елементів масиву. Функція malloc() видає адресу початку блоку пам’яті, цю адресу присвоєно вказівнику q, після чого її можна використовувати для ініціалізації масиву та у виразах. З метою візуального контролю за ходом виконання цього процесу адреса виводиться на екран оператором printf("Адреса масиву=%p\n",q); Функція її виводу містить специфікатор p, він служить для виводу адрес у 16-й системі числення.

Якщо потрібна кількість пам’яті не знайдена, функція malloc() видає адресу NULL, а програма повідомляє, що Немає пам’яті, і відбувається її примусове закінчення за допомогою функції exit(1). Ця функція може приймати і передавати в операційну систему два значення параметра: 1 – у випадку помилки або 0 – програма завершилася успішно.

Два цикли, один типу while, а другий – for, вкладені в зациклений блок while, служать для вводу та виводу нового масиву q (нагадаємо, що вказівник на масив є його ім’ям).

Другим важливим складником програми є функція free(), яка звільняє виділений функцією malloc() блок і повертає його в загальний пул для якогось іншого використання. Її викликом завершується зациклений блок while.

Аналізуючи видані програмою прикладу 7.4 результати, бачимо, що цей блок був виконаний три рази. За першим разом був утворений масив довжиною 2 елементи, за другим – 3, а за третім через ввід букви k замість числа відбулося його завершення. Під час кожного з перших двох разів то відводилася пам’ять для різних масивів, то звільнялася, тобто одна й та ж область пам’яті використовувалася повторно. Про повторне використання пам’яті свідчить той факт, що обидва масиви починалися з однієї і тієї ж адреси, яка дорівнювала 094E.

Ввід значення 0 змінної m теж спричиняє видачу функцією malloc() адреси NULL і закінчення програми, про це говорять результати запуску програми при m=0.

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

7.2 – Структури та інші типи

Структурою називають сукупність даних різних типів. Вона подібна до масиву, бо являє собою сукупність даних, але відрізняється від нього тим, що до її складу можуть входити дані різних типів. Подібний тип мають інші мови програмування, зокрема Паскаль має тип record. Структуру можна назвати аналогом запису баз даних.

Мова С належить до так званих структурованих мов програмування і саме структура надала їй цю назву. Крім розглянених типів, об’єктно-орієнтовані мови програмування, зокрема мова С++, мають класи, які базуються на структурі. За типом складників клас відрізняється від неї тим, що може містити ще й функції, що надає йому ширші властивості.

Структура являє собою блок, її оголошення має такий вигляд:

struct тег{оголошення складників структури}ім’я структури;

В цьому оголошенні, крім власне структури, на ім’я якої можна посилатися в програмі, вказано ще й тег. Його можна використовувати для створення інших структур того ж типу. Тег – не структура, а лише тип структури.

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

Приклад 7.2.1 – Демонстрація структури

#include<stdio.h> /* Структура */

int main(void)

{

struct robota{char priz[15];

int rob[2];

float zarp;

unsigned :6;

unsigned b:2;}tokar={"Петренко П.П.", { 7, 64}, 45.50, 03};

int *c=tokar.rob;

printf("Пріз=%s Год=%d Кіл=%d Зар=%f b=%d\n",

tokar.priz, *c, *(c+1), tokar.zarp, tokar.b);

getch();

return(0);

}

У цій програмі оголошена та ініціалізована структура tokar, яка має тег robota. Вона містить літерний масив priz довжиною 15 символів (Петренко П.П.), масив rob[2] цілого типу, ініціалізованого числами: 7 –число відпрацьованих годин за зміну та 64 кількість виготовлених деталей, змінну zarp типу float (45.5 – денна зарплата), безіменне бітове поле довжиною 6 біт, а також іменоване поле b довжиною 2 біти, в яке записано вісімкове число 03.

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

Пріз=Петренко П.П. Год=7 Кіл= 64 Зар=45.500000 b=3

За допомогою тега robota можна створити іншу структуру того ж типу, наприклад, структуру slusar за допомогою оголошення: struct robota slusar; Структура може й не мати тега, тоді в оголошенні вказується лише її ім’я.

Подібно до даних інших типів структура може мати адресу, тоді до неї та її елементів можна звертатися за допомогою вказівника. Це звернення демонструє програма прикладу 7.2.2.

Приклад 7.2.2 – Використання адреси структури

#include<stdio.h> /* Адреса структури */

int main(void)

{

struct {char s[15];

int k;

}ss={"Петренко П.П.", 456};

struct ss *curr = &ss;

puts(" ");

printf("curr=%p s=%s k=%d\n", curr, curr->s, curr->k);

}

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

curr=FFCC s=Петренко П.П. k=456

Програма прикладу 7.2.2 починається з оголошення та ініціалізації структури ss, яка має два елементи: літерний масив s[12] і змінну k цілого типу. Далі оголошений вказівник curr типу структури ss, ініціалізований адресою структури ss. Звернення до елементів структури виконується всередині функції printf() за допомогою імені вказівника та імені елемента, розділених знаками -> (мінус-більше). Тип структури ss вказівника curr означає, що нарощення цього вказівника на одиницю спричинить його зміщення на довжину структури в байтах.

В якості елемента структури може бути використаний і вказівник на іншу структуру або подібну до неї. Нижче це показано в масиві структур у програмі прикладу 7.2.3.

Приклад 7.2.3 – Масив структур

#include<stdio.h> /* Масив структур */

struct detal{char s[12];

int k;

struct detal *dali;};

int main(void)

{

int i;

struct detal *curr;

struct detal dano[3]={{"Долото ", 123, NULL},

{"Вертлюг ", 456, NULL},

{"Вентиль ", 789, NULL}

};

puts(" ");

curr=dano;

for(i=0; i<3; i++){curr->dali=curr+1;

printf("i=%d адр.=%p s=%s k=%d далі =%p\n",

i, curr, curr->s, curr->k, curr->dali);

curr++;

}

getch();

return 0;

}

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

i=0 адр.=FFA8 s=Долото k=123 далі =FFB8

i=1 адр.=FFB8 s=Вертлюг k=456 далі =FFC8

i=2 адр.=FFC8 s=Вентиль k=789 далі =FFD8

Програма починається з оголошення тега (типу) detal структури. Він містить три такі елементи: масив s довжиною 12 байт літерного типу, ціле число k типу int і вказівник *dali (від слова далі, наступний) на структуру типу detal, тобто на таку ж структуру, яка оголошена тегом. Нагадаємо, що тип вказівника означає крок, з яким розташовуються в пам’яті елементи, на які він вказує. Сумарна довжина елементів тега дорівнює 16 байтам (1*12+2+2=16). Це число спеціально підібрано кратним 16 (шістнадцяткове 10) для вигідного перегляду адрес пам’яті, які виводяться в 16-й системі числення. Зауважимо, що під час виконання програми застосовувався компілятор з довжинами типів: char – 1 байт, int – 2 байти та комп’ютер з 2-байтовою адресацією пам’яті.

Програмний блок містить такі оголошення: змінну i типу int – допоміжна змінна (параметр циклу), вказівник *curr (від слова current – поточний) типу detal та масив структур dano[3] теж типу detal. Масив dano ініціалізований під час оголошення трьома структурами, де s[12] – назва деталі, k – ціле число, і вказівник *dali – адреса NULL. Цей нуль – формальний, він ніде не використовується в програмі, за його допомогою показано лише, що в цьому місці має бути якась адреса.

Вказівник *curr містить поточну адресу структури, тобто адресу тієї структури, що в даний момент виводиться. Вказівник *dali – наступну адресу (curr->dali=curr+1;), тобто адресу наступної структури за поточною, нею буде замінений NULL, заданий під час оголошення. У програмі відбувається звернення до елементів структури – їх вивід на екран за допомогою функції printf().

Подібно до масивів простих змінних ім’я масиву структур можна використовувати як адресу першого елемента масиву структур, тобто адресу початку цього масиву. Нижче показано фрагмент програми – варіант циклу for, де використано ім’я масиву замість його адреси. Він видасть ті ж результати.

for(i=0; i<3; i++){(dano+i)->dali=dano+i+1;

printf("i=%d адр.=%p s=%s k=%d далі =%p\n",

i,dano+i, (dano+i)->s, (dano+i)->k, (dano+i)->dali);

}

Зауважимо, що ім’я масиву відрізняється від вказівника на масив тим, що його не можна змінювати за допомогою операції декременту. Натомість, для пересування по пам’яті його дозволено нарощувати на потрібну кількість кроків. У вищенаведеному фрагменті це ім’я нарощується на величину i. Вираз (dano+i) взятий у круглі дужки тому, що приорітет операції -> вищий за приорітет операції додавання. Якби цих дужок не було, то компілятор сприйняв би змінну i за ім’я масиву.

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

union тег{оголошення складників об’єднання}ім’я об’єднання;

Перерахунковий тип даних призначений для створення послідовності чисел. Його оголошення має такий вигляд:

enum{список перерахунку};

Пояснимо дію цього типу на такому прикладі:

enum

{

a,

b,

d,

u,

v=5,

z

};

В ньому оголошено перерахунковий тип, змінні якого приймають такі значення:

a=0,

b=1,

d=2,

u=3,

v=5,

z=6.

Значення змінної a тут дорівнює нулю, тому що нумерація завжди починається з нуля, якщо не вказано інше число. Наступні змінні: b=1, d=2 і т. д., бо значення кожної з них нарощується на 1. Можна змінити цей порядок, тобто продовжити нарощення, починаючи з іншого числа, тому, якщо v=5, то z=6, яке теж нароститься на 1.

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

enum{pn=1, wt, sr, ch, pt, sb, nd};

Тоді оператори програми: int i; for(i=pn; i<=nd; i++)printf(" %d", i); видадуть таку послідовність: 1 2 3 4 5 6 7.

Тип типу дозволяє програмісту створювати будь-який штучний тип даних на основі існуючих типів за допомогою такого оголошення:

typedef відомий тип новий тип;

Створимо, наприклад, новий тип cile на основі типу int за допомогою оголошення: typedef int cile;. Тоді його можна використовувати в програмі для оголошення змінної цілого типу: cile i=123;

Абстрактні типи даних існують лише для позначення якогось типу, не виділяючи ніякої пам’яті для нього. Одним із випадків його застосування є параметр операції sizeof(). Наприклад, функція printf("%d",sizeof(int)); видасть об’єм пам’яті в байтах, який дана версія мови С відводить для типу int.

До абстрактних типів даних належить також тег robota, використаний для створення структури slusar у прикладі 7.2.1. Зауважимо, що під час оголошення тега пам’ять для нього не виділяється.

Запитання для самоперевiрки

  1. Дайте визначення масиву.

  2. Масиви якої розмірності дозволяє обробляти мова С? Чи можна в програмах мовою С використовувати багатовимiрнi масиви?

  3. Як оголошуються масиви? Наведiть можливi приклади.

  4. Що таке iнiцiалiзацiя масиву і як вона виконується?

  5. Яка роль символа '\0' – нуль у масивах літерного типу?

  6. Скільки та в яких місцях матимемо машинних нулів після оголошення та ініціалізації багатовимірного літерного масиву?

  7. Як вiдбувається звертання до елемента масиву в програмах?

  8. Як розташовуються в пам’яті елементи масиву?

  9. Що таке вказівник та як він оголошується в програмі?

  10. Як можна ініціалізувати змінну за допомогою вказівника?

  11. Як можна звертатися до елемента масиву за допомогою вказівника на цей масив?

  12. Поясніть поняття: структура, об’єднання, дані перерахункового типу, абстрактні типи даних, тег.

  13. Чим відрізняється об’єднання від структури за призначенням? Який об’єм пам’яті займає структура? об’єднання?

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

  15. Що таке бітове поле структури і для чого воно використовується?

  16. Чи може бітове поле структури містити дійсне або від’ємне число?

  17. Як звернутися до елемента структури за допомогою її імені або адреси?

  18. Як можна оголосити структуру за допомогою тега?

8 ОБРОБКА ЛІТЕРНИХ РЯДКІВ ТА ДАТИ

8.1 – Рядки літер

Роботу з рядками символів забезпечують стандартні функції. Всі вони описані у відповідних файлах з розширенням *.h. Розглянемо деякі найбільш вживані функції.

Функції перевірки і перетворення описані в файлі ctype.h, вони такі:

  • isalpha(c) – перевіряє чи символ с є буквою;

  • isdigit(c) – перевіряє чи символ с є цифрою;

  • islower(c) – перевіряє чи символ с є малою літерою;

  • isprint(c) – перевіряє чи символ с можна видати на друк;

  • isspace(c) – перевіряє чи символ с є пробільним символом (пробіл, табуляція або новий рядок);

  • isupper(c) – перевіряє чи символ с є прописною буквою;

  • isalnum(c) – перевіряє чи символ с є алфавітноцифровим (буква або цифра);

  • isascii(c) – перевіряє чи символ с є кодом ASCII (0-127);

  • iscntrl(c) – перевіряє чи символ с є керуючим символом;

  • ispunct(c) – перевіряє чи символ с є знаком пунктуації;

  • toupper(c) – перетворює символ c у прописну букву;

  • tolower(c) – перетворює символ c у малу літеру.

Всі функції повертають число 1, тобто true, якщо результати перевірки виявилися успішними. Останні дві – перетворений символ.

Перетворення букви A з прописної у рядкову a подано в прикладі 8.1.1.

Приклад 8.1.1 – Перетворення букви A з великої на малу

# include <stdio.h>

# include <ctype.h>

main( )

{

char z, y = 'A';

z = tolower(y);

printf("y = %c z = %c\n ",y, z);

}

y = A z = a

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

  • atoi(a) – перетворення рядка a на ціле число;

  • atof(a) – перетворення рядка a на дійсне число;

  • itoa(i, s, d) – перетворення цілого числа i на рядок s. Параметр d визначає систему числення і, якщо вона десяткова, то d = 10.

Описані вони в файлі stdlib.h. Перші дві функції повертають результат перетворення, якщо воно було успішним, інакше – нуль.

Нижче подано приклад 8.1.2 перетворення двох рядків: 1234 і 12,3 на числа та цілого числа 18 – на рядок.

Приклад 8.1.2 – Символьно-числові перетворення

# include <stdio.h>

# include <stdlib.h>

main( )

{char s1[9] = "1234", s2[6] = "12.3", s3[3];

int k = 18, n;

float b;

n = atoi(s1); b = atof(s2);

itoa(k, s3, 10);

printf("n = %d b = %f s3 = %s\n", n, b, s3);

getch( );

}

n = 1234 b = 12.30 s3 = 18

Функції, що працюють із рядками, описані в файлі string.h. Вони, зокрема, такі:

  • strlen(s) – повертає довжину, кількість літер рядка s;

  • strcat(s1, s2) – додає рядок s1 в кінець рядка s2;

  • strncat(s1, s2, n) – додає n літер рядка s1 в кінець рядка s2, повертає новоутворений рядок;

  • strcmp(s1, s2) – порівнює вміст рядків s1 i s2. Результатом роботи функції є число, яке від’ємне, якщо коди літер рядка s2 менші за s1, додатнє, якщо – більші, і дорівню нулю, якщо вони рівні;

  • strncmp(s1, s2, n). Вона подібна до одноіменної, розгляненої вище, лише її параметр n визначає кількість символів, які повинні враховуватися під час порівняння;

  • strcpy(s1, s2) – копіює рядок s2 в s1, повертає рядок s1;

  • strdup(s) – дублює масив s, повертає точку входу в новий масив;

  • strrev(s) – реверсує всі символи рядка s (змінює порядок розташування літер у рядку на протилежний), повертає новоутворений рядок;

  • strchr(s, c) – перевіряє входження символа c в рядок s. Якщо символ не знайдений, то повертає NULL, інакше точку входу;

  • strrchr(s, c) – повертає частину, решту рядка s, починаючи з літери c або NULL, якщо символ c не знайдений;

  • strstr(s1, s2) – повертає входження рядка s2 в s1 або NULL, якщо входження не відбулося;

  • strupr(s) – перетворює всі літери рядка s на великі, повертає новий рядок;

  • strerror(i) – текст і-ї помилки, яка виникла під час обробки рядка.

Розглянемо приклад 8.1.3, де показано застосування декількох функцій.

Приклад 8.1.3 – Ілюстрація роботи рядкових функцій

# include<stdio.h>

# include<string.h>

int main(void)

{

char s1[5] = "ab", s2[3] = "cd";

char s3[5] = "abcd", s4[4] = "bcd";

char s5[9] = "student", c = 'd', s6[8] = "tude";

printf("1 s1+s2 = %s\n", strcat(s1, s2));

printf("2 ds3 = %d ds2 = %d\n", strlen(s3), strlen(s2));

printf("3 s1 = %s\n", strcpy(s1, s2));

printf("4 s3 = %s\n", strstr(s3, s4));

printf("5 cr = %s sr = %s\n", strchr(s5, c), strstr(s5, s6));

getch( );

return 0;

}

Програма прикладу 8.1.3 видала такі п’ять рядків результатів:

1 s1+s2 = abcd (конкатенація рядків s1 і s2)

2 ds3 = 4 ds2 = 2 (довжини рядків s3 і s2)

3 s1 = cd (копіювання рядка s2 в s1)

4 s3 = bcd (входження рядка s4 в s3)

5 cr = dent sr = tudent (входження символа c в рядок s5 та рядка s6 в s5).

Перелік помилок, які виникають під час роботи з літерними функціями, можна прочитати за допомогою програми, поданої в прикладі 8.1.4.

Приклад 8.1.4 – Видача текстів помилок

# include<stdio.h> /* Тексти помилок при обробці рядків*/

# include<string.h>

main( )

{int i;

for(i = 1; ; i++)

{printf("num = %d err = %s", i, strerror(i));

if(i % 12 == 0) getch( );

}

}

Нижче подано результати роботи функції strerror(). Кожна помилка має свій код, тут показані перші 10 кодів та відповідні їм назви.

num = 1 err = Invalid function number

num = 2 err = No such file or directory

num = 3 err = Path not found

num = 4 err = Too many open files

num = 5 err = Permission denied

num = 6 err = Bad file number

num = 7 err = Memory arena trashed

num = 8 err = Not enough memory

num = 9 err = Invalid memory block address

num = 10 err = Invalid environment

8.2 – Дата і час

Стандартна бібліотека С має багатий набір функцій для обробки дати та часу, всі вони оголошені в файлі time.h. Нижче подано опис окремих функцій.

Функція time() оголошена так:

time_t time(time_t * timer);

Вона видає поточну дату і час. Тип time_t – штучний, його визначено як арифметичний, він здатний представляти час. Це означає, що він може бути цілим типом, таким як signed або unsigned, int або long.

Функція difftime() має таке оголошення:

double difftime(time_t time1, time_t time2);

Вона обчислює інтервал у секундах між двома значеннями часу. Її використання демонструє приклад 8.2.1.

Приклад 8.2.1. – Вимірювання часових інтервалів

# include <stdio.h>

# include <stdlib.h>

# include <time.h> int main (void)

{ time_t time1= time(NULL), time2; printf("Зачекайте декілька секунд, після чого натисніть клавішу ENTER");

fflush(stdout); getchar(); time2 = time(NULL); printf("\n Інтервал часу=% .2 f секунд \n", difftime(time2, time1)); return 0;

} Тип struct_tm представляє дату і час розбитими на стандартні компоненти відповідно до Грегоріанського календара. У табл. 8.1 наведені елементи цього типу.

Таблиця 8.1 – Елементи структури struct tm

Елемент

Опис

int tm_sec

Секунди (0-59)

int tm_min

Хвилини (0-59)

int tm_hour

Години, відраховані від півночі (0-23)

int tm_mday

День місяця (1-31)

int tm_mon

Місяць року (від 0 – січень до 11 – грудень)

int tm_year

Рік, починаючи з 1900 року

int tm_wday

День тижня (від 0 – неділя до 6 – субота)

int tm_yday

День року (0 – 365)

int tm_isdst

Вказівник врахування літнього часу:

tm_isdst > 0 – літній час враховується;

tm_isdst = 0 – літній час не враховується;

tm_isdst < 0 – інформація недоступна.

Просте форматування часу забезпечують функції asctime() і ctime(), вони генерують рядок, перед яким ставиться конкретний час у фіксованому форматі, наприклад так:

Tue Feb 29 23:59:28 2000 \n

Це 23 год 59 хв 28 с, вівторок 29 лютого 2000 року. Тут \n – відомий символ переходу на новий рядок. Цей символ доводиться видаляти. Функції asctime() і ctime() оголошуються так:

char *asctime(const struct tm *timeptr);

char *ctime(const struct tm *timer);

Вони однакові, за винятком того, що asctime() отримує вказівник на об’єкт struct tm, тоді як ctime() – на time_t. Функція ctime() призначена для виведення локального часу. Звернення ctime(timer) еквівалентно зверненню asctime(localtime (timer)).

У різних частинах світу використовуються різні формати дат. Наприклад, у США основним форматом дати є мм/дд/рррр, тоді як у Великобританії – дд/мм/рррр. Формати дат і номерів тижня регламентуються міжнародним стандартом ISO 8601. Його повний формат дати має такий вигляд:

рр-мм-ддТгг:хх:сс,

де T – розділовий знак, який відділяє дату від часу.

Відповідно до стандарту ISO 8601 запис дати: 2000-02-29Т12:00:00 означає рівно опівдні 29 лютого 2000 року.

У прикладі 8.2.2 показано використання функцій localtime(), asctime() і ctime().

Приклад 8.2.2 – Приклад використання функцій часу

#include<stdio.h>

#include<time.h>

void seda(void)

{

time_t tt;

struct_tm *ptr;

tt = time(NULL); ptr = localtime(&tt); printf(asctime(ptr));

return;

}

void misc_ch(void)

{

time_t tt;

tt = time(NULL); printf(ctime(&tt));

return;

}

main( )

{

seda( );

misc_ch( );

getch( );

}

Thu Feb 03 12:07:59 2011

Одержані результати означають, що програма виконувалася в четвер 3 лютого 2011 року о 12 годині 7 хвилин 59 секунд.

Деякі компілятори мають функції getdate() і gettime(), їх використання демонструє приклад 8.2.3. Крім того, показано прийом, який дозволяє видавати дату і час у будь-якому форматі (в прикладі – державною мовою).

Приклад 8.2.3 – Застосування функцій getdate() і gettime()

# include <stdio.h> / * Системна дата і час * / # include <dos.h> main( ) {

char naz [12] [10] = {"січня", "лютого", "березня", "квітня",

"травня", "червня", "липня", "серпня",

"вересня", "жовтня", "листопада", "грудня"};  struct dat{int y; char d; char m;};  struct tim{ char m; char o; char h; char s;};  struct dat *dp;  struct tim *dt;  clrscr( );

gettime(dt);

getdate(dp);  printf("Програма виконана о %d год %d хв %d сек\n", dt->o, dt->m, dt->s);  printf("Була дата: %d %s %d-го року\ n", dp->d, naz[dp->m-1], dp->y);  getch ( ); }

Програма виконана o 13 год 22 хв 8 cek

Була дата: 3 лютого 2011-го року

Визначення часу виконання програми (часу роботи центрального процесора – CPU) забезпечує функція clock(). Вона оголошується так:

clock_t clock (void);

Файл time.h додатково визначає також тип clock_t і макрос CLOCKS_PER_SEC. Змінна clock_t часто є 32-бітовою. Це дозволяє мати максимальне значення, яке дорівнює 2147483647.

Функція clock() повертає значення, яке, будучи поділеним на CLOCKS_PER_SEC, дорівнює часу в секундах. Іншими словами: для конвертації інтервалу між двома викликами функції clock() в секунди потрібно поділити різницю на число CLOCKS_PER_SEC. Значення цього макроса залежить від технічних характеристик комп’ютера і в різних системах буде різним. Деякі ранні версії С мають подібний макрос CLK_TCK.

У прикладі 8.2.4 показано спосіб використання функції clock().

Приклад 8.2.4 – Використання функції clock()

# include<stdio.h>

# include<time.h> int main (void)

{ clock_t start, end;

start = clock( ); / * Текст програми, час виконання якої потрібно визначити * /

end = clock( ); printf("Інтервал =%f секунд\n", (end-start)/CLOCKS_PER_SEC); return 0;

}

Слід наголосити на тому, що одне значення clock() не має сенсу, корисною може бути тільки різниця між двома значеннями. У прикладі 8.2.4 ця різниця дорівнює end - start.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]