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

C++ programozás kezdőknek - változók, konstansok, literálok

[2016. szeptember 06.] [ christo161 ]

Ebben a fejezetben arról lesz szó, hogy hogyan tudunk adott típusú (szám, szöveg, karakter) értékeket tárolni, módosítani, illetve parancssorba kiíratni.

Előző rész: az első program
Következő rész: elágazások, logikai változók

Változók definiálása, kezdőértékek

Amikor egy változót definiálunk (létrehozunk), jelezzük a fordítóak, hogy a számítógép memóriájában szeretnénk lefoglalni valamennyi helyet, azért, hogy egy előre meghatározott típusú (szám, szöveg, karakter, logikai) értéket tárolhassunk, mely értéket a program futása során többször felhasználhatunk, megváltoztathatunk.
(A számítógép merevlemezén többnyire a későbbi használat céljából tárolunk adatokat, a feldolgozás alatt lévő adatokat a gyorsabb kezelhetőség érdekében a számítógép memóriájában tároljuk.)
Egy változó egyszerre csak egy érték tárolására alkalmas. Egyidejűleg több érték tárolásához például tömböket használhatunk, melyekről egy későbbi fejezetben lesz szó.

A változók értékét tetszőleges alkalommal megváltoztathatjuk, akár egy általunk (a program forráskódjában) megadott konkrét értékre (az ilyen értékeket literáloknak nevezzük), akár egy másik (hasonló típusú) változó értékére, akár egy olyan értékre, amit a felhasználó adhat majd meg a program futása közben (jelen feladatokban parancssorban történő begépeléssel), akár egy függvény által visszaadott értékre, akár egy fájlból beolvasott értékre, vagy valamilyen összetett kifejezés értékére. Az alábbiakban a fájlból beolvasást kivéve mindegyikre nézünk példát. A fájlból való beolvasásra egy későbbi fejezetben térünk ki.

Egy változó használatának a célja tulajdonképpen az, hogy egy adott értéket, eredményt eltároljunk (annak érdekében, hogy többször is hozzáférhessünk, többször hivatkozhassunk rá). Ha egy értékre, vagy eredményre a program futása során csupán egyszer van szükségünk, akkor nem muszáj eltárolni változóba, hanem azonnal fel lehet dolgozni, vagy ki lehet íratni. Természetesen az első példaprogramjainkban előfordulhat, hogy akkor is eltárolunk egy értéket egy változóba, ha csak egyszer használjuk a program futása során.

Egy változó definiálása például így néz ki:

int b;

Vagyis egyszerűen leírjuk a típusát, a nevét, és nem felejtünk el az utasítás végére pontosvesszőt tenni. Jelen példa esetén a típus int (egész szám), a név pedig b.

C++-ban nem változtathatjuk meg egy változó típusát. Vagyis ha definiáltunk egy egész szám típusú változót, abban nem tárolhatunk például szöveget.
Ez ugyan kényelmetlennek tűnhet más programnyelvekkel szemben, viszont C++-ban sokkal gyorsabban működő programokat írhatunk (ha jól használjuk a nyelvi elemeket).

Ügyeljünk arra, hogy C++-ban egy változót mindig definiálni kell a használata előtt, és egy adott blokkon belül nem lehet két változót azonos névvel definiálni.

A változók neve betűkből, számokból, és alsóvonalból ( _ ) állhat, nem kezdődhet számmal, nem tartalmazhat szóközt, valamint nem egyezhet meg a C++ nyelv kulcsszavaival (pl. if, while, double, class, typedef,..satöbbi).
A változók nevében számít a kis- és nagybetű (case sensitivity). Például az int b; és int B; utasítások különböző változót definiálnak. Egyes fordítók csak a nevek első 32 karakterét veszik figyelembe.
Ezek a megkötések nem csak a változók neveire vonatkoznak, hanem tömbök, függvények, osztályok, objektumok és egyéb dolgok neveire is. Az elnevezéseket összefoglaló néven azonosítóknak nevezzük a programozási nyelvekben.

Természetesen érdemes beszédes változóneveket használni, amik tükrözik, hogy milyen jellegű adatot szándékozunk egy adott változóban tárolni (pl. atlag, elemszam, vegosszeg, szobaszam). Ezekben az egyszerű példákban még nem így járunk el, mert ezek csak nagyon egyszerű, bemutató jellegű példák.

C++-ban ilyen típusú változókat használhatunk:

Egy változó típusának mérete azt jelenti, hogy mettől meddig tárolhatjuk benne az értékeket. Például egy 4 bájtos intben a legkisebb tárolható érték -2147483648, a legnagyobb pedig 2147483647. Az egyes típusok pontos mérete ezen fejezet későbbi részében lesz részletezve.

(Az alábbiakat így értelmezzük:
egy adott típus: ami ilyen érték tárolására alkalmas)

short int: kisebb egész szám
int: egész szám (pl. -1, 0, 128)
long int: nagyobb egész szám
long long int: nagyobb vagy mégnagyobb egész szám

Implementációfüggő (fordító- és számítógép-architektúrafüggő), hogy a long long int mérete bővebb-e mint a long inté, mivel a C++ szabvány csak azt köti meg, hogy legalább ugyanakkora legyen.

unsigned short int: kisebb nem negatív egész szám
unsigned int: nem negatív egész szám (pl. 0, 1024)
unsigned long int: nagyobb nem negatív egész szám
unsigned long long int: nagyobb vagy mégnagyobb nem negatív egész szám

float: tizedestört (pl. -0.013, 0.0, 124.78)
double: nagyobb pontosságú tizedestört
long double: mégnagyobb pontosságú tizedestört

Előfordulhat, hogy a float, double és long double típusokat más tananyagokban valós, racionális vagy lebegőpontos számoknak nevezik. Ebben a tananyagban az egyszerűség kedvéért tizedestörteknek nevezzük őket, noha nem csak tizedestörteket tartalmaznak, hanem például a végtelen és mínusz végtelen jelölésére alkalmas értéket, valamint a NaN hibajelző értéket (mely például a 0.0/0.0 osztás eredménye).
Tizedestörtek esetén ne felejtsük el, hogy tizedesvessző helyett tizedespontot kell használnunk, az angol jelölésnek megfelelően.

bool: logikai (true és false értékek)

A logikai változókat a következő fejezetben tárgyaljuk.

char: egy karakter (pl. 'a', '2', '%')

A char típusú változókban valójában egész számokat tárolunk, és a kiíratáskor az egyes számértéknek megfelelő karakter íródik ki. Erről jelen fejezet későbbi részében lesz szó bővebben. Ezt azért érdemes tudni, mert ennek köszönhetően aritmetikai műveletek is elvégezhetők char típusú változókkal. Például értelmes művelet, ha egy karakter értékét megnöveljük: 'A' + 32 //értéke 'a'
Így lehet például kisbetűsíteni, vagy nagybetűsíteni.

string: szöveg/karakterlánc (pl. "szoveg", "tobb szo", "tobb\nsor", "")

Az itt felsorolt típusok egyszerű típusok, kivéve a stringet. A string valójában osztály/objektum, de lényegében ugyanúgy kezelhető, mint a többi típus, és a nyelvben alapból szerepel (a C nyelvvel ellentétben). Ebben a tananyagban az egyszerűség kedvéért a string típusú objektumokat string típusú változónak fogjuk nevezni. Az osztályokról és az objektumokról csak későbbi fejezetben lesz szó.

wchar_t: egy karakter (bővebb karakterkódolást támogat, de a mérete implementációfüggő). Ebben a tananyagban nem fogjuk használni.

Az eddig felsorolt típusok mérete implementációfüggő (különböző rendszereken eltérő lehet). A C++11-es szabványban bevezettek fix méretű típusokat is.

Néhány C++11-es szabványban elérhető típus:

Ne felejtsük el, hogy ezeket a típusokat csak akkor használhatjuk, ha a fordítót a C++11-es szabvánnyal paraméterezzük. Ennek mikéntjére az első fejezetben tértünk ki.

