Tablice jednowymiarowe

W poprzednich lekcjach każda wartość potrzebowała własnej zmiennej — przy trzech produktach było to męczące, przy trzydziestu byłoby niemożliwe. Tablica to jedna nazwa dla wielu wartości tego samego typu. Dopiero teraz pętle pokażą, do czego naprawdę służą.

C# tablice indeksy 50 min
CEL LEKCJI

Czego się dziś nauczysz

  • Utworzysz tablicę i wypełnisz ją wartościami na trzy różne sposoby
  • Odczytasz i zmienisz dowolny element po jego numerze
  • Przejdziesz całą tablicę pętlą for i pętlą foreach
  • Policzysz sumę, średnią, minimum i maksimum z zawartości tablicy
  • Wyjaśnisz, dlaczego przypisanie tablicy do drugiej zmiennej nie tworzy kopii
TEORIA

Czym jest tablica

Wyobraź sobie szafkę z ponumerowanymi szufladami. Szafka ma jedną nazwę, a każda szuflada — swój numer. Wszystkie szuflady są tej samej wielkości i mieszczą ten sam rodzaj rzeczy. Ich liczba jest ustalana przy stawianiu szafki i już się nie zmieni.

To dokładnie tablica. Zamiast pisać:

bez tablicy
int ocena1 = 4;
int ocena2 = 5;
int ocena3 = 3;
int ocena4 = 5;
int ocena5 = 2;

int suma = ocena1 + ocena2 + ocena3 + ocena4 + ocena5;

piszemy:

z tablicą
int[] oceny = { 4, 5, 3, 5, 2 };

int suma = 0;

for (int i = 0; i < oceny.Length; i++)
{
    suma += oceny[i];
}

Największa zaleta widać dopiero przy zmianie

Wersja pierwsza przy dziesięciu ocenach wymaga dopisania pięciu zmiennych i pięciu składników w sumie. Wersja druga — zmiany jednej liczby w nawiasach klamrowych. Reszta kodu zostaje bez zmian, bo pętla sama sprawdza, ile jest elementów.

Cecha tablicyZnaczenie
jeden typWszystkie elementy są tego samego typu. Tablica int[] mieści tylko liczby całkowite.
stały rozmiarLiczba elementów jest ustalana przy tworzeniu i nie da się jej zmienić. Do list rosnących służy typ List<T> z bloku o C# nowoczesnym.
numerowanie od zeraPierwszy element ma numer 0, ostatni — Length - 1.
dostęp po numerzeoceny[2] to trzeci element. Odczyt jest natychmiastowy, niezależnie od wielkości tablicy.
TEORIA

Trzy sposoby utworzenia tablicy

Program.cs
// 1. pusta tablica o zadanym rozmiarze
int[] punkty = new int[5];              // piec elementow, kazdy rowny 0

// 2. z podanymi wartosciami - rozmiar wyliczy kompilator
int[] oceny = { 4, 5, 3, 5, 2 };        // piec elementow

// 3. zapis pelny - to samo, tylko dluzej
int[] wyniki = new int[] { 10, 20, 30 };
SposóbKiedy stosować
new int[5] Gdy znasz rozmiar, ale nie znasz jeszcze wartości — np. zaraz wypełnisz tablicę danymi od użytkownika. Wszystkie elementy dostają wartość domyślną.
{ 4, 5, 3 } Gdy znasz wartości. Najkrótszy i najczytelniejszy zapis. Uwaga: działa tylko przy deklaracji zmiennej.
new int[] { ... } Ten sam efekt, ale zapis działa też poza deklaracją — np. przy przekazywaniu tablicy do metody.

Wartości domyślne

Świeżo utworzona tablica nie jest pusta — każdy element ma wartość domyślną swojego typu, tę samą, którą poznałeś przy typach prostych:

Typ tablicyWartość domyślnaUwagi
int[], long[]0 
double[], decimal[]0 
bool[]false 
char[]znak o kodzie 0Niewidoczny — na ekranie wygląda jak nic.
string[]brak wartościNie pusty napis! Próba użycia takiego elementu przerywa program. Trzeba go najpierw czymś wypełnić.

Pusta tablica napisów to pułapka

string[] imiona = new string[3]; tworzy trzy „puste szuflady”, ale nie ma w nich pustych napisów — nie ma w nich nic. Wywołanie imiona[0].Length przerwie program błędem NullReferenceException. Jeśli chcesz mieć puste napisy, wypełnij tablicę: Array.Fill(imiona, "");.

TEORIA

Indeksy i Length

Element wskazujemy jego numerem w nawiasach kwadratowych. Numerowanie zaczyna się od zera — to nie jest kaprys, tylko konwencja większości języków programowania i trzeba się do niej przyzwyczaić.

