Kihagyás

2. gyakorlat

Ismétlés

  • stdio.h -> iostream
  • scanf -> cin
  • printf -> cout

C++ string

Az előző órán láttuk, hogyan bírjuk a stream-eket használni kiíratásra és olvasásra. Mi történik az alábbi programmal akkor, ha a fix méretű karakter tömbbe hosszabb méretű karakterláncot adunk meg?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <iostream>
#include <string>

using namespace std;

int main() {
  char nev[10];
  cout << "Adja meg a nevet: ";
  cin >> nev;
  cout << "A nev: " << nev << endl;
}

Az eredmény (ha a program lefut):

1
2
Adja meg a nevet: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Nev: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Azonban ha túl hosszú a bemenet, akkor a program hibával megállhat. A megoldás az, hogy az inputnak megfelelő méretű területet foglalunk, és oda írjuk be a felhasználó által megadott nevet. A C++-ban a "szövegeknek" van egy hatékonyabb, kevésbé körülményes megvalósítása is, a string osztály (osztályokról később lesz szó, most csak azt mutatjuk be, hogyan lehet használni a string-et). A félév folyamán stringek alatt ezt a reprezentációt értjük, nem pedig a char*-ot. A string objektumok használatához, string műveletekhez szükségünk lesz a string include-olására is (és nem string.h). Ezt követően kényelmesen használhatjuk a szövegeket, ahogy már Javaban is megszoktuk. Itt csak a fontosabb használati eseteket emeljük ki, bővebb leírásokhoz linkek:

A string fontosabb metódusai, amelyek a leggyakrabban használunk:

Létrehozás, azaz a konstruktorok

1
2
3
4
string s1;           // ures string
string s2("alma");   // string, amely az "alma" szoveget tartalmazza
string s3 = "korte"; // string, amely az "korte" szoveget tartalmazza
string s4(s2);       // string, amely az "alma" szoveget tartalmazza, mert az s2 "alma"

Beolvasás, kiírás

1
2
3
string str;
cin >> str;
cout << "A beolvasott szoveg: " << str << endl;

A scanf nem használható a string beolvasására, de a cin segítségével be tudjuk olvasni, és a cout segítségével ki tudjuk írni.

Összehasonlítás

1
2
3
4
5
6
string s1, s2;
cin >> s1 >> s2;
if (s1 == s2)
    cout << "A ket string azonos" << endl;
else
    cout << "A ket string nem azonos" << endl;

A string-eket az == operátor segítéségével össze lehet hasonlítani.

Összefűzés

1
2
3
4
string s1("Hello"), s2("World"), s3;
s3 = s1 + " " + s2 + "!";
cout << s3 << endl;
// kimenet: Hello World!

Méret, üres-e, tartalom törlése

1
2
3
4
5
6
7
string s("alma");
cout << "meret: " << s.size() << " " << s.length() << endl; // meret: 4 4
if (s.empty())
    cout << "s ures" << endl;
s.clear();
if (s.empty())
    cout << "s ures" << endl; // most kiirja, hogy "s ures"

i-edik elem

1
2
3
4
5
string s("alma");
cout << s[2] << endl; // m
s[0] = 'l';
s[1] = 'a';
cout << s << endl;    // lama

Ha olyan indexre hivatkozunk, amelyik érvénytelen, azaz a string hosszánál nagyobb, akkor nem definiált viselkedés lesz az eredménye.

Konverzió szám és string között

Számból string-gé a to_string segítségével konvertálhatunk.

1
2
3
4
int i = 5;
string s1 = to_string(i);
string s2 = to_string(3.14);
cout << s1 << " " << s2 << endl; // 5 3.140000

string-et számmá konvertálni az stoi függvénnyel lehet.

1
2
3
string s1("12345");
int i = stoi(s1);
cout << i << endl; // 12345

Konverziók a különböző típusokra:

  • int: stoi
  • long: stol
  • long long: stoll
  • unsigned long: stoul
  • unsigned long long: stoull
  • float: stof
  • double: stod
  • long double: stold

Mi történik,

  • ha a szöveg elején van szám, de van mögötte ,,egyéb'', akkor a számot átkonvertálja
1
2
string s("123 alma");
cout << stoi(s) << endl; // 123
  • ha nem fér bele a tartományba, akkor std::out_of_range kivétel dobódik (kivételkezelés, const és referencia később)
1
2
3
4
5
6
7
try {
  string s1("999999999999999999999999999999");
  cout << stoi(s1) << endl;
}
  catch (const out_of_range& e_out) {
  cout << "out_of_range exception" << endl;
}
  • ha nem lehet konvertálni, akkor std::invalid_argument kivétel dobódik
1
2
3
4
5
6
7
try {
  string s1("alma");
  cout << stoi(s1) << endl;
}
  catch (const invalid_argument& e_inv) {
  cout << "invalid_argument exception" << endl;
}

Operátor felüldefiniálás

Ahhoz, hogy meg tudjuk nézni, hogyan is működik az operátorfelüldefiniálás, először meg kell néznünk, hogyan működnek az operátorok. Ezt a += operátor segítségével mutatjuk be.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <iostream>
using namespace std;
int main() {
  int a = 5;
  int b = 7;

  cout << a << endl;
  cout << b << endl;

  a += 3;
  b += a;

  cout << a << endl;
  cout << b << endl;
}

Ha részletesebben megnézzük az a += 3 és b += a sorokat, látjuk, hogy a += operátort hívtuk, egyszer integer literált majd egy int típusú változót adva neki. Természetesen tudjuk, hogy ekkor a baloldali változó értékéhez hozzáadódik a joboldali érték. Ezt megírhatnánk egy növel függvénnyel is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

int& novel(int& a, int b) {
  a = a + b;
  return a;
}

int main() {
  int a = 5;
  int b = 7;

  cout << a << endl;
  cout << b << endl;

  novel(a,3);
  novel(b,a);

  cout << a << endl;
  cout << b << endl;
}

A fenti kódot lefuttatva ugyan azt kapjuk mint mikor a += operátort használtuk, csupán a kód olvashatósága rosszabb. Sokszor hasznos lenne ezt az olvashatóságot egyéb típusokra is használni nem csak a beépített s primitív típusokra.

Ha bankszámlát szeretnénk kezelni, akkor a már ismert struktúrát használhatjuk.

1
2
3
4
struct Bankszamla {
  string tulajdonos_neve;
  int szamla_osszege;  
};

Ha ezen a struktúrán bármilyen műveletet szeretnénk végezni (pl. utalás), meg kell írni hozzá egy függvényt.

utalás függvény

Az utaláskor tudnunk kell, hogy melyik számlát kell módosítani és milyen összeggel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;

typedef struct Bankszamla {
  string tulajdonos_neve;
  int szamla_osszege;  
} Bankszamla;

void utalas(Bankszamla& bsz, int utalt_osszeg) {
  bsz.szamla_osszege += utalt_osszeg; // hibakezelessel nem foglalkozunk
}

int main() {
  Bankszamla b_szamla;
  b_szamla.tulajdonos_neve = "Tisztesseges Ugyfel";
  b_szamla.szamla_osszege = 1000;

  // roviden:
  // Bankszamla b_szamla { "Tisztesseges Ugyfel", 1000 };

  cout << b_szamla.szamla_osszege << endl;

  utalas(b_szamla, 1500);

  cout << b_szamla.szamla_osszege << endl;
}

Látszódik, hogy létrehozunk egy bankszámlát, inicializáljuk az értékeket és kiíratjuk a számlán lévő összeget az utalás előtt és után is. Azonban pont mint a számok esetében, jobban olvasható kódot kapunk, ha nem egy függvényhívást, hanem pl. a += operátort használjuk. Mivel az operátorok ugyan olyan elemei a kódnak mint pl. az utalas függvényünk, cseréljük le a nevét.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
using namespace std;

typedef struct Bankszamla {
  string tulajdonos_neve;
  int szamla_osszege;  
} Bankszamla;

void operator+=(Bankszamla& bsz, int utalt_osszeg) { // szandekosan void
  bsz.szamla_osszege += utalt_osszeg; // hibakezelessel nem foglalkozunk
}

