1. Az objektum mint fekete doboz

  • egy objektumnak van állapota és viselkedése
  • a belső reprezentáció rejtve marad
  • csak úgy használható, ahogy az írója szánta
  • Ötlet: a sztringkezelés körülményes C-ben, csináljunk sztring objektumot!
  • a használatát tegyük kényelmessé operator overloadinggal!

2. Sztringkezelés C-ben

char* teljes_nev(char const* vezeteknev, char const* keresztnev) {
    if (vezeteknev == NULL || keresztnev == NULL)
        return NULL; // hibás paraméterek

    int h1 = strlen(vezeteknev);
    int h2 = strlen(keresztnev);

    int ujmeret = h1 + 1 + h2 + 1;  // szóköz + lezáró nulla
    char* eredmeny = (char*)malloc(ujmeret * sizeof(char));

    if (eredmeny == NULL) // hiba történt
        return NULL;

    strcpy(eredmeny, vezeteknev);
    strcat(eredmeny, " ");
    strcat(eredmeny, keresztnev);

    return eredmeny;
}

3. Sztringkezelés hívása C-ben

int main() {
    char* nev = teljes_nev("Kiss", "Istvan");
    if (nev == NULL)
        return 1;   // hibakezelés
    printf("%s\n", nev);
    free(nev);      // felszabadítás
}

4. Kívánságműsor C++-ban

String teljes_nev(String const& vezeteknev, 
                  String const& keresztnev) {
    return vezeteknev + " " + keresztnev;
}

int main() {
    std::cout << teljes_nev("Kiss", "Istvan");
}
  • eltűnt a hibakezelés
  • eltűnt a felszabadítás
  • ha hiba történik, automatikusan helyes a program

5. Hogy szeretnénk használni?

#include <iostream>
#include "String.h"

int main() {

    String s1, s2;
    std::cin >> s1 >> s2; // lehessen beolvasni...

    String s3 = s1 + s2; // ...összefűzni...
    std::cout << "s3 = " << s3 << std::endl; // ...kiírni...

    s2 += s1; // ...és a végéhez hozzáfűzni
    std::cout << "s2 = " << s2 << std::endl;

    return 0;
}

Ebből most mennyit tudunk megcsinálni?

7. Adatszerkezet

  • fix mérettel
  • nullával lezárva
class String {
    char str[256];      // nullával lezárt
  public:
    /* ... */
};

Majd jó lenne, ha tudna dinamikusan nyújtózni.

8. Fix mérettel, operátorokkal

#include <iostream>

class String {
    char str[256]; // nullával lezárt
  public:
    String(char const* s = "");

    String operator+(String const& rhs) const;
    String& operator+=(String const& rhs);
    String& operator+=(char rhs);

    int length() const;
    char const* c_str() const;
};

std::ostream& operator<<(std::ostream& os, String const& rhs);
std::istream& operator>>(std::istream& is, String& rhs);



int main() {

    String s1, s2;
    std::cin >> s1 >> s2;

    std::cout << "s1 + s2 = " << s1 + s2 << std::endl;

    s2 += s1;
    std::cout << "s2 + s1 = " << s2 << std::endl;

    return 0;
}
Megoldás
#include <iostream>

class String {
    char str[256]; // nullával lezárt
    public:
    String(char const* s = "") {
        strcpy(str, s);
    }

    String operator+(String const& rhs) const {
        String result = *this;
        result += rhs;
        return result;
    }
    String& operator+=(String const& rhs) {
        strcat(str, rhs.str);
        return *this;
    }
    String& operator+=(char rhs) {
        int len = strlen(str);
        str[len] = rhs;
        str[len+1] = '\0';
        return *this;
    }
    int length() const {
        return strlen(str);
    }
    char const* c_str() const {
            return str;
        }
};

std::ostream& operator<<(std::ostream& os, String const& rhs) {
    os << rhs.c_str();
    return os;
}
std::istream& operator>>(std::istream& is, String& rhs) {
    char c;
    String uj;
    while(is.get(c) && !isspace(c))
        uj += c;

    rhs = uj;
    return is;
}

9. Sztring indexelése

Ennek kéne működnie:

String s1;
std::cin >> s1;
s1[0] = toupper(s1[0]); // !
std::cout << s1;

10. Indexelő operátor

Ellenőrizzünk benne túlindexelést!

class String {
    char str[256];
  public:
    /* ... */

    char& operator[](int i) {
        if (i < 0 || i >= strlen(str))
            throw std::out_of_range("String: túlindexelés");

        return str[i];
    }
};

11. Konstans sztring indexelése

Ennek is kéne működnie.

