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

C++ programozás kezdőknek - várakozás enter billentyű megnyomásáig

[2021. május 03.] [ christo161 ]

Ebben a tananyagrészben arról lesz szó, hogyan tudjuk megoldani, hogy egy parancssoros program ablaka ne záruljon be azonnal, ahogy a program kiírta az utolsó eredményt/kimenetet, abban az esetben, ha a programot nem fejlesztői környezetből futtatjuk (a fejlesztői környezetekben jellemzően nem jelentkezik ez a probléma).
A szabványos megoldás mellett áttekintünk néhány nem platformfüggetlen megoldást is, amiket akkor lehet esetleg érdemes elolvasni, ha valaki az adott környezettel szeretne részletesebben foglalkozni.

Előző tananyagrész: megjegyzések (kommentek) a forráskódban
Következő tananyagrész: változók, konstansok, literálok

Ennek a tananyagrésznek a tartalma:

  • szabványos megoldás
  • a szabványos megoldás utasításairól
  • kerülendő példa

Nem szabványos (rendszerfüggő) megoldások:

  • getch függvény (MS-DOS)
  • _getch függvény (Windows)
  • system("pause"); utasítás (Windows)
  • getch függvény (Linux)
  • hibalehetőségek

Általában Windows operációs rendszeren jellemző, hogy egy parancssoros program (például a hello world példaprogram) futtatható fájlját megnyitjuk a grafikus felületi fájlkezelőben (Windows intézőben, vagy angolul Windows explorerben), de a parancssoros ablak a másodperc töredéke alatt be is zárul.

Szabványos megoldás

Ahhoz, hogy a parancssoros ablak az enter billentyű megnyomását követően záródjon csak be, a következőt tudjuk tenni:

A preprocesszor direktívák között szerepeljen a következő:

#include <limits>

A main függvény utolsó utasításai pedig a következők legyenek:

std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cin.get();

Példaprogram

//press enter to continue
#include <iostream>
#include <limits>

int main() {
  std::cout << "Hello World!\n";

  std::cout.put('\n');
  std::cout << "Press enter to exit...\n";
  std::cin.clear();
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  std::cin.get();
}

Ügyeljünk arra, hogy ezeket az utasításokat közvetlenül a main függvény return 0; utasítása elé helyezzük el, mert a return 0; után elhelyezett utasítások figyelmen kívül lesznek hagyva, azaz nem hajtódnak végre.

//press enter to continue
#include <iostream>
#include <limits>

int main() {
  std::cout << "Hello World!\n";

  std::cout.put('\n');
  std::cout << "Press enter to exit...\n";
  std::cin.clear();
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  std::cin.get();
  return 0;
}

itk_cpp_example_cin_get.png

Ha esetleg több return 0; utasítás is szerepel a main függvényben különböző elágazásokban, akkor mindegyik return 0; utasítás előtt szerepelnie kell ezeknek az utasításoknak ahhoz, hogy a program bármilyen lehetséges futása esetén ne záruljon be a parancssoros ablak. Ebben az esetben ezeket az utasításokat bele lehet pakolni egy függvénybe, és akkor elég csak meghívni a függvényt minden helyen, ahová ezeket az utasításokat szerettük volna írni.0

//press enter to continue
#include <iostream>
#include <limits>

inline void press_enter_to_continue() {
  std::cout.put('\n');
  std::cout << "Press enter to exit...\n";
  std::cin.clear();
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  std::cin.get();
}

int main() {
  //statements
  if (error) {
    press_enter_to_continue();
    return 0;
  }
  //statements
  press_enter_to_continue();
  return 0;
}

Mire valók ezek az utasítások?

Későbbi tananyagrészekben ezeket az utasításokat részletesebben is megismerjük, itt most csak nagyjából átnézzük, hogy mégis miért van rájuk szükség, hogy ne csak átmásolja valaki a kódjába, hanem meg is érthesse.

std::cin.get();

Valójában ez az az utasítás, ami az enter billentyű megnyomására várakozást megvalósítja, a többi utasítás csak egyes hibalehetőségeket előz meg.

Előfordulhat, hogy mások kódjában az std::cin.get(); utasítás helyett a C nyelvből megörökölt getchar(); utasítással találkozhatunk.

std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

Ha esetleg volt korábban egy olyan utasítás, ahol a felhasználótól kértünk be adatot (az itt látható példaprogramokban nyilván nincs ilyen), akkor a felhasználónak technikailag volt lehetősége sok karakterből álló szöveget megadni. Persze ha pont egy szöveg (string) jellegű értéket kérdeztünk be tőle, akkor ezzel nem is lenne gond, de ha például egyetlen karaktert kérdeztünk be a felhasználótól, és a felhasználó több karaktert írt be, akkor a további karakterek továbbadódnak a következő bekéréseknek, a jelenlegi esetben az std::cin.get(); utasításnak. Ezt hivatott kiküszöbölni ez az utasítás azzal, hogy törli az input buffer tartalmát, vagyis minden az előzőekben fel nem dolgozott adatot, amit a felhasználó írt be a parancssorba.
Ahhoz viszont, hogy lekérdezzük, hogy mekkora lehet az input buffer maximális mérete, szükségünk van a C++ standard library limits header fájlja tartalmának a beillesztéséhez a programunk forráskódjába, amit a #include <limits> preprocesszor direktíva hivatott megvalósítani.