int8_t: minden rendszerben 8 biten (1 bájton) ábrázolt egész szám
int16_t: ugyanaz 16 biten
int32_t: ugyanaz 32 biten
int64_t: ugyanaz 64 biten
uint8_t: fixen 8 bites nem negatív egész szám

char16_t: egy karakter (16 bitbe beleférő karakterkódolásokat támogat)
char32_t: ugyanaz 32 biten

A C++11-es típusokat ebben a tananyagban nem fogjuk használni.

Az itt felsorolt adattípusokból adatszerkezeteket (pl. tömb, láncolt lista, sor, halmaz, fa, verem, satöbbi, illetve egyedi adatszerkezeteket) is létrehozhatunk, melyekről csak későbbi fejezetekben lesz szó.

Inicializálásnak nevezzük, ha a változók definiálásakor megadjuk a kezdőértéküket is:

int b = 0;
float f = -2.17;
char c = 'y';
string s = "szoveg";

Egy utasításban akár több azonos típusú változót is definiálhatunk:

int a=-2, b, c=7;

Lehetőleg mindig adjunk kezdőértéket a változóinknak. Ha ezt nem tesszük meg, akkor is előfordulhat, hogy lesz valamilyen értékük, hiszen azon a memóriacímen, ami egy változó definiálásakor a változóhoz rendelődik, lehet, hogy már volt valamilyen érték tárolva a változó definiálása előtt.
(Úgy is elképzelhetjük, hogy a számítógép memóriájában 1-esek és 0-k sorozata áll, és ebből jelölünk ki egy kis részt egy változó számára. A változó típusa pedig valójában azt jelenti, hogy ezeket az 1-eseket és 0-kat hogyan értelmezzük (pl. az első számjegy az előjel, a következő valahány számjegy a kitevő, satöbbi). Nyilván egy adott memóriarészlet mást fog jelenteni, ha azt egy egész számként, vagy ha mondjuk egy karakterként értelmezzük.)

Kivételt képeznek a globális változók és a string típusú változók.

A globális változók (amelyeket a függvények blokkján kívül definiálunk, beleértve a main függvényt is) mindig 0-val lesznek inicializálva, ha nem adunk nekik kezdőértéket. A globális változókról csak később lesz szó bővebben.

Ha egy string típusú változó létrehozásakor nem adunk meg kezdőértéket, akkor üres string lesz az értéke (mivel a string összetett típus (valójában osztály), és a default konstruktorában meg van adva, hogy üres string legyen a kezdőértéke, ha nem adunk meg kezdőértéket). Tehát az alábbi két utasítás ugyanazt eredményezi:

string s;
string s = "";

Érdekességképpen megpróbálhatjuk kiíratni olyan egyszerű típusú, lokális változók értékét, amiknek nem adtunk értéket, viszont a későbbiek során (főleg bonyolultabb programok esetén) gondoskodjunk arról, hogy a változóink még az érdemi használatuk előtt kapjanak értéket.

/* Kerülendő, de működő kód: */
#include <iostream>

int main() {
	int a;
	std::cout << a << std::endl;
	return 0;
}
/* Itt most teszt jelleggel íratjuk ki az a változó értékét, de nem teszt jellegű kódban a biztonság kedvéért minden változónak adjunk kezdőértéket, mielőtt felhasználnánk valahol az értékét */

Kezdőértéknek egy adott típus értéktartományán belül tetszőleges értéket adhatunk, de célszerű lehet nullát vagy azzal egyenértékű értéket kezdőértéknek megadni:

int a = 0;
float b = 0.0;
char c = ' '; //vagy char c = '\0';
/* (Itt a két aposztróf között egy szóköz van) */

Ha char típusú változóknak szóköz helyett üres értéket szeretnénk értékül adni, akkor használjuk a '\0' karaktert.

/* Hibás kód */
/* (Itt a két aposztróf között nincs szóköz) */
char c = ''; //fordítási hiba
/* Ehelyett használjuk a char c = '\0'; utasítást */
/* Teszt */
char a = 'x';
char b = '\0';
char c = 'z';
cout << a << b << c << endl;
/* Ekkor xz íródik ki a parancssorba */

Értékadások és műveletek (a teljesség igénye nélkül)

Természetesen ugyanazokat a kifejezéseket használhatjuk, amikor egy változónak kezdőértéket adunk, illetve amikor később megváltoztatjuk az értékét.

Például így adhatunk értéket szám alapú (int, float, és hasonló típusú) változóknak:

b = -3;
a = b;
a = b = c;

c(4); //ugyanaz, mint c = 4;
/* A tananyag későbbi részeiben előfordulhat, hogy egyes dolgokat, csak így adhatunk értékül.
Pl. az a = nullptr kifejezés helyett az a(nullptr) kifejezés az elfogadott .*/

a = b = c esetén a és b változók értéke c változó értékével lesz egyenlő. Az értékadások jobbról balra lesznek kiértékelve, vagyis először b = c értékelődik ki, ezt követően a = b

a = b * -c; //ebben az utasításban a csillag karakter szorzásjel
a += b; //jelentése: a = a + b;

Az összeadáson kívül egyéb műveletek is rövidíthetők ily módon (pl: -=, *=, /=, %=), viszont ügyeljünk arra, hogy a kifejezést alkotó két műveleti jel közé ne tegyünk szóközt, különben a program nem fog lefordulni.

++c; //vagy c++; /*1-el növeljük a c változó értékét*/
--c; //vagy c--; /*1-el csökkenjük a c változó értékét*/
b = ++c; /*c változó értékét 1-el növeljük, és ezt értékül adjuk b változónak*/
b = c++; /*b változónak c meg nem növelt értékét adjuk értékül, majd 1-el növeljük c változó értékét*/

Egy változó értékének 1-el való megnövelését inkrementálásnak, csökkentését pedig dekrementálásnak nevezzük (magyarul léptetésnek szokták fordítani).
Fontos: a c++ vagy ++c kifejezések jelentése nem c + 1, hanem c = c + 1.

/* Teszt */
int a, b;
int i = 1, j = 1;

a = i++; //a értéke 1 lesz, majd i értéke 2 lesz
b = ++j; //j értéke 2 lesz, majd b értéke 2 lesz

/* kiíratjuk az a és b változók értékét (szóközzel elválasztva): */
std::cout << a << " " << b;

Így könnyű megjegyezni: ha a léptető operátor a változónév előtt szerepel, akkor az értékének az összetett kifejezésben való felhasználása előtt meg lesz növelve az értéke, ha pedig utána szerepel, akkor csak azt követően.

Amikor char (karakter) típusú változónak adunk értéket (a kezdőértéket is beleértve), akkor idézőjelek helyett aposztrófokat használunk:

c = '3';
c = '\n';

Ha aposztrófok helyett mégis idézőjelet használunk karakter típusú változó értékének megadásakor, a program nem fog lefordulni.

Ha esetleg pont egy aposztrófot szeretnénk értékül adni egy karakter típusú változónak, akkor azt escape karakterrel tehetjük meg:

c = '\'';

A '\'' kifejezés végén nem egy idézőjel, hanem két aposztróf szerepel.

Idézőjelek között adjuk meg azonban a string (karakterlánc) típusú változók értékét. Az értékként megadható szövegben speciális karakterek is szerepelhetnek.

s = "tobb szobol es\ntobb sorbol allo szoveg";

Két string típusú változó értékét összefűzhetjük (ezt a műveletet konkatenációnak hívjuk):

s1 = "egyik szoveg";
s2 = "masik szoveg";
s3 = s1 + " " + s2;

String típusú változók bizonyos szakaszait is felhasználhatjuk:

s4 = s1.substr(0,4) + ", " + s2.substr(0,4);

Például az s1.substr(0,4) kifejezésben a substr tagfüggvény visszaadja az s1-ben tárolt szöveg első karakterétől az ötödik karakteréig tartó részét (értelemszerűen a 0 paraméter jelenti az első karaktert, a 4 pedig az ötödiket). Így ezen kifejezés értéke jelen példa esetén "egyik" lesz. Ennek alapján könnyen kikövetkeztethető, hogy az s4 változó értékéül adott kifejezés értéke "egyik, masik" lesz.

