C++ programozás 16. – String

String, avagy minden, amit begépelhetsz (vagy nem)

Még nincs ellenőrizve, de felteszem, csak hogy haladni lehessen.

A C++ programozási nyelvben, mint megtudhattad, többféle változótípus létezik. Ezek egy része egyszerű (primitív) típus, de vannak összetett, komplexebb típusok. Ezek egyike a karakterlánc, más nevén string.

A string egy osztály, mely karakterek sorozatából áll, melyek egymás után meghatározott sorrendben karakterláncot alkotnak. Felfogható úgy is, mint egy tömb, karakterek tömbje. A stringek nem csak kiíratható karaktereket tartalmazhatnak, olyanok is lehetnek benne, amelyek nem látszanak, ezekről majd később szót ejtek.

string deklaráció és értékadás

A string deklarációja a következőképpen néz ki:

string s;

Ettől a ponttól kezdve van egy üres stringem. Akkor is, ha nem adtam neki semmilyen kezdőértéket.

Lássuk még milyen módon hozhatunk létre stringeket:

string s1 = "";       // üres string létrehozása;
string s2 = "Bela";   // string létrehozása tartalommal
string s3 = s2;       // string létrehozása, másik változó értéke alapján
string s4 ("alma");   // string létrehozása alma tartalommal
string s5 (s4, 6, 4); // s4-ből, 6 helytől 4 helynyit felhasználva (alma)
string s6 (10,'z');   // 10 darab z karakter elhelyezése egy új stringben

Az első példában ugyanúgy egy üres string-et hozunk létre. A második esetben direkt kezdőértéket állítunk meg, a direkt értékeket literálnak nevezzük. A harmadik példában a string értékének egy másik string értékét állítjuk be. Ez nem azt jelenti, hogy a két változó össze van kötve. Ha az eredeti s2 string értékét megváltoztatjuk, az s3 tőle függetlenül marad a neki beállított értéken. A negyedik esetben a stringet az úgynevezett konstruktorán keresztül hoztuk létre, ezt ritkán fogjuk használni, de legfőképp soha. Az ötödik esetben egy stringet egy másik string részét felhasználva hozzuk létre, szintén a konstruktoron keresztül, ez is ritka, az utolsó eset inkább csak érdekesség.

A string a C++ nyelvben megváltoztatható!

Azért emeltem ezt ki, mert más nyelvekben (Java) megváltoztathatatlan (immutable), ami azt jelenti, hogy ha a string megkapta a kezdőértékét, akkor annak megváltoztatása esetén új string jön létre, az eredeti pedig előbb-utóbb a szemétbe kerül. A C++ nyelvben a stringbe bele tudunk nyúlni, értékét direkt módon is megváltoztathatjuk, nem kell hozzá metódusokat használni.

A következő példa kicsit fura lehet:

string s = "abzd";
cout << s << endl; // abzd

s += "efgh";
cout << s << endl; // abzdefgh

s[2] = 'c';
cout << s << endl; // abcdefgh

Azt mondtam, hogy a string megváltoztatható. Nézzük, mit jelent ez a fenti példában :

  • 1 – megadom a string eredeti tartalmát és hozzárendelem az s változóhoz.
  • 4 – hozzáadok (hozzáfűzök) valamit az s változó tartalmához.
  • 7 – kihasználom, hogy a string egy karakterlánc, vagy karakterek tömbje, melynek adott eleme direkt módon tömbindexeléssel hozzáférhető, és tetszőlegesen módosítható.

string függvények

Lássuk akkor, hogy mit kezdhetünk a stringekkel. A stringek sok függvénnyel rendelkeznek, ezekkel lehet kezelni őket. Lássuk akkor ezeket példákon keresztül, hogy melyek ezek, és mire is használhatók. A felsorolás nem lesz teljes, de a legfontosabbakat úgy gondolom, hogy tartalmazza.

String hossza – length()

A C++ nyelvben a string hosszát a length() függvénnyel kaphatjuk meg. Szükséges lehet, ha a string karakterein szeretnénk egy ciklussal végigmenni valamilyen módosítás céljából. Fontos tudni, hogy ez egy előjel nélküli egészet ad vissza, amit ha direkt módon használunk egy ciklusban, akkor működik ugyan, de figyelmeztetést küld a fordítást végző program, hogy nem célszerű különböző típusú értékeket összehasonlítani. Célszerű a .length() függvény által visszaadott értéket berakni egy int típusú változóba, és azt használni a ciklusban. Erre példát lejjebb, a string adott karaktere pontban láthatsz.

