НОУ ІНТУЇТ | лекція | масиви

  1. масиви масивів
  2. Процедури і масиви
  3. Алгоритми і завдання
  4. Введення-виведення масивів
  5. Введення-виведення масивів в Windows-додатках

масиви масивів

Ще одним видом масивів C # є масиви масивів, звані також порізаними масивами (jagged arrays). Такий масив масивів можна розглядати як одновимірний масив, його елементи є масивами, елементи яких, в свою чергу знову можуть бути масивами, і так може тривати до деякого рівня вкладеності.

В яких ситуаціях може виникати необхідність в таких структурах даних? Ці масиви можуть застосовуватися для подання дерев, у яких вузли можуть мати довільне число нащадків. Таким може бути, наприклад, генеалогічне дерево. Вершини першого рівня - Fathers, що представляють батьків, можуть задаватися одновимірним масивом, так що Fathers [i] - це i-й батько. Вершини другого рівня представляються масивом масивів - Children, так що Children [i] - це масив дітей i-го батька, а Children [i] [j] - це j-й дитина i-го батька. Для уявлення онуків знадобиться третій рівень, так що GrandChildren [i] [j] [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]};

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

Наведу тепер трохи більше реальний приклад, що описує просте генеалогічне дерево, яке умовно назву "батьки і діти":

/// <summary> /// масив масивів - "Батьки і діти" /// </ summary> public void GenTree () {int Fcount = 3; string [] Fathers = new string [Fcount]; Fathers [0] = "Микола"; Fathers [1] = "Сергій"; Fathers [2] = "Петро"; string [] [] Children = new string [Fcount] []; Children [0] = new string [] { "Ольга", "Федір"}; Children [1] = new string [] { "Сергій", "Валентина", "Іра", "Дмитро"}; Children [2] = new string [] { "Марія", "Ірина", "Надія"}; Arrs.PrintAr3 (Fathers, Children); }

Тут батьків описує звичайний динамічний одновимірний масив Fathers. Для опису дітей цих батьків необхідний вже масив масивів, який також є динамічним на верхньому рівні, оскільки число його елементів збігається з числом елементів масиву Fathers. Тут показаний ще один спосіб створення таких масивів. Спочатку конструюється масив верхнього рівня, що містить посилання зі значенням void. А потім на нижньому рівні конструктор створює справжні масиви в динамічної пам'яті, з якими і зв'язуються посилання.

Я не буду демонструвати роботу з генеалогічним деревом, обмежуся лише печаткою цього масиву. Тут є кілька повчальних моментів. У класі Arrs для друку масиву створено спеціальний метод PrintAr3, якому в якості аргументів передаються масиви Fathers і Children. Ось текст цієї процедури:

/// <summary> /// Друк дерева "Батьки і діти", /// заданого масивами Fathers і Children /// </ summary> /// <param name = "Fathers"> масив батьків </ param> / // <param name = "Children"> масив масивів дітей </ param> public static void PrintAr3 (string [] Fathers, string [] [] Children) {for (int i = 0; i <Fathers.Length; i ++) {Console.WriteLine ( "Батько: {0}; Його діти:", Fathers [i]); for (int j = 0; j <Children [i] .Length; j ++) Console.Write (Children [i] [j] + ""); Console.WriteLine (); }} // PrintAr3

Наведу деякі коментарі до цієї процедури.

  • Зовнішній цикл по i організований за кількістю елементів масиву Fathers. Зауважте, тут використовується властивість Length, на відміну від раніше застосовуваного методу GetLength.
  • У цьому циклі з тим же успіхом можна було б використовувати і ім'я масиву Children. Властивість Length для нього повертає число елементів верхнього рівня, що збігається, як уже говорилося, з числом елементів масиву Fathers.
  • У внутрішньому циклі властивість Length викликається для кожного елемента Children [i], який є масивом.
  • Інші деталі, сподіваюся, зрозумілі.

Наведу висновок, отриманий в результаті роботи процедури PrintAr3.


Мал. 6.3. Дерево "Батьки і діти"

Процедури і масиви

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

  • В процедуру досить передавати тільки сам об'єкт - масив. Всі його характеристики (розмірність, кордону) можна визначити, використовуючи властивості і методи цього об'єкту.
  • Коли масив є вихідним аргументом процедури, як аргумент C в процедурі MultMatr, вихідний аргумент зовсім не обов'язково постачати ключовим словом ref або out (хоча і допустимо). Передача аргументу за значенням в таких ситуаціях так само хороша, як і передача за посиланням. В результаті обчислень змінюється сам масив в динамічної пам'яті, а посилання на нього залишається постійною. Процедура і її виклик без ключових слів виглядає простіше, тому зазвичай вони опускаються. Зауважте, в процедурі GetSizes, де визначалися межі масиву, ключове слово out, що супроводжує аргументи, абсолютно необхідно.
  • Функція може повертати масив в якості результату.

