Ebben a tananyagrészben a stringekről, azaz a több karakterből álló értékekről vagy változókról lesz szó. A stringeket szokták karakterláncnak, esetleg füzérnek is nevezni.
Előző tananyagrész: kétdimenziós tömbök
Következő tananyagrész: string-szám, szám-string konverziók
Tartalom
- Mikor melyik stringet használjuk?
- string műveletek:
összefűzés, méret/hossz, string egy karakterének módosítása, üres-e, egyenlőek-e, keresés a stringben, substr, insert, split, join, reverse, random string, trim
Alapvető tudnivalók
A C++ nyelvben a stringek technikailag karakterek tömbjei. A tömbökhöz hasonlóan, egy string karakterei a számítógép memóriájában egymás után vannak tárolva.
Bárki, aki egy kicsit jobban beleássa magát a C++ nyelv tanulásába, könnyen találkozhat azzal az állítással, hogy a stringkezelés a C++ nyelvben bonyolult. Ennek három oka van.
- blogs.msmvps.com - How many Strings does C++ have?
- CppCon 2015: Herb Sutter Writing Good C++14 (videó, 22:47-nél)
Egyrészt a C++ nyelvben a string literálok, az std::string típusú változók vagy a C nyelvből átvett karaktertömbök különböző típusúak (ez mondjuk Java vagy Python nyelvekben nem így van, ott az összes string egyforma típusú).
Például ebben az utasításban a string literál const char[14] típusú, nem pedig std::string típusú:
std::cout << "Hello World!\n";
Ha használjuk az auto kulcsszót típuskikövetkeztetéshez, ez zavaró lehet, mert például ebben az utasításban a string_1 nevű változó típusa nem std::string lesz:
//error
auto string_1 = "Hello World!\n";
Ez a C++14 vagy újabb szabvány szerint értelmezett kódban kiküszöbölhető, ha std::string literálokat használunk:
#include <iostream>
#include <typeinfo>
using namespace std::string_literals;
int main() {
//string literal:
auto string_1 = "Hello, world!\n";
//std::string literal:
auto string_2 = "Hello, world!\n"s;
std::cout << "type of string_1: \n" << typeid( string_1 ).name() << '\n';
std::cout << "type of string_2: \n" << typeid( string_2 ).name() << '\n';
}
Másrészt a C++ nyelvben egy string literálnak vagy egy std::stringnek megadhatjuk, hogy milyen legyen a karakterkódolása.
Harmadrészt a C++ nyelv szabványosítása előtt sok függvénykönyvtár vagy keretrendszer elkészítette a saját string típusát, amelyek nem tűntek el az std::string megjelenésével.
- CString (Microsoft)
- FBString (Facebook)
- GString (GTK+)
- OUString (LibreOffice)
- QString (Qt)
- wxString (wxWidgets)
Mikor melyik stringet használjuk?
string literálok
A string literálokat akkor használjuk, ha valamilyen szöveget ki akarunk íratni (anélkül, hogy egy változóban eltárolnánk), vagy egy std::string típusú változónak értéket szeretnénk adni.
std::cout << "Hello World!\n";
std::string string_1 = "Hello World!";
std::string
Az std::string típusú változókat stringek tárolására használjuk, általában akkor, ha egy stringre többször szeretnénk hivatkozni (pl. műveleteket szeretnénk végezni vele).
Ne felejtsük el, hogy az std::string nem a C++ nyelv része, hanem a C++ standard library string nevű header fájlját kell includeolnunk, ahhoz, hogy használhassunk std::stringeket.
Az std::string valójában egy osztály, és minden egyes std::string típusú változó egy objektum. A kulisszák mögött az std::string is egy tömbszerű adatszerkezet, de vannak segédfüggvényei, amelyek megkönnyítik a használatát.
A memóriakezelése is automatizált, például ha egy hosszabb szöveget szeretnénk benne tárolni, mint az addigi értéke, akkor nem nekünk kell nagyobb memóriaterületet lefoglalni, mint a C stringek esetén.
karaktertömbök, char*
C stringeknek, esetleg raw stringeknek szokták őket nevezni.
A karaktertömbök használata C++ nyelvben kerülendő, de mások kódjában jó eséllyel találkozhatunk velük és a kezelésükre való függvényekkel (strlen, strcpy, strcmp, strcat).
std::string_view
Egy std::string_view-t jellemzően akkor használunk, ha egy C stringet std::stringként szeretnénk kezelni (például ha egy C stringet adunk át egy függvénynek, de a függvényen belül std::stringként szeretnénk kezelni).
string műveletek
Összefűzés (konkatenáció)
string literálok esetén:
Egymás után lévő string literálok összefűzésre kerülnek. Tehát ezen utasítások ugyanazt eredményezik:
std::cout << "abc" "123\n";
std::cout << "abc123\n";
std::cout << "abc" << "123\n";
A fenti példában akár külön sorba is írhatnánk a két string literált, hosszú string literálok esetén ez akár sortördeléshez is hasznos lehet:
std::cout << "abcdefghijklmnopqrstuvwxyz0123456789\n"
"abcdefghijklmnopqrstuvwxyz0123456789\n";
A string literálok összefűzése akkor is így működik, ha egy std::string típusú változónak adjuk értékül az összefűzött string literált:
std::string str = "abc" "123";
std::cout << str << '\n';
Egy string literált és egy karaktert ily módon összefűzni nem lehet:
//error
std::string str = "abc" '\n';
std::string típusú változók esetén:
std::string típusú változók összefűzéséhez a + operátort használhatjuk.
std::string str1 = "abc";
std::string str2 = "123";
std::string str3 = str1 + ", " + str2 + '\n';
A + operátort stringek összefűzésére csak akkor használhatjuk, ha az összefűzendő dolgok között legalább az egyik std::string típusú.
//error
std::string str = "abc" + "123";
számok és stringek összefűzése
C++ nyelvben nincs implicit konverzió stringek és számok között, vagyis hibát okoz, ha egy számot stringgel szeretnénk összefűzni.
//error
#include <iostream>
#include <string>
int main() {
int num = 42;
std::string str = "hello";
std::cout << str + ' ' + num;
}
Amennyiben számokat szeretnénk stringekkel összefűzni, használjuk az std::to_string függvényt.
//string + num
#include <iostream>
#include <string>
int main() {
int num = 42;
std::string str = "hello";
std::cout << str + ' ' + std::to_string(num);
}
Méret, hossz
string literálok esetén:
Használhatjuk a C++ standard library cstring header fájljában található strlen függvényt:
#include <iostream>
#include <cstring>
int main() {
std::cout << strlen("Hello World!\n") << '\n';
}
Esetleg a sizeof operátort:
#include <iostream>
int main() {
std::cout << sizeof("Hello World!\n")-1 << '\n';
}
Ez utóbbi esetén az eredmény eggyel nagyobb lesz, mint az strlen által visszaadott eredmény, mivel a sizeof a string literálok végén lévő lezárókaraktert ('\0') is beleszámolja a méretbe, így ki kell vonnunk egyet a sizeof által visszaadott eredményből, hogy megkapjuk a tényleges méretet.
std::string típusú változók esetén:
A size vagy length tagfüggvényt használhatjuk (mindkettő teljesen ugyanúgy működik):
#include <iostream>
#include <string>
int main() {
std::string str = "abc123";
std::cout << "the size of str: " << str.size() << '\n';
}
#include <iostream>
#include <string>
int main() {
std::cout << "the size of \"Hello World!\\n\": " << std::string("Hello World!\n").size() << '\n';
}
#include <iostream>
#include <string>
int main() {
std::cout << "Kerem irjon be valamilyen szoveget:\n";
std::string str;
getline(std::cin, str);
std::cout << "A megadott szoveg merete: " << str.size() << '\n';
}
Fontos: a stringek hosszát, méretét bájtban kapjuk meg, ami nem mindig a karakterek számát jelenti. Például az ékezetes karakterek értéke több bájt lehet.
//size is bigger than number of chars
#include <iostream>
#include <string>
int main() {
std::string str = "áéíóöőúüű";
std::cout << str.size() << '\n';
}
Ha úgynevezett wstringet (esetleg u16stringet vagy u32stringet) használunk a karakterek tárolásához, akkor a size és length tagfüggvények akkor is a karakterek számát adják vissza, ha vannak ékezetes karakterek a stringben:
//wstring size
#include <iostream>
#include <string>
int main() {
std::wstring str = L"áéíüöőúüű";
std::cout << str.size() << '\n';
}
- stackoverflow.com - how to get number of characters in std::string
- stackoverflow.com - std::wstring vs std::string
A string egy karakterének módosítása
A C++ nyelvben az std::string típusú változók karaktereit egyszerű értékadással módosíthatjuk (ezzel ellentétben Javaban ez például nem így van, ott a StringBuilder osztály van ebben a segítségünkre).
Csakúgy mint az std::vector esetén, az [] operátor esetén a túlindexelés undefined behaviourt okozhat, az at() tagfüggvény viszont kivételt dob, ha túlindexelés történt.
Példák:
#include <iostream>
#include <string>
int main() {
std::string str_example = "hello";
std::cout << str_example << '\n';
//modify the first character from h to H
str_example.at(0) = 'H';
std::cout << str_example << '\n';
//modify the last character from o to zero
str_example.at(str_example.size()-1) = '0';
std::cout << str_example << '\n';
}
Üres-e?
Egy std::string típusú változóról az empty tagfüggvénnyel deríthetjük ki, hogy az éppen aktuális értéke üres string-e. Ez például elágazások és ciklusok esetén lehet hasznos.
#include <iostream>
#include <string>
int main() {
std::cout.setf(std::ios::boolalpha);
std::string str_example;
std::cout << "str_example is empty? " << str_example.empty() << '\n';
str_example = "something";
std::cout << "str_example is empty? " << str_example.empty() << '\n';
str_example = "";
std::cout << "str_example is empty? " << str_example.empty() << '\n';
}
Egyenlőek-e?
Vannak olyan programozási nyelvek, ahol az egyenlőségvizsgálat operátor nem használható stringek értékeinek egyenlőségvizsgálatára, például a Java és C# nyelvekben erre egy külön tagfüggvény, az Equals() tagfüggvény használható.
C++ nyelvben viszont az egyenlőségvizsgálat operátort használhatjuk, ha meg akarjuk vizsgálni, hogy két std::string értéke egyezik-e.
//std::string ==
#include <iostream>
#include <string>
int main() {
std::cout.setf(std::ios::boolalpha);
std::string str_example1 = "abc";
std::string str_example2 = "def";
std::cout << "is str_example1 equals to str_example2?\n" << (str_example1 == str_example2) << '\n';
str_example2 = "abc";
std::cout << "is str_example1 equals to str_example2?\n" << (str_example1 == str_example2) << '\n';
}
Keresés a stringben
Az std::string típusú változók esetén a find() tagfüggvény std::string::npos értéket (a lehető legnagyobb index érték) ad vissza, ha az adott stringben nem találta meg a neki paraméterül adott értéket.
//search in std::string
#include <iostream>
#include <string>
int main() {
std::string str_example = "abcdefghijklmno";
if (str_example.find("def") != str_example.npos) {
std::cout << "found\n";
} else {
std::cout << "not found\n";
}
}
substr
A substr() tagfüggvényt egy std::string bizonyos szakaszának felhasználásához használhatjuk.
Az alábbi példában a str_example1.substr(1,3) az str_example1 változóban tárolt szövegből a második karaktertől kezdve 3 darab karakternyi szöveget ad vissza, az str_example2.substr(str_example2.size()-3, 3) pedig az str_example2 változóban tárolt szövegből az utolsó 3 karaktert.
Első paraméter: a string hanyadik karakterétől kezdve (a 0 jelenti az első karaktert)
Második paraméter: hány darab karaktert használjunk fel
#include <iostream>
#include <string>
int main() {
std::string str_example1 = "abdcefghijkl";
std::string str_example2 = "abdcefghijkl";
std::cout << str_example1.substr(1,3) + ' ' + str_example2.substr(str_example2.size()-3, 3) << '\n';
}
insert
Az insert() tagfüggvényt valamilyen szöveg beillesztéséhez használhatjuk egy std::string már meglévő értékébe.
Az alábbi példában az str_example1.insert(1,str_example2); utasítás az str_example1 változó értékébe a második karaktertől kezdődően beilleszti az str_example2 értékét.
Első paraméter: hanyadik karaktertől kezdődően (a 0 jelenti az első karaktert)
Második paraméter: mit illesszünk be (string literál vagy std::string)
#include <iostream>
#include <string>
int main() {
std::string str_example1 = "abdcefghij";
std::string str_example2 = "0123456789";
std::cout << "str_example1: " << str_example1 << '\n';
str_example1.insert(1,str_example2);
std::cout << "str_example1: " << str_example1 << '\n';
}
Ezekhez hasonló tagfüggvények az erase, replace, append és assign.
std::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.
split
A split műveletben egy stringet valamilyen elválasztókarakter (általában szóköz) mentén feldarabolunk, és az egyes szavakat egy tömb egyes elemeinek adjuk értékül.
Más programozási nyelvekben a tömbök új elemekkel bővíthetőek, a C++ nyelvben erre az std::vectort használhatjuk.
//split example
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
int main() {
//init
std::string string_example = "The quick brown fox jumps over the lazy dog";
char delimiter = ' ';
std::vector<std::string> words;
std::stringstream sstream_example(string_example);
//split
std::string word;
while (getline(sstream_example, word, delimiter)) {
words.push_back(word);
}
//output
for (const std::string& element : words) {
std::cout << element << '\n';
}
}
join
A join műveletben egy tömb vagy egy std::vector elemeit fűzzük össze egy stringbe, elválasztókarakterekkel (általában szóközzel). A mondat végére pontot is tehetünk.
//join example
#include <iostream>
#include <string>
#include <vector>
int main() {
//init
std::vector<std::string> words = {"The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"};
//join
std::string result;
for (int i = 0; i < words.size()-1; ++i) {
result += words.at(i) + ' ';
}
result += words.at(words.size()-1) + '.';
//output
std::cout << result << '\n';
}
reverse
std::string megfordítása
1. módszer: std::reverse függvény
//std::reverse
#include <iostream>
#include <string>
#include <algorithm>
int main() {
std::string str = "something";
std::reverse(str.begin(), str.end());
std::cout << str << '\n';
}
2. módszer: mivel az std::string tömbszerű adatszerkezet, ezért csakúgy mint a tömbök elemeit, az std::stringek elemeit is feldolgozhatjuk for_each függvénnyel. Reverse iterátorok segítségével elvégezhetjük a fordítva kiíratást.
//string reverse
#include <iostream>
#include <string>
#include <algorithm>
int main() {
std::string str_example = "something";
std::for_each(
str_example.rbegin(), str_example.rend(), [](const char& element) {std::cout << element;}
);
std::cout.put('\n');
}
3. módszer: std::for_each függvény és reverse iterátorok segítségével egy másik std::string típusú változóba előállítjuk a megfordított stringet.
//std::for_each example
#include <iostream>
#include <string>
#include <algorithm>
int main() {
std::string str_example = "something";
std::string str_reverse = "";
std::for_each(
str_example.rbegin(), str_example.rend(), [&str_reverse](const char& element) {str_reverse += element;}
);
std::cout << str_reverse << '\n';
}
random string
Egy 10 és 20 közötti hosszúságú random stringet generálunk ebben a példában.
Először a random generátort beállítjuk egy 10 és 20 közötti számértékhez, valamint egy 0 és 93 közötti számértékhez. A 10 és 20 közötti szám a string hosszát adja meg, a 0 és 93 közötti szám pedig úgy jön ki, hogy 33 és 126 között vannak a kiírható ascii karakterek, ami 94 darab (vagyis 1-től 94-ig), egy tömbbe elhelyezve őket pedig a 0 és a 93 közötti indexű elemeket kapjuk, mivel a tömbök indexelése nem 1-től, hanem 0-tól kezdődik.
A karaktereket egy std::vectorba generáljuk le az std::iota függvénnyel, ami azt csinálja, hogy egy kezdőértéktől egyessével növelve feltölt egy tömböt. Vagyis ha a ! a kezdőérték és a tömb 94 elemű, akkor pont a ~ lesz az utolsó elem (a második elem ", a harmadik elem #, satöbbi).
Az std::stringet a létrehozása után átméretezzük egy 10 és 20 közötti random generált számnak megfelelő méretre.
Miután átméreteztük a stringet, egyessével feltöltjük, mégpedig oly módon, hogy a 0 és 93 közötti random generált szám az ascii karakterekkel feltöltött std::vector indexét adja meg, és ezt az indexet felhasználva kiválasztunk egy elemet az ascii karakterek tömbjéből, amit értékül adunk a string soron következő karakterének.
//random string
#include <iostream>
#include <random>
#include <string>
#include <numeric>
#include <vector>
int main() {
//random generator
std::random_device rnd_device;
std::mt19937 rnd_generator(rnd_device());
std::uniform_int_distribution<int> int_dist_str_size(10,20);
std::uniform_int_distribution<int> int_dist_rnd_chars(0,93);
//generate ascii characters from ! to ~ (from 33 to 126 = 94 elements)
std::vector<char> characters;
characters.resize(94);
std::iota(characters.begin(), characters.end(), '!');
//random string definition and set size to rand between 10 and 20
std::string random_string;
const int string_size = int_dist_str_size(rnd_generator);
random_string.resize(string_size);
//set random characters for the string
for (char& element : random_string) {
element = characters.at(int_dist_rnd_chars(rnd_generator));
}
//output
std::cout << random_string << '\n';
}
trim
A trimmelés azt jelenti, hogy egy string elejéről és végéről eltávolítjuk a szóközöket.
//trim
#include <iostream>
#include <string>
#include <algorithm>
void ltrim(std::string &p_str) {
p_str.erase(
p_str.begin(), std::find_if(p_str.begin(), p_str.end(), [](int char_element) { return !std::isspace(char_element);}
));}
void rtrim(std::string &p_str) {
p_str.erase(
std::find_if(p_str.rbegin(), p_str.rend(), [](int char_element) { return !std::isspace(char_element); }
).base(), p_str.end());
}
int main() {
std::string str_example = " str ";
std::cout << "Before trim:\n" << str_example << '\n';
ltrim(str_example);
rtrim(str_example);
std::cout << "After trim:\n" << str_example << '\n';
}
Egyéb tananyagok
Előző tananyagrész: kétdimenziós tömbök
Következő tananyagrész: string-szám, szám-string konverziók