Академический Документы
Профессиональный Документы
Культура Документы
Eduardo Alcaraz
Contents
1 Introducción
Grácos (se corresponden con los símbolos más usados para escribir
por los humanos).
1
1.2 Especicadores de Formato en la Función printf
Cuando a la función printf de la biblioteca estándar del lenguaje C se le
pase una lista de argumentos (expresiones), se debe escribir un especicador
de formato por cada argumento. En la función printf, los especicadores de
formato establecen el formato de salida por pantalla de los argumentos.
2 Ejemplo printf
#include <stdio.h>
int main(){
printf("*******\n");
printf("*******\n");
2
printf("*******\n");
return 0;
}
Ejemplo de printf
#include <stdio.h>
int main()
{
printf("Hola mundo \n mundo\n ");
printf("Hola1 \t Hola2 \n Hola3 ");
return 0;
}
Resultado
Hola mundo
mundo
Hola1 Hola2
Hola3
Ejemplo printf
#include <stdio.h>
int main()
{
printf("Hola mundo \n mundo");
printf("Hola1 \t Hola2 \n Hola3 ");
return 0;
}
Resultado
Hola mundo
mundoHola1 Hola2
Hola3
3
3 Tipo de Datos en C Enteros
Los enteros son el tipo de dato más primitivo en C. Se usan para representar
números enteros. Pero siempre se pueden encontrar otras aplicaciones para
los números enteros. En general se pueden usar para representar cualquier
variable discreta.
Los tipos de datos enteros son: short, int, long y long long, cada uno
representando un número entero de un tamaño o capacidad determinado.
Según el compilador y la plataforma de hardware, cada uno de estos tipos
de dato puede ocupar desde 1 byte hasta 8 bytes en memoria.\
0.5cm]
0.5cm]
4
3.2 Tipo de Datos en C Caracteres
Los caracteres se representan utilizando el tipo char, que tiene sólo 1 byte
de tamaño. Este tipo se utiliza para representar los 256 caracteres de la
tabla de caracteres del sistema. El tipo char es también un tipo entero, ya
que puede tomar valores de 0 a 255. Por lo tanto también puede ser signed
o unsigned. En cuanto a la forma de declarar variables de tipo char es la
misma forma que con los otros tipos.\
0.5cm]
Ejemplo de declaraciones de Caracteres
char a;
char a = 's';
unsigned char a = 48;
Ejemplo de tipos de Datos
#include <stdio.h>
int main(void)
{
int resultado;
float result;
char demo ='c';
resultado=5+2;
printf("Resultado de la suma: %d\n",resultado);
result=5.2-2;
printf("Resultado de la resta: %f\n",result);
printf("Contiene el caracter : %c \n",demo);
return 0;
}
Resultado
Resultado de la suma: 7
Resultado de la resta: 3.2
Contiene el caracter : c
5
Expresiones Una expresion es una combinación de constantes, variables
y operadores que se emplean para denotar cálculos. por ejemplo.
Ejemplo (2+3)*10
5 Función scanf
Ejemplo scanf
// Este ejemplo guarda un numero en n.
int n;
printf("Introduce un numero: ");
scanf("%d",&n);
// Este ejemplo guarda un caracter en m.
char m;
printf("Introduce un caracter: ");
scanf("%c",&m);
// Este ejemplo guarda una cadena de caracteres (solamente una
// palabra) en cad.
// Notese la ausencia de &
char cad[20];
printf("Introduce una palabra: ");
scanf("%s",cad);
Ejemplo scanf
6
//Si se introduce un dato erroneo se interrumpe la lectura
// del resto.
int validos;
printf("Introduce un entero, un real, y un caracter:");
validos=scanf("%i%f%c",&a,&b,&c); //Entrada: 5 1.2 J
// | Salida: 3
7
#include <stdio.h>
int main()
{
putchar('H');
putchar('o');
putchar('l');
putchar('a');
putchar(32);
putchar('m');
putchar('u');
putchar('n');
putchar('d');
putchar('o');
putchar('\n');
return 0;
}
El resultado es:
Hola mundo
8
#include <stdio.h>
int main()
{
int c;
putchar(c);
return 0;
}
#include <stdio.h>
int main()
{
produce el resultado
Bienvenido a la programacin
en lenguaje C
#include <stdio.h>
main()
{
char cadena[50];
9
puts("Escriba un texto:");
gets(cadena);
puts("El texto escrito es:");
puts(cadena);
}
La declaración char cadena[50]; crea una variable llamada cadena que puede
almacenar hasta 50 caracteres. Este código produce, cuando escribimos con
el teclado el texto Bienvenido a la programación en lenguaje C, el resultado:
Escriba un texto:
Bienvenido a la programacin en lenguaje C
El texto escrito es:
Bienvenido a la programacin en lenguaje C
#include <stdio.h>
int main()
{
char cadena[50];
char *p;
puts("Escriba un texto:");
p = gets(cadena);
if (p) {
puts("El texto escrito es:"); puts(cadena);
}
else
puts("No se ha guardado nada.");
return 0;
}
10
Esta función es un puede ser peligrosa porque no comprueba si nos hemos
pasado del espacio reservado (de 50 caracteres en este ejemplo: 49 caracteres
+ '\0').
7 Apuntadores.
8 Declaración de un apuntador
11
9 Apuntadores Ejemplos
9.1 Ejemplo
p=&c; Se asigna la dirección de c a la variable apuntador p
10 Apuntadores Ejemplos
10.1 Ejemplo
#include <stdio.h>
void main(){
int *pi, i, val;
i=100;
pi=&i;
val=*pi;
printf("%d", val);
}
12
La aritmética de operadores no suma las direcciones de memoria, sino
elementos. Esto quiere decir que si yo incremento en uno a una variable de
apuntador a entero no se incrementara un byte, sino dos bytes porque es el
espacio que ocupa un entero en memoria.
0.5cm]
Los usuarios pueden crear variables con valores que están en el rango de
valores legales y pueden operar sobre esos valores utilizando las operaciones
denidas.
Los módulos se utilizan frecuentemente como una técnica de implementación
para tipos abstractos de datos, y el tipo abstracto de datos es un concepto
más teórico. Para construir un tipo abstracto de datos se debe poner:
13
Proteger los datos asociados con el tipo de modo que sólo se puede
actuar sobre ellos con las rutinas proporcionadas.
struct punto3D
{
float x;
float y;
float z;
};
13 Sentencia typedef
14
Se a declarado el nuevo tipo punto3D, que permitirá denir variables o
declarar el tipo de una función.
0.5cm]
15 Estructura
15
Aquí struct se utiliza para iniciar una declaración de Estructura. auto es
el nombre de la etiqueta de la Estructura, en este ejemplo hay tres tipos de
variables, char, int y oat. Las variables tienen sus propios nombres como
anio modelo etc.
Observe que un nombre de etiqueta de Estructura, como auto, es el nom-
bre de una Estructura. El compilador emplea el nombre de etiqueta para
identicar a la Estructura.
struct infoLibro
{
char titulo[60];
char autor[30];
char editorial[30];
int anyo;
}libro1, libro2, libro3;
16
#include <stdio.h>
#define ELEMENTOS 3
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
struct estructura_amigo amigo[ELEMENTOS];
int main()
{
int num_amigo;
}
return 0;
}
Obsérvese la línea while(getchar()!= ). Esta línea se usa para vaciar el buer
de entrada
17
15.4 Inicialización de una estructura
A las estructuras se les pueden dar valores iniciales de manera análoga a como
se hace con los arrays. Primero debemos denir la estructura y después,
cuando declaramos una variable como estructura, le damos el valor inicial
que queremos. Por ejemplo, para la estructura que hemos denido antes
podría ser:
#include <stdio.h>
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
main()
{
18
printf( "%s tiene ", amigo.apellido );
printf( "%i anos ", amigo.edad );
printf( "y su telefono es el %s.\n" , amigo.telefono );
}
#include <stdio.h>
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
struct estructura_amigo amigo = {
"Juanjo",
"Lopez",
"983403367",
30
};
19
int main()
{
struct estructura_amigo *p_amigo;
p_amigo = &amigo;
printf( "%s tiene ", p_amigo->apellido );
printf( "%i anos ", p_amigo->edad );
printf( "y su telefono es el %s.\n" , p_amigo->telefono );
return 0;
}
Hasta la denición del puntero p_amigo todo resulta igual que antes.
Éste es un puntero a la estructura estructura_amigo. Dado que es un
puntero, debemos indicarle a dónde debe apuntar; en este caso hacemos que
apunte a la variable amigo: p_amigo = \amigo; (recordemos que el operador
\ signica 'dame la dirección donde está almacenado. . . ').
Ahora queremos acceder a cada campo de la estructura. Antes lo hacíamos
usando el operador '.', pero, como muestra el ejemplo, si se trabaja con pun-
teros se debe usar el operador '->'. Este operador, denominado selector
indirecto de miembro, viene a signicar algo así como: "dame acceso al
miembro . . . del puntero . . . ".
Vamos a ver ahora con un ejemplo cómo introducir datos en las estruc-
turas:
#include <stdio.h>
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
int main()
{
struct estructura_amigo *p_amigo;
p_amigo = &amigo;
20
gets(p_amigo->nombre);
printf("Apellido: ");
gets(p_amigo->apellido);
printf("Edad: ");
scanf( "%i", &p_amigo->edad );
#include <stdio.h>
#define ELEMENTOS 3
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
21
int main()
{
struct estructura_amigo *p_amigo;
int num_amigo;
#include <stdio.h>
#define ELEMENTOS 3
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
int main()
{
22
struct estructura_amigo amigo[ELEMENTOS], *p_amigo;
int num_amigo;
}
/* Imprimimos los datos */
p_amigo = amigo;
23
usamos de nuevo p_amigo = amigo;.
#include <stdio.h>
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
int main()
{
printf( "%s tiene ", amigo.apellido );
printf( "%i anos ", amigo.edad );
printf( "y dentro de 20 anos tendra %i.\n", suma(amigo) );
return 0
24
}
#include <stdio.h>
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
main()
{
printf( "%s tiene ", amigo.apellido );
printf( "%i anos ", amigo.edad );
printf( "y dentro de 20 anos tendra %i.\n", suma(&amigo) );
}
25
int suma( struct estructura_amigo *arg_amigo )
{
return arg_amigo->edad + 20;
}
int main()
{
printf( "%s tiene ", amigo.apellido );
printf( "%i anos ", amigo.edad );
printf( "y dentro de 20 anos tendra %i.\n", suma(amigo.edad) );
return 0;
}
26
15.9 Estructuras dentro de estructuras (estructuras anidadas)
Es posible crear estructuras que tengan como miembros otras estructuras.
Esto tiene diversas utilidades, por ejemplo contar con estructuras de datos
más ordenadas. Imaginemos la siguiente situación: una tienda de música
quiere hacer un programa para el inventario de los discos, cintas y cd's.
Para cada título se quiere conocer las existencias en cada soporte (cinta,
disco, cd), y los datos del proveedor. Podría pensarse en una estructura
así:
struct inventario {
char titulo[30];
char autor[40];
int existencias_discos;
int existencias_cintas;
int existencias_cd;
char nombre_proveedor[40];
char telefono_proveedor[10];
char direccion_proveedor[100];
};
Sin embargo, utilizando estructuras anidadas se podría hacer de esta otra
forma más ordenada:
struct est_existencias {
int discos;
int cintas;
int cd;
};
struct est_proveedor {
char nombre_proveedor[40];
char telefono_proveedor[10];
char direccion_proveedor[100];
};
struct est_inventario {
char titulo[30];
char autor[40];
struct est_existencias existencias;
struct est_proveedor proveedor;
} inventario;
27
Ahora, para acceder al número de cd de cierto título usaríamos:
inventario.existencias.cd
y para acceder al nombre del proveedor.
inventario.proveedor.nombre
16 Archivos
16.1 fopen
Esta función sirve para abrir y crear archivos en disco. El prototipo corre-
spondiente de fopen es:
28
"r+" : abrir un archivo para lectura y escritura, el archivo debe existir.
16.2 fclose
Esta función sirve para poder cerrar un archivo que se ha abierto.
El prototipo correspondiente de fclose es:
#include <stdio.h>
#include <stdlib.h>
return 0;
}
Como vemos, en el ejemplo se utilizó el opentype "r", que es para la
lectura.
Otra cosa importante es que el lenguaje C no tiene dentro de si una
estructura para el manejo de excepciones o de errores, por eso es necesario
29
comprobar que el archivo fue abierto con éxito "if (fp == NULL)". Si fopen
pudo abrir el archivo con éxito devuelve la referencia al archivo (FILE *), de
lo contrario devuelve NULL y en este caso se debera revisar la direccion del
archivo o los permisos del mismo. En estos ejemplos solo vamos a dar una
salida con un retorno de 1 que sirve para señalar que el programa termino
por un error.
16.3 eof
Esta función sirve para determinar si el cursor dentro del archivo encontró
el nal (end of le). Existe otra forma de vericar el nal del archivo que es
comparar el caracter que trae fgetc del archivo con el macro EOF declarado
dentro de stdio.h, pero este método no ofrece la misma seguridad (en especial
al tratar con los archivos "binarios"). La función feof siempre devolverá cero
(Falso) si no es encontrado EOF en el archivo, de lo contrario regresará un
valor distinto de cero (Verdadero).
El prototipo correspondiente de feof es:
16.4 Lectura
Un archivo generalmente debe verse como un string (una cadena de car-
acteres) que esta guardado en el disco duro. Para trabajar con los archivos
existen diferentes formas y diferentes funciones. Las funciones que podríamos
usar para leer un archivo son:
t t t
size fread(void *puntero, size tamano, size cantidad, FILE *archivo);
Las primeras dos de estas funciones son muy parecidas entre si. Pero la
tercera, por el numero y el tipo de parámetros, nos podemos dar cuenta de
que es muy diferente, por eso la trataremos aparte junto al fwrite que es su
contraparte para escritura.
30
16.5 fgetc
Esta función lee un caracter a la vez del archivo que esta siendo señalado
con el puntero *archivo. En caso de que la lectura sea exitosa devuelve el
caracter leído y en caso de que no lo sea o de encontrar el nal del archivo
devuelve EOF.
El prototipo correspondiente de fgetc es:
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *archivo;
int caracter;
archivo = fopen("prueba.txt","r");
if (archivo == NULL)
{
printf("\nError de apertura del archivo. \n\n");
}
else
{
printf("\nEl contenido del archivo de prueba es \n\n");
while((caracter = fgetc(archivo)) != EOF)
{
printf("%c",caracter);
}
}
fclose(archivo);
return 0;
}
31
16.6 fgets
Esta función está diseñada para leer cadenas de caracteres. Leerá hasta n-1
caracteres o hasta que lea un cambio de línea o un nal de archivo EOF.
En este último caso, el carácter de cambio de línea también es leído.
El prototipo correspondiente de fgets es:
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *archivo;
char caracteres[100];
archivo = fopen("prueba.txt","r");
if (archivo == NULL)
exit(1);
fclose(archivo);
return 0;
}
32
Este es el mismo ejemplo de antes con la diferencia de que este hace
uso de fgets en lugar de fgetc. La función fgets se comporta de la siguiente
manera, leerá del archivo apuntado por archivo los caracteres que encuentre
y a ponerlos en buer hasta que lea un caracter menos que la cantidad de
caracteres especicada en tamaño o hasta que encuentre el nal de una linea
() o hasta que encuentre el nal del archivo (EOF). En este ejemplo no vamos
a profundizar mas que para decir que caracteres es un buer, los pormenores
seran explicados en la sección de manejo dinámico de memoria.
El benecio de esta función es que se puede obtener una linea completa
a la vez. Y resulta muy útil para algunos nes como la construcción de un
parser de algún tipo de archivo de texto.
16.7 fread
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
size : Tamaño en bytes de cada elemento (de los que voy a leer).
16.8 fscanf
La función fscanf funciona igual que scanf en cuanto a parámetros, pero la
entrada se toma de un archivo en lugar del teclado.
El prototipo correspondiente de fscanf es:
33
#include <stdio.h>
char buffer[100];
fclose ( fp );
return 0;
}
16.9 Escritura
Así como podemos leer datos desde un archivo, también se pueden crear
y escribir archivos con la información que deseamos almacenar, Para tra-
bajar con los archivos existen diferentes formas y diferentes funciones. Las
funciones que podríamos usar para escribir dentro de un archivo son:
t t t
size fwrite(void *puntero, size tamano, size cantidad, FILE *archivo);
16.10 fputc
Esta función escribe un carácter a la vez del archivo que esta siendo señalado
con el puntero *archivo. El valor de retorno es el carácter escrito, si la
operación fue completada con éxito, en caso contrario será EOF.
El prototipo correspondiente de fputc es:
34
Mostramos un ejemplo del uso de fputc en un "archivo.txt", se escribira
dentro del archivo hasta que presionemos la tecla enter.
#include <stdio.h>
char caracter;
fclose ( fp );
return 0;
}
16.11 fputs
La función fputs escribe una cadena en un archivo. la ejecución de la misma
no añade el carácter de retorno de línea ni el carácter nulo nal. El valor de
retorno es un número no negativo o EOF en caso de error. Los parámetros
de entrada son la cadena a escribir y un puntero a la estructura FILE del
archivo donde se realizará la escritura.
El prototipo correspondiente de fputs es:
#include <stdio.h>
35
{
FILE *fp;
fputs( cadena, fp );
fclose ( fp );
return 0;
}
16.12 fwrite
Esta función está pensada para trabajar con registros de longitud constante
y forma pareja con fread. Es capaz de escribir hacia un archivo uno o varios
registros de la misma longitud almacenados a partir de una dirección de
memoria determinada. El valor de retorno es el número de registros escritos,
no el número de bytes. Los parámetros son: un puntero a la zona de memoria
de donde se obtendrán los datos a escribir, el tamaño de cada registro, el
número de registros a escribir y un puntero a la estructura FILE del archivo
al que se hará la escritura.
El prototipo correspondiente de fwrite es:
#include <stdio.h>
36
fwrite( cadena, sizeof(char), sizeof(cadena), fp ); //char cadena[]... cada pos
fclose ( fp );
return 0;
}
16.13 fprintf
La función fprintf funciona igual que printf en cuanto a parámetros, pero la
salida se dirige a un archivo en lugar de a la pantalla.
El prototipo correspondiente de fprintf es:
#include <stdio.h>
fprintf(fp, buffer);
fprintf(fp, "%s", "\nEsto es otro texto dentro del archivo.");
fclose ( fp );
return 0;
}
17 Ámbitos
37
o es accesible. El ámbito de acceso nos dice desde donde es accesible. En
este capítulo hablaremos un poco sobre el ámbito de las variables, pero no
entraremos en muchos detalles todavía, ya que es un tema largo. Por otra
parte, las funciones (y otros objetos de los que aún no hemos hablado nada),
también tienen distintos ámbitos.
Las variables declaradas dentro de una función, y recuerda que main tam-
bién es una función, sólo serán accesibles para esa función, desde el punto en
que se declaran hasta el nal. Esas variables son variables locales o de ámbito
local de esa función. Al igual que ocurre con las variables locales de bucle,
en las de función, las variables se crean al inciar la función y se destruyen
al terminar. Las variables declaradas fuera de las funciones, serán accesibles
desde todas las funciones denidas después de la declaración. Diremos que
esas variables son globales o de ámbito global. El ámbito temporal de estas
variables es también global: se crean junto con el programa, y se destruyen
cuando el programa concluye. Las variables globales son las únicas que son
inicializadas automáticamente con valor cero cuando se declaran. Esto no
sucede con ninguna variable local. En todos los casos descritos, el ámbito
38
temporal coincide con el de acceso: las variables que no pueden ser accedidas
es porque no existen todavía o porque han sido destruídas. Más adelante ver-
emos casos en que estos ámbitos no coinciden. Una variable global declarada
después de la denición de una función no será accesible desde esa función,
por eso, normalmente se declaran las variables globales antes de denir las
funciones. Pero esto es hablando de forma general, en realidad, en C++ está
mal visto usar variables globales, ya que se consideran poco seguras.
Ejemplo:
int main() {
// Declaracion de una variable local de main:
int EnteroLocal;
// Acceso a una variable local:
EnteroLocal = Funcion1(10);
// Acceso a una valiable global:
EnteroGlobal = Funcion1(EnteroLocal);
return 0;
}
int Funcion1(int a)
{
char CaracterLocal; // Variable local de funcion1
// Desde aqui podemos acceder a EnteroGlobal,
// y tambien a CaracterLocal
// pero no a EnteroLocal
if(EnteroGlobal != 0)
return a/EnteroGlobal;
return 0;
}
De modo que en cuanto a los ámbitos locales tenemos varios niveles:
<tipo> funcion(parametros)
{
<tipo> var1;
for(<tipo> var2;...)
...
39
<tipo> var3;
...
return var;
}
Ejemplo
int x;
int main() {
int x;
x = 10;
return 0;
}
int x = 10;
{
int x = 0;
for(int x = 0; x < 10; x++) HacerAlgoCon(x);
}
40
En este caso la declaración de x dentro del bloque enmascara la declaración
anterior, y a su vez, la declaración dentro del bucle for enmascara a la
declaración del bloque. Otra cuestión sería qué utilidad pueda tener esto.
int main()
{
int x; // Variable local que enmascara a la global
18 Fundamento de Clases
41
La lista de clases base se usa para derivar clases, de momento no le prestes
demasiada atención, ya que por ahora sólo declararemos clases base. La lista
de miembros será en general una lista de funciones y datos. Los datos se
declaran del mismo modo en que lo hacíamos hasta ahora, salvo que no
pueden ser inicializados, recuerda que estamos hablando de declaraciones de
clases y no de deniciones de objetos. En el siguiente capítulo veremos el
modo de inicializar las variables de un objeto. Las funciones pueden ser
simplemente declaraciones de prototipos, que se deben denir aparte de la
clase pueden ser también deniciones. Cuando se denen fuera de la clase
se debe usar el operador de ámbito "::". Lo veremos mucho mejor con un
ejemplo.
#include <iostream>
using namespace std;
class pareja {
private:
// Datos miembro de la clase "pareja"
int a, b;
public:
// Funciones miembro de la clase "pareja"
void Lee(int &a2, int &b2);
void Guarda(int a2, int b2) {
a = a2;
b = b2;
}
};
int main() {
pareja par1;
int x, y;
par1.Guarda(12, 32);
par1.Lee(x, y);
cout << "Valor de par1.a: " << x << endl;
42
cout << "Valor de par1.b: " << y << endl;
return 0;
}
Para que quede claro que nos referimos a la función "Lee" de la clase
"pareja". Ten en cuenta que pueden existir otras clases que tengan fun-
ciones con el mismo nombre, y también que si no especicamos que estamos
deniendo una función de la clase "pareja", en realidad estaremos deniendo
una función corriente. En el caso de la función "Guarda" la hemos denido
en el interior de la propia clase. Esto lo haremos sólo cuando la denición
sea muy simple, ya que diculta la lectura y comprensión del programa.
Además, las funciones denidas de este modo serán tratadas como inline, y
esto sólo es recomendable para funciones cortas, ya que, (como recordarás),
en estas funciones se inserta el código cada vez que son llamadas.
18.2 Clases
Una clase dene un nuevo tipo de dato que especica la forma de un objeto.
Una clase incluye los datos y el código que operará sobre esos datos. Además,
una clase enlaza datos y código. C++ usa una especicación de una clase
para construir objetos. Los objetos son instancias de una clase. Además, una
clase es esencialmente una serie de planes que especican cómo construir un
objeto. Es importante tener claro esto: Una clase es una abstracción lógica.
No es sino hasta que un objeto de esa clase sea creado que la repre-
sentación física de la clase existe en la memoria. Cuando se dene una clase,
se declaran los datos que ésta contiene y el código que opera en esos datos.
Aunque clases muy simples pueden contener sólo código o sólo datos, la may-
oría de las clases contienen ambos. En conjunto, los datos se almacenan en
las variables y el código en las funciones. Colectivamente, las funciones y
variables que constituyen una clase son llamados 'miembros' de la clase. Una
variable declarada dentro de una clase es llamada 'variable miembro', y una
43
función declarada en una clase es llamada 'función miembro'. En ocasiones
el término 'variable de instancia' es usado en lugar de variable miembro.
Una clase es creada con la palabra clave class. La declaración de una
clase es similar sintácticamente a una estructura ( y tienen muchísimo que
ver ). Aquí tenemos un ejemplo. La siguente clase dene un tipo llamado
CRender, el cual es usado para implementar operaciones de renderizado en
este caso.
44
un especicador del nuevo tipo. Por ejemplo la siguiente declaración crea 2
objetos llamados render1 y render2 del tipo CRender.
45
18.4 Acceso a las funciones
Las funciones miembros de una clase sólo pueden ser llamadas relativas a un
objeto especíco. Para llamar a una función miembro desde alguna parte
del programa que se encuentre fuera de la clase, se debe usar el nombre
del objeto y el operador de direcionamiento '.' ( punto ). Por ejemplo, lo
siguiente llama a m_Renderizar() en el objeto objeto1.
CRender objeto1, objeto2;
objeto1.m_Renderizar();
#include <iostream>
#include <cstring>
using std::cout;
using std::endl;
46
};
return (0);
}
Atributos:
47
En comparación con la programación tradicional, un atributo es lo mismo
que una variable cualquiera, salvo que como los atributos se declaran para
pertenecer a una clase especíca, se dice que todos los atributos de dicha clase
son miembros de la misma. Por lo demás, la declaración de los atributos es
exactamente igual que declarar cualquier otra variable.
Miembros:
A partir de este momento usaremos la palabra miembro para referirnos
al hecho de que un método o un atributo pertenece a tal o cual clase. Por
Ejemplo, en el programa ( visto anteriormente ) la Clase CRender posee dos
miembros, buer que es un atributo; y m Renderizar que es un método.
class CRender {
public:
char buffer[256]; // atributo
void m_Renderizar(const char *cadena); // mdo
};
48
En la clase Pareja que se verá en seguida, se declaran dos atributos y
cuatro métodos para la manipulación de dichos atributos. Observe que los
atributos son privados( por defecto ), mientras que los métodos se declaran
públicos.
class Pareja
{
// atributos
double a, b;
public:
// metodos
double getA();
double getB();
void setA(double n);
void setB(double n);
};
18.7 Subclases
Una subclase es una clase que se deriva de otra. La clase que sirve de base
suele conocerse como parent (padre), y a la subclase se le llama child (hija).
En C++ cada clase que es creada se convierte en candidata para servir de
base de donde se deriven otras. Por ejemplo, la clase Pareja es candidata para
convertirse en la base para las subclases Suma, Resta, Multiplica, Divide, y
otras posibles subclases en donde se utilice un par de valores numéricos. Para
poner un ejemplo, pensemos en que deseamos crear la clase Suma, misma
que será utilizada para obtener la suma de dos números. Puesto que la clase
Pareja posee dos atributos númericos puede ser usada como base para la
clase que estamos proyectando. Así, el siguiente ejemplo se constituye en un
caso de clases derivadas.
49
{
...
};
public:
// metodos de Suma
double calcular();
};
// implementacion de Suma
//
double Suma::calcular() { return getA() + getB(); }
#include <iostream.h>
int main()
{
Suma s;
s.setA(80);
s.setB(100);
cout << s.getA() << " + " << s.getB() << " = " << s.calcular() << endl;
cin.get();
return 0;
}
18.8 Herencia
La herencia es uno de los mecanismos más útiles de la programación ori-
entada al objeto, ya que por medio de la misma se puede llevar a cabo la
reutilización de código. Es decir, puesto que toda clase denida se convierte
en candidata para ser usada como base de donde se deriven otras, esto da
como resultado que las clases derivadas hereden todos los miembros de la
clase base. Por ejemplo, la clase Suma vista en la sección anterior, hereda
todos los miembros de la clase Pareja puesto que Suma es una extensión de
50
Pareja. En ese sentido, podemos decir que existen dos tipos de herencia, por
extensión y por agregación o composición. En el caso de las clases Pareja y
Suma, se dice que Suma es una extensión de Pareja. Vista grácamente, la
herencia por extensión se puede representar así:
//OPERADORES
//Aqui van los metodos que se apliquen sobre operadores
// OPERACIONES
/* Aqui van los metodos de esta clase que no sean ni de acceso ni de peticion o tratam
// ACCESO
/* Aqui van las funciones de acceso a los datos miembro o variables propias del objeto
/*
* Funcion 'set_ruedas'
* Recibe: num como int
* Devuelve: void
* Asigna al dato miembro 'mRuedas' el valor 'num'
*/
51
{
this->mRuedas = num;
}
/*
* Funcion 'get_ruedas'
* Recibe: void
* Devuelve: int
* Devuelve el valor que hay dentro del dato miembro 'mRuedas'
*/
int get_ruedas(void)
{
return this->mRuedas;
}
/*
* Funcion 'set_pasajeros'
* Recibe: num como int
* Devuelve: void
* Asigna al dato miembro 'mPasajeros' el valor 'num'
*/
void set_pasajeros(int num)
{
this->mPasajeros = num;
}
/*
* Funcion 'get_pasajeros'
* Recibe: void
* Devuelve: int
* Devuelve el valor que hay dentro del dato miembro 'mPasajeros'
*/
int get_pasajeros(void)
{
return this->mPasajeros;
}
52
// PETICIONES/TRATAMIENTOS
/* Aqui van las funciones del tipo "Is", que generalmente devuelven true/false */
private:
/* Generalmente en 'private' se situan los datos miembros */
int mRuedas;
int mPasajeros;
};
rodante '.
'vehiculo Entonces agrega 'carga' a la misma, en conjunto con las
funciones miembros que trabajen sobre este dato. Nótese como 'VehiculoRo-
dante' es heredado. La forma general para la herencia se muestra aquí:
// INCLUDES DE SISTEMA
//
#include <iostream>
// INCLUDES LOCALES
53
//
// DECLARACIONES
//
// OPERADORES
/* Aqui van los metodos que se apliquen sobre operadores */
// OPERACIONES
/* Aqui van los metodos de esta clase que no sean ni de acceso ni de peticion o tratam
/*
* Funcion 'set_ruedas'
* Recibe: num como int
* Devuelve: void
* Asigna al dato miembro 'mRuedas' el valor 'num'
*/
void set_ruedas(int num)
{
this->mRuedas = num;
}
/*
* Funcion 'get_ruedas'
* Recibe: void
* Devuelve: int
* Devuelve el valor que hay dentro del dato miembro 'mRuedas'
*/
int get_ruedas(void)
{
return this->mRuedas;
54
}
/*
* Funcion 'set_pasajeros'
* Recibe: num como int
* Devuelve: void
* Asigna al dato miembro 'mPasajeros' el valor 'num'
*/
void set_pasajeros(int num)
{
this->mPasajeros = num;
}
/*
* Funcion 'get_pasajeros'
* Recibe: void
* Devuelve: int
* Devuelve el valor que hay dentro del dato miembro 'mPasajeros'
*/
int get_pasajeros(void)
{
return this->mPasajeros;
}
// PETICIONES/TRATAMIENTOS
/* Aqui van las funciones del tipo "Is", que generalmente devuelven true/false */
private:
/* Generalmente en 'private' se situan los datos miembros */
int mRuedas;
int mPasajeros;
};
public:
// CICLO DE VIDA
55
/* En este lugar se situan los constructores, los destructores, y/o los constructores
// OPERADORES
/* Aqui van los metodos que se apliquen sobre operadores */
// OPERACIONES
/* Aqui van los metodos de esta clase que no sean ni de acceso ni de peticion o tratam
// ACCESO
/* Aqui van las funciones de acceso a los datos miembro o variables propias del objeto
/*
* Funcion 'set_carga'
* Recibe: size como int
* Devuelve: void
* Asigna al dato miembro 'mCarga' el valor 'size'
*/
void set_carga(int size)
{
this->mCarga = size;
}
/*
* Funcion 'get_carga'
* Recibe: void
* Devuelve: int
* Devuelve el valor que hay dentro del dato miembro 'mCarga'
*/
int get_carga(void)
{
return this->mCarga;
}
/*
* Funcion 'Mostrar'
* Recibe: void
* Devuelve: void
* Muestra por pantalla las ruedas, pasajeros y la capacidad de carga del objeto 'Camion
*/
56
void Mostrar(void);
// PETICIONES/TRATAMIENTOS
/* Aqui van las funciones del tipo "Is", que general
private:
/* Generalmente en 'private' se situan los datos miembros */
int mCarga;
};
void Camion::Mostrar(void)
{
std::cout << "ruedas: " << this->get_ruedas() << std::endl;
std::cout << "pasajeros: " << this->get_pasajeros() << std::endl;
std::cout << "Capacidad de carga en pies cubicos: " << this->get_carga() << std::endl;
}
// OPERADORES
/* Aqui van los metodos que se apliquen sobre operadores */
// OPERACIONES
/* Aqui van los metodos de esta clase que no sean ni de acceso ni de peticion o tratam
// ACCESO
/* Aqui van las funciones de acceso a los datos miembro o variables propias del objeto
/*
* Funcion 'set_tipo'
* Recibe: t como tipo
* Devuelve: void
* Asigna al dato miembro 'mTipoDeAutomovil' el valor 't'
57
*/
void set_tipo(tipo t)
{
this->mTipoDeAutomovil = t;
}
/*
* Funcion 'get_tipo'
* Recibe: void
* Devuelve: tipo
* Devuelve el valor que hay dentro del dato miembro 'mTipoDeAutomovil'
*/
void Mostrar(void);
private:
enum tipo mTipoDeAutomovil;
};
void Automovil::Mostrar(void)
{
std::cout << "ruedas: " << this->get_ruedas() << std::endl;
std::cout << "pasajeros: " << this->get_pasajeros() << std::endl;
std::cout << "tipo: ";
switch(this->get_tipo())
{
case deportivo:
std::cout << "deportivo";
break;
case berlina:
58
std::cout << "berlina";
break;
case turismo:
std::cout << "turismo";
}
std::cout << std::endl;
}
int main(void)
{
Camion Camion1;
Camion Camion2;
Automovil Automovil1;
Camion1.set_ruedas(18);
Camion1.set_pasajeros(2);
Camion1.set_carga(3200);
Camion2.set_ruedas(6);
Camion2.set_pasajeros(3);
Camion2.set_carga(1200);
Camion1.Mostrar();
std::cout << std::endl;
Camion2.Mostrar();
std::cout << std::endl;
Automovil1.set_ruedas(4);
Automovil1.set_pasajeros(6);
Automovil1.set_tipo(tipo::deportivo);
Automovil1.Mostrar();
return 0;
}
59
Capacidad de carga en pies cúbicos: 3200\
0.25cm]
ruedas: 6
pasajeros: 3
Capacidad de carga en pies cúbicos: 1200\
0.25cm]
ruedas: 4
pasajeros: 6
tipo: deportivo\
0.5cm]
Como este programa muestra, la mayor ventaja de la herencia es que
permite crear una clase base que puede ser incorporada en clases más especí-
cas. De esta manera, cada clase derivada puede ser precisamente ajustada
a las propias necesidades y aun siendo parte de la clasicación general. Por
otra parte, nótese que ambos, 'Camion' y 'Automovil', incluyen una función
miembro llamada 'Mostrar()', la cual muestra información sobre cada objeto.
Esto ilustra un aspecto del polimorsmo. Como cada función 'Mostrar()' esta
enlazada con su propia clase, el compilador puede fácilmente indicar cuál lla-
mar para cualquier objeto dado. Ahora que se ha visto los procedimientos
básicos por los cuáles una clase hereda de otra, se examinará la herencia en
detalle.
60
accesibles por miembros de la clase derivada. Por ejemplo, en el siguiente
programa, los miembros públicos de 'base' se convierten en miembros publics
de 'derivada'. Encima, son accesibles por otras partes del programa.
#include <iostream>
using namespace std;
class base {
int i, j;
public:
void set(int a, int b) { i = a; j = b; }
void mostrar() { cout << i << " " << j << "\n"; }
};
int k;
public:
derivada(int x) { k = x; }
void mostrar_k() { cout << k << "\n"; }
};
int main()
{
derivada obj(3);
return 0;
}
61
entonces 'derivada' no tuviera acceso a ellas, y el programa no compilaría.
RECUERDE: El especicador 'protected' le permite crear un miembro de la
clase que es accesible desde la jerarquía de la clase, pero de otra manera es
privado.
Cuando una clase derivada es usada como clase base para otra clase
derivada, entonces cualquier miembro protegido de la clase base inicial que
es heredado ( como public ) por la primera clase derivada puede ser heredado
nuevamente, como miembro protegido, por una segunda clase derivada. Por
ejemplo, el siguiente programa es correcto, y derivada2 tiene, de hecho, ac-
ceso a 'j' e 'i':
#include <iostream>
using namespace std;
class base {
protected:
int i, j;
public:
public:
public:
62
void setm() { m = i-j; } // legal
void mostrarm() { cout << m << "\n"; }
};
int main()
{
derivada1 obj1;
derivada2 obj2;
obj1.set(2, 3);
obj1.mostrar();
obj1.setk();
obj1.mostrark();
obj2.set(3, 4);
obj2.mostrar();
obj2.setk();
obj2.setm();
obj2.mostrark();
obj2.mostrarm();
return 0;
}
63
forma para esta clase, y no son accesibles por miembros de la clase derivada.
Por ejemplo, en el siguiente programa, los miembros públicos de 'base' se
convierten en miembros publics de 'derivada'. Encima , son accesibles por
otras partes del programa.
#include <iostream>
using namespace std;
class base {
int i, j;
public:
void set(int a, int b) { i = a; j = b; }
void mostrar() { cout << i << " " << j << "\n"; }
};
int k;
public:
derivada(int x) { k = x; }
void mostrar_k() { cout << k << "\n"; }
};
int main()
{
derivada obj(3);
return 0;
}
Como set() y mostrar() son heredadas como 'public', ellas pueden ser
llamadas en un objeto del tipo 'derivada' desde main(). Como i y j son
especicadas como 'private', ellas se mantienen privadas a base.
64
El opuesto de herencia publica es herencia privada. Cuando la clase
base es heredad como privada, entonces todos los miembros públicos de la
clase base se convierten en miembros privados de la clase derivada. Por
ejemplo, el programa mostrado a continuación no compilara, porque set() y
mostrar() son ahora miembros privados de 'derivada', y por ende no pueden
ser llamados desde main().
class base {
int i, j;
public:
void set(int a, int b) { i = a; j = b; }
void mostrar() { cout << i << " " << j << "\n"; }
};
int k;
public:
derivada(int x) { k = x; }
void mostrar_k() { cout << k << "\n"; }
};
int main()
{
derivada obj(3);
65
return 0;
}
La clave a recordar es que cuando una clase base es heredada como 'pri-
vate', los miembros públicos de la clase base se convierten en miebros pri-
vados de la clase derivada. Esto signica que aun ellos son accesibles por
miembros de la clase derivada, pero no pueden ser accedidos por otras partes
de su programa.
#include <iostream>
class base {
protected:
66
int i, j; // privados a base, pero accesibles a derivada.
public:
void set(int a, int b) { i = a; j = b; }
void mostrar() { cout << i << " " << j << "\n"; }
};
int k;
public:
// derivada puede accesar en base a 'j' e 'i'
void setk() { k = i * j; }
void mostrark() { cout << k << "\n"; }
};
int main()
{
derivada obj;
obj.setk();
obj.mostrark();
return 0;
}
67
nuevamente , como miembro protegido, por una segunda clase derivada.
Por ejemplo, el siguiente programa es correcto, y derivada2 tiene, de hecho,
acceso a 'j' e 'i':
#include <iostream>
using namespace std;
class base {
protected:
int i, j;
public:
public:
public:
void setm() { m = i-j; } // legal
void mostrarm() { cout << m << "\n"; }
};
int main()
{
68
derivada1 obj1;
derivada2 obj2;
obj1.set(2, 3);
obj1.mostrar();
obj1.setk();
obj1.mostrark();
obj2.set(3, 4);
obj2.mostrar();
obj2.setk();
obj2.setm();
obj2.mostrark();
obj2.mostrarm();
return 0;
}
class base {
protected:
int i, j;
public:
69
// Ahora todos los elementos de base son privados en derivada1.
class derivada1 : private base {
int k;
public:
// Esto es legal porque j e i son privadas a derivada1
void setk() { k = i*j; } // OK
void mostrark() { cout << k << "\n"; }
};
public:
// Ilegal porque j e i son privadas a derivada1
setm() { m = j-i; } // error
void mostrarm() { cout << m << "\n"; }
};
int main()
{
derivada1 obj1;
derivada2 obj2;
return 0;
}
70
permite ser heredadas.
El especicador 'protected' puede ser usado con estructuras. Sin em-
bargo no puede ser usado con una 'union' porque la union no puede heredar
otra clase o ser heredad. ( Algunos compiladores aceptaran su uso en una
declaracion en una union, pero como las uniones no pueden participar en la
herencia, 'protected' es lo mismo que 'private' en este contexto.)
El especicador 'protected' puede ocurrir en cualquier lugar en la declara-
cion de una clase, aunque tipicamente ocurre despues de ( por defecto ) que
los miembros privados son declarados, y antes de los miebros publicos. Ade-
mas, la mayor forma completa de la declaraciond de una clase es
class base {
int i;
protected:
int j;
public:
int k;
void seti(int a) { i = a; }
int geti() { return i; }
};
public:
71
void setj(int a) { j = a; }; // j es protected aqui.
void setk(int a) { k = a; }; // k es tambien protected.
int getj() { return j; }
int getk() { return k; }
};
int main()
{
derivada obj;
return 0;
}
Como puede ver leyendo los comentarios en este programa, k,j, seti()
y geti() en 'base' se convierten miembros 'protected' en 'derivada'. Esto
signica que ellos no pueden ser accesados por codigo fuera de 'derivada'.
Ademas, dentro de main(), referencias a estos miembros a traves de obj son
ilegales.
72
declarado como 'private', este puede ser accedido solo por miembros de su
clase. Además, clases derivadas no tienen acceso a miembros privados de la
clase base. Cuando un miembro es declarado como 'protected', este puede
ser accedido solo por miembros de su clase, o por clases derivadas. Además,
protected permite que un miembro sea heredado, pero que se mantenga pri-
vado en la jerarquía de la clase.
Cuando una clase base es heredada por el uso de 'public', sus miembros
publicos se convierten en miebros publicos de la clase derivada, y sus miem-
bros protected se convierten en miembros protected de la clase derivada.
Cuando una clase base es heredada por el uso de 'protected', sus miem-
bros publicos y protegidos se convierten en miembros protected de la clase
derivada.
Cuando una clase base es heredada por el uso de 'private', sus miembros
publicos y protegidos se convierten en miebros private de la clase derivada.
En todos los casos, los miembros privados de la clase base se mantienen
privados a la clase base, y no son heredados.
class base1 {
protected:
int x;
int m;
public:
void showx() { cout << x << "\n"; }
};
class base2 {
protected:
int y;
public:
73
void showy() { cout << y << "\n"; }
};
public:
void set(int i, int j) { x= i; y = j; };
};
int main()
{
derivada obj;
return 0;
}
Como este ejemplo ilustra, para causar que mas de una clase base sea
heredad, debe usarse una lista separada por comas. Ademas, asegurese de
usar un especicador de acceso para cada clase heredada.
#include <iostream>
using namespace std;
class base {
74
public:
base() { cout << "Construyendo base\n"; }
~base() { cout << "Destruyendo base\n"; }
};
int main()
{
derivada obj;
return 0;
}
75
den inverso a su derivación. Ya que la clase base contiene una clase derivada,
la destruccion de una clase base implica la destruccion de su clase derivada.
Además, el constructor derivado debe ser llamado antes de que el objeto sea
completamente destruido.
En el caso de una gran jerarquía de clases ( ej: cuando una clase derivada
se convierta en la base clase de otra clase derivada ), la regla general se aplica:
Los constructores son llamados en orden de su derivacion, los destructores
son llamados en orden inverso. Por ejemplo, este programa
#include <iostream>
using namespace std;
class base {
public:
public:
derivada1() { cout << "Construyendo derivada1\n"; }
~derivada1() { cout << "destruyendo derivada1\n"; }
};
public:
derivada2() { cout << "Construyendo derivada2\n"; }
~derivada2() { cout << "Destruyendo derivada2\n"; }
};
int main()
{
derivada2 obj;
76
return 0;
}
#include <iostream>
using namespace std;
class base1 {
public:
base1() { cout << "Construyendo base1\n"; }
~base1() { cout << "Desstruyendo base1\n"; }
};
class base2 {
public:
base2() { cout << "Construyendo base2\n"; }
~base2() { cout << "Destruyendo base2\n"; }
};
public:
derivada() { cout << "Construyendo derivada\n"; }
~derivada() { cout << "Destruyendo derivada\n"; }
};
int main()
{
derivada obj;
return 0;
}
77
18.17 PASANDO PARAMETROS A LOS CONSTRUCTORES
DE LA CLASE BASE
Hasta ahora, ninguno de los ejemplos anteriores han incluido constructores
que requieran argumentos. En casos donde solo el constructor de la clase
derivada requiera uno o mas argumentos, simplemente use la sintaxis es-
tandarizada de parametrizacion del constructor. Pero, como se le pasan
argumentos a un constructor en una clase base? La respuesta es usar una
forma expandida de la declaracion de la clase derivada, la cual pasa argu-
mentos entre uno o mas constructores de la clase base.
Aqui, desde base1 hasta baseN son los nombres de las clases base heredadas
por la clase derivada. Notese los dos puntos separando la declaracion del con-
structor de la clase derivada de las clases base, y que las clases base estan
separadas cada una de la otra por comas, en el caso de multiples clases base.
Considere este programa de ejemplo:
#include <iostream>
using namespace std;
class base {
protected:
int i;
public:
base(int x) { i = x; cout << "Construyendo base\n"; }
~base() { cout << "Destruyendo base\n"; }
};
public:
// derivada usa x, y es pasado en consjunto a base
derivada(int x, int y) : base(y)
{ j = x; cout << "Construyendo derivada\n"; }
78
};
int main()
{
derivada obj(3, 4);
obj.mostrar(); // muestra 4 3
return 0;
}
79