13. TANANYAG · FORRÁSKÓD
Tömbök és foreach
13_tombok_kommentelt.csAnimáció megnyitása →
namespace _13_Tombok // A namespace (névtér) összefogja az ehhez a példához tartozó kódelemeket; ennek a névtérnek a neve _13_Tombok.
{ // Itt kezdődik a névtér kódblokkja.
internal class Program // A Program nevű osztály deklarációja; ebben lesz a Main és a tömbökkel dolgozó metódusok.
{ // Itt kezdődik a Program osztály törzse.
static void Main(string[] args) // A Main a program belépési pontja. Figyeld meg: a string[] args is egy tömb!
{ // Itt kezdődik a Main metódus törzse.
// MIÉRT KELL TÖMB?
// A Napelem dolgozatban 30 nap termelését számoltuk ki egy ciklusban. Az összeg és az átlag megvolt,
// de a ciklus után a napi értékek ELVESZTEK: minden kör felülírta az előzőt.
// Ha azt kérdezzük, "melyik napokon volt az átlagnál több", ahhoz az összes napi értékre szükség van.
// A tömb sok, azonos típusú értéket tárol EGY névvel, sorszámozott (indexelt) helyeken.
Random random = new Random(); // Egyetlen Random objektum a véletlen hőmérsékletekhez.
// 1. PÉLDA – TÖMB LÉTREHOZÁSA KEZDŐÉRTÉKEKKEL.
string[] napok = { "hétfő", "kedd", "szerda", "csütörtök", "péntek", "szombat", "vasárnap" }; // string[]: szövegek tömbje. A { } között felsorolt elemekkel jön létre, így a mérete 7.
Console.WriteLine($"A napok tömb elemszáma: {napok.Length}"); // A Length tulajdonság megadja az elemek számát (Pythonban: len(napok)).
// 2. PÉLDA – INDEXELÉS: AZ ELEMEK ELÉRÉSE.
Console.WriteLine($"Az első elem (index 0): {napok[0]}"); // Az indexelés 0-tól indul: a napok[0] a hétfő.
Console.WriteLine($"Az utolsó elem (index {napok.Length - 1}): {napok[napok.Length - 1]}"); // Az utolsó elem indexe mindig Length - 1, itt 6.
// Console.WriteLine(napok[7]); // HIBA futás közben: IndexOutOfRangeException – 7 elemnél a legnagyobb index a 6.
int sorszam = 3; // A felhasználó és a feladatszöveg SORSZÁMMAL beszél: "a 3. nap".
Console.WriteLine($"A(z) {sorszam}. nap: {napok[sorszam - 1]}\n"); // Sorszámból indexet így kapunk: index = sorszám - 1, tehát a 3. nap a napok[2], vagyis szerda.
// 3. PÉLDA – ÜRES TÖMB LÉTREHOZÁSA new-val, MEGADOTT MÉRETTEL.
double[] homersekletek = new double[napok.Length]; // 7 darab double férőhely jön létre. A méret a létrehozás után már NEM változtatható.
Console.WriteLine($"Létrehozás után az első elem: {homersekletek[0]}\n"); // Az új double tömb minden eleme 0 – ez az alapértelmezett érték.
// 4. PÉLDA – FELTÖLTÉS for CIKLUSSAL.
for (int i = 0; i < homersekletek.Length; i++) // Az i végigmegy a 0, 1, ..., 6 indexeken. A feltétel i < Length, NEM i <= Length!
{ // Itt kezdődik a for ciklus törzse.
homersekletek[i] = random.Next(-50, 151) / 10.0; // -50..150 közötti egész, osztva 10.0-val: -5,0..15,0 fok egy tizedesjeggyel. Az i. helyre írjuk.
} // A ciklus végére mind a 7 hely kapott értéket.
// 5. PÉLDA – KIÍRÁS for CIKLUSSAL: SORSZÁM, NAP, ÉRTÉK.
Console.WriteLine("Heti hőmérsékletek:"); // Fejléc a táblázathoz.
for (int i = 0; i < napok.Length; i++) // Ugyanaz az i index mindkét tömbben ugyanahhoz a naphoz tartozik.
{ // Itt kezdődik a for ciklus törzse.
Console.WriteLine($"{i + 1}. nap ({napok[i]}): {homersekletek[i]:f1} °C"); // Kiíráskor indexből sorszám lesz: i + 1. Az :f1 egy tizedesjegyre formáz.
} // A ciklus után a táblázat minden sora kint van.
Console.WriteLine($"Így NEM lehet kiírni egy tömböt: {homersekletek}\n"); // A tömböt egyben kiírva csak a típusa jelenik meg (System.Double[]), az elemei nem.
// 6. PÉLDA – BEJÁRÁS foreach CIKLUSSAL: ÖSSZEG ÉS ÁTLAG.
double osszeg = 0; // Ebben gyűjtjük az elemek összegét. A 0 kezdőérték kötelező.
foreach (double homerseklet in homersekletek) // A foreach sorban felveszi a tömb minden elemét a homerseklet változóba. Pythonban: for homerseklet in homersekletek:
{ // Itt kezdődik a foreach ciklus törzse.
osszeg += homerseklet; // Hozzáadjuk az aktuális elemet az összeghez. Index itt nem kell, ezért kényelmesebb a for-nál.
// homerseklet = 0; // HIBA fordításkor: a foreach ciklusváltozója csak olvasható, a tömb elemét nem lehet így módosítani.
} // A ciklus végén az osszeg mind a 7 elem összege.
double atlag = osszeg / homersekletek.Length; // Az átlag: összeg / darabszám. Az osszeg double, ezért nem egészosztás történik.
Console.WriteLine($"A heti átlaghőmérséklet: {atlag:f2} °C"); // Kiírjuk az átlagot két tizedesjeggyel.
// 7. PÉLDA – EZÉRT KELLETT TÁROLNI: AZ ÁTLAG FELETTI NAPOK.
// Az átlagot csak az ÖSSZES elem ismeretében tudjuk. Ezért az adatokon KÉTSZER kell végigmenni:
// először kiszámoljuk az átlagot, utána megnézzük, mely napok voltak felette. Tömb nélkül ez nem menne.
Console.WriteLine("Az átlagnál melegebb napok:"); // Fejléc a listához.
for (int i = 0; i < homersekletek.Length; i++) // Itt for kell, nem foreach: a nap NEVÉHEZ az index kell, ami a napok tömbben is érvényes.
{ // Itt kezdődik a for ciklus törzse.
if (homersekletek[i] > atlag) // Ha az i. nap hőmérséklete az átlag felett van...
{ // Itt kezdődik az if blokk.
Console.WriteLine($"\t{napok[i]}: {homersekletek[i]:f1} °C"); // ...kiírjuk a nap nevét és a hőmérsékletet. A \t tabulátorral beljebb kezdi a sort.
} // Itt ér véget az if blokk.
} // Itt ér véget a for ciklus.
// 8. PÉLDA – TÖMB ÉS METÓDUS.
// A kiírásra mostantól többször is szükség lesz, ezért metódusba tesszük.
Console.WriteLine("\nKiderült, hogy a hőmérő 1 fokkal kevesebbet mutatott. Javítás után:"); // A történet: minden mért értéket korrigálni kell.
Korrigal(homersekletek, 1.0); // A metódus megkapja a tömböt, és az ELEMEIT módosítja. Nem ad vissza semmit (void)...
Kiir(napok, homersekletek); // ...mégis a javított értékek jelennek meg: a metódus ugyanazt a tömböt módosította, nem egy másolatot.
double[] fahrenheit = Fahrenheitre(homersekletek); // Ez a metódus NEM módosítja a kapott tömböt, hanem egy ÚJ tömböt hoz létre és ad vissza.
Console.WriteLine($"\nHétfő: {homersekletek[0]:f1} °C = {fahrenheit[0]:f1} °F"); // Az eredeti tömb (Celsius) megmaradt, mellette létezik az új (Fahrenheit).
} // Itt ér véget a Main metódus törzse.
static void Kiir(string[] napok, double[] homersekletek) // Két tömböt kap paraméterként; a [] jelzi, hogy tömböt vár. Nem ad vissza értéket.
{ // Itt kezdődik a Kiir() metódus törzse.
for (int i = 0; i < napok.Length; i++) // Ugyanaz a ciklus, mint az 5. példában – most egy helyen, névvel ellátva.
{ // Itt kezdődik a for ciklus törzse.
Console.WriteLine($"{i + 1}. nap ({napok[i]}): {homersekletek[i]:f1} °C"); // Sorszám, nap neve, hőmérséklet.
} // Itt ér véget a for ciklus.
} // Itt ér véget a Kiir() metódus.
static void Korrigal(double[] homersekletek, double eltolas) // A kapott tömb minden eleméhez hozzáadja az eltolást. void: nincs visszatérési érték.
{ // Itt kezdődik a Korrigal() metódus törzse.
for (int i = 0; i < homersekletek.Length; i++) // Módosítani csak for ciklussal, index segítségével lehet (foreach-csel nem).
{ // Itt kezdődik a for ciklus törzse.
homersekletek[i] += eltolas; // Az i. elem új értéke: a régi érték + eltolás. Ez a hívó oldalon lévő tömböt módosítja!
} // Itt ér véget a for ciklus.
} // Itt ér véget a Korrigal() metódus.
static double[] Fahrenheitre(double[] celsius) // A visszatérési típus double[]: a metódus egy egész tömböt ad vissza.
{ // Itt kezdődik a Fahrenheitre() metódus törzse.
double[] eredmeny = new double[celsius.Length]; // Új, ugyanakkora tömb az eredményeknek.
for (int i = 0; i < celsius.Length; i++) // Végigmegyünk a kapott tömb indexein.
{ // Itt kezdődik a for ciklus törzse.
eredmeny[i] = celsius[i] * 9 / 5 + 32; // Átváltás: °F = °C · 9 / 5 + 32. Az eredeti tömb érintetlen marad.
} // Itt ér véget a for ciklus.
return eredmeny; // Visszaadjuk az új tömböt a hívónak.
} // Itt ér véget a Fahrenheitre() metódus.
} // Itt ér véget a Program osztály törzse.
} // Itt ér véget a _13_Tombok névtér.
/*
* MI AZ A TÖMB?
* -------------
*
* A tömb azonos típusú értékek sorozata, egyetlen név alatt.
* Az elemeket a sorszámuk, vagyis az INDEXÜK alapján érjük el.
*
* double[] homersekletek = new double[7];
*
* index: [0] [1] [2] [3] [4] [5] [6]
* ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┐
* érték: │ 7,3 │ 9,1 │ -2,0 │ 12,4 │ 4,4 │ 10,0 │ 6,8 │
* └──────┴──────┴──────┴──────┴──────┴──────┴──────┘
* sorszám: 1. 2. 3. 4. 5. 6. 7.
*
* Három dolgot jegyezz meg rögtön:
*
* 1. Minden elem UGYANOLYAN típusú (itt mind double).
* 2. A méret a létrehozáskor dől el, és utána NEM változik.
* 3. Az indexelés 0-tól indul, az utolsó index: Length - 1.
*
* Mikor kell tömb?
*
* Ha az adatokra a feldolgozás során KÉSŐBB, vagy TÖBBSZÖR
* is szükség van. Például:
*
* - az átlag feletti elemek listázásához (előbb átlag, aztán lista);
* - ha a "3. nap" értékét kell kiírni;
* - ha az adatokat többféleképpen is fel kell dolgozni.
*
* Ha minden adatot csak egyszer kell felhasználni (például
* összegezni), akkor tömb nélkül is megoldható – ezt csináltuk
* a Napelem dolgozatban.
*/
/*
* TÖMB LÉTREHOZÁSA – KÉT MÓD
* --------------------------
*
* 1. Ha az elemeket már a kód írásakor ismerjük:
*
* string[] napok = { "hétfő", "kedd", "szerda" };
*
* A méretet a fordító számolja ki a felsorolásból (itt 3).
*
* 2. Ha csak a darabszámot tudjuk, az értékeket később kapjuk:
*
* double[] homersekletek = new double[7];
*
* double[] homersekletek = new double[7];
* ↓ ↓ ↓
* double elemekből a tömb 7 férőhelyes új tömb
* álló tömb típusa neve létrehozása
*
* Az új tömb elemei nem "üresek", hanem ALAPÉRTELMEZETT értéket kapnak:
*
* Típus Alapérték
* ----- ---------
* int, double 0
* bool false
* char '\0' (a 0 kódú karakter)
* string null (vagyis: nincs benne szöveg)
*
* A méret változóval is megadható – ez a gyakoribb:
*
* int db = 30;
* double[] termeles = new double[db];
*
* Vagy egy másik tömb méretéhez igazítva, ahogy a példában:
*
* double[] homersekletek = new double[napok.Length];
*/
/*
* INDEX ÉS SORSZÁM – A LEGGYAKORIBB HIBAFORRÁS
* --------------------------------------------
*
* Az ember sorszámmal gondolkodik (1., 2., 3. nap),
* a tömb indexszel (0, 1, 2).
*
* sorszám → index: index = sorszám - 1
* index → sorszám: sorszám = index + 1
*
* Mikor melyik kell?
*
* - a tömbhöz nyúlsz (olvasás, írás) → INDEX
* - a felhasználónak írsz ki valamit → SORSZÁM
*
* Console.WriteLine($"{i + 1}. nap: {homersekletek[i]}");
* ↑ ↑
* sorszám a index a
* felhasználónak tömbnek
*
* Ha a felhasználótól kérünk be egy napot:
*
* int nap = BekerEgesz("Hányadik nap? ", 1, 7); // 1..7 sorszám
* Console.WriteLine(homersekletek[nap - 1]); // 0..6 index
*
* Túlindexelés:
*
* homersekletek[7] // 7 elemnél nincs ilyen!
*
* Ez a program futása közben IndexOutOfRangeException hibát
* okoz, és a program leáll. Pythonban ennek az IndexError felel meg.
*
* C#-ban NINCS negatív index: a homersekletek[-1] nem az
* utolsó elem (mint Pythonban), hanem szintén hiba.
* Az utolsó elem: homersekletek[homersekletek.Length - 1].
*/
/*
* A TÖMB BEJÁRÁSA for CIKLUSSAL
* -----------------------------
*
* A tömb "bejárása" azt jelenti: sorban végigmegyünk az összes elemén.
*
* for (int i = 0; i < homersekletek.Length; i++)
* {
* ... homersekletek[i] ...
* }
*
* Mindig ezt a formát használd:
*
* - kezdés: i = 0 (az első index)
* - feltétel: i < tomb.Length (KISEBB, nem kisebb-egyenlő!)
* - léptetés: i++
*
* A < helyett írt <= a klasszikus hiba: az utolsó körben i == Length,
* és az már túlindexelés.
*
* A tömb méretét ne írd be számmal (i < 7), hanem kérd el a tömbtől
* (i < homersekletek.Length). Így ha a tömb mérete megváltozik,
* a ciklus magától igazodik hozzá.
*
* Python megfelelője:
*
* Python C#
* ------ --
* for i in range(len(homersekletek)): for (int i = 0; i < homersekletek.Length; i++)
* print(homersekletek[i]) {
* Console.WriteLine(homersekletek[i]);
* }
*/
/*
* A foreach CIKLUS
* ----------------
*
* foreach (double homerseklet in homersekletek)
* {
* osszeg += homerseklet;
* }
*
* foreach (double homerseklet in homersekletek)
* ↓ ↓ ↓ ↓ ↓
* "minden az elem ebbe kerül "-ban/ ezt a tömböt
* elemre" típusa sorban az -ben" járjuk be
* aktuális elem
*
* Python megfelelője szinte ugyanaz:
*
* for homerseklet in homersekletek:
* osszeg += homerseklet
*
* A foreach kényelmes, mert nem kell index, és nem lehet túlindexelni.
* De két dologra NEM alkalmas:
*
* 1. Az elem MÓDOSÍTÁSÁRA. A ciklusváltozó csak olvasható:
*
* homerseklet = 0; // fordítási hiba
*
* 2. Ha az INDEXRE is szükség van – például a sorszám kiírásához,
* vagy hogy egy MÁSIK tömb ugyanolyan indexű elemét is elérjük
* (napok[i] és homersekletek[i]).
*
* Döntési szabály:
*
* Csak olvasom az elemeket, és az index nem kell → foreach
* Módosítom az elemeket, vagy kell az index → for
*/
/*
* KÉT TÖMB, KÖZÖS INDEX
* ---------------------
*
* A példában két tömb van:
*
* napok: "hétfő" "kedd" "szerda" ...
* homersekletek: 7,3 9,1 -2,0 ...
* index: 0 1 2 ...
*
* Az összetartozó adatokat az INDEX köti össze: a napok[2] és a
* homersekletek[2] ugyanarról a napról szól. Ezért kell a 7. példában
* for ciklus – a foreach csak a hőmérsékletet adná, a nap nevét nem.
*
* Ezt a megoldást párhuzamos tömböknek hívjuk. A két tömbnek mindig
* ugyanakkorának kell lennie, ezért hoztuk létre így:
*
* double[] homersekletek = new double[napok.Length];
*/
/*
* A TÖMBÖT NEM LEHET EGYBEN KIÍRNI
* --------------------------------
*
* Pythonban:
*
* print(homersekletek) → [7.3, 9.1, -2.0, ...]
*
* C#-ban:
*
* Console.WriteLine(homersekletek); → System.Double[]
*
* A C# a tömb TÍPUSÁT írja ki, nem a tartalmát. Az elemeket egyenként,
* ciklussal kell kiírni – ezért van a programban Kiir() metódus.
*/
/*
* TÖMB ÁTADÁSA METÓDUSNAK
* -----------------------
*
* Paraméterként:
*
* static void Kiir(string[] napok, double[] homersekletek)
*
* A típus után álló [] jelzi, hogy tömböt vár a metódus.
* Meghíváskor csak a tömb nevét adjuk át, [] nélkül:
*
* Kiir(napok, homersekletek);
*
* Visszatérési értékként:
*
* static double[] Fahrenheitre(double[] celsius)
* {
* double[] eredmeny = new double[celsius.Length];
* ...
* return eredmeny;
* }
*
* double[] fahrenheit = Fahrenheitre(homersekletek);
*
* FONTOS: A METÓDUS AZ EREDETI TÖMBÖT KAPJA MEG, NEM MÁSOLATOT!
*
* Egy int paraméternél a metódus a szám MÁSOLATÁT kapja; ha
* megváltoztatja, a hívó változója nem változik.
*
* A tömb más: a metódus ugyanazt a tömböt látja, mint a hívó.
* Ha a metódus módosít egy elemet, az a hívónál is megváltozik.
* Ezért működik a Korrigal(), pedig void, és nem ad vissza semmit.
*
* Korrigal(homersekletek, 1.0); // a homersekletek elemei megváltoznak
*
* A két minta tehát:
*
* Korrigal() → az átadott tömböt MÓDOSÍTJA (void)
* Fahrenheitre() → ÚJ tömböt hoz létre és ad vissza (double[])
*
* Pythonban a lista ugyanígy viselkedik: ha egy függvény módosítja
* a kapott listát, a hívó is a módosított listát látja.
*/
/*
* TÖMB FELTÖLTÉSE BEKÉRÉSSEL
* --------------------------
*
* A 12-es anyag ellenőrzött bekérését a tömb feltöltésére is
* használhatjuk. A BekerValos() metódus ugyanaz, mint ott:
*
* for (int i = 0; i < homersekletek.Length; i++)
* {
* Console.Write($"{napok[i]}: ");
* homersekletek[i] = BekerValos(-30, 45);
* }
*
* Ha a darabszámot is a felhasználó adja meg, a tömb a bekérés UTÁN
* jön létre:
*
* int db = BekerEgesz("Hány mérés volt? ", 1, 100);
* double[] meresek = new double[db];
*/
/*
* TÖMB VAGY PYTHON-LISTA?
* -----------------------
*
* Python lista C# tömb
* ------------ -------
* lista = [7.3, 9.1] double[] t = { 7.3, 9.1 };
* len(lista) t.Length
* lista[0] t[0]
* lista[-1] t[t.Length - 1]
* lista.append(5) NINCS – a méret fix!
* vegyes típus lehet minden elem azonos típusú
* print(lista) ciklussal kell kiírni
* IndexError IndexOutOfRangeException
*
* A C# tömb tehát NEM a Python-lista megfelelője. Arra a List<T>
* való, amelyhez néhány hét múlva érünk el. A tömb akkor jó
* választás, ha a darabszám előre ismert: 7 nap, 30 nap, 5 lottószám.
*/
/*
* ÖSSZEFOGLALÁS
* -------------
*
* double[] t = new double[7]; 7 elemű, minden elem 0
* string[] s = { "a", "b", "c" }; 3 elemű, kezdőértékekkel
* t.Length elemszám
* t[0] ... t[t.Length - 1] első ... utolsó elem
* index = sorszám - 1 a "3. nap" a t[2]
*
* for (int i = 0; i < t.Length; i++) ha kell az index vagy módosítasz
* foreach (double x in t) ha csak olvasol
*
* static void M(double[] t) tömb paraméter (az eredetit kapja!)
* static double[] M(...) tömb visszatérési érték
*
* Ellenőrző kérdések:
*
* 1. Mit ír ki?
* int[] t = new int[4];
* Console.WriteLine(t[2] + t.Length);
*
* 2. Mi a hiba?
* for (int i = 0; i <= t.Length; i++)
* Console.WriteLine(t[i]);
*
* 3. Egy 30 elemű tömbben hányas indexű a 15. nap? És az utolsó?
*
* 4. Miért nem fordul le?
* foreach (int x in t)
* {
* x = x * 2;
* }
* Írd át úgy, hogy működjön!
*
* 5. Mit ír ki a program?
* int[] a = { 1, 2, 3 };
* Duplaz(a); // minden elemet megszoroz 2-vel
* Console.WriteLine(a[0]);
*/
namespace _13_Tombok
{
internal class Program
{
static void Main(string[] args)
{
Random random = new Random();
string[] napok = { "hétfő", "kedd", "szerda", "csütörtök", "péntek", "szombat", "vasárnap" };
Console.WriteLine($"A napok tömb elemszáma: {napok.Length}");
Console.WriteLine($"Az első elem (index 0): {napok[0]}");
Console.WriteLine($"Az utolsó elem (index {napok.Length - 1}): {napok[napok.Length - 1]}");
int sorszam = 3;
Console.WriteLine($"A(z) {sorszam}. nap: {napok[sorszam - 1]}\n");
double[] homersekletek = new double[napok.Length];
Console.WriteLine($"Létrehozás után az első elem: {homersekletek[0]}\n");
for (int i = 0; i < homersekletek.Length; i++)
{
homersekletek[i] = random.Next(-50, 151) / 10.0;
}
Console.WriteLine("Heti hőmérsékletek:");
for (int i = 0; i < napok.Length; i++)
{
Console.WriteLine($"{i + 1}. nap ({napok[i]}): {homersekletek[i]:f1} °C");
}
Console.WriteLine($"Így NEM lehet kiírni egy tömböt: {homersekletek}\n");
double osszeg = 0;
foreach (double homerseklet in homersekletek)
{
osszeg += homerseklet;
}
double atlag = osszeg / homersekletek.Length;
Console.WriteLine($"A heti átlaghőmérséklet: {atlag:f2} °C");
Console.WriteLine("Az átlagnál melegebb napok:");
for (int i = 0; i < homersekletek.Length; i++)
{
if (homersekletek[i] > atlag)
{
Console.WriteLine($"\t{napok[i]}: {homersekletek[i]:f1} °C");
}
}
Console.WriteLine("\nKiderült, hogy a hőmérő 1 fokkal kevesebbet mutatott. Javítás után:");
Korrigal(homersekletek, 1.0);
Kiir(napok, homersekletek);
double[] fahrenheit = Fahrenheitre(homersekletek);
Console.WriteLine($"\nHétfő: {homersekletek[0]:f1} °C = {fahrenheit[0]:f1} °F");
}
static void Kiir(string[] napok, double[] homersekletek)
{
for (int i = 0; i < napok.Length; i++)
{
Console.WriteLine($"{i + 1}. nap ({napok[i]}): {homersekletek[i]:f1} °C");
}
}
static void Korrigal(double[] homersekletek, double eltolas)
{
for (int i = 0; i < homersekletek.Length; i++)
{
homersekletek[i] += eltolas;
}
}
static double[] Fahrenheitre(double[] celsius)
{
double[] eredmeny = new double[celsius.Length];
for (int i = 0; i < celsius.Length; i++)
{
eredmeny[i] = celsius[i] * 9 / 5 + 32;
}
return eredmeny;
}
}
}