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

C++ programozás kezdőknek - osztályok/objektumok

[2022. január 18.] [ christo161 ]

Ebben a tananyagrészben az objektumorientált programozás (OOP) alapjairól lesz szó.

előző tananyagrész: függvények
következő tananyagrész: több forrásfájlból álló programok

Tartalom

Alapvető tudnivalók

Az osztályok/objektumok többek közt arra valók, hogy logikailag összetartozó (akár különböző típusú) adatokat (pl. változókat, tömböket), és a rajtuk végezhető műveleteket (függvényeket) csoportosítsuk.
A csoportosítás technikailag azt jelenti, hogy egy névhez/azonosítóhoz (az osztály nevéhez) rendeljük azokat a változókat, tömböket és függvényeket, amikről úgy gondoljuk, hogy logikailag összetartoznak.

Egy osztállyal például tipikusan egy adatbázis egy táblájának rekordjait reprezentálhatjuk, egy osztály egy példányával (úgynevezett objektummal) pedig egy konkrét (adatokkal feltöltött) rekordot. Például ha személyekről készítünk egy osztályt, akkor ennek az osztálynak egy objektuma egy konkrét személy.

Úgy is fel lehet fogni, hogy az osztály a típus, az objektum pedig a típusból létrehozott változó.

Adattagok

Ebben a nagyon egyszerű példában létrehozunk egy személy osztályt, ami a felhasználónév, teljes név és életkor változókat (úgynevezett adattagokat) tartalmazza (az adattagokat angolul fielndnek vagy member variable-nek szokták nevezni).
A main függvényben létrehozunk egy objektumot ez alapján az osztály alapján, és kezdőértékeket adunk neki, majd kiíratjuk az objektum adattagjait.

//basic example for grouping variables into a class
#include <iostream>
#include <string>

//class definition
class person {
public:
  std::string username;
  std::string fullname;
  int age;
};

int main() {
  //class instantiation, initialization
  person person1{"christo161", "Adam Kovacs", 30};

  std::cout << "Felhasznalonev: " << person1.username << '\n';
  std::cout << "Teljes nev: " << person1.fullname << '\n';
  std::cout << "Eletkor: " << person1.age << '\n';
}

Aki már programozott Javaban vagy C#-ban, annak a számára szokatlan lehet, hogy egy objektum egy ilyen utasítást követően létrejön:

person person1{"christo161", "Adam Kovacs", 30};

C++ nyelvben objektumok létrehozásához (az osztálydefiníción kívül) kerülendő new operátort használni, mivel amit new operátorral foglalunk le a memóriában, az C++ nyelvben nem lesz automatikusan felszabadítva akkor sem, ha a vezérlés annak a blokknak a végére ér, amiben létre lett hozva az objektum, illetve C++ nyelvben nincs garbage collector, mint Java és C# nyelvekben. Manuális törlés ugyan létezik C++ nyelvben a delete operátorral, viszont ha a new operátor használata után kivétel dobódik, akkor a vezérlés nem jut el a delete operátorig. Éppen ezért new operátort osztályok konstruktorában szoktunk használni, delete operátort pedig osztályok destruktorában, mivel az osztályok destruktora lefut, ha a vezérlés annak a blokknak a végére ér, amiben egy objektum létre lett hozva.
Az újabb modern C++ szabvány szerint értelmezett kódban pedig smart pointereket szoktak használni new operátor helyett.

Getter függvények és konstruktor

Az objektumorientál programozás egyik alapszabálya, hogy az adattagokat csak úgynevezett getter és setter tagfüggvényeken keresztül érhetjük el, vagyis az adattagok nem publikusak. Erre alapvetően azért van szükség, mert a tagfüggvényekben ellenőrízni lehet, hogy esetleg érvénytelen értéket adott valaki az egyik adattagnak (pl. egy dátum osztály esetén a hónapnak 13-at), viszont ha egy adattag publikus, akkor semmi nem akadályozza meg, hogy valaki érvénytelen értéket adhasson neki. Ezen felül debuggolás esetén a függvényhívásokat könnyebb nyomon követni, másrészt ha egy adattag nevét módosítjuk, akkor elég az azt használó tagfüggvényekben átírni.