Ha csak egyetlen karaktert szeretnénk felhasználni egy string értékéből, akkor például a valtozonev[0] kifejezést használhatjuk. Itt a 0 szintén a legelső karaktert jelenti, és például a 12 a tizenharmadikat.

string s5 = "tetszoleges";
cout << "s5 elso karaktere: " << s5[0] << endl;
s5[1] = '3';
s5[s5.length()-1] = '5';
cout << "s5 modositott erteke: " + s5 << endl;

Egy string értékébe be is szúrhatunk valamilyen szöveg típusú értéket, például:

string s6 = "aaa";
s6.insert( 2, "bb"); //ekkor s6 erteke erre modosul: aabba

Adott szövegrész törlésével is egybeköthetjük a beszúrást (felülírás), ekkor meg kell adni, hogy mettől meddig töröljük az aktuális érték egy részletét. A 0 szintén az első karaktert jelenti.

string s7 = "szoveg";
s7.replace( 1, 4, "qwert" ); //s7 erteke ezt kovetoen: sqwertg

Hasonlóan működik az erase tagfüggvény is. Értelemszerűen csak két paramétert vár, hogy mettől meddig töröljük az adott string típusú változó értékét.

A string típusú változók méretét a length() vagy size() tagfüggvénnyel adhatjuk meg.
Például a cout << "s4 karaktereinek szama: " << s4.size() << endl; utasítással kiíratjuk az s4 változóban tárolt érték karaktereinek számát.
Illetve a length() vagy size() tagfüggvény által visszaadott értéket valamilyen szám típusú változónak értékül is adhatjuk. Például:

int s4_merete = s4.size();

A string típusú változók további tagfüggvényei, és a használatukat bemutató példák például ezen az oldalon találhatóak.

A string típusú változók használatához szükség lehet a program elején elhelyezett #include <string> utasításra. Bizonyos fordítók használata esetén előfordulhat, hogy a program enélkül az utasítás nélkül is lefordul, és ugyanúgy működik, mintha az utasítás szerepelne a program elején. Ki lehet próbálni, hogy mi történik, ha lehagyjuk, de mindenesetre a végleges programjainkban biztonság kedvéért érdemes szerepelnie (pl. előfordulhat, hogy odaadjuk valakinek a forráskódot, aki más fordítóval készíti el a futtatható állományt).

Függvényhívások eredményeinek értékül adása változóknak

Noha függvények definiálásával az első néhány fejezetben még nem foglalkozunk, de a példaprogramjainkban használni fogunk néhány függvényt a C++ Standard Libraryből.
Például ha a programunk elején elhelyezzük az #include <cmath> utasítást, akkor használhatjuk a C++ Standard Library cmath nevű header fájljában deklarált függvényeket, például az abs() függvényt, amely eredményként a paraméterben átadott érték abszolút értékét adja. Egy függvény által visszaadott értéket a függvény visszatérési értékének nevezzük. (Persze előfordulhat, hogy egy függvény a visszatérési értéken kívül egyéb dolgokat is változtat a program állapotán (ezt hívjuk egy függvény mellékhatásának), illetve az is lehet, hogy egy függvénynek nincs visszatérési értéke).

Példák:

float a = abs( -5.03 ); //a változó értéke 5.03 lesz
a = abs( b ); //b valamilyen szám típusú változó
int c = abs( -3.14 ); //c változó értéke 3 lesz

Egy függvény használatát a függvény meghívásának nevezzük, a függvénynek átadott értékeket pedig a függvény aktuális paramétereinek. Ha több aktuális paraméter van, azokat egymástól vesszővel válasszuk el, és a sorrendjükre ügyeljünk.

Egy függvényhívás így néz ki:
függvénynév( aktuális paraméterek )

Egy függvény használatához nyilvánvalóan ismernünk kell a függvénynek átadható értékek (melyeket formális paramétereknek nevezünk) számát, típusát és pontos sorrendjét, és a függvény visszatérési értékének a típusát. Pontosan ezek az információk olvashatók ki egy függvény deklarációjából.
A C++ Standard Library header fájljaiban szereplő függvények deklarációinak könnyen utánanézhetünk például a cplusplus.com oldalon. Ezen az oldalunk kiválaszthatjuk mondjuk a cmath header fájlt, azon belül pedig az abs függvényt. A megnyíló oldalon az abs függvény működéséről olvashatunk, az első néhány sorban pedig az abs függvény deklarációit láthatjuk:

float abs (float x);
double abs (double x);
long double abs (long double x);

Egy függvénydeklarációt így kell értelmezni:

visszateresi_tipus fuggvenynev (formalis parameterek);

Az aktuális paraméter típusától függ, hogy ezek közül melyik hívódik meg. Ennél az egyszerű függvénynél ez nem bonyolult, például ha egy double típusú értéket adunk át a függvénynek, akkor double típusú értéket is kapunk vissza. A függvény túlterhelésének nevezzük, ha egy függvénynek több deklarációja van (tehát egyetlen függvénynévhez több paraméterlista tartozik).

Az, hogy egy függvény pontosan mit és hogyan csinál, a függvény definíciójában van. Függvények definiálásával csak későbbi fejezetben foglalkozunk.

Változók értékeinek kiíratása (parancssorba)

Változók értékeit például cout-tal írathatjuk ki a parancssorba. Bármilyen értéket kiírathatunk, amit egy változóban tárolni tudunk, de bizonyos speciális karaktereknek parancssorban nem biztos, hogy értelmezve lesz a jelentésük (pl. függőleges tabulátor). Olyan esettel is találkozunk majd, hogy nem az az érték íródik ki, amit a programkódban látunk (pl. ha kiíratjuk egy logikai változó értékét, akkor nem true vagy false íródik ki, hanem jó eséllyel 1 vagy 0).
Ha valamilyen értéket csak egyszer használunk a programunkban, akkor természetesen megfontolható, hogy eltároljuk-e egy változóba, vagy rögtön kiírassuk/feldolgozzuk.

cout << "b valtozo erteke: " << b << ", b valtozo abszoluterteke: " << abs(b) << endl;

Természetesen ha nem csak gyakorlásképp írunk programokat, hanem a programunkat valóban használni fogja rajtunk kívül valaki, akkor nem szükséges az egyes változók pontos neveit közölni a felhasználóval.

Változók értékeinek bekérése a felhasználótól (parancssorban)

Ha azt szeretnénk, hogy a program a felhasználótól kérje be egy adott változó értékét, akkor például a cin utasítást használhatjuk:

cin >> b;

Érdemes felhívni a felhasználó figyelmét arra, hogy éppen milyen típusú érték megadását várja el a program, így minden bekérés előtt cout segítségével kiírathatunk egy erre vonatkozó szöveget.
Amikor majd valaki futtatja a programot, és a program eljut egy bekérést tartalmazó utasításig, akkor az illető egy, a lentebbi képen láthatóhoz hasonló parancssoros ablakban írhatja be az értékeket, és szokás szerint az enter billentyű megnyomásával fejezheti be egy adott érték beírását. Természetesen ekkor nem kell idézőjelek vagy aposztrófok közé tenni a begépelt szöveget.

Egyszerre több változó értékét is bekérhetjük egy utasításban. A program futtatásakor ezt több egymás utáni bekérésnek fogjuk érzékelni. Ennek a nyilvánvaló hátránya, hogy a bekérések közé nem tudunk egyéb utasításokat beilleszteni (például kiírásokat arra vonatkozóan, hogy milyen érték megadását várja el a program).

cin >> a >> b;

Literálok

Literálnak hívjuk azokat az értékeket, amelyeket a programkód szövege tartalmaz (vagyis amiket mi, a programozók írunk le a programkódba).
Literál például a cout << "pi erteke: " << 3.14; utasításban a "pi erteke: ", és a 3.14, illetve az int b3 = -1; utasításban a -1 szintén egy literál, hiszen abban a pillanatban, amikor ez az utasítás végrehajtásra kerül, a -1 még nem a b3 nevű változó értéke.