int main() {

  Bankszamla b_szamla { "Tisztesseges Ugyfel", 1000 };

  cout << b_szamla.szamla_osszege << endl;

  // ez meg a fuggveny-szeru hivas, amit igy nem szoktunk kiirni
  operator+=(b_szamla, 10);

  cout << b_szamla.szamla_osszege << endl;

  // hasznalhatjuk a megszokott jelolest az operatornal
  b_szamla += 1490;

  cout << b_szamla.szamla_osszege << endl;

  return 0;
}

Ahogy látjuk, a névcsere után is megfelelően működik a függvény. Ami fontos, hogy a jelölésmód cseréje után is, hiszen a fordító tudja, hogy Bankszamla típust kell az első helyre keresnie, melyet baloldali operandusként meg is talál. Azt is felismeri, hogy a jobboldali operandus pont a második paraméternek felel meg, így be tudja azonosítani azt a függvényt amit meg kell hívnia.

befizetés függvény

Sokszor a bankszámlánk segítségével csekkeket is be tudunk fizetni. Ekkor hozzárendelünk egy csekket a számlánkhoz.

Erre az újonnan bevezetett jelöléssel a következőt írhatjuk:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
...
typedef struct Csekk {
  int egyenleg;
  string azonosito;
  string szervezet;
} Csekk;

...

Csekk csekk{600, "V2020-0065", "Vizmu"};

b_szamla += csekk;

Ez azonban fordítási hibát eredményez, hiszen mikor a baloldali operandust vizsgálja a fordító, azt nem tudja a += operátor második paramétereként értelmezni.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
using namespace std;

typedef struct Bankszamla {
  string tulajdonos_neve;
  int szamla_osszege;  
} Bankszamla;

typedef struct Csekk {
  int egyenleg;
  string azonosito;
  string szervezet;
} Csekk;

void operator+=(Bankszamla& bsz, Csekk& csekk) {
  bsz.szamla_osszege -= csekk.egyenleg; // hibakezelessel nem foglalkozunk
  // figyeljunk oda, hogy += operator van, de ez alap ertelmezesben levonast jelent, csekket fizetunk!
  csekk.egyenleg = 0; // A fizetendo nulla lesz kifizetes utan.
}

void operator+=(Bankszamla& bsz, int utalt_osszeg) {
  bsz.szamla_osszege += utalt_osszeg; // hibakezelessel nem foglalkozunk
}

int main() {
  Bankszamla b_szamla { "Tisztesseges Ugyfel", 1000 };
  Csekk csekk{600, "V2020-0065", "Vizmu"};
  Csekk csekk2{600, "G2020-0165", "Gazmu"};

  cout << b_szamla.szamla_osszege << endl;
  cout << csekk.egyenleg << endl;

  operator+=(b_szamla, csekk);

  b_szamla += 400;

  cout << b_szamla.szamla_osszege << endl;
  cout << csekk.egyenleg << endl;

  b_szamla += csekk2;

  cout << b_szamla.szamla_osszege << endl;
  cout << csekk2.egyenleg << endl;
}

Érdekesség, hogy míg az ,,eredeti'' += operátor a jobb oldalon szereplő értéket nem változtatja meg, addig az általunk implementált operátor arra is hatással van.

Miután megvalósítottuk azt a függvényt, aminek a neve a += operátor és mind a két paraméter megfelel az átadott paraméternek, a fordító megtalálja, hogy mit kell odaraknia s sikeresen futtathatjuk a kódot. Látszódik, hogy a két operátorfelüldefiniálás nem akad össze, hiszen a paramétereik megkülönböztetik azokat.

Dinamikus memóriakezelés

A bankszámlák kezelése során sokszor logok alapján vissza lehet állítani az éppen aktuális összeget. Ha tudjuk egy fix ponton a pontos összeget és az onnantól bekövetkezett az összes változást, meg tudjuk mondani az aktuális összeget. Ez a tranzakciós történet legyen egy tömb. A tömbben a pozitív érték növekedést a negatív érték csökkenést jelentsen!

Ezt a tömböt dinamikusan fogjuk foglalni, hogy meglássuk a C és C++ nyelvek közti különbséget.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
using namespace std;

struct Bankszamla {
  std::string tulajdonos_neve;
  int szamla_osszege;  
};

