Sobrecarga de operadores binarios c. Operadores de incremento y decremento. Interpretaciones de operadores binarios y unarios.

Hoy nos familiarizaremos con una característica maravillosa de cualquier lenguaje C++: la sobrecarga de operadores. Pero primero, averigüemos por qué es necesario.

CON Tipos basicos puedes utilizar cualquier operador: +, -, *, ++, = y muchos otros. Por ejemplo:

int a=2,b=3,c;
c = a + b;

Aquí arriba las variables escribe int La operación + se realiza primero y luego el resultado se asigna a la variable c usando la operación =. No puedes hacer esto con las clases. Creemos una clase simple:

código en lenguaje c++ clase Contador (público: int contador; Contador(): c(0) ()); Contador a,b,c; a.contador = 2; b.contador = 3; c = a + b;

Aquí el compilador arrojará un error en la última línea: no sabe exactamente cómo actuar cuando se utilizan las operaciones = y + en objetos de la clase Counter. Puede ser resuelto este problema como esto:

código en lenguaje c++ class Contador ( public: int contador; Contador() : conteo(0) () void AddCounters (Contador& a, Contador& b) ( contador = a.contador + b.contador; ) ); Contador a,b,c; a.contador = 2; b.contador = 3; c.AgregarContadores(a,b);

De acuerdo, usando las operaciones + y = en en este caso haría el programa más comprensible. Así que aquí está para usar operaciones estándar C++ con clases, estas operaciones deben sobrecargarse.

Sobrecarga de operadores unarios

Empecemos con operaciones simples- unario. En primer lugar, nos interesa el incremento. Cuando se utiliza esta operación en tipos básicos, se puede sumar o restar uno a una variable:

código en lenguaje c++ int a = 1; ++a; // a = 2; --a; // a = 1;

Ahora enseñemos a la clase Counter a usar preincremento:

