Розділ 11. Масиви
11.1. Поняття масива
Масив – це впорядкована сукупність однотипних елементів. Масиви часто використовуються для зберігання та опрацювання однорідної інформації, наприклад, таблиць, векторів, матриць, коефіцієнтів рівнянь тощо.
Кожний елемент масива можна однозначно визначити за ім’ям масива та індексом(-ми). Ім’я масива (ідентифікатор) утворюють за тими ж правилами, що й ідентифікатори змінних, а індексами можуть бути лише змінні, константи чи вирази цілого типу. Індекси визначають місцезнаходження елемента в масиві.
Вимірність масива визначається кількістю його індексів. Наприклад, елементи одновимірного масива (вектора) мають один індекс; двовимірного масива (матриці, таблиці) – два індекси: перший – номер рядка, а другий – номер стовпчика. Кількість індексів у масивах необмежена, тому можна створювати масиви різної вимірності.
Нумерація елементів у масиві починається з 0. Якщо масив містить N елементів, то це означає, що він містить елементи з індексами (порядковими номерами) 0, 1, 2, …, N-1. Елемента з номером N у масиві немає.
Основними відмінностями масива від звичайних змінних є:
- спільне ім’я для всіх значень;
- доступ до певного значення за його індексом;
- можливість опрацювання значень у циклі.
Масив - це кiнцева сукупнiсть елементiв одного типу. Оголошення n-вимiрного масиву здiйснюється наступним чином:
тип iм’я_масиву [i1][i2]/*...*/[in];
де iм’я_масиву є правильним iдентифiкатором, i1,i2,...,in - максимальна кiлькiсть елементiв масиву по кожному вимiру.
Кожен iндекс масиву повинен записуватися в окремих квадратних дужках [ ]. Кiлькiсть iндексiв масиву не обмежена. По замовчуванню загальний розмiр масиву не повинен перевищувати одного сегменту (64 К). Для оголошення масиву, бiльшого вiд 64 К, мiж типом та iменем масиву вказуються модифiкатор huge. Iндекси масиву по кожному вимiру мiняються вiд 0 до m-1, де m=1,..,n.
В якостi типу масиву можуть вказуватися:
1) один iз основних типiв (int, char, float, double або їх синонiми);
2) тип iншого масиву;
3) вказiвник, в тому числi вказiвник на функцiю;
4) структура (structure);
5) об’єднання (union).
Наприклад:
int mas[10]; /*масив iз 10 цiлих чисел*/ char line[80]; /*масив iз 80 символiв */ float a[5][4]; /* двовимiрний масив iз 5 x 4 елементiв типу float */ double b[7][7][7]; /* трьохвимiрний масив iз 7 x 7 x 7 елементiв типу double */ int *x[10]; /* масив iз 10 вказiвникiв на тип int */ |
При оголошеннi масиву допускається його iнiцiалiзацiя. Початковi значення елементiв повиннi задаватися через кому у фiгурних дужках {}. Значення елементiв по кожному окремому вимiру можуть охоплюватися фiгурними дужками, мiж якими ставиться кома, наприклад:
int year[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; float mas[2][3] = {{0.0, 0.1, 0.2},{1.0, 1.1, 1.2}}; |
Остання iнiцiалiзацiя еквiвалентна наступнiй:
float mas[2][3] = {0.0, 0.1, 0.2, 1.0, 1.1, 1.2};
Кiлькiсть елементiв не може бути бiльшою вiд вказаної розмiрностi. Якщо кiлькiсть елементiв менша вiд вказаної pозмipностi, то пpисвоєння вiдбувається тiльки для початкових елементiв, а iншi елементи приймають нульовi значення, якщо масив оголошений як зовнiшнiй або статичний, i невизначенi значення - в iнших випадках:
static int vec[10] = {1,8,3}; /* Першi три елементи приймуть заданi значення, а наступнi сiм - значення 0*/ auto float matr[3][4] = {{0.0},{1.0, 1.1},{2.0, 2.1, 2.2}}; /* елементи з індексами [0][0],[1][0],[1][1],[2][0],[2][1],[2][2] приймуть заданi значення, а всi iншi - невизначенi значення */ |
При явнiй iнiцiалiзацiї масивiв їх розмiрностi можна не вказувати. Тодi розмiрнiсть масиву визначається кiлькiстю заданих елементiв, наприклад:
int i_array [] = { 4, -2, 7 , 10, -3, 5, 3}; /* оголошений та iнiцiалiзований масив iз семи цiлих чисел */ |
При явнiй iнiцiалiзацiї двовимiрнiх масивiв перший iндекс може бути опущений, а другий вказується обов’язково:
float f_mas[][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
В даному прикладi оголошений двовимiрний масив, що складається з 3-х рядкiв i 4-х стовпчикiв. Приведене оголошення еквiвалентне наступному:
float f_mas[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
По ANSI - стандарту iнiцiалiзувати масив можна будь-де, в тому числi i всерединi функцiї. Для молодших версiй компiляторiв, щоб iнiцiалiзувати масив всерединi функцiй, його потрiбно оголосити як статичний за допомогою слова static:
static float vec [] = { 5.2, 6.3, 8.4 };
В пам’ятi елементи масиву розмiщуються в неперервнiй областi так, що правий iндекс змiнюється першим. Приклад розмiщення в пам’ятi масиву int matrix[2][3];
Звертання до елемента масиву вiдбувається вказанням iменi масиву i його iндексiв. Iндекси повиннi бути константами, змiнними або виразами цiлого типу. Кожен iндекс береться в окремi квадратнi дужки, наприклад:
i_array[3] /* звертання до четвертого елементу масиву i_array */ matrix[1][2] /* звертання до елементу масиву matrix, який знаходиться на перетинi другого рядка i третього стовпчика */ |
Iнший спосiб доступу до елементiв масиву полягає у використаннi вказiвникiв.
Iм’я одномiрного масиву є вказiвником на його нульовий елемент, наприклад:
int vec[5];
vec == &vec[0]; vec+i == &vec[i]; *vec == vec[0]; *(vec+i) == vec[i]; |
Вiдповiдно для оголошеного двовимiрного масиву
int matrix[3][4];
позначення matrix[i], де i=0,1,2 визначає адресу розмiщення в пам’ятi i-го рядка, а iм’я масиву matrix визначає вказiвник на початок масиву int matrix[3][4]. Iм’я масиву matrix є сумiсне з типом вказiвника на вектор з чотирьох цiлих чисел: int (*p)[4]. Значення вказiвникiв matrix та *matrix є однаковi, але несумiснi по типу. Правила сумiсностi та еквiвалентностi приведенi нижче.
*matrix == matrix[0] == &matrix[0][0]; *(matrix+i) == matrix[i] == &matrix[i][0]; *(matrix+i)+j == matrix[i]+j == &matrix[i][j]; **matrix == *matrix[0] == matrix[0][0]; **(matrix+i) == *matrix[i] == matrix[i][0]; *(*(matrix+i)+j) == *(matrix[i]+j) == matrix[i][j]; |
Аналогiчно, для оголошеного трьохвимiрного масиву
int fx[5][4][8];
позначення fx[i] визначає адресу розмiщення в пам’ятi i-го двовимiрного пiдмасиву розмiрнiстю 4x8 елементiв, а позначення fx[i][j] визначає адресу 8-елементного j-го рядка i-го двовимiрного пiдмасиву.
Враховуючи, що першi p iндексiв k-мiрного масиву (p<k) визначають адресу вiдповiдного пiдмасиву, для звертання до елементiв масиву чи їх вказiвникiв допускається форма, аналогiчна адресуванню комiрок пам’ятi через вказiвники:
(ip)[iм’я_масиву [i1][i2]/*...*/[i(p-1)]],
де ip - змiнна або вираз цiлого типу, але не константа. Наприклад:
(i)[vec] /* еквiвалентно звертанню vec[i] */ (j)[matrix[i]] /* еквiвалентно звертанню matrix[i][j] */ |
При роботi з масивами потрiбно пам’ятати, що iм’я масиву є константою, тому не дозволяються операцiї модифiкацiї значення цiєї константи, наприклад:
int a[5],y,n, int *z; /* наступнi операцiї не дозволяються */ a=y; a++; a+=n; z=&a; |
На вiдмiну вiд мови Pascal, на мовi С не можна присвоїти цiлком один масив iншому такого ж типу та розмiрностi. Копiювання масивiв здiйснюється поелементно за допомогою операторiв циклу.
На мовi С допускається використання масивiв вказiвникiв на всi типи даних, наприклад:
int *r[3];
де r[3] - масив, що мiстить адреси трьох елементiв типу int. Для доступу до значення, яке знаходиться за адресою r[i], необхiдно використати операцiю розiменування iндексованого вказiвника *r[i]. Робота iз масивом вказiвникiв демонструється наступним прикладом:
int i; float x1=1,x2=2,x3=3,sum=0,*r[3]; r[0]=&x1; r[1]=&x2; r[2]=&x3; for(i=0;i<3;i++) sum = sum + *r[i]; printf(“Сума = %f\n”,sum); |
Ввiд та вивiд числових масивiв даних здiйснюється поелементно за допомогою функцiй форматного вводу-виводу. Для вводу елементiв масиву використовується бiблiотечна функцiя scanf, яка має наступний формат:
int scanf (“форматний рядок”, список адрес елементів масиву);
Для виводу елементiв масиву використовується бiблiотечна функцiя printf, яка має наступний формат:
int printf(“форматний рядок”,список елементiв масиву);
Форматний рядок для вводу-виводу повинен мiстити специфiкацiї форматiв списку даних. Специфiкацiя формату повинна починатися iз символу процента (%), пiсля якого вказується один iз символiв, поданих в табл. 1.
Специфiкацiї форматiв списку даних
№ |
Назва |
Призначення |
1 |
i або d |
для вводу-виводу десяткового зi знаком; |
2 |
u |
для вводу-виводу десяткового без знаку; |
3 |
o |
для вводу-виводу вiсiмкового без знаку; |
4 |
x |
для вводу шiстнадцяткового зi знаком i виводу шiстнадцяткового без знаку; |
5 |
f |
для вводу-виводу числа у форматi з фiксованою крапкою; |
6 |
e або E |
для вводу-виводу числа у форматi з плаваючою крапкою; |
7 |
g або G |
для вводу-виводу числа у форматi з фiксованою або плаваючою крапкою в залежностi вiд можливої точностi представлення. |
Для вводу довгого цiлого (long) або дiйсного з подвiйною точнiстю (double) перед символом формату вказується символ l. Для вводу типу long double перед символом формату вказується символ L. В форматний рядок можуть входити iншi символи таблицi ASCII. Якщо такi символи включенi у форматний рядок функцiї scanf, то вони повиннi зустрiтися у вхiдному потоцi. Якщо ж додатковi символи включенi у форматний рядок функцiї printf, то вони будуть помiщенi у вихiдний потiк. Таким чином можна виводити допомiжнi повiдомлення.
11.2. Одновимірні масиви
11.2.1. Оголошення одновимірних масивів
Одновимірний масив оголошується так:
<тип_даних> <ім’я_масива> [<розмір>];
де тип_даних – тип даних елементів масива, при цьому елементами масива не можуть бути функції та елементи типу void; розмір – кількість елементів масива.
На відміну від інших мов, у мові С не перевіряється вихід за межі масива, тому, щоб уникнути помилок, слід уважно вказувати розмірність масивів.
Значення розміру масива при його оголошенні не вказують у таких випадках:
- коли при оголошенні масив ініціалізовується;
- коли масив оголошений як формальний параметр функції;
- коли масив оголошений як посилання на масив, явно визначений в іншому модулі.
Для доступу до елементів одновимірного масива використовують одну з таких форм доступу:
- індексну:
<ім’я_масива> [<індекс>]
- адресну (вказівникову):
*(<ім’я_масива> + <індекс>)
Переважно використовується індексна форма доступу.
Оскільки в мовах С/С++ нумерація індексів починається з нуля, то значення індексів мають знаходитися в діапазоні від нуля до величини, на одиницю меншу ніж розмір масива.
Приклад 11.1. Оголошення одновимірних масивів:
int a[12]; // масив 12 цілих чисел: a[0], a[1],..., a[11]
float m[30]; // масив 30 дійсних чисел: m[0], m[1],..., m[29]
double v[15]; /* масив 15 дійсних чисел з подвійною точністю:
v[0], v[1],..., v[14] */
char N[20]; // масив 20 символів: N[0], N[1],..., N[19]
Як і змінні, масиви можна ініціалізовувати при оголошенні. Якщо реальна кількість ініціалізованих значень є меншою за розмір числового масива, то всі неініціалізовані елементи автоматично отримують значення 0.
Приклад 11.2.
int a[5] = {9,33,–23,8,1}; // а[0]=9, а[1]=33, а[2]=–23, а[3]=8, а[4]=1
int b[] = {3, 0, –2, 5}; /* компілятор сам визначає довжину масива,
b[0]=3, b[1]=0, b[2]=-2, b[3]=5 */
float c[10] = {1.5, -3.8, 10}; /* c[0]=1.5, c[1]=-3.8, c[2]=10.0,
c[3]=c[4]= … =c[9]=0.0 */
Також для оголошення масивів можна використовувати типізовані констант-масиви, які дають змогу водночас оголосити масив і задати його значення як константи:
сonst <тип_даних> <ім’я_масива> [<розмір_масива>] =
{<значення_елементів_масива>};
ЗАУВАЖЕННЯ: змінювати значення елементів констант-масивів не можна.
Приклад 11.3. Оголошення одновимірних констант-масивів:
const int arr[5] = {9, 3, –7, 0, 13}; // масив з 5-ти цілих чисел
const С[5] = {15, –6, 54}; /* масив з 5-ти цілих чисел типу int,
де С[0]=15, С[1]=–6, С[2]=54, С[3]=0, С[4]=0 */
const float f[5] = {1.5, 6, 8, 7.4, –1.15}; // масив з 5-ти дійсних чисел
Крім того, масиви можна оголошувати на основі власного типу даних:
typedef <тип_даних> <ім’я_власного_типу_даних> [<розмір>];
<ім’я_власного_типу_даних> <ім’я_масива>;
Приклад 11.4. Створити власний тип даних TArray як масив з 10-ти цілих чисел і оголосити масиви a та b, цього типу:
typedef unsigned short TArray[10];
TArray a, b;
Фактично масиви і вказівники в мовах С/С++ можуть використовуватись взаємозамінно.
Під час програмної реалізації індексний спосіб доступу до елемента масива зводиться до вказівникового, оскільки операції над вказівниками виконуються швидше.
Приклад 11.5. Оголосити вказівник на одновимірний масив цілих чисел:
int m[6] = {0, -12, 4, 6, -2, 7};
При такому оголошенні масива пам’ять виділяється не лише для шести елементів масива, а й для вказівника з ім’ям m, значення якого дорівнює адресі першого елемента масива (m[0]), тобто сам масив залишається безіменним, а доступ до елементів масива здійснюється через вказівник з ім’ям m.
Рис. 11.1. Візуальне подання масиву з прикладу 11.5
РЕКОМЕНДАЦІЯ: якщо елементи масива опрацьовуються почергово, то доцільніше використовувати вказівниковий спосіб доступу. Якщо ж вибір елементів є випадковим, то краще використовувати індексний спосіб, який є більш наочним і зручним для сприйняття.
Оскільки ім’я масива є вказівником на перший елемент масива, то іншому вказівнику можна присвоїти адресу першого елемента масива за допомогою оператора присвоєння:
int m[6] = {0, -12, 4, 6, -2, 7};
// оголошення та ініціалізація вказівника адресою початку масива
int *w = m; // ця операція еквівалентна операції *w = &m[0];
// оголошення вказівника
int *ptr;
// ініціалізація вказівника адресою початку масива
ptr = m; // ця операція еквівалентна операції ptr = &m[0];
Тут обидва вказівники вказують на один і той же масив.
Індексація вказівників виконується аналогічно до індексації масивів.
Наприклад, щоб присвоїти першому елементу масива значення 2, можна скористатись одним із таких операторів:
1) *m = 2; або *(m + 0) = 2;
2) *ptr = 2; або *(ptr + 0) = 2;
3) m[0] = 2;
4) ptr[0] = 2;
ЗАУВАЖЕННЯ: не слід плутати оголошення int *p1[5] та int (*p2)[5]. В оголошенні int *p1[5] оголошено масив вказівників з ім’ям p1, що складається з 5 елементів, кожний з яких є вказівником на змінну типу int. А в оголошенні int (*p2)[5] оголошено змінну-вказівник з ім’ям p2, яка вказує на масив з 5 чисел типу int.
11.2.2. Операції над вказівниками на масиви
Над вказівниками можна виконувати арифметичні операції додавання та віднімання.
Результатом додавання до вказівника на масив цілого числа, за умови, що воно є меншим за розмір масива, отримаємо адресу елемента масива з індексом, що дорівнює доданому числу. При цьому слід контролювати величину значень чисел, що додаються, щоб уникнути виходу за межі масива.
Різниця вказівників, що вказують на різні елементи масива, дорівнює різниці їхніх адрес.
Приклад 11.6. Арифметичні операції з вказівниками на масиви:
int *ptr1, *ptr2, m[6] = {0, -12, 4, 6, -2, 7}, r;
ptr1 = m + 4; // записати адресу елемента m[4]
ptr2 = m + 9; // вихід за межі масива
r = ptr1 - ptr2; // різниця адрес; результат: r = -5
r = ptr2 - ptr1; // різниця адрес; результат: r = 5
ЗАУВАЖЕННЯ: порівняння вказівників за допомогою операцій “>”, “<”, “>=”, “<=” має сенс лише для вказівників на один і той же масив. Проте, операції відношення “= =” та “!=” мають сенс для будь-яких вказівників. При цьому вказівники є рівнозначними, якщо вони вказують на одну і ту ж саму адресу в пам’яті.
Приклад 11.7. Операції з вказівниками на масиви:
int *ptr1 = 0, *ptr2 = 0, *ptr3 = 0, m[6] = {0, -12, 4, 6, -2, 7};
ptr1 = m + 2; // запис адреси елемента m[2]
ptr2 = m + 5; // запис адреси елемента m[5]
ptr3 = m + 9; // вихід за межі масива
if (ptr1 < ptr2) m[1] = 5; // порівняння адрес, а не значень
Тут значення ptr1 менше за значення ptr2, тому виконається оператор m[1]=5.
11.2.3. Введення-виведення одновимірних масивів
Введення масива з клавіатури:
1 спосіб (за допомогою scanf):
const int colCount = 5;
for(int i = 0; i < colCount; i++) {
printf("a[%d]=", i);
scanf("%d", &a[i]);
}
2 спосіб (за допомогою cin):
const int colCount = 5;
for(int i = 0; i < colCount; i++) {
cout << "a[" << i << "]=";
cin >> a[i];
}
При цьому числа, що вводяться, можуть бути записані як у стовпчик, так і в рядок через пробіл.
Введення масива за допомогою генератора випадкових чисел:
1 спосіб (кожного разу генерується та сама послідовність чисел):
#include <stdlib.h>
...
const int colCount = 5, Low = 6, High = 20;
int a[colCount];
for (int i = 0; i < colCount; i++)
a[i] = Low + rand() % (High-Low+1);
2 спосіб (кожного разу генерується інша послідовність чисел):
#include <stdlib.h>
#include <time.h>
...
const int colCount = 5, Low = 6, High = 20;
srand((unsigned)time(NULL));
for (int i = 0; i < colCount; i++)
a[i] = Low + rand() % (High-Low+1);
Виведення масива:
1 спосіб (за допомогою printf):
const int colCount = 5;
int a[colCount];
for (int i = 0; i < colCount; i++)
printf("a[%d]=%d\t", i, a[i]);
2 спосіб (за допомогою cout):
// у стовпчик
for (int i = 0; i < colCount; i++)
cout << "a[" << i << "]=" << a[i] << endl;
// у рядок, значення розділяються за допомогою табуляції
for (int i = 0; i < colCount; i++)
cout << "a[" << i << "]=" << a[i] << "\t";
// або за допомогою встановлення ширини
for (int i = 0; i < colCount; i++)
cout << setw(5) << "a[" << i << "][" << j << "]="
<< setw(2) << a[i][j];
11.3. Багатовимірні масиви
11.3.1. Двовимірні масиви
11.3.1.1. Оголошення двовимірних масивів
Для зручного зберігання даних часто недостатньо однієї вимірності масива, тому використовують багатовимірні масиви.
Оголошення двовимірного масива має такий вигляд:
<тип_даних> <ім’я_масива> [<к-ть_рядків>] [<к-ть_стовпчиків>];
При цьому кількість елементів масива дорівнює добутку кількості елементів масива за кожним індексом.
Приклад 11.8. Оголосити двовимірний масив з 3-х рядків та 4-х стовпчиків (12-ти елементів) цілого типу:
int a[3][4];
Структура елементів цього масива буде така:
a[0][0] a[0][1] a[0][2] a[0][3]
a[1][0] a[1][1] a[1][2] a[1][3]
a[2][0] a[2][1] a[2][2] a[2][3]
Таким чином під масив автоматично виділяється пам’ять, необхідна для розміщення всіх його елементів.
При оголошенні масива його можна ініціалізовувати початковими значеннями. Якщо значень менше ніж елементів, то всі неініціалізовані елементи числових масивів автоматично отримують значення 0.
Приклад 11.9. Ініціалізація двохвимірних масивів:
1) int w[3][3] = { {2, 3, 4}, {3, 4, 8}, {1, 0, 9} };
Тут оголошено та ініціалізовано масив цілих чисел w[3][3]. Елементам масиву присвоєні відповідні значення зі списку: w[0][0]=2, w[0][1]=3, w[0][2]=4, w[1][0]=3 і т.д. Списки, взяті у фігурні дужки, відповідають рядкам масива.
2) float C[4][3] = {1.1, 2, 3, 3.4, 0.5, 6.8, 9.7, 0.9};
3) float C[4][3] = { {1.1, 2, 3}, {3.4, 0.5, 6.8}, {9.7, 0.9} };
4) float C[4][3] = { {1.1, 2, 3},
{3.4, 0.5, 6.8},
{9.7, 0.9} };
Оголошення в пунктах 2)-4) еквівалентні; елементи останнього рядка неініціалізовані, тому вони будуть ініціалізовані 0.
5) float C[][3] = { {1.1, 2, 3}, {3.4, 0.5, 6.8}, {9.7, 0.9} };
При ініціалізації масива першу розмірність можна не вказувати.
6) float C[4][3] = { {1.1, 2}, {3, 3.4, 0.5}, {6.8}, {9.7, 0.9} };
Вказівники на багатовимірні масиви – це масиви, елементами яких є інші масиви. При оголошені таких масивів в оперативній пам’яті створюється декілька різних об’єктів.
Якщо двовимірний масив оголосити та ініціалізувати так:
int a[4][3] = { {-2,0,1,2}, {3,4,0,5}, {0,6,2,8}, {3,7,2,4} };
то в пам’яті виділяється ділянка для зберігання значення змінної a, що є вказівником на масив з чотирьох вказівників. При цьому масиву з чотирьох вказівників також виділяється пам’ять. Кожний з цих чотирьох вказівників містить адресу масива з трьох елементів типу int. Таким чином у пам’яті комп’ютера виділяється чотири ділянки для зберігання чотирьох масивів чисел типу int, кожна з яких складається з трьох елементів (рис. 11.2).
(Для ознайомлення з повним текстом статті необхідно залогінитись)