string s1 = "abrakadabra";
cout << s1.length() << endl; // 11 a string mérete

String kiürítése – clear()

Ezzel lehet egy adott string tartalmát törölni. Írok egy példát, de szerintem egyértelmű a használata.

string s1 = "abrakadabra";
cout << s1 << endl; // abrakadabra

s1.clear();
cout << s1 << endl; // üres lesz

Üres-e a string – empty()

Logikai választ ad, hogy az adott string üres-e vagy sem. Üres a string abban az esetben, ha frissen deklaráltuk, és még nincs kezdőértéke, ha a “” literált kapta értékként, vagy ha kiürítettük.

string s1;
if( s1.empty() )
{
    cout << "A string ures." << endl;
}

s1 = "";
if( s1.empty() )
{
    cout << "A string ures." << endl;
}

s1 = "abrakadabra";
s1.clear();
if( s1.empty() )
{
    cout << "A string ures." << endl;
}

Stringek egyenlőségének vizsgálata – ==

A C++ nyelvben a stringek objektumok, de az egyenlőségvizsgálat nem olyan szigorú, mint a Java nyelvben. A C++ nyelvben két string egyenlőségének vizsgálata minden esetben a tartalmukat hasonlítja össze. Hiába két különböző objektumról van szó, melyek más memóriaterületen vannak, az egyenlőség vizsgálat operátor a tartalmuk alapján ad igaz vagy hamis értéket.

string s1 = "abc";
string s2 = "abc";
s1 += "d";
s2 += "d";

cout << (s1 == s2) << endl; // 1, vagyis true

Létrehozok két stringet. Mindkettőhöz hozzáfűzök egy karaktert. Az == operátor továbbra is azt mondja, hogy a két string egyenlő. Felhívnám a figyelmet arra, hogy az egyenlőség vizsgálatot zárójelekbe tettem. Enélkül a program fordítási hibával elszáll, ami valószínűleg a << (helyezd bele) operátorral kapcsolatos.

Stringhez hozzáfűzés – append(), +=

Sok esetben előfordul az, hogy egy stringet bővíteni kell. Van valamilyen kezdőértéke – akár a semmi – és ehhez hozzá kell fűzni valamilyen tartalmat. Ezt kétféleképp is megoldhatjuk. Az első gyakorlatilag nem is függvény, csak egy operátor. Lássuk az alábbi példát:

string s = "abc";
cout << s << endl; // abc
s = s + "defhg";
cout << s << endl; // abcdefgh

s += "ij";
cout << s << endl; // abcdefghij

Látható, hogy ha a stringhez “hozzáadok” egy másikat, akkor az első után hozzáfűzöm a másodikat. Itt működik az s = s + “valami” és az s += “valami” forma is, ahogy azt az operátoroknál már megtanulhattad.

Ez a hozzáfűzés azonban nem csak operátor szinten érhető el, hanem függvény szinten is. Lássuk a következő példát:

string s = "abc";
cout << s << endl; // abc
s.append("defhg");
cout << s << endl; // abcdefgh

s.append("ij");
cout << s << endl; // abcdefghij

Az eredmény ebben az esetben ugyanaz, mint az előbb, csak itt egy függvény oldja meg a hozzáfűzést. Nem tudom megmondani, mi értelme van kétféleképp megoldani a feladatot. A sebességmérések alapján talán a += megoldás egy hajszálnyival gyorsabb a függvényesnél, ami azonban az objektum orientált szemlélethez áll közelebb. Használd azt, amelyiket szeretnéd, de akkor használd következetesen és használd jól.

String adott karaktere – at(), []

Sokszor van arra szükség, hogy egy string karaktereit megvizsgáljuk. A string, mint ahogy említettem egy karakterlánc, karakterek tömbje. Azt is tudjuk, hogy a C++ nyelvben a string megváltoztatható, vagyis a karaktereit közvetlenül elérhetem, átírhatom. Ez arra is jó, hogy ezeken a karaktereken, mint egy tömbön végigmenjek egy ciklussal. Lássunk két példát arra, hogy hogyan írhatok ki egy stringet függőlegesen a konzolba, vagyis karakterenként egymás alá:

