Operatory¶
Przeciążanie operatorów¶
C++ umożliwia definiowanie operatorów dla typów zdefiniowanych przez użytkownika.
|
|
|
|
|
|
|
---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Operatory, które nie mogą być przeciążane.
|
|
|
|
|
|
|
|
Operatory dla klas zdefiniowanych przez użytkownika mogą być przeciążane jako:
operatorowa funkcja składowa klasy
globalna funkcja operatorowa
Funkcja operatorowa – funkcja o nazwie operator@
, gdzie @
jest znakiem operatora.
void f(const Complex& a, const Complex& b)
{
Complex c = a + b;
Complex d = a.operator+(b);
}
Operatory dwuargumentowe – binarne¶
Operator binarny aa@bb
może być zdefiniowany jako:
niestatyczna metoda przyjmująca jeden argument
aa.operator@(bb)
funkcja globalna przyjmująca dwa argumenty
operator@(aa, bb)
class X
{
public:
X(int);
void operator+(int);
};
void operator+(X, X);
void operator+(X, double);
void f(X a)
{
a + 1; // a.operator(1)
1 + a; // ::operator+(X(1), a)
a + 1.0; // ::operator+(a, 1.0)
}
Operatory jednoargumentowe¶
Operator jednoargumentowy może być zdefiniowany jako
@a
niestatyczna metoda -
aa.operator@()
globalna funkcja –
operator@(aa)
a@
niestatyczna metoda -
aa.operator@(int)
globalna funkcja –
operator@(aa, int)
class X
{
public:
X* operator&(); // operator pobrania adresu
X operator&(X); // dwuargumentowy X&X
};
X operator-(X); // jednoargumentowy minus
Operatory ++
i --
¶
Operatory ++
i --
występują w postaci:
przedrostkowej (zwiększ i pobierz)
przyrostkowej (pobierz i zwiększ)
Problem z przeciążeniem - operatory te nie pobierają argumentu. Operatory w postaci przyrostkowej pobierają argument typu int
(kompilatory w momencie wywołania przekazują 0
jako argument typu int
).
Operatory te przekazują różne typy. Operator przedrostkowy przekazuje referencję. Operator przyrostkowy przekazuje obiekt z modyfikatorem const
.
class Integer
{
int value_ = 0;
public:
Integer& operator++(); // przedrostkowy ++i
const Integer operator++(int); // przyrostkowy i++
Integer& operator+=(int);
};
Integer i;
++i; // wywołuje i.operator++();
i++; // wywołuje i.operator++(0);
Integer& Integer::operator++()
{
*this += 1; // zwiększ
return *this; // pobierz
}
const Integer Integer::operator++(int)
{
Integer old_value = *this;
++(*this);
return old_value;
}
int i;
++++i; // OK
i++++; // Błąd! i.operator++(0).operator++(0)
Operatory <<
i >>
¶
Aby przeciążyć operatory <<
i >>
dla typów definiowanych przez użytkownika należy zdefiniować globalne funkcje operatorowe. Jeżeli mają one mieć dostęp do prywatnych składowych klasy X
należy umieścić w klasie deklarację przyjaźni z tymi funkcjami.
class X
{
int value_ = 0;
public:
friend std::ostream& operator<<(std::ostream&, const X&);
friend std::istream& operator>>(std::istream&, const X&);
};
std::ostream& operator<<(std::ostream& out, const X& x)
{
out << x.value_;
return out;
}
std::istream& operator>>(std::istream& in, const X& x)
{
in >> x.value_;
return in;
}
// ...
X x;
std::cout << "X = " << x << std::endl;
Literały - operator ""
¶
W C++11 możliwe jest definiowanie literałów dla typów definiowanych przez użytkownika przy pomocy funkcji operatora literalnego.
Nazwa operatora literalnego to operator ""
wraz z odpowiednim sufiksem, np:
constexpr complex<double> operator"" i(long double d)
{
return {0, d};
}
std::string operator"" s(const char* p, size_t n)
{
return std::string{p, n};
}
Mechanizm tworzenia literałów użytkownika umożliwia zdefiniowanie nowego sufiksu dla literału, który go poprzedza. Istnieją cztery rodzaje literałów, które są akceptowane przy definicji literału użytkownika:
literał całkowity -
unsigned long long
lubconst char*
literał zmiennoprzecinkowy -
long double
lubconst char*
literał tekstowy - akceptujący parę argumentów
(const char*, size_t)
typ znakowy
char
,wchar_t
,char16_t
ichar32_t
Przykład 1:
Bignum operator"" x(const char* p)
{
return Bignum(p);
}
void f(Bignum);
f(123456789012345678901234567890123456789012345x);
Przykład 2:
class Integer
{
int value_ = 0;
public:
constexpr Integer(int value = 0) : value_{ value }
{}
constexpr int value() const
{
return value_;
}
};
constexpr Integer operator "" _i(unsigned long long x)
{
return Integer{ static_cast<int>(x) };
}
// ...
auto x = 42_i;
assert(x.value() == 42);
Operatory rzutowania w C++¶
Klasyczne operatory rzutowania¶
Klasyczne rzutowanie typu
char* hopeItWorks = (char*)0x00ff0000;
Możliwy jest też inny sposób wyrażenia konwersji
typedef char* PChar;
hopeItWorks = Pchar(0x00ff0000); // rzutowanie ze składnią wywołania funkcji
Oba sposoby rzutowania nie są zalecane w C++
Ograniczone operatory rzutowania w C++¶
W C++ wprowadzono cztery nowe operatory rzutowania:
static_cast
const_cast
dynamic_cast
reinterpret_cast
Operator const_cast
¶
Operator const_cast
pozwala na dodawanie lub usuwanie z typu kwalifikatorów const
i volatile
.
const Person* get_employee()
{
//...
}
//...
Person* anEmployee = const_cast<Person*>( get_employee() );
// odpowiednik klasycznego
anEmployee = (Person*)getEmployee();
Operator static_cast
¶
Operator static_cast
służy do rzutowania:
„w dół” hierarchii dziedziczenia – ze wskaźnika bądź referencji klasy bazowej na wskaźnik lub referencję klasy pochodnej (bez kontroli w czasie wykonania [run-time type check])
między typami liczbowymi (np. float na int)
class B {};
class D : public B {};
void f(B* pb, D* pd)
{
D* pd2 = static_cast<D*>(pb); // niebezpieczne, pb może wskazywać na B
B* pb2 = static_cast<B*>(pd); // bezpieczna konwersja
}
Operator dynamic_cast
¶
Operator dynamic_cast
służy do:
bezpiecznego rzutowania wskaźnika lub referencji klasy bazowej na wskaźnik lub referencję klasy pochodnej
rzutowanie można przeprowadzić jedynie dla typu polimorficznego
w przypadku nieudanego rzutowania wskaźników zwracany jest
nullptr
, a w przypadku referencji rzucany jest wyjątek std::bad_cast
// bezpieczna konwersja z kontrolą typów
const Circle* cp = dynamic_cast<const Circle*>(get_next_shape());
if (cp)
{
std::cout << "r: " << cp->radius() << std::endl;
}
try
{
const Circle& rc = dynamic_cast<const Circle&>(*get_next_shape());
}
catch(const std::bad_cast& e)
{
//...
}
Operator reinterpret_cast
¶
Operator reinterpret_cast
pozwala na konwersję typu całkowitego na dowolny typ wskaźnikowy i odwrotnie. Rzutowania przy pomocy reinterpret_cast
, zwykle nie będą przenośne między architekturami.
char* hope_it_works = reinterpret_cast<char*>(0x00ff0000);
int* hopeless = reinterpret_cast<int*>(hope_it_works);