Program.cs
int[] oceny = { 4, 5, 3, 5, 2 };

Console.WriteLine(oceny[0]);            // 4  - pierwszy element
Console.WriteLine(oceny[2]);            // 3  - trzeci element
Console.WriteLine(oceny.Length);        // 5  - ile elementow
Console.WriteLine(oceny[oceny.Length - 1]);   // 2  - ostatni element

oceny[4] = 6;                           // zmiana ostatniego elementu
Console.WriteLine(oceny[4]);            // 6
wynik w konsoli
4
3
5
2
6
Indeks01234
Wartość45352
Nazwaoceny[0]oceny[1]oceny[2]oceny[3]oceny[4]

Length to właściwość, nie metoda

Piszemy oceny.Length bez nawiasów. Nawiasy — oceny.Length() — to błąd CS1955. Dla napisów jest tak samo: tekst.Length. Zapamiętaj tę różnicę, bo w innych językach (na przykład w Javie) tablica ma length małą literą, a napis ma metodę length() z nawiasami.

Ostatni element to zawsze Length - 1

Tablica pięcioelementowa ma indeksy od 0 do 4. Ta „jedynka mniej” jest źródłem tylu błędów, że warto ją sobie powtórzyć na głos. Za chwilę poznasz krótszy zapis: oceny[^1].

TEORIA

Wyjście poza zakres

Odwołanie do nieistniejącego elementu nie jest błędem kompilacji — kompilator zwykle nie wie, jaki numer będzie w zmiennej. Program uruchamia się normalnie i przerywa działanie dopiero w tym miejscu:

Program.cs
int[] oceny = { 4, 5, 3, 5, 2 };

Console.WriteLine(oceny[5]);    // Index was outside the bounds of the array.
komunikat przy uruchomieniu
Unhandled exception. System.IndexOutOfRangeException:
Index was outside the bounds of the array.

Trzy najczęstsze przyczyny tego błędu:

ZapisProblem
for (int i = 1; i <= n.Length; i++)Zaczyna od 1 i kończy na Length — pomija pierwszy element i wychodzi poza ostatni. Poprawnie: i = 0 i i < n.Length.
for (int i = 0; i <= n.Length; i++)Jeden obieg za dużo. Znak musi być <, nie <=.
n[numerOdUzytkownika]Użytkownik wpisał 7, a tablica ma 5 elementów. Zawsze sprawdzaj numer przed użyciem.
Program.cs
int[] oceny = { 4, 5, 3, 5, 2 };

Console.Write($"Numer oceny (0-{oceny.Length - 1}): ");
bool ok = int.TryParse(Console.ReadLine(), out int numer);

if (!ok || numer < 0 || numer >= oceny.Length)
{
    Console.WriteLine("Nie ma takiej oceny.");
}
else
{
    Console.WriteLine($"Ocena numer {numer} to {oceny[numer]}.");
}

Warunek numer < 0 || numer >= oceny.Length to gotowa formułka na sprawdzenie indeksu. Warto ją zapamiętać — będzie wracać w każdym programie z tablicami.

TEORIA

Tablica i pętle

Tablice i pętle to para nierozłączna. Są dwa sposoby przejścia całej tablicy i każdy ma swoje miejsce.

Pętla for — gdy potrzebny jest numer

Program.cs
int[] oceny = { 4, 5, 3, 5, 2 };

for (int i = 0; i < oceny.Length; i++)
{
    Console.WriteLine($"Ocena numer {i + 1}: {oceny[i]}");
}
wynik w konsoli
Ocena numer 1: 4
Ocena numer 2: 5
Ocena numer 3: 3
Ocena numer 4: 5
Ocena numer 5: 2

Nagłówek for (int i = 0; i < tablica.Length; i++) to najczęstsza linia w programach na tablicach. Napisz ją kilkanaście razy, a wejdzie w palce.

Dlaczego {i + 1} w komunikacie

Program liczy od zera, człowiek od jedynki. Wewnątrz kodu używamy indeksu i, a użytkownikowi pokazujemy i + 1. To drobiazg, który odróżnia program przyjazny od dziwacznego.

Pętla foreach — gdy numer jest zbędny

Program.cs
int[] oceny = { 4, 5, 3, 5, 2 };
int suma = 0;

foreach (int ocena in oceny)
{
    suma += ocena;
}

Console.WriteLine($"Suma: {suma}");

Czyta się to prawie po polsku: „dla każdej oceny w ocenach”. Nie ma licznika, więc nie ma jak się pomylić o jeden ani wyjść poza zakres.

