Тема 6. Основи роботи з масивами
C#, масиви, одномірний масив, багатовимірний масив, масив масивів (jagged array), ініціалізація масивів, доступ до елементів, перебір масивів, foreach, for, Length, Array клас, сортування, пошук, копіювання.
Лекція ознайомить студентів із основами роботи з масивами в C#, включаючи їх оголошення, ініціалізацію, доступ до елементів, перебір за допомогою циклів, а також основні операції, такі як сортування, пошук і копіювання.
6.1. Поняття масиву даних
Масив задає спосіб організації даних. Масивом називають впорядковану сукупність елементів одного типу.
Кожен елемент масиву має індекси, що визначають порядок елементів. Число індексів характеризує розмір масиву. Кожен індекс змінюється в деякому діапазоні [а,b]
. У мові C#, як і в багатьох інших мовах, індекси задаються цілочисельним типом. Діапазон [а,b]
називається граничною парою, а
– нижньою межою, b
– верхньою межею. Якщо межі задані константними виразами, то число елементів масиву відоме у момент його оголошення і йому може бути виділена пам’ять ще на етапі трансляції. Такі масиви називаються статичними. Якщо ж вирази, які задають межі, залежать від змінних, то такі масиви називаються динамічними, оскільки пам’ять їм може бути відведена лише динамічно в процесі виконання програми, коли стають відомими значення відповідних змінних. Масиву, як правило, виділяється безперервна область пам’яті.
У мові C++ всі масиви є статичними. У мові C# знято істотне обмеження мови C++ на статичність масивів. Масиви в мові C# є справжніми динамічними масивами. Як наслідок цього масиви відносяться до посилальних типів (Reference
), пам’ять їм відводиться динамічно в “купі”.
У мові C++ “класичних” багатовимірних масивів немає. Тут введені одновимірні масиви і масиви масивів. Останні є загальнішою структурою даних і дозволяють задати не лише багатовимірний куб, але і порізану, ступінчасту структуру. У мові C# збережені одновимірні масиви і масиви масивів. На додаток до них в мову додані багатовимірні масиви. Динамічні багатовимірні масиви мови C# є потужною, надійною, зрозумілою і зручною структурою даних, яку сміливо можна рекомендувати до вживання не лише професіоналам, але і новачкам, що програмують на C#.
Після цього короткого огляду давайте перейдемо до більш систематичного вивчення деталей роботи з масивами в C#.
6.2. Одновимірні масиви
6.2.1. Оголошення
У спрощеному вигляді оголошення одновимірного масиву виглядає таким чином:
Увага, на відміну від мови C++ квадратні дужки приписані не до імені змінної, а до типу. Вони є невід’ємною частиною визначення класу, так що запис T[]
слід розуміти як клас одновимірний масив з елементами типу T
.
Що ж до меж зміни індексів, то ця характеристика до класу не відноситься, вона є характеристикою змінних - екземплярів, кожен з яких є одновимірним масивом зі своїм числом елементів, що задаються в оголошенні змінної.
Найчастіше при оголошенні масиву використовується ім’я з ініціалізацією. І знову-таки, як і в разі простих змінних, можуть бути два варіанти ініціалізації. У першому випадку ініціалізація є явною і задається константним масивом. Ось приклад:
Слідуючи синтаксису, елементи константного масиву слід брати у фігурні дужки. У другому випадку створення і ініціалізація масиву виконується в об’єктному стилі з викликом конструктора масиву. І це найбільш поширена практика оголошення масивів. Наведу приклад:
Отже, якщо масив оголошується без ініціалізації, то створюється лише висяче посилання із значенням void. Якщо ініціалізація виконується конструктором, то в динамічній пам’яті створюється сам масив, елементи якого ініціалізувалися константами відповідного типу, і посилання зв’язується з цим масивом. Якщо масив ініціалізувався константним масивом, то в пам’яті створюється константний масив, з яким і зв’язується посилання.
6.2.2. Ініціалізація
Ініціалізувати масиви, наприклад, можна наступними способами:
- Літералами відповідного типу (Лістинг 6.1).
- Випадковими числами (Лістинг 6.2).
- Ввести з клавіатури (Лістинг 6.3).
Заповнення масиву літералами може відбуватися наступним чином:
Лістинг 6.1. Заповнення одновимірного масиву літералами
int[] nums = { 99, 10, 100, 18, 78, 23, 63, 9, 87, 49 };
int[] nums;
nums = new int[ ] { 99, 10, 100, 18, 78, 23, 63, 9, 87, 49 };
int[] nums = new int[10] { 99, 10, 100, 18, 78, 23, 63, 9, 87, 49 };
Приклад програми на C#, яка заповнює масив випадковими числами:
Лістинг 6.2. Заповнення одновимірного масиву літералами
int[] array = new int[10];
//Параметр фіксується для відтворюваності результатів
Random rand = new Random(2021);
for (int i = 0; i < 10; ++i)
{
array[i] = rand.Next(0, 100);
Console.Write(" {0}", array[i]);
}
Результат виконання:
Ввести з клавіатури значення масиву можна наступним чином:
Лістинг 6.3. Заповнення масиву значеннями, введеними з клавіатури
//Додаємо кодування кириличних символів
Console.OutputEncoding = Encoding.UTF8;
int[] array;
Console.Write("\n Введіть розмір масиву >\t");
int size = Convert.ToInt32(Console.ReadLine());
array = new int[size];
for (int i = 0; i < array.Length; ++i)
{
Console.Write(" Введіть {0}-й елемент:\t", i + 1);
array[i] = Convert.ToInt32(Console.ReadLine());
}
Результат виконання:
Введіть розмір масиву > 4
Введіть 1-й елемент: 7
Введіть 2-й елемент: 45
Введіть 3-й елемент: 8
Введіть 4-й елемент: 11
6.3. Багатовимірні масиви
Жодної принципової різниці між одновимірними та багатовимірними масивами немає.
Одновимірні масиви - це окремий випадок багатовимірних. Можна говорити і по-іншому: багатовимірні масиви є природним узагальненням одновимірних. Одновимірні масиви дозволяють задавати такі математичні структури як вектори, двовимірні - матриці, тривимірні - куби даних, масиви більшої розмірності - багатовимірні куби даних. Варто відмітити, що при роботі з базами даних багатовимірні куби, так звані куби OLAP
, зустрічаються часто.
От як виглядає оголошення багатовимірного масиву в загальному випадку:
Приклад ініціалізації:
Давайте розглянемо класичне завдання додавання прямокутних матриць. Нам знадобиться три динамічні масиви для представлення матриць і три методи, один з яких заповнюватиме вхідні матриці випадковими числами (FillArray
), інший - виконувати додавання матриць (AddMatrix
), третій - друкувати самі матриці (PrintArray
).
Ось тестовий приклад (програма містить трішки довгий код, але варто його переглянути):
Лістинг 6.4. Приклад додавання матриць
static void Main(string[] args)
{
//Оголосимо розмірність наших матриць
int rows = 3, cols = 5;
//Оголосимо матриці
int[,] a = new int[rows, cols];
int[,] b = new int[rows, cols];
int[,] result = new int[rows, cols];
//Заповнимо матриці випадковими числами
a = FillArray(rows, cols, 1);
b = FillArray(rows, cols, 2);
//Виведемо на консоль матрицю А
Console.WriteLine("\nArray1: ");
PrintArray(a);
//Виведемо на консоль матрицю B
Console.WriteLine("\nArray2: ");
PrintArray(b);
//Додамо і присвоїмо результат
result = AddMatrix(a, b);
if (result != null)
{
Console.WriteLine("\nResultMatrix: ");
PrintArray(result);
}
}
static int[,] AddMatrix(int[,] a, int[,] b)
{
//Оголосимо матрицю у яку будемо записувати результат.
int[,] res = new int[a.GetLength(0), a.GetLength(1)];
//Перевіримо чи однаковий розмір матриць
if ((a.GetLength(0) != b.GetLength(0)) || (a.GetLength(1) != b.GetLength(1)))
Console.WriteLine("Мариці неоднакового розміру.");
else
{
for (int i = 0; i < a.GetLength(0); ++i)
{
//Виконуємо додавання елементів матриць
for (int j = 0; j < a.GetLength(1); ++j)
res[i, j] = a[i, j] + b[i, j];
}
return res;
}
return null;
}
static int[,] FillArray(int rows, int cols, int seed)
{
int[,] array = new int[rows, cols];
//Створює змінну класу Random для генерування значень
Random rand = new Random(seed);
for (int i = 0; i < rows; ++i)
{
for (int j = 0; j < cols; ++j)
{
array[i, j] = rand.Next(0, 100);
}
}
return array;
}
static void PrintArray(int[,] array)
{
for (int i = 0; i < array.GetLength(0); ++i)
{
for (int j = 0; j < array.GetLength(1); ++j)
{
//Виводимо значення на консоль
Console.Write(" {0}\t", array[i, j]);
}
Console.WriteLine();
}
}
Результат виконання:
Array1:
24 11 46 77 65
43 35 94 10 64
2 24 32 98 68
Array2:
77 40 16 98 10
30 80 44 22 1
76 2 0 51 38
ResultMatrix:
101 51 62 175 75
73 115 138 32 65
78 26 32 149 106
6.4. Масиви масивів
Ще одним видом масивів C# є масиви масивів, звані також різаними/рваними масивами (jagged arrays). Такий масив масивів можна розглядати як одновимірний масив, елементи якого є масивами, елементи яких, у свою чергу, знову можуть бути масивами, і так може тривати до деякого рівня вкладеності.
У яких ситуаціях може виникати необхідність в таких структурах даних? Ці масиви можуть застосовуватися для представлення дерев, в яких вузли можуть мати довільне число нащадків. Таким може бути, наприклад, генеалогічне дерево. Вершини першого рівня - Fathers
, що представляють батьків, можуть задаватися одновимірним масивом, так що Fathers[i]
- це i
-й батько. Вершини другого рівня представляються масивом масивів - Children
, так що Children[i]
- це масив дітей i
-го батька, а Children[i][j]
- це j
-а дитина i
-го батька. Для представлення внуків знадобиться третій рівень, так що GrandChildren[i][j][k]
представлятиме k
-го внука j
-ї дитини i
-го батька.
Є деякі особливості в оголошенні і ініціалізації таких масивів. Якщо при оголошенні багатовимірних масивів для вказівки розмірності використовувалися коми, то для порізаних масивів застосовується зрозуміліша символіка - сукупності пар квадратних дужок; наприклад, int[][]
задає масив, елементи якого - одновимірні масиви елементів типу int
.
Складніше зі створенням самих масивів і їх ініціалізацією. Тут не можна викликати конструктор new int[3][5]
, оскільки він не задає порізаний масив. Фактично потрібно викликати конструктор для кожного масиву на нийнижчому рівні. У цьому і полягає складність оголошення таких масивів.
Розпочнемо з формального прикладу:
Масив jagger
має всього два рівні. Можна вважати, що у нього три елементи, кожен з яких є масивом. Для кожного такого масиву необхідно викликати конструктор new
, аби створити внутрішній масив. У даному прикладі елементи внутрішніх масивів набувають значення, будучи явно ініціалізовані константними масивами. Звичайно, допустимим є і таке оголошення:
В цьому випадку елементи масиву набудуть при ініціалізації нульових значень. Реальну ініціалізацію потрібно буде виконувати програмним шляхом. Варто відмітити, що в конструкторі верхнього рівня константу 3
можна опустити і писати просто new int[][]
. Також виклик цього конструктора можна взагалі опустити - він матиметься на увазі:
Оголошувати вкладені масиви обов’язково.
6.5. Цикл foreach та масиви
У лекції про цикли загдувалося, що у мові С# визначений цикл foreach
, але детальний його розгляд був відкладений «на потім». Час для нього настав.
Цикл foreach
використовується для опису елементів колекції. Колекція – це група об’єктів. С# визначає декілька типів колекцій, і одним з них є масив. Формат запису циклу foreach
має такий вигляд:
Тут елементи тип та ім’я_змінної задають тип та ім’я ітераційної змінної, яка при функціонуванні циклу fоreach
набуватиме значень елементів з колекції. Елемент колекція служить для вказівки опитуваної колекції (в даному випадку як колекцію ми розглядаємо масив). Таким чином, елемент тип повинен збігатися (або бути сумісним) з базовим типом масиву. Тут важливо запам’ятати, що ітераційну змінну стосовно масиву можна використовувати лише для читання. Отже, неможливо змінити вміст масиву, присвоївши ітераційній змінній нове значення.
Розглянемо простий приклад використання циклу foreach
. Приведена нижче програма створює масив для зберігання цілих чисел і присвоює його елементам початкові значення. Потім вона відображає елементи масиву, попутно обчислюючи їх суму.
Лістинг 6.5. Робота з циклом foreach
Console.OutputEncoding = Encoding.UTF8;
int[] array = new int[10];
int suma = 0;
Random r = new Random(2021);
//Заповнюмє масив випадковими числами
for (int i = 0; i < array.Length; ++i)
array[i] = r.Next(10, 100);
Console.Write("Array:\t");
foreach (int element in array)
{
Console.Write("{0} ", element);
suma += element;
}
Console.WriteLine("\n\nСума елементів:\t{0}", suma);
Результат виконання:
Array: 59 83 48 93 69 73 48 41 10 56
Сума елементів: 580
6.6. Сортування масивів [-]
Розділ у процесі наповнення
6.7. Робота з класом Array [-]
Розділ у процесі наповнення
Задачі
Задача 6.1
Написати програму, що знаходить суму парних та суму непарних елементів масиву.Кількість елементів визначає користувач, елементи генеруються випадковим чином у діапазоні [10;100] (цілі числа).
Задача 6.2
Написати програму, що знаходить і виводить на консоль у цілочисельному масиві усі парні значення кратні
Задача 6.3
Написати програму, яка знаходить в масиві найменше непарне число і показує його на екран. Елементи масиву генеруються випадковим чином у діапазоні
Задача 6.4
Написати програму, що сортує масив у вказаному користувачем порядку: за зростанням або за спаданням. Елементи масиву генеруються випадковим чином у діапазоні
Задача 6.5
Дано пустий масив розмірності 5х5 елементів. Заповнити його випадковими значеннями з діапазону [10;20] і вивести на консоль.
Задача 6.6
Задано масив із
Задача 6.7
Дано одновимірний масив із дійсних чисел. Відсортувати його таким чином, щоб всі додатні елементи знаходилися на початку, а всі від’ємні – в кінці, і при цьому зберігся початковий порядок елементів в обох групах.
Задача 6.8
Написати програму, що обчислює середнє значення серед парних елементів масиву. Елементи генеруються випадковим чином у діапазоні [10; 100). Кількість елементів визначає користувач.
Задача 6.9
Написати програму, що генерує вектор випадкових елементів з діапазону 7
-ми. Генерацію випалкового числа винести в окремий метод.
Задача 6.10
Написати програму, що дозволяє маніпулювати елементами цілочисельного масиву. Суть роботи програми полягає у наступному: через діалог з користувачем потрібно реалізувати функції: додавання, видалення, вставки числа у існуючий масив.
На початку роботи програми масив уже повинен бути ініціалізований 5-ма випадковими значеннями з діапазону
Задача 6.11
Задано одновимірний масив. Знайти два серед його елементів, модуль різниці яких має найменше значення.
Задача 6.12
Написати програму, яка знаходить суму парних і суму непарних елементів масиву. Елементи масиву генеруються випадковим чином. Кількість стовпців та рядків вводить користувач.
Задача 6.13
Сформувати цілочисельний масив, елементами якого є випадкові числа із діапазону
Задача 6.14
Знайти мінімальний елемент серед тих елементів масиву
Задача 6.15
Визначити, скільки різних чисел міститься в заданому цілочисельному масиві.
Задача 6.16
Написати програму, яка знаходить в масиві значення, що повторюються два і більше разів, і показує їх на екран. Елементи масиву генеруються випадковим чином. Кількість стовпців та рядків вводить користувач. Наприклад:
45 12 12 63
15 12 45 78
75 56 89 1
Результат: 12, 75
Задача 6.17
Розробити функцію, що переставляє в зворотному порядку елементи головної діагоналі квадтаної матриці.
Задача 6.18
Переформатувати марицю (двовимірний масив) таким чином, щоб її рядки розміщувалися за зростанням їх поелементних сум.
Задача 6.19
Відсортувати рядки двохвимірного масиву за зростанням. Кількість рядків і стовпців вводить користувач. Масив заповнюється випадковими числами із діапазону
Наприклад:
45 12 12 63
15 12 45 78
75 56 89 1
Результат:
12 12 45 63
12 15 45 78
1 56 75 89
Задача 6.20
Згенерувати матрицю значень
Так, для підзавдання 6.16 і матриці поданої нижче
45 12 12 63
15 12 45 78
75 56 89 1
53 75 78 21
максимум становитиме 89.