Press "Enter" to skip to content

[C#] Tablice

(to też post na konkurs)

Tablice są zmiennymi tego samego typu, ułożone w pamięci sąsiednio tak, że wiedząc którym kolejno elementem od zerowego jest poszukiwany, możemy się do niego dostać. Możemy sobie wyobrazić tablicę jak pociąg złożony z wagoników (każdy z tych wagoników może przewozić tylko przedmioty jednego typu, na przykład kosz jabłek, lub samochód). Aby wyobrazić sobie drugi wymiar tablicy, trzeba by wyimaginować sobie pociąg, który jechałby jednocześnie po dwóch torach (na chwilę zapomnijmy że te tory powinny iść w przeciwnych kierunkach), więc jednym wymiarem będzie numer toru, drugim numer wagonika (liczone od zera). Dlaczego zmienne zgrupowane w tablicy muszą mieć ten sam typ? Jest to spowodowane koniecznością wyliczenia miejsca, gdzie znajduje się element o danym indeksie. Tak naprawdę komputerowi wystarczy, aby elementy zajmowały tę samą ilość pamięci – ale język jest napisany w taki a nie inny sposób i elementy muszą być tego samego typu i już.
Deklaracja najprostszej tablicy wygląda następująco: typ[] nazwa; – na przykład koszjablek[] mojkoszyk; gdzie typem może być cokolwiek: zmienna prosta, zmienna zadeklarowana przez nas, enum, struktura, tablica dowolnego typu, tablica tablic tablic, jakiś wskaźnik, słowem wszystko.
Aby zdefiniować tablicę, należy użyć operatora new. Trzymając się przykładu powyżej, byłoby to koszjablek[] koszyki = new koszjablek[10]; Zapis ten oznacza, że mamy nową tablicę złożoną z 10 koszyków jabłek położonych jeden przy drugim. Jeśli chcemy podmienić któryś z koszyków (np. bo poprzedni już jest pusty, a chcemy go podmienić pełnym) użyjemy instrukcji przypisania: mojkoszyk[2] = jakiskosz; Oczywiście zmienna jakiskosz musi być typu koszjablek. Właśnie wymieniliśmy trzeci koszyk (ponieważ liczymy od zera). Ogólnie mamy więc nazwa_tablicy[indeks] = wartosc; . Pamiętajmy, że indeks musi być liczbą znajdującą się w przedziale od 0 do (nazwa_tablicy.Length -1).
Możemy naszą tablicę wypełnić wartością w chwili tworzenia. Nie musimy wtedy podawać ilości jej elementów (w przypadku jagged dotyczy to tylko ostatniego wymiaru). Przykładem niech będzie tablica liczb zawierających kolejne potęgi dwójki:

  int[] bin = { 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048};

Jak widać, stworzona została tablica intów o podanych wartościach. Innym sposobem zapisu powyższego będzie :

  int[] bin = new int[]{ 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048};

Wypisanie wszystkich elementów tablicy wyglądałoby tak:

  
foreach (int liczba in bin)
{
Console.WriteLine(liczba);
}

W przeciwieństwie języka C/C++ tablice w C# mogą mieć wiele wymiarów. Wymiary te zapisujemy w tym samym nawiasie kwadratowym po przecinku. Spójrzmy na przykład na taką (celowo są 4 wymiary – bo te powyżej 3 wymiarów trudno sobie nawet wyobrazić):

  
//jakiś kod powyżej
int[,,,] tab = new int[5, 10, 2 ,4];
System.Console.WriteLine("Mamy do czynienia z tablicą {0} wymiarową.", tab.Rank);

Każdy z wymiarów możemy potraktować np. jako warstwę czegoś na mapie w jakiejś grze
Pierwsze 3 wymiary to byłaby długość wysokość i szerokość, a 4 to typ „klocka”.
Powyżej jest napisane, że zmienne muszą być jednego typu. I jest to prawda. Ale przecież tym typem może być równie dobrze object – a wiedząc co jest w którym miejscu tablicy użyć rzutowania.
Ktoś mógłby się zastanawiać jak to możliwe, skoro obiekty mają różny rozmiar. A mogą sobie mieć, przecież obiekt jest typem referencyjnym – więc w naszej tablicy są przechowywane wyłącznie referencje do tych obiektów – one same leżą sobie gdzieś na stercie i nie musimy się nimi przejmować. Mówiąc referencja myślimy o adresach, a te na danej platformie sprzętowej mają zawsze taką samą wielkość w bajtach (dla platformy 32 bit będzie to 4 bajty, dla 64 bit 8 bajtów).
Zazwyczaj używamy tablic dwuwymiarowych. Przykład inicjalizacji takiej tablicy wygląda następująco:

  
int[,] tablica = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };

Przykładowy odczyt elementu:

  
System.Console.WriteLine("Element({0},{1})={2}", 2, 1, tablica[2, 1]);

Mamy jeszcze taki twór jak tablice postrzępione (jagged), czyli normalne, znane chociażby z C++ tablice tablic. Możemy ją zdefiniować tak

  
int[][] postrzepiona = new int[5][];

Albo na przykład tak:

  
int[][] strzep = new int[][]
{
new int[4],
new int[7],
new int[5]
};

Możemy od razu wypełnić tablicę danymi, pisząc kod analogiczny do tego:

  
int[][] zainicjalizowanyStrzep =
{
new int[] {1,2},
new int[] {3,4,5,6},
new int[] {7,8,9}
};

Aby dobrać się do konkretnej wartości z powyższego twora i wypisać ją na ekran, można użyć takiej linijki:

  
Console.WriteLine(zainicjalizowanyStrzep[1][2]);

Możemy zainicjalizować również tylko jeden ze „strzępków” (czyli jedną z „podtablic) w następujący sposób:

  
int[][] InnaPostrzepiona = new int[6][];
InnaPostrzepiona[0] = new int[4] { 1, 2, 3, 4 };

Należy pamiętać, że nie ważne czy weźmiemy tablicę jedno-, wielowymiarową czy postrzępioną, zawsze ilość wymiarów i długość każdego z nich jest podawana w momencie jej tworzenia. Jednak jeżeli się okaże, że wielkość wymiarów nam nie pasuje, to możemy to zmienić, tworząc nowy obiekt i przypisując go do starej zmiennej. Jednym słowem poniższy kod:

  
int[,] tablica = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
System.Console.WriteLine("Element({0},{1})={2}", 2, 1, tablica[2, 1]);
//jakieś operacje
tablica = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, {9, 10} };
//jakieś inne operacje

jest jak najbardziej poprawny. Dlaczego? Ponieważ mamy do czynienia z zupełnie nową tablicą, przypisaną do starej referencji.
Jeżeli w przypadku inicjalizacji nie zainicjalizujemy całej tablicy tylko jej kawałek, pozostała jej część zostaje zainicjalizowana niejawnie wartościami domyślnymi tj. wykonywany jest operator default na danym typie. Dla typów numerycznych jest to zero, dla boola false, dla typów referencyjnych jest to null itd.
Tablica może być użyta w funkcji jako parametr wejściowy (chociażby w najczęściej spotykanej funkcji – Main),

  
static void Main(string[] args)
{
//jakiś kod
}

oraz jako parametr wyjściowy (jak poniżej)

  
static void WypelniaczTablic(out int[] tablica)
{
tablica = new int[5] { 1, 2, 3, 4, 5 };
}

I można ich normalnie użyć w funkcjach, odwołując się do poszczególnych jej elementów i tak dalej.

Podsumowując: użycie tablic w języku C# jest proste i intuicyjne. Pozwala ono poukładać powiązane dane tego samego typu w jeden twór i wygodnie z tego tworu korzystać.