Literálok típusa

Egy egész szám típusú literál int típusú, ha belefér az int típus tartományába, vagyis ha -2147483648 és 2147483647 értékek között van, viszont ha ettől a tartománytól kisebb vagy nagyobb, akkor long int lesz a típusa.
Egy int típus tartományán belül lévő literált long int típusúvá tehetünk, ha mögéírunk egy L betűt (pl. -2L, 128L).
Példák:
128: int típusú
128u: unsigned int típusú
2147483647: int típusú
2147483648: long int típusú
100000000000: long int típusú
1L: long int típusú
128uL: unsigned long int típusú

Egy tizedestört literál alapvetően double típusú, f postfix-szel csinálhatunk belőle float típusút, és L postfix-szel pedig long double-t.
1.2: double típusú
-3.6f: float típusú
128.127L: long double típusú

Ne felejtsük el, hogy egész számok is felírhatók tizedestört alakban, például:
1.0: double típusú
0.0f: float típusú

Ennek ismerete hasznos lehet például egész számok osztásánál. Például az 5/2 kifejezés maradékos osztás, melynek eredménye 2 (a maradék pedig az 5%2 kifejezéssel kapható meg, amely 1), az 5.0/2, 5/2.0 vagy 5.0/2.0 kifejezések viszont valós osztások, melyek eredménye 2.5.

float f = 2 / 3; //f értéke 0
float g = 2.0 / 3; //g értéke 0.666667

Konstansok (állandók)

Ha olyan értékeket szeretnénk tárolni, melyeket nem szeretnénk a program futása során megváltoztatni, akkor erre a célra a konstansokat érdemes használni.
Konstansokat hasonlóan definiálhatunk mint változókat.

const int b2 = 123;
const char c = 'a';

Ne felejtsünk el (kezdő)értéket adni nekik, különben a program nem fog lefordulni.

/* Hibás kód: */
const float d; //kezdőérték megadásának hiánya fordítási hibát okoz

Ebben a tananyagban a const típusminősítővel definiált változókat nevezzük konstansnak, más tananyagokban, tanfolyamokon előfordulhat, hogy a változóban el nem tárolt értékeket (például literálokat, és kifejezések eredményeit (pl. 2 + 2, a - 5)) is konstansnak nevezik.

Fordítási hibát okoz, ha literálok vagy konstansok egyenlőségjel vagy += (és hasonló műveleti jelek) bal oldalán vagy ++ és -- operátorok operandusaként szerepelnek.

A C programozási nyelvben nem voltak konstansok, később a C is átvette őket a C++-ból. Ma is találkozhatunk olyan forráskóddal, ahol preprocesszor makrókat használtak konstansok helyett. Lehetőség szerint konstansok definiálásához a const típusminősítőt használjuk preprocesszor makrók helyett, mert ez biztonságosabb.

Sztringliterálok és karaktertömbök típusa (kiegészítő információ)

A C programozási nyelvben nincs string típus (mivel osztályok/objektumok sincsenek). Helyette char típusú tömböket lehet több karakterből álló szövegek tárolásához használni (a tömbökről egy későbbi fejezetben lesz szó).
A C++ nyelvben szövegek tárolásához használhatunk string típust, de C nyelvből átvett örökség, hogy a sztringliterálok nem string típusúak, hanem const char[karakterszám+1], vagyis konstans karaktertömb típusúak, melyek mérete az adott szöveg karaktereinek számánál eggyel nagyobb.
Például a cout << "pi erteke: " << 3.14; utasításban a "pi ertreke: " sztringliterál típusa: const char[13], vagyis egy 13 elemű char típusú tömb. Azért 13 elemű (eggyel több, mint ahány karakterből áll), mert a végén szerepel egy '\0' szöveglezáró karakter is.
Érdemes lehet tudni, hogy a cstring header fájlban deklarált strlen függvény egy sztringliterál karaktereinek a számát adja vissza, viszont a sizeof függvény hozzászámolja a '\0' szöveglezáró karaktert is. Ezért pl. a "Hello World!" sztringliterál méretét az strlen függvény 12-nek adja meg, a sizeof függvény pedig 13-nak:

//strlen() vs sizeof()
#include <iostream> //std::cout
#include <cstring> //std::strlen()

int main() {
	std::cout << std::strlen("Hello World!") << std::endl; //12
	std::cout << sizeof("Hello World!") << std::endl; //13
	return 0;
}

A string s1 = "szoveg"; utasításban a "szoveg" szintén egy sztringliterál (const char[7] típusú) , mely az utasítást végrehajtását követően stringgé konvertálódik, és az s1 változó értéke lesz.

Előfordulhat, hogy olyan C++ kóddal találkozunk, melyekben string típus helyett karaktertömböket használtak a kód szerzői szövegek tárolásához is.
A karaktertömböket szokták C stílusú sztringeknek is nevezni. Előfordulhat, hogy bizonyos függvények nem fogadják el a string típusú paramétereket, csak a karaktertömböket. Ekkor át kell őket konvertálnunk karaktertömbbé. Például az str nevű string típusú változót a str.c_str() tagfüggvénnyel konvertálhatjuk át karaktertömbbé.

A string típusról érdemes tudni, hogy valójában egy osztály/objektum (a tananyagban az egyszerűség kedvéért változónak nevezzük), és valójában egy char típusú tömböt tartalmaz, de a mérete automatikusan kezelt. Ha egy string típusú változó értékét egy hosszabb szövegre módosítjuk, akkor a szükséges memóriaterület lefoglalása automatizálva van. Ezzel szemben ha karaktertömbökkel használunk, erről magunknak kell gondoskodni. A tananyagban szereplő példákban nem használunk karaktertömböket szövegek tárolásához, csak említést tettünk a létezésükről, ha esetleg az olvasó találkozik velük.

Vannak olyan tananyagok, ahol a string típust std::string-ként említik, hogy senki ne keverje össze a karaktertömbökkel, hiszen a sztring kifejezés csupán több karakterből álló értékeket jelent, de annak a pontos megvalósítását nem határozza meg.

Példaprogram

Íme egy példaprogram, amiben definiálunk néhány különböző típusú változót, ezt követően a felhasználótól bekérjük az értékeiket, majd a beolvasott értékeket kiíratjuk.
Nem biztos, hogy egy beolvasott értrék egyezni fog azzal, amit a felhasználó megad, mivel például az adott bekéréskor nem értelmezett értéket is megadhat a felhasználó (pl. szám típusú érték bekérésekor betűket vagy túl nagy számot), ekkor alapbeállítás szerint az összes további bekérés kihagyásra kerül. Az ékezetes karakterekkel is lehet gond, vagy például string típusú változók cin-nel és >> operátorral történő bekérésekor csak az első szóközig lesz beolvasva a felhasználó által megadott érték (kivéve, ha a megadott érték elején szerepel szóköz). Ezeket az eseteket ebben a példaprogramban nem kezeljük.

//2. peldaprogram forraskodja
#include <iostream>
#include <string>

using namespace std;

int main() {
	/* Egesz szam tipusu valtozok definialasa: */
	short int a;
	int b;
	long int c;
	/* Egesz szam tipusu konstans definialasa: */
	const int b2 = -123;
	/* Tizedestort tipusu valtozok definialasa */
	float f;
	double g;
	long double h;
	/* Karakter es string tipusu valtozo definialasa */
	char k; 
	string s;

	cout << "A program most bekeri a kovetkezo ertekeket:\n";
	cout << "Egesz szam (-32768-tol 32767-ig):\n";
	cin >> a;
	cout << "Egesz szam (-2147483648-tol 2147483647-ig):\n";
	cin >> b;
	cout << "Egesz szam (-9223372036854775808-tol 9223372036854775807-ig):\n";
	cin >> c;
	cout << "Tizedestort (6 szamjegy pontossag):\n";
	cin >> f;
	cout << "Tizedestort (15 szamjegy pontossag):\n";
	cin >> g;
	cout << "Tizedestort (18 szamjegy pontossag):\n";
	cin >> h;
	cout << "Egy karakter\n";
	cin >> k;
	cout << "Szoveg/karakterlanc (elso szokozig)\n";
	cin >> s;

	cout << "A beolvasott ertekek:" << '\n';
	cout << "Egesz szamok:\n";
	cout << a << " " << b << " " << c << '\n';
	cout << "Tizedestortek:\n";
	cout << f << " " << g << " " << h << '\n';
	cout << "Karakter:\n";
	cout << k << '\n';
	cout << "Szoveg/karakterlanc:\n";
	cout << s << '\n';
	return 0;
}

