12. TANANYAG · C# ALAPOK

While és do…while.

Feltételes ciklusok, ellenőrzött bekérés

A lépésről lépésre animáció egy kockája: Visual Studio és magyarázó tábla

ANIMÁCIÓ · NARRÁLT

Lépésről lépésre

Tanári bemutató: a program üres fájlból, sorról sorra épül fel, közben a tábla mutatja a memóriát, a kiértékelést és a konzolt. A futások valódi .NET-futtatásból származnak.

18,6 perc · 14 fejezet · hang és felirat · fejezetválasztó

Lejátszás
Az áttekintő animáció egy kockája

ANIMÁCIÓ · FELIRATOS

Áttekintés

Rövid, hang nélküli összefoglaló a kód elemeiről, felirattal. Ismétléshez vagy a téma gyors felidézéséhez.

4,8 perc · felirat · saját lejátszósáv

Megnyitás
12_while_kommentelt.csTeljes nézet →Letöltés
namespace _12_While // A namespace (névtér) összefogja az ehhez a példához tartozó kódelemeket; ennek a névtérnek a neve _12_While.
{ // 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 két saját bekérő metódus.
    { // Itt kezdődik a Program osztály törzse.
        static void Main(string[] args) // A Main a program belépési pontja; static: példány nélkül futtatható, void: nem ad vissza értéket.
        { // Itt kezdődik a Main metódus törzse.
            Random random = new Random(); // Egyetlen Random objektumot hozunk létre, és a program végéig ezt használjuk.

            // 1. PÉLDA – while: ELÖLTESZTELŐ CIKLUS.
            // Dobunk egy kockával, amíg 6-ost nem kapunk. Előre NEM tudjuk, hány dobás kell – ezért nem for ciklus.

            int dobas = 0; // Ide kerül az aktuális dobás. A 0 biztosan nem 6, így a feltétel elsőre igaz lesz.
            int dobasokSzama = 0; // Számláló: hány dobásra volt szükség.

            while (dobas != 6) // A feltételt a ciklusmag ELŐTT vizsgálja: amíg a dobás nem 6, addig ismétel.
            { // Itt kezdődik a while ciklus törzse.
                dobas = random.Next(1, 7); // Új dobás 1..6 között; a 7 már nem lehet eredmény.
                dobasokSzama++; // Eggyel növeljük a számlálót.
                Console.Write($"{dobas} "); // Kiírjuk a dobást ugyanabba a sorba.
            } // A blokk végén a program visszaugrik a feltételhez, és újra ellenőrzi.

            Console.WriteLine($"\n{dobasokSzama} dobás kellett a 6-oshoz.\n"); // Ide csak akkor jut el a program, ha a feltétel hamis lett, vagyis dobas == 6.

            // 2. PÉLDA – while ÉS do…while KÜLÖNBSÉGE.
            // Mindkét ciklus feltétele már az elején hamis. Melyik fut le, és hányszor?

            int szam = 10; // A szam értéke 10, tehát a szam < 5 feltétel hamis.

            while (szam < 5) // Elöltesztelő: a feltétel hamis, ezért a ciklusmag EGYSZER SEM fut le.
            {
                Console.WriteLine("while: ez a sor nem jelenik meg."); // Ez a sor soha nem hajtódik végre.
            }

            do // Hátultesztelő: a program előbb végrehajtja a ciklusmagot, és csak utána vizsgál.
            {
                Console.WriteLine("do…while: ez a sor egyszer megjelenik, pedig a feltétel hamis.\n"); // Ez a sor EGYSZER biztosan lefut.
            } while (szam < 5); // A feltétel a ciklus VÉGÉN van; itt pontosvessző is kell!

            // 3. PÉLDA – ELLENŐRZÖTT BEKÉRÉS: do…while + int.TryParse.
            // A bekérést addig ismételjük, amíg a felhasználó nem ad meg érvényes számot a kért tartományban.

            int kor; // Ebbe kerül a beolvasott életkor. Itt még nem kap értéket, a TryParse tölti fel.
            bool sikeres; // Ebbe kerül, hogy sikerült-e számmá alakítani a beírt szöveget.

            do // A bekérésnek legalább egyszer meg kell történnie, ezért hátultesztelő ciklus kell.
            {
                Console.Write("Hány éves vagy? (14–20): "); // Kiírjuk a kérdést; a Write után a kurzor ugyanabban a sorban marad.
                sikeres = int.TryParse(Console.ReadLine(), out kor); // Megpróbálja számmá alakítani a beírt szöveget: true, ha sikerült, és ilyenkor a kor változóba kerül a szám.

                if (!sikeres || kor < 14 || kor > 20) // Hibás, ha nem szám, VAGY túl kicsi, VAGY túl nagy.
                {
                    Console.WriteLine("Hibás érték! 14 és 20 közötti egész számot adj meg!"); // A felhasználó megtudja, mit rontott el.
                }
            } while (!sikeres || kor < 14 || kor > 20); // Ugyanaz a feltétel: amíg hibás az adat, addig ismétlünk.

            Console.WriteLine($"Rendben, {kor} éves vagy.\n"); // Ide már csak érvényes adattal jut el a program.

            // 4. PÉLDA – VALÓS SZÁM BEKÉRÉSE PONTTAL ÉS VESSZŐVEL IS (metódusban).
            // Pontosan ilyen feladat szerepel a technikusi vizsga konzolos részében.

            double ertekeles = BekerValos(0, 10); // A metódus addig kérdez, amíg 0 és 10 közötti valós számot nem kap, és azt adja vissza.
            Console.WriteLine($"A megadott értékelés: {ertekeles}\n"); // Kiírjuk a már biztosan érvényes valós számot.

            // 5. PÉLDA – SZÁMKITALÁLÓ: A KÉT CIKLUS EGYÜTT.
            // A gép gondol egy számra 1 és 100 között. A tippek ellenőrzött bekéréssel érkeznek.

            int gondolt = random.Next(1, 101); // A gép által gondolt szám: 1..100.
            int tipp = 0; // A 0 biztosan nem a gondolt szám, így a while feltétele elsőre igaz.
            int tippekSzama = 0; // Számláló a tippek darabszámához.

            while (tipp != gondolt) // Elöltesztelő ciklus: addig fut, amíg el nem találjuk a számot.
            {
                tipp = BekerEgesz("Tipped: ", 1, 100); // A metódus csak érvényes, 1..100 közötti egész számot ad vissza.
                tippekSzama++; // Minden érvényes tipp után nő a számláló.

                if (tipp < gondolt) // Ha a tipp kisebb a gondolt számnál...
                {
                    Console.WriteLine("Nagyobb számra gondoltam."); // ...felfelé irányítjuk a játékost.
                }
                else if (tipp > gondolt) // Ha a tipp nagyobb a gondolt számnál...
                {
                    Console.WriteLine("Kisebb számra gondoltam."); // ...lefelé irányítjuk a játékost.
                }
            } // Ha tipp == gondolt, a feltétel hamis lesz, és a ciklus véget ér.

            Console.WriteLine($"Eltaláltad! A szám {gondolt} volt, {tippekSzama} tippből."); // A ciklus után már biztosan eltalálták a számot.
        } // Itt ér véget a Main metódus törzse.

        static int BekerEgesz(string kerdes, int minimum, int maximum) // Egész számot kér be a [minimum; maximum] tartományból; a kérdés szövegét paraméterként kapja.
        { // Itt kezdődik a BekerEgesz() metódus törzse.
            int ertek; // Ide kerül a beolvasott szám.
            bool sikeres; // Ide kerül, hogy sikerült-e a számmá alakítás.

            do // Legalább egyszer meg kell kérdezni a felhasználót.
            {
                Console.Write(kerdes); // Kiírjuk a paraméterként kapott kérdést.
                sikeres = int.TryParse(Console.ReadLine(), out ertek); // Szövegből egész szám – hiba esetén nem áll le a program, csak false-t kapunk.

                if (!sikeres || ertek < minimum || ertek > maximum) // Hibás adat esetén...
                {
                    Console.WriteLine($"Hibás érték! {minimum} és {maximum} közötti egész számot adj meg!"); // ...hibaüzenet, és a ciklus újra kérdez.
                }
            } while (!sikeres || ertek < minimum || ertek > maximum); // Amíg hibás az adat, addig ismétlünk.

            return ertek; // Ide csak érvényes adattal jutunk el; ezt adjuk vissza a hívónak.
        } // Itt ér véget a BekerEgesz() metódus.

        static double BekerValos(double minimum, double maximum) // Valós számot kér be a [minimum; maximum] tartományból; a tizedespontot és a tizedesvesszőt is elfogadja.
        { // Itt kezdődik a BekerValos() metódus törzse.
            double ertek; // Ide kerül a beolvasott valós szám.
            bool sikeres; // Ide kerül, hogy sikerült-e a számmá alakítás.

            do // Legalább egyszer meg kell kérdezni a felhasználót.
            {
                Console.Write($"Adj meg egy értékelést {minimum} és {maximum} között: "); // A kérdésben a tartomány is megjelenik.
                string beirt = Console.ReadLine()!.Replace('.', ','); // Beolvassuk a szöveget, és minden pontot vesszőre cserélünk; így a "7.5" és a "7,5" is "7,5" lesz.
                sikeres = double.TryParse(beirt, out ertek); // Magyar területi beállításnál a double.TryParse a tizedesvesszőt várja.

                if (!sikeres || ertek < minimum || ertek > maximum) // Hibás adat esetén...
                {
                    Console.WriteLine($"Hibás érték! {minimum} és {maximum} közötti számot adj meg!"); // ...hibaüzenet, és a ciklus újra kérdez.
                }
            } while (!sikeres || ertek < minimum || ertek > maximum); // Amíg hibás az adat, addig ismétlünk.

            return ertek; // Csak érvényes valós számot adunk vissza.
        } // Itt ér véget a BekerValos() metódus.
    } // Itt ér véget a Program osztály törzse.
} // Itt ér véget a _12_While névtér.


