You are on page 1of 45

ALGUNOS CONCEPTOS

Extensión de los ficheros

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.

Los comentarios de C siguen siendo válidos en C++. Por ejemplo:

/* 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.

El preprocesador es una forma de indicarle al compilador de C++ cómo ha de manipular el


programa antes de compilarlo. Por ejemplo, podemos querer dividir un programa muy grande
en dos ficheros. Entonces, habría que decirle al preprocesador que añada un fichero al otro
antes de compilar. El compilador las verá entonces como un único fichero, aunque en realidad
son dos. La forma de comunicarse con el preprocesador es usando # como primer carácter de
una línea, seguido por el comando adecuado. Por ejemplo, include añade otro fichero en ese
punto:

#include "horario"

añade un fichero llamado "horario" al programa, en el lugar donde esté esta línea.

Cabeceras (header files).

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.

La asignación de valor es tan sencilla como:

x = 10;

También se puede dar un valor inicial a la variable cuando se define:

int x = 15;

También se pueden inicializar varias variables en una sola línea:

int x = 15, y = 20;

El tipo long

Es igual que int, pero con longitud de 32 bits

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.

Para declarar una variable de tipo char hacemos:

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:

La letra es A y su valor ASCII es 65


Ahora es B y su valor ASCII es 66

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:

unsigned char variable;

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.

Declaración de una variable de tipo float:

float numero;

Para imprimir valores tipo float Usamos %f.

float num=4060.80;
printf( "El valor de num es : %f", num );
Resultado:
El valor de num es: 4060.80

Si queremos escribirlo en notación exponencial usamos %e:

float num = 4060.80;


printf( "El valor de num es: %e", num );
Que da como resultado:
El valor de num es: 4.06080e003

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;

Para imprimir se usan los mismos modificadores que en float.

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.

Ejemplos de nombres válidos:

camiones
numero
buffer
a1
j10hola29
num_alumnos

Ejemplos de nombres no válidos:

1abc
nombre?
num/alumnos

Tampoco valen como nombres de variable las palabras reservadas que usa el compilador. Por
ejemplo: for, main, do, while.

C distingue entre mayúsculas y minúsculas. Por lo tanto:

Nombre
nombre
NOMBRE
serían tres variables distintas.

¿Dónde se declaran las variables?

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.

Podemos declarar más de una variable en una sola línea:

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 () {

for (int i=0; i < 10; i++) {

cout << "hola" << '\n';

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:

const int numero = 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:

enum Color {rojo, verde, azul, negro};


Esta declaración define un nuevo tipo, Color, y cuatro variables inalterables de ese tipo. Por ejemplo,

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:

enum Poligono {triángulo = 3, cuadrado = 4, pentágono =5};

Declaraciones typedef.

La declaración typedef da un nombre adicional a un tipo ya existente. Por ejemplo:

typedef float temperatura;

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.

Conversiones aritméticas usuales


Si los operandos de un operador aritmético binario son del mismo tipo, ese tipo es el tipo del
resultado. Pero si no es así uno de los operandos es convertido al tipo del otro, y el resultado de
la operación tiene ese tipo común. El tipo común es el tipo del operando que aparece primero en
la lista long double, double, float, long int, int. Losoperandos de tipo char y short son tratados
como si fueran de tipo int

Conversiones forzadas por el programador


Las conversiones de tipo se hacen en C++ igual que en C:

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.

Los operadores aritméticos que se pueden utilizar en C++ son:

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.

Los operadores relacionales comparan sus operandos y devuelven el valor 1 si la relación es


cierta, y 0 si no lo es. Son:

Operador Propósito
< Menor que
<= Menor o igual que
> Mayor que
>= Mayor o igual que
== Igual
!= No igual

Operadores lógicos.

Los operadores lógicos que se pueden utilizar en C++ son:

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.

La interpretación de cualquier expresión en C++ está determinada por la precedencia y


asociatividad de los operadores en dicha expresión. Cada operador tiene una precedencia, y los
operadores en una expresión se evalúan en orden de mayor a menor precedencia. La evaluación
de operadores con la misma precedencia viene determinada por su asociatividad. Y, al igual que
en matemáticas, los paréntesis anulan las reglas de precedencia.

En la siguiente tabla se listan los operadores en C++, su precedencia y su asociatividad. Los


operadores se listan en orden de prioridad decreciente (los situados más arriba tienen mayor
prioridad). Los operadores en la misma línea horizontal tienen la misma precedencia.

Operador Propósito Asociatividad


:: Scope (unario) De derecha a izquierda
:: Scope (binario) De izquierda a derecha
-> . Selección de miembros De izquierda a derecha
[] Índices De izquierda a derecha
() Llamada a función De izquierda a derecha
++ Postincremento De izquierda a derecha
-- Postdecremento De izquierda a derecha
sizeof Tamaño de un objeto De derecha a izquierda
++ Preincremento De derecha a izquierda
-- Predecremento De derecha a izquierda
*&+-!~ Operadores unarios De derecha a izquierda
new Crea un objeto De derecha a izquierda
delete Borra un objeto De derecha a izquierda
() Conversión de tipo (type cast) De derecha a izquierda
->* .* Puntero a un miembro De izquierda a derecha
*/% Operadores multiplicativos De izquierda a derecha
+- Operadores aditivos De izquierda a derecha
<< >> Operadores bitwise De izquierda a derecha
< > <= >= Operadores de relación De izquierda a derecha
== != Operadores de igualdad De izquierda a derecha
& Y bitwise De izquierda a derecha
^ bitwise O exclusivo De izquierda a derecha
| bitwise O inclusivo De izquierda a derecha
&& Y lógico De izquierda a derecha
|| O lógico De izquierda a derecha
?: Operador condicional De derecha a izquierda
= *= /= += -=
>*gt;= Operadores de asignación De derecha a izquierda
&= ^= |= %= <<=
, Operador coma De derecha a izquierda