Алгоритми і завдання

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

Послідовність елементів - - одна з улюблених структур в математиці. Послідовність можна розглядати як функцію , Яка по заданому значенню індексу елемента повертає його значення. Ця функція задає відображення , де - це тип елементів послідовності. У програмуванні послідовності це одномірні масиви, але від цього вони не перестають бути менш улюбленими.

Визначення. Масив - це впорядкована послідовність елементів одного типу. Порядок елементів задається за допомогою індексів.

На відміну від математики, де послідовність може бути нескінченною, масиви завжди мають кінцеве число елементів. Для програмістів важливо те, як масиви зберігаються в пам'яті. Масиви займають безперервну область пам'яті, тому, знаючи адресу початкового елемента масиву, знаючи, скільки байтів пам'яті потрібно для зберігання одного елемента, і знаючи індекс (індекси) деякого елемента, неважко обчислити його адресу, а значить, і збережене за цією адресою значення елемента. На цьому заснована адресна арифметика в мовах C і C ++, де адреса елемента a (i) задається адресним вираженням a + i, в якому ім'я масиву a сприймається як адреса першого елемента. При обчисленні адреси i-го елемента індекс i множиться на довжину слова, необхідного для зберігання елементів типу T. Адресна арифметика використовує 0-базуються елементів масиву, вважаючи індекс першого елемента рівним нулю, оскільки першому елементу відповідає адресний вираз а + 0.

Мова C # зберіг 0-базуються масивів. Індекси елементів масиву в мові C # змінюються в щільному інтервалі значень від нижньої межі, завжди рівний 0, до верхньої межі, яка задана динамічно обчислюваним виразом, можливо, залежать від змінних. Масиви C # є 0-базуються динамічними масивами. Це важливо розуміти з самого початку.

Не менш важливо розуміти і те, що масиви C # відносяться до посилальним типам.

Введення-виведення масивів

Як у масивів з'являються значення, як вони змінюються? Можливі три основні способи:

  • обчислення значень в програмі;
  • значення вводить користувач;
  • зв'язування з джерелом даних.

У завданнях цього розділу обмежимося поки розглядом перших двох способів. Перший спосіб більш-менш зрозумілий. Прості приклади його застосування наводилися неодноразово. Варто тільки відзначити, що в класі, що працює з масивами, завжди корисно мати метод FillArray, що дозволяє заповнювати масив випадковими числами. У прикладах використання можливостей класу Random для моделювання елементів масиву зустрічалося неодноразово.

Наведу деякі рекомендації по введенню і виведенню масивів, орієнтовані на роботу з кінцевим користувачем.

Для консольних додатків введення масиву зазвичай проходить кілька етапів:

  • введення розмірів масиву;
  • створення масиву;
  • організація циклу по числу елементів масиву, в тілі якого виконується:
    • запрошення до введення чергового елемента;
    • введення елемента;
    • перевірка коректності введеного значення.

Спочатку у користувача запитуються розміри масиву, потім створюється масив заданого розміру. У циклі за кількістю елементів організовується введення значень. Запровадженню кожного значення передує запрошення до вводу із зазначенням типу значення, що вводиться, а при необхідності - і діапазону, в якому повинно знаходитися потрібну установку. Оскільки введення значень - це відповідальна операція, а на користувача ніколи не можна покластися, після введення часто організовується перевірка коректності введеного значення. При некоректному завданні значення елемента введення повторюється, поки не буде досягнуто бажаного результату.

При виведенні масиву на консоль зазвичай спочатку виводиться ім'я масиву, а потім його елементи у вигляді пари: <ім'я> = <значення> (наприклад, f [5] = 77,7). Завдання ускладнюється для багатовимірних масивів, коли користувачеві важливо бачити не тільки значення, але і структуру масиву, маючи в своєму розпорядженні рядок масиву в рядку екрану.

Як організувати контроль введення? Найбільш розумно використовувати для цих цілей конструкцію охоронюваних блоків - try - catch блоків. Це загальний підхід, коли всі небезпечні дії, пов'язані з роботою користувача, зовнішніх пристроїв, зовнішніх джерел даних, розміщуються в охоронюваних блоках.

Як правило, для введення-виведення масивів пишуться спеціальні процедури, що викликаються в потрібний момент.

Введення-виведення масивів в Windows-додатках

Додатки Windows дозволяють побудувати доброзичливий інтерфейс користувача, який полегшує роботу по введенню і виведенню масивів. І тут, коли дані задаються користувачем, заповнення масиву проходить через ті ж етапи, що розглядалися для консольних додатків. Але виглядає все це більш красиво, наочно і зрозуміло. Приклад подібного інтерфейсу, що забезпечує роботу по введенню і виведенню одновимірного масиву, показаний на Мал. 6.4 .


