Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.9.18b2  Sobrecarga de operadores aritméticos

§1  Sinopsis

En el presente epígrafe trataremos la sobrecarga de operadores aritméticos (binarios) que no hayan sido expuestos anteriormente. Como ejemplo, expondremos casos de sobrecarga para el operador multiplicación * aplicado a objetos de la clase Vector que venimos utilizando repetidamente en nuestros ejemplos.

Suponemos que los miembros de la clase Vector representan vectores libres en un espacio de dos dimensiones y que las variables x e y representan las coordenadas cartesianas del vector respecto a un sistema de ejes [1]:

class Vector { public:  float x, y; }


Definimos tres productos: el producto de un vector v por un escalar m  (V . m ); el producto vectorial de dos vectores V1 ^ V2, y el producto escalar (también entre vectores) V1 x V2. Con objeto de disponer de un álgebra mínima para operar con los elementos de la clase, sobrecargamos también los operadores de igualdad ==  y de suma + binaria, utilizando en lo posible paso de argumentos por referencia ( 4.2.3) en las nuevas funciones.

Intentaremos que el diseño de estos operadores coincida con el usual para el álgebra de vectores en los textos de física elemental, y comentaremos las dificultades que presenta el lenguaje para conseguir un mimetismo perfecto.


§2  Recuerde que siendo @ un operador binario, la expresión x @ y puede ser interpretada de dos formas [2]:

a.  Declarando una función miembro no estática que acepte un argumento:  x.operator@(y)

b.  Declarando una función no miembro que acepte dos argumentos: operator@(x, y)


§3.1   V1 == V2    Igualdad de vectores

Decimos que se cumple la relación de igualdad si los componentes escalares son respectivamente iguales. En términos de geometría vectorial decimos que se trata de una Equipolencia o igualdad geométrica. La condición para que la relación  V1 == V2 sea cierta puede ser expresada:

V1x == V1y   &&   V2x == V2y

Recuerde que en esta expresión utilizamos la versión global (para los tipos básicos -float-) del operador == y del operador && para los tipos bool.

Ya hemos visto ( 4.9.18b1) que la función-operador que cumple la especificación propuesta es la siguiente:

bool operator== (const Vector& v) {   // V == V
  return ((v.x == x) && (v.y == y))? true: false;
}

definición que se ha incluido en L.17 del programa resultante . En estas condiciones la relación cumple las propiedades reflexiva, simétrica y transitiva.


§3.2   V1 + V2    Suma de vectores

Definimos la suma binaria entre vectores como una operación cerrada respecto a los vectores (que produce otro vector) cuyos componentes escalares son la suma de los componentes escalares de los sumandos.  En otras palabras, si: Vr == V1 + V2, se cumple que:

Vrx == V1x + V2x

Vry == V1y + V2y

La definición de la función-operador correspondiente es:

Vector operator+ (const Vector& v) {    // V + V
    Vector vr;
    vr.x = x + v.x;   vr.y = y + v.y;
    return vr;
  }

Con esta definición (L.20 del programa resultante ), la operación suma es conmutativa; asociativa; tiene el elemento neutro, y el opuesto [3].

Ver otro ejemplo de sobrecarga del operador suma en ( 4.9.18d1).


§3.3   V · m    Producto de un vector por un escalar

El resultado es un vector cuyo módulo es igual al módulo del vector multiplicado por el escalar. La consecuencia es que este producto es conmutativo:

Vr  ==  V · m  ==  m · V

Vrx = Vx * m

Vry = Vy * m

En estas condiciones, la primera dificultad que se presenta es el identificador que utilizaremos para representar el operador correspondiente en el código fuente. En el texto escrito estamos utilizando el símbolo · ( V · m ), pero sabemos ( 4.9.18) que no es posible designar un nuevo token como operador, por lo que tendremos que utilizar el símbolo * para expresar este producto en el código. Observe que no existe posibilidad de confusión al respecto; en cualquiera de las formas: producto por la izquierda (V * m) o por la derecha (m * V), los argumentos no pueden ser confundidos con ningún otro caso (uno es tipo numérico, el otro Vector).

