1.
En la definición de la clase no se ha reservado memoria
para la cadena de caracteres, sólo para el puntero pstr.
La razón es que no se sabe a priori cuántos caracteres
va a tener cada objeto de la clase cadena y se piensa
por ello utilizar reserva dinámica de memoria: cuando se
sepa eltexto que se quiere guardar en un objeto determinado, se
reservará la memoria necesaria para ello.
2.
Se han definido tres constructores y un destructor.
El primer constructor es un constructor por defecto que
no requiere ningún argumento, pues inicializa el objeto
a una cadena vacía de cero caracteres. Al segundo constructor
se le pasa como argumento un puntero a una cadena de caracteres
cualquiera y crea un objeto que apunta a esa cadena. El tercer
constructor es un constructor de copia que recibe
como argumento una referencia constante a un objeto de la
clase cadena. El argumento -un objeto de la clase cadena-
se pasa por referencia por motivos de eficiencia
(para no tener que sacar una copia); por otra parte se pasa como
const por motivos de seguridad (para evitar que
sea modificado por el constructor). En lo sucesivo ya no se volverá
a justificar el paso por referencia y como const
de los argumentos que no deban ser modificados. En este
caso, los constructores de oficio no sirven, pues
ya se han definido otros constructores y además una
variable miembro es un puntero y se va a utilizar
reserva dinámica de memoria.
3.
Se ha definido un destructor porque las cadenas
de caracteres son arrays y se va a utilizar reserva
dinámica de memoria, memoria que habrá que
liberar expresamente.
4.
La función miembro setcad() permite proporcionar
o cambiar el valor de la cadena de caracteres que contiene un
objeto. Es una función miembro típica y sólo
se ha introducido aquí a modo de ejemplo. Se le pasa como
argumento un puntero a char que contiene la dirección
del primer carácter del texto a introducir. No necesita
devolver ningún valor.
5.
Se han definido siete operadores sobrecargados -uno
como miembro y seis como friends-,
y hay cuatro operadores relacionales más incluidos entre
comentarios que se podrían terminar de definir de modo
similar.
6.
El primer operador sobrecargado es el operador de asignación
(=). Como es un operador binario que modifica el primer
operando debe necesariamente ser definido como miembro.
Este operador asigna un objeto cadena a otro. El
miembro izquierdo de la igualdad es el primer operando y en este
caso es el argumento implícito del operador. En la lista
de argumentos formales que figura entre paréntesis sólo
hay que incluir el segundo operando, que es un objeto cadena que
se pasa por referencia y como const, pues
no debe ser modificado. El valor de retorno de este
operador requiere un comentario especial.
7.
Estrictamente hablando, en este caso el operador de asignación
(=) no necesita ningún valor de retorno: su misión
en una sentencia tal como c1 = c2; queda suficientemente
cumplida si hace que las variables miembro de c1 sean
iguales a las de c2 (siendo c1 y c2 dos
objetos de la case cadena). Sin embargo el operador
(=) estándar de C tiene un valor de retorno que
es una referencia al resultado de la asignación. Esto es
lo que permite escribir sentencias como la
siguiente: a = b = c; que es equivalente a hacer
b igual a c, y devolver un valor
que puede ser asignado a a. Al final las tres variables
tienen el mismo valor. Para que el operador sobrecargado se parezca
todo lo posible al de C y para poder escribir sentencias de asignación
múltiples con objetos de la clase cadena (c1 = c2
= c3;), es necesario que el operador de asignación
sobrecargado (=) tenga valor de retorno. El valor de retorno
es una referencia al primer operando por motivos de eficiencia,
pues si no hay que crear un objeto nuevo, diferente de los dos
operandos, para devolverlo como valor de retorno.
8.
A continuación aparecen tres operadores suma
(+) sobrecargados para realizar concatenación
de
cadenas.
Puede observarse que no son operadores miembro sino friend.
Así pues, no hay argumento implícito, y los dos
argumentos aparecen entre paréntesis. Los tres operadores
(+) tienen un objeto de la clase cadena como valor
de retorno. El primero de ellos concatena dos objetos
de la clase cadena, y devuelve un nuevo objeto cadena
cuyo texto es la concatenación de las cadenas de
caracteres de los objetos operandos. Este resultado es diferente
del de la función strcat(), que añade
el texto del segundo argumento sobre el primer argumento (modificándolo
por tanto). En este caso se ha preferido obtener un nuevo objeto
y no modificar los operandos. Los otros dos operadores (+) sobrecargados
concatenan el texto de un objeto cadena y una cadena
de caracteres estándar, y una cadena de caracteres
estándar y el texto de un objeto cadena,
respectivamente. Recuérdese que el orden y el tipo de los
argumentos deciden qué definición del operador sobrecargado
se va a utilizar. Si se quiere poder escribir tanto c1+"
anexo" como "prefacio "+c2 es
necesario programar dos operadores distintos, además del
que concatena dos objetos c1+c2.
9.
¿Por qué los operadores (+) anteriores se han
definido como friend y no como miembros?
La verdad es que ésta es una cuestión interesante
y no fácil de ver a primera vista. En realidad, la primera
y la segunda de las definiciones podrían haberse hecho
con operadores miembro. Sin embargo, la tercera no puede hacerse
más que como friend. ¿Por qué?
Pues porque los operadores miembro siempre tienen un primer
operando que es miembro de la clase y que va como argumento
implícito. La tercera definición del operador
(+) no cumple con esta condición, pues su
primer operando (para concatenar por ejemplo "prefacio
"+c2) es una simple cadena de caracteres. Este hecho
es muy frecuente cuando se sobrecargan los operadores aritméticos:
puede por ejemplo sobrecargarse el operador producto (*) para
pre y post-multiplicar matrices por un escalar. Al menos en el
caso de la pre-multiplicación por el escalar es necesario
que el operador (*) no sea miembro de la clase matriz.
10.
A continuación figura la declaración de los operadores
relacionales (==) y (!=), que permiten saber si dos objetos
contienen o no el mismo texto. En este caso el valor de retorno
del test de igualdad (==) es un int
que representará true (1) o false (0). Para
el operador de desigualdad (!=) la situación
es un poco más compleja, pues se desea que sirva para ordenar
cadenas alfabéticamente, de modo similar a la función
strcmp(). Por ello el valor de retorno de c1!=c2
es un (-1) si c1 es diferente y anterior
alfabéticamente a c2, un cero (0 ó
false) si las cadenas son idénticas, y un (1) si son diferentes
y c1 es posterior a c2 en orden alfabético.
11.
Los operadores relacionales cuyas declaraciones
están contenidas entre comentarios /*
*/ no
son necesarios. No hay inconvenientes en comparar objetos
de la clase cadena y cadenas de caracteres estándar,
aunque no se disponga de los operadores relacionales cuyos argumentos
se ajusten a los tipos exactos. La razón está en
la inteligencia contenida en los compiladores
de C++: cuando el compilador encuentra un operador (==)
o (!=) que relaciona una cadena estándar
y un objeto de la clase cadena, intenta ver si dispone
de alguna forma segura de promover o convertir
la cadena estándar a un objeto de la clase
cadena. Como tiene un constructor general
que inicializa un objeto cadena a partir de una
cadena estándar, utiliza dicho constructor para convertir
la cadena estándar en un objeto cadena y
luego poder utilizar la definición del operador relacional
que compara dos objetos de la clase cadena. Esto
lo hace independientemente de qué operando es el objeto
y cuál es la cadena estándar. Esto es similar a
lo que hace el compilador de C cuando se le pide
que compare un int con un double:
antes de hacer la comparación promueve (convierte)
el int a double, y luego realiza la
comparación entre dos variables double.
12.
La pregunta obligada es, ¿y no pasa lo mismo
con el operador (+)? ¿No bastaría con definir
un único operador (+) que concatenase objetos de
la clase cadena y confiar a la inteligencia del compilador
el promover a objetos las cadenas de caracteres
estándar que se quisieran concatenar, ya fueran el primer
o el segundo operando? La respuesta es que sí: en este
caso no hace falta sobrecargar el operador (+) con tres
definiciones. Se ha hecho porque el problema se resuelve
de modo más eficiente (no hay que perder
tiempo en promover variables creando objetos) y porque en otras
aplicaciones más complicadas que las cadenas de caracteres
lo de lapromoción de una variable estándar a un
objeto de una clase puede que no esté nada claro. Por ejemplo,
en la clase matriz, ¿cómo se promueve
un escalar a matriz? Si es para la operación suma
se podría hacer con una matriz cuyos elementos fueran todos
igual al escalar, pero si es para la operación producto
sería más razonable promover el escalar a una matriz
diagonal. En definitiva, C++ ofrece posibilidades muy interesantes,
pero no conviene abusar de ellas.
13.
Finalmente, en la declaración de la clase cadena
se muestra la sobrecarga del operador de inserción
en el flujo de salida (<<), que tiene como finalidad
el poder utilizar cout con objetos de la clase.
Este operador, al igual que el (>>), se suele sobrecargar
como operador friend.Recibe dos argumentos: Una
referencia al flujo de salida (ostream, de output
stream) y una referencia constante al objeto cadena que
se desea insertar en dicho flujo. El valor de retorno es
una referencia al flujo de salida ostream en el
que ya se habrá introducido el objeto cadena.