Tema
15 Sobrecarga de funciones y de operadores
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Teoría:
Definición de funciones y operadores
de la clase cadena
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Una
vez vistas y explicadas la declaración de la clase cadena
y de sus funciones y operadores miembro y friend,
contenidas en el fichero cadena.h, se va a presentar
la definición de todas estas funciones y operadores, que
están contenidas en otro fichero llamado cadena.cpp.
Obsérvese que se han introducido algunas sentencias
de escritura de mensajes, con objeto de determinar con claridad
cuándo y en qué circunstancias se ejecuta cada función.
Los
usuarios de la clase cadena no tendrían por
qué tener acceso a este fichero fuente, aunque sí
al fichero objeto correspondiente. A continuación se muestra
el contenido del fichero cadena.cpp intercalando algunos
comentarios explicativos.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
cadena.cpp
#include <string.h>
#include "cadena.h"
//
constructor por defecto
cadena::cadena()
{
pstr
= new char[1];
strcpy(pstr, "");
nchar = 0;
cout << "Constructor por defecto " << (long)this
<< endl;
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dos
observaciones acerca del constructor por defecto (sin
argumentos). La primera es que la variable miembro psrt,
que es un puntero a char, se inicializa apuntando a una cadena vacía
(sólo tiene el '\0' de fin de cadena). Se utiliza reserva
dinámica de memoria con el operador new. La
variable miembro nchar, que representa el número
de caracteres, no incluye el carácter de final de
cadena. Se ha incluido una sentencia de escritura que imprimirá
un mensaje avisando de que se ha utilizado este constructor. Imprime
también la dirección en memoria del objeto creado,
con idea de saber cuándo se crea y se destruye cada objeto
concreto del programa. Para ello se utiliza el puntero this
y un cast a long.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
constructor general
cadena::cadena(char* c)
{
nchar
= strlen(c);
pstr = new char[nchar + 1];
strcpy(pstr, c);
cout << "Constructor general " << (long)this
<< endl;
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
El
constructor general admite como argumento la dirección
del carácter inicial de una cadenade caracteres, a partir
de la cual se inicializará el nuevo objeto. Se utiliza también
reserva dinámica de memoria. Se utilizan las funciones strlen()
y strcpy() para determinar el número de caracteres
y para copiar el argumento en la dirección a la que apunta
la variable miembro pstr. Se imprime un mensaje que
permite saber qué constructor se ha llamado y qué
objeto ha sido creado.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
constructor de copia
cadena::cadena(const cadena& cd)
{
nchar
= cd.nchar;
pstr = new char[nchar +1];
strcpy(pstr, cd.pstr);
cout << "Constructor de copia " << (long)this
<< endl;
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Puede
verse que el constructor de copia es muy similar al
constructor general, con la única diferencia
de que recibe como argumento una referencia a un objeto cadena,
a partir del cual inicializa el nuevo objeto con reserva dinámica
de memoria. Obsérvese que se tiene acceso a las variables
miembro del objeto cadena pasado como argumento, pero
que hay que utilizar el operador punto (.). A las variables miembro
del objeto pasado como argumento implícito se tiene acceso
directo.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
destructor
cadena::~cadena()
{
delete
[] pstr;
cout << "Destructor " << (long)this <<
endl;
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
En
este caso no sirve el destructor de oficio, porque se está
utilizando reserva dinámica de memoria. El destructor debe
liberar la memoria ocupada por las cadenas de caracteres, para lo
que hay que utilizar la sentencia delete [] pstr;.
Además se imprime un mensaje incluyendo la dirección
del objeto borrado. De este modo se puede saber cuándo se
crea y se destruye cada objeto, lo cual
será de gran utilidad en los ejemplos que se presentarán
más adelante.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
función miembro setcad()
void cadena::setcad(char* c)
{
nchar
= strlen(c);
delete [] pstr;
pstr = new char[nchar + 1];
strcpy(pstr, c);
cout << "Función setcad()" << endl;
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
La
función miembro setcad() permite sustituir
el contenido de un objeto cadena a partir de una cadena
de caracteres estándar. Esta función se diferencia
del constructor general visto previamente en que actúa sobre
un objeto que ya existe. Esto hace que la primera tarea a realizar
sea liberar la memoria a la que la variable pstr apuntaba
anteriormente. Después se reserva memoria para el nuevo contenido
al que pstr apuntará. Los constructores siempre
actúan sobre un objeto recién creado, y por eso no
tienen que liberar memoria.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
operador de asignación sobrecargado (=)
cadena& cadena::operator= (const cadena& cd)
{
if(*this
!= cd) {
nchar = cd.nchar;
delete [] pstr;
pstr = new char[nchar + 1];
strcpy(pstr, cd.pstr);
cout << "Operador =" << endl;
}return
*this;
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Este
operador miembro permite asignar un objeto cadena
a otro, por ejemplo en la forma c2=c1;, donde se supone
que ambos objetos existían previamente. El objeto c2
sería el argumento implícito y c1 el
que se pasa explícitamente por ventana. Por eso a las variables
miembro de c2 se accede directamente, mientras que
a las de c1 -representado por cd- hay
que acceder con el operador punto (.). Como c1 ya existía,
hay que liberar la memoria que estaba ocupando. Este operador se
define con un valor de retorno que es una referencia
a un objeto cadena para poderlo utilizar en asignaciones
múltiples (c3=c2=c1;). Como valor de retorno
se devuelve el propio miembro izquierdo de la asignación,
que es el argumento implícito del operador miembro; para
hacer referencia a dicho objeto hay que utilizar el
puntero this (*this es el objeto, mientras
que this es su dirección).
En
la definición del operador miembro (=) llama
la atención la sentencia if que aparece al
comienzo de la función. ¿Cuál es su razón
de ser? Su razón de ser es evitar los efectos perjudiciales
que puede tener una sentencia de asignación de un objeto
a sí mismo (c1=c1;). Esta sentencia no es verdaderamente
muy útil, pero no hay razón para no prever las cosas
de modo que sea inofensiva; y
la verdad es que si no se introduce ese if esta sentencia
es todo menos inofensiva. Al asignarse un objeto a otro, lo primero
que se hace es liberar la memoria ocupada por el primer operando
(el que está a la izquierda), para después sacar una
copia de la memoria a la que apunta el segundo operando y asignar
su dirección a la variable miembro del primero. El problema
es que si ambos objetos coinciden (son el mismo objeto), al liberar
la memoria del primer operando, el segundo (que es el mismo) la
pierde también y ya no hay nada para copiar ni para asignar,
llegándose en realidad a la destrucción del objeto.
El remedio es chequear, a la entrada de la definición del
operador (=), que los dos operandos son realmente
objetos distintos. Una vez más, es necesario utilizar el
puntero this.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
operador de inserción en ostream
ostream& operator<< (ostream& co, const cadena&
cad) {
co
<< cad.pstr;
return co;
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
La
definición del operador inserción (<<)
sobrecargado es muy sencilla, pues lo único que se hace es
insertar en el flujo de salida la cadena de caracteres estándar
a la que apunta la variable miembro pstr.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
Definiciones del operador de concatenación de cadenas
cadena operator+ (const cadena& a, const cadena& b)
{
cadena
c;
c.nchar = a.nchar + b.nchar;
c.pstr = new char[c.nchar + 1];
strcpy(c.pstr, a.pstr);
strcat(c.pstr, b.pstr);
return c;
}
cadena
operator+ (const cadena& a, const char* ch)
{
cadena
c;
c.nchar = a.nchar + strlen(ch);
c.pstr = new char[c.nchar + 1];
strcpy(c.pstr, a.pstr);
strcat(c.pstr, ch);
return c;
}
cadena
operator+ (const char* ch, const cadena& b)
{
cadena
c;
c.nchar = strlen(ch) + b.nchar;
c.pstr = new char[c.nchar + 1];
strcpy(c.pstr, ch);
strcat(c.pstr, b.pstr);
return c;
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Las
tres definiciones anteriores del operador de concatenación
(+) son casi evidentes. En todos los casos se crea un nuevo
objeto cadena en el que se concatenan las cadenas de los dos operandos.
Se podría haber realizado la programación de modo
que el segundo operando se añadiera al primero (de modo semejante
a como actúa la función strcat()). Se
ha preferido hacerlo así para dejar intactos los operandos,
y porque la otra forma de actuar sería más propia
del operador de asignación incremental (+=).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
sobrecarga de los operadores relacionales
int operator== (const cadena& c1, const cadena& c2)
{
if(strcmp(c1.pstr,
c2.pstr)==0) return 1;
return 0;
}
int
operator!= (const cadena& c1, const cadena& c2)
{
int
dif = strcmp(c1.pstr, c2.pstr);
if(dif<0) return (-1);
if(dif==0)
return 0;
else
return 1;
}
// fin del fichero cadena.cpp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
La
sobrecarga de los operadores relacionales está
muy clara y no requiere explicaciones distintas de las que se han
dado al hablar de su declaración.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|