std::cin.clear();

Visszaállítja alaphelyzetbe a bekérésekkel kapcsolatos hibaflageket. Például ha a programban valahol egy számot kértünk be a felhasználótól, de a felhasználó (véletlenül vagy szándékosan) nem szám karaktereket adott meg, akkor bebillen egy úgynevezett hibaflag, és onnantól kezdve az összes bekérés figyelmen kívül lesz hagyva, jelen esetben a program utolsó utasítása, a std::cin.get();
Az itt szereplő példaprogramokban ez a veszély nyilvánvalóan nem áll fenn.

Kerülendő példa

Sokan csinálják azt, hogy létrehoznak egy ideiglenes változót, legyen az akár egyetlen karakter tárolására alkalmas változó, és a program végén abba a változóba bekérnek valamilyen tetszőleges adatot. Például:

//bad example
#include <iostream>

int main() {
std::cout << "Hello World!\n";

std::cout.put('\n');
std::cout << "Please write some value and press enter to exit...\n";
char temp{};
std::cin >> temp;
}

Ennek csupán annyi a szépséghibája, hogy a felhasználónak mindenképp be kell írnia valamilyen karaktereket, még ha teljesen mindegy is, hogy mit, de nem elég önmagában csak az enter billentyűt megnyomni.

Nem szabványos (rendszerfüggő) megoldások

getch függvény

A mai napig találkozhatunk Turbo C++ fejlesztői környezetben készített forráskódokkal internetszerte. A Windows-t megelőző MS-DOS operációs rendszerben futtatható programokat készíthetünk benne. Bár ma már igencsak elavultnak tekinthető, végülis a programozási alapok megtanulására esetleg jó lehet. A Turbo C++ fejlesztői környezetben készített példaprogramok forráskódjában jó eséllyel szerepel az #include <conio.h> preprocesszor direktíva és a main függvény végén a getch függvényhívás.

//turbo c++ getch (non-standard)
#include <iostream>
#include <conio.h>

int main() {
  cout << "Hello World!\n";

  cout.put('\n');
  cout << "Press any key to exit...\n"
  getch();
  return 0;
}

itk_turbocpp_getch.png

Mivel ez a fejlesztői környezet és a hozzá tartozó fordító korábban készült el, mint ahogy a C++ nyelv szabványosítva lett, így a standard névteret nem kell (nem is lehet) jelölni benne (a using namespace std; utasítás sem működik), sőt, ekkoriban névterek és sok más dolog sem létezett a C++ nyelvben.

_getch függvény

Visual Studio fejlesztői környezetben szintén találkozhatunk a conio.h header fájllal, ám ebben az esetben a getch függvény helyett a _getch függvényt érdemes használni, mivel a getch elavult (angolul deprecated).

//visual studio getch (non-standard)
#include <iostream>
#include <conio.h>

int main() {
  std::cout << "Hello World!\n";

  std::cout.put('\n');
  std::cout << "Press any key to exit...\n";
  _getch();
  return 0;
}

Előfordulhat, hogy kapunk egy warningot, hogy a _getch függvény által visszaadott értéket nem használjuk fel.

itk_cpp_visual_studio_getch_return_value_ignored.png

Mivel jelen esetben a beolvasott karaktert nem is szeretnénk felhasználni, így valamilyen módon jelezhetjük a fordítónak, illetve a többi programozónak hogy nem is áll szándékünkban. Ezt például úgy tudjuk megtenni, ha a _getch(); utasítás helyett a következőt írjuk: static_cast<void>(_getch());
Akár komment formájában is jelezhetjük, hogy a visszatérési érték figyelmen kívül hagyása a cél, mert ez talán nem minden programozó számára nyilvánvaló.

itk_cpp_visual_studio_getch_return_value_ignore.png

Egyébként például így tudnánk felhasználni a visszaadott értéket:

char return_value = _getch();
std::cout << "the return value of _getch(): " << return_value << '\n';

A modern C++ szabványok szerint értelmezett forráskódokban a visszatérési érték konvertálása, vagyis a static_cast<void>(function_call()); kifejezés használatát kerülendőnek tekintik. Helyette például az std::ignore = function_call(); kifejezés használható, ha includeoljuk a C++ standard library tuple header fájlját. Érdemes ismerni a [[maybe_unused]] attribútumot is.

system("pause") utasítás

Windowsban használhatjuk a system("pause"); utasítást is. Például Visual Studioban. Szintén a return 0; utasítás előtt kell szerepelnie (ha van return 0; utasítás a main függvényben), viszont nem kell elé kiíratnunk valamilyen szöveget, mint például a "Press any key to exit...", mert a system("pause"); utasítás automatikusan kiírja, hogy "Press any key to continue..."