código en lenguaje c++ clase Contador ( privado: contador; público: Contador() : contador(0) () void operador++ () ( contador += 1; // También puedes contrar++ o ++contador - // no importa en este caso ) ); Contador a; ++a; ++a; ++a;

Este código funciona. Cuando se utiliza la operación ++ (¡es importante que este signo esté antes del identificador!) en un objeto de la clase Contador, la variable contador del objeto a aumenta.

EN en este ejemplo Sobrecargamos la operación ++. Esto se hace creando un método dentro de la clase. La única característica importante este método- nombre del identificador. El nombre del identificador para operaciones sobrecargadas consta de la palabra clave del operador y el nombre de la operación. En todos los demás aspectos, este método se define como cualquier otro.

Utilice operadores sobrecargados con tipos personalizados muy simple - al igual que con tipos regulares datos.

Sobrecarga del operador Postfix

Ejemplos de operación postfix:

int a = 3;
a++;
a--;

Es decir, aquí el signo de operación se coloca después del nombre del identificador. Para utilizar operaciones postfix con tipos de datos personalizados, necesita muy poco:

código en lenguaje c++ público: operador nulo ++ () ( contador += 1; ) operador nulo ++ (int) ( contador += 1; )

La única diferencia entre la operación de prefijo y la operación de postfijo es palabra clave int en la lista de argumentos. ¡Pero int no es un argumento! Esta palabra dice que el operador postfix está sobrecargado. Ahora el operador ++ se puede utilizar tanto antes como después del identificador de objeto:

Contador a;
++a;
++a;
a++;
a++;

Sobrecarga del operador binario

La sobrecarga de operadores de dos argumentos es muy similar a la sobrecarga de operadores binarios:

código en lenguaje c++ Operador de contador+ (Contador t) ( Suma de contador; suma.contador = contador + t.contador; suma de retorno; ) Contador c1,c2,c3; c1.contador = 3; c2.contador = 2; c3 = c1 + c2;

¿Qué variable llamará a la función operador+? En operadores binarios sobrecargados, siempre se llama al método del operando izquierdo. En este caso, el método operador+ llama al objeto c1.

Pasamos un argumento al método. El argumento es siempre el operando correcto.

Además, en este caso, la operación + debe devolver algún resultado para poder asignarlo al objeto c3. Devolvemos un objeto Contador. El valor de retorno se asigna a la variable c3.

Observe que sobrecargamos el operador +, ¡pero no sobrecargamos el operador =! Por supuesto que necesitas agregar este método a la clase Counter:

código en lenguaje c++ Operador de contador = (Contador t) ( Contador asignar; contador = t.contador; asignar.contador = t.contador; devolver asignar; )

Dentro del método, creamos una variable de asignación adicional. Esta variable se utiliza para que puedas trabajar con el siguiente código:

Contrac1(5),c2,c3;
c3 = c2 = c1;

Sin embargo, puede utilizar métodos más elegantes para el valor de retorno, que pronto conoceremos.

¡Buen día!

deseo de escribir Este artículo apareció después de leer la publicación Sobrecarga de operadores C++, porque en ella no se tratan muchos temas importantes.

Lo más importante que hay que recordar es que la sobrecarga del operador es simplemente más manera conveniente llamadas a funciones, así que no se deje llevar por la sobrecarga del operador. Sólo debe usarse cuando facilite la escritura de código. Pero no tanto como para dificultar la lectura. Después de todo, como usted sabe, el código se lee con mucha más frecuencia de lo que se escribe. Y no olvide que nunca se le permitirá sobrecargar operadores junto con tipos integrados; la posibilidad de sobrecarga solo está disponible para tipos/clases definidos por el usuario.

Sintaxis de sobrecarga

La sintaxis de sobrecarga de operadores es muy similar a definir una función llamada operador@, donde @ es el identificador del operador (por ejemplo +, -,<<, >>). Consideremos ejemplo más simple:
clase Entero (privado: valor int; público: Entero(int i): valor(i) () const Operador entero+(const Entero& rv) const (retorno (valor + rv.valor); ) );
En este caso, el operador está enmarcado como miembro de una clase, el argumento determina el valor ubicado en el lado derecho del operador. En general, hay dos formas principales de sobrecargar operadores: funciones globales que son amigables para la clase o funciones en línea de la propia clase. Consideraremos qué método es mejor para qué operador al final del tema.

En la mayoría de los casos, los operadores (excepto los condicionales) devuelven un objeto o una referencia al tipo al que pertenecen sus argumentos (si los tipos son diferentes, entonces usted decide cómo interpretar el resultado de la evaluación del operador).

Sobrecarga de operadores unarios

Veamos ejemplos de sobrecarga de operadores unarios para la clase Integer definida anteriormente. Al mismo tiempo, definámoslas en forma de funciones amigables y consideremos los operadores de decremento e incremento:
clase Entero ( privado: valor int; público: Entero(int i): valor(i) () //unario + amigo const Integer& operador+(const Integer& i); //unario - amigo const Integer operador-(const Integer& i) ; //incremento de prefijo amigo const Integer& operador++(Integer& i); incremento de sufijo amigo const Operador entero++(Entero& i, int); //prefijo decremento amigo const Integer& operador--(Integer& i); //postfix decrement amigo const Operador entero--(Integer& i, int); ); //unario plus no hace nada. const Integer& operator+(const Integer& i) ( return i.value; ) const Integer operator-(const Integer& i) ( return Integer(-i.value); ) //la versión del prefijo devuelve el valor después del incremento const Integer& operator++(Integer& i) ( i.value++; return i; ) //la versión postfix devuelve el valor antes del incremento const Integer operator++(Integer& i, int) ( Integer oldValue(i.value); i.value++; return oldValue; ) //la versión del prefijo devuelve el valor después de la disminución const Integer& operator--(Integer& i) (i.value--; return i; ) //la versión postfix devuelve el valor antes de la disminución const Integer operator--(Integer& i, int) ( Integer oldValue(i. valor); i .valor--; devolver valor antiguo;
Ahora ya sabe cómo el compilador distingue entre versiones de decremento e incremento con prefijo y posfijo. En el caso de que vea la expresión ++i, se llama al operador de función++(a). Si ve i++, entonces se llama al operador++(a, int). Es decir, se llama a la función operador ++ sobrecargada, y para esto se usa el parámetro int ficticio en la versión postfix.

Operadores binarios

Veamos la sintaxis para sobrecargar operadores binarios. Sobrecarguemos un operador que devuelve un valor l, uno operador condicional y un operador creando un nuevo valor (definámoslos globalmente):
clase Entero (privado: valor int; público: Entero(int i): valor(i) () amigo const Entero operador+(const Entero& izquierda, const Entero& derecha); amigo Entero& operador+=(Entero& izquierda, const Entero& derecha); amigo operador bool == (const entero y izquierda, const entero y derecha); const Entero operador+(const Entero& izquierda, const Entero& derecha) ( return Entero(valor.izquierdo + valor.derecho); ) Entero& operador+=(Entero& izquierda, const Entero& derecha) ( valor.izquierdo += valor.derecho; return izquierda; ) operador bool==(const Entero& izquierda, const Entero& derecha) ( return left.value == right.value; )
En todos estos ejemplos, los operadores están sobrecargados para el mismo tipo; sin embargo, esto no es necesario. Puede, por ejemplo, sobrecargar la adición de nuestro tipo entero y definido a su semejanza Flotador.

Argumentos y valores de retorno

Como puede ver, los ejemplos utilizan varias maneras pasar argumentos a funciones y devolver valores de operador.
  • Si un operador no modifica el argumento, en el caso de, por ejemplo, un signo unario más, debe pasarse como referencia a una constante. En general, esto es cierto para casi todo el mundo. operadores aritméticos(suma, resta, multiplicación...)
  • El tipo de valor de retorno depende de la naturaleza del operador. Si el operador debe devolver un nuevo valor, entonces se debe crear un nuevo objeto (como en el caso del plus binario). Si desea evitar que un objeto se modifique como un valor l, debe devolverlo como una constante.
  • Los operadores de asignación deben devolver una referencia al elemento modificado. Además, si desea utilizar el operador de asignación en construcciones como (x=y).f(), donde se llama a la función f() para la variable x, después de asignarla a y, no devuelva una referencia al constante, solo devuelve una referencia.
  • Los operadores lógicos deberían devolver un int en el peor de los casos y un bool en el mejor de los casos.

Optimización del valor de retorno

Al crear nuevos objetos y devolverlos desde una función, debe utilizar una notación similar al ejemplo del operador binario más descrito anteriormente.
return Entero(valor.izquierdo + valor.derecho);
Para ser honesto, no sé qué situación es relevante para C++11; todos los argumentos adicionales son válidos para C++98.
A primera vista, esto parece similar a la sintaxis para crear un objeto temporal, es decir, no parece haber diferencia entre el código anterior y este:
Temperatura entera (valor.izquierdo + valor.derecho); temperatura de retorno;
Pero, de hecho, en este caso, se llamará al constructor en la primera línea, luego se llamará al constructor de copia, que copiará el objeto, y luego, al desenrollar la pila, se llamará al destructor. Cuando se utiliza la primera entrada, el compilador crea inicialmente el objeto en la memoria en el que se debe copiar, guardando así las llamadas al constructor y al destructor de la copia.

Operadores especiales

C++ tiene operadores que tienen una sintaxis y métodos de sobrecarga específicos. Por ejemplo, el operador de indexación. Siempre se define como miembro de una clase y, dado que el objeto indexado debe comportarse como una matriz, debe devolver una referencia.
operador de coma
Los operadores "especiales" también incluyen el operador de coma. Se llama a objetos que tienen una coma al lado (pero no se llama a listas de argumentos de funciones). No es fácil encontrar un caso de uso significativo para este operador. Habrowser AxisPod en los comentarios al artículo anterior sobre sobrecarga habló de una cosa.
Operador de desreferencia de puntero
La sobrecarga de estos operadores puede justificarse para clases de punteros inteligentes. Este operador se define necesariamente como una función de clase y se le imponen algunas restricciones: debe devolver un objeto (o una referencia) o un puntero que permita el acceso al objeto.
Operador de asignación
El operador de asignación se define necesariamente como una función de clase porque está intrínsecamente vinculado al objeto a la izquierda de "=". Definición del operador de asignación en globalmente permitiría anular el comportamiento predeterminado del operador "=". Ejemplo:
clase Entero (privado: valor int; público: Entero(int i): valor(i) () Entero& operador=(const Integer& derecha) ( //comprueba la autoasignación if (this == &right) ( return *this; ) valor = valor.correcto; devolver *esto;

Como puede ver, al inicio de la función se realiza una verificación de autoasignación. En general, en este caso la autoapropiación es inofensiva, pero la situación no siempre es tan sencilla. Por ejemplo, si el objeto es grande, puede perder mucho tiempo copiando innecesariamente o trabajando con punteros.

Operadores no sobrecargables
Algunos operadores en C++ no están sobrecargados en absoluto. Al parecer esto se hizo por motivos de seguridad.
  • Operador de selección de miembros de clase ".".
  • Operador para desreferenciar un puntero a un miembro de la clase ".*"
  • C++ no tiene el operador de exponenciación (como en Fortran) "**".
  • Está prohibido definir sus propios operadores (puede haber problemas para determinar las prioridades).
  • Las prioridades del operador no se pueden cambiar
Como ya hemos descubierto, hay dos tipos de operadores: como función de clase y como función global amigable.
Rob Murray, en su libro C++ Strategies and Tactics, definió las siguientes recomendaciones por elección de forma de operador:

¿Porqué es eso? En primer lugar, algunos operadores inicialmente están limitados. En general, si no hay diferencia semántica en cómo se define un operador, entonces es mejor diseñarlo como una función de clase para enfatizar la conexión, además, la función estará en línea. Además, a veces puede ser necesario representar el operando de la izquierda como un objeto de otra clase. Probablemente el ejemplo más llamativo sea la redefinición.<< и >> para flujos de E/S.

Literatura

Bruce Eckel - Filosofía C++. Introducción al estándar C++.

Etiquetas: Agregar etiquetas

Última actualización: 20/10/2017

La sobrecarga de operadores le permite definir las acciones que realizará un operador. La sobrecarga implica la creación de una función cuyo nombre contiene la palabra operador y el símbolo del operador que se está sobrecargando. Una función de operador se puede definir como miembro de una clase o fuera de una clase.

Sólo puede sobrecargar operadores que ya estén definidos en C++. No se pueden crear nuevos operadores.

Si la función del operador se define como función separada y no es miembro de la clase, entonces el número de parámetros de dicha función coincide con el número de operandos del operador. Por ejemplo, una función que representa operador unario, tendrá un parámetro, pero la función que representa el operador binario tendrá dos parámetros. Si un operador toma dos operandos, entonces el primer operando se pasa al primer parámetro de la función y el segundo operando se pasa al segundo parámetro. En este caso, al menos uno de los parámetros debe representar el tipo de clase.

Veamos un ejemplo con la clase Counter, que representa un cronómetro y almacena la cantidad de segundos:

#incluir << seconds << " seconds" << std::endl; } int seconds; }; Counter operator + (Counter c1, Counter c2) { return Counter(c1.seconds + c2.seconds); } int main() { Counter c1(20); Counter c2(10); Counter c3 = c1 + c2; c3.display(); // 30 seconds return 0; }

Aquí la función del operador no forma parte de la clase Contador y se define fuera de ella. Esta función sobrecarga el operador de suma para el tipo Contador. Es binario, por lo que necesita dos parámetros. En este caso, estamos agregando dos objetos Counter. La función también devuelve un objeto Contador que almacena el número total de segundos. Es decir, en esencia, la operación de suma aquí se reduce a sumar los segundos de ambos objetos:

Operador de contador + (Contador c1, Contador c2) ( return Contador(c1.segundos + c2.segundos); )

No es necesario devolver un objeto de clase. También puede ser un objeto de tipo primitivo incorporado. Y también podemos definir sobrecargas de operadores adicionales:

Operador int + (Contador c1, int s) (retorna c1.segundos + s;)

Esta versión agrega un objeto Contador a un número y también devuelve el número. Por lo tanto, el operando izquierdo de la operación debe ser de tipo Contador y el operando derecho debe ser de tipo int. Y, por ejemplo, podemos aplicar esta versión del operador de la siguiente manera:

Contador c1(20); int segundos = c1 + 25; // 45 estándar::cout<< seconds << std::endl;

Además, las funciones de operador se pueden definir como miembros de clases. Si una función de operador se define como miembro de una clase, entonces se puede acceder al operando izquierdo a través de este puntero y representa el objeto actual, y el operando derecho se pasa a la función similar como único parámetro:

#incluir clase Contador ( publico: Contador(int sec) ( segundos = seg; ) void display() ( std::cout<< seconds << " seconds" << std::endl; } Counter operator + (Counter c2) { return Counter(this->segundos + c2.segundos); ) int operador + (int s) ( devuelve esto->segundos + s; ) int segundos; ); int main() ( Contador c1(20); Contador c2(10); Contador c3 = c1 + c2; c3.display(); // 30 segundos int segundos = c1 + 25; // 45 devuelve 0; )

En este caso, accedemos al operando izquierdo en las funciones de operador a través del puntero this.

¿Qué operadores deberían redefinirse y dónde? Los operadores de asignación, indexación (), llamada (()), acceso a un miembro de la clase mediante puntero (->) deben definirse como funciones de miembro de la clase. Los operadores que cambian el estado de un objeto o están directamente asociados con el objeto (incremento, decremento) generalmente también se definen como funciones miembro de clase. Todos los demás operadores suelen definirse como funciones individuales en lugar de miembros de una clase.

Operadores de comparación

Varios operadores están sobrecargados en pares. Por ejemplo, si definimos el operador ==, entonces también debemos definir el operador !=. Y al definir el operador< надо также определять функцию для оператора >. Por ejemplo, sobrecarguemos estos operadores:

Operador bool == (Contador c1, Contador c2) ( devuelve c1.segundos == c2.segundos; ) operador bool != (Contador c1, Contador c2) ( devuelve c1.segundos != c2.segundos; ) operador bool > ( Contador c1, Contador c2) ( return c1.segundos > c2.segundos; ) operador bool< (Counter c1, Counter c2) { return c1.seconds < c2.seconds; } int main() { Counter c1(20); Counter c2(10); bool b1 = c1 == c2; // false bool b2 = c1 >c2; // verdadero std::cout<< b1 << std::endl; std::cout << b2 << std::endl; return 0; }

Operadores de Asignación

#incluir clase Contador ( publico: Contador(int sec) ( segundos = seg; ) void display() ( std::cout<< seconds << " seconds" << std::endl; } Counter& operator += (Counter c2) { seconds += c2.seconds; return *this; } int seconds; }; int main() { Counter c1(20); Counter c2(10); c1 += c2; c1.display(); // 30 seconds return 0; }

Operaciones de incremento y decremento

Redefinir los operadores de incremento y decremento puede ser particularmente desafiante, ya que necesitamos definir las formas de prefijo y postfijo para estos operadores. Definamos operadores similares para el tipo Contador:

#incluir clase Contador ( publico: Contador(int sec) ( segundos = seg; ) void display() ( std::cout<< seconds << " seconds" << std::endl; } // префиксные операторы Counter& operator++ () { seconds += 5; return *this; } Counter& operator-- () { seconds -= 5; return *this; } // постфиксные операторы Counter operator++ (int) { Counter prev = *this; ++*this; return prev; } Counter operator-- (int) { Counter prev = *this; --*this; return prev; } int seconds; }; int main() { Counter c1(20); Counter c2 = c1++; c2.display(); // 20 seconds c1.display(); // 25 seconds --c1; c1.display(); // 20 seconds return 0; }

Contador& operador++ () ( segundos += 5; return *this; )

En la función misma, puede definir alguna lógica para incrementar el valor. En este caso, el número de segundos aumenta en 5.

Los operadores de sufijo deben devolver el valor del objeto antes del incremento, es decir, el estado anterior del objeto. Para que la forma de sufijo sea diferente de la forma de prefijo, las versiones de sufijo reciben un parámetro adicional de tipo int, que no se utiliza. Aunque en principio podemos usarlo.

Operador de contador++ (int) ( Contador anterior = *this; ++*this; return anterior; )

¡Buen día!

El deseo de escribir este artículo apareció después de leer el post, porque en él no se trataban muchos temas importantes.

Lo más importante que debe recordar es que la sobrecarga de operadores es simplemente una forma más conveniente de llamar funciones, así que no se deje llevar por la sobrecarga de operadores. Sólo debe usarse cuando facilite la escritura de código. Pero no tanto como para dificultar la lectura. Después de todo, como usted sabe, el código se lee con mucha más frecuencia de lo que se escribe. Y no olvide que nunca se le permitirá sobrecargar operadores junto con tipos integrados; la posibilidad de sobrecarga solo está disponible para tipos/clases definidos por el usuario.

Sintaxis de sobrecarga

La sintaxis de sobrecarga de operadores es muy similar a definir una función llamada operador@, donde @ es el identificador del operador (por ejemplo +, -,<<, >>). Veamos un ejemplo sencillo:
clase Entero (privado: valor int; público: Entero(int i): valor(i) () const Operador entero+(const Entero& rv) const (retorno (valor + rv.valor); ) );
En este caso, el operador está enmarcado como miembro de una clase, el argumento determina el valor ubicado en el lado derecho del operador. En general, hay dos formas principales de sobrecargar operadores: funciones globales que son amigables para la clase o funciones en línea de la propia clase. Consideraremos qué método es mejor para qué operador al final del tema.

En la mayoría de los casos, los operadores (excepto los condicionales) devuelven un objeto o una referencia al tipo al que pertenecen sus argumentos (si los tipos son diferentes, entonces usted decide cómo interpretar el resultado de la evaluación del operador).

Sobrecarga de operadores unarios

Veamos ejemplos de sobrecarga de operadores unarios para la clase Integer definida anteriormente. Al mismo tiempo, definámoslas en forma de funciones amigables y consideremos los operadores de decremento e incremento:
clase Entero ( privado: valor int; público: Entero(int i): valor(i) () //unario + amigo const Integer& operador+(const Integer& i); //unario - amigo const Integer operador-(const Integer& i) ; //incremento de prefijo amigo const Integer& operador++(Integer& i); //incremento de prefijo amigo const Integer operador++(Integer& i, int); //incremento de prefijo amigo const Integer& operador--(Integer& i); yo, int); //unario plus no hace nada. const Integer& operator+(const Integer& i) ( return i.value; ) const Integer operator-(const Integer& i) ( return Integer(-i.value); ) //la versión del prefijo devuelve el valor después del incremento const Integer& operator++(Integer& i) ( i.value++; return i; ) //la versión postfix devuelve el valor antes del incremento const Integer operator++(Integer& i, int) ( Integer oldValue(i.value); i.value++; return oldValue; ) //la versión del prefijo devuelve el valor después de la disminución const Integer& operator--(Integer& i) (i.value--; return i; ) //la versión postfix devuelve el valor antes de la disminución const Integer operator--(Integer& i, int) ( Integer oldValue(i. valor); i .valor--; devolver valor antiguo;
Ahora ya sabe cómo el compilador distingue entre versiones de decremento e incremento con prefijo y posfijo. En el caso de que vea la expresión ++i, se llama al operador de función++(a). Si ve i++, entonces se llama al operador++(a, int). Es decir, se llama a la función operador ++ sobrecargada, y para esto se usa el parámetro int ficticio en la versión postfix.

Operadores binarios

Veamos la sintaxis para sobrecargar operadores binarios. Sobrecarguemos un operador que devuelve un valor l, un operador condicional y un operador que crea un nuevo valor (definámoslos globalmente):
clase Entero (privado: valor int; público: Entero(int i): valor(i) () amigo const Entero operador+(const Entero& izquierda, const Entero& derecha); amigo Entero& operador+=(Entero& izquierda, const Entero& derecha); amigo operador bool == (const entero y izquierda, const entero y derecha); const Entero operador+(const Entero& izquierda, const Entero& derecha) ( return Entero(valor.izquierdo + valor.derecho); ) Entero& operador+=(Entero& izquierda, const Entero& derecha) ( valor.izquierdo += valor.derecho; return izquierda; ) operador bool==(const Entero& izquierda, const Entero& derecha) ( return left.value == right.value; )
En todos estos ejemplos, los operadores están sobrecargados para el mismo tipo; sin embargo, esto no es necesario. Puede, por ejemplo, sobrecargar la adición de nuestro tipo Integer y Float definido por su similitud.

Argumentos y valores de retorno

Como puede ver, los ejemplos utilizan diferentes formas de pasar argumentos a funciones y devolver valores de operador.
  • Si un operador no modifica el argumento, en el caso de, por ejemplo, un signo unario más, debe pasarse como referencia a una constante. En general, esto es cierto para casi todos los operadores aritméticos (suma, resta, multiplicación...)
  • El tipo de valor de retorno depende de la naturaleza del operador. Si el operador debe devolver un nuevo valor, entonces se debe crear un nuevo objeto (como en el caso del plus binario). Si desea evitar que un objeto se modifique como un valor l, debe devolverlo como una constante.
  • Los operadores de asignación deben devolver una referencia al elemento modificado. Además, si desea utilizar el operador de asignación en construcciones como (x=y).f(), donde se llama a la función f() para la variable x, después de asignarla a y, no devuelva una referencia al constante, solo devuelve una referencia.
  • Los operadores lógicos deberían devolver un int en el peor de los casos y un bool en el mejor de los casos.

Optimización del valor de retorno

Al crear nuevos objetos y devolverlos desde una función, debe utilizar una notación similar al ejemplo del operador binario más descrito anteriormente.
return Entero(valor.izquierdo + valor.derecho);
Para ser honesto, no sé qué situación es relevante para C++11; todos los argumentos adicionales son válidos para C++98.
A primera vista, esto parece similar a la sintaxis para crear un objeto temporal, es decir, no parece haber diferencia entre el código anterior y este:
Temperatura entera (valor.izquierdo + valor.derecho); temperatura de retorno;
Pero, de hecho, en este caso, se llamará al constructor en la primera línea, luego se llamará al constructor de copia, que copiará el objeto, y luego, al desenrollar la pila, se llamará al destructor. Cuando se utiliza la primera entrada, el compilador crea inicialmente el objeto en la memoria en el que se debe copiar, guardando así las llamadas al constructor y al destructor de la copia.

Operadores especiales

C++ tiene operadores que tienen una sintaxis y métodos de sobrecarga específicos. Por ejemplo, el operador de indexación. Siempre se define como miembro de una clase y, dado que el objeto indexado debe comportarse como una matriz, debe devolver una referencia.
operador de coma
Los operadores "especiales" también incluyen el operador de coma. Se llama a objetos que tienen una coma al lado (pero no se llama a listas de argumentos de funciones). No es fácil encontrar un caso de uso significativo para este operador. Habrauser en los comentarios del artículo anterior sobre sobrecarga.
Operador de desreferencia de puntero
La sobrecarga de estos operadores puede justificarse para clases de punteros inteligentes. Este operador se define necesariamente como una función de clase y se le imponen algunas restricciones: debe devolver un objeto (o una referencia) o un puntero que permita el acceso al objeto.
Operador de asignación
El operador de asignación se define necesariamente como una función de clase porque está intrínsecamente vinculado al objeto a la izquierda de "=". Definir el operador de asignación globalmente permitiría anular el comportamiento predeterminado del operador "=". Ejemplo:
clase Entero (privado: valor int; público: Entero(int i): valor(i) () Entero& operador=(const Integer& derecha) ( //comprueba la autoasignación if (this == &right) ( return *this; ) valor = valor.correcto; devolver *esto;

Como puede ver, al inicio de la función se realiza una verificación de autoasignación. En general, en este caso la autoapropiación es inofensiva, pero la situación no siempre es tan sencilla. Por ejemplo, si el objeto es grande, puede perder mucho tiempo copiando innecesariamente o trabajando con punteros.

Operadores no sobrecargables
Algunos operadores en C++ no están sobrecargados en absoluto. Al parecer esto se hizo por motivos de seguridad.
  • Operador de selección de miembros de clase ".".
  • Operador para desreferenciar un puntero a un miembro de la clase ".*"
  • C++ no tiene el operador de exponenciación (como en Fortran) "**".
  • Está prohibido definir sus propios operadores (puede haber problemas para determinar las prioridades).
  • Las prioridades del operador no se pueden cambiar
Como ya hemos descubierto, hay dos tipos de operadores: como función de clase y como función global amigable.
Rob Murray, en su libro C++ Strategies and Tactics, define las siguientes pautas para elegir la forma del operador:

¿Porqué es eso? En primer lugar, algunos operadores inicialmente están limitados. En general, si no hay diferencia semántica en cómo se define un operador, entonces es mejor diseñarlo como una función de clase para enfatizar la conexión, además, la función estará en línea. Además, a veces puede ser necesario representar el operando de la izquierda como un objeto de otra clase. Probablemente el ejemplo más llamativo sea la redefinición.<< и >> para flujos de E/S.

Literatura

Bruce Eckel - Filosofía C++. Introducción al estándar C++.

Etiquetas:

  • C++
  • operadores sobrecargando
  • sobrecarga del operador
Agregar etiquetas

Muchos lenguajes de programación utilizan operadores: como mínimo, asignaciones (=, := o similares) y operadores aritméticos (+, -, * y /). En la mayoría de los lenguajes de tipado estático, estos operadores están vinculados a tipos. Por ejemplo, en Java, la suma con el operador + solo es posible para números enteros, números de coma flotante y cadenas. Si definimos nuestras propias clases para objetos matemáticos, como matrices, podemos implementar un método para sumarlos, pero solo podemos llamarlo con algo como esto: a = b.add(c) .

En C++ no existe tal limitación: podemos sobrecargar casi cualquier operador conocido. Las posibilidades son infinitas: puede elegir cualquier combinación de tipos de operandos, la única limitación es que debe estar presente al menos un tipo de operando definido por el usuario. Es decir, definir un nuevo operador sobre tipos integrados o reescribir uno existente. esta prohibido.

¿Cuándo debería sobrecargar a los operadores?

Recuerde lo principal: sobrecargar operadores si y sólo si tiene sentido. Es decir, si el significado de la sobrecarga es obvio y no contiene sorpresas ocultas. Los operadores sobrecargados deben comportarse de la misma manera que sus versiones base. Naturalmente, se permiten excepciones, pero sólo en los casos en que vayan acompañadas de explicaciones claras. Un buen ejemplo son los operadores.<< и >> la biblioteca estándar iostream, que claramente no se comporta como operadores normales.

A continuación se muestran buenos y malos ejemplos de sobrecarga de operadores. La suma de matrices anterior es un caso ilustrativo. Aquí, sobrecargar el operador de suma es intuitivo y, si se implementa correctamente, no requiere explicación:

Matriz a, b; Matriz c = a + b;

Un ejemplo de una mala sobrecarga del operador de suma sería agregar dos objetos de jugador en un juego. ¿Qué quiso decir el creador de la clase? ¿Cuál será el resultado? No sabemos qué hace la operación y, por lo tanto, utilizar este operador es peligroso.

¿Cómo sobrecargar a los operadores?

La sobrecarga de operadores es similar a la sobrecarga de funciones con nombres especiales. De hecho, cuando el compilador ve una expresión que contiene un operador y un tipo definido por el usuario, reemplaza la expresión con una llamada a la función correspondiente del operador sobrecargado. La mayoría de sus nombres comienzan con la palabra clave operador seguida de la designación del operador correspondiente. Cuando la notación no consta de caracteres especiales, por ejemplo, en el caso de un operador de conversión de tipo o de gestión de memoria (nuevo, eliminar, etc.), la palabra operador y la notación del operador deben estar separadas por un espacio (operador nuevo) ; de lo contrario, el espacio se puede ignorar (operador+).

La mayoría de los operadores pueden sobrecargarse mediante métodos de clase o funciones simples, pero existen algunas excepciones. Cuando el operador sobrecargado es un método de una clase, el tipo del primer operando debe ser esa clase (siempre *this), y el segundo debe declararse en la lista de parámetros. Además, las declaraciones de métodos no son estáticas, con la excepción de las declaraciones de administración de memoria.

Cuando sobrecarga un operador en un método de clase, obtiene acceso a los campos privados de la clase, pero la conversión oculta del primer argumento no está disponible. Por tanto, las funciones binarias suelen estar sobrecargadas como funciones libres. Ejemplo:

Class Rational ( public: // El constructor se puede utilizar para la conversión implícita de int: Rational(int numerator, int denominador = 1); Rational operator+(Rational const& rhs) const; ); int main() ( Racional a, b, c; int i; a = b + c; //ok, no es necesaria ninguna conversión a = b + i; //ok, conversión implícita del segundo argumento a = i + c; //ERROR: el primer argumento no se puede convertir implícitamente)

Cuando los operadores unarios están sobrecargados como funciones libres, tienen a su disposición la conversión de argumentos ocultos, pero esto no se suele utilizar. Por otra parte, esta propiedad es necesaria para los operadores binarios. Por tanto, el principal consejo sería el siguiente:

Implemente operadores unarios y operadores binarios como " X=” en forma de métodos de clase y otros operadores binarios, en forma de funciones libres.

¿Qué operadores se pueden sobrecargar?

Podemos sobrecargar casi cualquier operador de C++, sujeto a las siguientes excepciones y limitaciones:

  • No puede definir un nuevo operador, como operador** .
  • Los siguientes operadores no se pueden sobrecargar:
    1. ?: (operador ternario);
    2. :: (acceso a nombres anidados);
    3. . (acceso a los campos);
    4. .* (acceso a campos mediante puntero);
    5. Operadores sizeof, typeid y cast.
  • Los siguientes operadores sólo se pueden sobrecargar como métodos:
    1. = (tarea);
    2. -> (acceso a campos mediante puntero);
    3. () (Llamada de función);
    4. (acceso por índice);
    5. ->* (acceso al campo de puntero mediante puntero);
    6. Operadores de conversión y gestión de memoria.
  • El número de operandos, el orden de ejecución y la asociatividad de los operadores están determinados por la versión estándar.
  • Al menos un operando debe ser de tipo usuario. Typedef no cuenta.

La siguiente parte presentará las sobrecargas de operadores de C++, en grupos e individualmente. Cada sección se caracteriza por la semántica, es decir. comportamiento esperado. Además, se mostrarán formas típicas de declarar e implementar operadores.




Arriba