En C++ las reglas de precedencia y asociatividad de operadores corresponden a las reglas


matemáticas. Sin embargo, esto no funcionará, por ejemplo, con los operadores bitwise. Para
tales operadores, se debe utilizar la tabla anterior, o bien paréntesis.

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>

int indice = 13;

main() {

float indice = 3.2567;

cout <<"El valor de la variable local es:"<< indice << '\n';

cout <<"El valor de la variable global es:"<< ::indice << '\n';

::indice = indice + 7;

cout <<"El valor de la variable local es:"<< indice << '\n';

cout <<"El valor de la variable global es:"<< ::indice << '\n';

La salida de este programa es:

El valor de la variable local es:3.2567

El valor de la variable global es: 13

El valor de la variable local es:3.2567

El valor de la variable global es: 10

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 );

Suponiendo que x valga 10 (x=10) en la pantalla tendríamos:

10

Para ver el contenido de dos variables, por ejemplo x e y, podemos hacer:

printf( "%i %i", x, y );

resultado (suponiendo x=10, y=20):

10 20

También podemos mezclar texto con enteros:

printf( "El valor de x es %i, ¡que bien!\n", x );

que quedará como:

El valor de x es 10, ¡que bien!

Como vemos %i al imprimir se sustituye por el valor de la variable.

Salida por pantalla y entrada por teclado.

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;

printf ("Introduzca el nombre del fichero %d: ", num);

scanf (" %s", nombre)

En C++ podría escribirse así:

char nombre;

int num=2;

cout << "Introduzca el nombre del fichero " << num << ": ";

cin >> nombre;

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

La sintaxis general de un bloque if es:

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.

También se puede utilizar un bloque if-else:

