|
5.1
Introducción
Los
tipos definidos por el usuario o tipos
abstractos de datos (TAD)
empaquetan elementos dato con las operaciones que se realizan sobre esos datos.
C++ soporta los TAD con el tipo clase que puede ser implementado con estructuras,
uniones y clases.
5.2
Abstracción
de datos
Una
característica importante de cualquier lenguaje de programación es la
capacidad de crear tipos de datos definidos por el usuario. Aunque se pueden
crear en C sus propios tipos, utilizando las palabras reservadas typedef y
struct, los tipos resultantes no se pueden integrar fácilmente en el resto del
programa. Además, en C, sólo se pueden definir los tipos en términos de
datos; es decir, las funciones utilizadas para manipular esos tipos no son parte
de la definición del tipo.
Una definición de un tipo que incluye datos y funciones y el modo para
encapsular los detalles, se conoce como tipo abstracto de dato. En C++ se
implementa mediante el uso de tipos de datos definidos por el usuario, llamados clases.
clase
= datos + funciones
Una
diferencia importante entre C y C++, es que en C++ se pueden declarar funciones
dentro de una estructura (no se requiere declarar punteros a funciones). Las
estructuras pueden tener también especificadas regiones de acceso (medios en
que se puede controlar el acceso a los datos).
La abstracción de datos en C++ se obtiene con los tipos de datos estructura (struct)
y clase (class).
5.3
Concepto de
clase
Una
clase es un tipo de dato que contiene
uno o más elementos dato llamados miembros dato, y cero, una o más funciones
que manipulan esos datos (llamadas funciones miembro). Una clase se puede
definir con struct, union
o class. La sintaxis de una clase
es:
|
class
nombre_clase |
|
{ |
|
miembro1; |
|
miembro2; |
|
... |
|
funcion_miembro1(); |
|
funcion_miembro2(); |
|
... |
|
}; |
Una
clase es sintácticamente igual a una estructura, con la única diferencia
de que en el tipo class todos los
miembros son por defecto privados mientras que en el tipo struct son por defecto públicos.
En C se utiliza el término variable estructura para referirse a una variable de
tipo estructura. En C++ no se utiliza el término variable de clase, sino instancia
de la clase.
El término objeto es muy importante y
no es más que una variable, que a su vez no es más que una instancia de una
clase. Por consiguiente una clase es:
|
class
cliente |
|
{ |
|
char nom[20]; |
|
char num; |
|
}; |
y
un objeto de esta clase se declara: cliente
cli;
Una
definición de una clase consta de dos
partes: una declaración y una implementación.
La declaración lista los miembros de la clase. La implementación o cuerpo
define las funciones de la clase.
Una de las características fundamentales de una clase es
ocultar tanta información como sea posible. Por consiguiente, es necesario
imponer ciertas restricciones en el modo en que se puede manipular una clase y
de cómo se pueden utilizar los datos y el código dentro de una clase.
Una clase puede contener partes públicas
y partes privadas. Por defecto, todos los miembros definidos en la clase son
privados. Para hacer las partes de una clase públicas (esto es, accesibles
desde cualquier parte del programa) deben declararse después de la palabra
reservada public. Todas las variables o funciones definidas después de
public son accesibles a las restantes funciones del programa. Dado que una
característica clave de la POO es la ocultación de datos, debe tenerse
presente que aunque se pueden tener variables públicas, desde un punto de vista
conceptual se debe tratar de limitar o eliminar su uso. En su lugar, deben
hacerse todos los datos privados y controlar el acceso a ellos a través de
funciones públicas.
|
class
artiiculo |
|
{ |
|
private: |
|
float
precio; |
|
char
nombre[]; |
|
public: |
|
void
indicar(); |
|
}; |
Por
defecto u omisión todo lo declarado dentro de una clase es privado y sólo se
puede acceder a ello con las funciones miembro declaradas en el interior de la clase o con funciones
amigas.
Los miembros que se declaran en la sección protegida de una clase sólo pueden
ser accedidos por funciones miembro declaradas dentro de la clase, por funciones
amigas o por funciones miembro de clases derivadas.
A los miembros que se declaran en la región pública de una clase se puede
acceder a través de cualquier objeto de la clase de igual modo que se accede a
los miembros de una estructura en C.
|
class
alfa |
|
{ |
|
int x;
//miembros dato privados |
|
float y; |
|
char z; |
|
public: |
|
double k;
//miembro dato público |
|
void fijar(int,float,char);
//funciones miembro públicas |
|
void visualizar(); |
|
}; |
|
void
main() |
|
{ |
|
alba obj;
//declaración de un objeto |
|
obj.fijar(3,2.1,'a');
//invocar a una función miembro |
|
obj.visualizar();
//invocar a una función miembro |
|
obj.x=4;
//error: no se puede acceder a datos privados |
|
obj.k=3.2;
//válido: k está en la región pública |
|
} |
La
definición de funciones miembro es muy similar a la definición ordinaria de
función. Tienen una cabecera y un cuerpo y pueden tener tipos y argumentos. Sin
embargo, tienen dos características especiales:
a)
Cuando se define una función miembro se utiliza el operador de resolución de
ámbito (::) para identificar la
clase a la que pertenece la función.
b) Las funciones miembro (métodos) de las clases pueden acceder a las
componentes privadas de la clase.
| Opción
1 |
Opción
2 |
|
class
ejemplo |
class
ejemplo |
|
{ |
{ |
|
int x,y; |
int
x,y; |
|
public: |
public: |
|
void f() |
void f(); |
|
{ |
}; |
|
cout<<"x=
"<<x<<" y= "<<y<<endl; |
void
ejemplo::f() |
|
} |
{ |
|
}; |
cout<<"x=
"<<x<<" y= "<<y<<endl; |
|
} |
En
la primera opción la función está en línea (inline).
Por cada llamada a esta función,
el compilador genera (vuelve a copiar) las diferentes instrucciones de la función.
En la segunda opción (la más deseable) la función f se llamará con una
llamada verdadera de función.
La declaración anterior significa que la función f es miembro de la clase
ejemplo. El nombre de la clase a la cual está asociada la función miembro se añade
como prefijo al nombre de la función. El operador :: separa el nombre de la
clase del nombre de la función. Diferentes clases pueden tener funciones del
mismo nombre y la sintaxis indica la clase asociada con la definición de la
función.
5.4
Objetos
En
C++ un objeto es un elemento declarado
de un tipo clase. Se conoce también como una instancia de una clase.
Los objetos se pueden tratar como cualquier variable C. La principal diferencia
es que se puede llamar a cualquiera de las funciones que pertenecen a un objeto,
esto es, se puede enviar un mensaje a ella.
|
class
rectangulo |
|
{ |
|
int base,altura; |
|
public: |
|
void dimensiones(int,int); |
|
int area(); |
|
}; |
|
void
rectangulo::dimensiones(int b,int h) |
|
{ |
|
base=b; |
|
altura=h; |
|
} |
|
int
rectangulo::area() |
|
{ |
|
return base*altura; |
|
} |
|
void
main() |
|
{ |
|
rectangulo r;
//declarar el objeto |
|
r.dimensiones(3,5);
//definir el tamaño |
|
cout<<"area
"<<r.area(); |
|
} |
5.5
Acceso a los
miembros de una clase
A
los miembros de una clase se accede de igual forma que a los miembros de una
estructura. Existen dos métodos para acceder a un miembro de una clase: el
operador punto (.) y el operador flecha (->)
que actúan de modo similar.
|
class
contador |
|
{ |
|
public:leer() {return 1;} |
|
}; |
|
void
main() |
|
{ |
|
contador c; |
|
contador *p=new(contador); |
|
leer();
//error: función desconocida, no en ámbito |
|
cout<<c.leer(); //correcto |
|
cout<<p->leer();
//correcto |
|
} |
5.6
Clases
vacías
Aunque
el propósito de una clase es encapsular código y datos, una clase puede tener
también una declaración vacía.
class
vacia{};
Naturalmente,
no se pueden realizar grandes cosas con esta clase, pero se pueden crear objetos
de ella: vacia
obj;
Con
frecuencia, en el desarrollo de un proyecto grande se necesitan comprobar
implementaciones de primeras versiones en las que algunas clases no están
totalmente identificadas o implementadas todavía. Se suelen denominar
resguardos (stubs) y se diseñan para
obtener códigos que se compilen sin errores, permitiendo comprobar alguna parte
de ellos.
5.7
Clases
anidadas
La
potencia de abstracción de una clase se puede incrementar incluyendo otras
declaraciones de clase. Una clase declarada en el interior de otra se denomina clase
anidada, y se puede considerar como una clase miembro.
El identificador de una clase anidada está sujeto a las mismas reglas de acceso
que los restantes miembros. Si una clase anidada se declara en la sección private
de la clase circundante, la clase anidada será utilizable sólo por los
miembros datos de la clase que la circunde. La clase que encierra puede acceder
al nombre de la clase anidada sin resolución de ámbito. Si un nombre de una
clase anidada es accesible a una clase o función que no la circunda, se debe
aplicar el operador ::.
|
class
externa |
|
{ |
|
public: |
|
class interna |
|
{ |
|
public: |
|
int
x; |
|
}; |
|
}; |
|
void
main() |
|
{ |
|
externa::interna valor; |
|
int v=valor.x; |
|
} |
5.8
Los miembros
dato
La
lista de miembros de una clase puede comprender cualquier tipo válido en C++.
Puede contener tipos primarios, estructuras e incluso punteros a cualquier tipo
válido. Los miembros dato pueden ser incluso clases. En cualquier caso sólo
las instancias de clases definidas o declaradas previamente pueden ser miembros.
Los
miembros dato declarados en la clase se deben considerar equivalentes a campos
de una estructura, no a variables. Tal como las estructuras, se debe declarar un
objeto de un tipo clase y a continuación se inicializan sus miembros dato.
Un miembro de una clase se puede declarar estático (static).
Para un miembro dato, la designación static significa que existe sólo una
instancia de ese miembro. Un miembro dato estático es compartido por todos los
objetos de una clase.
A un miembro dato static se le asigna una zona fija de almacenamiento en tiempo
de compilación, al igual que una variable global, pero el identificador de la
variable está dentro de ámbito utilizando solamente el operador :: con el
nombre de la clase.
Los miembros datos se asignan generalmente con la misma clase de almacenamiento.
Para declarar o inicializar un miembro static se utiliza la misma notación que
una variable global.
|
class
ejemplo |
|
{ |
|
public: |
|
static int valor;
//declarar miembro estático |
|
}; |
|
int
ejemplo::valor; //definir miembro estático |
|
void
main() |
|
{ |
|
ejemplo e1,e2; |
|
e1.valor=1; |
|
e2.valor=10; |
|
} |
A
los miembros dato static se puede acceder:
1.
Utilizando el operador punto
2. Utilizando el operador flecha, si el lado izquierdo es un puntero a objeto
3. Utilizando el identificador de la clase sin referenciar un objeto real:
ejemplo::valor=3;
Los
miembros datos static no siempre tienen que ser public.
|
class
ejemplo |
|
{ |
|
private: |
|
static int valor;
//declarar miembro estático |
|
}; |
|
int
ejemplo::valor=5; //definir miembro estático |
|
void
main() |
|
{ |
|
ejemplo e1; |
|
e1.valor=1;
//error: aceso no válido |
|
} |
Para
acceder a un miembro dato private static se necesita utilizar el operador ::.
Otros medios son:
1.- A través de una función miembro de la clase
2.- A través de una función declarada amiga de la clase
5.8
Ámbito de
una clase
Una
clase actúa como cualquier otro tipo de dato con respecto al ámbito. Todos los
miembros de una clase se dice que están en el ámbito de esa clase; cualquier
miembro de una clase puede referenciar a cualquier otro miembro de la misma
clase.
Las funciones miembro de una clase tienen acceso no restringido a los miembros
dato de esa clase. El acceso a los miembros dato y funciones de una clase fuera
del ámbito de la clase está controlado por el programador. La idea es
encapsular la estructura de datos y funcionalidad de una clase, de modo que el
acceso a la estructura de datos de la clase desde fuera de las funciones miembro
de la clase, sea limitada o innecesaria.
El nombre de la clase tiene que ser único dentro de su ámbito.
5.9
Especificadores
de acceso a los miembros de una clase
En
una definición de clase, un especificador
de acceso se utiliza para controlar la visibilidad de los miembros de una
clase fuera del ámbito de la clase.
Los miembros de una clase pueden ser públicos, privados o protegidos. Las
palabras reservadas public, private
y protected
se utilizan para controlar el modo de acceso a la clase.
Dentro de una declaración de clase, cada una de estas palabras se puede
utilizar para preceder a una o más declaraciones de los miembros de una clase
·
Acceso
protegido. Los miembros
protegidos significan que sólo se puede acceder a ellos por funciones miembro
dentro de la misma clase y por funciones miembro de clases derivadas de esta
clase.
·
Acceso
público. Los miembros públicos
son accesibles por cualquier parte del programa.
·
Acceso
privado. Los miembros privados
sólo pueden ser utilizados por las funciones miembro de la clase y las
funciones amigas de la clase.
5.10
Funciones
miembro
Las
funciones miembro son miembros de una
clase y son funciones diseñadas para implementar las operaciones permitidas
sobre los tipos de datos de una clase. Para declarar una función miembro hay
que situar su prototipo en el cuerpo de la clase. No hay que definir la función
dentro de la clase; dicha definición puede estar fuera de la clase e incluso en
un archivo independiente, aunque también pueden ser definidas en línea dentro
de la clase.
Las
funciones miembro son necesarias para acceder a los datos privados.
En general, son públicas; si no lo fueran, ninguna otra función podría
llamarlas. Se pueden declarar para devolver valores con tipos incluyendo objetos
de clases, punteros o referencias. Pueden ser declaradas también para aceptar
cualquier número y tipo de argumentos. Los argumentos por defecto están
permitidos, así como la notación de puntos suspensivos.
5.11
El puntero
this
Nunca
se puede llamar a una función miembro privada de una clase a menos que se
asocie con un objeto. En C cada vez que se utiliza un puntero para acceder a los
miembros de una estructura, debe utilizarse el operador de puntero (->) para
acceder a los datos. ¿Cómo sabe una función miembro cuál es la instancia de
una clase asociada con ella?
El método utilizado por C++ es añadir un argumento extra oculto a las
funciones miembro. Este argumento es un puntero al objeto de la clase que lo
enlaza con la función asociada y recibe un nombre especial denominado this.
Dentro de una función miembro, this
apunta al objeto asociado con la invocación de la función miembro. El tipo de
this en una función miembro de una clase T es T
*const es
decir, un puntero constante a un objeto de tipo T.
Normalmente, el programador no necesita preocuparse por este puntero, ya que C++
realiza la operación automáticamente, haciendo el uso de this
transparente a las funciones miembro que la utilizan. Dentro de una función
miembro, se pueden hacer referencias a los miembros del objeto asociado a ella
con el prefijo this y el operador de acceso ->. Sin embargo, este proceso no
es necesario, ya que es redundante. Consideremos como ejemplo el constructor de
una clase que manipula números complejos:
|
complejo::complejo
(float a,float b) |
|
{ |
|
real=a; |
|
imag=b; |
|
} |
|
Este
constructor se puede escribir: |
|
complejo::complejo
(float a,float b) |
|
{ |
|
this->real=a; |
|
this->imag=b; |
|
} |
|
this->nombre_miembro
: apunta a un miembro |
|
*this
: es el objeto total. Es un valor constante |
|
this
: es la dirección del objeto apuntado |
5.12
Funciones miembro estáticas
Las
funciones miembro static sólo pueden
acceder a otras funciones y datos declarados en una clase, pero no pueden
manipular funciones ni datos no estáticos. La razón de esta característica es
que una función miembro static no tiene asignado un puntero this y por ello no puede acceder a miembros dato de la clase a menos
que se pase explícitamente este puntero this.
5.13
Constructores
Un
constructor es una función especial
que sirve para construir o inicializar objetos. En C++ la inicialización de
objetos no se puede realizar en el momento en que son declarados; sin embargo,
tiene una característica muy importante y es disponer de una función llamada
constructor que permite inicializar objetos en el momento en que se crean.
Un constructor es una función que sirve para construir un nuevo objeto y
asignar valores a sus miembros dato. Se caracteriza por:
- Tener el mismo
nombre de la clase que inicializa
- Puede definirse inline o fuera de la declaración de la clase
- No devuelve valores
- Puede admitir parámetros como cualquier otra función
- Puede existir más de un constructor, e incluso no existir
Si
no se define ningún constructor de una clase, el compilador generará un
constructor por defecto. El constructor por defecto no tiene argumentos y
simplemente sitúa ceros en cada byte de las variables instancia de un objeto.
Si se definen constructores para una clase, el constructor por defecto no se
genera.
Un constructor del objeto se llama cuando se crea el objeto implícitamente:
nunca se llama explícitamente a las funciones constructoras. Esto significa que
se llama cuando se ejecuta la declaración del objeto. También, para objetos
locales, el constructor se llama cada vez que la declaración del objeto se
encuentra. En objetos globales, el constructor se llama cuando se arranca el
programa.
El constructor por defecto es un constructor que no acepta argumentos. Se llama
cuando se define una instancia pero no se especifica un valor inicial.
Se pueden declarar en una clase constructores múltiples, mientras tomen parte
diferentes tipos o número de argumentos. El compilador es entonces capaz de
determinar automáticamente a qué constructor llamar en cada caso, examinando
los argumentos.
Los argumentos por defecto se pueden especificar en la declaración del
constructor. Los miembros dato se inicializarán a esos valores por defecto, si
ningún otro se especifica.
C++ ofrece un mecanismo alternativo para pasar valores de parámetros a miembros
dato. Este mecanismo consiste en inicializar miembros dato con parámetros.
|
class
prueba |
|
{ |
|
tipo1 d1; |
|
tipo2 d2; |
|
tipo3 d3; |
|
public: |
|
prueba(tipo1 p1, tipo2 p2,
tipo3 p3):d1(p1),d2(p2),d3(p3) |
|
{
} |
|
}; |
Un
constructor que crea un nuevo objeto a partir de uno existente se llama constructor
copiador o de copias. El constructor de copias tiene sólo un argumento: una
referencia constante a un objeto de la misma clase. Un constructor copiador de
una clase complejo es:
|
complejo::complejo(const
complejo &fuente) |
|
{ |
|
real=fuente.real; |
|
imag=fuente.imag; |
|
} |
Si
no se incluye un constructor de copia, el compilador creará un constructor de
copia por defecto. Este sistema funciona de un modo perfectamente satisfactorio
en la mayoría de los casos, aunque en ocasiones puede producir dificultades. El
constructor de copia por defecto inicializa cada elemento de datos del objeto a
la izquierda del operador = al valor del elmento dato equivalente del objeto de
la derecha del operador =. Cuando no hay punteros invicados, eso funciona bien.
Sin embargo, cuando se utilizan punteros, el constructor de copia por defecto
inicializará el valor de un elemento puntero de la izquierda del operador = al
del elemento equivalente de la derecha del operador; es decir que los dos
punteros apuntan en la misma dirección. Si ésta no es la situación que se
desea, hay que escribir un constructor de copia.
5.14
Destructores
Un
destructor es una función miembro con
igual nombre que la clase, pero precedido por el carácter ~.
Una clase sólo tiene una función destructor que, no tiene argumentos y no
devuelve ningún tipo. Un destructor realiza la operación opuesta de un
constructor, limpiando el almacenamiento asignado a los objetos cuando se crean.
C++ permite sólo un destructor por clase. El compilador llama automáticamente
a un destructor del objeto cuando el objeto sale fuera del ámbito. Si un
destructor no se define en una clase, se creará por defecto un destructor que
no hace nada.
Normalmente los destructores se declaran public.
5.15
Creación y
supresión dinámica de objetos
Los
operadores new y delete
se pueden utilizar para crear y destruir objetos de una clase, así como dentro
de funciones constructoras y destructoras.
Un objetro de una determinada clase se crea cuando la ejecución del programa
entra en el ámbito en que está definida y se destruye cuando se llega al final
del ámbito. Esto es válido tanto para objetos globales como locales, ya que
los objetos globales se crean al comenzar el programa y se destruyen al salir de
él. Sin embargo, se puede crear un objeto también mediante el operador new,
que asigna la memoria necesaria para alojar el objeto y devuelve su dirección,
en forma de puntero, al objeto en cuestión.
Los constructores
normalmente implican la aplicación de new.
|
p
=new int(9) //p es un
puntero a int inicializado a 9 |
|
cadena
cad1("hola"); |
|
cadena
*cad2=new cadena; |
Un
objeto creado con new no tiene ámbito, es decir, no se destruye automáticamente
al salir fuera del ámbito, sino que existe hasta que se destruye explícitamente
mediante el operador delete.
|
class
cadena |
|
{ |
|
char *datos; |
|
public: |
|
cadena(int); |
|
~cadena(); |
|
}; |
|
cadena::cadena(int
lon) |
|
{ |
|
datos=new char[lon]; |
|
} |
|
cadena::~cadena() |
|
{ |
|
delete datos; |
|
} |
5.16
Funciones
amigas
Una
función amiga es una función no
miembro de una clase que puede tener acceso a las partes privadas de una clase;
se debe declarar como amiga de la clase mediante la palabra reservada friend.
Las funciones amigas se declaran situando su prototipo de función en la clase
de la que son amiga precediéndola con la palabra reservada friend. Por ejemplo:
|
class
cosa |
|
{ |
|
int datos; |
|
public: |
|
friend void cargar (cosa&
t, int x); |
|
}; |
|
void
cargar(cosa& t, int x) |
|
{ |
|
t.datos=x; |
|
} |
Como
la función cargar se declara amiga de la clase cosa puede acceder al miembro
privado datos.
Las razones fundamentales para utilizar funciones amigas es que algunas
funciones necesitan acceso privilegiado a más de una clase. Una segunda razón
es que las funciones amigas pasan todos sus argumentos a través de la lista de
argumentos y cada valor de argumento se somete a la conversión de asignación.
5.17
Clases amigas
No
sólo puede ser una función, amiga de una clase, también una clase completa
puede ser amiga de otra clase. En este caso todas las funciones de la clase
amiga pueden acceder a las partes privadas de la otra clase.
Una clase amiga puede ser declarada antes de que pueda ser designada como amiga.
|
class
animales; |
|
class
hombre |
|
{ |
|
public: |
|
friend
class animales; |
|
}; |
|
class
animales |
|
{//.. |
|
}; |
Cada
función miembro de animales es amiga de la clase hombre. La designación de un
amigo puede estar en una sección private o public de una clase.
|