ТЕМА 19: Масиви
. Масиви. Оператор за цикъл foreach
Масивът е n-мерна наредена съвкупност от елементи от един и същи тип. Масивите могат да бъдат едномерни, двумерни, тримерни и т.н. макар че най-често се използват едномерните. С указване името на масива и индекси може да се използва съответния елемент на масива. Данните в масива са организирани така, че те лесно и удобно да бъдат обработвани. Масивите в С# имат една особеност, която се състои в това, че те са реализирани като обекти. Това дава допълнителни възможности, например отстраняване от паметта на неизползваните масиви при събиране на боклука. Дефинирането на едномерен масив има следния общ вид:
тип[] име_на_масива = new тип[размер];
Тук „тип” задава типа на данните на всеки елемент на масива. [] указват, че се дефинира едномерен масив. „Размер” определя броя на елементите на масива. Тъй като масива е реализиран като обект, то процесът на създаване на масива включва два етапа. На първия етап се обявява името на масива като променлива от указателен тип. На втория етап – за масива се разпределя памет и на името на масива се присвоява адреса на тази област от паметта, т.е. в С# с помощта на new се разпределя динамична памет за масива. При дефиниране на масива в С# [] са след „тип”, а не след името на масива, както е в С/С++. Например:
int[] sample = new int[10];
Се дефинира масива sample, който се състои от 10 елемента от тип int. Това може да стане и така:
int[] sample;
sample = new int[10];
В този случай, след дефинирането на променливата на масива sample, тя няма да сочи към физически обект. Само след изпълнението на втория оператор, на sample ще се присвои адреса на масива. Достъпът до отделен елемент на масива се осъществява с използване на индекс. Първият елемент на всички масиви в С# има нулев индекс. Например:
int[] sample;
sample = new int[10];
int i;
for (i = 0; i < 10; i++)
sample[i] = i;
for (i = 0; i < 10; i++)
Console.WriteLine("На елемента sample[{0}] е била присвоена стойност = {1}", i, sample[i]);
Да съставим програма за намиране на минималната и максимална стойност на елементите на масив.
using System;
class MinMax
{
public static void Main()
{
int[] nums = new int[10];
int min, max;
nums[0] = 99;
nums[1] = -10;
nums[2] = 100123;
nums[3] = 18;
nums[4] = -978;
nums[5] = 5623;
nums[6] = 463;
nums[7] = -9;
nums[8] = 287;
nums[9] = 49;
min = max = nums[0];
for (int i = 0; i < 10; i++)
{
if (min > nums[i])
min = nums[i];
if (max < nums[i])
max = nums[i];
}
Console.WriteLine("Най-големият елемент в масива е {0}, а най-малкият е {1}", max, min);
}
};
В този пример стойностите на елемента на масива nums бяха зададени ръчно с използване на 10 отделни оператора за присвояване. Съществува по-ефективен начин за изпълнение на тези действия. Масива може да се инициализира веднага при неговото създаване. Инициализирането на едномерен масив има следния общ вид:
тип[]име_на_масива={стойност1, стойност2, … , стойностN};
На елементите на масива, започвайки от първия, т.е. с индекс 0, се присвояват зададените стойности от ляво на дясно. В С# за масива автоматично се разпределя памет, достатъчна за съхраняване на зададените стойности, без да е необходимо явно да се използва операцията new. Да съставим по-ефективно предната програма:
using System;
class MinMax
{
public static void Main()
{
int min, max;
int[] nums = { 99, -10, 100123, 18, -978, 5623, 463, -9, 287, 49 };
min = max = nums[0];
for (int i = 0; i < 10; i++)
{
if (min > nums[i])
min = nums[i];
if (max < nums[i])
max = nums[i];
}
Console.WriteLine("Най-големият елемент в масива е {0}, а най-малкият е {1}", max, min);
}
};
Зададените стойности се присвояват на елементите на масива при неговото създаване. Инициализирането на масива nums може да стане и с използване на операцията new, но това не е необходимо. Това става така:
int[] nums = new int[]{ 99, -10, 100123, 18, -978, 5623, 463, -9, 287, 49 };
Такъв начин на инициализация може да се използва за присвояване адреса на нов масив на вече съществуваща променлива от указателен тип. Например:
int[] nums;
nums=new int[]{ 99, -10, 100123, 18, -978, 5623, 463, -9, 287, 49 };
В този случай променливата на масива nums се дефинира в първия оператор, а се инициализира във втория.
В С# строго се контролира обръщението към елемент на масив. Ако индекса е извън обявените граници, възниква грешка по време на изпълнение на програмата. В С/С++ такъв контрол не се прави(за повече бързина).
int[] sample = new int[10];
int i;
for (i = 0; i < 100; i++) //генерира се обект изключение IndexOutOfRangeException()
sample[i] = i; //и завършва изпълнението на програмата
Ще непишем програма за сортировка, реализираща метода на мехурчето. Този метод може да се използва за не големи масиви, но става неефективен при сортиране елементите на големи масиви.
using System;
class Bubble
{
public static void Main()
{
int[] nums = { 99, -10, 100123, 18, -978, 5623, 463, -9, 287, 49 };
int i, pos, temp;
int size;
bool change;
size = 10;
pos = 1;
do
{
change = false;
for (i = 0; i < size - pos; i++)
{
if (nums[i] > nums[i + 1])
{
temp = nums[i];
nums[i] = nums[i + 1];
nums[i + 1] = temp;
change = true;
}
}
pos++;
} while (change);
Console.WriteLine("\nМасивът след сортирането:");
for (i = 0; i < size; i++)
Console.Write(" " + nums[i]);
Console.WriteLine();
}
}
Многомерните масиви имат две или повече размерности, а достъпът до техните елементи се осъществява с два или повече индекса. Двумерният масив може да се разглежда като информационна таблица или матрица, като единият индекс дава номер на ред, а другият – номер на стълб. Например, двумерният цял масив table с размерност 10х20 се дефинира така:
int[,]table = new int[10, 20];
Броят на елементите на масива е равен на произведението от горните граници на размерностите, т.е. 10х20=200. В първата част на дефиницията, с помощта на запетая в квадратните скоби, се указва, че се създава променлива от указателен тип за двумерен масив. Разпределяне на памет за масива с new става при изпълнение на втората част от дефиницията на масива, като горните граници на размерностите са разделени със запетаи. За получаване на достъп до елемент на двумерен масив е необходимо да се зададат два индекса, разделени със запетаи. Например:
table[3, 5]=10;
В С# дефинирането и достъпът до елементите на неедномерните масиви се различава от това в С/С++ и Java. При тях всяка размерност на масива и индексите се задават в отделни квадратни скоби. Да разгледаме програма, в която на елементите на двумерен масив се присвояват стойности от 1 до 12 и след това тези стойности се извеждат на екрана.
using System;
class TwoD
{
public static void Main()
{
int t, i;
int[,] table = new int[3, 4];
for (t = 0; t < 3; t++)
{
for (i = 0; i < 4; i++)
{
table[t, i] = t * 4 + i + 1;
Console.Write(table[t, i] + " ");
}
Console.WriteLine();
}
}
};
Дефинирането на многомерен или n-мерен масив има следния общ вид:
тип[, …, ] име =new тип[размер1, …, размерN];
Например, при:
int[, ,] MD = new int[4, 10, 4];
Ще бъде създаден тримерен масив с размерност 4х10х3, или 120 елемента. Примерно присвояване:
MD[2, 4, 1] = 100;
Многомерен масив може да се инициализира като се включи списък от стойности за всяка размерност в отделен блок, затворен във фигурни скоби. Например, инициализирането на двумерен масив има следния общ вид:
тип[,]име={ {стойност, стойност, …, стойност}, {стойност, стойност, …, стойност}, …, {стойност, стойност, …, стойност} };
Блоковете, съдържащи стойности са разделени със запетаи, а най-накрая има ;. Всеки вътрешен блок присвоява стойности на елементите от съответния ред. В рамките на реда, първата стойност ще се присвои на елемента от ред с индекс 0, втората – с индекс 1 и т.н. Пример:
int[,]ex={{1, 1}, {2, 4}, {4, 5}, {5, 6}, {1, 1}, {8, 64}};
Разгледаните двумерни масиви мога да бъдат наречени правоъгълни или изравнени масиви, тъй като те имат редове с еднаква дължина. В С# могат да бъдат създавани и двумерни, неизравнени масиви. Те представляват външен масив, който се състои от вътрешни масиви с различна дължина. Следователно, неизравненият масив може да се използва за създаване на таблица, съдържаща редове с различна дължина. При дефиниране на неизравнен масив, за задаване на всяка размерност, се използва отделна двойка квадратни скоби. Например, двумерен, неизравнен масив се дефинира по следния начин:
тип[][]име_на_масива=new тип[брой_редове][];
Размерът на редовете не се задава и памет за тях не се разпределя. /*:D:D:D:D */ За всеки ред е необходимо изпълнение на отделна операция new. Тази технология позволява да се определят редове с различна дължина. Например:
int[][] array = new int[3][];
При дефиниране на масива array памет се разпределя само за външния масив.
array[0] = new int[2];
array[1] = new int[3];
array[2]=new int[4];
При изпълнение на тези три оператора, памет се разпределя отделно за всеки вътрешен масив. Когато неизравнения масив е създаден, достъпът до неговите елементи се получава като се зададе всеки индекс в двойка квадратни скоби. Например:
array[2][1]=10;
Използването на неизравнени двумерни масиви е ефективно, когато те са големи и не са изцяло запълнени.
Когато стойността на една променлива от указателен тип, която сочи към масив, се присвоява на друга променлива от този тип, то на втората променлива се присвоява адреса на същия масив, към който сочи първата. При това не се създава копие на масива и не се копира съдържанието на единия масив в другия. Да разгледаме един пример:
int i;
int[] nums1 = new int[10];
int[] nums2 = new int[10;
for (i=0; i<10; i++)
nums1[i]=i;
for (i=0; i<10; i++)
nums2[i]=-i;
nums2=nums1; //сега nums2 сочи към същия масив, какъвто е nums1
nums2[3]=99; //фактически се прави промяна на nums1[3]
В С# масивите са реализирани като обекти и това два определени преимущества при работа с тях. Едно такова преимущество е възможността да се използва свойството Length, което съдържа информация за максималния брой елементи на масива. Ще разгледаме пример за копиране на един масив във друг с използване на свойството Length:
using System;
class ACopy
{
public static void Main()
{
int[] nums1 = new int[10];
int[] nums2 = new int[10];
for (int i = 0; i < nums1.Length; i++)
{
nums1[i] = i;
}
if (nums2.Length >= nums1.Length) //проверява се дали позволява размера
//на масива nums2 да се прекопират
{ //всички елементи на масива nums1
for (int i = 0; i < nums1.Length; i++)
{
nums2[i] = nums1[i];
}
}
for (int i = 0; i < nums1.Length; i++)
Console.WriteLine(nums2[i]+" ");
}
};
Свойството Length позволява да се създава код, който може да работи с масиви с различна дължина. Това опростява алгоритмите и ги прави по-ефективни.
Особено внимание трябва да се обърне на използване на свойството Length в двумерни, неизравнени масиви. Например:
int[][] table = new int[3][];
table[0] = new int[] { 1, 2, 3 };
table[1] = new int[] { 4, 5 };
table[2] = new int[] { 6, 7, 8, 9 };
table.Length; //дава броя на масивите, които се съдържат в масива table, т.е.3
//за да се получи дължината на всеки вътрешен масив,
//във външния масив е необходимо да се използва следния израз:
table[0].Length; //3
table[1].Length; //2
table[2].Length; //4
Пример за последователно опашка реализирана чрез масив:
using System;
class Queue
{
public char[] q;
public int putloc, getloc;
public Queue(int size)
{
q = new char[size+1];
putloc=getloc=0;
}
public void put(char ch)
{
if(putloc==q.Length-1)
{
Console.WriteLine(“Opa6kata e zapalnena”);
return;
}
putloc++;
q[putloc]=ch;
}
Public char get()
{
if(getloc==putloc)
{
Console.WriteLine(“Opa6kata e prazna”);
return (char)0;
}
getloc++;
return q[getloc];
}
}
class QDemo
{
public static void Main()
{
Queue Q = new Queue(26);
char ch;
int i;
for(i=0; i<26; i++)
Q.put((char)(‘A’+i));
Q.put(‘Z’);
for(i=0; i<26; i++)
{
ch==Q.get();
if(ch!=(char)0)
Console.Write(ch);
}
Conslole.WriteLine();
Ch=Q.get();
}
}
Оператора foreach се използва за обработка на елементи от колекции. Колекцията е набор от обекти. Масива е една от пиковите колекции в C#. Оператора за цикъл foreach има следния прототип:
foreach (тип име_на_итерационна_променлива in колекция)
оператор;
При изпълнение на цикъла итерационната променлива ще приема за стойности елементите на колекцията. Типа на итерационната променлива трябва да е съвместим с типа на колекцията, например масива. При работа с масиви итерационната променлива е достъпна само за четене. Следователно оператора за цикъл foreach не може да се използва за присвояване на нови стойности на елементите на масива. Също така, не може да се осъществява достъп до елементите на масива в обратен ред. Пример:
using System;
class ForeachDemo
{
public static void Main()
{
int suma = 0;
int[] nums = new int[10];
for(int i=0; i<10; i++)
nums[i] = i;
foreach(int x in nums)
{
Console.WriteLine(“Stoinosta na elementa na masiva=” + x);
suma+=x;
}
Console.WriteLine(“Sumata ot stoinostite na vsi4ki elementi na masiva=” + suma);
}
}
От резултата на изпълнението на програмата се вижда, че при достъпа на елементите на масива, индекса нараства. С помощта на foreach могат да се обработват многомерни масиви. Достъпът на елементите е по редове. От първия до последния елемент в реда.
using System;
class ForeachDemo
{
public static void Main()
{
int suma = 0;
int[,] nums = new int[3,5];
for(int i=0; i<3; i++)
for(int j=0; j<5; j++)
nums[i,j]=(i+1)*(j+1);
foreach(int x in nums)
{
Console.WriteLine(“Stoinosta na elementa na masiva=” + x);
suma+=x;
}
Console.WriteLine(“Sumata ot stoinostite na vsi4ki elementi na masiva=” + suma);
}
}
Да използваме foreach за намиране на минималната и максималната стойност в масив:
using System;
class MinMax
{
public static void Main()
{
int[] nums = {99, -10, 100123, 18, -978, 5623, 463, 287, 49}
int min,max;
min=max=nums[0];
foreach(int value in nums)
{
if(value<min)
min=value;
if(value>max)
max=value;
}
Console.WriteLine(“Min=” + min + “Max=” + max);
}
}
Също така foreach се използва за пресмятане на средната стойност, търсене на стойност или елемент и копиране на масив.