/*
 * MIRE VALÓ A while CIKLUS?
 * -------------------------
 *
 * A for ciklust akkor használjuk, amikor előre tudjuk,
 * hányszor kell ismételni:
 *
 *     for (int i = 0; i < 10; i++)      // pontosan 10-szer
 *
 * Sokszor azonban NEM tudjuk előre, hányszor kell ismételni:
 *
 *     - dobunk, amíg 6-ost nem kapunk;
 *     - kérdezünk, amíg a felhasználó jó adatot nem ad;
 *     - tippelünk, amíg el nem találjuk a számot.
 *
 * Ilyenkor nem a darabszám, hanem egy FELTÉTEL dönti el,
 * meddig fut a ciklus. Erre való a while és a do…while.
 *
 * Röviden:
 *
 *     for        → tudom, HÁNYSZOR
 *     while      → tudom, MEDDIG
 */


/*
 * A while CIKLUS FELÉPÍTÉSE
 * -------------------------
 *
 *     while (feltétel)
 *     {
 *         ciklusmag
 *     }
 *
 * A működése:
 *
 *     1. A program megvizsgálja a feltételt.
 *     2. Ha IGAZ: végrehajtja a ciklusmagot, majd visszamegy az 1. lépéshez.
 *     3. Ha HAMIS: kilép a ciklusból, és a ciklus utáni sorral folytatja.
 *
 * Mivel a feltétel a ciklusmag ELŐTT áll, ennek a neve:
 *
 *     ELÖLTESZTELŐ CIKLUS
 *
 * Következmény: ha a feltétel már az elején hamis,
 * a ciklusmag EGYSZER SEM fut le (lásd a 2. példát).
 *
 * Python megfelelője szinte betűre ugyanaz:
 *
 *     Python                          C#
 *     ------                          --
 *     while dobas != 6:               while (dobas != 6)
 *         dobas = random.randint(1,6) {
 *                                         dobas = random.Next(1, 7);
 *                                     }
 *
 * A különbség csak a jelölésben van:
 *
 *     - a feltétel zárójelbe kerül;
 *     - a ciklusmagot nem a behúzás, hanem a { } jelöli;
 *     - a sor végén nincs kettőspont.
 */


