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.18b3  Sobrecarga de operadores de manejo de bits

§1  Sinopsis

En el presente epígrafe trataremos la sobrecarga de los operadores de manejo de bits ("Bitwise"). En concreto, los operadores de desplazamiento derecho >> e izquierdo <<.

Recordemos ( 4.9.3) que son operadores binarios que responden a la sintaxis:

expr-desplazada >> expr-desplazamiento
expr-desplazada << expr-desplazamiento

En la versión global (existente en el leguaje para los tipos básicos), ambos operandos deben ser de tipo entero, y el patrón de bits representado por expr-desplazada, sufre un desplazamiento, derecho o izquierdo, acorde con el valor indicado en expr-desplazamiento, que una vez promovida a entero, debe ser un entero positivo y menor que la longitud del primer operando.

En nuestro caso, recurriremos a sobrecargarlos para una clase Char, cuyos objetos están destinados a contener los caracteres del alfabeto.  Para simplificar, supondremos que son las mayúsculas del juego de caracteres US-ASCII ( 2.2.1a). Son 26 caracteres, de la 'A' a la 'Z', a lo que corresponden los valores decimales 65 a 90 inclusive. El esqueleto de nuestra clase tiene el siguiente diseño:

class Char {
  public: char letra;
}


El comportamiento que queremos para los operadores << y >> en nuestra clase tiene dos vertientes:  en la primera, dado un objeto obje tipo Char que almacene una letra.  Por ejemplo, obje.letra == 'E', el resultado de aplicarle el desplazamiento derecho o izquierdo de valor n, supondría cambiar el valor de char según un desplazamiento equivalente en el abecedario. Por ejemplo:

obje >> 2   // -> obje.letra == 'G'
obje << 2   // -> obje.letra == 'C'
// etc.

En la segunda forma, si el segundo operando es otro objeto tipo Char, se considerará que equivale al desplazamiento correspondiente a la posición de la letra en la secuencia.  Por ejemplo, en el caso anterior, si objc es otro objeto tipo Char tal que objc.letra == 'C'  (tercera letra de la secuencia), se tendría:

obje >> objc  // -> obje.letra == 'H'
obje << objc  // -> obje.letra == 'B'

Puesto que se trata de operadores binarios, la sobrecarga puede hacerse de dos formas:

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)

En nuestro caso utilizaremos la versión a, y es evidente que debemos utilizar dos versiones de cada operador: la primera aceptará un int como argumento. La segunda aceptará un objeto tipo Char. Además, puesto que deseamos poder utilizar nuestros operadores en expresiones encadenadas del tipo

obj1 << obj2 >> obje3 << obj4;

está claro que debemos utilizar referencias como valor devuelto (véase al respecto la discusión relativa al operador suma binaria + 4.9.18b). El resultado de incluir la definición de los cuatro operadores es el siguiente diseño:

class Char { // clase para caracteres ASCII
  public: char caracter;

  Char& operator>> (int desp) {
    int pos = caracter;      // valor ACII
    caracter = static_cast<char> (pos + desp);
    return *this;
  }
  Char& operator>> (Char& ch) {
    int pos = caracter;
    int desp = ch.caracter - 64; // posicion en la secuencia
    caracter = static_cast<char> (pos + desp);
    return *this;
  }

  Char& operator<< (int desp) {
    int pos = caracter;
    caracter = static_cast<char> (pos - desp);
    return *this;
  }
  Char& operator<< (Char& ch) {
    int pos = caracter;
    int desp = ch.caracter - 64;
    caracter = static_cast<char> (pos - desp);
    return *this;
  }
};

Comentario

Los dos primeros métodos representan las dos versiones del operador desplazamiento derecha >>, que se usarán respectivamente según el segundo operador sea un int o un Char. Los otros dos son simétricos de los anteriores, y corresponden al operador de desplazamiento izquierda <<. Observe que en todos los casos se devuelve una referencia al propio objeto, una vez modificado, mediante la indirección del puntero this.

La sentencia que calcula el valor ASCII del objeto antes de la modificación:

int pos = caracter; // valor ACII

implica una conversión estándar de tipo charint, que es realizada automáticamente por el compilador. En cambio, la sentencias que calculan el carácter final:

caracter = static_cast<char> (pos + desp);
caracter = static_cast<char> (pos - desp);

utilizan una conversión explícita de tipos ( 4.9.9b) intchar, que resulta más ortodoxa.


