STL és iostream gyorstalpaló

Czirkos Zoltán · 2019.04.14.

STL és iostream gyorstalpaló

A C++ tartalmaz csomó olyan adatszerkezetet és osztályt, amire minden programnak szüksége lehet. Ilyenek a sztring, a duplán láncolt lista, a dinamikus tömb és még sok egyéb. Ezt az osztály gyűjteményt STL-nek, "Standard Template Library"-nek hívják. A programozást nagyban megkönnyítik, mert ezek a C++-ban szabványosak, bármikor használhatóak, nem kell őket újra megírni.

Ezeknek az osztályoknak a részletes megértéséhez szükség van eddig nem bemutatott C++ nyelvi elemek ismeretére is (pl. osztály sablonok), ezért általában a félév végén szoktak szerepelni a tananyagban. Az egyszerűbb felhasználáshoz azonban, amilyen például a nagy házi feladatokhoz is elegendő, ez nem olyan lényeges még.

A házik közül a legtöbb feladat nem valamilyen alapvető osztály létrehozására koncentrál, és ilyenkor igazából nem éri meg gürcölni a saját, kézzel megvalósított dinamikus sztringgel (char* rémálom), saját dinamikus tömbbel stb. Hogy az energia ne ezeknek a megvalósítására menjen el, röviden leírom, milyen beépített tároló osztályokat ad a C++. Ennek az irománynak az áttanulmányozása után sokkal könnyebb lesz a házit elkezdeni.

Az STL részletesebb dokumentációja elérhető pl. a http://cplusplus.com helyen.

Megj.: az osztályok mind az std névtérben vannak; én mindig ki is írom ezt. Nem szeretek using namespace-elni (vannak hátrányai), de ez ízlés kérdése.

1. std::string, a beépített string típus

Dinamikusan átméreteződő, okos string, az összes elképzelhető operátora átdefiniálva.

#include <string>

std::string s;      // Üres stringet hozunk létre.
                    // A méretével nem kell foglalkozni.
std::string m("Hello vilag!");
std::string a("alma"), k("korte");

s = "Hello";        // Beállítjuk valamilyen értékre.
s += " vilag!";     // Hozzátoldunk valamit.
std::cout << s;     // Kiírjuk a konzolra.

if (s == m)         // Összehasonlítjuk.
    std::cout << "egyformák!";
if (a < k)
    std::cout << a << " (kvázi) előbb van az ABC-ben.";

s[0] = 'h';             // hello vilag!
s.erase(2, 3);          // Töröl a belsejéből: "he vilag!"
s = m.substr(2, 3);     // m belsejéből egy darab, "llo"
int i = a.find("lm");   // Keresés: 1, mert "alma" szóban "lm" pozíciója
a.insert(3, "er");      // Beszúrás: "alma"-ból almera lesz

2. std::vector, átméretezhető, egydimenziós tömb

Ez egy osztály sablon. A tömb bármilyen elemeket tartalmazhat; a tartalmazott típusokat <> között kell megadni. Például egész számokat tartalmazó tömb: std::vector<int>.

#include <vector>

std::vector<int> t;         // Tömb, ami egyelőre üres.
std::vector<double> d(50);  // 50 elemű double tömb.

d[23] = 3.14;
t.push_back(1);             // A tömb végére rak egy új számot,
t.push_back(2);             // és meg is növeli a tömb méretét.

for (int i = 0; i < t.size(); ++i)  // Kiírjuk a tömb tartalmát,
    std::cout << t[i] << ' ';       // az aktuális méretét a size() adja meg.
std::cout << std::endl;
t.clear();                  // Kitörli a tömböt, újra üres lesz.

d[60] = 10.3;               // NEM SZABAD!!! Továbbra is 0..49 között
                            // indexelődik az 50 elemű tömb.
d.at(120) = 0.012;          // Ugyanaz, mint az indexelő, csak
                            // ellenőrzi, hogy nem hivatkozunk-e
                            // nem létező indexre. Ha igen, kivételt dob.

#include <algorithm>        // rendezéshez, std::sort
#include <cstdlib>          // rand() véletlenszámokhoz