Kryteriumforforeach
Znasz numer elementutaknie
Możesz zmieniać elementytaknie
Możesz iść co drugi albo od końcataknie
Ryzyko pomyłki o jedenjestnie ma
Typowe zastosowaniewypełnianie, modyfikowanie, porównywanie sąsiadówsumowanie, wypisywanie, wyszukiwanie

foreach nie zmienia zawartości tablicy

Zmienna pętli foreach to kopia elementu. Przypisanie do niej niczego nie zmieni w tablicy — a w C# jest nawet zabronione i kończy się błędem kompilacji CS1656. Do modyfikowania służy for.

Program.cs
foreach (int ocena in oceny)
{
    ocena = ocena + 1;      // BLAD CS1656: nie mozna przypisac do zmiennej petli
}

for (int i = 0; i < oceny.Length; i++)
{
    oceny[i] = oceny[i] + 1;    // OK - to dziala
}
TEORIA

Indeksy od końca i zakresy

Od C# 8 istnieje krótszy zapis dla „liczone od końca”. Znak ^ (daszek) oznacza „tyle od tyłu”:

Program.cs
int[] liczby = { 10, 20, 30, 40, 50 };

Console.WriteLine(liczby[^1]);      // 50 - ostatni  (to samo co liczby[liczby.Length - 1])
Console.WriteLine(liczby[^2]);      // 40 - przedostatni

int[] srodek = liczby[1..4];        // elementy o indeksach 1, 2, 3
int[] poczatek = liczby[..2];       // od poczatku do indeksu 2 (bez niego)
int[] koniec = liczby[3..];         // od indeksu 3 do konca

Console.WriteLine(string.Join(", ", srodek));
Console.WriteLine(string.Join(", ", poczatek));
Console.WriteLine(string.Join(", ", koniec));
wynik w konsoli
50
40
20, 30, 40
10, 20
40, 50

Górna granica zakresu jest wyłączna

liczby[1..4] daje elementy 1, 2 i 3 — czwórka jest granicą, a nie ostatnim zabranym elementem. To ta sama reguła co przy Random.Next(1, 101) z lekcji 14 i przy pętli for (i = 0; i < Length; i++). W C# „do” prawie zawsze znaczy „przed”.

^1 to ostatni, nie ^0

Indeksy od przodu zaczynają się od zera, od tyłu — od jedynki. Wygląda to niekonsekwentnie, ale ma sens: liczby[^0] byłoby elementem za ostatnim, czyli poza tablicą. Zapamiętaj: ^1 to ostatni.

Wycinek liczby[1..4] tworzy nową tablicę z kopiami elementów. Zmiana w wycinku nie wpłynie na oryginał — w przeciwieństwie do zwykłego przypisania, o którym za chwilę.

TEORIA

Tablica jest referencją

To jedna z najważniejszych rzeczy w tej lekcji i pierwsza okazja, żeby zobaczyć różnicę między typem wartościowym a referencyjnym.

Program.cs
// typ wartosciowy - kopiuje sie wartosc
int a = 5;
int b = a;
b = 99;
Console.WriteLine($"a = {a}, b = {b}");        // a = 5, b = 99

// typ referencyjny - kopiuje sie ADRES
int[] x = { 1, 2, 3 };
int[] y = x;
y[0] = 99;
Console.WriteLine($"x[0] = {x[0]}, y[0] = {y[0]}");   // OBA po 99!
wynik w konsoli
a = 5, b = 99
x[0] = 99, y[0] = 99

Zmienna tablicowa nie zawiera elementów — zawiera adres miejsca, w którym one leżą. Przypisanie y = x kopiuje ten adres, więc obie zmienne pokazują na tę samą szafkę. To jak dorobienie drugiego klucza, a nie postawienie drugiej szafki.

Jak zrobić prawdziwą kopię

Program.cs
int[] oryginal = { 1, 2, 3 };

// sposob 1: wycinek calosci
int[] kopia1 = oryginal[..];

// sposob 2: metoda Clone (wymaga rzutowania)
int[] kopia2 = (int[])oryginal.Clone();

// sposob 3: recznie, petla
int[] kopia3 = new int[oryginal.Length];
for (int i = 0; i < oryginal.Length; i++)
{
    kopia3[i] = oryginal[i];
}

kopia1[0] = 99;
Console.WriteLine($"oryginal[0] = {oryginal[0]}");   // nadal 1
wynik w konsoli
oryginal[0] = 1

Ta sama zasada dotyczy porównywania

if (x == y) dla tablic sprawdza, czy to ta sama tablica, a nie czy mają tę samą zawartość. Dwie osobne tablice o identycznych wartościach dadzą false. Do porównania zawartości trzeba przejść je pętlą albo użyć gotowej metody — pokażemy ją w następnej lekcji.

METODY WBUDOWANE

Użyte w tej lekcji elementy