//visual studio system("pause"); (non-standard)
#include <iostream>
#include <cstdlib>

int main() {
  std::cout << "Hello World!\n";
  system("pause");
}

itk_cpp_visual_studio_system_pause.png

A program futtatása:

itk_cpp_visual_studio_system_pause_run.png

Akár a Hello World! szöveg kiíratása nélkül is letesztelhetjük, viszont az #include <cstdlib> preprocesszor direktívára mindenképp szükség van, mivel a system függvény a C++ standard library cstdlib header fájljában van deklarálva.
Bár annak ellenére, hogy a system függvény a C++ standard library része, ami azt jelenti, hogy szabványos, a system("pause"); utasítás csak Windowson működik, mivel ez egyenértékű azzal, hogy a Windowsos parancssorban (cmd-ben) futtatjuk a pause parancsot, a pause parancs pedig jó eséllyel nem működik másik operációs rendszereken.

itk_cpp_visual_studio_system_pause_minimal.png

getch függvény (ncurses.h)

Linuxon szokták ajánlani a curses.h vagy ncurses.h header fájlokat (a pdcurses is hasonló, csak Windowsra), mivel ezekben is van getch függvény, ami hasonlóan működik, mint Windowson a conio.h-ban lévő getch függvény.

Az ncurses.h jó eséllyel nincs telepítve alapból, ezért nekünk kell telepíteni, Ubuntuban például ezzel a terminál paranccsal:

sudo apt-get install ncurses*

itk_install_ncurses.png

Más Linux disztribúciókon nagyjából hasonlóan kell telepíteni, csak nem az apt csomagkezelőt kell használni, hanem egy másikat (pl. yum, dnf, pacman).

Viszont az ncurses.h segítségével kicsit más utasításokkal kell felépíteni egy parancssoros programot, mint azt a C++ standard library esetén megszokhattuk. Ki lehet próbálni, hogy mi történik, ha a couttal is kiíratunk valamilyen szöveget.

//ncurses example (non-standard)
//#include <iostream>
#include <ncurses.h>

int main() {
  initscr();
  //std::cout << "cout: Hello World\n";
  printw("printw: Hello World\n");

  addch('\n');
  printw("Press any key to exit...\n");
  getch();
  endwin();
  return 0;
}

itk_ncurses_example.png

A fordításhoz használjuk a következő parancsot:

g++ ./hello.cpp -lncurses -o ./hello

itk_ncurses_compile.png

A program futtatása:

itk_ncurses_example_running.png

Az ncurses.h segítségével egyébként olyasmi kinézetű parancssoros programokat készíthetünk, mint a midnight commander, vagy az MS-DOS-os megfelelője a norton commander.

További tananyag az ncurses.h használatáról:

system("pause"); utasítás megfelelője Linuxon

Mint ahogy Windowson, a system függvény Linuxon is csak egy a programunktól független terminál parancsot futtat le. Például ha a programunk forráskódjában az az utasítás szerepel, hogy system("echo $LANG"); az olyan mintha Linuxon a parancssorba beírnánk, hogy echo $LANG és megnyomnánk az enter billentyűt. Eszerint például a system("read REPLY"); utasítást használhatjuk enter billentyű megnyomásáig történő várakoztatáshoz.

//linux: system("read REPLY"); example (non-standard)
#include <iostream>
#include <cstdlib>

int main() {
  std::cout << "Hello World!\n";

  std::cout.put('\n');
  std::cout << "Press enter to exit...\n";
  system("read REPLY");
  return 0;
}

itk_linux_system_read_reply.png

A program futtatása:

itk_linux_system_read_reply_running.png

Hibalehetőségek

Visual Studioban jó eséllyel át kell váltani debug módról release módra ahhoz, hogy például a system("pause"); utasítás megfelelően működjön. Elvégre debug módban jó eséllyel fejlesztői környezetben futtatjuk a programot, a fejlesztői környezetben pedig nincs szükség system("pause"); és hasonló utasításokra ahhoz, hogy a parancssoros ablak ne záruljon be a program lefutása után.

Elképzelhető, hogy egy fejlesztői környezet a fentebbi példák egyikét automatikusan hozzáteszi egy parancssoros program futtatható fájljához, akkor is, ha nem a fejlesztői környezetben futtatjuk a programot.

Az is előfordulhat, hogy egy parancssoros program a fejlesztői környezet nélkül el sem indul (pl. hiányzó dll fájlokra hivatkozik), vagy csak akkor indul el, ha bizonyos beállításokat használunk. Például cygwin esetén van olyan fordító, amivel ha elkészítjük a futtatható fájlt, akkor azt a futtatható fájlt csak cygwinen belül lehet futtatni, erről szó volt a parancssoros fordítás tananyagrészben. Vagy a CodeBlocks újabbb verziói esetén bizonyos fordítási kapcsolókat be kell állítani.

Egyéb

Előző tananyagrész: megjegyzések (kommentek) a forráskódban
Következő tananyagrész: változók, konstansok, literálok

A bejegyzés trackback címe:

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

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