En el caso de los operadores globales, el Estándar establece que si expr-desplazamiento resulta en un valor mayor que la longitud del primer operando, el resultado es indefinido (depende de la implementación). En nuestra versión, debemos incluir salvaguardas para el caso que el valor ASCII resultante correspondiera a un carácter fuera de la secuencia (menor que la 'A' o mayor que la 'Z'). Esto puede hacerse controlando los resultados (pos + desp) y (pos - desp) mediante una función que tome las medidas correctoras pertinentes. Por ejemplo:

int control (int resultado) {
   while (resultado > 90 ) { resultado -= 26; }
   while (resultado < 65 ) { resultado += 26; }
   return resultado;
}

Esta función define un desplazamiento circular; cuando a un carácter le corresponde un valor inmediatamente posterior al máximo 'Z', vuelve a corresponderle el primero ('A'). Del mismo modo, cuando a un carácter le corresponde el valor anterior al mínimo 'A', vuelve a corresponderle el máximo ('Z'). Esta función la implementamos como un método privado en la clase [1]. Además, incluimos en cada función-operador un par de sentencias que nos muestren los valores del carácter antes y después de la transformación producida por el operador (estos controles nos permitirán más adelante comprobar el orden de ejecución en el caso de expresiones encadenadas).

Una vez incluidas las modificaciones anteriores, el diseño de la clase queda como sigue:

class Char { // clase para caracteres ASCII
  public: char caracter;

  Char& operator>> (int desp) {
    int pos = caracter;    // valor ACII
    char previo = caracter;
    caracter = static_cast<char> (control(pos + desp));
    cout << "Caracter " << previo << " -> " << caracter << endl;
    return *this;
  }
  Char& operator>> (Char& ch) {
    int pos = caracter;
    int desp = ch.caracter - 64; // posicion en la secuencia
    char previo = caracter;
    caracter = static_cast<char> (control(pos + desp));
    cout << "caracter " << previo << " -> " << caracter << endl;
    return *this;
  }

  Char& operator<< (int desp) {
    int pos = caracter;
    char previo = caracter;
    caracter = static_cast<char> (control(pos - desp));
    cout << "Caracter " << previo << " -< " << caracter << endl;
    return *this;
  }
  Char& operator<< (Char& ch) {
    int pos = caracter;
    int desp = ch.caracter - 64;
    char previo = caracter;
    caracter = static_cast<char> (control(pos - desp));
    cout << "caracter " << previo << " -< " << caracter << endl;
    return *this;
  }
  private: int control (int);
};

int Char::control (int resultado) {
  while (resultado > 90 ) { resultado -= 26; }
  while (resultado < 65 ) { resultado += 26; }
  return resultado;
}

Comentario

El único punto a destacar es que hemos tenido que almacenar un valor auxiliar previo, con el estado anterior a la modificación para poder mostrar los valores anterior y posterior a la acción del operador. También que las expresiones que calculan el carácter final hemos sustituido los valores (pos + desp) y (pos - desp) por el resultado de la invocación al método control.

Con esta definición para Char, y suponiendo que, en todos los casos los valores de partida son:

Char a = { 'A' };
Char e = { 'E' };
Char c = { 'C' };
Char z = { 'Z' };

Se obtienen los resultados siguientes:

a >> 27;  // Caracter A -> B
z << 27;  // Caracter Z -< Y
e >> 2;   // Caracter E -> G
e << 5;   // Caracter E -< Z
e >> c;   // caracter E -> H
e << c;   // caracter E -< B

A continuación probamos algunas expresiones encadenadas:

e >> c >> 2;  // caracter E -> H  \n  Caracter H -> J
e >> c >> c;  // caracter E -> H  \n  caracter H -> K
e >> c << a;  // caracter E -> H  \n  caracter H -< G

Recordemos que estos operadores tienen efectos laterales, en el sentido de que modifican el operando a la izquierda (en su caso, el derecho no sufre modificación). Como puede verse, el orden de evaluación es de izquierda a derecha, de forma, que las expresiones anteriores equivalen a:

((e >> c) >> 2);
((e >> c) >> c);
((e >> c) << a);

  Inicio.


[1]  Este mátodo lo definimos off-line porque el compilador Borland nos avisa que: Functions containing while are not expanded inline. Es decir, las funciones que contengan bucles while no son suceptibles de expansión in-line, con lo que no tiene sentido mantener su definición dentro del cuerpo de la clase.