/*
 * A FELTÉTELBEN SZEREPLŐ VÁLTOZÓNAK KELL KEZDŐÉRTÉK
 * -------------------------------------------------
 *
 * Az 1. példában ezt írtuk:
 *
 *     int dobas = 0;
 *     while (dobas != 6)
 *
 * Miért kell a = 0?
 *
 * Mert a while az első kör ELŐTT már megvizsgálja a dobas értékét.
 * Ha a változónak nincs értéke, a C# fordító hibát jelez:
 *
 *     Use of unassigned local variable 'dobas'
 *
 * Ezért olyan kezdőértéket adunk, amellyel a feltétel biztosan igaz,
 * így a ciklus legalább egyszer elindul. A 0 jó választás,
 * mert kockával 0-t nem lehet dobni.
 *
 * Ugyanezért kapja a tipp is a 0 kezdőértéket a számkitalálóban.
 */


/*
 * A do…while CIKLUS FELÉPÍTÉSE
 * ----------------------------
 *
 *     do
 *     {
 *         ciklusmag
 *     } while (feltétel);
 *
 * A működése:
 *
 *     1. A program végrehajtja a ciklusmagot.
 *     2. Megvizsgálja a feltételt.
 *     3. Ha IGAZ: visszamegy az 1. lépéshez.
 *     4. Ha HAMIS: kilép a ciklusból.
 *
 * Mivel a feltétel a ciklusmag UTÁN áll, ennek a neve:
 *
 *     HÁTULTESZTELŐ CIKLUS
 *
 * Következmény: a ciklusmag LEGALÁBB EGYSZER biztosan lefut,
 * akkor is, ha a feltétel már az elején hamis.
 *
 * Figyelem! A while (feltétel) után itt PONTOSVESSZŐ kell:
 *
 *     } while (szam < 5);
 *
 * Pythonban NINCS do…while. Ott ezt így szoktuk megoldani:
 *
 *     while True:
 *         ...
 *         if feltétel:
 *             break
 */