string s = "abrakadabra";
int meret = s.length();
for( int i = 0; i < meret; i++ )
{
    cout << s[i] << endl;
}
string s = "abrakadabra";
int meret = s.length();
for( int i = 0; i < meret; i++ )
{
    cout << s.at(i) << endl;
}

A kiemelt sorokban valami furcsaságot láthatsz. Erről ugyan nem beszéltem, de az egész típusokon belül léteznek olyanok, amelyek előjel nélküli (nem negatív) számokat tartalmazhatnak. A string mérete, a length() egy ilyen előjel nélküli eredményt ad. Amikor azonban direk módon a for ciklusba rakod bele a string méretét lekérdező függvényt, akkor a fordításkor egy figyelmeztetést (warning) kapsz, hogy a for ciklus egy előjeles számot várna. Ha azonban előtte egy int típusba berakod a length() függvény eredményét, akkor a meret változó már előjeles lesz, így a for figyelmeztetés nélkül tudja használni. Működni a figyelmeztetés ellenére is működik, de szerencsésebbnek tartom, hogy figyelmeztetés nélkül ezzel a kis trükkel használd a string méretét a bejárásához.

Vagy mi van akkor, ha egy string tartalmát fordítva kellene kiírni?

string s = "abrakadabra";
int meret = s.length();
for( int i = meret-1; i > -1; i-- )
{
    cout << s[i];
}
cout << endl;

Az előző példában természetesen a [] helyett a .at() függvényt is használhattam volna. Szintén kell egy adott string karaktere akkor, ha mondjuk bizonyos karaktereket mondjuk cserélni szeretnénk valami másra.

stringek összehasonlítása – compare()

stringek összehasonlítása – > = <

Ennél a függvénynél sokféle használati mód lehetséges. Lássuk ezeket külön-külön. Lássuk először a compare() függvényt.

Két string összehasonlítása – str1.compare(str2)

Itt alapvetően azt vizsgáljuk, hogy a két string egymáshoz képest hol helyezkedik el az abc rendnek megfelelően, amely valójában az ascii kódtábla alapján dolgozik. Ebben az esetben a compare függvény egy számot ad vissza, mely az egymáshoz képest elfoglalt pozíciójukat jelenti. Ezen használati mód formailag így néz ki:

// egyenlőségvizsgálat
string str1 = "abc";
string str2 = "abc";
cout << str1.compare(str2) << endl; // 0, vagyis egyenlőek

// nem-egyenlőségvizsgálat
str1 = "abc";
str2 = "abd";
cout << str1.compare(str2) << endl; // nem 0, vagyis nem egyenlőek

// pozíció egymáshoz képest
str1 = "abc";
str2 = "abf";
cout << str1.compare(str2) << endl; // -1, vagyis az első kisebb
                                    // előrébb van az abc-ben

// pozíció egymáshoz képest
str1 = "hij";
str2 = "def";
cout << str1.compare(str2) << endl; // 1, vagyis az első nagyobb
                                    // hátrább van az abc-ben

A megjegyzésekben szereplő dolgok (egyenlőség, nem egyenlőség, stb) valójában nem a használatot változtatják meg. Ha megnézed, akkor a vizsgálatban használt kód minden esetben ugyanaz. A különbség a visszatérési értékben van:

  • 0 – egyenlőek
  • nem 0 – nem egyenlőek
  • 1 az első hátrább szerepel abc rendben
  • -1 a második hátrább szerepel abc rendben

A második eset, amikor a visszatérési érték nem 0, az azt jelenti, hogy a két string nem egyenlő. Azt nem tudjuk melyik a “nagyobb”, csak azt, hogy nem egyenlőek. Az utolsó két eset pedig ezt a nem egyenlőséget bontja ketté. Az előrébb és hátrább van az abc-ben egy kicsit pongyola megfogalmazás. Valójában arról van szó, hogy az ASCII kódtáblában elfoglalt helyük alapján dönti ez a függvény, hogy melyik string előzi meg a másikat. A kódtáblában a nagybetűk megelőzik a kisbetűket! Ha a stringek eleje megegyezik, akkor megkeresi az első különböző karaktert és aszerint dönti el, hogy melyik string szerepel a másik előtt, és annak megfelelő végeredményt ad (lásd 3. példa).