Fontos: ügyeljünk arra, hogy amelyik tagfüggvénnyel nem szeretnénk módosítani az objektumot, annak a paraméterlistája után írjunk egy const kulcsszót, mert csak így lesz használható az adott tagfüggvény konstansként példányosított objektumokon, illetve konstans referenciaként átvett paramétereken.

Ha az adattagok privátak, akkor nem tudunk nekik a fentebbi módszerrel kezdőértéket adni, mivel a privát adattagokhoz csak tagfüggvények férhetnek hozzá.
Az adattagok inicializálásához használható tagfüggvény a konstruktor, ami egy objektum létrehozásakor fut le, a neve megegyezik az osztály nevével és nincs visszatérési értéke (nem void a visszatérési értéke, hanem nem írunk semmit a visszatérési érték helyére).
A konstruktorban akár default értékeket is megadhatunk, illetve feltételeket is szabhatunk (pl. hogy egy dátum osztályban a hónap ne lehessen több 12-nél).

Konstruktort kétféleképp definiálhatunk. Vagy a blokkjában adjuk értékül a paraméterben kapott értékeket az adattagoknak:

person(std::string p_username, std::string p_fullname, int p_age) {
  username = p_username; fullname = p_fullname; age = p_age;
}

Vagy pedig inicializáló listát használunk:

person(std::string p_username, std::string p_fullname, int p_age) :
username{p_username}, fullname{p_fullname}, age{p_age} {}

Az inicializáló lista használata gyorsabb, illetve konstans (const) adattagokat csak ezzel a módszerrel lehet inicializálni.

A példaprogram kódja:

//basic example for grouping variables into a class
#include <iostream>
#include <string>

//class definition
class person {
public:
  //constructor
  person(std::string p_username, std::string p_fullname, int p_age) :
  username{p_username}, fullname{p_fullname}, age{p_age} {}

  //getter functions
  std::string get_username() const { return username; }
  std::string get_fullname() const { return fullname; }
  int get_age() const { return age; }

private:
  //member variables
  std::string username;
  std::string fullname;
  int age;
};

int main() {
  //class instantiation, initialization
  person person1{"half-life", "Gordon Freeman", 50};

  std::cout << "Felhasznalonev: " << person1.get_username() << '\n';
  std::cout << "Teljes nev: " << person1.get_fullname() << '\n';
  std::cout << "Eletkor: " << person1.get_age() << '\n';
}

Konstruktorokat elsősorban azért használunk, hogy elkerüljük azt, hogy egy objektum adattagjainak ne legyen kezdőértéke. Például mondjuk egy string osztály esetén fontos, hogy a méretet tároló adattagnak jól be legyen állítva az értéke (ha pl. üres string a kezdőértéke, akkor a méretet tároló adattag értéke 0).

Nem csak adattagok lehetnek privátak, hanem bizonyos tagfüggvények is, amik pl. valamilyen belső számítást végeznek (mondjuk a tört osztályban a tört egyszerűsítését végző tagfüggvény).

Az osztály publikus részét szokták interfésznek, a privát részét pedig implementációnak nevezni.

Abban az esetben, ha adattagokat vagy tagfüggvényeket nem a private: vagy a public: részhez írjuk, hanem nem adunk meg semmit, akkor azok egy class esetén privátak lesznek (struct esetén pedig publicok).

Konstans (const) adattagok

Konstans adattagoknak inicializáló listával tudunk kezdőértéket adni (ebben a példában a birthplace, azaz születési hely).

//basic example for grouping variables into a class
#include <iostream>
#include <string>

//class definition
class person {
public:
  //constructor
  person(std::string p_username, std::string p_fullname, int p_age, std::string p_birthplace) :
  username{p_username}, fullname{p_fullname}, age{p_age}, birthplace{p_birthplace}{}