NazwaZwracaDziałanie
tablica.Lengthint Właściwość (bez nawiasów) — liczba elementów. Ustalana przy tworzeniu i niezmienna.
tablica[i]element Odczyt lub zapis elementu o numerze i. Numer poza zakresem przerywa program.
tablica[^i]element Element liczony od końca; ^1 to ostatni.
tablica[a..b]tablica Nowa tablica z elementami od a do b (bez b). Zapis [..] daje kopię całości.
string.Join(sep, tab)string Skleja elementy w jeden napis, wstawiając między nie separator: string.Join(", ", oceny) daje "4, 5, 3, 5, 2". Najwygodniejszy sposób wypisania całej tablicy.
tablica.Clone()object Kopia tablicy. Wynik trzeba rzutować: (int[])oceny.Clone().
Array.Fill(tab, w)nic Wypełnia całą tablicę podaną wartością. Przydatne zwłaszcza dla string[], żeby uniknąć pustych elementów.
Math.Round(x, n)double Zaokrąglenie wartości — używane przy średnich. Pamiętaj o regule połówek z lekcji 11.

Klasa Array ma tego znacznie więcej

Array.Sort, Array.Reverse, Array.IndexOf, Array.Resize — cała następna lekcja jest o gotowych metodach do tablic. W tej lekcji celowo robimy wszystko ręcznie, żebyś rozumiał, co te metody wykonują za ciebie.

PRZYKŁAD Z OMÓWIENIEM

Dziennik ocen

Program wczytuje oceny do tablicy, a potem wylicza z nich komplet statystyk. To wzorzec, który przewija się przez cały blok o tablicach.

Program.cs
const int IleOcen = 6;

int[] oceny = new int[IleOcen];

Console.WriteLine("=== DZIENNIK OCEN ===");
Console.WriteLine();

// --- 1. wypelnienie tablicy z walidacja ---
for (int i = 0; i < oceny.Length; i++)
{
    bool poprawna;

    do
    {
        Console.Write($"Ocena {i + 1} z {oceny.Length} (1-6): ");
        bool ok = int.TryParse(Console.ReadLine(), out int wpis);

        poprawna = ok && wpis >= 1 && wpis <= 6;

        if (poprawna)
        {
            oceny[i] = wpis;
        }
        else
        {
            Console.WriteLine("  Ocena musi byc liczba od 1 do 6.");
        }
    }
    while (!poprawna);
}

// --- 2. jedno przejscie, cztery wyniki ---
int suma = 0;
int najnizsza = oceny[0];
int najwyzsza = oceny[0];

foreach (int ocena in oceny)
{
    suma += ocena;

    if (ocena < najnizsza) { najnizsza = ocena; }
    if (ocena > najwyzsza) { najwyzsza = ocena; }
}

double srednia = (double)suma / oceny.Length;

// --- 3. drugie przejscie: ile powyzej sredniej ---
int powyzej = 0;

foreach (int ocena in oceny)
{
    if (ocena > srednia)
    {
        powyzej++;
    }
}

// --- 4. wypisanie ---
Console.WriteLine();
Console.WriteLine("Wprowadzone oceny:");

for (int i = 0; i < oceny.Length; i++)
{
    Console.WriteLine($"  [{i}] = {oceny[i]}   {new string('*', oceny[i])}");
}

Console.WriteLine();
Console.WriteLine($"{"Suma:",-18}{suma,6}");
Console.WriteLine($"{"Srednia:",-18}{srednia,6:F2}");
Console.WriteLine($"{"Najnizsza:",-18}{najnizsza,6}");
Console.WriteLine($"{"Najwyzsza:",-18}{najwyzsza,6}");
Console.WriteLine($"{"Powyzej sredniej:",-18}{powyzej,6}");
Console.WriteLine();
Console.WriteLine($"Wszystkie: {string.Join(", ", oceny)}");
wynik w konsoli (dla ocen 4, 5, 3, 5, 2, 6)
=== DZIENNIK OCEN ===

Ocena 1 z 6 (1-6): 4
Ocena 2 z 6 (1-6): 5
Ocena 3 z 6 (1-6): 3
Ocena 4 z 6 (1-6): 5
Ocena 5 z 6 (1-6): 2
Ocena 6 z 6 (1-6): 6

Wprowadzone oceny:
  [0] = 4   ****
  [1] = 5   *****
  [2] = 3   ***
  [3] = 5   *****
  [4] = 2   **
  [5] = 6   ******

Suma:                 25
Srednia:            4,17
Najnizsza:             2
Najwyzsza:             6
Powyzej sredniej:      3

Wszystkie: 4, 5, 3, 5, 2, 6

Omówienie

