Programozás alapjai II. (4. ea) C++

konstruktor és értékadás, dinamikus szerkezetek

Szeberényi Imre, Somogyi Péter BME IIT szebi@iit.bme.hu

Hol tartunk?

Konstr: létrehoz+alapáll.

#include <cmath>
class Komplex {
  double re, im;
public:
  // A programozott törzs lefutása előtt számos feladata van.
  // Pl.: létrehozza az adattagokat: hívja azok konstruktorát.
  Komplex() { re = 0; im = 0; }
  Komplex(double r) { re = r; im = 0; }
  Komplex(double r, double i) { re = r; im = i; }
  double abs() const { return sqrt(re * re + im * im); }
};
int main() {
  Komplex k;        // paraméter nélkül hívható (default)
  Komplex k1(1);    // 1 paraméteres
  Komplex k2(1, 1); // 2 paraméteres
}

Inicializáló lista

class Valami {
  const double c1; // inicializálni kell
  // c1 = 3.14 kellene, de c++03-ban nem lehet:
  // error: in-class initialization
  // Hogyan lehetne inicializálni? (c++11-ben OK)
  Komplex k1;
public:
  // Valami(double c) { c1 = c; } sem jó, mert:
  // error: cannot assign to non-static data member 'c1' with
  //    const-qualified type 'const double'
  Valami(double c) : c1(c) {}
  //                 ^ inicializáló lista
  Valami(double c, Komplex k) : c1(c), k1(k) {}
  //   inicializáló lista       ^
};

Destruktor: megszüntet (ism.)

#include <cstddef>
class String {
  size_t len;  // tároljuk a hosszt
  char *pData; // pointer az adatara
public:
  String(size_t len) {
    pData = new char[this->len = len]; // terület lefoglalása
    // ...
  }
  ~String() { delete[] pData; } // terület felszabadítása
  // ^ A programozott törzs lefutása után feladata van.
  // Pl.: megszünteti az adattagokat: hívja azok destruktorát
};

A pData és a len megszüntetése automatikus, ahogy egy lokális változó is megszűnik. A new-val foglalt dinamikus terület felszabadítása azonban a mi feladatunk, ahogyan C-ben is fel kell szabadítani a dinamikusan foglalt területet.

Műveletekkel bővített Komplex

class Komplex {
  double re, im;
public:
  Komplex(double r = 0.0, double i = 0.0) {
    re = r; im = i; }
  Komplex operator+(const Komplex &k) const {
    Komplex sum(k.re + re, k.im + im); return sum;
  } // k1 + k2;
  Komplex operator+(double r) const {
    return operator+(Komplex(r)); } // k1 + 3.14;
};
int main() { Komplex k1, k2, k3; k1 + k2; k1 + 3.14; k1 = k2; }
// 3.14 + k1; bal oldal nem obj.! Ezért globális fv. kell!
// k1 = k2; alapértelmezett értékadó operátor

double + Komplex (ism.)

class Komplex {
  double re, im;
public:
  Komplex(double r, double i) {
    re = r;
    im = i;
  }
};
// Globális fv., nem tagfüggvény:
Komplex operator+(double r, const Komplex &k) {
  return Komplex(k.re + r, k.im);
}
// Baj van! Nem férünk hozzá, mivel privát!

1. megoldás: privát adat elérése pub. fv. használatával:

class Komplex {
  double re, im;

public:
  Komplex(double r, double i = 0) {
    re = r; im = i;
  }
  double getRe() const { return re; } // publikus lekérdező
  double getIm() const { return im; } // függvények
};
// Globális fv., nem tagfüggvény:
Komplex operator+(const double r, const Komplex &k) {
  return Komplex(k.getRe() + r, k.getIm());
}

2. megoldás: védelem enyhítése

class Komplex {
  double re, im;
public:
  Komplex(double re = 0, double im = 0) { this->re = re; this->im = im; }
  double getRe() const { return re; }
  double getIm() const { return im; }
  // FONTOS! Ez nem tagfüggvény, csak így jelöli, hogy barát
  friend Komplex operator+(double r, const Komplex &k);
};

Komplex operator+(double r, const Komplex &k) {
  return Komplex(k.re + r, k.im); // hozzáfér a privát adatohoz
}
#include <iostream>
std::ostream &operator<<(std::ostream &os, const Komplex &k) {
  return os << k.getRe() << '+' << k.getIm() << 'i';
}

