Artículos de la serie:
- Un ejemplo sencillo: los números complejos
- Clases, atributos y funciones miembro
- Plantillas de clase
- Sobrecarga de operadores
- Literales definidos por el usuario
Sobrecarga de operadores
Llegados a este punto, desearíamos habilitar las operaciones aritméticas básicas de los números complejos (suma, diferencia, producto de números complejos y producto por escalares reales). En primer lugar, sobrecargaremos los operadores binarios de asignación compuesta +=, -= y *= en la interfaz pública de la clase. Estos operadores permitirán calcular la suma, diferencia o multiplicación de un objeto dado (considerado de manera implícita como operando izquierdo) con otro número real o complejo (operando derecho), almacenándose el resultado en el operando izquierdo. Así, por ejemplo, la expresión x+=y resultará ser equivalente a x=x+y. Emplearemos el operador operator* para denotar tanto el producto por números complejos como el producto por escalares reales:
template<typename T>
requires std::floating_point<T>
class Complex {
// ...
public:
// ...
constexpr auto& operator+=(Complex const& z) noexcept
{
re_ += z.re_;
im_ += z.im_;
return *this;
}
constexpr auto& operator+=(T t) noexcept
{
re_ += t; // im_ permanece inalterado
return *this;
}
constexpr auto& operator-=(Complex const& z) noexcept
{
re_ -= z.re_;
im_ -= z.im_;
return *this;
}
constexpr auto& operator-=(T t) noexcept
{
re_ -= t; // im_ permanece inalterado
return *this;
}
constexpr auto& operator*=(Complex const& z) noexcept
{
T const r = re_*z.re_ - im_*z.im_;
im_ = re_*z.im_ + im_*z.re_;
re_ = r;
return *this;
}
constexpr auto& operator*=(T t) noexcept
{
re_ *= t;
im_ *= t;
return *this;
}
};
Una asignación del tipo a+=b es, en este caso, equivalente a a.operator+=(b). Como es usual, las funciones miembro anteriores retornan una referencia al propio objeto asignado con el fin de garantizar la asociatividad de izquierda a derecha de los operadores, permitiéndose así la concatenación de asignaciones del tipo a+=b+=c (equivalente a a+=(b+=c)). Se permiten también operaciones de auto-asignación del tipo a*=a. A modo de ejemplo del funcionamiento de las funciones anteriores, se proporciona el código siguiente:
auto x = Complex<double>{2.0, 3.0};
auto y = Complex<double>{1.0, 4.0};
x *= y; // ahora x = (-10,11)
y += x; // ahora y = (-9,15)
Por su parte, los operadores aritméticos usuales de suma, diferencia y producto deben ser implementados como funciones externas a la plantilla, y no como funciones miembro como en el caso de los operadores de asignación compuesta, con el fin de que tanto objetos de tipo Complex<T> como escalares de tipo T puedan ser aceptados como primer operando. En efecto, una función miembro de signatura
constexpr auto Complex::operator+(Complex const&) noexcept-> Complex&;
permitiría expresiones de la forma x+y (equivalente a x.operator+(y)), pero imposibilitaría aquellas otras de la forma 5.0+x, dado que el primer operando (un simple escalar) no es de tipo Complex<T> y, por tanto, la llamada al operador produciría un error de compilación. Se proporcionan, a continuación, las funciones relativas a la suma de números complejos, parametrizadas también con un tipo escalar genérico T al igual que la plantilla de clase Complex<T>. La implementación de las funciones correspondientes a la diferencia y el producto constituye una mera adaptación de código que dejaremos como ejercicio al lector:
template<typename T>
constexpr auto operator+(Complex<T> const& x, Complex<T> const& y) noexcept
{
auto r = x;
r += y;
return r;
}
template<typename T>
constexpr auto operator+(Complex<T> const& x, T y) noexcept
{
auto r = x;
r += y;
return r;
}
template<typename T>
constexpr auto operator+(T x, Complex<T> const& y) noexcept
{
auto r = y;
r += x;
return r;
}
// etc
A modo de ejemplo:
auto x = Complex<double>{2.0, 3.0};
auto y = Complex<double>{1.0, 4.0};
auto z = 1.0 + x*(2.0 + 3.0*y); // z = (-25,39) de tipo Complex<double>
Tan sólo resta sobrecargar los operadores de relación con el fin de poder determinar si dos números complejos (o un número complejo y un escalar real) son idénticos:
template<typename T>
constexpr auto operator==(Complex<T> const& x, Complex<T> const& y) noexcept -> bool
{
return x.real() == y.real() and x.imag() == y.imag();
}
template<typename T>
constexpr auto operator==(Complex<T> const& x, T t) noexcept -> bool
{
return x.real() == t and x.imag() == T{};
}
template<typename T>
constexpr auto operator==(T t, Complex<T> const& y) noexcept -> bool
{
return t == y.real() and T{} == y.imag();
}
// código similar para operator!= ...
Así, por ejemplo:
auto x = Complex<double>{2.0, 3.0};
auto y = Complex<double>{1.0, 4.0};
std::cout << std::boolalpha << (x == y); // output: false
Comprobamos, pues, que la sobrecarga de operadores en el lenguaje C++ es una herramienta de enorme utilidad que permite operar con los números complejos a través de una sintaxis natural (también empleada con los tipos numéricos primitivos como int o double) análoga a la encontrada en los textos matemáticos.
No hay comentarios:
Publicar un comentario