if( expresión ) {

contenidos bloque 1 ;

else { contenidos bloque 2 ;

Bucles

En C++ hay tres clases de bucles:

Bucle while

while( expresión ) {

statement ; // cuerpo del bucle

...

}
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 {

statement ; // cuerpo del bucle do-while

...

} 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

for ( init-statement; expresión de continuación; expresión de incremento ) {

statement ; // cuerpo del 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.

Ambos comandos deben utilizarse lo menos posible

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];

El primer elemento de este array es a[0], y el último a[9].

Un array se puede inicializar así:

float a[3] = {10.1,10.2,10.3};


Si no se indica el tamaño del array, éste será igual al número de elementos que se indiquen:

float x[] = {1.3, 2.4};

crea un vector x de tamaño 2.

Arrays multidimensionales

Los arrays multidimensionales se declaran:

int a[3][3], b[2][3];

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:

int b[2][3] = { {1,2,3}, {4,5,6} };

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.

Permite reservar memoria en tiempo de ejecución en lugar de en tiempo de compilación, lo que


significa que el tamaño de un vector puede ser determinado por el usuario en lugar de por el
programador.

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.

Las direcciones se representan normalmente por un número hexadecimal, pudiendo contener un


carácter, un entero o un real (aunque en realidad todos son almacenados como números binarios).

Para obtener la dirección de una variable se utiliza el operador 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.

Un puntero en C o en C++ se declara anteponiendo un * al nombre de la variable, que es el operador


inverso a &. El puntero apunta entonces a una variable del tipo especificado, y no debe ser usado con
variables de otros tipos. Un experto en C podría forzar la utilización de un puntero con un tipo distinto
del que se ha declarado, pero no es recomendable, ya que podría conducir a un uso erróneo.

Ejemplos de declaración de punteros:

int *puntero1;

int *puntero2, *puntero3;

char variable, *punteroCaracter;

float *punteroReal, real;

En la primera línea, declaramos un puntero a un entero. En la segunda, dos punteros a entero. En la


tercera, un carácter ( variable) y un puntero a carácter (punteroCaracter). Por último, punteroReal es
un puntero a un real, y real es declarado como un número real.

Ejemplo de uso del operador * y del operador de dirección (&):

#include <iostream.h>

main() {

double d, *dp;

d = 2.7183;

dp = &d;

cout << "numero = " << d << "\tdirección = " << &d << '\n';

}
Operaciones con punteros.

Un puntero es un tipo de dato similar a un entero, y hay un conjunto de operaciones definidas


para punteros:

La suma o resta de un entero produce una nueva localización de memoria.

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.

La siguiente tabla resume los operadores que manipulan punteros:

RESERVA DINAMICA DE MEMORIA EN C


Los operadores new y delete se utilizan para reservar y liberar memoria dinámicamente. new y
delete son parte del lenguaje C++ y no parte de una librería como sucedía con las funciones
equivalentes malloc() y free() de C. Ahora los operadores new y delete.

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í.

A continuación se presenta a modo de ejemplo un programa que reserva memoria de modo

dinámico para un vector de caracteres:

#include <iostream.h>

#include <string.h>

void main() {

char Nombre[50];

cout << "Introduzca su Nombre:";

cin >> Nombre;

char *CopiaNombre = new char[strlen(Nombre)+1];

strcpy(CopiaNombre, Nombre); //copio Nombre en la variable CopiaNombre

cout << CopiaNombre;

delete [] CopiaNombre; //libero memoria


}

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;

Un_Usuario = new usuario;

Cuando una variable ya no es necesaria se destruye con el operador delete para poder utilizar

la memoria que estaba ocupando, mediante una instrucción del tipo:

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

La declaración de una función es el prototipo. El prototipo da un modelo de la interface a la


función. Veamos un ejemplo:

# include <iostream.h>

void haz_algo (int alas, float pies, char ojos);

main() {

int ala = 2;

float pie = 1000.0;

char ojo = 2;

haz_algo (3, 12.0, 4);

haz_algo (ala, pie, ojo);

void haz_algo (int alas, float pies, char ojos) {

cout << "Hay " << alas << "alas." << '\n';

cout << "Hay " << pies << "pies. " << '\n';

cout << "Hay " << int(ojos) << "ojos." << '\n';
}

La salida de este programa será:

Hay 3 alas.

Hay 12 pies.

Hay 4 ojos.

Hay 2 alas.

Hay 1000 pies.

Hay 2 ojos.

Cada llamada a la función haz_algo() debe verificar:

El número de parámetros debe ser exactamente tres.

Los tipos deben ser compatibles con los de la declaración.

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.

El uso de prototipos no supone coste alguno en tiempo ni en velocidad de ejecución. El prototipo se


verifica durante la compilación.

Funciones con void como argumento.

Una función sin lista de argumentos como

void func ();

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);

En C++, ambas expresiones son equivalentes

Pasar punteros a funciones.(paso por valor y por referencia)

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>

void change_values(int a,int b) {

a=4;

b=5;

main() {

int a, b;

a=1;

b=2;

change_values(a,b);

cout << "A is " << a <<",B is" << b <<'\n';

La salida de programa es: A is 1, B is 2

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>

void change_values(int *a,int *b) {

*a=4;

*b=5;

main() {

int a, b;
a=1;

b=2;

change_values(&a,&b);

cout << "A is " << a <<",B is" << b <<'\n';

Ahora la salida del programa es:

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>

void show(int val) {

cout <<" Es un entero :"<< val << '\n';

void show(char *val) {

cout <<"Es un carácter: "<< val << '\n';

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

Comentarios sobre la sobrecarga de funciones:

· 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.

Parámetros por defecto

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:

void funcion (int y = 2)

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.

Para definir una estructura usamos el siguiente formato:

struct nombre_de_la_estructura {
campos de estructura;
};

NOTA: Es importante no olvidar el ';' del final.

Vamos a crear una declaración de estructura llamada amigo:

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:

struct estructura_amigo amigo[10];

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.

Inicializar una estructura

Primero se define la estructura y luego al declarar una variable como estructura le damos el
valor inicial que queramos. Ejemplo:

struct estructura_amigo amigo = {


"Juanjo",
"Lopez",
"592-0483",
30
};

Por supuesto hemos de meter en cada campo el tipo de datos correcto.

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 es un puntero a la estructura estructura_amigo. Dado que es un puntero tenemos que


indicarle dónde debe apuntar, en este caso vamos a hacer que apunte a la variable amigo:

p_amigo = &amigo;

El operador & que significa 'dame la dirección donde está almacenado...'.

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 '->'.

Punteros a arrays de estructuras

Por supuesto también podemos usar punteros con arrays de estructuras.

Paso de estructuras a funciones

Las estructuras se pueden pasar directamente a una función igual que hacíamos con las variables.
Ejemplo:

int suma( struct estructura_amigo arg_amigo ) {

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 método es una función contenida en una clase.

· 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:

double longitud, anchura, altura;

};

main () {

Caja pequeña, mediana;

class Caja grande;

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 {

double longitud, anchura, altura;

public:

void set (double dim1, double dim2, double dim3);

void print (void);

double getLongitud (void);

};

void Caja :: set (double dim1, double dim2, double dim3) {


longitud = dim1;

anchura = dim2;

altura = dim3;

};

void Caja :: print (void) {

cout << "longitud = " << longitud <<'\n';

cout << "anchura = " << anchura <<'\n';

cout << "altura = " << altura <<'\n';

};

double Caja:: getLongitud (void) {

return longitud;

};

main () {

double longitud;

Caja pequeña, mediana, grande;

pequeña.set(5, 4, 10);

mediana.set (10, 6, 20);

grande.set (20, 10, 30);

pequeña.print();

mediana.print();

grande.print();

longitud = grande.getLongitud();

cout << "longitud de la caja grande = " << longitud <<'\n';

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 {

double longitud, anchura, altura;

public:

void set (double dim1, double dim2, double dim3);

double volumen (void);

};

void Caja :: set (double dim1, double dim2, double dim3) {

longitud = dim1;

anchura = dim2;

altura = dim3;
};

double Caja:: volumen (void) {

return longitud * anchura * altura;

};

main () {

double longitud; Caja pequeña, mediana, grande;

pequeña.set(5, 4, 10);

mediana.set (10, 6, 20);

grande.set (20, 10, 30);

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.

CLASES CONSTRUCTORES Y DESCTRUCTORES EN C


Constructores.

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 {

double longitud, anchura, altura;

public:

Caja (double dim1, double dim2, double dim3);

double volumen (void);

};

Caja :: Caja (double dim1, double dim2, double dim3) {

longitud = dim1;

anchura = dim2;

altura = dim3;
};

double Caja:: volumen (void) {

return longitud * anchura * altura;

};

main () {

Caja pequeña(5, 4, 10), mediana (10, 6, 20), grande(20, 10, 30);

cout << "El volumen de la caja grande es " << grande.volumen() << '\n';

En este sencillo ejemplo vemos las características más importantes de un constructor:

· El constructor tiene el mismo nombre que la clase a la que pertenece. Por ejemplo, el
constructor de la clase Caja se llama Caja.

· El constructor no devuelve nada (ni siquiera void).

· Los argumentos se pasan al constructor en la declaración de un objeto. Así, Caja pequeña(5, 4,


10) crea un nuevo objeto de la clase Caja y llama al constructor con los parámetros 5, 4 y 10.

Cuando se crea un objeto de una clase, se llama automáticamente al constructor. Si tuviésemos un


puntero a una clase, su constructor no sería llamado a menos que reserváramos memoria para lo que
apunta el puntero, utilizando new.

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 {

double longitud, anchura, altura;

public:

Caja (double dim1, double dim2, double dim3);

Caja (void);

double volumen (void);

};

Caja :: 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.

El destructor es muy similar al constructor, excepto que es llamado automáticamente cuando


cada objeto sale de su ámbito de validez. Recordemos que las variables automáticas tienen un
tiempo de vida limitado, ya que dejan de existir cuando se sale del bloque en que han sido
declaradas. Cuando un objeto es liberado automáticamente, su destructor, si existe, es llamado
automáticamente.

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.

Si algún bloque de memoria fue reservado dinámicamente en un objeto, se puede utilizar el


destructor para liberarlo antes de que se pierdan los punteros a esas variables.

#include <iostream.h>

class Taco {

public:

Taco (int hard) {

dureza = new int;

*dureza = hard;

~Taco() {

cout << "Destruyendo taco con dureza " ;

cout << *dureza <<;\n';

delete dureza;

private:

int *dureza;

};

main () {

Taco hard(10);

Taco *soft = new Taco (0);

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:

· Caja.h, que contiene la definición de la clase

· Caja.cxx, que contiene la implementación de los métodos declarados en la clase Caja

· main.cxx, la función principal.

Fichero Caja.h:

class Caja {

double longitud, anchura, altura;

public:

Caja (double dim1, double dim2, double dim3);

Caja (void);

void set (double nuevaLongitud, double nuevaAnchura, double nuevaAltura);

void print (void);

double getLongitud (void);

double volumen (void) {return longitud * anchura * altura};

~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"

Caja :: Caja (double dim1, double dim2, double dim3) {


longitud = dim1;

anchura = dim2;

altura = dim3;

};

Caja :: Caja (void) {

longitud = 8;

anchura = 8;

altura = 8;

};

void Caja :: set (double nuevaLongitud,nuevaLongitud, double nuevaAnchura, double


nuevaAltura) {

longitud = nuevaLongitud;

anchura = nuevaAnchura;

altura = nuevaAltura;

void Caja :: print (void) {

cout << "longitud = " << longitud <<'\n';

cout << "anchura = " << anchura <<'\n';

cout << "altura = " << altura <<'\n';

};

double Caja:: getLongitud (void) {

return longitud;

};

Caja::~Caja (void) {

longitud = 0; anchura = 0; altura = 0;

Vemos que:

· Se incluye el fichero cabecera Caja.h, que contiene los prototipos de las funciones.

· Caja.cpp contiene la implementación de las funciones declaradas en la clase Caja.


Este fichero puede ser compilado, pero no ejecutado, porque no tiene función main. Cuando sea
compilado, el código objeto será almacenado, y estará disponible para usarlo por otros programas.

La separación de la definición y la implementación es un paso importante hacia la ingeniería de


software. El fichero que contiene la definición es todo lo que el usuario necesita para utilizar la clase en
un programa. No necesita conocer la implementación real de las funciones.

Fichero main.cpp:

#include <iostream.h>

#include "Caja.h"

main () {

Caja pequeña(5, 4, 10), mediana (10, 6, 20), grande(20, 10, 30);

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>

# include " Caja.h"

main () {

Caja pequeña(5, 4, 10), mediana(10, 6, 20), grande, varias [4];

grande.set (20, 10, 30);

for (int indice = 1; indice < 4; indice ++)

varias [indice].set(indice + 10, 10, 10);

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';

for (indice = 0; indice < 4; indice ++)

cout << " El volumen del array de cajas es" << varias[indice].volumen() <<'\n';

El resultado de la ejecución de este programa será:


El volumen de la caja pequeña es 200

El volumen de la caja mediana es 1200

El volumen de la caja grande es 6000

El volumen del array de cajas es 512

El volumen del array de cajas es 1100

El volumen del array de cajas es 1200

El volumen del array de cajas es 1300

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;

static int ejemplo2;

public:

Ejemplo (void);

void print(void);

};

int Ejemplo :: ejemplo2;

Ejemplo :: Ejemplo (void) {

ejemplo1 = 1;

ejemplo2 = 1;

}
void Ejemplo :: print (void) {

ejemplo1++;

ejemplo2++;

cout << "ejemplo1 = " << ejemplo1 << '\n';

cout << "ejemplo2 = " << ejemplo2 << '\n'; ;

main() {

Ejemplo primero, segundo;

primero.print();

segundo.print();

La salida de este programa es:

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.

Un objeto con un puntero interno


Modifiquemos la clase Caja para que incluya un puntero a una variable entera como atributo
privado:

#include <iostream.h>

class Caja {

double longitud, anchura, altura;


int *point;

public:

Caja(double dim1, double dim2, double dim3, int valorAlmacenado);

~Caja();

double volumen(void) {return longitud * anchura * altura;} // Inline

int getValor(void) {return *point;} // Inline

};

//implementación del constructor

Caja::Caja(double dim1, double dim2, double dim3, int valorAlmacenado) {

longitud = dim1;

anchura = dim2;

altura = dim3;

point = new int;

*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";

El resultado de la ejecución será:


El volumen de la caja pequeña es 200

El volumen de la caja mediana es 1200

El volumen de la caja grande es 6000

El valor almacenado en la caja pequeña es 1

El valor almacenado en la caja mediana es 2

El valor almacenado en la caja grande es 3

· 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:

p = new int [4];

en el destructor la liberaríamos de la siguiente forma:

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 grande (20, 10, 30);

Caja *punteroACaja1;

Caja *punteorACaja2;

punteroACaja1 = new Caja;

punteroACaja2 = new Caja(1, 2, 3);

cout << "El volumen de la caja grande es " << grande.volumen() << '\n';

cout << "El nuevo volumen 2 es " << punteroACaja2 -> volumen() << '\n';

punteroACaja1 -> set (2, 4, 6);

cout << "El nuevo volumen 1 es " << punteroACaja1 -> volumen() << '\n';

delete punteroACaja1, punteroACaja2;

La salida de este programa será:

El volumen de la caja grande es 6000

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.

Cuando declaramos punteroACaja1 y punteroACaja2 no se llama al constructor de Caja. Esto se hace


cuando reservamos memoria para un objeto de ese tipo.

El acceso a los componentes del objeto se hace a través del operador ->

Las siguientes sentencias son equivalentes:

punteroACaja1 -> volumen()

(*punteroACaja1).volumen()

pero la notación -> es la que se suele utilizar.

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 {

double longitud, anchura, altura;

Caja *otraCaja;

public:

Caja(double dim1, double dim2, double dim3); //Constructor

double volumen(void);

void point_at_next(Caja *where_to_point);

Caja *get_next(void);

};

Caja::Caja(double dim1, double dim2, double dim3) { //implementación del constructor

longitud = dim1;

anchura = dim2;

altura = dim3;

otraCaja = NULL;

double Caja::volumen(void) {

return (longitud * anchura * altura);

void Caja::point_at_next(Caja *where_to_point) {

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 pequeña(5, 4, 10), mediana (10, 6, 20), grande(20, 10, 30);

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";

El resultado de la ejecución será :

El volumen de la Caja pequeña es 200

El volumen de la Caja mediana es 1200

El volumen de la Caja grande es 6000

La Caja apuntada tiene volumen = 1200

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.

· En el programa principal declaramos un puntero a un objeto del tipo Caja, CajaPointer.

· 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.

· A continuación hacemos que el puntero CajaPointer apunte a la caja pequeña y lo utilizamos


para referenciar a la caja pequeña y actualizarlo al valor contenido en la caja pequeña que es la
dirección de la caja mediana. Hemos pasado, pues, de un elemento de la lista al siguiente
llamando a una función de uno de los objetos.
Un objeto embebido en otro objeto. El puntero this.

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.

No pueden ser funciones amigas los constructores ni los destructores

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.

Los siguientes operadores no pueden ser sobrecargados:

o El operador ternario ?.
o El operador de acceso a una clase o estructura : .
o El operador scope ::

Sobrecarga de operadores binarios.

Un operador binario es el que tiene dos operandos.

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;

double anchura, altura;

public:

void set(int l, int w, int h) {longitud = l; anchura = w; altura = h;}

double volumen(void) {return longitud * anchura * altura;}

Caja operator+(Caja a);

};

Caja operator+(Caja a) {

Caja temp;

temp.longitud = longitud + a.longitud;

temp.anchura = anchura + a.anchura;

temp.altura = altura + a.altura;

return temp;

main() {

Caja pequeña, mediana, grande;

Caja temp;

pequeña.set(2, 4, 5);

mediana.set(5, 6, 8);

grande.set(8, 10, 12);

cout << "El volumen es " << pequeña.volumen() << "\n";

cout << "El volumen es " << mediana.volumen() << "\n";

cout << "El volumen es " << grande.volumen() << "\n";

temp = pequeña + mediana;

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;

double anchura, altura;

public:

void set(int l, int w, int h) {longitud = l; anchura = w; altura = h;}

int volumen(void) {return longitud * anchura * altura;}

friend Caja operator+(Caja a, Caja b); // Add two Cajas

friend Caja operator+(int a, Caja b); // Add a constant to a Caja

friend Caja operator*(int a, Caja b); // Multiply a Caja by a constant

};

Caja operator+(Caja a, Caja b) {

Caja temp;

temp.longitud = a.longitud + b.longitud;

temp.anchura = a.anchura + b.anchura;

temp.altura = a.altura + b.altura;

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 operator*(int a, Caja b) { // Multiply a Caja by a constant

Caja temp;

temp.longitud = a * b.longitud;

temp.anchura = a * b.anchura;

temp.altura = a * b.altura;

return temp;

main() {

Caja pequeña, mediana, grande;

Caja temp;

pequeña.set(2, 4, 5);

mediana.set(5, 6, 8);

grande.set(8, 10, 12);

cout << "El volumen es " << pequeña.volumen() << "\n";

cout << "El volumen es " << mediana.volumen() << "\n";

cout << "El volumen es " << grande.volumen() << "\n";

temp = pequeña + mediana;

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 resultado de la ejecución es:

El volumen es 40

El volumen es 240.

El volumen es 960.

El nuevo volumen es 840.

El nuevo volumen es 2520.

El nuevo volumen es 61440.

· 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.

Sobrecarga de operadores unarios.

Un operador unario sólo tiene un operando. Ejemplos de operadores unitarios son ++ y --.

#include <iostream.h>

class Burrito {

private:

int amtbeef, amtbean;

public:

Burrito(int beef, int bean) {

amtbeef = beef;

amtbean = bean;

Burrito operator ++() {

amtbeef++; amtbean++;

return Burrito(amtbeef, amtbean);


}

int getBeef() { return amtbeef; }

};

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.