  //getter functions
  std::string get_username() const { return username; }
  std::string get_fullname() const { return fullname; }
  int get_age() const { return age; }
  std::string get_birthplace() const { return birthplace; }

private:
  //member variables
  std::string username;
  std::string fullname;
  int age;
  const std::string birthplace;
};

int main() {
  //class instantiation, initialization
  person person1{"christo161", "Kovacs Adam", 30, "Budapest"};

  std::cout << "Felhasznalonev: " << person1.get_username() << '\n';
  std::cout << "Teljes nev: " << person1.get_fullname() << '\n';
  std::cout << "Eletkor: " << person1.get_age() << '\n';
  std::cout << "Szuletesi hely: " << person1.get_birthplace() << '\n';
}

default értékek

Az adattagoknak default értékeket például a konstruktorban adhatunk, hasonlóan mint a nem tagfüggvények esetén. Ekkor az objektum létrehozásánál annyival kevesebb paramétert adhatunk meg, ahány adattagnak adtunk default értéket.

//basic example for grouping variables into a class
#include <iostream>
#include <string>

//class definition
class person {
public:
  //constructors
  person(std::string p_username = "default_username", std::string p_fullname = "default_fullname", int p_age = 0) :
  username{p_username}, fullname{p_fullname}, age{p_age} {}

  //getter functions
  std::string get_username() const { return username; }
  std::string get_fullname() const { return fullname; }
  int get_age() const { return age; }

private:
  //member variables with default values
  std::string username;
  std::string fullname;
  int age;
};

int main() {
  //class instantiation
  person person1;

  std::cout << "Felhasznalonev: " << person1.get_username() << '\n';
  std::cout << "Teljes nev: " << person1.get_fullname() << '\n';
  std::cout << "Eletkor: " << person1.get_age() << '\n';
}

Ügyeljünk arra, hogy amikor 0 paraméterrel példányosítunk egy osztályt, akkor ne használjunk kerek zárójeleket, mert az hibát okoz.

Ehelyett tehát...

//error
person person1();

Ezek közül használjuk az egyiket...

person person1;
person person1{};

...példányosítás esetén.

Abban az esetben, ha egy többparaméteres konstruktort írunk, de nem írunk 0 paraméteres konstruktort, vagy nem adunk meg default értékeket, akkor csak a többparaméteres konstruktorral tudjuk példányosítani az osztályt.

Hibát okoz, ha default értékeket is megadunk, és 0 paraméteres konstruktort is definiálunk, hiszen ekkor a fordító számára nem egyértelmű, hogy melyik konstruktort hívja meg.

operator<< túlterhelése

Az objektum adattagjait egy std::cout << person1; utasítással is kiírathatjuk, ha túlterheljük az operator<<-t. Mivel ez globális függvény (free function), ezért az osztályon kívül kell túlterhelnünk. Ahhoz, hogy hozzáférjen az osztály privát adattagjaihoz, az osztály friend függvényévé kell tennünk, amit úgy tehetünk meg, hogy az osztály publikus függvényei alatt egy friend kulcsszó után leírjuk a függvény deklarációját.

A C++ nyelvben ezt a módszert szoktuk használni, ha ki akarjuk íratni egy objektum adattagjait. A getter függvényeket pedig csak akkor használjuk, ha bizonyos adattagokkal valamilyen műveletet szeretnénk végezni.

#include <iostream>
#include <string>

//class definition
class person {
public:
  //constructor
  person(std::string p_username, std::string p_fullname, int p_age) :
  username{p_username}, fullname{p_fullname}, age{p_age} {}

  friend std::ostream& operator<<(std::ostream& p_ostream, const person& p_person);

private:
  //member variables
  std::string username;
  std::string fullname;
  int age;
};

std::ostream& operator<<(std::ostream& p_ostream, const person& p_person) {
  p_ostream << "Felhasznalonev: " << p_person.username << '\n';
  p_ostream << "Teljes nev: " << p_person.fullname << '\n';
  p_ostream << "Eletkor: " << p_person.age << '\n';
  return p_ostream;
}