Alapértelmezett tagfüggvények (ism.)

A másoló konstruktor és az értékadó operátor alapértelmezés szerint meghívja az adattagok megfelelő tagfüggvényét. Alaptípus esetén (bitenként) másol!

Példa: Intelligens string

String adatszerkezete

String osztály

class String {
  char *p; size_t len;
public:
  String(const char *s = "") {
    //                 ^ Ez a default
    // (paraméter nélkül hívható) konstruktor is
    p = new char[(len = strlen(s)) + 1]; strncpy(p, s, len); p[len] = 0;
  }
  ~String() { delete[] p; }
  //                ^ new[] után csak így!
  const char &operator[](int i) const { return p[i]; }
  char &operator[](int i) { return p[i]; }
  //   ^ referenciát ad, így balérték is lehet
};

Függvényhívás mint balérték

#include <cstddef>
#include <cstring>
int main() {
  String s("Hello");
  const String cs("Konstans");
  // konstruktorok: s.p = new char[6] s.len = 5
  //               cs.p = new char[9] s.len = 8
  char c = s[3]; // c = s.operator[](3); -> c=s.p[3];
  //         ^ String s("Hello"); ctr hívás
  c = cs[4];     // c = cs.operator[](4) const; -> c=cs.p[4];
  s[1] = 'u';    // s.operator[](1) ='u'; -> s.p[1]='u';
} // destruktorok: delete[] cs.p, delete[] s.p

Értékadás problémája

Megoldás: operátor= átdefiniálás

class String {
// ...
  // Paraméterként kapja azt, amit értékül kell adni
  // egy létező objektumnak:
  String &operator=(const String &s) { // s1=s2=s3 miatt
    if (this != &s) {                  // s = s kivédésére
      delete[] p;
      p = new char[(len = s.len) + 1];
      strncpy(p, s.p, len); p[len] = 0; ;
    }
    return *this; // visszaadja saját magát
  }
};

operator=-vel már nincs baj

Kezdeti értékadás problémája

Megoldás: másoló konstruktor

class String {
// ...
  String(const String &s) {
    //                ^ Referenciaként kapja azt a
    // példányt, amit lemásolva létre kell hoznia egy
    // *új* objektumot.
    p = new char[(len = s.len) + 1];
    strncpy(p, s.p, len);
    p[len] = 0;
  }
};

Másoló konstruktorral már jó

Miért más mint az értékadás?

Függvényhívás és visszatérés

Összetett algebrai kifejezés

  String s, s0, s1, s2;
  s = s0 + s1 + s2;
  //  ~~~~~~~
  //  ^ tmp1 = s0 + s1         1. lépés
  //
  //  tmp1 + s2
  //  ~~~~~~~~~
  //  ^ tmp2 = tmp1 + s2       2. lépés
  //
  //  s = tmp2                 3. lépés
  //
  //  tmp1, tmp2 megszüntetése 4. lépés
  //             dtr. hívással

String rejtvény

class String {
   char *p; size_t len;
public:
   String( );                  // 1
   String(const char *);       // 2
   String(const String&);      // 3
   ~String( );                 // 4
   String operator+(String&);  // 5
   char&  operator[](int);     // 6
   String& operator=(String&); // 7
};
int main( ) {
      String s1("rejtvény"); String s2; String s3 = s2;                     
      char c = s3[3]; s2 = s3; s2 = s3 + s2 + s1; return 0; // destr.   
}
class String {
   char *p; size_t len;
public:
   String( );                  // 1
   String(const char *);       // 2
   String(const String&);      // 3
   ~String( );                 // 4
   String operator+(String&);  // 5
   char&  operator[](int);     // 6
   String& operator=(String);  // 7, érték szerint
};
int main( ) {
      String s1("rejtvény"); String s2; String s3 = s2;                     
      char c = s3[3]; s2 = s3; s2 = s3 + s2 + s1; return 0; // destr.   
}

Miért referencia?

Miért kell referencia a másoló konstruktorhoz?

Miért fontos a delete[]?

String +

class String {
// ...
  String operator+(const String &s);
  String operator+(char c);
  friend String operator+(char c, const String &s);
  // ^ Védelem enyhítése
};
String operator+(char c, const String &s) {
  //   ^ Nem tagfüggvény!
  char *p = new char[s.len + 2];
  *p = c; strncpy(p + 1, s.p, s.len + 1);
  String ret(p);
  delete[] p; return ret;
}