A sztringliterálokban szereplő értéktartományok különböző rendszereken eltérhetnek. Például nem biztos, hogy egy int típusú változó egy másik számítógépet, vagy másik fordítót használva -2147483648 és 2147483647 között veheti fel az értékeket. Persze meg lehet oldani, hogy a program azt az értéket írja ki, ami azon a rendszeren érvényes, ahol épp fut a program (erre lentebb nézünk megoldást), viszont egyrészt ez kicsit elbonyolítaná a fenti forráskódot, másrészt pedig sajnos a bejegyzés karakterlimitjére is tekintettel kell lennem.

A program futtatásának egy lehetséges kimenete:

codeblocks_cin_example.jpg

A kép alján a Build logban észrevehetjük, hogy a CodeBlocks figyelmeztet minket arra, hogy nem használjuk a b2 egész szám típusú konstanst (warning: unused variable 'b2'). A program ettől még lefordul, ez csak egy figyelmeztető üzenet (warning), de érdemes az ilyen típusú üzenetek okát is megvizsgálni, és lehetőleg orvosolni a problémát (pl. ki lehet kommentelni a b2 egész szám konstans létrehozására vonatkozó utasítást).

Lokális vs globális változók (kiegészítő információ)

Vegyük észre, hogy az eddigi változóinkat a main függvény blokkján belül definiáltuk. Lokális változóknak hívjuk azokat a változókat, amiket egy függvényen belül definiálunk (legyen az akár a main függvény, vagy egyéb általunk definiált függvény). Megtehettük volna, hogy a main függvényen kívül definiáljuk a változókat (ezeket pedig globális változóknak nevezzük).
A különbség közöttük csupán az, hogy a globális változókhoz minden függvény hozzáfér, a lokális változókhoz viszont csak akkor fér hozzá egy függvény, ha paraméterként átadjuk neki, amikor meghívjuk az adott függvényt. Ennek a különbségnek ugyan jelenleg még nincs jelentősége, hiszen a main függvény a programunk egyetlen függvénye, de érdemes már most megjegyezni, hogy az átlátgatóság és a biztonság szempontjából jobb ha nem használunk globális változókat, hanem inkább lokális változókat adogatunk át a használni kívánt függvényeknek.

#include <iostream>

int a; //ez egy globális változó (kezdőértéke 0)
int b = 2; //ez egy globális változó (kezdőértéke 2)

/*Itt tetszőleges függvénydefiníciók szerepelhetnek*/

int main() {

	int b=10,c=20; //ezek lokális változók
	cout << b << endl; //ekkor 10 íródik ki
	/* A :: operátorral érünk el egy globális változót egy olyan blokkban, ahol van egy azonos nevű lokális változónk*/
	cout << ::b << endl; //ekkor pedig 2
	return 0;
}

Változók definiálása vs deklarálása

Ebben a tananyagban egy változó definiálása alatt azt értjük, ha memóriaterületet foglalunk le a számára, például:

int a;

Deklarálásnak nevezzük, ha csak jelezzük a fordítónak, hogy egy változó a forráskód későbbi részében (akár egy másik fájlban) van definiálva, de ezzel az utasítással nem foglalunk le memóriaterületet az adott változónak. Például:

extern int a;

Ez utóbbi utasítás csak globális változókra vonatkozhat és általában header fájlokban szokott szerepelni (melyeket majd a több fájlból álló forráskódok esetén fogunk létrehozni).

Egyszerű típusú változók méretei, tartományai

Egyszerű típusoknak nevezzük azokat a típusokat, amik nem tartalmaznak további adattípusokat. Később lesz szó bővebben az összetett típusokról (struct és class adatszerkezetek segítségével definiált típusok, például a string típus). Az egyszerű típusok C++-ban a következők: bool, char, wchar_t, int, float (továbbá az int és float különböző méretű változatai, pl. short int vagy double).

A C++ szabvány nem határozza meg pontosan az egyszerű típusok méretét, így azok fordítótól és hardverkörnyezettől függően eltérőek lehetnek különböző rendszereken (másképpen fogalmazva implementációfüggő a méretük). A rendszerünkön érvényes méreteket, tartományokat a sizeof operátorral, illetve a C++ Standard Library limits header fájljában deklarált osztálysablonok tagfüggvényeivel és adattagjaival kérdezhetjük le.

Mindenesetre abban biztosak lehetünk, hogy ezek a megkötések minden rendszeren érvényesek C++-ban. (A <= jel jelentése: kisebb vagy egyenlő, legfeljebb akkora, mint...).

  • 1 == sizeof(char) <= sizeof(short int) <= sizeof(int) <= sizeof(long int)
  • 1 <= sizeof(bool) <= sizeof(long int)
  • sizeof(char) <= sizeof(wchar_t) <= sizeof(long int) <= sizeof(long long int)
  • sizeof(float) <= sizeof(double) <= sizeof(long double)
  • sizeof(int) == sizeof(signed int) == sizeof(unsigned int) és ugyanez short int, long int, valamint long long int esetén is érvényes

Fontos megjegyezni, hogy az 1 == sizeof(char) kifejezésben az 1 nem feltétlenül 1 bájtot jelent, hanem egy egységet, ami bizonyos rendszereken lehet például 2 vagy 4 bájt. Mindenesetre az egy egység ebben a kifejezésben az adott rendszeren a számítógép memóriájában lefoglalható legkisebb értéket jelenti.
Továbbá elő szokott fordulni, hogy egy adott rendszeren például a long int és a long long int mérete megegyező, figyeljük meg, hogy a fenti megkötésekben csak az van előírva, hogy a long long int mérete legalább akkora legyen, mint a long int-é, nem pedig az, hogy mindenképp nagyobb.

A következő utasításokat használhatjuk, ha le akarjuk kérdezni az egyes típusok méreteit:

std::cout << sizeof(short int) << std::endl;

Az én gépemen ezen utasítás végrehajtásakor 2-t ír ki a program. Ebből az következik, hogy az általam használt számítógépen, az általam használt fordító 2 bájtot foglal le a short int típusú változóknak a számítógép memóriájában.

A következő utasítások csak akkor működnek, ha a programkód elején szerepel az #include <limits> utasítás.

std::cout << std::numeric_limits<short int>::digits << std::endl;

Az én gépemen ennél az utasításnál 15-öt ír ki a program. A 2-ből úgy lesz 15, hogy 1 bájt = 8 bit, 2 * 8 = 16, és mivel a short int alapvetően pozitív és negatív számok tárolására is alkalmas, így a változó számára lefoglalt 16 bitnyi memóriaterületből 1 bit az előjel értékének tárolására van lefoglalva (16 - 1 = 15 ).

Ez az utasítás unsigned short int esetén már 16-ot írat ki, mert az unsigned short int típus nem tartalmaz negatív számokat, így nincs szükség az előjel értékének tárolására sem.

std::cout << std::numeric_limits<unsigned short int>::digits;

Egy adott típusnak megadható legalacsonyabb és legmagasabb értéket a következő utasításokkal írathatjuk ki. (Akár a 2. példaprogram értékeit is kicserélhetjük ezekre a kifejezésekre).

std::cout << std::numeric_limits<unsigned short int>::min();
std::cout << std::numeric_limits<unsigned short int>::max();

Ámde a min() tagvüggvény float, double és long double típusok esetén az adott típusnak megadható 0-hoz legközelebb lévő pozitív értéket írja ki. A legkisebb megadható negatív értéket a lowest() tagfügvénnyel írathatjuk ki, viszont ez csak C++11-es szabványtól érthető el.