/*
 * MIKOR while ÉS MIKOR do…while?
 * ------------------------------
 *
 * A kérdés mindig ez:
 *
 *     Előfordulhat-e, hogy a ciklusmagnak EGYSZER SEM kell lefutnia?
 *
 *     IGEN → while
 *     NEM, legalább egyszer biztosan kell → do…while
 *
 * Példák:
 *
 *     Feladat                                    Ciklus
 *     -------                                    ------
 *     Dobás, amíg nem 6                          while (vagy do…while)
 *     Tippelés, amíg el nem találja              while (vagy do…while)
 *     Adat bekérése, amíg nem jó                 do…while
 *     Menü kiírása, amíg nem választ kilépést    do…while
 *
 * Bekérésnél azért természetes a do…while, mert MIELŐTT
 * ellenőrizni tudnánk az adatot, legalább egyszer meg kell kérdezni.
 *
 * Az 1. példa do…while ciklussal is megírható, sőt akkor nem kell
 * a "trükkös" 0 kezdőérték sem:
 *
 *     int dobas;
 *     do
 *     {
 *         dobas = random.Next(1, 7);
 *     } while (dobas != 6);
 *
 * Mindkét megoldás helyes. A lényeg, hogy tudd megindokolni,
 * miért azt választottad.
 */