void kiir_elso(String const& str) {
    std::cout << s1[0]; // itt str konstans!
    char const* eleje = &s1[0]; // a címe is képezhető!
}

Az &s1[0] kifejezésnek a tömb első elemére mutató pointert kell adnia.

12. Kétféle indexelő operátor

class String {
    char str[256];
  public:
    /* ... */

    char& operator[](int i) {
        if (i < 0 || i >= strlen(str))
            throw std::out_of_range("String: túlindexelés");

        return str[i];
    }

    char const& operator[](int i) const { // két const!
        if (i < 0 || i >= strlen(str))
            throw std::out_of_range("String: túlindexelés");

        return str[i];
    }
};

13. const és nem const tagfüggvény

Mikor melyik hívódik?

  • nem-const objektumra, ha mindkettő van, a nem-const tfv. hívódik
  • egyébként a const tfv. hívódik
  • const objektumra amúgy is csak const tfv. hívható
  • az overload kiválasztásakor a this const-sága is szempont, nem csak a szokásos paramétereké

14. A friend kulcsszó

Lábbal hajtós beolvasás:

std::istream& operator>>(std::istream& is, String& str) {
    char c;
    String uj;
    while (is.get(c) && !isspace(c))
        uj += c; // mindig megkeressük a végét
    rhs = uj;
    return is;
}

15. A friend kulcsszó

Lásson bele a String belsejébe:

class String {
    /* ... */
    friend std::istream& operator>>(std::istream& is, String& s);
}

std::istream& operator>>(std::istream& is, String& s) {
    /* ... */
}

Osztály is megadható: friend class X;

16. friend helyett inkább

  • a friend általában OOP alapelveket sért
  • inkább tagfüggvény, amit a friend tud hívni
  • létezik bonyolult példa, ahol nem sérti

Dinamikus String

18. Adatszerkezet

  • sztring: dinamikus karaktertömb
  • dinamikus tömb: pointer + méret
  • érdemes továbbra is nullával lezárni
  • méretbe ne számoljuk bele a lezáró nullát
  • size_t: tömb méretét tárolni képes valamilyen unsigned típus
class String {
    size_t size;
    char* str;
  public:
    String();
    String(char const* s);
    ~String(); // amennyi a new, annyi a delete
    /* ... */
};

19. Konstruktor, destruktor

Írjuk meg a konstruktort és a destruktorokat!

#include <iostream>
class String {
    size_t size;
    char* str;
  public:
    String();
    String(char const* s);
    ~String();

    String operator+(String const& rhs) const;
    String& operator+=(String const& rhs);
    String& operator+=(char rhs);

    size_t length() const {
        return size;
    }
    char const* c_str() const {
        return str;
    }
};

std::ostream& operator<<(std::ostream& os, String const& rhs) {
    os << rhs.c_str();
    return os;
}

std::istream& operator>>(std::istream& is, String& rhs) {
    char c;
    String uj;
    while (is.get(c) && !isspace(c))
        uj += c;

    rhs = uj;
    return is;
}

int main() {

    String s1, s2;
    std::cin >> s1 >> s2;

    std::cout << "s1 + s2 = " << s1 + s2 << std::endl;

    s2 += s1;
    std::cout << "s2 + s1 = " << s2 << std::endl;

    return 0;
}
Megoldás
#include <iostream>

class String {
    char str[256]; // nullával lezárt
    public:
    String(char const* s = "") {
        strcpy(str, s);
    }

    String operator+(String const& rhs) const {
        String result = *this;
        result += rhs;
        return result;
    }
    String& operator+=(String const& rhs) {
        strcat(str, rhs.str);
        return *this;
    }
    String& operator+=(char rhs) {
        int len = strlen(str);
        str[len] = rhs;
        str[len+1] = '\0';
        return *this;
    }
    int length() const {
        return strlen(str);
    }
    char const* c_str() const {
            return str;
        }
};

std::ostream& operator<<(std::ostream& os, String const& rhs) {
    os << rhs.c_str();
    return os;
}
std::istream& operator>>(std::istream& is, String& rhs) {
    char c;
    String uj;
    while(is.get(c) && !isspace(c))
        uj += c;

    rhs = uj;
    return is;
}

20. Objektum másolása

  • C-ben egy struktúra másolása adattagonként, egyesével
  • ha nem kérjük másként, C++-ban is
  • pointermásolás: mutasson ugyanoda, ahova az eredeti
  • itt ezt csinálná:
  • ennek kéne történnie:
  • kétszeres felszabadítás

21. Másoló konstruktor

Írjuk meg a másoló konstruktort!