Mi a helyzet akkor, ha a két string nem egyforma méretű, de a rövidebb string ugyanaz, mint a hosszabb string eleje? Lássuk az alábbi példát:

// pozíció egymáshoz képest nem azonos mérettel
str1 = "abc";
str2 = "abcd";
cout << str1.compare(str2) << endl; // -1, vagyis az első kisebb
                                    // előrébb van az abc-ben

És ha a két string szöveg szerint egyforma, de kis-nagybetű különbség van?

// pozíció egymáshoz képest nem azonos mérettel
str1 = "abc";
str2 = "Abc";
cout << str1.compare(str2) << endl; // 1, vagyis az első nagyobb
                                    // hátrább van az abc-ben

string részének összehasonlítása másik string-gel – str1.compare(honnan,meddig,mivel)

Ebben az esetben azt tudjuk megvizsgálni, hogy a string egy része megegyezik-e valamilyen mintával. Lássunk rá egy példát:

string str = "sargadinnye";

if( str.compare( 5, 6, "dinnye" ) == 0 )
{
    cout << "A sargadinnye egy dinnyefajta." << endl;
}

Ebben a példában annyi történik, hogy megvizsgáljuk az str-ben megadott stringben, hogy az 5-ös karaktertől kezdődő 6 hosszú szakasz összehasonlítva a “dinnye” szóval milyen eredményt ad. Ha az eredmény 0, az azt jelenti, hogy az str-ben megadott string része megegyezik a megadott mintával. A -1 és 1 visszatérési értékek szintén az abc rendben egymáshoz viszonyított eltérést jelentik.

két string adott részének összehasonlítása- str1.compare(honnan,meddig,mivel,honnan,meddig)

str1 = "granatalma";
str2 = "zoldalma";

if( str1.compare(str1.length()-4,4,str2,str2.length()-4,4) == 0 )
{
    cout << "Mindket szo vegen alma szo van." << endl;
}

A fenti példában azt vizsgálom meg, hogy mindkét szó végén ugyanaz részlet szerepel-e. Ebben a pédában kihasználom azt, hogy mindkét szó végén egy 4 betűs részletet keresek, ezért a honnan paraméternél a string hosszából kivonom a keresett részlet hosszát, így mindenképp a szó végén keresem a 4 hosszú részletet.

Jöjjön a relációs jeles összehasonlítás:

Az előzőhöz hasonlóan itt is összehasonlítunk, de itt a relációs jeleket használjuk, a válasz pedig minden esetben egy logikai érték.

// egyenlőségvizsgálat
string str1 = "abc";
string str2 = "abc";
cout << (str1 == str2) << endl; // 1 (igaz), vagyis egyenlőek

// egyenlőségvizsgálat
str1 = "abc";
str2 = "Abc";
cout << (str1 == str2) << endl; // 0 (hamis), vagyis nem egyenlőek

// nem-egyenlőségvizsgálat
str1 = "abc";
str2 = "abd";
cout << (str1 != str2) << endl; // 1 (igaz), vagyis nem egyenlőek

// pozíció egymáshoz képest
str1 = "abc";
str2 = "abf";
cout << (str1 < str2) << endl; // 1 (igaz), vagyis az első kisebb
                               // előrébb van az abc-ben

// pozíció egymáshoz képest azonos mérettel
str1 = "hij";
str2 = "def";
cout << (str1 > str2) << endl; // 1 (igaz), vagyis a második kisebb
                               // előrébb van az abc-ben

// pozíció egymáshoz képest nem azonos mérettel
str1 = "abc";
str2 = "abcd";
cout << (str1 < str2) << endl; // 1 (igaz), vagyis a második nagyobb
                               // hátrább van az abc-ben

// egyenlőség vizsgálat szövegben azonos, kis-nagybetű különbséggel
str1 = "abc";
str2 = "Abc";
cout << (str1 == str2) << endl; // 0 (hamis), vagyis nem egyenlőek

// pozíció egymáshoz képest
str1 = "klm";
str2 = "def";
cout << (str1 < str2) << endl; // 0 (hamis), de nem tudjuk, mi a helyzet