int main() {
  //class instantiation, initialization
  person person1{"half-life", "Gordon Freeman", 50};

  std::cout << person1;
}

Setter függvények

Ezidáig mindösszesen annyit értünk el, hogy kezdőértékeket adtunk egy objektum adattagjainak, majd ezeket az értékeket kiírattuk. Noha ez nem tűnik túl izgalmasnak, ezen felül általában olyan műveletek szoktak lenni, amik módosítanak valamilyen adattagot. A teljes név módosítására persze nincs túl nagy igény, de mondjuk például a munkahely, munkakör változhat.

#include <iostream>
#include <string>

//class definition
class person {
public:
  //constructor
  person(std::string p_username, std::string p_fullname, int p_age, std::string p_job = "unemployed") {
    username = p_username;
    fullname = p_fullname;
    age = p_age;
    job = p_job;
  }

//getter functions
  std::string get_username() const { return username; }
  std::string get_fullname() const { return fullname; }
  int get_age() const { return age; }
  std::string get_job() const  { return job; }

//setter functions
  void set_job(const std::string& p_job) { job = p_job; }

  friend std::ostream& operator<<(std::ostream& p_ostream, const person& p_person);

private:
  //member variables
  std::string username;
  std::string fullname;
  int age;
  std::string job;
};

std::ostream& operator<<(std::ostream& p_ostream, const person& p_person) {
  p_ostream << "Felhasznalonev: " << p_person.username << '\n';
  p_ostream << "Teljes nev: " << p_person.fullname << '\n';
  p_ostream << "Eletkor: " << p_person.age << '\n';
  p_ostream << "Munka: " << p_person.job << '\n';
  return p_ostream;
}

int main() {
  //class instantiation, initialization
  person person1{"half-life", "Gordon Freeman", 50};

  std::cout << person1;

  std::cout.put('\n');

  person1.set_job("elmeleti fizikus");

  std::cout << person1;
}

this pointer, elfedés

Ha egy setter függvény paramétere és egy adattag azonos nevű, akkor a paraméter elfedi az adattagot. Ebben az esetben a this pointer segítségével hivatkozhatunk az adattagra. Például:

void set_job(const std::string& job) { this->job = job; }

Előfordulhat, hogy más programozási nyelvekben (pl. Javascriptben) osztálydefiníción belül az adattagok elé kötelező kiírni a this-t, akkor is, ha az adattagok nevei és a paraméterek nevei között nincs egyezés.

statikus adattagok

A statikus adattagok olyan adattagok, amikben minden objektum esetén ugyanaz az adat tárolódik. Tipikusan például azt szoktuk static adattagban tárolni, hogy hány darab objektum létezik az adott osztályból. Esetleg tárolható statikus adattagban az objektumok egyes adattagjának összege, átlaga, maximuma, minimuma.

Fontos, hogy a statikus adattagoknak kezdőértéket adni az osztálydefiníción kívül kell, valamint a statikus adattagokat és a statikus függvényeket a pont helyett a scope operátorral érjük el.

//static member variable example
#include <iostream>
#include <string>

class person {
public:
  person(std::string p_username, std::string p_fullname, int p_age) {
    username = p_username;
    fullname = p_fullname;
    age = p_age;
    ++instances;
  }

  ~person(){ --instances; }

  std::string get_username() const { return username; }
  std::string get_fullname() const { return fullname; }
  int get_age() const { return age; }
  static int get_instances() { return instances; }

private:
  std::string username;
  std::string fullname;
  int age;
  static int instances;
};

//static member initialization
int person::instances = 0;

int main() {
  //class instantiation, initialization
  person person1{"half-life", "Gordon Freeman", 50};
  person person2{"duke_nukem", "Duke Nukem", 45};
  person person3{"shadow_warrior", "Lo Wang", 54};

  std::cout << "Instances of person class: " << person::get_instances() << '\n';
}