Las funciones que responden al diseño son:

Vector operator* (const Vector& v, float m ) {  // V . m
   Vector vr;
   vr.x = v.x * m;   vr.y = v.y * m;
   return vr;
}

Vector operator* (float m, const Vector& v ) {  // m . V
   Vector vr;
   vr.x = m * v.x;   vr.y = m * v.y;
   return vr;
}

En este caso las utilizamos como funciones externas (friend) que aceptan dos argumentos (L.24/25 del programa resultante ).

Observe que desde el punto de vista del resultado el producto es conmutativo, pero no desde el punto de vista de nuestros operadores, por lo que tenemos que definir dos funciones con los argumentos en el orden adecuado para cada una de las posibilidades (izquierda y derecha).


§3.4   V1 ^ V2    Producto vectorial de dos vectores

Es posible dar una definición geométrica de este producto entre vectores, aunque para nuestro propósito nos limitaremos a decir que el resultado es un vector perpendicular al plano de los operandos, cuyo módulo viene determinado por: V1x * V2y - V1y * V2x. Este valor representa el área del paralelogramo definido por ambos vectores.

Nota: en el apartado §4.13.2 (Clases dentro de clases) se incluye un ejemplo que utiliza esta propiedad del producto vectorial para calcular un área ( 4.13.2), incluyendo una figura con su representación geométrica.

Aunque aquí, en el texto impreso, utilizamos el símbolo ^ para indicar el producto vectorial ( V1 ^ V2 ), para su representación en el fuente nos encontramos nuevamente con el inconveniente del identificador (no es posible definir un nuevo token como identificador de un operador), por lo que utilizaremos también el operador * para representar este producto (una versión de la función-operador operator*). Afortunadamente no existe posibilidad de confusión con las definiciones anteriores porque en este caso ambos argumentos de la función-operador son tipo Vector. Además, como nuestro programa se refiere al plano, tomaremos la convención de que este producto devuelve un objeto de tipo Vz. Una clase que definimos para contener vectores en el eje Z que es perpendicular al plano de la clase Vector. Su diseño es el siguiente:

class Vz {
  public: float z;
  void operator()() { cout << "VectorZ: (" << z << ")" << endl; }
};

La función operador que responde a la definición es (L.26 del programa resultante ):

Vz operator* (const Vector& v1, const Vector& v2) {      // V ^ V
  class Vz v;
  v.z = v1.x * v2.y - v1.y * v2.x;
  return v;
}

Como puede verse, el producto vectorial no resulta conmutativo, por lo que en general V1 ^ V2  !=  V2 ^ V1


§3.5   V1 x V2    Producto escalar de dos vectores

El producto escalar de dos vectores se define diciendo que produce un escalar m cuyo valor expresado en función de las componentes de los operandos es:  V1x * V2x + V1y * V2y (suma de productos de los componentes escalares de los operandos)

El operador es conmutativo, y aunque en el texto hemos utilizado el signo x para representarlo ( V1 x V2 ) nos vuelve a presentar el problema del identificador. Además, en este caso no podemos ya utilizar el token * porque todas las posibilidades de ambos argumentos están ocupadas.

Puesto que el cociente entre vectores no está definido, ni lo necesitamos en nuestro programa, utilizamos el token %, correspondiente al operador aritmético módulo ( 4.9.1) para sobrecargarlo y representar con él nuestros productos escalares (en la línea 8 de la función main del programa resultante se ha utilizado con este significado).

La versión sobrecargada de la función correspondiente (L.27 del programa resultante ), queda de la siguiente forma:

float operator% (const Vector& v1, const Vector& v2) {    // V x V
  return v1.x * v2.x + v1.y * v2.y;
}