std::cout << std::numeric_limits<unsigned short int>::lowest();

Ezeket az értékeket nem csak kíváncsiságból érdemes lekérdezni, hanem akkor is hasznosak lehetnek, ha olyan programot szeretnénk írni, amit különböző rendszereken szeretnénk szeretnénk fordítani, és számít az egyes változók értéktartománya közötti különbség (portábilis kódot szeretnénk írni).

Kiegészítő információ: a numeric_limits egyébként egy sablonosztály (típussal paraméterezhető osztály). A digits ennek az osztálynak egy statikus adattagja, a min() pedig egy statikus tagfüggvénye. A statikus adattagokat és tagfüggvényeket . helyett :: operátorral jelöljük. Ezt a témát szintén egy későbbi fejezetben fejtjük ki.

Sajnos a karakterlimit miatt nem tudom ide beilleszteni annak a programnak a forráskódját, amely minden típus méretét kiírja, de ezen a képernyőképen nagyjából láthatjuk, hogy ez hogyan oldható meg:

cpp_sizeof_minmax.png

A könnyebb áttekinthetőség érdekében egy táblázatot is készítettem ezekről az értékekről, viszont sokadszorra megemlítem, hogy ezek az értékek különböző rendszereken eltérőek lehetnek, így a táblázatban szereplő értékeket csak egy példának tekintsük.
A táblázat elkészítéséhez ezek a jellemzők voltak érvényesek: 64 bites processzor (Intel i5), CodeBlocks (16.01) fejlesztői környezet, 4.6.3 verziószámú g++, Ubuntu 12.04 operációs rendszer.

cpp_fundamental_variables.png

Néhány megjegyzés a táblázathoz:

  • A string nem szerepel a táblázatban, mert nem egyszerű típus, hanem osztály/objektum.
  • Csak a char és az int típusoknak van signed (előjeles) és unsigned (előjel nélküli) változata, a float, double és long double típusoknak nincs.
  • Azért létezik signed és unsigned char, mert a számítógépek hőskorában a különböző rendszereken a signed chart, vagy az unsigned chart gondolták optimálisabbnak, de mindkettő megmaradt a nyelvben, hogy azok a programkódok is használhatók legyenek, amikben alkalmazták őket. A legtöbb fordító a char típust signed char-ként definiálja. (A fentebb említett sablonosztályokkal lekérdezhetjük, hogy a fordítónk melyiket használja).
  • A táblázatban szereplő összes típushoz használhatók a következő típusminősítők: const, extern, volatile. Ezeket egy adott változó definiálásakor van lehetőségünk használni (pl. const int elemszam = 10;).
    Az extern és a volitale típusminősítővel csak később foglalkozunk.
  • Az int és a signed int ugyanazt takarja. Ennek alapján természetesen a short int és a signed short int is ugyanaz, illetve a long int és a signed long int is.
  • Short int helyett írhatunk csak simán short-ot, valamint long int helyett long-ot. Továbbá például unsigned short int helyett unsigned short-ot.

Ha unsigned típusokat használunk, ne felejtsük el, hogy lehetséges hibaforrás ha egy műveletben használjuk őket signed típusokkal, mert ekkor átkonvertálódnak signedre, és így értékvesztés történhet.

Túlcsordulásnak (vagy körbefordulásnak) nevezzük, ha egy változó (nem bekéréssel, hanem értékadással) olyan értéket kap, ami túlhaladja a típusának megfelelő tartományt. Például kipróbálhatjuk, mi történik, ha egy int típusú változónak értékül adjuk az int típusban értelmezett legnagyobb értéket (nálam ez 2147483647), majd a változó értékét megnöveljük eggyel. Az eredmény -2147483648 lesz, vagyis a legkisebb érték az int típus tartományában.

Implicit és explicit típuskonverziók

Implicit típuskonverziónak nevezzük az automatikusan történő konvertálásokat. Például ha egy int típusú változónak bekérjük az értékét, a felhasználó viszont tizedestörtet ad meg, akkor a megadott szám egész számmá konvertálódik, és az átkonvertált értéket kapja meg az adott int típusú változó. Hasonló a helyzet ha egy int típusú változónak mondjuk egy double típusú literált, vagy egy double típusú változó értékét adjuk értékül.
Akkor is implicit konverzió történik, ha műveletet végzünk egy int és egy double típusú literállal: 5 / 2.0, ekkor az int típusú literál double típusúvá konvertálódik, és az eredmény is double típusú lesz.

Explicit típuskonverziónak nevezzük amikor a programozó ad utasítást a konvertálásra.
Például ebben az esetben a 2.5 double típusú literál intté konvertálódik (ekkor természetesen veszít a pontosságából):

static_cast<int> (2.5);

Ugyanezt persze változók értékével is megtehetjük, akár a kiíratással egy utasításban:

double valtozo = 2.5; cout << static_cast<int>(valtozo) << endl;

Ezzel az utasítással természetesen csak hasonló típusokat konvertálhatunk. Például egy string típusú érték számmá konvertálása csak másképp valósítható meg, akkor is, ha az a string típusú érték konkrétan egy szám.

Typedef

A typedef-ek aliasok (álnevek, rövidítések) a típusokra és a típusminősítőkre nézve. Érdemes lehet használni őket, ha gyakran definiálunk a programunkban olyan változókat, amiket típusminősítőkkel együtt hosszú lenne kiírni (például extern unsigned long int).
Használatuk egyszerű, amikor definiálunk egy typedef-t, megmondjuk, hogy az milyen típusminősítő(ke)t és típust helyettesítsen, és onnantól kezdve a programunkban egy változó definíciójában nem kell mindig kiírni a típusminősítőket és a típust, hanem elég helyette leírni a typedef-et. Például:

typedef unsigned long int uli_t; //az uli_t ezentúl jelentse azt, hogy unsigned long int
uli_t a; //unsigned long int típusú változó definiálása

KIEGÉSZÍTŐ INFORMÁCIÓK

Értékadás vs bekérés

A bekérés kicsit máshogy működik, mint az értékadás. Például ha egy karakter típusú változónak megpróbálunk értékül adni egy több karakterből álló szöveget (char c = "szoveg";), akkor a program le sem fordul. Viszont ha bekéréskor írunk be egy több karakterből álló szöveget, amikor épp egy karakter értékét kéri be a program, akkor a program a megadott szöveg első karakterét fogja értékül adni a karakter változónak, a többi karaktert pedig továbbadja a következő bekérésnek. Ez azért van, mert a beírt szöveg először az input bufferbe (a számítógép memóriájának egy értékek bekérésére elkülönített területére) kerül, az egyes bekérések pedig valójában az input buffert dolgozzák fel.

Szóközt tartalmazó karakterlánc bekérése

Felmerülhet a kérdés, hogy ha a string típus (és a karaktertömbök) alkalmas szövegek tárolására, akkor miért nem tudtunk szóközt is tartalmazó karakterláncot bekérni a fenti példában. A cin >> operátora alapvetően csak az első szóközig olvas be egy szöveget, viszont a getline függvénnyel be lehet olvasni (pl. string típusú változókba, vagy karaktertömbökbe) akár újsor karakterig is.
Mint ahogy a cout és a cin is (bár azok nem függvények), a getline is az iostream header fájlban van deklarálva.
A cin >> s; helyett így használjuk a getline-t:

getline( cin, s, '\n' );

A zárójelben lévő, vesszővel elválasztott elemek a getline függvény aktuális paraméterei. Az első paraméterben azt kell megadni, hogy honnan olvasson be (pl. parancssorból (cin), vagy fájlból). A második paraméter az a változó, aminek bekért szöveget értékül adjuk. A harmadik paraméter pedig egy tetszőlegesen választott karakter, aminek az első előfordulásáig tart a változó értékéül adandó szöveg (ha elhagyjuk, az egyenértékű azzal, mintha újsor karaktert adtunk volna meg). Ez a két utasítás egyenértékű:

getline(cin,s,'\n');
getline(cin,s);