Változó méretű tömb mint adattag

Ha hagyományos tömböt vagy std::array-t használunk adattagként, azoknak a mérete nem változhat, a használatuk egyszerűbb, mint a változó méretű tömböké.

Változó méretű tömbszerű adatszerkezet lehet dinamikusan allokált tömb vagy std::vector. Például az előző munkahelyek listája.

std::vector

Változó méretű tömbök esetén kezdőknek std::vector használata ajánlott, mivel dinamikusan allokált tömb esetén a programozó feladata a memória lefoglalása és felszabadítása, amiben könnyű hibát ejteni.

#include <iostream>
#include <string>
#include <vector>

class person {
public:
  //constructor
  person(std::string p_username, std::string p_fullname, int p_age) {
    username = p_username;
    fullname = p_fullname;
    age = p_age;
}

  //getter functions
  std::string get_username() const  { return username; }
  std::string get_fullname() const { return fullname; }
  int get_age() const { return age; }
  std::string get_workplace() const { return workplace; }
  std::vector<std::string> get_former_workplaces() const { return former_workplaces; }

  //setter functions
  void set_workplace(const std::string& p_workplace) {
    if (workplace != "") { former_workplaces.push_back(workplace); }
    workplace = p_workplace;
  }

private:
  std::string username;
  std::string fullname;
  int age;
  std::string workplace;
  std::vector<std::string> former_workplaces{};
};

int main() {
  person person1{"christo161", "Adam Kovacs", 30};

  person1.set_workplace("BKV");
  person1.set_workplace("FKF");
  person1.set_workplace("MVM");
  person1.set_workplace("Fotav");

  std::cout << "Former workplaces of " << person1.get_fullname() << ":\n";

  for (const std::string& element : person1.get_former_workplaces()) {
    std::cout << element << '\n';
  }
}

dinamikusan allokált tömb

Hagyományos tömböt csak akkor használhatunk, ha a tömb mérete nem változik. Az előző munkahelyek listája esetén ez nem igaz, mivel ahogy egyre több előző munkahely lesz, úgy növekszik a tömb mérete is, amiben tároljuk őket. (Ha a tömb mérete a felhasználó által megadott adatoktól függ, akkor sem használhatunk hagyományos tömböt).

Dinamikusan allokált tömb használata esetén viszont a programozó feladata a memória lefoglalása és felszabadítása. Ahhoz, hogy adatokat tudjunk tárolni egy dinamikusan allokált tömbben, előbb memóriát kell lefoglalnunk a new operátorral. Egyrészt ezt a konstruktorban, egy objektum létrehozásakor tesszük meg.

Másrészt ahogy a tömb mérete növekszik, úgy egyre nagyobb tömb szükséges az elemek tárolásához, a méret megváltoztatásához pedig újra kell allokálni a tömböt.

A tömböt nem érdemes újraallokálni minden egyes új elem esetén. Helyette a feladatban kétszeresére növeljük a tömb méretét amikor betelik. Ehhez viszont 2 méret tárolására is szükség van: hogy mekkora a tömb és hogy meddig van feltöltve adatokkal. Ezeket a feladatban az fw_capacity és fw_length adattagokban tároljuk.

Egy dinamikusan allokált tömböt úgy tudunk újra allokálni, ha először töröljük a delete[] operátorral, majd a new operátor segítségével az új méretnek megfelelő helyet allokálunk. A törlés esetén viszont elvesznek az adatok.
Ezt úgy tudjuk megoldani, hogy egy másik tömböt allokálunk az új mérettel (capacity), abba belemásoljuk az adatokat (length-ig, mivel a length határozza meg, hogy aktuálisan mennyi elem van a tömbben), az eredeti tömböt töröljük a delete[] operátor segítségével, majd a másik tömböt adjuk értékül az eredeti tömb pointerének. (Hagyományos tömbök esetén nem lehetne egy tömböt értékül adni a másiknak, viszont dinamikusan allokált tömbök esetén ez lehetséges, mivel azokat pointerekkel kezeljük).