Мал. 6.4. Форма для введення-виведення одновимірного масиву

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

У разі успішного створення масиву користувач може переходити до наступного етапу - введення елементів масиву. Черговий елемент масиву вводиться в текстове вікно, а обробник командної кнопки "Ввести елемент" забезпечує передачу значення в масив. Коректність введення контролюється і на цьому етапі, перевіряючи значення введеного елемента і виводячи в спеціальне вікно повідомлення в разі його некоректності, домагаючись, в кінцевому підсумку, отримання від користувача коректного введення.

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

на Мал. 6.4 форма розділена на дві частини - для введення і виведення масиву. Вкрай важливо вміти організувати введення масиву, приймаючи дані від користувача. Не менш важливо вміти відображати існуючий масив в формі, зручній для сприйняття користувача. На малюнку показані три різних елемента управління, придатні для цих цілей, - ListBox, CheckedListBox і ComboBox. Як тільки вводиться черговий елемент, він негайно відображається у всіх трьох списках.

В реальності відображати масив в трьох списках, звичайно, не потрібно, це зроблено тільки з метою демонстрації можливостей різних елементів управління. Для цілей виведення підходить будь-який з них, вибір залежить від контексту і переваг користувача. Елемент ComboBox має додаткове текстове вікно, в яке користувач може вводити значення. Елемент CheckedListBox володіє додатковими властивостями в порівнянні з елементом ListBox, дозволяючи відзначати деякі елементи списку (масиву). Зазначені користувачем елементи складають спеціальну колекцію. Ця колекція доступна, з нею можна працювати, що іноді дуже корисно. Найчастіше для виведення масиву використовується елемент ListBox.

Подивимося, як це все організовано програмно. Почну з полів форми OneDimArrayForm, показаної на Мал. 6.4 :

// fields int n = 0; double [] mas; int currentindex = 0; double ditem = 0; const string SIZE = "Коректно задайте розмір масиву!"; const string INVITE = "Введіть число в форматі m [, n]"; const string EMPTY = "Масив порожній!"; const string ITEMB = "mas ["; const string ITEME = "] ="; const string FULL = "Введення недоступний!"; const string OK = "Коректне введення!"; const string ERR = "Помилка введення числа! Повторіть введення!";

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

private void buttonCreateArray_Click (object sender, EventArgs e) {try {n = Convert.ToInt32 (textBoxN.Text); mas = new double [n]; labelInvite.Text = INVITE; labelItem.Text = ITEMB + "0" + ITEME; labelResult.Text = EMPTY; textBoxItem.ReadOnly = false; listBox1.Items.Clear (); comboBox1.Items.Clear (); checkedListBox1.Items.Clear (); comboBox1.Items.Clear (); currentindex = 0; } Catch (Exception) {labelResult.Text = SIZE; }}

Насамперед приймається розмір масиву, який Ви самі ввели. Перетворення до типу int введеного значення поміщено в охоронюваний блок, тому помилки некоректного введення будуть перехоплені з видачею відповідного повідомлення. Якщо ж масив успішно створений, то не започатковано початковими значеннями всі елементи інтерфейсу, які беруть участь в введенні елементів масиву. Розглянемо, як влаштований введення елементів.

private void buttonAddItem_Click (object sender, EventArgs e) {// Заповнення масиву елементами if (GetItem ()) {mas [currentindex] = ditem; listBox1.Items.Add (mas [currentindex]); checkedListBox1.Items.Add (mas [currentindex]); comboBox1.Items.Add (mas [currentindex]); currentindex ++; labelItem.Text = ITEMB + currentindex + ITEME; textBoxItem.Text = ""; labelResult.Text = OK; if (currentindex == n) {labelInvite.Text = ""; labelItem.Text = ""; labelResult.Text = FULL; textBoxItem.Text = ""; textBoxItem.ReadOnly = true; }}}

Функція GetItem вводить значення чергового елемента. Якщо користувач коректно поставив його значення, то елемент додається в масив, а заодно і в списки, що відображають поточний стан масиву. Створюється підказка для введення наступного елемента масиву, а якщо масив повністю визначений, то форма переходить в стан закінчення введення.

/// <summary> /// Введення з контролем поточного елемента масиву /// </ summary> /// <returns> true у разі коректного введення значення </ returns> bool GetItem () {string item = textBoxItem.Text; bool res = false; if (item == "") labelResult.Text = INVITE; else {try {ditem = Convert.ToDouble (item); res = true; } Catch (Exception) {labelResult.Text = ERR; }} Return res; }

Форму OneDimArrayForm можна розглядати як певний шаблон, корисний при організації введення і виведення одновимірних масивів.

В яких ситуаціях може виникати необхідність в таких структурах даних?
Як організувати контроль введення?