• Nem Talált Eredményt

C-stílusú sztringek kezelése

7. Tömbök és sztringek

7.4. C-stílusú sztringek kezelése

int main() {

int sorokszama, sorhossz;

cout<<"Sorok szama : "; cin >> sorokszama;

cout<<"Sorok hossza: "; cin >> sorhossz;

vector< vector<int> > m (sorokszama, sorhossz);

// A tömb elérése

for (int i=0; i<sorokszama; i++) for (int j=0; j<sorhossz; j++) m[i][j]= i+j;

}

7.4. C-stílusú sztringek kezelése

A C/C++ nyelv az alaptípusai között nem tartalmazza a karaktersorozatok tárolására alkalmas sztring típust.

Mivel azonban a szövegek tárolása és feldolgozása elengedhetetlen része a C/C++ programoknak, a tároláshoz egydimenziós karaktertömböket használhatunk. A feldolgozáshoz szükséges még egy megegyezés, ami szerint az értékes karaktereket mindig egy 0 értékű bájt zárja a tömbben. A sztringek kezelését operátorok ugyan nem segítik, azonban egy gazdag függvénykészlet áll a rendelkezésünkre (lásd cstring fejállomány).

I.21. ábra - Sztring konstans a memóriában

A programban gyakran használunk kettős idézőjelekkel határolt szövegeket (sztringliterálokat), amiket a fordító az inicializált adatok között tárol a fent elmondottak szerint. A

cout << "C++ nyelv";

utasítás fordításakor a szöveg a memóriába másolódik, (I.21. ábra - Sztring konstans a memóriában) és a <<

művelet jobb operandusaként a const char * típusú tárolási cím jelenik meg. Futtatáskor a cout objektum karakterenként megjeleníti a kijelöl tárterület tartalmát, a 0 értékű bájt eléréséig.

A széles karakterekből álló sztringek szintén a fentiek szerint tárolódnak, azonban ebben az esetben a tömb elemeinek típusa wchar_t.

wcout << L"C++ nyelv";

A C++ nyelven a string illetve a wstring típusokat is használhatjuk szövegek feldolgozásához, ezért dióhéjban ezekkel a típusokkal is megismerkedünk.

7.4.1. Sztringek egydimenziós tömbökben

Amikor helyet foglalunk valamely karaktersorozat számára, akkor a sztring végét jelző bájtot is figyelembe kell vennünk. Ha az str tömbben maximálisan 80-karakteres szöveget szeretnénk tárolni, akkor a tömb méretét 80+1=81-nek kell megadnunk:

char sor[81];

A programozás során gyakran használunk kezdőértékkel ellátott sztringeket. A kezdőérték megadására alkalmazhatjuk a tömböknél bemutatott megoldásokat, azonban nem szabad megfeledkeznünk a '\0' karakter elhelyezéséről:

char st1[10] = { 'O', 'm', 'e', 'g', 'a', '\0' };

wchar_t wst1[10] = { L'O', L'm', L'e', L'g', L'a', L'\0' };

char st2[] = { 'O', 'm', 'e', 'g', 'a', '\0' };

wchar_t wst2[] = { L'O', L'm', L'e', L'g', L'a', L'\0' };

Az st1 sztring számára 10 bájt helyet foglal a fordító, és az első 6 bájtba bemásolja a megadott karaktereket. Az st2 azonban pontosan annyi bájt hosszú lesz, ahány karaktert megadtunk az inicializációs listában. A széles karakteres wst1 és wst2 sztringek esetén az előző bájméretek kétszeresét foglalja le a fordító.

A karaktertömbök inicializálása azonban sokkal biztonságosabban elvégezhető a sztringliterálok (sztring konstansok) felhasználásával:

char st1[10] = "Omega"; wchar_t wst1[10] = L"Omega";

char st2[] = "Omega"; wchar_t wst2[] = L"Omega";

A kezdőértékadás ugyan mindkét esetben - a karakterenként illetve a sztring konstanssal elvégzett - azonos eredményt ad, azonban a sztring konstans használata sokkal áttekinthetőbb. Nem beszélve arról, hogy a sztringeket lezáró 0-ás bájtot szintén a fordító helyezi el a memóriában.