A példában destruktor is szerepel, ami egy objektum megszűnésekor fut le. A destruktorban fel kell szabadítanunk azt a memóriát, amit a konstruktorban lefoglaltunk, mivel a new operátorral lefoglalt memória nem szabadul fel automatikusan, csak a program futásának befejezésekor.

A feladatban arra is látunk példát, hogy egy taggfügvény privát. Ez a tömb újraallokálását megvalósító tagfüggvény. Ez a tagfüggvény azért privát, mert nincs szükség arra, hogy kívülről hívható legyen, csak az osztály tagfüggvényei használják, amikor a tömb növelésére lehet szükség.

A hosszabb tagfüggvényeket az osztályon kívül definiáljuk, beleértve a konstruktort is. Ez esetben scope operátor segítségével meg kell adni az osztályt is, mivel ha két osztálynak lenne azonos nevű függvénye, akkor az névütközést okozna (az osztály megadása nélkül).

//dynamic allocated array example
#include <iostream>
#include <string>

class person {
public:
  //constructor declaration
  person(std::string p_username, std::string p_fullname, int p_age);

  //getter functions
  std::string get_username() const { return username; }
  std::string get_fullname() const { return fullname; }
  int get_age() const { return age; }
  std::string get_workplace() const { return workplace; }

  void get_former_workplaces() const;

  //setter functions
  void set_workplace(const std::string& p_workplace);

  //destructor
  ~person() {
    delete[] former_workplaces;
  }

private:
  std::string username;
  std::string fullname;
  int age;
  std::string workplace;

  size_t fw_length = 1; //actual size
  size_t fw_capacity = 1; //full size

  std::string* former_workplaces = nullptr;

  void former_workplaces_grow();
};

//constructor definition
person::person(std::string p_username, std::string p_fullname, int p_age) {
  username = p_username;
  fullname = p_fullname;
  age = p_age;

  //allocating dynamic array
  former_workplaces = new std::string[fw_length]{};
}

void person::get_former_workplaces() const {
  for (int i = 0; i < fw_capacity; ++i) {
    std::cout << former_workplaces[i] << '\n';
  }
}

void person::former_workplaces_grow() {
  if (fw_capacity == fw_length) {
    //grow size
    fw_capacity *= 2;

    //allocate backup array
    std::string* former_workplaces_backup = new std::string[fw_capacity];

    //copy elements to backup array
    for (int i = 0; i < fw_length; ++i) {
      former_workplaces_backup[i] = former_workplaces[i];
    }

    //delete original array
    delete[] former_workplaces;

    //assign the backup array to original array
    former_workplaces = former_workplaces_backup;
  }
}

void person::set_workplace(const std::string& p_workplace) {
  //we don't need to save the empty string
  if (workplace == "") {
    workplace = p_workplace;
    return;
  }
  //check if we need a bigger array
  former_workplaces_grow();
  //save the old workplace
  former_workplaces[fw_length] = workplace;
  ++fw_length;
  //set the new workplace
  workplace = p_workplace;
}

int main() {
  person person1{"christo161", "Adam Kovacs", 30};

  person1.set_workplace("BKV");
  person1.set_workplace("BKK");
  person1.set_workplace("FKF");
  person1.set_workplace("MVM");
  person1.set_workplace("Fotav");

  std::cout << "Former workplaces of " << person1.get_fullname() << ":\n";

  person1.get_former_workplaces();
}

Copy konstruktor és operator=

Amennyiben dinamikusan allokált tömböket (illetve raw pointereket, azaz nem smart pointereket) használunk, meg kell írnunk a copy konstruktort ahhoz, hogy egy ilyen utasítás megfelelően működhessen:

person person2 = person1;

Illetve az operator=-t ahhoz, hogy egy ilyen utasítás megfelelően működhessen:

person person2;
person2 = person1;

