Статьи

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

  1. Загальний погляд
  2. оголошення масивів
  3. Оголошення одновимірних масивів
  4. динамічні масиви
  5. багатовимірні масиви

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

Проект до даної лекції Ви можете завантажити Проект до даної лекції Ви можете завантажити   тут тут .

Загальний погляд

Масив задає спосіб організації даних. Масивом називають впорядковану сукупність елементів одного типу. Кожен елемент масиву має індекси, що визначають порядок елементів. Число індексів характеризує розмірність масиву. Кожен індекс змінюється в деякому діапазоні [a, b]. У мові C #, як і в багатьох інших мовах, індекси задаються цілочисельним типом. В інших мовах, наприклад, в мові Паскаль, індекси можуть належати счетному кінцевому безлічі, на якому визначені функції, що задають наступний і попередній елемент. Діапазон [a, b] називається граничної парою, a - нижньою межею, b - верхньою межею індексу. При оголошенні масиву кордону задаються виразами. Якщо всі межі задані константними виразами, то число елементів масиву відомо в момент його оголошення і йому може бути виділена пам'ять ще на етапі трансляції. Такі масиви називаються статичними. Якщо ж вирази, які визначають межі, залежать від змінних, то такі масиви називаються динамічними, оскільки пам'ять їм може бути відведена тільки динамічно в процесі виконання програми, коли стають відомими значення відповідних змінних. Масиву, як правило, виділяється безперервна область пам'яті.

У мові C # знято істотне обмеження мови C ++ на статичність масивів. Масиви в мові C # є динамічними. Як наслідок цього, нагадаю, масиви відносяться до посилальним типам, пам'ять їм відводиться динамічно в "купі". На жаль, не знято обмеження 0-базуються, що означає, що нижня межа масивів C # фіксована і дорівнює нулю. Було б набагато зручніше в багатьох задачах мати можливість працювати з масивами, у яких нижня межа зміни індексу не дорівнює нулю.

У мові C ++ "класичних" багатовимірних масивів немає.Тут введені одновимірні масиви і масиви масивів.Останні є більш загальною структурою даних і дозволяють задати не тільки багатовимірний куб, а й порізаний, ступінчасту структуру.Однак використання масиву масивів менш зручно, і, наприклад, класик і автор мови C ++ Бьерн Страуструп в своїй книзі "Основи мови C ++" пише: "Вбудовані масиви є головним джерелом помилок - особливо коли вони використовуються для побудови багатовимірних масивів. Для новачків вони також є головним джерелом збентеження і нерозуміння. По можливості користуйтеся шаблонами vector, valarray і т. п. "

.

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

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

оголошення масивів

Розглянемо, як оголошуються одновимірні масиви, масиви масивів і багатовимірні масиви.

Оголошення одновимірних масивів

Нагадаю загальну структуру оголошення:

[<Атрибути>] [<модифікатори>] <тип> <об'явітелі>;

Забудемо поки про атрибути і модифікатори. Оголошення одновимірного масиву виглядає наступним чином:

<Тип> [] <об'явітелі>;

Зауважте, на відміну від мови C ++ квадратні дужки приписані ні до імені змінної, а до типу. Вони є невід'ємною частиною визначення типу, так що запис T [] слід розуміти як тип, що задає одновимірний масив з елементами типу T.

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

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

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

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

Слідуючи синтаксису, елементи константного масиву необхідно укладати в фігурні дужки.

У другому випадку створення і ініціалізація масиву виконується в об'єктному стилі з викликом конструктора масиву. І це найбільш поширена практика оголошення масивів. Наведу приклад:

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

Як зазвичай задаються елементи масиву, якщо вони не задані при ініціалізації? Вони або обчислюються, або вводяться користувачем. Давайте розглянемо перший приклад роботи з масивами з проекту з ім'ям Arrays, що підтримує цю лекцію:

