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