CodeGym /Kursy /C# SELF /Zaawansowana praca z tablicami

Zaawansowana praca z tablicami

C# SELF
Poziom 7 , Lekcja 2
Dostępny

1. Tablica typu string

No i na szybko chciałbym opowiedzieć o tablicy typu string.

Jak już mówiliśmy, tablica może być dowolnego typu. To znaczy, że można stworzyć tablicę typu string. Tak wyglądałby kod, gdybyśmy mieli napisać program, który „wczytuje z klawiatury 10 stringów i wypisuje je na ekran w odwrotnej kolejności”.

string[] array = new string[10];	// Tworzymy obiekt-tablicę na 10 elementów
for (int i = 0; i < 10; i++)		// Pętla od 0 do 9
{
   array[i] = Console.ReadLine();	// Czytamy string z klawiatury i zapisujemy go do komórki tablicy
}
for (int i = 9; i >= 0; i--)		// Pętla od 9 do 0
{
    Console.WriteLine(array[i]);	// Wypisujemy na ekran kolejną komórkę tablicy
}

Kod praktycznie się nie zmienił! Trzeba było tylko przy tworzeniu tablicy zamienić typ int na string. Poza tym wszystko dokładnie tak samo!

2. Tablica typu string w pamięci

I jeszcze jeden przydatny fakt. Zobaczmy 3 obrazki:

Obrazek 1. Jak obiekt string jest rozmieszczony w pamięci:

Tablica typu string w pamięci

Zwróć uwagę, że tekst stringa nie jest przechowywany bezpośrednio w zmiennej: dla niego jest wydzielony osobny blok pamięci. A w zmiennej typu string przechowywany jest adres (referencja) do obiektu z tekstem.

Obrazek 2. Jak tablica liczb całkowitych jest rozmieszczona w pamięci:

int array in memory

Ten obrazek też już widziałeś.

Obrazek 3. Jak w pamięci jest rozmieszczona tablica stringów:

How a string array is stored in memory

Po lewej widzimy zmienną-tablicę typu string[] (przechowuje adres obiektu-tablicy).
W środku — obiekt-tablica typu string.
A po prawej — obiekty-stringi, które przechowują jakieś teksty.

W komórkach obiektu-tablicy typu string nie są przechowywane same stringi (teksty), tylko ich adresy (referencje). Dokładnie tak samo, jak w zmiennych typu string przechowywane są adresy stringów (tekstu).

3. Szybka inicjalizacja tablicy w C#

Tablice — mega przydatna rzecz, więc twórcy C# postarali się, żeby praca z nimi była jak najwygodniejsza. I pierwsze co zrobili — to uprościli inicjalizację tablicy, czyli wpisywanie do niej wartości początkowych.

Bo bardzo często, oprócz danych, które program skądś pobiera, potrzebuje jeszcze swoich wewnętrznych danych. Na przykład musimy przechowywać w tablicy długości wszystkich miesięcy. Jak może wyglądać ten kod:

int[] months = new int[12];
months[0] = 31; // styczeń
months[1] = 28; // luty
months[2] = 31; // marzec
months[3] = 30; // kwiecień
months[4] = 31; // maj
months[5] = 30; // czerwiec
months[6] = 31; // lipiec
months[7] = 31; // sierpień
months[8] = 30; // wrzesień
months[9] = 31; // październik
months[10] = 30; // listopad
months[11] = 31; // grudzień

Ale jest sposób, żeby to zapisać krócej — dzięki twórcom C#:

// długości miesięcy roku
int[] months = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

Możesz po prostu wypisać po przecinku wszystkie wartości tablicy!

Wygodne, nie? Ale to jeszcze nie wszystko.

Okazuje się, że kompilator może określić typ kontenera (obiektu-tablicy) na podstawie typu zmiennej-tablicy. A żeby określić długość tablicy — po prostu policzy ilość elementów wpisanych w nawiasach klamrowych.

Dlatego ten kod można zapisać jeszcze krócej:

// długości miesięcy roku
int[] months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

No czy to nie piękne? 🙂

Taki zapis nazywa się „szybka inicjalizacja tablicy”. I działa nie tylko dla typu int...

// nazwy miesięcy roku
string[] months = { "Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"};

4. Pętla foreach

Przechodzenie po wszystkich elementach tablicy to tak częsta operacja, że wymyślono do tego osobną pętlę(!). Nazywa się foreach. I zapisuje się tak:


foreach (int score in scores)
{
    Console.WriteLine($"Punkty: {score}");
}
        
Iteracja po elementach tablicy za pomocą foreach