/**
* Vegrehajtja a kapott tranzakciot.
* \returns A maradek osszeget a szamlan
*/
int roll_back_single(Bankszamla& bsz, const int* const t) {
    bsz.szamla_osszege += (*t);  // hibakezelessel nem foglalkozunk
    return bsz.szamla_osszege;
}

/**
* Vegrehajtja a kapott tranzakciokat a zaro nullas tranzakcioig.
* \returns A maradek osszeget a szamlan
*/
int roll_back_multiple(Bankszamla& bsz, const int* multiple) {

    do {
        bsz.szamla_osszege += (*multiple);
    }while(*(multiple++) != 0);

    return bsz.szamla_osszege;
}

int main() {
  int* single = new int; // csak egy tranzakcio
  int* multiple = new int[4];

  *single = -70;

  multiple[0] = 5;
  multiple[1] = -100;
  multiple[2] = 840;
  multiple[3] = 0;

  Bankszamla bsz{"Ugyfel", 1000};

  cout << bsz.szamla_osszege << endl;

  roll_back_single(bsz, single);

  cout << bsz.szamla_osszege << endl;

  roll_back_multiple(bsz, multiple);

  cout << bsz.szamla_osszege << endl;

  delete single;
  delete[] multiple;

  return 0;
}

Feladatok

  1. Írjunk programot, mely bekér két stringet és összefűzi azokat úgy, hogy bekérés sorrendjétől függetlenül mindig a hosszabb kerül előre (egyenlő hosszúságnál a bekérés sorrendjében fűzze össze)! Az eredményt írja is ki!

    Megoldás
  2. Írjunk egy programot, mely beolvas egy stringet és egy számot. Írja ki a string elemeit a kapott indextől! Ha az index nem valid, írja ki, az alapértelmezett hiba csatornára, hogy ,,invalid index''!

    Megoldás
  3. A stringhez kapcsolódó referencia oldalak segítségével írjunk olyan programot, mely bekér két stringet és a hosszabban megkeresi a rövidebbet! Ha nem található, írja ki, az alapértelmezett hibacsatornára, hogy "not found"! Ha megtalálta, írja ki a pozíciót ahol kezdődik!

    Megoldás
  4. A referencia oldalak segítségével írjunk olyan programot, mely a legjobboldalabbi 'b' betű pozícióját írja ki egy bekért szövegből! Nézzük meg, mit kapunk, ha nem található 'b' betű a szövegben!

    Megoldás
  5. Írjunk programot, mely kideríti [-100;100] -on, hogy melyik számnak egyezik meg az értéke és a string reprezentáció hossza!

    Megoldás
  6. Írjunk programot, mely bekér 2 stringet és 2 számot (lehetséges értékek: 2,8,10,16)! A program a 2 stringet rendre a 2 bekért szám szerinti számrendszerben vett számként értelmezze és váltsa át azokat tízes számrendszerbeli számmá! (megj.: a konvertáló függvények átnézése a referencia oldalakon!)

    Megoldás
  7. Miért nem működik a -= operátor, hogyan lehetne megoldani a problémát?

    Megoldás
  8. A -= operátor nélkül ,,vonjunk'' le a számláról (negatív hozzáadása)!

    Megoldás
  9. Csak akkor működjön a += operátor, ha a csekkben meghatározott szervezet "Vizmu"!

    Megoldás
  10. Csak akkor működjön a += operátor, ha a csekkben meghatározott szervezet "Vizmu" a cél számla tulaja "Szilveszter Janos" és szilveszteri ajándékként a forrásszámla 0,75-szörösét vonja le negatív egyenleg esetén, különben 1,5-szörösét!

    Megoldás
  11. Valósítsuk meg a roll_back_multiple függvényt a /= operátor segítségével!

    Megoldás
  12. Valósítsuk meg a /= operátort (roll_back_multiple), hogy const int* const típusú legyen a második paraméter!

    Megoldás
  13. Írjunk a fenti példák alapján egy programot, mely bekér egy számot (tranzakciók darabszáma), akkora dinamikus tömböt foglal, majd bekéri a tranzakciók összegét! Egy alap számlán hajtsa végre a tranzakciókat a /= operátor segítségével! Ne felejtsük el a memória felszabadítást sem!

    Megoldás

Utolsó frissítés: 2020-09-16 10:41:19