A C++ nyelvben például a + és - operátoroknak több független funkciója van: aritmetikai műveleteket végez
egész számokkal, valós számokkal és mutatókkal. Hogy hogyan végezi a műveletet,
az a kontextustól függ, azaz hogy milyen adattípussal van használva. Minden
adattípusnál másképp dolgozik, azaz másképp van definiálva a művelet. Ezek a
definíciók vagy átdefiniálások úgymond túlterhelik az operátort (overloading).
Saját adattípussal is túl lehet terhelni az operátorokat, például osztállyal
vagy struktúrával létrehozott típussal. A túlterhelő függvény egy szimpla függvény,
csakhogy elébe van írva az operator kulcsszó, amit a túlterhelni kívánt operátor követ:
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_seBFWu47D6ZZkO4CqpSQb0Nm4PzV1guJr9eaPjGMYDqOFpa956BP6WuvcnfxjCKrE0ygjY5vzjWleJsiFzDxfV5cER4RPeQVn5m2cVU_7nNHPMCWQneKPfC1-9LDISjHNzYweQBDHg/s640/1.png)
Az operátorfüggvény általában tagfüggvény vagy
friend függvény lehet. A (), [] és -> operátorokat csakis
nem statikus tagfüggvényekkel lehet túlterhelni, a new és delete
operátorokat pedig statikus tagfüggvényekkel. Hogy milyen függvényt válasszunk
a túlterhelésre, az az operátortól függ.
- Amikor az operátorfüggvény tagfüggvény,
akkor a baloldali vagy az egyetlen operandus az osztályának az objektuma vagy
az arra hivatkozó referencia kell legyen. Ha a feladat úgy követlei, hogy a
baloldali operandus egy másik osztály objektuma legyen, akkor az
operátorfüggvény nem lehet tagfüggvény. Ha nem tagfüggvény, de közvetlenül el
kell érje az adott osztály privát vagy protected tagjait, akkor friend típusnak
kell deklarálni. Ha viszont az adott osztálynak van getter és setter tagfüggvénye a
privát tagváltozók módosításához, akkor nem kötelező a friend típus használata.
- Egy példa erre a << és a >> operátor, melyek baloldalán be- vagy
kimeneti stream-ek vannak. A << operátornak egy ostream& típusú operandusa kell legyen bal oldalt,
mint például a cout,
ezért az operátorfüggvénye nem lehet tagfüggvény. Ugyanez igaz a >> operátorra is.
- Egy másik ok, hogy az operátor ne legyen
tagfüggvény, a kommutativitás. Például egy long int típusú number objektum és egy HugeInteger típusú hugenumber objektum (amely a számítógép korlátainál
nagyobb egészeket képes összeadni) közötti összeadó művelet kommutatív kell
maradjon. A hugenumber+number
művelethez az operator+ függvény
maradhat tagfüggvény, de hogy a number+hugenumber is működjön, az operátorfüggvényt az osztályon kívül kell megírni.
A << és a >>
operátor túlterhelése
A PhoneNumber osztály a következő példából egy olyan adattípus,
amely egy 4+6 számjegyből álló telefonszámot határoz meg:
#include<iostream>
using std::cout;
using std::cin;
using std::ostream;
using std::istream;
#include<iomanip>
using std::setw;
class PhoneNumber
{
friend ostream&operator<<(ostream&,
const PhoneNumber&);
friend istream&operator>>(istream&,
PhoneNumber&);
private:
char areaCode[5];//4 prefix és '\0'
char number[7];
//6 szam és '\0'
};
ostream&operator<<(ostream&
out, const PhoneNumber& num)
{
out
<< num.areaCode <<"/"<<
num.number;
return out; // fűzér mód
}
istream&operator>>(istream&
in, PhoneNumber& num)
{
in
>> setw(5) >> num.areaCode;
in.ignore(1);
//figyelmen kívül hagyja a következő karaktert (/)
in >>
setw(7) >> num.number;
return in; // fűzér mód
}
int main()
{
PhoneNumber
phone;
cout
<<"Add meg a telefonszámot 0711/123456
formátumban: ";
cin
>> phone;
// a cin meghívja az operator>> függvényt a (cin,
phone) paraméterrel
cout
<<"A beírt telefonszám: "<<
phone;
// a cout meghívja az operator<< függvényt a (cout,
phone) paraméterrel
return 0;
}
A kimenet:
Add meg a
telefonszámot 0711/123456 formátumban: 0366/456231
A beírt
telefonszám: 0366/456231
Az operator>>
függvény a stream-ből (adatfolyamból) olvas ki, melynek első
paramétere egy istream referencia, a második pedig egy PhoneNumber referencia. Az első paraméter lehetőséget ad, hogy hozzáférjünk a cin-el beolvasott adatokhoz, a második pedig,
hogy betegyük őket a privát tagváltozókba. Amikor azt írjuk, hogy cin >> phone , akkor valójában az operator>>(cin,phone) függvény hívódik meg. A függvény
változatlanul vissza is téríti a kapott információt, így lehetőség van füzérszerűen
használni az operátort, például: cin >> phone1 >> phone2;Az operator<<
függvénynél is ugyanez a helyzet, a cout<<phone meghívásánál az operator<<(cout,phone) hívódik meg. Ez a két operátorfüggvény
friend típusú kell legyen, mert a PhoneNumberobjektum mindig jobboldali operandusként szerepel. Hogy tagfüggvények
legyenek az osztály objektuma baloldali operandusként kell szerepeljen.
Az unáris operátorok
túlterhelése
Az unáris (egy operandusú) operátor túlterhelő függvényét
tagfüggvényként is meg lehet írni, egy paraméterrel vagy paraméter nélkül. Ha
paramétere van, akkor az az osztály objektuma vagy az osztály objektumára hivatkozó referencia kell legyen. Legyen egy String nevű osztály, melyben átdefiniáljuk a ! operátort, hogy leellenőrizze, hogy az
objektum üres-e vagy sem. Ha például s a String osztály objektuma,
akkor a !s kifejezés az s.operator!() függvényt fogja meghívni. Az
operátorfüggvényt az osztályon belül deklaráljuk:
class String
{
public:
booloperator!() const;
...
};
Ha osztályon kívül deklaráljuk, akkor
argumentumként meg kell adni az osztály objektumát vagy annak referenciáját.
class String
{
friend booloperator!(const
String&);
...
};
Ebben az esetben a !s kifejezés az operator!(s) függvényt fogja meghívni. Ha
választhatunk, akkor kerüljük a friend függvények használatát.
A bináris operátorok
túlterhelése
A bináris (két operandusú) operátor túlterhelő függvényét
tagfüggvényként is meg lehet írni, egy vagy két paraméterrel, melyből az egyik
az osztály objektuma vagy annak referenciája. Legyen x és y a String osztály objektuma és legyen a += operátor ezen két objektum összekapcsoló
operátora. Ha tagfüggvénynek írjuk az operátorfüggvényt, akkor az y+=z kifejezés az y.operator+=(z) függvényt fogja meghívni.
class String
{
public:
const String&operator+=(const String&);
...
};
Ha friend-ként deklaráljuk, akkor bináris
operátorként terheljük túl és az y+=z
kifejezés az operator+=(y,z) függvényt
fogja meghívni.
class String
{
friendconst String&operator+=(String&,
const String&);
...
};
Típusátalakítás
A fordító sokféle típusátalakítást ismer, ám a programozó által
definiált típusokat nem tudja átalakítani. Ezért a programozó dolga, hogy
meghatározza miként lehet az ő típusait más típusokká alakítani. Ezt
legegyszerűbb az osztály konstruktorában megvalósítani, ahol más osztályok
objektumait konvertálhatjuk a jelenlegi osztály típusára. A String osztály konstruktora például
átalakíthatja a char* értékeket String típusú objektummá. Erre a célra ideális a
cast operátor, amelyet csakis tagfüggvényben
alkalmazhatunk. A függvény prototípusa:
A::operator char*()
const;
Ez egy cast túlterhelő függvényt (operátorfüggvényt)
deklarál, melyekkel ideiglenes char* típusú objektumok kreálhatók az A típusú objektumból. Nem kell meghatározni
a visszatérített típust, hisz az az objektum átkonvertált típusa lesz. Amikor a
fordító találkozik a (char*) s kifejezéssel, meghívja az s.operator char*() tagfüggvényt.
A cast és a konverziós
operátorok egyik hasznos tulajdonsága, hogy valahányszor szükség van rájuk, a
fordító meghívja őket hogy ideiglenes objektumokat hozzon létre. Például ha az s objektum a String osztályból olyan helyen jelenik meg, ahol
valójában char* kéne legyen, mint például a cout<<s kifejezésben, akkor a fordító meghívja a
túlterhelt cast operátort, hogy
átalakítsa az objektumot char* típusba. Ha létezik ez az
operátorfüggvény a String
osztályban, akkor nincs is már szükség a << operátor túlterhelésére a String objektumok kiírásához.
A String osztály
A String osztály a C++
standard könyvtárának része. Ettől függetlenül, a következő példában egy saját String osztály van megvalósítva, amely bemutatja
az operátorok túlterhelését.
string1.h
#ifndef STRING1_H
#define STRING1_H
#include <iostream>
using std::ostream;
using std::istream;
class String
{
friend ostream& operator<<(ostream&,
const String&);
friend istream& operator>>(istream&,
String&);
public:
String(const char* = ""); //átalakító konstruktor: char* -> objektum
String(const String&);
//másoló konstruktor
~String();
const String& operator=(const String&); //érték adás
const String& operator+=(const String&); //összekapcsolás
bool operator!() const; //üres-e ?
bool operator==(const String&) const;
//s1==s2
?
bool operator<(const String&) const; //s1<s2
?
bool operator!=(const String& right) const
//s1!=s2 ?
{
return !( *this ==
right );
}
bool operator>(const String& right) const // s1>s2 ?
{
return right < *this;
}
bool operator<=(const String& right) const
//
s1<=s2 ?
{
return !( right < *this
);
}
bool operator>=(const String& right) const
//
s1>=s2 ?
{
return !( *this <
right );
}
char& operator[](int); //indexelés
const char& operator[](int) const; //indexelés
String
operator()(int,
int); //substringet térít vissza
int getLength() const; //a
string hossza
private:
int length; //string hossz
char* sPtr; //mutató a string elejére
void setString(const char*); //segítő függvény:
beállítja az sPtr mutatót
};
#endif
string1.cpp
#include <iostream>
using std::cout;
using std::endl;
#include <iomanip>
using std::setw;
#include <cstring>
#include <cassert>
#include"string1.h"
//Átlakító konstruktor:
char*->objektum
String::String(const
char* s) : length(strlen(s))
{
cout
<< "Átalakító konstruktor: "
<< s << '\n';
setString(s);
//beállítja az sPtr mutatót az s elejére
}
//Másoló konstruktor
String::String(const
String& copy) : length(copy.length)
{
cout
<< "Másoló konstruktor: "
<< copy.sPtr << '\n';
setString(copy.sPtr);
}
//Destruktor
String::~String()
{
cout
<< "Destruktor: " <<
sPtr << '\n';
delete[] sPtr; //memória
felszabadítás
}
//Az = operátor túlterhelése:
stringek másolása
const String& String::operator=(const String& right)
{
cout
<< "Az = operátor meghívódott\n";
if(&right != this) //ha nem
saját magával tettük egyenlővé
{
delete[] sPtr; //a régi string törlése (ha netán volt tartalma)
length
= right.length;//az új string hossza
setString(right.sPtr);//az új string mutatója
}
else
cout
<< "Nem tehető saját magával
egyenlővé\n";
return *this; //fűzér mód
}
//A += operátor túlterhelése:
stringek összekapcsolása
const String& String::operator+=(const String& right)
{
char* tempPtr = sPtr; //elmenti a bal oldali stringet
length
+= right.length; //új hossz
sPtr
= new char[length+1]; //új memóriaterület
lefoglalása
assert(sPtr!=0); //abort,
ha nem tudta lefoglalni
strcpy(sPtr,
tempPtr); //új string = régi string
strcat(sPtr,
right.sPtr); //
+ az új string
delete[] tempPtr; //a régi string türlése
return *this; //fűzér mód
}
//Üres a string?
bool String::operator!()
const
{
return length==0;
}
//Egyenlő a jobb oldali stringgel?
bool String::operator==(const String& right) const
{
return strcmp(sPtr, right.sPtr)==0;
}
//Stringek összehasonlítása
bool String::operator<(const String& right) const
{
return strcmp(sPtr, right.sPtr)<0;
}
//Visszatéríti a string[i] karakter
referenciáját mint lvalue
char& String::operator[](int i)
{
assert(i>=0
&& i<length); //abort, ha az index a
korlátokon kívül van
return sPtr[i]; // lvalue
}
//Visszatéríti a string[i] karakter
referenciáját mint rvalue
const char&
String::operator[](int
i) const
{
assert(i>=0
&& i<length); //abort, ha az index a
korlátokon kívül van
return sPtr[i]; // rvalue
}
//Substring: indextől indulva
subLength hosszúsággal
String String::operator()(int index, int
subLength)
{
assert(index>=0
&& index<length && subLength>=0); // abort ha nem teljesül
int len; // a substring hossza
if(subLength==0)
len
= length-index;
else
len
= subLength;
char* tempPtr = new char[len+1]; //a substring memóriahelye+'\0'
assert(tempPtr!=0); //abort,
ha nem sikerül lefoglalni
strncpy(tempPtr,
&sPtr[index], len);//bemásolja a substringet a
memóriába
tempPtr[len]='\0'; //string vége karakter
String
tempString(tempPtr); //a substring is
String típusú objektum
delete[] tempPtr; //felszabadítja a substring mutató cimét
return tempString;
}
//tagfüggvény, amely megadja a
length tagváltozó értékét
int String::getLength() const
{
return length;
}
//segédfüggvény: beállítja az sPtr
mutatót a string elejére
void String::setString(const
char* string2)
{
sPtr
= new char[length+1];
//memória lefoglalás
assert(sPtr!=0);
//abort, ha nem
sikerül lefoglalni
strcpy(sPtr,
string2);
}
//Objektumot kiíró operátor
ostream& operator<<(ostream&
output, const String& s)
{
output
<< s.sPtr;
return output;
}
//Objektumot beolvasó operátor
istream& operator>>(istream&
input, String& s)
{
char temp[100]; //100 karakterláncot fogad be (a '\0'-val együtt)
input
>> setw(100)>>temp; // csak az első 99
karaktert veszi figyelembe
s =
temp; //az
s objektum megkapja a beolvasott értéket
return input;
//lehetővé teszi a fűzérszerű alkalmazást
}
- A program első két friend függvénye ugyanúgy van megvalósítva, mint
az előző program PhoneNumber osztályában.
Az egyik kiírja, a másik beolvassa az objektumot. Minden függvény, ami nem
változtat az eredeti objektumon, az konstansnak van deklarálva.
- A konstruktort lehet típus átalakítónak
tekinteni, mert egy paramétere van (ami éppen egy karakterlánc), és abból kreál
objektumot. Ez azt jelenti, hogy minden tagváltozóját inicializálja abból az
egy paraméterből. A length
tagváltozó már a fejlécben megkapja a karakterlánc hosszát, a setString() segédfüggvény pedig beállítja az sPtr mutatót a karakterlánc elejére.
- Ez után egy újabb konstruktor
következik. Az osztálynak lehet több konstruktora és mindig az hívódik meg
amelyikre éppen szükség van. Az átalakítóra mindig szükség van valahányszor char* típussal dolgozunk, viszont szükség van egy másoló konstruktorra is,
például amikor egy objektumot egy másik alapján hozunk létre. Ez gyakorlatilag
ugyanolyan mint az átalakító konstruktor, csak karakterlánc helyett egy létező
objektum referenciáját kapja meg.
- A destruktor dolga, hogy felszabadítsa a
dinamikusan lefoglalt memóriát.
- Az értékadó (=) operátor az objektumok másolását végzi. Ugyanez
a szerepe túlterhelés nélkül is, azonban itt az átdefiniált változat hívódik
meg. Az operátor egy objektum paramétert kap a jobb oldalára, amibe bemásolja a
bal oldali objektum összes értékét. Végül pedig visszatéríti az eredeti
objektumot, hogy füzérszerű másolást is lehessen végezni.
- Az összekapcsoló (+=) operátorfüggvény a jobb oldalára kapott
objektumot hozzáadja a bal oldali objektumhoz. Ez abból áll, hogy kiveszi a két
objektumból a stringeket (az első karakterre mutató mutatókat), majd az strcat paranccsal összefűzi őket. Az objektum
hosszait pedig összeadja, és az új értékekkel felülírja az eredeti (bal oldali)
objektumot.
- A String objektum ürességét a ! operátor
ellenőrzi. Ez 1-et térít vissza, ha a length tagváltozó nulla.
- A String objektumok összehasonlíthatók egymással: ==, !=, <, >, <=, >=. Erre jó a strcmp parancs, mely binárisan hasonlítja össze két bemenetének karaktereit. Nullát
térít vissza, ha minden karakter talál, negatívat, ha a bal oldal rövidebb mint
a jobb, és pozitívat ellenkező esetben. Elegendő volt az == és a < operátoroknál használni ezt és a cstring header fájl miatt a string1.cpp-ben került sor a definíciókra. A többi
összehasonlító operátor a string1.cpp fájlban van definiálva az == és a < túlterhelt operátorok segítségével.
- A string adott karakterére a [] operátorfüggvény hivatkozik. Ez leellenőrzi a keresett index hitelességét,
majd visszatéríti az sPtr[] értékét. Ez lehet lvalue (amikor az index
egy memóriacímmel rendelkező változó, például s[i]) és rvalue (amikor az indexnek nincs pontos memóriacíme, például s[25] vagy s[i+7]).
- A ()operátor egy substring-et vesz ki a
stringből, például az s1(0,14) kiírja a nulladik pozíciótól vett első 14 karaktert. A függvény
leellenőrzi a paramétereket, majd beállítja a substring hosszát. Ha a kért substring
nulla hosszúságú, akkor kiírja az indextől a string végéig. A substringnek
dinamikusan van lefoglalva a memória a tempPtr mutató révén, amibe bemásoljuk az eredeti
stringből az index pozíciótól számítva
a kért hosszúságú karakterláncot. Az erre megfelelő parancs a strncpy. Ezután String típusú objektumot
készítünk a karakterláncból (amit a konstruktor végez el), majd visszatérítjük
az objektumot.
test_string1.cpp
#include <iostream>
using std::cout;
using std::endl;
#include"string1.h"
int main()
{
String
s1("happy"), s2(" birthday"), s3;
//relációs operátorok tesztelése
cout
<< "s1 = \"" <<
s1 << "\"; s2 = \""
<< s2 << "\"; s3 =
\"" << s3 << '\"'
<< "\ns2
== s1: " << (s2==s1 ? "true"
: "false")
<< "\ns2
!= s1: " << (s2!=s1 ? "true"
: "false")
<< "\ns2
> s1: " <<
(s2>s1 ? "true"
: "false")
<< "\ns2
< s1: " <<
(s2<s1 ? "true"
: "false")
<< "\ns2
>= s1: " << (s2>=s1 ? "true"
: "false")
<< "\ns2
<= s1: " << (s2<=s1 ? "true"
: "false");
//a ! operátor tesztelése
if(!s3)
{
cout
<< "\n\nAz s3 üres; Legyen s3 =
s1;\n";
s3
= s1; //az = operátor tesztelése
cout
<< "s3 = \"" <<
s3 << "\"";
}
//az összekapcsoló operátor tesztelése
s1 +=
s2;
cout
<< "\n\ns1 += s2 => s1 = "
<< s1 << endl;
//az átalkító konstruktor tesztelése
s1 +=
" to you";
cout
<< "s1 += \" to you\" => s1
= " << s1 << "\n\n";
//a substring operátor tesztelése
cout
<< "s1(0,11): " <<
s1(0,11) << "\n"; //0. indextől 11 karakter hosszú
cout
<< "s1(11,0): " <<
s1(11,0) << "\n"; //11. indextől a string végéig
cout
<< endl;
//a másoló konstruktor tesztelése
String*
s4Ptr = new String(s1);
cout
<< "*s4Ptr = " <<
*s4Ptr << "\n\n";
//az = operátor tesztelése saját magával (kondício)
cout
<< "*s4Ptr = *s4Ptr kifejezes \n";
*s4Ptr
= *s4Ptr;
cout
<< endl;
//a destruktor tesztelése
delete s4Ptr;
//az index operátor tesztelése
s1[0]
= 'H';
s1[6]
= 'B';
cout
<< "\ns1 az s1[0] = 'H' és s1[6] = 'B'
után: " << s1 << "\n\n";
//helytelen index tesztelése
cout
<< "s1[30]='d' kifejezés "
<< endl;
s1[30]
= 'd';
//Assertion failed!
system("pause"); //
hogy ne záródjon be a konzol ablaka
return 0;
}
A kimenet:
Átalakító konstruktor: happy
Átalakító konstruktor: birthday
Átalakító konstruktor:
s1 =
"happy"; s2 = " birthday"; s3 = ""
s2 == s1: false
s2 != s1: true
s2 > s1:
false
s2 < s1: true
s2 >= s1:
false
s2 <= s1:
true
Az s3 üres;
Legyen s3 = s1;
Az = operátor
meghívódott
s3 =
"happy"
s1 += s2 =>
s1 = happy birthday
Átalakító konstruktor: to you
Destruktor: to you
s1 += " to
you" => s1 = happy birthday to you
Átalakító konstruktor: happy birth
s1(0,11): happy
birth
Destruktor:
happy birth
Átalakító konstruktor: day to you
s1(11,0): day to
you
Destruktor: day
to you
Másoló konstruktor: happy birthday to you
*s4Ptr = happy
birthday to you
*s4Ptr = *s4Ptr
kifejezés
Az = operátor
meghívódott
Nem tehető saját
magával egyenlővé
Destruktor:
happy birthday to you
s1 az s1[0] =
'H' es s1[6] = 'B' után: Happy Birthday to you
s1[30]='d'
kifejezés
Assertion
failed!
Nincsenek megjegyzések:
Megjegyzés küldése