Constructores
Los constructores son funciones miembro especiales que sirven para inicializar un objeto de una determinada clase al mismo tiempo que se declara.
Los constructores son especiales por varios motivos:
-Tienen el mismo nombre que la clase a la que pertenecen.
-No tienen tipo de retorno, y por lo tanto no retornan ningún valor.
-No pueden ser heredados.
-Por último, deben ser públicos, no tendría ningún sentido declarar un constructor como privado, ya que siempre se usan desde el exterior de la clase, ni tampoco como protegido, ya que no puede ser heredado.
Sintaxis:
class <identificador de clase> {
public:
<identificador de clase>(<lista de parámetros>) [: <lista de constructores>] {
<código del constructor>
}
...
} |
Añadamos un constructor a nuestra clase pareja:
#include <iostream>
using namespace std;
class pareja {
public:
// Constructor
pareja(int a2, int b2);
// Funciones miembro de la clase "pareja"
void Lee(int &a2, int &b2);
void Guarda(int a2, int b2);
private:
// Datos miembro de la clase "pareja"
int a, b;
};
pareja::pareja(int a2, int b2) {
a = a2;
b = b2;
}
void pareja::Lee(int &a2, int &b2) {
a2 = a;
b2 = b;
}
void pareja::Guarda(int a2, int b2) {
a = a2;
b = b2;
}
int main() {
pareja par1(12, 32);
int x, y;
par1.Lee(x, y);
cout << "Valor de par1.a: " << x << endl;
cout << "Valor de par1.b: " << y << endl;
return 0;
} |
Si no definimos un contructor el compilador creará uno por defecto, sin parámetros, queno hará absolutamente nada. Los datos del los objetos declarados en el programa contendrán basura.
Si una clase posee constructor, será llamado siempre que se declare un objeto de esa clase. Si ese constructor requiere argumentos, como en este caso, es obligatorio suministrarlos.
Por ejemplo, las siguientes declaraciones son ilegales:
pareja par1;
pareja par1();
La primera porque el constructor de "pareja" requiere dos parámetros, y no se suministran.
La segunda es ilegal por otro motivo más complejo. Aunque existiese un constructor sin parámetros, no se debe usar esta forma para declarar el objeto, ya que el compilador lo considera como la declaración de un prototipo de una función que devuelve un objeto de tipo "pareja" y no ite parámetros.
Cuando se use un constructor sin parámetros para declarar un objeto no se deben escribir los paréntesis.
Y las siguientes declaraciones son válidas:
pareja par1(12,43);
pareja par2(45,34);
Constructor por defecto
Cuando no especifiquemos un constructor para una clase, el compilador crea uno por defecto sin argumentos. Por eso el ejemplo del capítulo anterior funcionaba correctamente. Cuando se crean objetos locales, los datos no se inicializarían, contendrían la "basura" que hubiese en la memoria asignada al objeto. Si se trata de objetos globales, los datos se inicializan a cero.
Para declarar objetos usando el constructor por defecto o un constructor que hayamos declarado sin parámetros no se debe usar el paréntesis:
pareja par2();
Se trata de un error frecuente cuando se empiezan a usar clases, lo correcto es declarar el objeto sin usar los paréntesis:
pareja par2;
Inicialización de objetos
Hay un modo específico para inicializar los datos de los objetos en los constructores, que consiste en invocar los constructores de los objetos miembro antes de las llaves de la definición del constructor.
En C++ incluso las variables de tipos básicos como int, char o float son objetos. En C++ cualquier variable (u objeto) tiene, al menos un constructor, el constructor por defecto, incluso aquellos que son de un tipo básico.
Sólo los constructores de las clases iten inicializadores. Cada inicializador consiste en el nombre de la variable miembro a inicializar, seguida de la expresión que se usará para inicializarla entre paréntesis. Los inicializadores se añadirán a continuación del paréntesis cerrado que encierra a los parámetros del constructor, antes del cuerpo del constructor y separado del paréntesis por dos puntos ":".
Por ejemplo, en el caso anterior de la clase "pareja" teníamos este constructor:
pareja::pareja(int a2, int b2) {
a = a2;
b = b2;
}
Podemos (y debemos) sustituir ese constructor por este otro:
pareja::pareja(int a2, int b2) : a(a2), b(b2) {}
Por supuesto, también pueden usarse inicializadores en línea, dentro de la declaración de la clase.
Ciertos es obligatorio inicializarlos, ya que no pueden ser asignados, por ejemplo las constantes o las referencias. Es preferible usar la inicialización siempre que sea posible en lugar de asignaciones, ya que frecuentemente, es menos costoso y más predecible inicializar objetos en el momento de la creación que usar asignaciones.
Veremos más sobre este tema cuando veamos ejemplos de clases que tienen como objetos de otras clases.
Sobrecarga de constructores
Los constructores son funciones, también pueden definirse varios constructores para cada clase, es decir, el constructor puede sobrecargarse. La única limitación (como en todos los casos de sobrecarga) es que no pueden declararse varios constructores con el mismo número y el mismo tipo de argumentos.
Por ejemplo, añadiremos un constructor adicional a la clase "pareja" que simule el constructor por defecto:
class pareja {
public:
// Constructor
pareja(int a2, int b2) : a(a2), b(b2) {}
pareja() : a(0), b(0) {}
// Funciones miembro de la clase "pareja"
void Lee(int &a2, int &b2);
void Guarda(int a2, int b2);
private:
// Datos miembro de la clase "pareja"
int a, b;
}; |
De este modo podemos declarar objetos de la clase pareja especificando los dos argumentos o ninguno de ellos, en este último caso se inicializarán los dos datos con cero.
Constructores con argumentos por defecto
También pueden asignarse valores por defecto a los argumentos del constructor, de este modo reduciremos el número de constructores necesarios.
Para resolver el ejemplo anterior sin sobrecargar el constructor suministraremos valores por defecto nulos a ambos parámetros:
class pareja {
public:
// Constructor
pareja(int a2=0, int b2=0) : a(a2), b(b2) {}
// Funciones miembro de la clase "pareja"
void Lee(int &a2, int &b2);
void Guarda(int a2, int b2);
private:
// Datos miembro de la clase "pareja"
int a, b;
}; |
Asignación de objetos
Probablemente ya lo imaginas, pero la asignación de objetos también está permitida. Y además funciona como se supone que debe hacerlo, asignando los valores de los datos .
Con la definición de la clase del último ejemplo podemos hacer lo que se ilustra en el siguiente:
#include <iostream>
using namespace std;
int main() {
pareja par1(12, 32), par2;
int x, y;
par2 = par1;
par2.Lee(x, y);
cout << "Valor de par2.a: " << x << endl;
cout << "Valor de par2.b: " << y << endl;
return 0;
} |
La línea "par2 = par1;" copia los valores de los datos de par1 en par2.
En realidad, igual que pasa con los constructores, el compilador crea un operador de asignación por defecto, que copia los valores de todos los datos miembro de un objeto al otro. Veremos más adelante que podemos redefinir ese operador para nuestras clases, si lo consideramos necesario.
Constructor copia
Un constructor de este tipo crea un objeto a partir de otro objeto existente. Estos constructores sólo tienen un argumento, que es una referencia a un objeto de su misma clase.
En general, los constructores copia tienen la siguiente forma para sus prototipos:
tipo_clase::tipo_clase(const tipo_clase &obj); |
De nuevo ilustraremos esto con un ejemplo y usaremos también "pareja":
class pareja {
public:
// Constructor
pareja(int a2=0, int b2=0) : a(a2), b(b2) {}
// Constructor copia:
pareja(const pareja &p);
// Funciones miembro de la clase "pareja"
void Lee(int &a2, int &b2);
void Guarda(int a2, int b2);
private:
// Datos miembro de la clase "pareja"
int a, b;
};
// Definición del constructor copia:
pareja::pareja(const pareja &p) : a(p.a), b(p.b) {} |
Para crear objetos usando el constructor copia se procede como sigue:
int main() {
pareja par1(12, 32)
pareja par2(par1); // Uso del constructor copia: par2 = par1
int x, y;
par2.Lee(x, y);
cout << "Valor de par2.a: " << x << endl;
cout << "Valor de par2.b: " << y << endl;
return 0;
} |
Aunque pueda parecer confuso, el constructor copia en otras circunstancias:
int main() {
pareja par1(12, 32)
pareja par2 = par1; // Uso del constructor copia
... |
En este caso se usa el constructor copia porque el objeto par2 se inicializa al mismo tiempo que se declara, por lo tanto, el compilador busca un constructor que tenga como parámetro un objeto del tipo de par1, es decir, busca un constructor copia.
Tanto es así que se invoca al constructor copia aunque el valor a la derecha del signo igual no sea un objeto de tipo pareja.
Disponemos de un constructor con valores por defecto para los parámetros, así que intentemos hacer esto:
int main() {
pareja par2 = 14; // Uso del constructor copia
... |
Ahora el compilador intenta crear el objeto par2 usando el constructor copia sobre el objeto 14. Pero 14 no es un objeto de la clase pareja, de modo que el compilador usa el constructor de pareja con el valor 14, y después usa el constructor copia.
También para cualquier clase, si no se especifica ningún constructor copia, el compilador crea uno por defecto, y su comportamiento es exactamente el mismo que el del definido en el ejemplo anterior.
Para la mayoría de los casos esto será suficiente, pero en muchas ocasiones necesitaremos redefinir el constructor copia.
Ir al Principio
Comentarios