#include <iostream>
class String {
    size_t size;
    char* str;
  public:
    String() {
        size = 0;
        str = new char[1];
        str[0] = '\0';
    }
    String(char const* s) {
        size = strlen(s);
        str = new char[size + 1];
        strcpy(str, s);
    }
    ~String() {
        delete[] str;
    }

    size_t length() const {
        return size;
    }
    char const* c_str() const {
        return str;
    }
};

int main() {

    String s1, s2;
    std::cin >> s1 >> s2;

    String a = "hello";
    String b = a;
    s2 = b;

    std::cout << "s1 + s2 = " << s1 + s2 << std::endl;

    s2 += s1;
    std::cout << "s2 + s1 = " << s2 << std::endl;

    return 0;
}
Megoldás
#include <iostream>
class String {
    /* ... */
    String(String const& other) {
        size = other.size;
        str = new char[size + 1];
        strcpy(str, other.str);
    }
};

22. Értékadás

  • C-ben értékadás ugyanúgy adattagonként, mint a másolás
  • itt ezt csinálná:
  • ennek kéne történnie:
"deep copy"
  • memory leak, majd kétszeres felszabadítás

23. Értékadó operátor

Írjuk meg az értékadó operátort!

#include <iostream>
class String {
    size_t size;
    char* str;
  public:
    String() {
        size = 0;
        str = new char[1];
        str[0] = '\0';
    }
    String(char const* s) {
        size = strlen(s);
        str = new char[size + 1];
        strcpy(str, s);
    }
    String(String const& other) {
        size = other.size;
        str = new char[size + 1];
        strcpy(str, other.str);
    }
    ~String() {
        delete[] str;
    }

    size_t length() const {
        return size;
    }
    char const* c_str() const {
        return str;
    }
};

int main() {

    String s1, s2;
    std::cin >> s1 >> s2;

    String a = "hello";
    String b = "assign";
    b = a;

    std::cout << "s1 + s2 = " << s1 + s2 << std::endl;

    s2 += s1;
    std::cout << "s2 + s1 = " << s2 << std::endl;

    return 0;
}
Megoldás
#include <iostream>
class String {
    /* ... */
    String& operator=(String const& other) {
        if (this != &other) {
            size = other.size;
            str = new char[size + 1];
            strcpy(str, other.str);
        }
        return *this;
    }
};

24. Példák másolásra és értékadásra

String f1() {
    String s("hello");
    return s; // érték szerint visszaadott objektum
}
void f2(String s) { // érték szerinti paraméter
    std::cout << s.c_str() << std::endl;
}

String s2;
String s3 = s2;
String s4(s3);
s4 = s3;
s4 = String("hello");
String s5 = String("vilag");

25. Hármas szabály (rule of three)

  • ha konstruktorban van new, kell destruktor is
  • ha kell destruktor, a kettős felszabadítást elkerülendő, kell másoló konstruktor
  • ha van másoló konstruktor, kell operator= is
  • (C++11: rule of five, több eszköz van erre)

Dinamikus memóriát kezelő osztályba mindegyik kell...

26. Rule of zero

...de lehetőleg egyiket se kelljen megírnunk:

  • char* helyett használjunk sztring osztályt
  • String helyett használjunk std::string-et!
  • Single Responsibility Principle
struct Vonatjegy {
    std::string indulo_allomas;
    std::string vegallomas;
};
  • ennek a struct-nak nem kell egyiket se megírni
  • a fordító által generált copy ctor, operator= jó lesz

27. Adattag inicializálása

class Vonatjegy {
    std::string indulo_allomas;
    std::string vegallomas;
  public:
    Vonatjegy(std::string const& honnan, std::string const& hova){
        // a konstruktor törzse előtt default konsturktorok
        indulo_allomas = honnan; // értékadás
        vegallomas = hova; // értékadás
    }
};
Megoldás
class Vonatjegy {
    std::string indulo_allomas;
    std::string vegallomas;
    public:
    Vonatjegy(std::string const& honnan, std::string const& hova) 
        : indulo_allomas(honnan), vegallomas(hova) {
    }
};

28. Inicializáló lista

  • megadhatjuk, hogy egy adattagnak melyik konstruktora hívódjon
  • inicializálás != értékadás
  • kötelező, ha az adattagnak nincs default konstruktora
  • van, amit kötelező inicializálni
    • referencia adattag, const adattag, de ilyet úgyse érdemes

29. Életciklus

  • konstruktor, inicializálás
  • másolás
  • értékadás (felülírás)
  • destruktor, megszűnés

Következik: öröklés

31. Kudos

  • BME EET-nek az InfoC motorért

    • Kohári Zsolt
  • CPPFTW-nek

    • Máté Gábor
    • Csala Péter
    • Czirkos Zoltán
  • prog2.cppftw.org