Тема 6. Основи роботи з масивами

Автор

Юрій Клебан

Опубліковано

2/2/25

Keywords

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.

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

int[] а, b, с;

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

double[] x= {5.5, 6.6, 7.7};

Слідуючи синтаксису, елементи константного масиву слід брати у фігурні дужки. У другому випадку створення і ініціалізація масиву виконується в об’єктному стилі з викликом конструктора масиву. І це найбільш поширена практика оголошення масивів. Наведу приклад:

int[] d= new int[5];

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


6.2.2. Ініціалізація

Ініціалізувати масиви, наприклад, можна наступними способами:

  1. Літералами відповідного типу (Лістинг 6.1).
  2. Випадковими числами (Лістинг 6.2).
  3. Ввести з клавіатури (Лістинг 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]);
  }

Результат виконання:

  54 81 42 92 65 70 42 34 0 51

Ввести з клавіатури значення масиву можна наступним чином:

Лістинг 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, зустрічаються часто.

От як виглядає оголошення багатовимірного масиву в загальному випадку:

int[,] array;

Приклад ініціалізації:

int[,]matrix = {1,2},{3,4};

Давайте розглянемо класичне завдання додавання прямокутних матриць. Нам знадобиться три динамічні масиви для представлення матриць і три методи, один з яких заповнюватиме вхідні матриці випадковими числами (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], оскільки він не задає порізаний масив. Фактично потрібно викликати конструктор для кожного масиву на нийнижчому рівні. У цьому і полягає складність оголошення таких масивів.

Розпочнемо з формального прикладу:

  int[][] jagger = new int[3][]
  {
    new int[] {5,7,9,11},
    new int[] {2,8},
    new int[] {6,12,4}
  };

Масив jagger має всього два рівні. Можна вважати, що у нього три елементи, кожен з яких є масивом. Для кожного такого масиву необхідно викликати конструктор new, аби створити внутрішній масив. У даному прикладі елементи внутрішніх масивів набувають значення, будучи явно ініціалізовані константними масивами. Звичайно, допустимим є і таке оголошення:

  int[][] jagger1 = new int[3][]
  {
    new int[4],
    new int[2],
    new int[3]
  };

В цьому випадку елементи масиву набудуть при ініціалізації нульових значень. Реальну ініціалізацію потрібно буде виконувати програмним шляхом. Варто відмітити, що в конструкторі верхнього рівня константу 3 можна опустити і писати просто new int[][]. Також виклик цього конструктора можна взагалі опустити - він матиметься на увазі:

  int[][] jagger2 =
  {
    new int[4],
    new int[2],
    new int[3]
  };

Оголошувати вкладені масиви обов’язково.


6.5. Цикл foreach та масиви

У лекції про цикли загдувалося, що у мові С# визначений цикл foreach, але детальний його розгляд був відкладений «на потім». Час для нього настав.

Цикл foreach використовується для опису елементів колекції. Колекція – це група об’єктів. С# визначає декілька типів колекцій, і одним з них є масив. Формат запису циклу foreach має такий вигляд:

  foreach (тип ім'я_змінної in колекція) 
  {
    //інструкції;
  }

Тут елементи тип та ім’я_змінної задають тип та ім’я ітераційної змінної, яка при функціонуванні циклу 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. Сортування масивів [-]

Note

Розділ у процесі наповнення


6.7. Робота з класом Array [-]

Note

Розділ у процесі наповнення


Задачі


Задача 6.1

Написати програму, що знаходить суму парних та суму непарних елементів масиву.Кількість елементів визначає користувач, елементи генеруються випадковим чином у діапазоні [10;100] (цілі числа).


Задача 6.2

Написати програму, що знаходить і виводить на консоль у цілочисельному масиві усі парні значення кратні 3-м. Елементи масиву генеруються випадковим чином у діапазоні [100,200]. Кількість елементів визначає користувач.


Задача 6.3

Написати програму, яка знаходить в масиві найменше непарне число і показує його на екран. Елементи масиву генеруються випадковим чином у діапазоні [1,1000]. Кількість стовпців вводить користувач.


Задача 6.4

Написати програму, що сортує масив у вказаному користувачем порядку: за зростанням або за спаданням. Елементи масиву генеруються випадковим чином у діапазоні [100,200]. Кількість елементів визначає користувач.


Задача 6.5

Дано пустий масив розмірності 5х5 елементів. Заповнити його випадковими значеннями з діапазону [10;20] і вивести на консоль.


Задача 6.6

Задано масив із n дійсних чисел. Обчислити різницю між максимальним та мінімальним за модулем елементами цього масиву. Елементи масиву генеруються випадковим чином. Кількість стовпців та рядків вводить користувач.


Задача 6.7

Дано одновимірний масив із дійсних чисел. Відсортувати його таким чином, щоб всі додатні елементи знаходилися на початку, а всі від’ємні – в кінці, і при цьому зберігся початковий порядок елементів в обох групах.


Задача 6.8

Написати програму, що обчислює середнє значення серед парних елементів масиву. Елементи генеруються випадковим чином у діапазоні [10; 100). Кількість елементів визначає користувач.


Задача 6.9

Написати програму, що генерує вектор випадкових елементів з діапазону [100;200] та виводить його на консоль. Усі елементи вектора повинні бути кратними 7-ми. Генерацію випалкового числа винести в окремий метод.


Задача 6.10

Написати програму, що дозволяє маніпулювати елементами цілочисельного масиву. Суть роботи програми полягає у наступному: через діалог з користувачем потрібно реалізувати функції: додавання, видалення, вставки числа у існуючий масив.

На початку роботи програми масив уже повинен бути ініціалізований 5-ма випадковими значеннями з діапазону 10;500.


Задача 6.11

Задано одновимірний масив. Знайти два серед його елементів, модуль різниці яких має найменше значення.


Задача 6.12

Написати програму, яка знаходить суму парних і суму непарних елементів масиву. Елементи масиву генеруються випадковим чином. Кількість стовпців та рядків вводить користувач.


Задача 6.13

Сформувати цілочисельний масив, елементами якого є випадкові числа із діапазону [100;300]. Знайти суму елементів масиву, значення яких кратні 8.


Задача 6.14

Знайти мінімальний елемент серед тих елементів масиву A, які не є елементами масиву B.


Задача 6.15

Визначити, скільки різних чисел міститься в заданому цілочисельному масиві.


Задача 6.16

Написати програму, яка знаходить в масиві значення, що повторюються два і більше разів, і показує їх на екран. Елементи масиву генеруються випадковим чином. Кількість стовпців та рядків вводить користувач. Наприклад:

  45  12  12  63
  15  12  45  78
  75  56  89  1
  
  Результат: 12, 75

Задача 6.17

Розробити функцію, що переставляє в зворотному порядку елементи головної діагоналі квадтаної матриці.


Задача 6.18

Переформатувати марицю (двовимірний масив) таким чином, щоб її рядки розміщувалися за зростанням їх поелементних сум.


Задача 6.19

Відсортувати рядки двохвимірного масиву за зростанням. Кількість рядків і стовпців вводить користувач. Масив заповнюється випадковими числами із діапазону [10;100).

Наприклад:

  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

Згенерувати матрицю значень nn (квадратну). Знайти максимальний елемент у виділеному кольором діапазоні масиву:

Так, для підзавдання 6.16 і матриці поданої нижче

  45  12  12  63
  15  12  45  78 
  75  56  89  1
  53  75  78  21

максимум становитиме 89.