Az egyetlen probléma a getline függvénnyel, ha volt előtte egy cin >> s; típusú utasítás. Tehát ha például így néz ki az adott programkód részlet:

cout << "Kerem adjon meg egy karaktert: \n";
cin >> c;

cout << "Kerem adjon meg egy tetszoleges szoveget (szokozt is tartalmazhat): \n";
getline(cin,s,'\n');

Ekkor biztos, hogy a cin >> c; utasítás után marad az input bufferben legalább egy újsor karakter, amit a getline függvény a bemenet végeként fog értelmezni, és így nem történik meg a bekérés (a felhasználónak nem lesz lehetősége szöveget beírni). Azért írtam, hogy legalább egy újsor karakter, mert például ha a felhasználó a karakter értékének bekérésekor nem csak egy karaktert ad meg, hanem többet is, akkor az input bufferben az újsor karakter előtt is lesz még valami (bár ez valószínáleg ritka eset).
Tehát ha e példaprogram futtatása esetén a c változó értékének bekérésekor csak egy karaktert ad meg a felhasználó, akkor az s változó értéke egy üres string ("") lesz, ha pedig több karaktert, akkor az s változó értéke a felhasználó által megadott karakterek sorozata lesz, leszámítva persze az első karaktert.

Szerencsére van erre is megoldás. A legegyszerűbb, ha megduplázzuk azt a getline utasítást, ami egy cin >> c; típusú bekérés után következik. A két getline utasítás közül közül az első elnyeli az input buffer tartalmát (beleértve az újsor karaktert), a második esetén pedig megtörténik a bekérés.

getline(cin, s, '\n');
getline(cin, s, '\n');

Valamivel talán elegánsabb megoldás, ha az input bufferben esetlegesen maradt szemét begyűjtésére egy külön erre a célra kijelölt (és nevében erre utaló) változót használunk, így talán könnyebben olvasható a kód.

#include <iostream>
#include <string>

using namespace std;

int main() {
	char c;
	string s;
	string tmp;

	cout << "Kerem adjon meg egy karaktert: \n";
	cin >> c; //c valtozo ertekenek bekerese a felhasznalotol
	getline(cin,tmp,'\n'); //az input buffer tartalmanak uritese, ujsor karaktert beleertve

	cout << "Kerem adjon meg egy tetszoleges szoveget (szokozt is tartalmazhat): \n";
	getline(cin,s,'\n'); //s valtozo ertekenek bekerese a felhasznalotol

	cout << "\nA beolvasott ertekek: \nKarakter: " << c << ", szoveg: " << s << "\n";

	return 0;
}

Fontos, hogy csak akkor helyezzünk el egy újabb getline utasítást a programkódban, ha arra számítunk, hogy az input buffer tartalmát üríteni kell (például egy cin >> c; típusú utasítást követő getline elé). Két cin >> c; utasítás vagy két getline között nem kell üríteni az input buffert.

Másik megoldás a cin.ignore(1000,'\n'); utasítás, mely törli az input buffer első 1000 karekterét (természetesen tetszőleges számot is megadhatunk paraméterként), illetve amennyiben az input bufferben az első 1000 karaktert megelőzően helyezkedik el egy újsor karakter, akkor csak addig az újsor karakterig (beleértve azt is) lesz törölve az input buffer tartalma.

Egy másik megoldás:

cin.clear();
while (cin.get() != '\n') {
	continue;
}

Ezt a kódsort is elhelyezhetjük egy cin >> c; típusú utasítás és egy getline közé, mely szintén az input buffer tartalmának ürítését fogja eredményezni (újsor karakterig, beleértve azt is).
(Ezen kódrészlet megértéséhez a ciklusok fejezetben tárgyalt tudnivalók szükségesek).

A char típusról bővebben

A char típusban valójában számok tárolódnak.

char c = 97;
cout << c << endl;

Ekkor nem 97 íródik ki, hanem egy a betű, mivel az 'a' karakter ASCII kódja 97. Az ASCII táblában nézhetjük meg, hogy melyik karakternek mi az ASCII kódja (a képen a Dec oszlopban). A gyakran használt karakterek 32-től 126-ig találhatóak, a 0 és 31 között lévő számokhoz, illetve a 127-hez speciális jelentés tartozik (például a 10-es az újsor karakter).

Az ASCII tábla:

ascii_table.gif

Ha azt szeretnénk, hogy karakter helyett a neki megfelelő számérték íródjon ki, ezt az utasítást használjuk:

char c = 97;
cout << static_cast<int>( c ) << endl;

Viszont ha a felhasználótól kérünk be értéket egy char típusú változóba, akkor csak az első karakter lesz beolvasva (pl. ha 97-et ír be a felhasználó, akkor 9 lesz beolvasva, pontosabban a 9 karakternek megfelelő ASCII érték, azaz 57).

char c;
cin >> c;

Ha a felhasználó által beírt többjegyű számot szeretnénk egy char típusú változónak értékül adni, akkor először egy int típusú változóba kérjük be az értéket, majd az int típusú változó értékét adjuk értékül a char típusúnak.

int temp;
char c;

cin >> temp;
c = temp;

Persze ha a felhasználó túl sok számjegyből álló számot ad meg, akkor nem lesz eltárolva a tényleges érték, hiszen a char típus csak 1 bájtos.

1 bájton 256 érték tárolható, tehát egy char típusban vagy -128 és 127, vagy pedig 0 és 255 közötti értéket tárolhatunk, attól függően, hogy a char típus signed, vagy unsigned.

Az alap char típus jó eséllyel signed, de a numeric_limits sablonosztály min() és max() tagfüggvényeivel lekérdezhetjük a szélső értékeit (ne felejtsük el, hogy most számként szeretnénk őket kiíratni, használjuk a static_cast operátort inttel paraméterezve):

std::cout << static_cast<int>( std::numeric_limits<char>::min() );

Ha számok tárolására szeretnénk char típust használni, akkor az alap char típus helyett érdemesebb signed vagy unsigned char-t használni.
Persze ekkora spórolásra a mai asztali számítógépeken nincs szükség, nyugodtan használjunk intet vagy short intet számok tárolásához.

Íme egy rövid példaprogram, amiben definiálunk egy signed és egy unsigned char típusú változót, majd az értéküket parancssorból kérjük be, és a beolvasott értékeket kiíratjuk.

#include <iostream>
using namespace std;

int main() {

	signed char b;
	unsigned char c;

	int temp;

	cout << "Kerem adjon meg egy egesz szamot (-128 es 127 kozott)" << endl;
	cin >> temp;
	b = temp;
	cout << "Kerem adjon meg egy egesz szamot (0 es 255 kozott)" << endl;
	cin >> temp;
	c = temp;

	cout << "A beolvasott ertekek: " << static_cast<int> ( b ) <<
", valamint: " << static_cast<int> ( c ) << endl;

	return 0;
}

Ha a felhasználó az int típus tartományán kívül eső számot ad meg, akkor az aktuális és az azt követő bekérések ki lesznek hagyva (így program természetesen nem fog rendeltetésszerűen működni).
Viszont ha a char típusok tartományán kívül eső számot ad meg, akkor túlcsordulás történik. Tekintsünk erre néhány példát.
A signed char típus tartománya a -128 és 127 közti egész számok. Ha a felhasználó 128-at ad meg a signed char változó értékének bekérésekor, akkor annak értéke -128 lesz. Ha 129-et, akkor -127.
Az unsigned char típus tartománya a 0 és 255 közti egész számok. Ha a felhaználó 256-ot ad meg az értékének bekérésekor, akkor 0 lesz az értéke. Ha -1-et, akkor 255.

Ékezetes karakterek tárolására használjuk inkább a string típust, mivel rendszerfüggő, hogy a char típusban az alap ASCII tábla értékein felül tárolhatunk-e még egyéb karaktereket. Persze lehet kísérletezni, hogy a rendszerünkön tudunk-e értékadással vagy bekéréssel ékezetes karaktereket char típusú változóknak értékül adni, de ez inkább haladóknak szóló téma, ha valaki kedvet érez hozzá, ezeken a linkeken olvashat erről bővebben: [1] [2] [3]