Az utolsó feladattal gond van. Ismét felhívnám a figyelmet: relációs jeles összehasonlításnál az eredmény igaz vagy hamis. De összehasonlításnál alapvetően 3 eset lehetséges: egyenlőek, az első nagyobb, a második nagyobb. Az utolsó feladatnál csak annyit tudunk a hamis eredményből, hogy az első biztosan nem kisebb (előrébb), de attól még lehet egyenlő, vagy nagyobb (hátrább) is. Ezt viszont egy vizsgálatból nem tudjuk eldönteni.

Amikor azonban igaz értéket kapunk válaszként egy > vagy < relációjeles összehasonlításra, akkor ott egyértelmű, hogy melyik a nagyobb vagy kisebb.

Beszúrás string-be – insert()

Szövegkezelési feladatainknál sokszor előfordul, hogy egy létező szövegbe kell beszúrnunk egy új részletet. Ennek is sokféle felhasználási módja van, én csak a legfontosabbakat említeném meg.

Beszúrás adott pozícióhoz- str.insert(hova,mit)

string str = "alma";
str.insert(0,"granat");
cout << str << endl; // granatalma

Ebben az esetben megadjuk, hogy pontosan hova szeretnénk beilleszteni a kívánt darabot. Ha a pozíció 0, akkor az értelemszerűen a string eleje lesz. Természetesen bárhova beszúrhatunk, lásd a következő példát:

string str = "acelmu";
str.insert(4,"henger");
cout << str << endl;

A beszúrás az eredeti tartalomból nem töröl, hanem kibővíti. És mi van akkor, ha a végére szeretnénk beszúrni? Akkor nyilván használhatjuk a hozzáfűzésnél tanultakat, vagy akár ezt is:

string str = "adat";
str.insert(str.length(),"bazis");
cout << str << endl;

String részének törlése – erase()

Többször előfordul az, hogy egy string részét törölni szeretnénk. Éppen meg lehetne oldani úgy is, hogy egy másik stringbe átrakjuk a megmaradó részeket, kihagyva a törlendő részt, de ettől a tényleges törlés azért jóval egyszerűbb. Törölni tudunk egyetlen karaktert is, vagy egy adott részt. Lássuk akkor, hogyan lehet ezt megtenni.

string str = "adatbazis-kezeles";
cout << str << endl; // adatbazis-kezeles

str.erase(4,5);      // adatbazis-kezeles
cout << str << endl; //     ^^^^^

str.erase(4,1);      // adat-kezeles
cout << str << endl; //     ^

str.erase(4);        // adatkezeles
cout << str << endl; //     ^^^^^^^

string részének kinyerése – substr()

Előfordulhat, hogy egy stringből ki kell szednünk egy kisebb részletet. Erre szolgál a substr() függvény.

Amikor egy részt akarunk kinyerni egy stringből, akkor meg kell mondanunk, hogy milyen karaktertől kezdődően akarom ezt megkapni, valamint hogy ettől a ponttól kezdve hány darabot. Melyiktől kezdjük, és mennyi karakter kell. Ha csak a kezdő pozíciót adjuk meg, akkor onnantól a string végéig az egészet megkapjuk. A substr() mindig string típusú eredményt ad vissza.

string s = "abrakadabra";
cout << s.substr(0,5) << endl; // abrak
cout << s.substr(2,3) << endl; // rak
cout << s.substr(5,3) << endl; // ada
cout << s.substr(6) << endl;   // dabra
cout << s.substr(s.length()) << endl; // mindig üres string

String adott részének cseréje – replace()

Ha a feladat úgy kívánja, lehetőségünk van a string bizonyos részeinek cseréjére. Természetesen ez is megoldható lenne, hogy az adott részt töröljük, majd beszúrjuk oda a kivánt részt, de ettől természetesen van egyszerűbb lehetőség is. A csere nem csak azonos méretű részek esetén működik, egy kisebb részt cserélhetsz nagyobbra is, akkor a string mérete megnő. Fontos azonban tudni, hogy a csere nem szöveg részleteket keres, hanem a pozíciókat kell megadni, hogy mettől-meddig cserélsz, nem úgy, hogy mit cserélsz és mire. A replace függvénynek sokféle használati módja létezik, én most csak a legfontosabbakat mutatom be példákon keresztül.