String + friend nélkül

class String {
// ...
  String operator+(const String& s);
  String operator+(char c);
};
String operator+(char c, const String &s) {
    return String(c) + s;
}

Keletkezett-e += ?

Változtatható viselkedés

Statikus tag

String statikus taggal

class String {
  char *p;
  size_t len;
  static bool ucase; // statikus adattag deklarálása
public:
  // ...
  static void Ucase(bool b) { ucase = b; } // beállít
  static bool Ucase() { return ucase; }    // lekérdez
  friend ostream &operator<<(ostream &os, const String &s);
};
bool String::ucase = false; // Definíció FONTOS !!!
//           ^ Adattag definíciója
ostream &operator<<(ostream &os, const String &s) {
  for (size_t i = 0; i < s.len; i++) {
    char ch = String::ucase ? toupper(s.p[i]) : s.p[i];
    // Osztályhoz tartozik, nem a példányhoz.
    // Lehetne s::ucase is, de félrevezető lenne.
    os << ch; // miért kell ch?
  }
  return os;
}
// Friend nélkül:
// Publikus tagfüggvényekkel: size(), Ucase(), operator[]()
ostream &operator<<(ostream &os, const String &s) {
  for (size_t i = 0; i < s.size(); i++) {
    char ch = String::Ucase() ? toupper(s[i]) : s[i];
    os << ch;
  }
  return os;
}

Statikus adattag inicializálás

Ha a statikus adattag konstans és integrál- vagy enum típusú, akkor inicializálható az osztály deklarációjában is. Ez a definíciót is kiváltja.

struct A {
  static const int a = 35; // definíció is
  static enum { s1, s2 } const st = s1;
};

// Nem kell/szabad:
const int A::a = 35;
// error: static data member 'a' already has an initializer

Adattag inicializálás

Memóriakép (ism.)

Data szegmens

Példa

// cat maci.c;
char buf[100];           // 100 byte bss
char duma[] = "Hello !"; // 8 byte data
const char *pp = "C++";  // 8 byte data + 4 RO data
void f() {}              // ?? byte text
~$ gcc –c maci.c
~$ size -A maci.o
section           size   addr
.text                7      0
.data                8      0
.bss               100      0
.rodata              4      0
.data.rel.local      8      0
.comment            30      0

Mi van a motorházban?

~$ gcc –S maci.c
~$ more maci.s
        .file   "maci.c"
        .text
        .globl  buf
        .bss
        .align 32
        .type   buf, @object
        .size   buf, 100
buf:
        .zero   100
        .globl  duma
        .data
        .align 8
        .type   duma, @object
        .size   duma, 8
duma:
        .string "Hello !"
        .globl  pp
        .section        .rodata
.LC0:
        .string "C++"
        .section        .data.rel.local,"aw"
        .align 8
        .type   pp, @object
        .size   pp, 8
pp:
        .quad   .LC0
        .text
        .globl  _Z1fv
        .type   _Z1fv, @function
_Z1fv:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        nop
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   _Z1fv, .-_Z1fv

Komplex példa újból

KomplexTar osztály

class KomplexTar {
  Komplex *t; // pointer a dinamikusan foglalt tömbre
  size_t db;  // elemek száma/ tömb mérete
public:
  class Tar_Hiba {}; // osztály az osztályban a hibakezeléshez
  KomplexTar(size_t m = 10) : db(m) {
    t = new Komplex[m];
  }                                            // konstruktor (def = 10)
  KomplexTar(const KomplexTar &kt);            // másoló konstruktor
  Komplex &operator[](size_t i);               // indexelés
  const Komplex &operator[](size_t i) const;   // indexelés
  KomplexTar &operator=(const KomplexTar &kt); // értékadás
  ~KomplexTar() { delete[] t; }                // felszabadítás
};
KomplexTar::KomplexTar(const KomplexTar &kt) { // másoló konstr.
  //        ^ A memcpy nem hívná meg a konstruktort
  t = new Komplex[db = kt.db];
  for (size_t i = 0; i < db; i++) { t[i] = kt.t[i]; }
  // miért nem memcpy?
}
KomplexTar &KomplexTar::operator=(const KomplexTar &kt) { // =
  if (this != &kt) {
    delete[] t; t = new Komplex[db = kt.db];
    for (size_t i = 0; i < db; i++) { t[i] = kt.t[i]; }
    // miért nem memcpy?
  }
  return *this;
}
KomplexTar::KomplexTar(const KomplexTar &kt) { // másoló 2.vált.
  //        ^ Visszavezettük értékadásra
  t = NULL;
  *this = kt; // trükkös, de rendben van !
}