public void TestDeclaration () {// оголошуються три одновимірних масиву A, B, C int [] A = new int [5], B = new int [5], C = new int [5]; Arrs.CreateOneDimAr (A); Arrs.CreateOneDimAr (B); for (int i = 0; i <5; i ++) C [i] = A [i] + B [i]; // оголошення масиву з явною ініціалізацією int [] x = {5,5,6,6,7,7}; // оголошення масивів з відкладеною ініціалізацією int [] u, v; u = new int [3]; for (int i = 0; i <3; i ++) u [i] = i + 1; // v = {1,2,3}; // присвоювання константного масиву неприпустимо v = new int [4]; v = u; // допустиме присвоювання Arrs.PrintAr1 ( "A", A); Arrs.PrintAr1 ( "B", B); Arrs.PrintAr1 ( "C", C); Arrs.PrintAr1 ( "X", x); Arrs.PrintAr1 ( "U", u); Arrs.PrintAr1 ( "V", v); }

На що слід звернути увагу, аналізуючи цей текст?

  • У процедурі показані різні способи оголошення масивів. Спочатку оголошуються одновимірні масиви A, B і C, створювані конструктором. Значення елементів цих трьох масивів мають один і той же тип int. Те, що вони мають однакове число елементів, відбулося з волі програміста, а не диктувалося вимогами мови. Зауважте, що після такого оголошення з ініціалізацією конструктором всі елементи мають значення, в даному випадку - нуль, і можуть брати участь в обчисленнях.
  • Масив x оголошений з явною ініціалізацією. Число і значення його елементів визначається сталою масивом.
  • Масиви u і v оголошені з відкладеним ініціалізацією. У наступних операторах масив u инициализируется в об'єктному стилі, його елементи отримують в циклі значення.
  • Зверніть увагу на закоментувавши оператор присвоювання. На відміну від ініціалізації використовувати константних масив в правій частині оператора присвоювання неприпустимо. Ця спроба призводить до помилки, оскільки v - це посилання, якою можна привласнити посилання, але не можна привласнити константний масив. Посилання привласнити можна. Що відбувається в операторі присвоєння v = u.? Це коректне Посилальне присвоювання: хоча u і v мають різну кількість елементів, але вони є об'єктами одного класу. В результаті привласнення пам'ять, відведена масиву v, звільниться, їй займеться тепер збирач сміття. Обидві посилання u і v будуть тепер вказувати на один і той же масив, так що зміна елемента одного масиву негайно відбивається на іншому масиві.
  • Для підтримки роботи з масивами створено спеціальний клас Arrs, ​​статичні методи якого виконують різні операції над масивами. Зокрема, в прикладі використані два методи цього класу, один з яких заповнює масив випадковими числами, другий - виводить масив на друк.

Ось текст першого з цих методів:

public static void CreateOneDimAr (int [] A) {for (int i = 0; i <A.GetLength (0); i ++) A [i] = rnd.Next (1,100); } // CreateOneDimAr

Тут rnd - це статичне поле класу Arrs, ​​оголошене в такий спосіб:

private static Random rnd = new Random ();

Процедура друку масиву з ім'ям name виглядає так:

public static void PrintAr1 (string name, int [] A) {Console.WriteLine (name); for (int i = 0; i <A.GetLength (0); i ++) Console.Write ( "\ t" + name + "[{0}] = {1}", i, A [i]); Console.WriteLine (); } // PrintAr1

на Мал. 6.1 показаний консольний висновок результатів роботи процедури TestDeclarations:


Мал.6.1.

Результати оголошення і створення масивів

Особливу увагу зверніть на висновок, пов'язаний з масивами u і v.

динамічні масиви

У всіх вищенаведених прикладах оголошувалися статичні масиви, оскільки нижня межа дорівнює нулю за визначенням, а верхня завжди задавалася в цих прикладах константою. Нагадаю, що в C # все масиви, незалежно від того, яким виразом описується межа, розглядаються як динамічні і пам'ять для них розподіляється в "купі". Вважаю, що це відображення розумної точки зору: адже статичні масиви швидше виняток, а правилом є використання динамічних масивів. Дійсно реальні потреби в розмірі масиву, швидше за все, з'ясовуються в процесі роботи в діалозі з користувачем.

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

Наведу приклад, в якому описана робота з динамічним масивом:

public void TestDynAr () {// оголошення динамічного масиву A1 Console.WriteLine ( "Введіть число елементів масиву A1"); int size = int.Parse (Console.ReadLine ()); int [] A1 = new int [size]; Arrs.CreateOneDimAr (A1); Arrs.PrintAr1 ( "A1", A1); } // TestDynAr

В особливих коментарів ця процедура не потребує. Тут верхня межа масиву визначається користувачем.

багатовимірні масиви

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

Розмірність масиву це характеристика типу. Як синтаксично при оголошенні типу масиву вказати його розмірність? Це робиться досить просто, за рахунок використання ком. Ось як виглядає оголошення багатовимірного масиву в загальному випадку:

<Тип> [...,] <об'явітелі>;

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

public void TestMultiArr () {int [,] matrix = {{1,2}, {3,4}}; Arrs.PrintAr2 ( "matrix", matrix); } // TestMultiArr

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

public void TestMultiMatr () {int n1, m1, n2, m2, n3, m3; Arrs.GetSizes ( "MatrA", out n1, out m1); Arrs.GetSizes ( "MatrB", out n2, out m2); Arrs.GetSizes ( "MatrC", out n3, out m3); int [,] MatrA = new int [n1, m1], MatrB = new int [n2, m2]; int [,] MatrC = new int [n3, m3]; Arrs.CreateTwoDimAr (MatrA); Arrs.CreateTwoDimAr (MatrB); Arrs.MultMatr (MatrA, MatrB, MatrC); Arrs.PrintAr2 ( "MatrA", MatrA); Arrs.PrintAr2 ( "MatrB", MatrB); Arrs.PrintAr2 ( "MatrC", MatrC); } // TestMultiMatr

Три матриці MatrA, MatrB і MatrC мають довільні розміри, з'ясовуються в діалозі з користувачем, і використання для їх опису динамічних масивів можна вважати цілком природним. Метод CreateTwoDimAr заповнює випадковими числами елементи матриці, переданої йому в якості аргументу, метод PrintAr2 виводить матрицю на друк. Я не буду приводити їх код, схожий на код їх одновимірних аналогів.

Метод MultMatr виконує множення прямокутних матриць. Це класична задача з набору завдань, що вирішуються на першому курсі. Ось текст цього методу:

public void MultMatr (int [,] A, int [,] B, int [,] C) {if (A.GetLength (1)! = B.GetLength (0)) Console.WriteLine ( "MultMatr: помилка розмірності! "); else for (int i = 0; i <A.GetLength (0); i ++) for (int j = 0; j <B.GetLength (1); j ++) {int s = 0; for (int k = 0; k <A.GetLength (1); k ++) s + = A [i, k] * B [k, j]; C [i, j] = s; }} // MultMatr

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

Погляньте, як виглядають результати консольного виведення на даному етапі роботи.

Як зазвичай задаються елементи масиву, якщо вони не задані при ініціалізації?
Як синтаксично при оголошенні типу масиву вказати його розмірність?

Новости