t.resize(100);              // Méretezzük át a tömböt 100 eleműre.
srand(time(0));             // Véletlenszám-generátor indítása
for (int i = 0; i < t.size(); ++i)
    t[i] = rand() % 200;    // Minden elem: véletlenszám 0..199 között
std::sort(t.begin(), t.end());      // Növekvő sorrendbe az egész
                                    // tömb, az elejétől a végéig.
for (int i = 0; i < t.size(); ++i)
    std::cout << t[i] << ' ';
std::cout << std::endl;

// Érdemes abba belegondolni, hogy az egyszerű másolás,
// mint lehetőség, illetve a méret az adatokkal
// történő egységbe zárása miatt már sokszor megéri
// std::vectort használni sima tömb helyett.
// Például nem kell átadnunk egy függvénynek a tömb
// méretét, mert az már tartalmazza azt:
double atlag(const double *tomb, int meret);
double atlag(const std::vector<double> &tomb);

// Egy függvény, amely valahány (nem kell tudni
// előre, mennyi) stringgel tér vissza:
std::vector<std::string> beolvas();

3. std::list, kétszeresen láncolt lista osztály

Tetszőleges típusú adattagokkal.

#include <list>
#include <cctype>

std::list<std::string> szavak;  // stringekből álló lista (!)
szavak.push_back("alma");       // alma  (... került az üres lista végére)
szavak.push_back("korte");      // alma, korte
szavak.push_front("málna");     // málna, alma, korte   (elejére teszi!)
szavak.push_front("villa");     // villa, málna, alma, korte
std::cout << szavak.size() << " szót tartalmaz.\n";
szavak.remove("villa");         // kitöröljük a kakukktojást
std::cout << "Az első elem: " << szavak.front() << std::endl;
std::cout << "Az utolsó elem: " << szavak.back() << std::endl;

std::list<std::string> masolat;
masolat = szavak;                   // Lemásoljuk a listát: értékadás!
while (!masolat.empty()) {          // Amíg nem üres a lista:
    std::cout << masolat.back() << ' '; // Kiírjuk az utolsó elemet...
    masolat.pop_back();                 // és kitöröljük.
}

Az egyes tároló osztályokhoz létezik ún. iterátor osztály, amely legegyszerűbb esetben arra jó, hogy az összes adaton egy ciklussal végig tudjunk menni. Az iterátoron értelmezett két legalapvetőbb művelet a ++ (következő elemre ugrás) és a * (jelenlegi elem elérése). Ebből a szempontból ugyanúgy működnek, mint a pointerek, csak sokkal okosabbak azoknál! Figyeljük meg, hogy a lista belső felépítéséről semmit nem tudunk, ez el van rejtve előlünk. Az iterátor viszont együtt dolgozik a listával; tudja, hogy a lista belsejében mi hogyan van megoldva. De azt nekünk nem kell tudni. Legyen az az iterátor dolga.

std::list<std::string>::iterator it;    // Sztringekből álló lista iterátora.
// Indulunk a lista elejéről (begin). Addig megyünk, amíg
// el nem érjük a lista végét (end). Amikor egy adott elemmel
// elvégeztük a dolgunkat, ugrunk a következőre (++). Ismerős?
for (it = szavak.begin(); it != szavak.end(); ++it) {
    char & c = (*it)[0];        // Az iterátor által mutatott sztring
                                // kezdő karakterére referencia.
    c = toupper(c);             // nagybetűsítjük az első karakterét
    std::cout << *it << ' ';    // kiírjuk a szót.
}

4. std::set, halmaz osztály

Bármilyen típusokat képes tárolni. Adott értékről meg tudja mondani, szerepel-e benne.

Az iterátorok itt is használhatóak. (És egyébként az összes tárolónál használhatóak és ugyanígy működnek! Vektornál, listánál, halmaznál, mindenhol. Nem kell foglalkoznunk azzal, hogyan néznek ki belülről!) Az egész számokat tartalmazó halmaz iterátorát lent a ciklus fejlécében deklarálom, a for (int i=0...) mintájára természetesen ezt is lehet.

#include <set>

std::set<int> s;            // Egész számok halmaza

