konstruktor és értékadás, dinamikus szerkezetek
Szeberényi Imre, Somogyi Péter BME IIT szebi@iit.bme.hu
#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
}
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 ^
};
#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.
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
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!
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());
}
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';
}
X() // nincs paramétere
X(const X&) // referencia paraméter
operator=(const X&) // értékadó
operator&() // címképző
operator*() // dereferáló
operator->() // tag elérése pointerrel
operator,(const X&) // vessző
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!
String
tárolására alkalmas objektum, ami csak annyi helyet foglal a memóriában, amennyi feltétlenül szükséges. → dinamikus adatszerkezet
[]
=
==
(String + String)
, (String + char)
,(char + String)
cout <<
cin >>
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
};
#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
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
}
};
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;
}
};
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
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 kell referencia a másoló konstruktorhoz?
delete st
hatására csak a *st
, azaz az st[0]
destruktora hívódik meg! Az st[1]
és az st[2]
által foglalt memória nem szabadul fel! A delete[]
meghívja minden elem destruktorát.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;
}
class String {
// ...
String operator+(const String& s);
String operator+(char c);
};
String operator+(char c, const String &s) {
return String(c) + s;
}
operator=
és az operator+
meglétéből nem következik az operator +=
String
csupa nagybetűvel íródjon ki!Nem szükséges objektummal hivatkozni rá.
pl: String::SetUcase(true);
Felhasználás: globális változók elrejtése
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;
}
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
Csupán érdekesség (szorgalmi): C++11-től: Az osztály deklarációjában is lehet. Ha mindkettőben van, akkor a deklarációnál megadott nem hajtódik végre.
// 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
~$ 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
,
KomplexTar
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 !
}
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);
}
// 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];
}
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];
}
Default konstruktornak fontos szerepe van a tömböknél.
Létrehozás, megsemmisítés feladatait a konstruktor és destruktor látja el.
X() // nincs paramétere
automatikusan létrejön, ha nincs másik konstr.X(const X&) // referencia paramétere van
automatikusan létrejön: meghívja az adattagok másoló konstr.-át, ha objektumok, egyébként bitenként másol.operator=(const X&) // értékadó operátor
automatikusan létrejön: meghívja az adattagok értékadó operátorát, ha objektumok, egyébként bitenként másol.delete[] // [] nélkül csak a 0. tömbelemre!!
automatikusan létrejön: meghívja az adattagok destr.html
, latex
, rtf
, man
, … formátumban)/**
* 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