string str = "Ez egy nagyon hosszu mondat.";
cout << str << endl;       // Ez egy nagyon hosszu mondat.

str.replace(7,13,"rovid"); // Ez egy rovid mondat.
cout << str << endl;

str.replace(7,6,"");      // Ez egy mondat.
cout << str << endl;      // akár törlésre is...

str.replace(13,1,"?");    // Ez egy mondat?
cout << str << endl;

str.replace(3,3,"kerdo"); // Ez kerdo mondat?
cout << str << endl;

Keresés string-ben – find(), rfind(), find_first_of(), find_last_of(), find_first_not_of(), find_last_not_of()

Mi van akkor, ha valamilyen okból tudni szeretnénk, hogy az adott string-ben előfordul-e valamilyen részlet, és ha benne van, azt is tudni szeretnénk, hogy pontosan hol. Ennek több oka is lehet. Szeretnénk oda beszúrni valamit, ki szeretnénk törölni a megtalált részt, esetleg ki szeretnénk cserélni valami másra. Önmagában a keresés is előfordulhat persze, de legtöbbször kombináljuk valami más művelettel. A keresés minden esetben a keresett rész pozícióját adja meg. Ha mondjuk az eredmény 0, akkor az azt jelenti, hogy a string elején találtuk meg. Azt láthatod, hogy több kereső függvényt is felírtam. Lássuk akkor sorban, először felsorolás jelleggel, hogy melyik mit is csinál.

  • find – megkeresi az adott rész első előfordulásának kezdetét
  • rfind – megkeresi az adott rész utolsó előfordulásának kezdetét
  • find_first_of – megkeresi a felsorolt karakterek közül az első előfordulását
  • find_last_of – megkeresi a felsorolt karakterek közül az utolsó előfordulását
  • find_first_not_of – megkeresi az első nem felsorolt karaktert
  • find_last_not_of – megkeresi az utolsó nem felsorolt karaktert
string s = "abrakadabra";
cout << s << endl;
cout << s.find("br") << endl;               // 1
cout << s.rfind("br") << endl;              // 8
cout << s.find_first_of("kd") << endl;      // 4
cout << s.find_last_of("bd") << endl;       // 8
cout << s.find_first_not_of("abr") << endl; // 4
cout << s.find_last_not_of("bra") << endl;  // 6
cout << s.find("bela") << endl;             // 4294967295 ?

Talán az utolsó szorul csak magyarázatra. Amikor nem találja meg benne a keresett string-et, akkor egy meglehetősen furcsa értéket kapunk. Ha megnézzük a példát, láthatjuk, hogy a “bela” szövegrész nem található meg az eredeti string-ben. Jól sejtheted, hogy ezzel lehet kapcsolatban a dolog.

A string, ahogy fent említettem, karakterek tömbje. A kapott eredmény a legnagyobb létrehozható string mérete. A string karaktereinek indexelése ugyanúgy 0 értékkel kezdődik, mint a tömböknél, vagyis az eredmény, mint méret, soha nem lehet valódi index. Hasonlóan, mint a tömböknél: a tömb mérete nem lehet index.

A string-nek van egy npos nevű adata, amit kiolvashatunk belőle. Az npos pontosan azt az értéket jelenti, hogy mekkora lehet a legnagyobb létrehozható string, vagyis ez indexként soha nem fordulhat elő. Ha egy keresés eredménye az npos-ban tárolt számmal egyenlő, akkor a keresett rész nem található meg a string-ben, ha nem egyenlő az npos értékével, akkor megtalálható benne.

string s = "abrakadabra";
if( s.find("zabra") != s.npos )
{
    cout << "A zabra resz megtalalhato a string-ben." << endl;
}
else
{
    cout << "A zabra resz NEM talalhato meg a string-ben." << endl;
}

Eljutottál az alap leckék végére. Ha jól dolgoztál, akkor az ezekben található tananyagot megfelelően alkalmazni is tudod. Jöjjenek akkor a haladó leckék.

Következő lecke: Saját függvények

2 Replies to “C++ programozás 16. – String”

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük

*

Ez a weboldal az Akismet szolgáltatását használja a spam kiszűrésére. Tudjunk meg többet arról, hogyan dolgozzák fel a hozzászólásunk adatait..