Observe que este cambio de significado del operador % respecto a los objetos de la clase Vector, aunque atípico, es perfectamente válido y puede servir a nuestros propósitos. Además tenemos la suerte que la asociatividad y precedencia de nuestro "producto escalar" es la habitual para el operador producto * (4.9.0a), así que las expresiones de nuestra particular "álgebra" vectorial deben funcionar razonablemente bien.

§4  Ejemplo

El programa resultante es como sigue:

#include <iostream>
using namespace std;
#define Linea(Lin) cout << "Linea " #Lin << ": "; // funcion auxiliar

class Vz {                                               // L.5
  public: float z;
  void operator()() {                                    // L.7
   cout << "VectorZ (" << z << ")" << endl;
  }
};

class Vector {
  public: float x, y;
  void operator()() {                                    // L.14
    cout << "VectorXY (" << x << ", " << y << ") " << endl;
  }
  bool operator== (const Vector& v) {                    // L.17
    return ((v.x == x) && (v.y == y))? true: false;
  }
  Vector operator+ (const Vector& v) {                   // L.20
    Vector vr;   vr.x = x + v.x;   vr.y = y + v.y;
    return vr;
  }
  friend Vector operator* (const Vector&, float );       // L.24: V . m
  friend Vector operator* (float, const Vector& );       // L.25: m . V
  friend Vz operator* (const Vector&, const Vector&);    // L.26: V ^ V
  friend float operator% (const Vector&, const Vector&); // L.27: V x V
};

Vector operator* (const Vector& v, float m ) {           // V . m
  Vector vr;   vr.x = v.x * m;   vr.y = v.y * m;
  return vr;
}
Vector operator* (float m, const Vector& v ) {           // m . V
  Vector vr;   vr.x = m * v.x;   vr.y = m * v.y;
  return vr;
}
Vz operator* (const Vector& v1, const Vector& v2) {      // V ^ V
  class Vz v;   v.z = v1.x * v2.y - v1.y * v2.x;
  return v;
}
float operator% (const Vector& v1, const Vector& v2) {   // V x V
  return v1.x * v2.x + v1.y * v2.y;
}

void main () {   // =============
  Vector v1 = {2, 3};           // M.1
  Vector v2 = {4, 5};           // M.2
  Vector v3 = v1 + v2;  Linea(3);  v3();
  Vector v4 = 5 * v1;   Linea(4);  v4();
  Vector v5 = v1 * 5;   Linea(5);  v5();
  Vz v6 = v1 * v2;      Linea(6);  v6();
  Vz v7 = v2 * v1;      Linea(7);  v7();
  Linea(8); cout << "Resultado == " << v1 % v2 << endl;
}

Salida:

Linea 3: VectorXY (6, 8)
Linea 4: VectorXY (10, 15)
Linea 5: VectorXY (10, 15)
Linea 6: VectorZ (-2)
Linea 7: VectorZ (2)
Linea 8: Resultado == 23

Comentario

El programa requiere poco comentario, ya hemos introducido previamente la mayoría de las sentencias. La función main se reduce a crear dos objetos de la clase (en M.1 y M2) con los que posteriormente realizamos algunas operaciones, mostrándose también los resultados obtenidos.

El sentido de los métodos operator( ) de las clases Vz (L.7) y Vector (L.14), se explica al tratar la sobrecarga del operador de invocación de función ( 4.9.18f).

  Inicio.


[1]  Recordemos que las componentes escalares x y de cualquier objeto Vector pueden adoptar valores negativos.

[2]  Si han sido declaradas ambas formas, se aplica la congruencia estándar de argumentos ( 4.4.1a) para resolver cualquier posible ambigüedad.

[3]  Se define el elemento neutro 0 para la suma aquel que:  V == V +  0  para cualquier V. Evidentemente, el vector 0 tiene componentes x == y == 0.

Definimos el opuesto V de un vector V si: V + V == 0. Evidentemente el opuesto del vector de componentes x y tiene componentes -x -y. Puede comprobarse que: -1 . V == V