FragmentCo robi i dlaczego tak
new int[IleOcen] Rozmiar bierzemy ze stałej, a nie wpisujemy szóstki w trzech miejscach. Zmiana liczby ocen to poprawka jednej linii.
do-while wewnątrz for Pętla zewnętrzna przechodzi po komórkach tablicy, wewnętrzna pilnuje, żeby do każdej trafiła sensowna wartość. Wzorzec walidacji z lekcji 14, tym razem powtórzony dla każdego elementu.
oceny[i] = wpis; Do tablicy trafia wartość dopiero po sprawdzeniu. Zapisanie od razu wyniku TryParse wstawiłoby tam zero po każdej pomyłce.
najnizsza = oceny[0]; Pierwszy element jako punkt startowy — to samo, co w zadaniu ze statystyką z lekcji 14. Start od zera dałby zawsze zero jako minimum, bo oceny są dodatnie.
suma, minimum i maksimum w jednej pętli Trzy wyniki z jednego przejścia. Trzy osobne pętle dałyby ten sam wynik, ale trzykrotnie więcej pracy — przy sześciu ocenach bez znaczenia, przy milionie rekordów już nie.
drugie przejście po średniej Tego akurat nie da się połączyć z pierwszym: żeby porównać ocenę ze średnią, trzeba już znać średnią, a ta jest znana dopiero po zsumowaniu wszystkiego. Klasyczny przypadek, w którym drugie przejście jest konieczne.
(double)suma / oceny.Length Rzutowanie przed dzieleniem. Bez niego 25 / 6 dałoby 4 zamiast 4,1666…
new string('*', oceny[i]) Prosty wykres słupkowy: tyle gwiazdek, ile wynosi ocena. Jedna linia zamiast pętli wewnętrznej.
string.Join(", ", oceny) Cała tablica w jednej linii, z przecinkami. Zwróć uwagę, że metoda sama radzi sobie z ostatnim elementem — nie ma przecinka na końcu.

Sprawdź rachunek

4 + 5 + 3 + 5 + 2 + 6 = 25, średnia 25 / 6 = 4,1666… → po zaokrągleniu do dwóch miejsc 4,17. Powyżej średniej są trzy oceny: 5, 5 i 6 — czwórka jest poniżej, bo 4 < 4,1666. To dobra okazja, żeby pokazać uczniom, dlaczego porównujemy z pełną wartością średniej, a nie z zaokrągloną.

CZĘSTE BŁĘDY

Na czym się najczęściej potykamy

BłądCo się dzieje i jak poprawić
i <= tablica.Length IndexOutOfRangeException w ostatnim obiegu. Warunek to zawsze i < tablica.Length.
tablica.Length() CS1955Length to właściwość, bez nawiasów.
int[5] oceny; Rozmiar nie należy do nazwy zmiennej. Poprawnie: int[] oceny = new int[5];.
int[] a; a[0] = 1; Sama deklaracja nie tworzy tablicy — nie ma jeszcze żadnych szuflad. Potrzebne new int[n] albo lista wartości.
zmiana rozmiaru tablicy Nie da się. Array.Resize w rzeczywistości tworzy nową tablicę i przepisuje zawartość. Gdy rozmiar ma rosnąć, właściwym narzędziem jest List<T>.
przypisanie zamiast kopii y = x nie kopiuje tablicy, tylko adres. Zmiana przez y widać przez x. Kopia: x[..] albo (int[])x.Clone().
x == y dla tablic Porównuje adresy, nie zawartość. Dwie różne tablice o tych samych liczbach dają false.
zmiana elementu w foreach CS1656. Do modyfikowania używaj for.
string[] bez wypełnienia NullReferenceException przy pierwszym użyciu elementu. Wypełnij: Array.Fill(tab, "").
minimum startujące od zera Dla samych liczb dodatnich wynikiem zawsze będzie zero. Zaczynaj od tablica[0].
szukanie w pustej tablicy tablica[0] na tablicy o rozmiarze 0 przerywa program. Gdy rozmiar może być zerowy, sprawdź go najpierw.
ZADANIA

Zadania

ZAD 1Tablica kwadratów★☆☆

Utwórz tablicę dziesięcioelementową i wypełnij ją kwadratami kolejnych liczb od 1 do 10, po czym wypisz jej zawartość w jednej linii.

pokaż rozwiązanie
int[] kwadraty = new int[10];

for (int i = 0; i < kwadraty.Length; i++)
{
    int liczba = i + 1;                 // indeks 0 to liczba 1
    kwadraty[i] = liczba * liczba;
}

Console.WriteLine(string.Join(", ", kwadraty));
Console.WriteLine($"Pierwszy: {kwadraty[0]}, ostatni: {kwadraty[^1]}");
wynik w konsoli
1, 4, 9, 16, 25, 36, 49, 64, 81, 100
Pierwszy: 1, ostatni: 100

