
- •1.1 Історія виникнення
- •1.2 Елементи мови Сі
- •1.3 Структура програми. Базові типи даних
- •1.4 Основні операції
- •1.4.3 Операції порівняння
- •1.5 Основи алгоритмізації
- •1.7 Тип перерахування enum
- •1.8 Покажчики
- •1.9 Масиви
- •1.10 Масиви покажчиків
- •1.11 Символьні рядки
- •1.12 Основні методи сортування масивів
- •1.13 Структури
- •1.14 Об'єднання (union)
- •1.15 Файлові потоки
- •1.16 Функціональний підхід
- •1.17 Складені оголошення
- •1.19 Динамічні структури даних
1.9 Масиви
1.9.1 Основні поняття Між покажчиками і масивами існує тісний взаємозв'язок. Будь-яка дія над елементами масивів, що досягається індексуванням, може бути виконана за допомогою покажчиків (посилань) і операцій над ними. Варіант програми з покажчиками буде виконаний швидше, але для розуміння він складніший. Як показує практика роботи на Сі, покажчики рідко використовуються зі скалярними змінними, а частіше - з масивами. Покажчики дають можливість застосовувати адреси приблизно так, як це робить ЕОМ на машинному рівні. Це дозволяє ефективно організувати роботу з масивами. Будь-яку серйозну програму, що використовує масиви, можна написати за допомогою покажчиків. Для роботи з масивом необхідно: 1. визначити ім'я масиву, його розмірність (кількість вимірів) і розмір - кількість елементів масиву; 2. виділити ОП для його розміщення. У мові Сі можна використовувати масиви даних будь-якого типу: • статичні: з виділенням ОП до початку виконання функції; ОП виділяється в стеку або в ОП для статичних даних; • динамічні: ОП виділяється з купи в процесі виконання програми, за допомогою функцій malloc() і calloc(). Динамічні змінні використовують, якщо розмір масиву невідомий до початку роботи програми і визначається в процесі її виконання, наприклад за допомогою обчислення або введення. Розмір масиву визначається: 1. для статичних масивів при його оголошенні; ОП виділяється до початку виконання програми; ім'я масиву - покажчик-константа; кількість елементів масиву визначається: a. явно; наприклад:int а[5]; b. неявно, при ініціалізації елементів масиву; наприклад: int а[] = { 1, 2, 3 };2. для динамічних масивів у процесі виконання програми; ОП для них запитується і виділяється динамічно, з купи; ім'я покажчика на масив - це змінна; масиви ці можуть бути: a. одновимірні і багатовимірні; при цьому визначається кількість елементів усього масиву й ОП запитується для всього масиву; b. вільні (спеціальні двовимірні); при цьому визначається кількість рядків і кількість елементів кожного рядка, і ОП запитується і виділяється для елементів кожного рядка масиву в процесі виконання програми; при використанні вільних масивів використовують масиви покажчиків; Розмір масиву можна не вказувати. В цьому разі необхідно вказати порожні квадратні дужки: 1. якщо при оголошенні ініціалізується значення його елементів; наприклад: static int а[] = {1, 2, 3}; char b[] = "Відповідь:";2. для масивів - формальних параметрів функцій; наприклад: int fun1(int a[], int n); int fun2(int b[k][m][n]);3. при посиланні на раніше оголошений зовнішній масив; наприклад: int а[5]; /* оголошення зовнішнього масиву */ main () { extern int а[]; /*посилання на зовнішній масив */ }В усіх оголошеннях масиву ім'я масиву - це покажчик-константа! Для формування динамічного масиву може використовуватися тільки ім'я покажчика на масив - це покажчик-змінна. Наприклад: int *m1 = (int * ) malloc ( 100 * sizeof (int)) ; float *m2 = (float * ) malloc ( 200 * sizeof (float)) ;деm1- змінна-покажчик на масив 100 значень типуint; m2- змінна-покажчик на масив 200 значень типуfloat. Звільнення виділеної ОП відбувається за допомогою функції:free (покажчик-змінна) ;Наприклад:free(ml); free(m2);Звертання до елементів масивів m1 і m2 може виглядати так:m1[i],m2[j]. Пересилання масивів у Сі немає. Але можна переслати масиви поелементно або сумістити масиви в ОП, давши їм практично те саме ім'я. Наприклад:int *m1 = (int *) malloc(100 * sizeof(int)); int *m2 = (int *) malloc(100 * sizeof(int));Для пересилання елементів одного масиву в іншій можна використати оператор циклу:for (i = 0; i < 100; i++ ) m2[i] = ml [i] ;Замість m2[i] = m1 [i]; можна використовувати:*m2++ = *ml++; або:*(m2 + i) = *(ml + i) ;За допомогою покажчиків можна сполучити обидва масиви й у такий спосіб:free(m2); m2 = ml ;Після цього обидва масиви займатимуть одну й ту саму область ОП, виділену для масивуm1. Однак це не завжди припустимо. Наприклад, коли масиви розташовані в різних типах ОП: один - у стеку, інший - у купі. Наприклад, у функціїmain() оголошені:int *m1 = (int *) malloc(100* sizeof(int)); int m2[100] ;У вищенаведеному прикладіm1- пакажчик-змінна, і масивm1розташований у купі,m2- покажчик-константа, і масивm2розташований у стеку. У цьому випадку помилковий оператор:m2 = m1; тому щоm2- це покажчик-константа. Але післяfree(m1)припустимим є оператор:m1 = m2; /* оскільки m1 - покажчик-змінна */Для доступу до частин масивів і до елементів масивів використовується індексування (індекс). Індекс - це вираз, що визначає адресу значення або групи значень масиву, наприклад адреса значень чергового рядка двовимірного масиву. Індексування можна застосовувати до покажчиків-змінних на одновимірний масив - так само, як і до покажчиків-констант. Індексний вираз обчислюється шляхом додавання адреси початку масиву з цілим значенням для одержання адреси необхідного елемента або частини масиву. Для одержання значення за індексним виразом до результату - адреси елемента масиву застосовується операція непрямої адресації (*), тобто одержання значення за заданою адресою. Відповідно до правил обчислення адреси цілочисельний вираз, що додається до адреси початку масиву, збільшується на розмір кванта ОП типу, що адресується покажчиком. Розглянемо способи оголошення і формування адрес частини масиву й елементів одновимірних і багатомірних масивів за допомогою покажчиків.
1.9.2 Оголошення та звертання в одновимірних масивах Форма оголошення одновимірного масиву з явною вказівкою кількості елементів масиву:тип ім'я_масива [кількість-елементів-масива]; Звертання до елементів одновимірного масиву в загальному випадку можна представити індексуванням, тобто у виглядіім'я-масива [вираз];деім'я-масиву- покажчик-константа;вираз- індекс, число цілого типу; він визначає зсув - збільшення адреси заданого елемента масиву щодо адреси нульового елемента масиву. Елементи одновимірного масиву розташовуються в ОП підряд: нульовий, перший і т д. Приклад оголошення масиву:int а[10]; іnt *p = а; /* - р одержує значення а */ При цьому компілятор виділяє масив в стеку ОП розміром(sizeof(Type) * розмір-масиву )байтів. У вищенаведеному прикладі це 2 * 10 = 20 байтів. Причомуа- покажчик-константа, адреса початку масиву, тобто його нульового елемента,р- змінна; змінній р можна присвоїти значення одним із способів:р = а; р = &а[0]; р = &a[i];де&а[i] == (а + i)- адресаі-елемента масиву. Відповідно до правил перетворення типів значення адреси i-елемента масиву на машинному рівні формується таким чином:&а[i]= а + i * sizeof(int); Справедливі також наступні співвідношення:&a == a+0 == &a[0] - адреса а[0] - нульового елемента масиву;а+2 == &а[2]- адреса а[2] - другого елементи масиву;а+i == &a[i] - адреса a[i] - i-гo елемента масиву;*а==*(а+0)==*(&а[0])==a[0]- значення 0-ого елемента масиву;*(а + 2) == а[2]- значення а[2] - другого елементи масиву;*(а + i) == а[i] - значення a[i] - i-гo елемента масиву;*а + 2 == а[0] + 2- сума значень а[0] і 2. Якщор- покажчик на елементи такого ж типу, які і елементи масиву a таp=а, тоатарвзаємозамінні; при цьому:p == &a[0] == a + 0; p+2 == &a[2] == a + 2; *(p + 2) == (&a[2]) == a[2] == p[2]; *(p + i) == (&a[i]) == a[i] == p[i]; Для a та p еквівалентні всі звертання до елементів a у вигляді:a[i], *(a+i), *(i+a), i[a], таp[i], *(p+i), *(i+p), i[p]
1.9.3 Оголошення та звертання до багатовимірних масивів У даному розділі розглянемо оголошення і зв'язок покажчиків і елементів багатомірних масивів - що мають 2 та більше вимірів. Багатомірний масив у мові Сі розглядається як сукупність масивів меншої розмірності. Наприклад, двовимірний масив - це сукупність одновимірних масивів (його рядків), тривимірний масив - це сукупність матриць, матриці - сукупності рядків, а рядок - сукупність елементів одновимірного масиву. Елементи масивів розташовуються в ОП таким чином, що швидше змінюються самі праві індекси, тобто елементи одновимірного масиву розташовуються підряд, двовимірного - по рядках, тривимірного - по матрицях, а матриці - по рядках. Для звертання до елементів багатомірного масиву можна використовувати нуль і більш індексів (індексних виразів):ім'я-масиву [вираз1][вираз2] ... Наприклад, для звертання: • до одновимірного масиву можна використовувати одно-індексний вираз (індекс); • до двовимірного - 1 або 2 індексний вираз; • до тривимірного - 1, 2 або 3 індексний вираз і т.д. При звертанні до багатомірних масивів одержання значення елемента масиву можливо тільки після визначення адреси елемента масиву, тобто при повній кількості індексів. При цьому обчислюються індексні вираз зліва на право, і доступу до значення виконується після обчислення останнього індексного виразу. Приклад оголошення двовимірного масиву значень типуint:int а[m][n] ; Цей масив складається зmодновимірних масивів (рядків), у кожному з яких утримуєтьсяnелементів (стовпців). При роботі з цим двовимірним масивом можна використовувати одно або 2 індексний вираз. Наприклад: а[i][j]- містить 2 індекси; використовується для звертання до елементаi-рядка,j-стовпця масиву; обчислюються індексні вирази, визначається адреса елемента масиву і вилучається його значення; a[i]- містить 1 індекс; визначає адресу одновимірного масиву: адреса початку i-рядка масиву; а- не містить індексу і визначає адресу масиву, його нульового елемента. Таким чином, звертання до двовимірних масивів за допомогою імені і тільки одного індексу визначає покажчик на початок відповідного рядка масиву (адреса його нульового елемента). Наприклад:а[0] == &a[0][0] == a+0*n*sizeof(int); а[1] == &а[1][0] == a+1*n*sizeof(int); a[i] == &a[i][0] == a+i*n*sizeof(int); Приклад оголошення тривимірного масиву:int а[k][m][n] ;де: •k- кількість матриць з m рядками і n стовпцями; •m- кількість рядків (одновимірних масивів) у матриці; •n - кількість стовпців (елементів у рядку) матриці. Цей масив складається зk матриць, кожна з яких складається зmодновимірних масивів (рядків) поnелементів (стовпців). При звертанні до цього масиву можна використовувати імена: a[l][i][j]- містить 3 індекси; використовується для звертання до елементаl-матриці,i-рядка.j-стовпця масиву; обчислюються індексні вирази, визначається адреса елемента масиву і вилучається його значення; a[k][i]- визначає одновимірний масив - адреса початкуi-рядка;k - матриці; a[k] - визначає двовимірний масив - адреса початкуk- матриці, тобто нульового елемента його нульового рядка; а- адреса початку масиву, нульового елемента нульового рядка нульової матриці. Наприклад:int b[3][4][5]; int i, *ip, *ipp; i = b[0][0][1]; ip = b[2][0]; ipp = b[2];де:ip,ipp- покажчики на значення типу int. Після ip = b[2][0];ipє покажчиком на елемент 0-рядка 0-го стовпця 2-й матриці масиву, тобто b[2][0][0]. Після ipp = b[2];ippадресує 0-й рядок 2-ї матриці масиву, тобто містить адреса b[2][0][0]. Звертання до елементів багатомірного масиву більш детально розглянемо на прикладі двовимірного масиву. Наприклад:int а[3][4]; /* а - покажчик-константа */ int *р = а; /* р - покажчик-змінна */ Після цього покажчик р можна використовувати замість покажчика а для звертання до рядків або елементів масиву а у вигляді: ім'я покажчика і зсув елемента щодо адреси початку масиву а. В ОП елементи масиву а розташовуються таким чином, що швидше всіх змінюється самий правий індекс, тобто в послідовності:а[0][0] а[0][1] а[0][2] а[0][3] а[1][0] ... а[2][2] а[2][3]. При цьому для звертання до масиву а можна використовувати імена:&a == а == &а[0][0] == *а- адреса а[0][0] - елемента 0-ого рядка 0-ого стовпця масиву а;**а == *(&а[0][0]) == а[0][0] - значення елемента нульового рядка нульового стовпця масиву а;a[i] == (а + i) == *(а + i) == &а[i][0] - адреса елементаi-рядка 0-стовпця;*a[i] == **(а + i) == *(&а[i]) == a[i][0]- значення 0-го елементаi-рядка;a[i][j] == *(*(а + i) + j) == *(a[i] + j) == a[i][j] - значення елементаi-рядкаj-стовпця масивуа; де:(а + i) == *(а + i) == a[i] - адреса 0-го елементаi-рядка == &a[i][0];(*(а + i) + j)- адресаj-елементаi-рядка = &a[i][j];*(*(а + i) + j)- значенняj-елемента i-рядка = a[i][j]. Значення адреси початкуi-рядка (адреси 0-елементаi-рядка) на машинному рівні формується у виді:a[i] = а + i == (a+i*n*sizeof(int)), деn- кількість значень в одному рядку. Таким чином, адреса (i+1)-рядка відстоїть відi-рядка на (n*sizeof(int)) байтів, тобто на відстань одного рядка масиву. Виразa[i][j]компілятор Сі переводить в еквівалентний вираз:*(*а + i) + j). Зрозуміло, записa[i][j]більш традиційний у математиці і більш наочний. До елементів двовимірного масиву можна звернутися і за допомогою скалярного покажчика на масив. Наприклад, після оголошення:int а[m][n], *р = а;*(p+i*n+j)- значенняj- елемента i-рядка ; де:n- кількість елементів у рядку;i*n + j - змішанняа[i][j]- елемента відносно початку масивуа.