[itk] Számítástechnika kezdőknek

C++ programozás kezdőknek - stringek, stringműveletek

[2021. december 14.] [ christo161 ]

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

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.

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.

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';
}

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, egy tömbbe elhelyezve őket pedig a 0 és a 93 közötti indexű elemeket kapjuk.

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);

  //ascii characters from ! to ~ (from 33 to 126 = 94)
  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

A bejegyzés trackback címe:

https://itkezdoknek.blog.hu/api/trackback/id/tr9916773778

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.
süti beállítások módosítása