Felmerülhet a kérdés, hogy lehet-e bekérés segítségével egy char típusú változónak szóközt, tabulátort, újsort (összefoglaló néven whitespacek) értékül adni. Ehhez használjuk a cin.unsetf(ios::skipws); utasítást, például ily módon:

#include <iostream>
using namespace std;

int main() {
	char b, c;
	cin.unsetf(ios::skipws); //ezt kovetoen szokoz, tabulator ertekul adasa is elfogadott bekeresnel
	cin >> b;
	cin.setf(ios::skipws); //szokoz, tabulator ertekul adasa bekeres eseten ujra ervenytelen lesz
	cin >> c;
	cout << "Hello" << b << "world!\n" << c << endl;
	return 0;
}

Ebben a kis példaprogramban az első bekéréskor esetlegesen megadott szóköz, tabulátor, újsor értékül lesz adva a b változónak, ezt követően a cin.setf(ios::skipws); utasítással újból érvénytelenné tesszük a whitespacek bekérését, így a c változó esetén a program már csak egy "tényleges" karaktert fogad el a bekéréskor (whitespacek megadása esetén a program megismétli a bekérést).
(Ez a módszer nem helyettesíti a getline függvényt, hiába használjuk a cin.unsetf(ios::skipws); utasítást, ettől még string típusú változó értékének bekérésekor egy cin >> s; típusú utasítás csak az első szóközig tartó szöveget fogja értékül adni egy string típusú változónak).

A cin.unsetf(ios::skipws); utasítás online C++ fordítók (pl. rextester, C++ shell) esetén nem biztos, hogy megfelelően működik.

Következő rész: elágazások, logikai változók

A bejegyzés trackback címe:

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

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.

christo161 · http://itkezdoknek.blog.hu/ 2018.01.16. 21:12:35

Hogyan számoljuk ki egy típus bájtban megadott méretéből a típus tartományának szélső értékeit? (kiegészítő információ)

Az én laptopomon például az int típus 4 bájtos. 1 biten 2 érték tárolható: 0 vagy 1. 1 bájt 8 bit. Így tehát 1 bájton 28 érték tárolható, ami 256, de mivel a 0-t is beleszámoljuk így az értékek 0-tól 255-ig terjednek.
4 bájt esetén nem a 256-ot szorozzuk meg néggyel, hanem a 28 kitevőjét, ami így 232 lesz. 232 = 4294967296. Viszont mivel az int típusban negatív számok is találhatók, ezért a (4 bájtos) int tartománya a -2147483648-tól 2147483647-ig terjedő számok. Mivel a 0 is szerepel az értékek között, így (megegyezés szerint) egyel kevesebb pozitív szám szerepel az int típus tartományában, mint negatív.
Létezik előjel nélküli int is (unsigned int), amit akkor használhatunk, ha tudjuk, hogy egy int típusú változóban úgyis csak nem negatív számokat (0-t, vagy pozitív számokat) fogunk tárolni. Ennek a tartománya 0 és 4294967295 között van.

Felmerülhet a kérdés, hogy miért kell pl. 4 bájtot lefoglalni a számítógép memóriájában, ha abban csak egyetlen számot szeretnénk tárolni. Nos, erre a válasz az, hogy pl. unsigned int (és hasonló típusok) esetén a legnagyobb megadható érték szabja meg a lefoglalt bájtmennyiséget (hiszen nem lehet pl. 4294967295 egy short int értéke, mert egy short int típusú változó legnagyobb megadható értéke 32767), signed int (és hasonló típusok) esetén pedig értelemszerűen a tartomány megfeleződik, mivel negatív és pozitív számokat is tartalmaz (illetve a 0-át).

christo161 · http://itkezdoknek.blog.hu/ 2018.02.26. 01:04:30

Kulcsszavak

A C++ nyelv kulcsszavai (vagy foglalt szavai) olyan szavak, melyeket csak a nekik tulajdonított jelentés felhasználására alkalmazhatunk. Nem adhatunk a kulcsszavakkal megegyező nevet a változóinknak.

Egyes fordítók vagy fejlesztői környezetek bővebb kulcsszókészlettel rendelkezhetnek (pl. a Visual Studio).

christo161 · http://itkezdoknek.blog.hu/ 2018.02.26. 01:09:41

Számrendszerek

Prefixek segítségével megadhatunk egy egész számot 8-as (oktális), vagy 16-os (hexadecimális) számrendszer szerinti alakban. A gyakorlati életben ugyebár 10-es (decimális) számrendszer szerinti alakban írjuk a számokat, így tehát a 10-es számrendszerbeli számokat nem kell prefixszel jelölni.

(Egy számrendszer azt határozza meg, hogy hányféle számjegyet használhatunk a számok leírásához. 8-as számrendszer esetén például 8 féle számjegyet használhatunk: 0, 1, 2, 3, 4, 5, 6, 7. Így tehát 8-as számrendszerben 7 + 1 = 10. Persze a 8-as számrendszerbeli 10 nem egy ember kézujjainak számával egyenlő, hanem annál 2-vel kevesebb (azaz decimális 8), csak 8-as számrendszerben ebben az alakban írjuk: 10.

A 16-os számrendszerben többféle számjegyet használhatunk, mint a 10-es számrendszerben: 0, 1 .. 9, a, b, c, d, e, f. Így tehát 16-os számrendszerben 9 + 1 = a, 9 + 2 = b, ..., f + 1 = 10, és a 16-os számrendszerbeli 10 egyenlő a 10-es számrendszerbeli 16-tal.
Ha 16-os számrendszerben például azt írjuk, hogy 23e, akkor az alatt 2 * 256 + 3 * 16 + 1 * 14 = 574-et értünk. Ugyebár 256 = 162, mint ahogy 100 = 102.)

C++-ban a 8-as számrendszerbeli alakot úgy jelölhetjük, ha az adott szám elejére egy 0-t írunk, a 16-os számrendszerbeli alakot pedig hasonlóan, ha a szám elejére 0x-et írunk.
A cout alapból 10-es számrendszerbeli alakban írja ki az egész számokat. Ha a kiírandó elemek közé oct-ot írunk, akkor azt azt követő számok 8-as számrendszerbeli alakban lesznek kiírva, hasonlóképp a 10-es számrendszerbeli alakhoz dec-t kell írni, a 16-oshoz pedig hex-et.

int nyolcas = 07;
int nyolcas_2 = 07 + 1;
cout << nyolcas << ", " << nyolcas_2 << endl << "Oktalis alak: " << oct << nyolcas << ", " << nyolcas_2 << endl;

(Mivel nyolcas számrendszerben nem használunk 9-es karaktert, ezért fordítási hibát okoz, ha 0-val kezdődő számokban szerepel a 9-es karakter, pl. nyolcas_2 = 0789;).

int tizenhatos = 0x9 + 1;
int tizenhatos_2 = 0xf + 1;
cout << "Hexadecimalis alak: " << hex << tizenhatos << ", " << tizenhatos_2 << endl << "Decimalis alak: " << dec << tizenhatos << ", " << tizenhatos_2 << endl;

A nagy tizedestörteket felírhatjuk normál alakban is. (Normál alak: a tizedesvesszőt eltoljuk úgy, hogy az adott szám helyett egy 1 és 10 (negatív szám esetén -1 és -10) közötti számot kapjunk, majd az így kapott számot megszorozzuk 10-nek annyi hatványával, ami a tizedesvesszőt visszatolja úgy, hogy megkapjuk az eredeti számot. Például 1234,56 normál alakja: 1,23456 * 103.
Ha valaki esetleg nem tudná, a hatványozást például így értelmezzük: 1012 = egy egyes és 12 darab 0.)
Például a 1,2 * 1012 -t C++-ban így írhatjuk le szabályosan: 1.2e12, valamint a 3,3 * 10-20 -t pedig így: 3.3e-20. Ezt az alakot általában túl nagy vagy túl kicsi számok esetén használjuk. (Az e betű valójában nem prefix, nem is postfix, hanem infix).