a) Indexelés

KomplexTar::KomplexTar(const KomplexTar &kt) { // cpy 2.vált.
  //        ^ Visszavezettük értékadásra
  t = NULL;  *this = kt; // trükkös, de rendben van !
}
Komplex &KomplexTar::operator[](size_t i) {
  if (i >= db) { throw Tar_Hiba(); }
  return t[i];
}
int main() {
  KomplexTar t(5); // a tárolóban 5 elemünk van
  try {
    for (size_t i = 0; i < 20; i++) { cin >> t[i]; // beolvasás }
    KomplexTar t2 = t; // másoló konstruktor
    for (size_t i = 19; i >= 0; i--) {
      cout << t[i] << ' ' << (double)t[i] << endl; // kiírás
    }
  } catch (KomplexTar::Tar_Hiba) {
    cerr << "Indexelesi hiba\n"; // hibakezelés
  }
  return (0);
}

b) Változó méretű KomplexTar

// Indexelés hatására növekszik a méret, ha kell
Komplex &KomplexTar::operator[](size_t i) {
  if (i >= db) { // növekednie kell, célszerű kvantumokban
    Komplex *tmp = new Komplex[i + 10]; // legyen nagyobb
    for (size_t j = 0; j < db; j++) {
      tmp[j] = t[j]; // átmásol
    }
    delete[] t;  // régi törlése
    t = tmp;     // pointer az új területre
    db = i + 10; // megnövelt méret
  }
  return t[i]; // referencia vissza
}
// Konstans tároló nem tud növekedni
const Komplex &KomplexTar::operator[](size_t i) const {
  if (i >= db) {
    throw Tar_Hiba();
  }
  return t[i];
}

c) Gyakorlatiasabb változat

class KomplexTar {
  static const size_t nov = 3; // növekmény érteke
  Komplex *t;                  // pointer a dinamikusan foglalt adatra
  size_t db;                   // elemek száma
  size_t kap;                  // tömb kapacitása
public:
  KomplexTar(size_t m = 10) : db(m), kap(m + nov) { t = new Komplex[kap]; }
  KomplexTar(const KomplexTar & /*kt*/);
  class Tar_Hiba {}; // osztály az osztályban a hibakezeléshez
  size_t capacity() const { return kap; }
  size_t size() const { return db; }
  Komplex &operator[](size_t i);
  const Komplex &operator[](size_t i) const;
  KomplexTar &operator=(const KomplexTar & /*kt*/);
  ~KomplexTar() { delete[] t; } // felszabadítás
};
Komplex &KomplexTar::operator[](size_t i) {
  if (i >= kap) {
    Komplex *tmp = new Komplex[i + nov]; // legyen nagyobb
    for (size_t j = 0; j < db; j++) {
      tmp[j] = t[j]; // átmásol
    }
    delete[] t;    // régi törlése
    t = tmp;       // pointer az új területre
    kap = i + nov; // megnövelt kapacitás
  }
  if (i >= db) {
    db = i + 1; // megnövelt darab
  }
  return t[i]; // referencia vissza
}
const Komplex &KomplexTar::operator[](size_t i) const {
  if (i >= db) {
    throw Tar_Hiba();
  }
  return t[i];
}

Összefoglalás

Létrehozás

Megsemmisítés

Milyen furcsa kommentek!

/** 
* Komplex osztály.
* Komplex viselkedést megvalósító osztály.
* Csak a feladat megoldásához szükséges műveleteket
* definiáltuk.
*/
class Komplex {

class Komplex {
// ...
/**
* Konstruktor nulla, egy és két paraméterrel
* @param r - valós rész (alapértelmezése 0)
* @param i - képzetes rész (alapértelmezése 0)
*/
Komplex(double r = 0, double i = 0) :re(r), im(i) {}
operator double() { return sqrt(re*re + im*im); } ///< abszolút érték
friend istream& operator>>(istream& s, Komplex& k); ///< Komplex beolvasás
friend ostream& operator<<(ostream& s, const Komplex k); ///< Komplex kiírás