A tömbben történő tárolás következménye, hogy a sztringekre vonatkozóan szintén nem tartalmaz semmilyen műveletet (értékadás, összehasonlítás stb.) a C++ nyelv. A karaktersorozatok kezelésére szolgáló könyvtári függvények azonban gazdag lehetőséget biztosítanak a programozónak. Nézzünk néhány karaktersorozatokra vonatkozó alapművelet elvégzésére szolgáló függvényt!

Művelet Függvény (char) Függvény (wchar_t)

szöveg beolvasása cin>>, cin.get () , cin.getline () wcin>>, wcin.get () , wcin.getline ()

szöveg kiírása cout<< wcout<<

értékadás strcpy () , strncpy () wcscpy () , wcsncpy ()

hozzáfűzés strcat () , strncat () wcscat () , wcsncat ()

sztring hosszának lekérdezése strlen () wcslen ()

sztringek összehasonlítása strcmp () , strcnmp () wcscmp () , wcsncmp ()

karakter keresése szringben strchr () wcschr ()

A char típusú karakterekből álló sztringek kezeléséhez az iostream és a cstring deklarációs állományok beépítése szükséges, míg a széles karakteres függvényekhez a cwchar.

Az alábbi példaprogram a beolvasott szöveget nagybetűssé alakítva, fordítva írja vissza a képernyőre. A példából jól látható, hogy a sztringek hatékony kezelése érdekében egyaránt használjuk a könyvtári függvényeket és a karaktertömb értelmezést.

#include <iostream>

#include <cstring>

#include <cctype>

using namespace std;

int main() { char s[80];

cout <<"Kerek egy szoveget: ";

cin.get(s, 80);

cout<<"A beolvasott szoveg: "<< s << endl;

for (int i = strlen(s)-1; i >= 0; i--) cout<<(char)toupper(s[i]);

cout<<endl;

}

A megoldás széles karakteres változata:

#include <iostream>

#include <cwchar>

#include <cwctype>

using namespace std;

int main() { wchar_t s[80];

wcout <<L"Kerek egy szoveget: ";

wcin.get(s, 80);

wcout<<L"A beolvasott szoveg: "<< s << endl;

for (int i = wcslen(s)-1; i >= 0; i--) wcout<<(wchar_t)towupper(s[i]);

wcout<<endl;

}

Mindkét példában a szöveg beolvasásához a biztonságos cin.get() függvényt használtuk. A függvény minden karaktert beolvas az <Enter> lenyomásáig. A megadott tömbbe azonban legfeljebb az argumentumként megadott méret -1 karakter kerül, így nem lehet túlírni a tömböt.

7.4.2. Sztringek és a pointerek

A sztringek kezelésére karaktertömböket és karaktermutatókat egyaránt használhatunk, azonban a mutatókkal óvatosan kell bánni. Tekintsük az alábbi gyakran alkalmazott definíciókat!

char str[16] = "alfa";

char *pstr = "gamma";

Első esetben a fordító létrehozza a 16-elemű str tömböt, majd belemásolja a kezdőértékként megadott szöveg karaktereit, valamint a 0-ás bájtot. A második esetben a fordító az inicializáló szöveget eltárolja a sztringliterálok számára fenntartott területen, majd a sztring kezdőcímével inicializálja a pstr mutatót.

A pstr mutató értéke a későbbiek folyamán természetesen megváltoztatható (ami a példánkban a "gamma"

sztring elvesztését okozza):

pstr = "iota";

Ekkor mutató-értékadás történik, hisz a pstr felveszi az új sztringliterál címét. Ezzel szemben az str tömb nevére irányuló értékadás fordítási hibához vezet:

str = "iota"; // hiba! ↯

Amennyiben egy sztringet karakterenként kell feldolgozni, akkor választhatunk a tömbös és a pointeres megközelítés között. A következő példaprogramban a beolvasott karaktersorozatot először titkosítjuk a kizáró vagy művelet felhasználásával, majd pedig visszaállítjuk az eredeti tartalmát. (A titkosításnál a karaktertömb, míg a visszakódolásnál a mutató értelmezést alkalmazzuk.) Mind a két esetben a ciklusok leállási feltétele a sztringet záró nullás bájt elérése.

#include <iostream>

using namespace std;

const unsigned char kulcs = 0xCD;

int main() {

usigned char s[80], *p;

cout <<"Kerek egy szoveget: ";

cin.get(s, 80);

for (int i = 0; s[i]; i++) // titkosítás s[i] ^= kulcs;

cout << "A titkositott szoveg : "<< s << endl;

p = s;

while (*p) // visszaállítás *p++ ^= kulcs;

cout << "Az eredeti szoveg : "<< s << endl;

}