std::cout << "Írj be számokat, 0=vége!" << std::endl;
int i;
std::cin >> i;
while (i != 0) {
    if (s.find(i) != s.end()) // Így jelzi, ha nincs benne
        std::cout << "Ez már benne volt!\n";
    else                    // Berakjuk a halmazba a kapott számot.
        s.insert(i);        // Természetesen, ha már benne volt,
                            // amúgy se történne semmi.
    std::cout << "Most " << s.size() << " szám van a halmazban.\n";

    std::cout << "Kérem a következőt!\n";
    std::cin >> i;
}


std::cout << "A halmaz elemei: ";
for (std::set<int>::iterator it = s.begin(); it != s.end(); ++it)
    std::cout << *it << ' ';

5. std::map, asszociatív tömb

Értékeket képez le. Mintha egy tömb lenne, amit nem számmal kell indexelni.

#include <map>

// Sztringeket képezünk le egész számokra;
// vagyis minden sztringhez rendelünk egy egész számot.
std::map<std::string, int> m;

m["Ernőke"] = 5;        // Mintha a tömböt indexelnénk, csak épp sztringgel.
m["Pistike"] = 7;
m["Orsika"] = 4;

std::cout << "Ernőke, még csak " << m["Ernőke"] << " éves vagy." << std::endl;

// Vigyázni kell, hogy ha olyan névvel indexeljük a map-et, ami még
// nem szerepel benne, akkor létrejön az az elem! És kap kezdeti
// értéket is, ami intek esetén nulla.
//      std::cout<<m["NincsIlyen"]<<std::endl;
// Ez a sor nullát írna ki, és létrehozna egy NincsIlyen nevű embert!!!
// Ha ellenőrizni szeretnénk, hogy van-e benne, akkor a find
// függvény használható.
std::cout << "Kinek szeretnéd tudni a korát? ";
std::string s;
std::cin >> s;
if (m.find(s) != m.end())
    std::cout << s << ' ' << m[s] << " éves." << std::endl;
else
    std::cout << "Nincs ilyen név, hogy " << s << "!" << std::endl;

// Persze itt is lehet iterátorunk, viszont nem csak az értéket, hanem
// a kulcsot is látjuk. it->first a kulcs, it->second pedig az érték,
// mert az std::map belül std::pair elemeket tárol.
// Sztringeket egészekre leképező map iterátora:
std::map<std::string, int>::iterator it;
for (it = m.begin(); it != m.end(); ++it)
    std::cout << it->first << " még csak " << it->second << " éves." << std::endl;

6. iostream, fstream, ...

A C++ fájl műveleteit megvalósító osztályai. Jól kombinálhatók az std::stringgel.

#include <fstream>
#include <iomanip>      // manipulátorok, lásd lent

std::ifstream is;       // input file stream, vagyis olvasunk egy fájlból

is.open("fajl.txt");    // megnyitjuk a fájlt
if (!is) {
    std::cerr << "Nincs ilyen fájl!";
    // ... meg csinálunk is valami értelmeset ilyenkor
}

// Beolvasunk egy teljes sort. A string szükség szerint átméreteződik!
std::string s;
int i = 0;
while (getline(is, s))
    // Számozva kiírjuk a sorokat. std::setw manipulátor: az utána
    // következő kiírt dolgot 5 karakter szélességűre veszi.
    // Mintha printf %5d lenne.
    std::cout << std::setw(5) << ++i << ". sor: " << s << std::endl;
is.close();

7. Komplexebb példa

Számoljuk meg, egy szövegfájlban melyik szó hányszor szerepel.

#include <iostream>
#include <map>
#include <string>
#include <fstream>

int main()
{
    std::ifstream is("fajl.txt");   // konstruktor egyből meg is nyitja
    if (!is) {
        std::cerr << "Nem lehet megnyitni!" << std::endl;
        return 1;
    }

    std::string s;
    std::map<std::string, int> m;   // sztringeket képzünk le egészekre

    // Beolvasunk egy szót...
    while (is >> s) {
        // A map megfelelő számát növeljük eggyel. Ha még
        // nem volt olyan, létrejön 0 értékkel, és az növelődik.
        m[s]++;
    }

    std::map<std::string, int>::iterator it;
    for (it = m.begin(); it != m.end(); ++it) {
        std::cout << it->second << "x szerepelt ez: ";
        std::cout << it->first << std::endl;
    }
}