/*
 * A FELTÉTEL: AMÍG ISMÉTELNI KELL, NEM AMÍG KÉSZ!
 * -----------------------------------------------
 *
 * A while feltétele azt írja le, hogy MIKOR KELL MÉG folytatni,
 * nem azt, hogy mikor kell megállni.
 *
 * A feladat szövege általában a megállást mondja:
 *
 *     "Dobj, AMÍG 6-ost NEM kapsz."     → megállás: dobas == 6
 *
 * A feltételbe ennek az ellentéte kerül:
 *
 *     while (dobas != 6)               → folytatás: dobas != 6
 *
 * Összetett feltételnél ugyanez a helyzet. Bekérésnél:
 *
 *     jó az adat:     sikeres  ÉS  kor >= 14  ÉS  kor <= 20
 *     ismételni kell: !sikeres VAGY kor < 14  VAGY kor > 20
 *
 * Vagyis a "jó" feltétel tagadásakor:
 *
 *     - minden rész az ellentétére fordul (== → !=, >= → <, <= → >);
 *     - az ÉS (&&) helyére VAGY (||) kerül.
 *
 * Ez a leggyakoribb hiba a while ciklusoknál: && kerül || helyett.
 * Ha nem vagy biztos benne, írd így, ez is helyes:
 *
 *     while (!(sikeres && kor >= 14 && kor <= 20));
 */


/*
 * VÉGTELEN CIKLUS
 * ---------------
 *
 * Ha a ciklusmagban semmi nem változtatja meg a feltételt,
 * a ciklus soha nem áll le:
 *
 *     int dobas = 0;
 *     while (dobas != 6)
 *     {
 *         Console.WriteLine("Dobok...");    // a dobas soha nem változik!
 *     }
 *
 * Ezért minden while ciklusnál nézd meg:
 *
 *     Melyik sor változtatja meg a ciklusmagban azt,
 *     amit a feltétel vizsgál?
 *
 * Ha ilyen sor nincs, a ciklus végtelen.
 *
 * Futó végtelen ciklust a konzolablakban a Ctrl + C billentyűkkel
 * lehet leállítani.
 */


/*
 * int.TryParse – BIZTONSÁGOS SZÁMMÁ ALAKÍTÁS
 * ------------------------------------------
 *
 * Eddig így kértünk be számot:
 *
 *     int kor = int.Parse(Console.ReadLine());
 *
 * Ha a felhasználó azt írja be, hogy "alma", az int.Parse
 * hibát dob, és a program LEÁLL:
 *
 *     System.FormatException: The input string 'alma' was not
 *     in a correct format.
 *
 * Az int.TryParse ezzel szemben nem állítja le a programot.
 * Megpróbálja az átalakítást, és két dolgot ad vissza:
 *
 *     bool sikeres = int.TryParse(Console.ReadLine(), out kor);
 *       ↓                              ↓                ↓
 *     sikerült-e?             ezt próbálja számmá    ide teszi
 *     (true / false)          alakítani              a számot
 *
 * Esetek:
 *
 *     Beírt szöveg     sikeres     kor
 *     ------------     -------     ---
 *     "17"             true        17
 *     "alma"           false       0
 *     ""               false       0
 *     "17,5"           false       0     (nem egész szám!)
 *
 * Az out kulcsszó jelzi, hogy a metódus ebbe a változóba ÍR.
 * Egy metódus normál esetben csak egy értéket tud visszaadni (return),
 * az out paraméterrel egy második eredményt is "kiad".
 *
 * Python megfelelője:
 *
 *     Python                              C#
 *     ------                              --
 *     try:                                bool sikeres =
 *         kor = int(input())                  int.TryParse(Console.ReadLine(), out kor);
 *     except ValueError:                  if (!sikeres)
 *         print("Hibás!")                     Console.WriteLine("Hibás!");
 *
 * Pythonban a hibát utólag "elkapjuk", C#-ban a TryParse
 * előre megmondja, hogy sikerült-e. (A C#-nak is van try–catch
 * szerkezete, azzal később foglalkozunk.)
 */


/*
 * AZ ELLENŐRZÖTT BEKÉRÉS MINTÁJA – EZT TUDD FEJBŐL!
 * -------------------------------------------------
 *
 *     int ertek;
 *     bool sikeres;
 *     do
 *     {
 *         Console.Write("Kérdés: ");
 *         sikeres = int.TryParse(Console.ReadLine(), out ertek);
 *     } while (!sikeres || ertek < MIN || ertek > MAX);
 *
 * Három rész:
 *
 *     1. do        → legalább egyszer kérdezünk;
 *     2. TryParse  → nem áll le a program hibás szövegnél;
 *     3. feltétel  → ismétlünk, ha nem szám VAGY tartományon kívül esik.
 *
 * A feltétel sorrendje számít: a !sikeres áll elöl.
 * Ha az átalakítás nem sikerült, a || miatt a többi részt
 * a program már meg sem vizsgálja.
 *
 * Ha hibaüzenetet is szeretnénk, a ciklusmagba egy if kerül
 * ugyanazzal a feltétellel (lásd a 3. példát).
 *
 * Érdemes az egészet metódusba tenni (BekerEgesz), így a programban
 * akárhányszor használhatjuk egyetlen sorral:
 *
 *     int tipp = BekerEgesz("Tipped: ", 1, 100);
 *     int ev   = BekerEgesz("Születési év: ", 1900, 2026);
 */


/*
 * TIZEDESPONT VAGY TIZEDESVESSZŐ?
 * -------------------------------
 *
 * Magyar területi beállítású gépen a double.TryParse a
 * TIZEDESVESSZŐT várja:
 *
 *     "7,5"   → sikeres, 7,5
 *     "7.5"   → NEM az, amit szeretnénk
 *
 * A felhasználók viszont gyakran pontot írnak. Ezért a beolvasott
 * szövegben a pontokat vesszőre cseréljük, MIELŐTT számmá alakítjuk:
 *
 *     string beirt = Console.ReadLine()!.Replace('.', ',');
 *
 * A Replace('.', ',') a szöveg minden '.' karakterét ','-re cseréli.
 * Így mindkét írásmód ugyanazt adja:
 *
 *     "7.5"  → "7,5"  → 7,5
 *     "7,5"  → "7,5"  → 7,5
 *
 * A ! jel a ReadLine() után azt mondja a fordítónak: "tudom,
 * hogy itt nem lesz null érték". Enélkül a Visual Studio
 * figyelmeztetést (zöld/sárga hullámvonalat) mutatna.
 *
 * A technikusi vizsga konzolos részében szó szerint ilyen feladat
 * szerepelt:
 *
 *     "A bekérést mindaddig ismételje meg, amíg a felhasználó egy
 *     [0;10] intervallumba eső valós számot nem ad meg. (...)
 *     Biztosítsa, hogy a program a tizedesvesszővel és a
 *     tizedesponttal megadott számokat is elfogadja."
 *
 * Ez a BekerValos(0, 10) metódus.
 */


/*
 * ÖSSZEFOGLALÁS
 * -------------
 *
 *     for          ismert darabszám        for (int i = 0; i < n; i++)
 *     while        elöltesztelő            0-szor is lefuthat
 *     do…while     hátultesztelő           legalább 1-szer lefut, ; a végén!
 *
 *     int.TryParse(szöveg, out valtozo)      true / false + a szám
 *     double.TryParse(szöveg, out valtozo)   ugyanez valós számra
 *     .Replace('.', ',')                     pont helyett vessző
 *
 * Ellenőrző kérdések:
 *
 *     1. Hányszor fut le a ciklusmag?
 *            int x = 3;
 *            while (x > 5) { x++; }
 *
 *     2. És ha do…while-ra írjuk át?
 *
 *     3. Mi a hiba?
 *            int dobas = 0;
 *            while (dobas != 6)
 *            {
 *                Console.WriteLine(dobas);
 *            }
 *
 *     4. Írd fel annak a bekérésnek a feltételét, amely egy 1 és 5
 *        közötti osztályzatot fogad el!
 *
 *     5. Mi lesz a sikeres és a szam értéke?
 *            bool sikeres = int.TryParse("12a", out int szam);
 */