A következő példában a léptető és az indirektség operátorokat együtt alkalmazzuk, amihez kellő óvatosság szükséges. Az alábbi példában az sp mutatóval egy dinamikusan tárolt karaktersorozatra mutatunk.

(Megjegyezzük, hogy a C++ implementációk többsége nem engedi módosítani a sztringliterálokat.) char *sp = new char [33];

strcpy(sp, "C++");

cout << ++*sp << endl; // D cout << sp << endl; // D++

cout << *sp++ << endl; // D cout << sp << endl; // ++

Az első esetben (++*sp) először az indirektség operátorát értelmezi a fordító, majd pedig elvégzi a hivatkozott karakter léptetését. A második esetben (*sp++) először a mutató léptetése értékelődik ki, azonban a hátravetett forma következtében csak a teljes kifejezés feldolgozása után megy végbe a léptetés. A kifejezés értéke pedig a hivatkozott karakter.

7.4.3. Sztringtömbök használata

A C++ programok többsége tartalmaz olyan szövegeket, például üzeneteket, amelyeket adott index (hibakód) alapján kívánunk kiválasztani. Az ilyen szövegek tárolására a legegyszerűbb megoldás a sztringtömbök definiálása.

A sztringtömbök kialakítása során választhatunk a kétdimenziós tömb és a mutatótömb között. Kezdő C++

programozók számára sokszor gondot jelent ezek megkülönböztetése. Tekintsük az alábbi két definíciót!

int a[4][6];

int* b[4];

Az a „igazi” kétdimenziós tömb, amely számára a fordító 24 (4x6) darab int típusú elem tárolására alkalmas folytonos területet foglal le a memóriában. Ezzel szemben a b 4-elemű mutatóvektor. A fordító csak a 4 darab mutató számára foglal helyet a definíció hatására. A inicializáció további részeit a programból kell elvégeznünk.

Inicializáljuk úgy a mutatótömböt, hogy az alkalmas legyen 5x10 egész elem tárolására!

int s1[6], s2[6], s3[6], s4[6];

int* b[4] = { s1, s2, s3, s4 };

Látható, hogy a 24 int elem tárolására szükséges memóriaterületen felül, további területet is felhasználtunk (a mutatók számára). Joggal vetődik fel a kérdés, hogy mi az előnye a mutatótömbök használatának. A választ a sorok hosszában kell keresni. Míg a kétdimenziós tömb esetén minden sor ugyanannyi elemet tartalmaz,

addig a mutatótömb esetén az egyes sorok mérete tetszőleges lehet.

int s1[1], s2[3], s3[2], s4[6];

int* b[4] = { s1, s2, s3, s4 };

A mutatótömb másik előnye, hogy a felépítése összhangban van a dinamikus memóriafoglalás lehetőségeivel, így fontos szerepet játszik a dinamikus helyfoglalású tömbök kialakításánál.

E kis bevezető után térjünk rá az alfejezet témájára, a sztringtömbök kialakítására! A sztringtömböket általában kezdőértékek megadásával definiáljuk. Az első példában kétdimenziós karaktertömböt definiálunk, az alábbi utasítással:

static char nevek1[][10] = { "Iván", "Olesya", "Anna", "Adrienn" };

Az definíció során létrejön egy 4x10-es karaktertömb - a sorok számát a fordítóprogram az inicializációs lista alapján határozza meg. A kétdimenziós karaktertömb sorai a memóriában folytonosan helyezkednek el (I.22.

ábra - Sztringtömb kétdimenziós tömbben tárolva).

I.22. ábra - Sztringtömb kétdimenziós tömbben tárolva

A második esetben mutatótömböt használunk a nevek címének tárolására:

static char* nevek2[] = { "Iván", "Olesya", "Anna", "Adrienn" };

A fordító a memóriában négy különböző méretű területet foglal le a következő ábrán (I.23. ábra - Optimális tárolású sztringtömb) látható módon:

I.23. ábra - Optimális tárolású sztringtömb

Érdemes összehasonlítani a két megoldást mind a definíció, mind pedig a memóriaelérés szempontjából.

cout << nevek1[0] << endl; // Iván cout << nevek1[1][4] << endl; // y cout << nevek2[0] << endl; // Iván cout << nevek2[1][4] << endl; // y