Professional Documents
Culture Documents
Los ficheros fuente de C++ tienen la extensión *.cpp (de C plus plus), en lugar de *.c. Esta distinción es
muy importante, pues determina ni más ni menos el que se utilice el compilador de C o el de C++.
Comentarios.
/* Esto es un comentario */
C++ define además otra forma de comentar una línea. Un comentario comienza con //, y continúa
hasta el final de esa línea, donde termina automáticamente:
// Esto es un comentario
El nuevo método impide comentar inadvertidamente varias líneas de código. Esto podía ocurrir en C al
olvidar incluir el final de la notación de comentario. Las dos formas de comentario pueden anidarse en
C++.
Existe todavía otra forma de comentario, y es utilizando el preprocesador, vía #ifdef, #endif. Este es el
mejor método para comentar varias líneas de código, ya que /* y */ no funcionarán si el código
contiene comentarios del mismo tipo:
#if 0
a = b + c;
x = u * v;
#endif
El preprocesador.
#include "horario"
añade un fichero llamado "horario" al programa, en el lugar donde esté esta línea.
La principal utilidad del preprocesador es incluir headers o cabeceras. La idea es que cuando se
quiera llamar a funciones que se construyen a partir del lenguaje (como sacar algo por
pantalla), se utiliza #include para incluir el fichero que define esas funciones.
Las cabeceras más utilizadas son: iostream.h, para imprimir por pantalla y leer desde el teclado,
y math.h, que contiene funciones matemáticas, como raíces cuadradas y logaritmos.
Estos ficheros se llaman cabeceras de sistema, porque no han sido creadas por el usuario, sino
que son parte del compilador. Para incluirlas, se ponen entre corchetes:
#include <iostream.h>
#include <math.h>
Esto indica al preprocesador que queremos incluir cabeceras de sistema, y no otras creadas por
nosotros mismos.
TIPOS EN EL LENGUAJE C
El tipo int
En una variable de este tipo se almacenan números enteros (sin decimales). En memoria ocupa
16 o 32 bits (según sea un ordenador de 16 o 32 bits)
La declaro con:
int numero;
Esto hace que declaremos una variable llamada numero que va a contener un número entero.
x = 10;
int x = 15;
El tipo long
El tipo char
Las variables de tipo char sirven para almacenar caracteres. Los caracteres se almacenan en un
byte como números del 0 al 255. Los 128 primeros (0 a 127) son el ASCII estándar. El resto es
el ASCII extendido y depende del idioma y del ordenador.
char letra;
En una variable char sólo podemos almacenar solo un carácter. Para almacenar un dato en una
variable char tenemos dos posibilidades:
letra = 'A';
o
letra = 65;
En ambos casos se almacena la letra 'A' en la variable (el código ASCII de la letra 'A' es el 65)
Las variables tipo char se pueden usar (y de hecho se usan mucho) para almacenar enteros. Si
necesitamos un número pequeño (entre -127 y 127) podemos usar una variable char (8bits) en
vez de un int.
Una curiosidad:
letra = 'A';
printf( "La letra es: %c y su valor ASCII es: %i\n", letra, letra );
letra = letra + 1;
printf( "Ahora es: %c y su valor ASCII es: %i\n", letra, letra );
En este ejemplo letra comienza con el valor 'A', que es el código ASCII 65. Al sumarle 1 pasa a
tener el valor 66, que equivale a la letra 'B' (código ASCII 66). La salida de este ejemplo sería:
El modificador Unsigned
Este modificador (que significa sin signo) modifica el rango de valores que puede contener una
variable. Sólo admite valores positivos. Si hacemos:
Esta variable en vez de tener un rango de -128 a 128 pasa a tener un rango de 0 a 255.
El tipo float
En este tipo de variable podemos almacenar números decimales, no sólo enteros como en los
anteriores. El rango de posibles valores es del 3,4E-38 al 3,4E38.
float numero;
float num=4060.80;
printf( "El valor de num es : %f", num );
Resultado:
El valor de num es: 4060.80
El tipo double
En las variables tipo double se almacenan números reales del 1,7E-307 al 1,7E308. Se declaran
como double:
double numero;
Operación Acción
x++ Postincremento
++x Preincremento
x-- Postdecremento
--x Predecremento
+x + unario
-x - unario
x*y Multiplicación
x/y División
x%y Módulo
x+y Suma
x-y Resta
VARIABLES Y CONSTANTES EN C
Nombres de las variables
Sólo están permitidas letras de la 'a' a la 'z' (la ñ no vale), números y el símbolo '_', puede
contener números, pero no en el primer carácter.
camiones
numero
buffer
a1
j10hola29
num_alumnos
1abc
nombre?
num/alumnos
Tampoco valen como nombres de variable las palabras reservadas que usa el compilador. Por
ejemplo: for, main, do, while.
Nombre
nombre
NOMBRE
serían tres variables distintas.
Tenemos dos posibilidades, una es declararla como global y otra como local. Es global aquella
variable que se declara fuera de la función main y local la que se declara dentro:
Variable Global Variable Local
#include <stdio.h>
#include <stdio.h>
int x;
int main() {
int x;
int main() {
}
}
Las variables globales se pueden usar en cualquier procedimiento y las locales sólo pueden
usarse en el procedimiento en el que se declaran. Es buena costumbre usar variables locales en
vez de globales.
int x, y;
En C++, las variables pueden ser declaradas en cualquier lugar dentro de un programa. No es
necesario, como en C, que sean declaradas al comienzo de una función o de un bloque. Esto
puede ser útil en códigos grandes, cuando una variable se utiliza en un trozo de código lejano al
comienzo del bloque. En este caso, el declarar el tipo de la variable cerca del lugar donde se va
a utilizar puede hacer que el programa sea más fácil de leer.
Un ejemplo de esta posibilidad de C++ es la declaración del contador dentro de un bucle. Por
ejemplo:
# include
main () {
El índice i se ha definido dentro del bucle for. En algunos compiladores, su validez se extiende hasta el
final del bloque donde han sido definidos
Constantes.
Las constantes se declaran, como en C, igual que una variable normal, pero añadiendo la
palabra const delante. Por ejemplo, para declarar una constante con valor 14:
Estas constantes no pueden ser modificadas a lo largo del programa. Por eso deben ser
definidas al mismo tiempo que declaradas.
Enumeraciones.
C++ permite definir nombres mnemotécnicos para enteros agrupados en conjuntos. Una
enumeración es un conjunto ordenado de nombres:
Color c=verde;
declara la variable c del tipo Color con valor inicial verde. Cada nombre en la enumeración se inicializa
con un número entero, empezando en 0 e incrementándose en 1 al ir de izquierda a derecha. Se pueden
especificar los valores enteros asociados a cada identificador en la enumeración:
Declaraciones typedef.
hace que temperatura sea un sinónimo de float, pero no define un nuevo tipo, y por tanto no afecta a
las reglas de conversión de tipo. y p como variables de distinto tipo, ambas son en realidad de tipo float
.
Conversiones de tipo.
c = (int)a;
En C++ las conversiones de tipo se pueden escribir también como una llamada a función:
c = int(a);
Operación Acción
x++ Postincremento
++x Preincremento
x-- Postdecremento
--x Predecremento
+x + unario
-x - unario
x*y Multiplicación
x/y División
x%y Módulo
x+y Suma
x-y Resta
OPERADORES EN EL LENGUAJE C
Operadores aritméticos.
Operación Acción
x++ Postincremento
++x Preincremento
x-- Postdecremento
--x Predecremento
+x + unario
-x - unario
x*y Multiplicación
x/y División
x%y Módulo
x+y Suma
x-y Resta
Los operadores incremento y decremento proporcionan una forma breve de sumar o restar 1 a
una variable. Usados como prefijo, como ++i, el valor de la variable se incrementa
(decrementa) antes de que la variable sea usada; usada como sufijo, como i++ el valor de la
variable se incrementa (decrementa) después de su utilización.
Operadores relacionales.
Operador Propósito
< Menor que
<= Menor o igual que
> Mayor que
>= Mayor o igual que
== Igual
!= No igual
Operadores lógicos.
Operador Acción
! Negación lógica
<< Y lógico
|| O lógico
Los valores lógicos en C++ están representados por enteros: 0 es falso y un valor no cero es
verdadero. Por ejemplo, el operador ! toma un operando numérico y devuelve int 1 para un
operando cero y int 0 en caso contrario. El operador << devuelve 1 si los dos operandos son
valores no cero, y 0 en cualquier otro caso. El operador || devuelve 1 si cualquiera de los dos
operandos es no cero, y 0 en otro caso. Los operadores << y || evalúan primero el operando de
su izquierda, y no evalúan el operando de la derecha si no es necesario. Por ejemplo, si y es
cero, la expresión y << x/y da 0 y no realiza la división por 0.
Operadores de asignación.
Un operador de asignación altera el valor de un objeto si alterar su tipo. El operador usual de
asignación (=), copia el valor del operando de la derecha en el operando de la izquierda,
aplicando las conversiones de tipo usuales cuando es necesario. En C++ existen además los
siguientes operadores de asignación:
+= -=
*= /=
%= > >=
<<= &=
^= |=
En cada caso, para una variable a de un tipo predefinido en C++, op = expr equivale a a = a
op(expr) . Así, por ejemplo, a+=5 equivale a a = a +5
Precedencia de operadores.
El operador ::
El operador :: (scope) es una característica nueva, puesto que no existe nada similar en C.
Permite el acceso a una variable global aunque exista una variable local con el mismo nombre.
El uso de :: delante del nombre de la variable, indica al compilador que debe utilizar la variable
global, en lugar de la local. Ejemplo:
#include <iostream.h>
main() {
::indice = indice + 7;
Se recomienda no abusar del uso del operador ::. Es mejor utilizar diferentes nombres para las
variables.
ENTRADA/SALIDA EN C
Función printf()
Sirve para imprimir por pantalla. Supongamos que queremos mostrar el contenido de la variable
"x" por pantalla:
printf( "%i", x );
10
10 20
En C++ además de las funciones printf() y scanf(), que siguen estando vigentes, se pueden
utilizar los operadores cin y cout. Para utilizar estos nuevos operadores es necesario incluir la
librería iostream.h con la instrucción #include <iostream.h>. Así en un programa en C habría
que hacer algo de este estilo:
char nombre;
int num=2;
char nombre;
int num=2;
cout << "Introduzca el nombre del fichero " << num << ": ";
Es importante darse cuenta de que ahora ya no hace falta especificar el tipo de dato que va a ser
impreso o leído, asociándolo con un formato determinado. Es el propio programa el que decide el tipo
de dato en tiempo de ejecución gracias a que estos operadores están sobrecargados de tal manera que
admiten tanto los tipos predefinidos como aquellos tipos de datos definidos por el usuario.
Códigos de escape.
Se utilizan para producir un tabulador, retorno de carro, movimiento del cursor hacia atrás,
incluso un pitido. Siempre comienzan por un " \ " seguido de una letra. Algunos son:
\n Newline
\r Retorno de carro.
\t Tabulador horizontal.
\v Tabulador vertical.
\b Espacio hacia atrás
ESTRUCTURAS DE CONTROL EN C
Bloques if
if( expresión ) {
statement ;
...
La expresión debe ir entre paréntesis y dar un valor numérico. Si el valor es no cero, las expresiones
que van entre llaves son ejecutadas.
if( expresión ) {
contenidos bloque 1 ;
Bucles
Bucle while
while( expresión ) {
...
}
El bucle while ejecuta el cuerpo del bucle repetidamente mientras la expresión sea distinta de cero (sea
verdadera). El test se hace antes de ejecutar el cuerpo del bucle, lo que significa que se éste se ejecuta
cero o más veces.
Se debe utilizar un bucle while cuando es posible que el cuerpo del bucle no sea ejecutado. Por
ejemplo, para leer y procesar el contenido de un fichero de tamaño desconocido.
Bucle do-while
do {
...
} while ( expresión );
El cuerpo del bucle se ejecuta repetidamente mientras la expresión es distinta de cero (verdadera). El
test se hace después de ejecutar el cuerpo del bucle, por lo que éste se ejecuta al menos una vez.
Debe utilizarse este tipo de bucles cuando el cuerpo debe ser ejecutado al menos una vez. En
particular, en aquellos casos en que el bucle calcula un valor que es necesario para la condición de
terminación. Por ejemplo, los cálculos iterativos que terminan cuando se da una condición de
convergencia, cuando una expresión calculada dentro del bucle es menor que un determinado valor.
Bucle for
...
break y continue
break termina la ejecución del bucle en que se encuentra. continue hace que el bucle pase directamente
a la siguiente iteración.
ARRAYS Y PUNTEROS EN C
Arrays.
Un array es una colección ordenada de objetos, llamados elementos del array, todos del mismo
tipo. Un array de 10 elementos se declara de la siguiente forma:
float a[100];
Arrays multidimensionales
En este ejemplo, a es una matriz 3x3, y b una matriz 2x3. Los elementos se almacenan por filas,
al contrario de lo que sucedía en FORTRAN. Así, podemos inicializar b de la siguiente forma:
Punteros.
El concepto de puntero está unido a la forma en que los tipos de datos son almacenados en la memoria
de un ordenador, ya que denotan la dirección de una variable determinada. El nombre de la variable
determina el tipo (char, int, float o double) y su dirección determina dónde está almacenada. Conocer la
dirección de una variable es importante porque:
Permite que las funciones cambien el valor de sus argumentos, como veremos en el capítulo
siguiente.
Permite pasar vectores de forma eficiente entre funciones: en lugar de copiar cada elemento del
vector, se copia la dirección del primer elemento.
El nombre de un array es un puntero al array. Por tanto, los punteros y los arrays están
íntimamente ligados en C y en C++.
Terminología básica.
Para entender los punteros y los vectores, es necesario conocer primero cómo se almacenan los
números en la memoria del ordenador. El valor de cada variable en un programa de ordenador se
guarda en una sección de memoria cuyo tamaño está determinado por el tipo de dato de la variable. La
localización de esta sección de memoria es almacenada en la dirección de la variable. Por tanto, es
como si cada variable estuviera compuesta de dos partes: su valor y su dirección. Cada celda de
memoria se puede considerar compuesta de una parte con el contenido, y otra en la que se almacena la
dirección. Esto es análogo a una fila de casas: cada casa tiene diferentes contenidos, y para mirarla
necesitamos conocer su dirección.
#include <iostream.h>
main() {
double d = 2.7183;
cout << "numero = " << d << "\tdirección = " << &d << '\n';
El valor de d es 2.7183. El valor de &d es una dirección (donde 2.7183 está almacenado). La dirección
es imprimida en formato hexadecimal.
Direcciones y punteros
Un puntero guarda la dirección de un objeto en memoria, y como tal un puntero es también una
variable. Puede parecer algo confuso, es como decir que el contenido de una casa es la dirección de
otra vivienda. Las direcciones se guardan como números hexadecimales, por lo que no hay ninguna
razón por la que no podamos definir otro tipo de dato, similar a un entero, pero a través del cual se
puede modificar el valor almacenado en esa dirección. Es importante entender la relación entre
punteros y direcciones:
Cada variable tiene su dirección, que puede ser obtenida mediante el operador unario &.
La dirección de una variable puede ser almacenada en un tipo de dato llamado puntero.
int *puntero1;
#include <iostream.h>
main() {
double d, *dp;
d = 2.7183;
dp = &d;
cout << "numero = " << d << "\tdirección = " << &d << '\n';
}
Operaciones con punteros.
Se pueden comparar punteros, utilizando expresiones lógicas, para ver si están apuntando o no
a la misma dirección de memoria.
La resta de dos punteros da como resultado el número de variables entre las dos direcciones.
Siempre que se realiza una operación aritmética sobre un puntero, sumando o restando un
entero, el puntero se incrementa o decrementa un número apropiado de sitios tal que el nuevo
valor apunta a la variable que está n elementos (no n bytes) antes o después que el dado. De la
misma forma, al restar dos punteros se obtiene el número de objetos entre las dos
localizaciones. Finalmente, dos punteros son iguales si y sólo si apuntan a la misma variable (el
valor de las direcciones es el mismo). No son necesariamente iguales si sus valores indirectos
son los mismos, ya que estas variables podrían estar en diferentes localizaciones de memoria.
El propósito de new es crear arrays cuyo tamaño pueda ser determinado mientras el programa
se ejecuta.
delete funciona igual que free() en C. La memoria a la que apunta el puntero es liberado, pero
no el puntero en sí.
#include <iostream.h>
#include <string.h>
void main() {
char Nombre[50];
Se puede utilizar el operador new para crear variables de cualquier tipo. New devuelve, en todos los
casos, un puntero a la variable creada. También se pueden crear variables de tipos definidos por el
usuario.
struct usuario {
..........
};
usuario* Un_Usuario;
Cuando una variable ya no es necesaria se destruye con el operador delete para poder utilizar
FUNCIONES EN LENGUAJE C
Las funciones se declaran y se definen exactamente igual que en C, y, al igual que en éste, se puede
utilizar prototipo (prototype).
Prototipos
# include <iostream.h>
main() {
int ala = 2;
char ojo = 2;
cout << "Hay " << alas << "alas." << '\n';
cout << "Hay " << pies << "pies. " << '\n';
cout << "Hay " << int(ojos) << "ojos." << '\n';
}
Hay 3 alas.
Hay 12 pies.
Hay 4 ojos.
Hay 2 alas.
Hay 2 ojos.
Nótese que cuando llamamos a la función, la comprobación de tipo la hace el compilador basándose en
el prototipo (en la declaración) puesto que la función todavía no ha sido definida.
Los nombres de variables que aparecen en el prototipo son opcionales y actúan casi como comentarios
al lector del programa, ya que son completamente ignorados por el compilador.
Tipos compatibles
Son compatibles cualquiera de los tipos simples (definidos en C++) que pueden ser convertidos
de uno a otro de manera significativa. Por ejemplo, si llamamos con un entero a una función que
está esperando un número real como parámetro, el sistema lo convertirá automáticamente, sin
mencionarlo al usuario. Esto también es cierto de float a char, o de char a int.
En cambio, si pasamos un puntero a un entero a una función que estaba esperando un entero,
no habrá conversión de tipo, ya que son dos variables completamente distintas. De la misma
forma, un tipo definido por el usuario (estructura o clase) no puede ser convertido
automáticamente a un long float, a un array o incluso a otra estructura o clase diferente, porque
son tipos incompatibles y no puede realizarse la conversión de manera significativa.
Sin embargo, el tipo devuelto por la función, void en el ejemplo anterior, debe ser compatible
con el tipo que se espera que devuelva en la función de llamada, o el compilador dará un
warning.
significa en C que no se ha declarado el tipo de la lista de argumentos que recibe la función, por lo que
el compilador no producirá errores respecto al uso impropio de los argumentos. Cuando en C se declara
una función que no tiene argumentos se utiliza el tipo void:
void func (void);
Cuando se llama a una función, todos los parámetros con los que la llamamos son copiados y
pasados a la función (paso por valor). Esto significa que si la función cambia el valor de los
parámetros, sólo lo hace dentro del ámbito de la función. Por ejemplo:
#include <iostream.h>
a=4;
b=5;
main() {
int a, b;
a=1;
b=2;
change_values(a,b);
La llamada a la función no ha cambiado el valor de las variables que se le han pasado. La función
cambia las copias de lo que se le ha pasado.
Si queremos pasar parámetros por referencia hay que pasar punteros a los datos. Para hacer esto,
utilizamos el operador &, que da la dirección de una variable:
#include <iostream.h>
*a=4;
*b=5;
main() {
int a, b;
a=1;
b=2;
change_values(&a,&b);
A is 4, B is 5
La función main pasa la dirección de a y b, por lo que a la función change_values se le pasa una copia
de las direcciones. Utilizando las direcciones de a y b, la función puede acceder a los datos
directamente.
Polimorfismo.
En C++ es posible declarar dos funciones diferentes que tengan el mismo nombre. Las funciones deben
diferir en la lista de argumentos, bien en el número de variables que se pasan a la función, bien en el
tipo de argumentos que recibe. Así, por ejemplo, se puede definir una función que trabaje, bien con
enteros, bien con strings; sólo hay que definir dos funciones separadas con el mismo nombre:
#include <iostream.h>
main() {
show (42);
show ("A");
show (452.2);
En la primera llamada a la función show, se le pasa un entero, por tanto se llama a la primera copia de
la función show. La segunda vez, el argumento es un carácter, por tanto se utiliza la segunda
definición, aquella que utiliza un carácter. Ahora bien, la tercera llamada utiliza un número real, y no
existe una definición de la función para este caso. El compilador utiliza la primer definición. La salida
del programa es:
Es un entero :42
Es un carácter: A
Es un entero :452
· El uso de más de una función con el mismo nombre pero acciones diferentes debe ser evitado.
En el ejemplo anterior, las funciones show() están relacionadas: imprimen información en la
pantalla.
· C++ no permite que varias funciones difieran sólo en su valor devuelto. Dos funciones de este
tipo no podrían ser distinguidas por el compilador.
Es una forma de indicar qué valor debe ser pasado a una función en el caso en que en la llamada no se
pase nada, o se pasen menos argumentos de los definidos. Un ejemplo de definición de una función que
tiene parámetros por defecto en su lista de argumentos es:
En este caso, estamos definiendo un valor, 2, que tomará la variable y en caso de que no se pase nada
en la llamada a la función:
funcion ();
ESTRUCTURAS EN LENGUAJE C
Las estructuras ya estaban presentes en C. Hay quien las ve como una clase, pero sin métodos
(sólo almacena datos).
Supongamos que queremos hacer una agenda con los números de teléfono de nuestros amigos.
Necesitaríamos un array de Cadenas para almacenar sus nombres, otro para sus apellidos y otro
para sus números de teléfono. Esto puede hacer que el programa quede desordenado y difícil de
seguir. Y aquí es donde vienen en nuestro auxilio las estructuras.
struct nombre_de_la_estructura {
campos de estructura;
};
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
char edad;
};
A cada elemento de esta estructura (nombre, apellido, teléfono) se le llama campo o miembro.
Una vez definida la estructura, podemos usarla declarando una variable con esa estructura:
struct estructura_amigo amigo;
Ahora la variable amigo es de tipo estructura_amigo. Para acceder al nombre de amigo usamos:
amigo.nombre.
Arrays de estructuras
Supongamos ahora que queremos guardar la información de varios amigos. Con una variable de
estructura sólo podemos guardar los datos de uno. Necesitamos declarar arrays de estructuras:
Ahora necesitamos saber cómo acceder a cada elemento del array. La variable definida es amigo, por lo
tanto para acceder al primer elemento usaremos amigo[0] y a su miembro nombre: amigo[0].nombre.
Primero se define la estructura y luego al declarar una variable como estructura le damos el
valor inicial que queramos. Ejemplo:
Punteros a estructuras
Primero hay que definir la estructura igual que antes, pero al declarar la variable de tipo
estructura debemos ponerle el operador '*' para indicarle que es un puntero.
Es importante recordar que un puntero no debe apuntar a un lugar cualquiera, debemos darle
una dirección válida donde apuntar. No podemos por ejemplo crear un puntero a estructura y
meter los datos directamente mediante ese puntero, no sabemos dónde apunta el puntero y los
datos se almacenarían en un lugar cualquiera.
Y para comprender cómo funcionan nada mejor que un ejemplo. Este programa utiliza un
puntero para acceder a la información de la estructura:
#include <stdio.h>
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
struct estructura_amigo amigo = {
"Juanjo",
"Lopez",
"592-0483",
30
};
struct estructura_amigo *p_amigo;
int main()
{
p_amigo = &amigo;
printf( "%s tiene ", p_amigo->apellido );
printf( "%i años ", p_amigo->edad );
printf( "y su teléfono es el %s.\n" , p_amigo->telefono );
}
p_amigo = &amigo;
Para acceder a cada campo de la estructura antes lo hacíamos usando el operador '.', pero,
como muestra el ejemplo, si se trabaja con punteros se debe usar el operador '->'.
Las estructuras se pueden pasar directamente a una función igual que hacíamos con las variables.
Ejemplo:
return arg_amigo.edad+20;
CLASES
Terminología.
· Una clase es un grupo de datos y métodos (funciones). Es sólo un patrón que será usado para
crear una variable que pueda ser manipulada en el programa.
· Un objeto es un ejemplo de una clase, lo que es similar a decir que una variable que hemos
definido como un ejemplo de un tipo. Un objeto es lo que realmente se utiliza en un programa,
ya que tiene valores que pueden ser cambiados.
· Un mensaje es lo mismo que una llamada a una función. En programación orientada a objetos
se envían mensajes en lugar de llamar a funciones. En principio se puede pensar en ambas
cosas como equivalentes
Clases.
El principal objetivo en un lenguaje orientado a objeto es que se pueden crear objetos que
contienen los datos y los métodos para manipularlos. Además, el programador puede decidir si
los datos y los métodos son visibles o no al resto del programa. En C++, estos objetos se
implementan mediante clases:
class Caja {
// Datos y Métodos
};
La declaración de clase está encerrada entre { y }, como cualquier otro bloque en C++. Es importante
el punto y coma que sigue a }. Utilizaremos para los nombres de las clases una letra mayúscula como
primer carácter, y las demás minúsculas.
Veamos una clase muy simple, que contiene sólo datos. Hagámoslos públicos, para que puedan ser
accedidos desde el programa:
# include <iostream.h>
class Caja {
public:
};
main () {
pequeña.longitud = 5;
mediana.longitud = 10;
grande.longitud = 20;
pequeña.anchura = 4;
mediana.anchura = 6;
grande.anchura = 10;
pequeña.altura = 10;
mediana.altura = 20;
grande.altura = 30;
cout << " La longitud de la caja grande es " << grande.longitud <<'\n';
cout << " La anchura de la caja pequeña es " << pequeña.anchura <<'\n';
cout << " La altura de la caja mediana es " << mediana.longitud <<'\n';
Vemos que:
· En primer lugar, definimos una clase llamada Caja, formada por tres variables: longitud,
anchura y altura.
· En la función principal, declaramos tres variables de este tipo (tres objetos), pequeña,
mediana y grande, para lo cual podemos utilizar o no la palabra class antes del nombre de la
clase. Cada una de estas cajas contiene tres números reales, sus dimensiones.
· La palabra public en la definición de la clase es en este caso necesaria porque las variables en
una clase son por defecto privadas, y no se puede acceder a ellas directamente desde el
programa principal. En este caso, las tres variables reales están disponibles desde cualquier
parte del programa principal. Cada variable puede ser inicializada, incrementada, leída,
modificada, o sufrir cualquier operación que definamos sobre ella. Como ejemplo, en el
programa hemos inicializado las dimensiones de cada objeto y a continuación las hemos
imprimido.
· Los datos miembro de una clase son accedidos como los de una estructura de C: el nombre de
la variable estructura/clase seguido por el dato miembro, separados ambos por un "."
clases y encapsulacion en C
Encapsulación.
Los métodos son una interface a través de la cual se manipulan los datos almacenados en una
clase. Usando estos métodos, podemos manipular y extraer datos de un objeto Caja sin saber
qué tipos de datos se usan para almacenar los datos. Esto se conoce como encapsulación de
datos, y es un concepto muy importante en la programación orientada a objetos. Encapsulación
es la habilidad de una parte de un programa para ocultar sus datos al resto del código,
impidiendo así accesos incorrectos o conflictos con los nombres de otras variables.
En el ejemplo anterior, cada clase tenía sólo una sección, etiquetada como public. Pero podemos
utilizar también la etiqueta private. Así, la palabra public hace que los campos (variables o
funciones) que le siguen en la clase puedan ser accedidas desde cualquier parte del programa,
mientras que la palabra private indica que los campos que le siguen sólo son accesibles por el
código que forma parte de la misma clase.
Si no se indica lo contrario, por defecto en una clase todos los elementos son privados. Veamos
un ejemplo:
# include <iostream.h>
class Caja {
public:
};
anchura = dim2;
altura = dim3;
};
};
return longitud;
};
main () {
double longitud;
pequeña.set(5, 4, 10);
pequeña.print();
mediana.print();
grande.print();
longitud = grande.getLongitud();
La ventaja de las funciones miembro es que la función llamada puede automáticamente acceder a los
datos del objeto para el cual fue llamado. Así, en grande.print(), el objeto grande es el 'substrato': las
variables longitud, altura y anchura que son utilizadas en el código de la función se refieren al objeto
grande.
La sección privada de una clase es la parte de los datos que no puede ser accedida desde fuera de la
clase, está escondida para cualquier acceso externo. Las variables longitud, anchura y altura del
ejemplo, que son parte del objeto grande, no están disponibles para su uso en cualquier lugar del
programa principal.
Se introduce la palabra public para indicar que puede accederse a todo lo que le sigue desde fuera de
esta clase. En este caso, hemos declarado tres funciones públicas, que pueden ser llamadas desde la
función main. Por supuesto, estas funciones, llamadas funciones miembro, tienen acceso a la parte
privada de la clase. Por tanto, desde el programa principal sólo se puede acceder a las dimensiones de
una caja llamando a las estas funciones.
Una vez declaradas las funciones dentro de la clase, debemos definirlas para establecer qué acción
realizan. Esto se hace de la forma habitual, salvo que el nombre de la clase se antepone, seguido del
operador ::, al nombre de la función. La definición de una función se denomina la implementación de la
función. Es necesario especificar el nombre de la clase porque se puede utilizar el mismo nombre para
funciones de otras clases, y el compilador debe saber a qué clase corresponde cada implementación.
Los datos privados de la clase son accesibles para las funciones miembro, y en la implementación de
éstas pueden ser modificados y leídos en la forma habitual. Esto se puede hacer con los datos privados
que forman parte de la clase a la que pertenece la función, pero los datos privados de otras clases están
escondidos y no se puede acceder a ellos a través de funciones miembro de esta clase. Esta es la razón
por la que se antepone el nombre de la clase al nombre de la función cuando se define ésta.
En la parte privada de una clase se pueden incluir variables y funciones, y otras variables y funciones
en la parte públicas. En la mayor parte de las situaciones prácticas, las variables se incluyen en la parte
privada, y las funciones sólo en la parte pública de la clase.
En C++ tenemos tres ámbitos de validez para las variables: local, file y clase. Las variables locales
están localizadas en una única función, y las variables de fichero se pueden utilizar en cualquier lugar
del fichero que sigue a su definición. Una variable cuyo ámbito es una clase se puede utilizar en el
ámbito de utilización de la clase y en ningún otro sitio.
Más funciones.
La idea básica de la programación orientada a objetos es definir tipos de dato abstractos, y las
operaciones o métodos que pueden actuar sobre ellos. Añadamos al ejemplo anterior una función que
calcule el volumen de un objeto de la clase Caja:
# include <iostream.h>
class Caja {
public:
};
longitud = dim1;
anchura = dim2;
altura = dim3;
};
};
main () {
pequeña.set(5, 4, 10);
cout << "El volumen de la caja grande es " << grande.volumen() << '\n';
Esto impide que al calcular el volumen de una caja mezclemos las dimensiones de dos objetos distintos.
Cuando creamos un objeto de una clase, siempre seguimos los mismos pasos para llamar a un
conjunto de métodos que inicializen los datos del objeto. En C++, se define una función
especial, el constructor, que es llamada cuando se crea un nuevo objeto de la clase. Este
constructor puede recibir parámetros, como cualquier otra función. Veamos un ejemplo de clase
sin constructor y con constructor:
# include <iostream.h>
class Caja {
public:
};
longitud = dim1;
anchura = dim2;
altura = dim3;
};
};
main () {
cout << "El volumen de la caja grande es " << grande.volumen() << '\n';
· El constructor tiene el mismo nombre que la clase a la que pertenece. Por ejemplo, el
constructor de la clase Caja se llama Caja.
Un constructor es una función, y por tanto podemos aplicarle todo lo que hemos visto que las funciones
en C++ pueden hacer. Por ejemplo, podemos sobrecargarlo:
# include <iostream.h>
class Caja {
public:
Caja (void);
};
longitud = 8;
anchura = 8;
altura = 8;
};
Hemos creado una función constructora que inicializa las dimensiones de la caja a 8. Esta será llamada
cuando creemos un objeto del tipo Caja sin ningún parámetro.
Destructores.
Un destructor tiene el mismo nombre que la clase a la que pertenece, pero precedido con una
tilde (~). Igual que el constructor, un destructor no devuelve nada.
#include <iostream.h>
class Taco {
public:
*dureza = hard;
~Taco() {
delete dureza;
private:
int *dureza;
};
main () {
Taco hard(10);
delete soft;
};
En este ejemplo, vemos que el destructor tiene el mismo nombre que la clase, con un ~ delante. Cuando
se crean punteros a clases, como soft en el ejemplo, se llama al destructor cuando se libera la memoria
del puntero. Si esto no se hace, nunca se llamará al destructor.
Con clases declaradas estáticamente, como Taco hard, el destructor se llama al final de la función
donde se declara el objeto (en el ejemplo, al final de la función main.
Incluso cuando se interrumpe un programa usando una llamada a exit(), se llama a los destructores de
los objetos que existen en ese momento.
Descomposicion en modulos EN C
Vamos a separar nuestro ejemplo en tres ficheros:
Fichero Caja.h:
class Caja {
public:
Caja (void);
~Caja (void);
};
En Caja.h hemos incluido sólo la definición de la clase. No se dan detalles sobre las diversas funciones.
Es decir, tenemos la definición completa de cómo utilizar una clase sin detalles de implementación. Este
fichero no puede ser compilado ni ejecutado.
Fichero Caja.cpp:
# include "Caja.h"
anchura = dim2;
altura = dim3;
};
longitud = 8;
anchura = 8;
altura = 8;
};
longitud = nuevaLongitud;
anchura = nuevaAnchura;
altura = nuevaAltura;
};
return longitud;
};
Caja::~Caja (void) {
Vemos que:
· Se incluye el fichero cabecera Caja.h, que contiene los prototipos de las funciones.
Fichero main.cpp:
#include <iostream.h>
#include "Caja.h"
main () {
cout << "El volumen de la caja grande es " << grande.volumen() << '\n';
ARRAY DE OBJETOS EN C
Un array de objetos.
De la misma forma que declaramos vectores cuyos elementos son los tipos definidos en C++
(int, float, double, ...) podemos definir vectores formados por objetos definidos por el usuario.
Veamos un ejemplo, partiendo de la clase Caja.
# include <iostream.h>
main () {
cout << " El volumen de la caja pequeña es " << pequeña.volumen() <<'\n';
cout << " El volumen de la caja mediana es " << mediana.volumen() <<'\n';
cout << " El volumen de la caja grande es " << grande.volumen() <<'\n';
cout << " El volumen del array de cajas es" << varias[indice].volumen() <<'\n';
Declaramos varias, un array formado por cuatro objetos del tipo Caja. Al hacer esta declaración,
estamos llamando al constructor para cada uno de los cuatro objetos. Para declarar un array de
objetos, debe existir un constructor para ese objeto que no reciba parámetros.
El contador del bucle for, indice, toma 1 como valor inicial, dejando que el primer objeto, varias [0],
tome los valores por defecto (todas las dimensiones iguales a 8). Dentro del bucle, se llama a la función
set para dar valor a las dimensiones de cada objeto. Esta construcción es similar a la de los objetos
normales.
La variable indice se declara en el primer bucle, y está todavía disponible para su uso en el bucle de
impresión, ya que no hemos salido del bloque en el que se declaró, la función main.
CLASES STATIC EN C
Ejemplo:
# include <iostream.h>
class Ejemplo {
int ejemplo1;
public:
Ejemplo (void);
void print(void);
};
ejemplo1 = 1;
ejemplo2 = 1;
}
void Ejemplo :: print (void) {
ejemplo1++;
ejemplo2++;
main() {
primero.print();
segundo.print();
ejemplo1 = 2
ejemplo2 = 2
ejemplo1 = 2
ejemplo2 = 3
Una variable declarada static (ejemplo2) es una variable externa y sólo puede existir una copia de esa
variable. Todos los objetos de esta clase (en este caso, primero y segundo) comparten una misma copia
de esta variable, que es global a estos objetos.
En la definición de clase, la variable sólo es declarada. La declaración dice que la variable existirá y le
da un nombre, pero la definición es la que realmente define un lugar para guardarla en la memoria del
ordenador. Por definición, una variable puede ser declarada en la cabecera del fichero, pero no definida
allí, sino fuera de ella, normalmente en el fichero de implementación.
El constructor inicializa las dos variables internas a 1 cada vez que se crea un objeto. Para mostrar que
ejemplo2 es compartida por todos los objetos de esta clase, definimos una función, print, que
incrementa el valor de las variables internas y a continuación las imprime.
#include <iostream.h>
class Caja {
public:
~Caja();
};
longitud = dim1;
anchura = dim2;
altura = dim3;
*point = valorAlmacenado;
Caja::~Caja(void) { //Destructor
delete point;
main() {
Caja pequeña(5, 4, 10, 1), mediana (10, 6, 20, 2), grande(20, 10, 30, 3);
cout << "El volumen de la caja pequeña es " << pequeña.volumen() << "\n";
cout << "El volumen de la caja mediana es" << mediana.volumen() << "\n";
cout << "El volumen de la caja grande es" << grande.volumen() << "\n";
cout << "El valor almacenado en la caja pequeña es " << pequeña.getValor() << "\n";
cout << "El valor almacenado en la caja mediana es" << mediana.get_valor() << "\n";
cout << "El valor almacenado en la caja grande es " << grande.get_valor() << "\n";
· Declaramos como atributo privado de la clase point, un puntero a un entero. Pero en la declaración no
estamos asociando memoria a este puntero. Es en el constructor donde dinámicamente reservamos
memoria para un entero, utilizando el operador new.
· Declaramos tres objetos, pequeña, mediana, y grande, del tipo Caja. Cada uno de ellos contiene un
puntero que apunta a tres localizaciones de memoria diferentes. Cada objeto tiene su propia variable
dinámicamente reservada para su uso privado. Además, en esta variable dinámicamente reservada se
almacena el valor entero pasado al constructor.
· En un programa pequeño como éste, no se agotará la memoria, por tanto no es necesario comprobar
que existe memoria disponible. En programas largos, sería conveniente comprobar que el valor del
puntero devuelto no es NULL, para asegurar que los datos están realmente reservados.
· Hemos definido un destructor que borra con delete la memoria reservada dinámicamente con new. El
destructor es llamado cuando los objetos que hemos definido (pequeña, mediana y grande) abandonan
el bloque en el que han sido definidos. Si no hubiésemos definido el destructor, al salir de la función,
quedarían en la memoria las tres variables reservadas dinámicamente sin nada apuntando a ellas. Por
esta razón, el destructor se utiliza para borrar la variable a la que el puntero apunta cuando cada objeto
sale de su ámbito de definición.
· En este caso particular, las variables serían automáticamente liberadas al volver al sistema operativo.
Pero si se tratara de una función que llama a otra función, estaríamos llenando la memoria.
· Recordemos que si hubiésemos reservado memoria para más de un entero, por ejemplo:
delete [] p;
· Mencionemos de nuevo que las funciones declaradas inline deben ser utilizadas cuando la rapidez es
lo más importante en el programa, ya que el código de la función se reescribe en el lugar en que se
utiliza, y no se produce una llamada a una función definida de forma independiente. Definimos entonces
el código como parte de la declaración de la clase, no en la implementación de las funciones. Si el
código de la función es demasiado largo, el compilador puede ignorar el requisito de inline y tratarla
como un método implementado independientemente, pero lo hará de forma invisible al usuario. Las
funciones inline violan el principio de protección de la información, ya que el se hace el código de la
función visible al usuario.
PUNTEROS A CLASES EN C
Punteros a clases.
Como cualquier otro tipo de dato, podemos tener punteros a clases, punteros a punteros a
clases, punteros a punteros a punteros a clases, etc. Veamos un ejemplo:
# include <iostream.h>
# include "Caja.h"
main() {
Caja *punteroACaja1;
Caja *punteorACaja2;
cout << "El volumen de la caja grande es " << grande.volumen() << '\n';
cout << "El nuevo volumen 2 es " << punteroACaja2 -> volumen() << '\n';
cout << "El nuevo volumen 1 es " << punteroACaja1 -> volumen() << '\n';
El nuevo volumen 2 es 6
El nuevo volumen 1 es 48
En este ejemplo hemos declarado dos punteros, punteroACaja1 y punteroACaja2, a objetos del tipo
Caja. Hemos reservado memoria para la clase utilizando new, y al final de la función la hemos liberado
utilizando delete.
El acceso a los componentes del objeto se hace a través del operador ->
(*punteroACaja1).volumen()
Si la clase tuviese un destructor, éste sería llamado automáticamente cuando liberamos la memoria
reservada dinámicamente para punteroACaja1 y punteroACaja2.
OBJETOS ENCADENADOS EN C
Un objeto con un puntero a otro objeto: Objetos encadenados.
Añadamos a la clase Caja como atributo privado un puntero a otro objeto de la misma clase:
#include <iostream.h>
class Caja {
Caja *otraCaja;
public:
double volumen(void);
Caja *get_next(void);
};
longitud = dim1;
anchura = dim2;
altura = dim3;
otraCaja = NULL;
double Caja::volumen(void) {
otraCaja = where_to_point;
Caja *Caja::get_next(void) { // Este método devuelve la caja a la que apunta la caja actual
return otraCaja;
}
main() {
Caja *CajaPointer;
cout << "El volumen de la Caja pequeña es " << pequeña.volumen() << "\n";
cout << "El volumen de la Caja mediana es " << mediana.volumen() << "\n";
cout << "El volumen de la Caja grande es" << grande.volumen() << "\n";
pequeña.point_at_next(&mediana);
mediana.point_at_next(&grande);
CajaPointer = &pequeña;
CajaPointer = CajaPointer->get_next();
cout << "La Caja apuntada tiene volumen = " << CajaPointer->volumen() << "\n";
En este programa:
· Hemos definido, en la parte privada de la clase Caja, un puntero a un objeto de la misma Clase.
Esta es la estructura utilizada para la construcción de listas encadenadas.
· El constructor asigna el valor NULL al puntero. Esta es una buena idea, inicializar siempre los
punteros. Haciendo esta asignación en el constructor, se garantiza que cada objeto de esta
clase tendrá automáticamente su puntero inicializado.
· Hemos añadido dos funciones: point_at_next y get_next. Esta última devuelve un puntero a un
objeto de la clase Caja.
· Hacemos que el puntero embebido en la caja pequeña apunte a la caja mediana, y que el
puntero embebido en la caja mediana apunte a la caja grande. Hemos generado una lista
encadenada con tres elementos.
En C++ se define this dentro de un objeto como un puntero al objeto en que está contenido. Se
declara implícitamente como:
class_name *this;
y se inicializa para apuntar al objeto para el cual se llama a la función miembro. Este puntero es
muy útil cuando se trabaja con punteros y especialmente en listas encadenadas cuando se
necesita referenciar un puntero al objeto que se está insertando en la lista. La palabra this está
disponible para este propósito y puede ser utilizada en cualquier objeto. Realmente la forma
apropiada de referenciar a cualquier variable en una lista es a través del uso del puntero
predefinido this, escribiendo this -> nombre_variable, pero el compilador supone que se está
usando, y podemos omitir el puntero.
Funciones amigas.
Una función fuera de una clase puede definirse como función amiga por la clase que le da libre
acceso a los miembros privados de la clase. Hay casos en que esto ayuda a hacer más legible un
programa, y permite el acceso controlado a los datos.
Una función aislada puede ser declarada como amiga, así como miembro de otras clases, e
incluso se le puede dar el status de amiga a clases enteras, si es necesario.
SOBRECARGA DE OPERADORES EN C
Sobrecarga de operadores.
La sobrecarga de operadores permite redefinir ciertos operadores, como "+" y "-", para usarlos
con las clases que hemos definido. Se llama sobrecarga de operadores porque estamos
reutilizando el mismo operador con un número de usos diferentes, y el compilador decide cómo
usar ese operador dependiendo sobre qué opera.
La sobrecarga de operadores sólo se puede utilizar con clases, no se pueden redefinir los
operadores para los tipos simples predefinidos.
Los operadores lógicos && y || pueden ser sobrecargados para las clases definidas por el
programador, pero no funcionarán como operadores de short circuit. Todos los miembros de la
construcción lógica serán evaluados sin ning´n problema en lo que se refiere a la salida.
Naturalmente los operadores lógicos predefinidos continuarán siendo operadores de short
circuit como era de esperar, pero no los sobrecargados.
o El operador ternario ?.
o El operador de acceso a una clase o estructura : .
o El operador scope ::
Ejemplo donde sobrecargamos el operador + para, a partir de dos cajas, crear otra de
dimensiones igual a la suma de las dimensiones de las cajas dadas:
#include <iostream.h>
class Caja {
double longitud;
public:
};
Caja operator+(Caja a) {
Caja temp;
return temp;
main() {
Caja temp;
pequeña.set(2, 4, 5);
mediana.set(5, 6, 8);
cout << "El nuevo volumen es " << temp.volumen() << "\n";
}
El resultado de la ejecución será:
El volumen es 40.
El volumen es 240.
El volumen es 960.
El volumen es 840.
Observamos que :
· El operador se llama desde la clase que precede al operador, y el objeto que le sigue es
enviado como parámetro. Esto significa que el parámetro (a) es mediana.
· El operador puede acceder a los miembros privados del parámetro que es enviado. En el
ejemplo, accede a a.longitud, a.anchura, a.altura.
Operadores amigos
#include <iostream.h>
class Caja {
double longitud;
public:
};
Caja temp;
return temp;
}
Caja operator+(int a, Caja b) { // Add a constant to a Caja
Caja temp;
temp.longitud = a + b.longitud;
temp.anchura = a + b.anchura;
temp.altura = a + b.altura;
return temp;
Caja temp;
temp.longitud = a * b.longitud;
temp.anchura = a * b.anchura;
temp.altura = a * b.altura;
return temp;
main() {
Caja temp;
pequeña.set(2, 4, 5);
mediana.set(5, 6, 8);
cout << "El nuevo volumen es " << temp.volumen() << "\n";
temp = 10 + pequeña;
cout << "El nuevo volumen es " << temp.volumen() << "\n";
temp = 4 * grande;
cout << "El nuevo volumen es " << temp.volumen() << "\n";
El volumen es 40
El volumen es 240.
El volumen es 960.
· Hemos sobrecargado los operadores + y *, declarándolos como funciones amigas, de forma que
podemos utilizar funciones con dos parámetros. Si no los hubiésemos utilizado la construcción friend, la
función sería parte de uno de los objetos y ese objeto sería el objeto al que se le pasa el mensaje.
· No hay límite superior para el número de operadores o de funciones sobrecargadas. Se puede definir
cualquier número de operadores sobrecargados, siempre que difieran en la lista de argumentos.
· Se observa que la implementación de las funciones amigas no es realmente parte de la clase porque el
nombre de la clase no precede al de la función.
Un operador unario sólo tiene un operando. Ejemplos de operadores unitarios son ++ y --.
#include <iostream.h>
class Burrito {
private:
public:
amtbeef = beef;
amtbean = bean;
amtbeef++; amtbean++;
};
main() {
Burrito b1(5,10);
cout << "Burrito #1 has" << b1.getBeef() << "ounces of beef." << '\n';
b1++;
cout << "Now Burrito #1 has" << b1.getBeef() << "ounces of beef."<< '\n';
Vemos que sobrecargar un operador unario es muy similar a la forma en que se hace para uno binario.
De hecho, la única diferencia real es que ahora no se pasa ningún parámetro al operador, ya que ahora
sólo hay un operando, que es el objeto cuyo operador se usa.
Hay si embargo una pequeña cuestión que debe tenerse en cuenta cuando se sobrecargan los
operadores ++ y --. Sabemos que b = ++a; es diferente a b=a++;. La primera expresión equivale a a =
a +1; b = a;, mientras que la segunda es : b = a; a = a +1;. Sin embargo, cuando se sobrecarga el
operador ++ (o el --) no se puede hacer distinción entre estas dos situaciones. Los dos usos del
operador tienen el mismo efecto.