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żą.
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ą
fori pętląforeach - Policzysz sumę, średnią, minimum i maksimum z zawartości tablicy
- Wyjaśnisz, dlaczego przypisanie tablicy do drugiej zmiennej nie tworzy kopii
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ć:
int ocena1 = 4;
int ocena2 = 5;
int ocena3 = 3;
int ocena4 = 5;
int ocena5 = 2;
int suma = ocena1 + ocena2 + ocena3 + ocena4 + ocena5;
piszemy:
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 tablicy | Znaczenie |
|---|---|
| jeden typ | Wszystkie elementy są tego samego typu. Tablica int[] mieści tylko liczby całkowite. |
| stały rozmiar | Liczba 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 zera | Pierwszy element ma numer 0, ostatni — Length - 1. |
| dostęp po numerze | oceny[2] to trzeci element. Odczyt jest natychmiastowy, niezależnie od wielkości tablicy. |
Trzy sposoby utworzenia tablicy
// 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ób | Kiedy 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 tablicy | Wartość domyślna | Uwagi |
|---|---|---|
int[], long[] | 0 | |
double[], decimal[] | 0 | |
bool[] | false | |
char[] | znak o kodzie 0 | Niewidoczny — na ekranie wygląda jak nic. |
string[] | brak wartości | Nie 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, "");.
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ć.
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
4
3
5
2
6
| Indeks | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| Wartość | 4 | 5 | 3 | 5 | 2 |
| Nazwa | oceny[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].
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:
int[] oceny = { 4, 5, 3, 5, 2 };
Console.WriteLine(oceny[5]); // Index was outside the bounds of the array.
Unhandled exception. System.IndexOutOfRangeException:
Index was outside the bounds of the array.
Trzy najczęstsze przyczyny tego błędu:
| Zapis | Problem |
|---|---|
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. |
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.
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
int[] oceny = { 4, 5, 3, 5, 2 };
for (int i = 0; i < oceny.Length; i++)
{
Console.WriteLine($"Ocena numer {i + 1}: {oceny[i]}");
}
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
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.
| Kryterium | for | foreach |
|---|---|---|
| Znasz numer elementu | tak | nie |
| Możesz zmieniać elementy | tak | nie |
| Możesz iść co drugi albo od końca | tak | nie |
| Ryzyko pomyłki o jeden | jest | nie ma |
| Typowe zastosowanie | wypełnianie, modyfikowanie, porównywanie sąsiadów | sumowanie, 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.
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
}
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”:
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));
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ę.
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.
// 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!
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ę
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
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.
Użyte w tej lekcji elementy
| Nazwa | Zwraca | Działanie |
|---|---|---|
tablica.Length | int |
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.
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.
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)}");
=== 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
| Fragment | Co 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ą.
Na czym się najczęściej potykamy
| Błąd | Co się dzieje i jak poprawić |
|---|---|
i <= tablica.Length |
IndexOutOfRangeException w ostatnim obiegu. Warunek to zawsze i < tablica.Length. |
tablica.Length() |
CS1955 — Length 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
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]}");
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.
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)}");
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.
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}.");
}
}
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.
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)}");
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.
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)}");
}
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.
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]})");
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.
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. Lengthto właściwość — bez nawiasów.- Standardowa pętla to
for (int i = 0; i < tab.Length; i++)— znak<, nie<=. foreachjest krótszy i bezpieczniejszy, ale nie pozwala zmieniać elementów.tab[^1]to ostatni element,tab[a..b]to nowa tablica z elementami odadob(bezb).- Tablica jest referencją:
y = xkopiuje adres, nie zawartość, ax == yporó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.