Ha nem írjuk meg a copy konstruktort, illetve az operator=-t, akkor az imént említett utasítások esetén ha az adott osztálynak van raw pointer, azaz nem smart pointer (melybe a dinamikusan allokált tömb is beletartozik) adattagja, akkor az ilyen adattagoknak nem az értéke, hanem a címe másolódna le.

Vagyis például ha módosítanánk a person2 former_workplaces adattagját (ami ugyebár std::string* típusú), akkor a person1 former_workplaces adattagja is módosulna.

Hozzá kell tenni, hogy a person person2; utasítás ebben a példaprogramban amiatt nem működne, mivel a person osztály nem példányosítható 0 paraméterrel, de ettől most tekintsünk el.

A copy konstruktor és operator= tartalma egyező, az összes adattagot le kell másolni, illetve allokálni kell a tömböt is, és for ciklussal egyenként átmásolni az elemeit.
Ha ezt nem tennénk meg, akkor az automatikusan generált copy konstruktor és operator= a dinamikusan allokált tömbnek a címét másolná le, ami azt jelentené, hogy mindkét objektum ugyanazzal a tömbbel dolgozna, ami jó eséllyel hibát okozna.

Copy konstruktor

//copy constructor
person(const person& rhs) {
  username = rhs.username;
  fullname = rhs.fullname;
  age = rhs.age;
  workplace = rhs.workplace;

  fw_length = rhs.fw_length;
  fw_capacity = rhs.fw_capacity;

  //copy the content of former workplaces array
  former_workplaces = new std::string[fw_capacity];
  for (int i = 0; i < fw_length; ++i) {
    former_workplaces[i] = rhs.former_workplaces[i];
  }
}

operator= (assignment operator)

Az értékadó operátor esetén vizsgálni kell azt is, hogy nem önmagát adjuk-e értékül egy objektum esetén, mivel ebben az esetben hibát okozhat az újraallokálás.

Azt is vizsgálni kell, hogy az objektumban, aminek a másik objektumot értékül adjuk, létezik-e már allokált tömb, mivel akkor azt fel kell szabadítani mielőtt újraallokálnánk.

Ügyeljünk arra, hogy az operator= blokkjának a végén szerepeljen a return *this; utasítás, mert ennek köszönhetően lesz láncolható az értékadás (pl. a = b = c;).

//operator=
person& operator=(const person& rhs) {
  if (this == &rhs) {
    return *this;
  }
  username = rhs.username;
  fullname = rhs.fullname;
  age = rhs.age;
  workplace = rhs.workplace;

  fw_length = rhs.fw_length;
  fw_capacity = rhs.fw_capacity;

//if this has allocated former_workpaces array, it should be deleted if (former_workplaces) { delete[] former_workplaces; }
//allocate or reallocate former_workpaces array former_workplaces = new std::string[fw_capacity]; //copy the content of former workplaces array for (int i = 0; i < fw_length; ++i) { former_workplaces[i] = rhs.former_workplaces[i]; } return *this; }

Természetesen ennél a két tagfüggvénynél is meg lehet tenni, hogy az osztálydefinícióba csak a deklarációjukat írjuk, és az osztályon kívül definiáljuk őket, mivel ezek is viszonylag több utasításból álló tagfüggvények.

Feladatok osztályokkal/objektumokkal

Természetesen nem csak személyek adatai tárolhatók objektumokban. Egyetemi vagy tanfolyami feladatokban osztályokból általában adatszerkezeteket (pl. verem, sor, halmaz, rendezett tömb) vagy matematikai fogalmakat (pl. komplex szám, tört szám, mátrix, síkidomok) vagy esetleg dátumot szoktak megvalósítani.
Azt viszont mindenképp érdemes szem előtt tartani, hogy új adatszerkezetek létrehozása általában csak gyakorlás, alapesetben érdemes használni a C++ standard library adatszerkezeteit.

előző tananyagrész: függvények
következő tananyagrész: több forrásfájlból álló programok

A bejegyzés trackback címe:

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

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