Kluczowa jest linia int liczba = i + 1; — indeks 0 ma zawierać kwadrat jedynki, a nie zera. Można też napisać kwadraty[i] = (i + 1) * (i + 1);, ale wersja ze zmienną pomocniczą wyraźniej pokazuje, o co chodzi.

ZAD 2Odwrócenie tablicy★☆☆

Wypisz zawartość tablicy w odwrotnej kolejności — bez zmieniania samej tablicy. Następnie utwórz nową tablicę zawierającą te elementy w odwróconej kolejności.

pokaż rozwiązanie
int[] liczby = { 10, 20, 30, 40, 50 };

// wypisanie od konca - petla idzie w dol
Console.Write("Od konca: ");
for (int i = liczby.Length - 1; i >= 0; i--)
{
    Console.Write($"{liczby[i]} ");
}
Console.WriteLine();

// nowa tablica z odwrocona zawartoscia
int[] odwrocona = new int[liczby.Length];

for (int i = 0; i < liczby.Length; i++)
{
    odwrocona[i] = liczby[liczby.Length - 1 - i];
}

Console.WriteLine($"Nowa tablica: {string.Join(", ", odwrocona)}");
Console.WriteLine($"Oryginal:     {string.Join(", ", liczby)}");
wynik w konsoli
Od konca: 50 40 30 20 10
Nowa tablica: 50, 40, 30, 20, 10
Oryginal:     10, 20, 30, 40, 50

Pętla w dół startuje od Length - 1 (ostatni indeks) i kończy warunkiem i >= 0. Gdyby napisać i > 0, pierwszy element zostałby pominięty.

Wzór liczby.Length - 1 - i to lustrzane odbicie indeksu: dla i = 0 daje ostatni, dla i = 1 przedostatni. Warto go sobie sprawdzić na kartce dla obu końców.

Krócej: Array.Reverse — ale ta metoda zmienia oryginał. Poznasz ją w następnej lekcji.

ZAD 3Wyszukiwanie wartości★★☆

Wczytaj szukaną liczbę i sprawdź, czy występuje w tablicy. Podaj indeks pierwszego wystąpienia oraz łączną liczbę wystąpień.

pokaż rozwiązanie
int[] liczby = { 3, 7, 2, 7, 9, 7, 1 };

Console.Write("Czego szukamy? ");
bool ok = int.TryParse(Console.ReadLine(), out int szukana);

if (!ok)
{
    Console.WriteLine("Podaj liczbe calkowita.");
}
else
{
    int pierwszy = -1;
    int ile = 0;

    for (int i = 0; i < liczby.Length; i++)
    {
        if (liczby[i] == szukana)
        {
            if (pierwszy == -1)
            {
                pierwszy = i;        // zapamietujemy tylko pierwsze trafienie
            }

            ile++;
        }
    }

    if (ile == 0)
    {
        Console.WriteLine($"Liczby {szukana} nie ma w tablicy.");
    }
    else
    {
        Console.WriteLine($"Liczba {szukana} wystepuje {ile} raz(y).");
        Console.WriteLine($"Pierwsze wystapienie na indeksie {pierwszy}.");
    }
}
wynik w konsoli
Czego szukamy? 7
Liczba 7 wystepuje 3 raz(y).
Pierwsze wystapienie na indeksie 1.

Nie ma tu break, bo liczymy wszystkie wystąpienia — pętla musi dojść do końca. Gdyby wystarczyło pierwsze trafienie, break byłby na miejscu.

Warunek if (pierwszy == -1) zapisuje indeks tylko raz. Bez niego zmienna byłaby nadpisywana i przechowałaby indeks ostatniego wystąpienia.

W tablicy { 3, 7, 2, 7, 9, 7, 1 } siódemki stoją na indeksach 1, 3 i 5 — pierwszy to 1.

ZAD 4Rozdzielenie na parzyste i nieparzyste★★☆

Mając tablicę liczb, utwórz dwie nowe tablice: jedną z liczbami parzystymi, drugą z nieparzystymi. Pamiętaj, że rozmiar tablicy trzeba znać przed jej utworzeniem.

pokaż rozwiązanie
int[] liczby = { 12, 7, 4, 9, 20, 3, 8, 15 };

// --- przejscie 1: liczymy, ile bedzie kazdych ---
int ileParzystych = 0;

foreach (int x in liczby)
{
    if (x % 2 == 0)
    {
        ileParzystych++;
    }
}

int ileNieparzystych = liczby.Length - ileParzystych;

// --- teraz mozemy utworzyc tablice o wlasciwym rozmiarze ---
int[] parzyste = new int[ileParzystych];
int[] nieparzyste = new int[ileNieparzystych];

// --- przejscie 2: przepisujemy ---
int p = 0;
int n = 0;

foreach (int x in liczby)
{
    if (x % 2 == 0)
    {
        parzyste[p] = x;
        p++;
    }
    else
    {
        nieparzyste[n] = x;
        n++;
    }
}

Console.WriteLine($"Wszystkie:   {string.Join(", ", liczby)}");
Console.WriteLine($"Parzyste:    {string.Join(", ", parzyste)}");
Console.WriteLine($"Nieparzyste: {string.Join(", ", nieparzyste)}");
wynik w konsoli
Wszystkie:   12, 7, 4, 9, 20, 3, 8, 15
Parzyste:    12, 4, 20, 8
Nieparzyste: 7, 9, 3, 15

To jest sedno zadania. Tablica ma stały rozmiar, więc zanim ją utworzysz, musisz wiedzieć, ile elementów w niej wyląduje. Stąd dwa przejścia: pierwsze liczy, drugie przepisuje.

p i n to osobne liczniki pozycji w tablicach docelowych. Nie można użyć indeksu z pętli — element o numerze 4 w oryginale trafia na pozycję 2 wśród parzystych.

Liczba nieparzystych wynika z odejmowania, więc nie trzeba jej liczyć osobno.

W bloku o C# nowoczesnym to samo zadanie zajmie dwie linie dzięki metodzie Where z LINQ. Warto jednak raz zrobić to ręcznie, żeby wiedzieć, co się dzieje pod spodem.

ZAD 5Rzuty kostką★★☆

Wykonaj 1000 losowań kostką sześcienną i policz, ile razy wypadła każda ścianka. Wyniki przedstaw w tabeli razem z udziałem procentowym i słupkiem z gwiazdek.

pokaż rozwiązanie
const int Rzutow = 1000;

Random kostka = new Random();
int[] licznik = new int[7];        // indeksy 0-6, uzywamy 1-6

for (int i = 0; i < Rzutow; i++)
{
    int oczka = kostka.Next(1, 7);     // 1-6, siodemka wylaczona
    licznik[oczka]++;                  // liczba oczek jest indeksem!
}

Console.WriteLine($"Wynik {Rzutow} rzutow:");
Console.WriteLine();

for (int oczka = 1; oczka <= 6; oczka++)
{
    double procent = (double)licznik[oczka] / Rzutow * 100;
    int slupek = licznik[oczka] / 5;          // 5 rzutow = jedna gwiazdka

    Console.WriteLine($"{oczka}: {licznik[oczka],5}  {procent,6:F1}%  {new string('*', slupek)}");
}
przykładowy wynik (za każdym razem inny)
Wynik 1000 rzutow:

1:   168    16,8%  *********************************
2:   157    15,7%  *******************************
3:   171    17,1%  **********************************
4:   164    16,4%  ********************************
5:   175    17,5%  ***********************************
6:   165    16,5%  *********************************

Najważniejsza sztuczka w tym zadaniu: liczba oczek jest indeksem. Zamiast sześciu zmiennych i drabinki else if mamy jedną linię licznik[oczka]++. To wzorzec „tablica zliczająca” — wraca przy histogramach, statystykach ocen i zliczaniu liter.

Tablica ma siedem elementów, choć potrzebujemy sześciu. Element o indeksie 0 zostaje niewykorzystany, ale dzięki temu numer oczek pokrywa się z indeksem i nie trzeba nigdzie odejmować jedynki. Marnujemy cztery bajty, zyskujemy czytelność — dobra zamiana.

Next(1, 7), a nie Next(1, 6): górna granica jest wyłączna, więc szóstka wypadłaby tylko przy siódemce w nawiasie.

Przy 1000 rzutów każda ścianka powinna wypaść około 167 razy (1000 / 6 = 166,67), czyli około 16,7%. Odchylenia rzędu kilkunastu rzutów są normalne — zwiększ liczbę rzutów do 100 000, a proporcje wyrównają się wyraźnie.

ZAD 6Analiza temperatur★★★

Mając tablicę siedmiu temperatur (jeden tydzień), wypisz: średnią, dzień najcieplejszy i najzimniejszy (z nazwą dnia tygodnia), liczbę dni powyżej średniej oraz największy skok temperatury między kolejnymi dniami.

pokaż rozwiązanie
double[] temp = { 18.5, 21.0, 23.5, 19.0, 15.5, 17.0, 22.5 };
string[] dni = { "poniedzialek", "wtorek", "sroda", "czwartek",
                 "piatek", "sobota", "niedziela" };

// --- suma oraz INDEKSY skrajnych dni ---
double suma = 0;
int indeksMax = 0;
int indeksMin = 0;

for (int i = 0; i < temp.Length; i++)
{
    suma += temp[i];

    if (temp[i] > temp[indeksMax]) { indeksMax = i; }
    if (temp[i] < temp[indeksMin]) { indeksMin = i; }
}

double srednia = suma / temp.Length;

// --- dni powyzej sredniej ---
int powyzej = 0;

foreach (double t in temp)
{
    if (t > srednia)
    {
        powyzej++;
    }
}

// --- najwiekszy skok miedzy SASIEDNIMI dniami ---
double najwiekszySkok = 0;
int dzienSkoku = 0;

for (int i = 1; i < temp.Length; i++)      // uwaga: start od 1
{
    double skok = Math.Abs(temp[i] - temp[i - 1]);

    if (skok > najwiekszySkok)
    {
        najwiekszySkok = skok;
        dzienSkoku = i;
    }
}

// --- wypisanie ---
for (int i = 0; i < temp.Length; i++)
{
    Console.WriteLine($"{dni[i],-14}{temp[i],7:F1} C");
}

Console.WriteLine(new string('-', 23));
Console.WriteLine($"{"Srednia:",-14}{srednia,7:F2} C");
Console.WriteLine();
Console.WriteLine($"Najcieplej: {dni[indeksMax]} ({temp[indeksMax]:F1} C)");
Console.WriteLine($"Najzimniej: {dni[indeksMin]} ({temp[indeksMin]:F1} C)");
Console.WriteLine($"Dni powyzej sredniej: {powyzej}");
Console.WriteLine($"Najwiekszy skok: {najwiekszySkok:F1} stopnia " +
                  $"({dni[dzienSkoku - 1]} -> {dni[dzienSkoku]})");
wynik w konsoli
poniedzialek     18,5 C
wtorek           21,0 C
sroda            23,5 C
czwartek         19,0 C
piatek           15,5 C
sobota           17,0 C
niedziela        22,5 C
-----------------------
Srednia:        19,57 C

Najcieplej: sroda (23,5 C)
Najzimniej: piatek (15,5 C)
Dni powyzej sredniej: 3
Najwiekszy skok: 5,5 stopnia (sobota -> niedziela)

Zapamiętujemy indeks, a nie wartość. To najważniejsza zmiana w stosunku do poprzednich zadań. Znając indeksMax, mamy dostęp i do temperatury (temp[indeksMax]), i do nazwy dnia (dni[indeksMax]). Dwie tablice „chodzą razem” — element o tym samym numerze opisuje ten sam dzień. To bardzo częsty układ, zanim pozna się klasy.

Pętla po sąsiadach zaczyna się od 1. Przy i = 0 zapis temp[i - 1] oznaczałby temp[-1] i przerwałby program. Zawsze, gdy porównujesz element z poprzednim, licznik startuje od 1 — albo od 0, ale wtedy sięgasz do i + 1 i kończysz na Length - 1.

Math.Abs sprawia, że skok w dół liczy się tak samo jak w górę. Bez tej metody spadek z 23,5 na 19,0 dałby −4,5 i nigdy nie byłby „największy”.

Rachunek: suma to 137,0, średnia 137 / 7 = 19,571… → 19,57. Powyżej średniej są trzy dni: 21,0, 23,5 i 22,5. Uwaga na czwartek — 19,0 jest mniejsze od 19,57, więc się nie liczy. To dobry moment, żeby pokazać uczniom, że porównanie ze średnią trzeba robić na pełnej wartości, a nie na tej z ekranu.

Największy skok to 5,5 stopnia między sobotą (17,0) a niedzielą (22,5). Zwróć uwagę, że dzienSkoku przechowuje indeks dnia późniejszego, więc dzień wcześniejszy to dzienSkoku - 1.

PODSUMOWANIE

Co trzeba zapamiętać

  • Tablica to wiele wartości jednego typu pod jedną nazwą, o stałym rozmiarze.
  • Numerowanie zaczyna się od zera; ostatni element ma indeks Length - 1.
  • Length to właściwość — bez nawiasów.
  • Standardowa pętla to for (int i = 0; i < tab.Length; i++) — znak <, nie <=.
  • foreach jest krótszy i bezpieczniejszy, ale nie pozwala zmieniać elementów.
  • tab[^1] to ostatni element, tab[a..b] to nowa tablica z elementami od a do b (bez b).
  • Tablica jest referencją: y = x kopiuje adres, nie zawartość, a x == y porównuje adresy.
  • Kopia to x[..], (int[])x.Clone() albo przepisanie pętlą.
  • Minimum i maksimum zaczynaj od tab[0], nigdy od zera.
  • Numer podany przez użytkownika sprawdzaj zawsze: numer < 0 || numer >= tab.Length.