Tutaj pętla sama iteruje po elementach ― żadnego ręcznego indeksu. Nie można zmieniać elementów bezpośrednio (w pętli są tylko do odczytu), ale za to mega wygodnie po prostu "przejść się" po tablicy.

Kompilator zamieni kod pętli foreach na taki kod:


for (int i = 0; i < scores.Length; i++)
{
    int score = scores[i];
    Console.WriteLine($"Punkty: {score}");
}
        
Jak działa foreach "pod maską"

Więc żadnej magii. Ale teraz już wiadomo, czemu nie możesz zmieniać elementów tablicy przez zmienną score.

Napiszmy jeszcze przykład: znajdźmy sumę wszystkich punktów.


int sum = 0;
foreach (int score in scores)
{
    sum += score;
}
Console.WriteLine($"Suma wszystkich punktów: {sum}");
        
Sumowanie elementów tablicy za pomocą foreach

Kiedy używać której pętli?

  • Jeśli potrzebujesz indeksu albo musisz zmieniać elementy — pętla for.
  • Jeśli po prostu chcesz przejść po wartościach — foreach jest szybszy i czytelniejszy.

5. Indeksowanie od końca: operator ^

Od jakiegoś czasu w C# można pobierać elementy od końca tablicy. Kiedyś jak chciałeś ostatni element tablicy — pisałeś coś w stylu:


int lastScore = scores[scores.Length - 1];
        
Pobieranie ostatniego elementu tablicy po staremu

Teraz jest dużo prościej. Wystarczy przed indeksem napisać operator ^ i liczenie idzie od końca:


int lastScore = scores[^1];        // Ostatni element
int penultimate = scores[^2];      // Przedostatni element
        
Indeksowanie od końca za pomocą ^
  • ^1 — to pierwszy od końca (ostatni).
  • ^2 — to drugi od końca.

Wizualizacja

Indeks Wartość w tablicy scores Zwykły indeks Indeks od końca
0 10 0 ^5
1 15 1 ^4
2 8 2 ^3
3 22 3 ^2
4 17 4 ^1

score[^1] = 17, score[^2] = 22 — proste i czytelne.

Ważne! Zwróć uwagę, że indeksy od końca liczymy od 1, nie od zera.

6. Zakresy: operator .. (range)

Czasem trzeba pobrać podtablicę z oryginalnej tablicy. W C# do tego można użyć specjalnego operatora zakresu .. — to zwięzły i bardzo wygodny sposób na pobranie potrzebnego "kawałka" tablicy.

Taka podtablica nazywana jest czasem wycinkiem (od ang. "slice") i określana jest przez dwa krańce: początek i koniec.
Ważny moment: prawy kraniec nie jest włączany do wyniku!

Jak to wygląda


int[] scores = { 10, 15, 8, 22, 17 };
int[] top3 = scores[0..3]; // Bierzemy elementy od 0 do 2: {10, 15, 8}
        
Pobieranie wycinka tablicy za pomocą ..

  • scores[0..3] — bierzemy elementy od indeksu 0 (włącznie) do indeksu 3 (NIE włącznie).
  • Wynikowa tablica będzie zawierać elementy o indeksach 0, 1 i 2.

Skróty

Jeśli lewy kraniec nie jest podany, bierzemy od początku:


int[] firstTwo = scores[..2]; // to samo co scores[0..2], czyli {10, 15}
        

Jeśli prawy kraniec nie jest podany, bierzemy do końca:


int[] fromThird = scores[2..]; // {8, 22, 17}
        

Indeksy ujemne

Za pomocą symbolu ^ można liczyć indeksy od końca:


int[] lastTwo = scores[^2..]; // {22, 17}
        

Tutaj ^2 — to "drugi element od końca".

Można też brać zakres "od-do", używając ujemnych indeksów po obu stronach:


int[] mid = scores[1..^1]; // {15, 8, 22} (od drugiego do przedostatniego)
        

Co warto zapamiętać

  • Prawy kraniec nigdy nie jest włączany — czyli jeśli piszesz a..b, dostajesz wszystkie elementy od a do b-1.
  • Jeśli podasz krańce wychodzące poza tablicę, zostanie rzucony wyjątek IndexOutOfRangeException.
  • Wycinek — to nowa tablica, oryginał się nie zmienia. Jakiekolwiek zmiany w nowej tablicy nie wpłyną na oryginał.

Praktyczne przykłady

int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
int[] arr = { 1, 2, 3, 4, 5, 6, 7 };

int[] start = arr[..3];      // {1, 2, 3}
int[] end = arr[^2..];       // {6, 7}
int[] middle = arr[2..5];    // {3, 4, 5}
int[] all = arr[..];         // {1, 2, 3, 4, 5, 6, 7} (pełna kopia)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION