[itk] Számítástechnika kezdőknek

C++ programozás kezdőknek - do-while ciklus, input ellenőrzés

[2021. október 04.] [ christo161 ]

Ebben a tananyagrészben a do-while ciklus alkalmazásairól lesz szó, illetve arról, hogy hogyan kezelhetjük az olyan eseteket, amikor például egy számot kérünk be a felhasználótól, de a felhasználó nem számot ad meg.

Figyelem: ezek a példakódok online fordítókkal (pl. wandbox, rextester) nem próbálhatók ki megfelelően.

Előző tananyagrész: while ciklus, fájlkezelés
Következő tananyagrész: continue és break utasítás

Tartalom

Alapvető tudnivalók

A C és C++ nyelvek egyaránt azt a filozófiát követik, hogy a legelemibb műveletekhez nem kapcsolódnak automatikusan ellenőrzések, hanem azokat a programozónak kell megoldania, ha igényt tart rá. Ez azért van így, mert elképzelhető, hogy valahol inkább az az elvárás, hogy a program a lehető leggyorsabb legyen, nem pedig az, hogy a nem rendeltetésszerű használatot is kezelje. A nem rendeltetésszerű használatra egy nagyon egyszerű példa a parancssoros programok esetén ha a felhasználótól számot kér be a program, de a felhasználó betűket vagy egyéb karaktereket ad meg, akár véletlenül vagy szándékosan.

do-while ciklus

A do-while ciklus egy viszonylag speciális esetben használt ciklus, jellemzően 3 esetben használjuk:

  • adatbevitel-ellenőrzés
  • egy adott kódrészlet újbóli lefuttatásának megkérdezése a felhasználótól
  • parancssoros menürendszer

struktogram_ciklus_hatultesztelos.png

A while ciklussal és a for ciklussal ellentétben a do-while ciklus ciklusmagjában lévő utasítások egyszer mindenképpen lefutnak, a feltétel kiértékelésétől függetlenül. A fent felsorolt feladatok esetén ez persze logikus, hiszen ha például ellenőrízni szeretnénk, hogy a felhasználó helyes adatot adott-e meg, akkor ahhoz, hogy ezt eldöntsük, egyszer mindenképp végre kell hajtani a bekérést, és csak azután lehet a feltételeket ellenőrízni, miután a bekérés megtörtént.

adatbevitel-ellenőrzés do-while ciklussal

Ha a felhasználó nem szám értéket ad meg szám típusú változóba való bekéréskor

Ebben a példában addig kérünk be adatot a felhasználótól, amíg egy pozitív számot nem ad meg, másképp fogalmazva a bekérés csak akkor lesz elfogadva ha a felhasználó pozitív számot ad meg, ellenkező esetben a bekérés meg lesz ismételve.

Ez a példa azokat az eseteket is kezeli, ha a felhasználó nem számot ad meg, hanem például betűket vagy egyéb írásjeleket. Ekkor alapesetben az std::cin beállít egy hibaflaget, és onnantól kezdve a program összes többi bekérése figyelmen kívül lesz hagyva. Az std::cin clear() tagfüggvényével tudjuk a hibaflaget visszabillenteni az alapállapotva. Furcsának tűnhet, hogy ez nem történik meg automatikusan, de hibaelhárításnál, debuggolásnál hasznos lehet, ha látjuk, hogy melyik volt az utolsó sikeres bekérés.
Hiba esetén az std::cin.clear(); utasítást mindenképp érdemes végrehajtani, ha ezt nem tennénk meg, végtelen ciklust eredményezne ha a felhasználó egyszer nem számot adna meg, hiszen a bekérés meg lenne ismételve, viszont a hibaflag sosem állítódna vissza.
Hiba esetén az input buffer tartalmát is ürítjük (a tartalmát getline függvénnyel bekérjük egy temp változóba), hiszen ha a felhasználó nem számot ad meg, akkor a beírt adatok nem törlődnek automatikusan az input bufferből.

A program futásakor azt láthatjuk, hogy ha a felhasználó nem számot ad meg a bekérésnél, a program meg fogja ismételni a bekérést ("Enter a number:" hibaüzenettel).

Vegyük észre, hogy egész szám típusú változóba kérünk be értéket a felhasználótól. Érdekes módon a C++ nyelv nem veszi hibának, ha a felhasználó valós számot ad meg, (pl. 3.14-et), ekkor ez a szám átkonvertálódik egész számmá (a 3.14 esetén 3-á).

//input validation example
#include <iostream>
#include <string>

int main() {
  long int input_number = 0;

  bool error_flag = false;
  do {
    std::cout << "Enter a number:\n";
    std::cin >> input_number;
    error_flag = std::cin.fail();

    if (error_flag) {
      std::cout << "Invalid data!" << '\n';
      std::cin.clear();
      std::string temp;
      getline(std::cin,temp,'\n');
    }
  } while (error_flag);

  std::cout << "The input value: " << input_number << '\n';
}

Amelyik változóba a számot bekérjük, annak a típusa természetesen átírható double-ra is, hogy ne csak egész számokat tudjunk benne tárolni, hanem tizedestörteket is.

Figyelem: a pdf dokumentumok díszített idézőjeleket és aposztrófokat tartalmaznak. Ha onnan másoljuk ki a programkódot, az idézőjelek és aposztrófok átírása nélkül nem fog lefordulni.

Nem negatív számok bekérése

Ha valamilyen feltétel szerint szűkíteni szeretnénk a bekért számra vonatkozó feltételeket, akkor ezt a sort kell módosÍtanunk:

error_flag = std::cin.fail();

Például ha nem negatív számokat szeretnénk bekérni, akkor:

error_flag = std::cin.fail() || input_number <= 0;

Ekkor érdemes a bekérésre vonatkozó kiíratást is módosítani, például:

std::cout << "Enter a positive number:\n";

A példaprogram:

//input validation example
#include <iostream>
#include <string>

int main() {
  long int input_number = 0;

  bool error_flag = false;
  do {
    std::cout << "Enter a positive number:\n";
    std::cin >> input_number;
    error_flag = std::cin.fail() || input_number <= 0;

    if (error_flag) {
      std::cout << "Invalid data!" << '\n';
      std::cin.clear();
      std::string temp;
      getline(std::cin,temp,'\n');
    }
  } while (error_flag);

  std::cout << "The input value: " << input_number << '\n';
}

Kapcsolódó tananyagrész:

adatbevitel-ellenőrzés while ciklussal

Aki tanult már más programozási nyelveket, annak a számára lehet, hogy furcsának tűnhet, hogy egy ciklus feltételeként utasításokat is megadhatunk. Ezt másképp úgy is szokták fogalmazni, hogy a C++ nyelv egy mellékhatásos nyelv, vagyis ha egy utasítást írunk a feltétel helyére, akkor amellett, hogy az utasítás végrehajtódik, az utasítás kiértékelődik logikai értékként. Az std::cin esetén ekkor pont az történik, hogyha bebillen a hibaflag, vagyis a felhasználó szám helyett betűket vagy egyéb karaktereket ad meg, akkor az false értékként lesz kiértékelve.

//input validation example
#include <iostream>
#include <limits>

int main() {
  int age = 0;
  while ((std::cout << "How old are you?\n") && !(std::cin >> age) || age < 0) {
    std::cout << "That's not a positive number.\n";
    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  }
  std::cout << "You are " << age << " years old\n";
  //...
}

Az std::cin.ignore(/*...*/); utasításról már volt részletesebben szó korábbi tananyagban, az input buffer ürítését végzi el ez az utasítás, mivel hiba esetén ez nem történik meg automatikusan, ezért ha nem tennénk meg, a következő bekérés is megkapná azt, amit a felhasználó begépelt.

input buffer teszt

Ezt akár le is tesztelhetjük, ha az első input után beillesztünk még egy input utasítást egy std::string változóba, amelynek az értékét aztán kiíratjuk. Úgy tudjuk tesztelni, ha először valamilyen rossz inputot adunk meg (pl. betű karaktereket). Amit megadtunk az első bekérésnél, azt bizony benne marad az input bufferben, és a második bekérés fel fogja dolgozni.

//input buffer test
#include <iostream>
#include <limits>

int main() {
  int age = 0;
  while ((std::cout << "How old are you?\n") && !(std::cin >> age)|| age < 0) {
    std::cout << "That's not a positive number.\n";
    std::cin.clear();
    //input buffer test
    std::string temp;
    std::cin >> temp;
    std::cout << "Input buffer: " << temp << '\n';
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  }
  std::cout << "You are " << age << " years old\n";
}

Függvény: pozitív szám bekérése

Az ellenőrzött bekérés kódját akár bele is tehetjük egy függvénybe, és számok bekéréséhez használhatjuk többször is azt a függvényt.

//input validation example
#include <iostream>
#include <limits>

int input_validate(std::string p_message) {
  int number = 0;
  while ((std::cout << p_message << '\n') && !(std::cin >> number)) {
    std::cout << "That's not a positive number.\n";
    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  }
  return number;
}

int main() {
  int age = input_validate("How old are you? ");
  std::cout << "You are " << age << " years old\n";
  int year = input_validate("In which year were you born? ");
  std::cout << "You were born in " << year << '\n';
}

A C++ Core Guidelines a do-while ciklusok mellőzését javasolja, mivel a C++ nyelvben while ciklussal is meg lehet oldani a do-while ciklussal megoldható feladatokat:

Függvény: alsó és felső határ bekérése random szám generáláshoz

Fontos, a while-os módszer esetén ha több bekérést használunk, azokat ||-al válasszuk el egymástól, például:

while ((std::cout << "Min:\n") && !(std::cin >> min) ||
(std::cout << "Max:\n") && !(std::cin >> max))

A példaprogram:

//input validation example
//random numbers
//min, max: user input
#include <iostream>
#include <limits>
#include <random>

int generate_rnd_number(const int& min, const int& max) {
  static std::mt19937 rnd_generator( std::random_device{}());
  std::uniform_int_distribution<int> int_dist(min,max);

  return int_dist(rnd_generator);
}

int input_validate_random() {
  int min = 0;
  int max = 0;

  while ((std::cout << "Min:\n") && !(std::cin >> min) ||
  (std::cout << "Max:\n") && !(std::cin >> max) || min > max) {
    std::cout << "Min shouldn't be greater than max\n";
    std::cin.clear()
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  }
  return generate_rnd_number(min, max);
}

int main() {
  int random_num = input_validate_random();

  std::cout << "The random value is: " << random_num << '\n';
}

Próbáljuk ki a példaprogramot úgy is, ha az alsó határnak nagyobb számot adunk meg mint a felső határnak.

do-while ciklus: egy adott kódrészlet újbóli lefuttatásának megkérdezése a felhasználótól

Előfordulhat, hogy valamilyen kódrészletet, akár az egész programot újra végre szeretnénk hajtani, de csak akkor, ha a felhasználó erre utasítást ad. Ezt is do-while ciklussal oldhatjuk meg.
Fontos, hogy a változót, amibe a választ kérjük be a felhasználótól, a cikluson kívül hozzuk létre, mivel a cikluson belül létrehozott változókra a ciklusfeltételben nem hivatkozhatunk.

Szemléltető kódrészlet:

char restart{};
do {
  //statements
  std::cout << "Would you like to start again? (Y/N)\n";
  std::cin >> restart;
} while ( restart == 'y' || restart == 'Y' );

Bár függvények definiálásáról eddig még nem szerepelt külön tananyagrész, de természetesen bonyolultabb programok esetén átláthatóbb a kód, ha nem minden utasítást a do-while ciklus blokkjába írunk, hanem egy külön függvénybe.

Szintén egy szemléltető kód:

#include <iostream>

void function_example() {
  //statements
}

int main() {
  char restart{};
  do {
    function_example();
    std::cout << "Would you like to start again? (Y/N)\n";
    std::cin >> restart;
  } while ( restart == 'y' || restart == 'Y' );
}

do-while ciklus példa: gondoltam egy számra

Ebben a példaprogramban egy 1 és 30 közötti random generált számot találhat ki a felhasználó. A szám kitalálására 5 próbálkozás van. A program minden próbálkozás esetén kiírja, hogy a felhasználó tippje kisebb-e, nagyobb-e mint a random generált szám, vagy esetleg hogy eltalálta a felhasználó.

A példában két do-while ciklus is található. A belső ciklus felelős egyetlen játék lebonyolításáért (5 tipp bekérése), a külső ciklus pedig a játék újrakezdéséért.

//do-while example: number guessing
#include <iostream>
#include <random>

int main() {
  std::cout << "This is a number guessing game.\n";
  std::cout << "Guess a number between 1 and 30.\n";
  std::cout << "You have 5 tries.\n";
  std::cout.put('\n');

  std::random_device rnd_device;
  std::mt19937 rnd_generator(rnd_device());
  std::uniform_int_distribution<int> int_dist(1,30);

  int guess{};
  char restart_game{};

  do {
    int random_number = int_dist(rnd_generator);
    int number_of_guesses = 1;
    do {
      std::cout << "Your " << number_of_guesses << ". guess: " << '\n';
      std::cin >> guess;

      if (guess < random_number) {
        std::cout << "The random number is greater than your guess" << '\n';
      } else if (guess > random_number) {
        std::cout << "The random number is smaller than your guess" << '\n';
      }
      number_of_guesses++;
    } while ( guess != random_number && number_of_guesses <= 5 );

    if ( guess == random_number ) {
      std::cout << "Congratulations! Your guess (" << guess << ") is equals to the random number." << '\n';
    } else {
      std::cout << "Neither of your 5 tries is equal to the random number: " << random_number << '\n';
    }
    std::cout.put('\n');
    std::cout << "Start a new game? (y/n)\n";
    std::cin >> restart_game;
  } while (restart_game != 'n' && restart_game != 'N');
}

Hasonlóan lehet megvalósítani a dobókockával végzett műveleteket is.

do-while ciklus: menürendszer parancssoros programokban

Amikor egy parancssoros programban különböző lehetőségek közül választhatunk, azt gyakran egy menürendszerrel valósítjuk meg. Ehhez is használhatunk do-while ciklust. Egy szemléltető példa:

//command line menu example
#include <iostream>

int main() {
  int selection{};
  do {
    std::cout << "Please make a selection: \n";
    std::cout << "1) Menu 1\n";
    std::cout << "2) Menu 2\n";
    std::cout << "3) Menu 3\n";
    std::cout << "4) Menu 4\n";
    std::cin >> selection;
  } while (selection != 1 && selection != 2 &&
  selection != 3 && selection != 4);

  std::cout << "You selected option #" << selection << "\n";

/*
  switch(selection) {
    case 1:
  //statements
    case 2:
  //statements
    case 3:
  //statements
    case 4:
  //statements
  }
*/
}

Előző tananyagrész: while ciklus, fájlkezelés
Következő tananyagrész: continue és break utasítás

A bejegyzés trackback címe:

https://itkezdoknek.blog.hu/api/trackback/id/tr7916688664

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.
süti beállítások módosítása