Aunque puede sobrecargar casi todos los operadores disponibles en C, el uso de
operadores sobrecargados es bastante restrictivo. En particular, no puede combinar
operadores que actualmente no tienen significado en C (como **
para
representar la potencia), no puede cambiar la precedencia de evaluación de operadores,
y tampoco el número de argumentos requeridos por un operador. Estas restricciones
existen para prevenir que la creación de nuevos operadores ofusquen el significado
en lugar de clarificarlo.
Las siguientes dos subsecciones muestran ejemplos de todos los operadores normales, sobrecargados en la forma habitual.
El siguiente ejemplo muestra la sintaxis para sobrecargar todos los operadores
unarios, en ambas formas: como funciones globales (funciones friend
, no
métodos) y como métodos. Estas expandirán la clase Integer
vista previamente y añadirá una nueva clase byte
. El
significado de sus operadores particulares dependerá de la forma en que los use,
pero considere a los programadores del grupo antes de hacer algo inesperado. He
aquí un catálogo de todas las funciones unarias:
//: C12:OverloadingUnaryOperators.cpp #include <iostream> using namespace std; // Non-member functions: class Integer { long i; Integer* This() { return this; } public: Integer(long ll = 0) : i(ll) {} // No side effects takes const& argument: friend const Integer& operator+(const Integer& a); friend const Integer operator-(const Integer& a); friend const Integer operator~(const Integer& a); friend Integer* operator&(Integer& a); friend int operator!(const Integer& a); // Side effects have non-const& argument: // Prefix: friend const Integer& operator++(Integer& a); // Postfix: friend const Integer operator++(Integer& a, int); // Prefix: friend const Integer& operator--(Integer& a); // Postfix: friend const Integer operator--(Integer& a, int); }; // Global operators: const Integer& operator+(const Integer& a) { cout << "+Integer\n"; return a; // Unary + has no effect } const Integer operator-(const Integer& a) { cout << "-Integer\n"; return Integer(-a.i); } const Integer operator~(const Integer& a) { cout << "~Integer\n"; return Integer(~a.i); } Integer* operator&(Integer& a) { cout << "&Integer\n"; return a.This(); // &a is recursive! } int operator!(const Integer& a) { cout << "!Integer\n"; return !a.i; } // Prefix; return incremented value const Integer& operator++(Integer& a) { cout << "++Integer\n"; a.i++; return a; } // Postfix; return the value before increment: const Integer operator++(Integer& a, int) { cout << "Integer++\n"; Integer before(a.i); a.i++; return before; } // Prefix; return decremented value const Integer& operator--(Integer& a) { cout << "--Integer\n"; a.i--; return a; } // Postfix; return the value before decrement: const Integer operator--(Integer& a, int) { cout << "Integer--\n"; Integer before(a.i); a.i--; return before; } // Show that the overloaded operators work: void f(Integer a) { +a; -a; ~a; Integer* ip = &a; !a; ++a; a++; --a; a--; } // Member functions (implicit "this"): class Byte { unsigned char b; public: Byte(unsigned char bb = 0) : b(bb) {} // No side effects: const member function: const Byte& operator+() const { cout << "+Byte\n"; return *this; } const Byte operator-() const { cout << "-Byte\n"; return Byte(-b); } const Byte operator~() const { cout << "~Byte\n"; return Byte(~b); } Byte operator!() const { cout << "!Byte\n"; return Byte(!b); } Byte* operator&() { cout << "&Byte\n"; return this; } // Side effects: non-const member function: const Byte& operator++() { // Prefix cout << "++Byte\n"; b++; return *this; } const Byte operator++(int) { // Postfix cout << "Byte++\n"; Byte before(b); b++; return before; } const Byte& operator--() { // Prefix cout << "--Byte\n"; --b; return *this; } const Byte operator--(int) { // Postfix cout << "Byte--\n"; Byte before(b); --b; return before; } }; void g(Byte b) { +b; -b; ~b; Byte* bp = &b; !b; ++b; b++; --b; b--; } int main() { Integer a; f(a); Byte b; g(b); } ///:~
Listado 12.2. C12/OverloadingUnaryOperators.cpp
Las funciones están agrupadas de acuerdo a la forma en que se pasan los argumentos. Más tarde se darán unas cuantas directrices de cómo pasar y devolver argumentos. Las clases expuestas anteriormente (y las que siguen en la siguiente sección) son las típicas, así que empiece con ellas como un patrón cuando sobrecargue sus propios operadores.
Los operadores de incremento++
y de decremento --
provocan un conflicto porque querrá ser capaz de llamar diferentes funciones
dependiendo de si aparecen antes (prefijo) o después (postfijo) del objeto sobre
el que actúan. La solución es simple, pero la gente a veces lo encuentra un poco
confusa inicialmente. Cuando el compilador ve, por ejemplo, ++a
(un
pre-incremento), genera una llamada al operator++(a)
pero
cuando ve a++
, genera una llamada a operator++(a,
int)
. Así es como el compilador diferencia entre los dos tipos,
generando llamadas a funciones sobrecargadas diferentes. En
OverloadingUnaryOperators.cpp
para la versión de funciones
miembro, si el compilador ve ++b
, genera una llamada a
B::operator++()
y si ve b++
genera una llamada a
B::operator++(int)
.
Todo lo que el usuario ve es que se llama a una función diferente para las
versiones postfija y prefija. Internamente, sin embargo, las dos llamadas de
funciones tienen diferentes firmas, así que conectan con dos cuerpos
diferentes. El compilador pasa un valor constante ficticio para el argumento
int
(el cual nunca se proporcionada por un identificador porque el
valor nunca se usa) para generar las diferentes firmas para la versión postfija.