Вы находитесь на странице: 1из 79

Programación en C apuntes

Eduardo Alcaraz

May 19, 2017

Contents

1 Introducción

El objetivo principal es que cualquier persona sin conocimientos previos de


programación pueda ser capaz de programar en el lenguaje C. Una vez lo-
grado el dominio del lenguaje, es probable que los lectores se interesen por
otros temas más complejos que superen a los temas básicos. También les
será más o menos sencillo aprender cualquier otro lenguaje de programación
estructurada. Sin embargo, este no es un libro que apunte únicamente a
programadores principiantes.
También puede resultar de interés para quienes ya tengan experiencia en
el área de programación. En esta introducción hay dos secciones en las que
se explica para los dos grupos principales de lectores, qué camino seguir para
comenzar a programar en el lenguaje C o bien perfeccionar conocimientos.
El lenguaje C es tan usado porque es un lenguaje de programación que
emplea pocas instrucciones en lenguaje máquina para traducir elementos del
código. Esto reduce los tiempos de ejecución de los programas.

1.1 Secuencia de escape de la función printf en C


Una secuencia de escape siempre representa a un carácter del ASCII. Dichos
caracteres se pueden clasicar en:

ˆ Grácos (se corresponden con los símbolos más usados para escribir
por los humanos).

ˆ No grácos (representan a acciones, como por ejemplo, mover el cursor


de la pantalla al principio de la línea siguiente).

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

Numerosos lenguajes de programación implementan una función printf (print


formatted), para mostrar una cadena con formato. Esta, originaria del
lenguaje de programación C, tiene un prototipo similar al siguiente: Ejem-
plo

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

Ejemplos de declaraciones de Enteros


int a;
unsigned int a;
signed long a;
signed long long a = 10000000;

3.1 Tipo de Datos en C Flotantes


Se denomina otantes a los tipos de datos que representan a los números
reales, ya que utilizan un sistema de representación basado en la técnica de
coma otante, que permite operar con números reales de diversas magni-
tudes, mediante un número decimal llamado mantisa y un exponente que
indica el orden de magnitud.
El tipo de dato otante en lenguaje C sólo tiene dos tamaños: el oat y
el double, que son 4 bytes y 8 bytes respectivamente. Se los puede utilizar
tanto para representar números decimales, como para representar números
enteros con un orden de magnitud muy grande.\

0.5cm]

Ejemplo de declaraciones de Flotantes


float a;
double a = 1e23;
double a = 3.1416;
float a = 4e-9;
double a = -78;

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

4 Elementos básicos de un programa en C

Así como un edicio está hecho de ladrillos, un programa de C está hecho de


elementos básicos, como expresiones, instrucciones, bloques de instrucciones
y bloques de funciones.

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

En C, la función scanf() (scan-format, analizar con formato), en realidad


representa a una familia de funciones que analizan una entrada de datos con
formato y cargan el resultado en los argumentos que se pasan por referencia
a dicha función o funciones:
scanf
ˆ La función scanf() lee los datos de entrada en el stdin (ujo de entrada
estándar).

ˆ La función fscanf() (le-scanf ) lee en un ujo de entrada dado, por lo


general un chero (le) abierto para lectura.

ˆ La función sscanf() (string-scanf ) obtiene la entrada que se va a


analizar de una cadena de caracteres dada (string).

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

//Lectura de varios datos introducidos por teclado,


// de tipos int, float y char

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

//Lectura de una cadenas de caracteres


printf("Introduce una palabra: ");
scanf("%10s",cad); // lee maximo 10 caracteres y
//le concatena el caracter cero.

6 Entradas y salidas de caracteres

En la biblioteca estándar de C se denen las dos principales vías de comu-


nicación de un programa:

ˆ la entrada estándar ( stdin), y


ˆ stdout).
la salida estándar (

Generalmente están ambas asociadas a nuestro terminal, de manera que


cuando se imprimen datos en la salida estándar los caracteres aparecen en el
terminal, y cuando leemos caracteres de la entrada estándar los leemos del
teclado del terminal. La entrada y salida estándar trabajan con caracteres
(en modo carácter), con datos o números binarios. Es decir, todos los datos
que enviemos a la salida estándar deben ser cadenas de caracteres. Por
ello para imprimir cualquier dato en la salida estándar primero deberemos
convertirlo en texto, es decir, en cadenas de caracteres. Sin embargo esto
lo haremos mediante las funciones de biblioteca, que se encargan de realizar
esta tarea ecientemente:

6.1 putchar y getchar


Comenzaremos con las dos funciones principales de salida y entrada de car-
acteres: putchar y getchar.
La función putchar escribe un único carácter en la salida estándar. Su
uso en sencillo y generalmente está implementada como una macro en la
cabecera de la biblioteca estándar. Por ejemplo:

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

En el código anterior putchar(32); muestra el espacio entre ambas pal-


abras (32 es el código ASCII del carácter espacio ' ') y putchar('$$'); imprime
un salto de línea tras el texto.
La función getchar devuelve el carácter que se halle en la entrada están-
dar. Esta función tiene dos particularidades. La primera es que aunque se
utiliza para obtener caracteres no devuelve un carácter, sino un entero. Esto
se hace así ya que con un entero podemos representar tanto el conjunto de
caracteres que cabe en el tipo carácter (normalmente el conjunto ASCII de
caracteres) como el carácter EOF de n de chero. En UNIX es habitual
representar los caracteres usando el código ASCII, tanto en su versión de 7
bits como en su versión ampliada a 8 bits. Estos caracteres se suelen repre-
sentar como un entero que va del 0 al 127 o 256. El carácter EOF entonces es
representado con un -1. Además esto también lo aplicaremos cuando leamos
los cheros binarios byte a byte. Un ejemplo:

8
#include <stdio.h>

int main()
{
int c;

c = getchar(); /* Notese que getchar() no devuelve nada


hasta que se presiona ENTER */

putchar(c);
return 0;
}

Aquí se almacena en el entero c el carácter pulsado en el teclado. Poste-


riormente se muestra con putchar.

6.2 puts y gets


La función puts simplemente imprime una cadena de caracteres en la salida
estándar (y produce un salto de línea). Le debemos proporcionar la dirección
donde encontrar la cadena de caracteres. El código:

#include <stdio.h>

int main()
{
produce el resultado

Bienvenido a la programacin
en lenguaje C

La función gets simplemente toma una cadena de caracteres de la en-


trada estándar (cuya introducción es preciso terminar con un ENTER) y la
almacena en una variable string. Supongamos este código:

#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

Esta función nos permite introducir frases enteras, incluyendo espacios.


Algunos detalles más; la sintaxis es:

char *gets(char *buffer);

Si se ha almacenado algún carácter en buer le añade un '\0' al nal y


devuelve un puntero a su dirección. Si no se ha almacenado ninguno (error
o carácter n-de-chero, CTRL+Z) devuelve un puntero NULL.

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

Un apuntador es una variable que contiene una dirección de memoria. Esta


dirección puede ser la posición de otra variable en la memoria. Por ejem-
plo:

int edad *aedad;


edad=26;
aedad=&edad;
Ejemplo

8 Declaración de un apuntador

La declaración de un apuntador como ya vimos en el ejemplo se hace:


Ejemplo
tipo *nombre de la variable

cSe agrega * como prejo al declarar una variable de tipo apuntador.


Operadores de apuntador.
ˆ & Operador de dirección o referencia. Devuelve la dirección de
memoria de la variable.

ˆ * Operador de indirección o "desreferencia". Devuelve el valor situado


en la dirección del operador.

11
9 Apuntadores Ejemplos

Ejemplos. Supongamos un apuntador p y dos variables c y d de tipo char.


Char *p, c, d;

9.1 Ejemplo
ˆ p=&c; Se asigna la dirección de c a la variable apuntador p

ˆ d=*p; Asigna el contenido de c (al que apunta p) a la variable d

10 Apuntadores Ejemplos

Un pequeño programa de ejemplo: Inicializa una variable entera i con el valor


de 100, posteriormente se asigna la dirección de i al apuntador pi. Después
la variable val recibe el contenido de lo apuntado por pi (es decir 100) y
nalmente se despliega el contenido de val.

10.1 Ejemplo
#include <stdio.h>
void main(){
int *pi, i, val;
i=100;
pi=&i;
val=*pi;
printf("%d", val);
}

11 Operadores Aritmeticos para apuntadores.

Las únicas operaciones que se pueden realizar con variables de apuntador


son la suma y la resta, de manera que los operadores válidos son:

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.

12 Tipo abstractos de datos

Un tipo abstracto de datos ( TAD) es un tipo de dato denido por el pro-


gramador que se puede manipular de un modo similar a los tipos de datos
denidos por el sistema. Al igual que los tipo denidos por el sistema, un
tipo de dato abstracto corresponde con un conjunto (puede ser de tamaño
indenido) de valores legales de datos y un número de operaciones primitivas
que se pueden realizar sobre esos valores.\

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:

ˆ Exponer una denición del tipo.

ˆ Hacer disponible un conjunto de operaciones que se pueden utilizar


para manipular instancias de ese tipo.

13
ˆ Proteger los datos asociados con el tipo de modo que sólo se puede
actuar sobre ellos con las rutinas proporcionadas.

ˆ Hacer instancia múltiples del tipo.

Las características del lenguaje C que va a permitir un TAD son, prin-


cipalmente, las estructuras o registros para representación de los datos, y las
funciones para representar las operaciones especicadas.

Una estructura es una agrupación de campos o variables de cualquier


tipo predenido. Permite que un grupo de variables que tienen una cierta
relación lógica sean tratadas como un todo. Por ejemplo, un punto en el
espacio tridimensional se caracteriza por tres variables que representan a las
respectivas coordenadas.

struct punto3D
{
float x;
float y;
float z;
};

13 Sentencia typedef

La Sentencia typedef permite dar un nombre a un tipo de dato ya denido,


de tal forma que a partir de ella el programador se puede referir al tipo de
dato con el nuevo nombre.
Estructura typedef
typedef tipo_predefinido nuevo_nombre;
En los programas C, y en particular al declarar estructura, la sentencia
typedef se utiliza conjuntamente con la Declaración del tipo Estructura. Así,
para estructura que representa coordenadas tridimensionales.

13.1 Ejemplo typedef


typedef struct
float x;
float y;
float z;
} punto3D;

14
Se a declarado el nuevo tipo punto3D, que permitirá denir variables o
declarar el tipo de una función.

13.2 Ejemplo typedef


punto3D p1, p2;
pinto3D desplazamiento(punto3D, float)

14 Qué es una estructura

En C, una estructura reúne variables de diferentes elementos de datos de


manera que se le puede hacer referencia como a una sola unidad.\

0.5cm]

Hay varias diferencias importantes entre un arreglo y una estructura.


Además de que los elementos de datos es una estructura pueden tener dis-
tintos tipos, cada elemento de datos tiene su propio nombre en vez del valor
de un índice. De hecho, los elementos de datos de una estructura se llaman
miembros de la estructura.

14.1 Ejemplo de Estructura


struct nombrestructura{
tipo_de_dato variable;
tipo_de_dato variable;
tipo_de_dato variable;
};

15 Estructura

15.1 Ejemplo de Estructura


struct auto{
int anio;
char modelo[8];
int potencia_del_motor;
float peso;
};

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.

15.2 Ejemplos denión/declaración


struct infoLibro
{
char titulo[60];
char autor[30];
char editorial[30];
int anyo;
};

La denición de la estructura se puede hacer así:

struct infoLibro
{
char titulo[60];
char autor[30];
char editorial[30];
int anyo;
}libro1, libro2, libro3;

15.3 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.
Para manejar los datos de más gente necesitamos declarar arrays de es-
tructuras.

struct estructura_amigo amigo[ELEMENTOS];


Ahora necesitamos saber cómo acceder a cada elemento del array. La
variable denida es amigo; por tanto, para acceder al primer elemento usare-
mos amigo[0], y a su nombre: amigo[0].nombre. Veámoslo con un ejemplo
en el que se supone que tenemos que introducir los datos de tres amigos:

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;

for( num_amigo=0; num_amigo<ELEMENTOS; num_amigo++ ) {


printf( "\nDatos del amigo numero %i:\n", num_amigo+1 );

printf( "Nombre: " );


gets(amigo[num_amigo].nombre);
printf( "Apellido: " );
gets(amigo[num_amigo].apellido);
printf( "Telefono: " );
gets(amigo[num_amigo].telefono);
printf( "Edad: " );
scanf( "%i", &amigo[num_amigo].edad );

while(getchar()!= '\n'); /* Vacia el buffer de entrada */


}
/* Impresion de los datos */
for( num_amigo=0; num_amigo<ELEMENTOS; num_amigo++ ) {
printf( "Mi amigo %s ", amigo[num_amigo].nombre );
printf( "%s tiene ", amigo[num_amigo].apellido );
printf( "%i anos ", amigo[num_amigo].edad );
printf( "y su telefono es el %s.\n" , amigo[num_amigo].telefono );

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

struct estructura_amigo amigo = {


"Juanjo",
"Lopez",
"983403367",
30
};

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


Por lo tanto el nombre ("Juanjo") debe ser una cadena de no más de 29 car-
acteres (recordemos que hay que reservar un espacio para el símbolo '\0'),
el apellido ("López") una cadena de menos de 39, el teléfono una de 9 y la
edad debe ser de tipo int.

Vamos a ver la inicialización de estructuras en acción:

#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
};

main()
{

18
printf( "%s tiene ", amigo.apellido );
printf( "%i anos ", amigo.edad );
printf( "y su telefono es el %s.\n" , amigo.telefono );
}

También se puede inicializar un array de estructuras de la forma siguiente:

struct estructura_amigo amigo[] = {


"Juanjo", "Lopez", "983403367", 30,
"Marcos", "Ramos", "983427890", 42,
"Ana", "Martz", "983254789", 20
};
En este ejemplo cada línea es un registro. Como sucedía en los arrays,
si asignamos valores iniciales al array de estructuras no hace falta indicar
cuántos elementos va a tener. En este caso la matriz tiene 3 elementos, que
son los que le hemos introducido.

15.5 Punteros a estructura


También se pueden usar punteros con estructuras. Antes de nada, hay que
denir la estructura de igual forma que hacíamos antes. La diferencia está
en que al declarar la variable de tipo estructura debemos anteponerle el
operador '*' para indicarle que es un puntero.
Veamos 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",
"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;

/* Introducimosc los datos mediante punteros */


printf("Nombre: ");

20
gets(p_amigo->nombre);
printf("Apellido: ");
gets(p_amigo->apellido);
printf("Edad: ");
scanf( "%i", &p_amigo->edad );

/* Mostramos los datos */


printf( "Mi amigo %s ", p_amigo->nombre );
printf( "%s tiene ", p_amigo->apellido );
printf( "%i anos.\n", p_amigo->edad );
return 0;
}
NOTA: p_amigo es un puntero que apunta a la estructura amigo. Sin
embargo, $p\amigo $->edad es una variable de tipo int. Por eso al usar el
scanf debemos anteponer &.

15.6 Punteros a arrays de estructuras


Por supuesto, también se pueden usar punteros con arrays de estructuras.
La forma de trabajar es la misma, aunque hay que asegurarse de que el
puntero inicialmente apunte al primer elemento, después saltar al siguiente,
etc., hasta llegar al último.

#include <stdio.h>

#define ELEMENTOS 3

struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};

struct estructura_amigo amigo[] = {


"Juanjo", "Lopez", "983403367", 30,
"Marcos", "Ramos", "983427890", 42,
"Ana", "Martinez", "983254789", 20
};

21
int main()
{
struct estructura_amigo *p_amigo;
int num_amigo;

p_amigo = amigo; /* apunta al primer elemento del array */

/* Imprimimos los datos */


for( num_amigo=0; num_amigo<ELEMENTOS; num_amigo++ ) {

printf( "Mi amigo %s ", p_amigo->nombre );


printf( "%s tiene ", p_amigo->apellido );
printf( "%i anos ", p_amigo->edad );
printf( "y su telefono es el %s.\n" , p_amigo->telefono );
/* saltamos al siguiente elemento */
p_amigo++;
}
return 0;
}

En vez de p_amigo = amigo; se podía usar la forma pa migo = \amigo[0],


es decir, que apunte al primer elemento (el elemento 0) del array. La primera
forma es más frecuente pero la segunda, quizás, indica más claramente al
principiante lo que se pretende.
Ahora veamos el ejemplo anterior de cómo introducir datos en un array
de estructuras mediante punteros:

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

p_amigo = amigo; /* apunta al primer elemento del array */

/* Introducimos los datos mediante punteros */


for( num_amigo=0; num_amigo<ELEMENTOS; num_amigo++ ) {
printf( "\nDatos del amigo %i:\n", num_amigo+1 );

printf( "Nombre: " );


gets(amigo[num_amigo].nombre);
printf( "Apellido: " );
gets(amigo[num_amigo].apellido);
printf( "Telefono: " );
gets(amigo[num_amigo].telefono);
printf( "Edad: " );
scanf( "%i", &amigo[num_amigo].edad );

while(getchar()!= '\n'); /* Vacia el buffer de entrada */

p_amigo++; /* Siguiente elemento */

}
/* Imprimimos los datos */
p_amigo = amigo;

for( num_amigo=0; num_amigo<ELEMENTOS; num_amigo++ ) {

printf( "Mi amigo %s ", p_amigo->nombre );


printf( "%s tiene ", p_amigo->apellido );
printf( "%i anos ", p_amigo->edad );
printf( "y su telefono es el %s.\n" , p_amigo->telefono );
p_amigo++;
}
return 0;
}

Es importante no olvidar que al terminar el primer bucle for el puntero


p_amigo apunta al último elemento del array de estructuras. Para mostrar
los datos debemos hacer que vuelva a apuntar al primer elemento, por eso

23
usamos de nuevo p_amigo = amigo;.

15.7 Paso de estructura a funciones


Las estructuras se pueden pasar directamente a una función igual que se
hace con las variables. Por supuesto, en la denición de la función de debe
indicar el tipo de argumento que usamos:

tipo nombre_funcion ( struct nombre_de_la_estructura nombre_de_la


variable_estructura )

En el ejemplo siguiente se usa una función llamada suma que calcula


cual será la edad 20 años más tarde (simplemente suma 20 a la edad). Esta
función toma como argumento la variable estructura arg amigo . Cuando se
ejecuta el programa llamamos a suma desde main y en esta variable se copia
el contenido de la variable amigo.

#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
};

int suma( struct estructura_amigo );

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
}

int suma( struct estructura_amigo arg_amigo )


{
return arg_amigo.edad + 20;
}

Si dentro de la función suma hubiésemos cambiado algún valor de la estruc-


tura, dado que es una copia, no hubiera afectado a la variable amigo de main.
Es decir, si dentro de suma hacemos, por ejemplo, arg _amigo_amigo.edad
= 20; el valor de arg _amigo cambiará, pero el de amigo seguirá siendo 30.
También se pueden pasar estructuras mediante punteros o se puede pasar
simplemente un miembro (o campo) de la estructura. Si usamos punteros
para pasar estructuras como argumentos habrá que hacer unos cambios al
código anterior (en negrita):

#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
};

int suma( struct estructura_amigo * );

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

Lo primero será indicar a la función suma que va a recibir es puntero,


por lo cual ponemos * a arg _amigo. Segundo, como dentro de la función
suma usamos un puntero a estructura y no una variable estructura, debemos
cambiar el '.' por el '->'. Tercero, dentro de main, cuando llamamos a suma,
debemos pasar la dirección de amigo, no su valor, por lo tanto debemos an-
teponer '\'.

Si usamos punteros a estructuras corremos el riesgo (o tenemos la ventaja,


según cómo se mire) de poder cambiar los datos de la estructura de la variable
amigo de main.

15.8 Paso de miembro de una estructura a funciones


Otra posibilidad es no pasar toda la estructura a la función, sino tan sólo
los miembros que sean necesarios. El ejemplo anterior sería más correcto
usando esta tercera opción, ya que sólo usamos el miembro edad:

int suma( int );

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

int suma( int edad )


{
return edad + 20;
}

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

El estándar de C contiene varias funciones para la edición de archivos, estas


están denidas en la cabecera stdio.h y por lo general empiezan con la letra
f, haciendo referencia a le. Adicionalmente se agrega un tipo FILE, el cual
se usará como apuntador a la información del archivo.
La secuencia que usaremos para realizar operaciones será la siguiente:

ˆ Crear un apuntador del tipo FILE *

ˆ Abrir el archivo utilizando la función fopen y asignándole el resultado


de la llamada a nuestro apuntador.

ˆ Hacer las diversas operaciones (lectura, escritura, etc).

ˆ Cerrar el archivo utilizando la función fclose.

16.1 fopen
Esta función sirve para abrir y crear archivos en disco. El prototipo corre-
spondiente de fopen es:

FILE * fopen (const char *filename, const char *opentype);


Los parámetros de entrada de fopen son:
lename: una cadena que contiene un nombre de archivo válido. open-
type: especica el tipo de archivo que se abrirá o se creará.
Una lista de parámetros opentype para la función fopen son:

ˆ "r" : abrir un archivo para lectura, el archivo debe existir.

ˆ "w" : abrir un archivo para escritura, se crea si no existe o se sobree-


scribe si existe.

ˆ "a" : abrir un archivo para escritura al nal del contenido, si no existe


se crea.

28
ˆ "r+" : abrir un archivo para lectura y escritura, el archivo debe existir.

ˆ "w+" : crear un archivo para lectura y escritura, se crea si no existe o


se sobreescribe si existe.

ˆ "r+b ó rb+" : Abre un archivo en modo binario para actualización


(lectura y escritura).

ˆ "rb" : Abre un archivo en modo binario para lectura.

Adicionalmente hay tipos utilizando "b" (binary) los cuales no serán


mostrados por ahora y que solo se usan en los sistemas operativos que no
pertenecen a la familia de unix.

16.2 fclose
Esta función sirve para poder cerrar un archivo que se ha abierto.
El prototipo correspondiente de fclose es:

int fclose (FILE *stream);

Un valor de retorno cero indica que el archivo ha sido correctamente


cerrado, si ha habido algún error, el valor de retorno es la constante EOF.
Un ejemplo pequeño para abrir y cerrar el archivo llamado archivo.in en
modo lectura:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv)


{
FILE *fp;
fp = fopen ( "archivo.in", "r" );
if (fp==NULL) {fputs ("File error",stderr); exit (1);}
fclose ( fp );

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:

int feof(FILE *archivo);


Literalmente signica "rebobinar", sitúa el cursor de lectura/escritura al
principio del archivo.
El prototipo correspondiente de rewind es:

void rewind(FILE *archivo);

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:

ˆ char fgetc(FILE *archivo)

ˆ char *fgets(char *buer, int tamano, FILE *archivo)

ˆ t t t
size fread(void *puntero, size tamano, size cantidad, FILE *archivo);

ˆ int fscanf(FILE *archivo, const char *formato, argumento, . . . );

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:

char fgetc(FILE *archivo);

Esta función se usa generalmente para recorrer archivos de texto. A


manera de ejemplo vamos a suponer que tenemos un archivo de texto llamado
"prueba.txt" en el mismo directorio en que se encuentra el fuente de nuestro
programa. Un pequeño programa que lea ese archivo será:

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

char *fgets(char *buffer, int tamao, FILE *archivo);

El primer parámetro buer lo hemos llamado así porque es un puntero


a un espacio de memoria del tipo char (podríamos usar un arreglo de char).
El segundo parámetro es tamaño que es el limite en cantidad de caracteres a
leer para la funcion fgets. Y por ultimo el puntero del archivo por supuesto
que es la forma en que fgets sabra a que archivo debe leer.

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *archivo;

char caracteres[100];

archivo = fopen("prueba.txt","r");

if (archivo == NULL)
exit(1);

printf("\nEl contenido del archivo de prueba es \n\n");


while (feof(archivo) == 0)
{
fgets(caracteres,100,archivo);
printf("%s",caracteres);
}
system("PAUSE");

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

Esta función lee un bloque de una "stream" de datos. Efectúa la lectura


de un arreglo de elementos "count", cada uno de los cuales tiene un tamaño
denido por "size". Luego los guarda en el bloque de memoria especicado
por "ptr". El indicador de posición de la cadena de caracteres avanza hasta
leer la totalidad de bytes. Si esto es exitoso la cantidad de bytes leídos es
(size*count).
PARAMETROS:

ˆ ptr : Puntero a un bloque de memoria con un tamaño mínimo de


(size*count) bytes.

ˆ size : Tamaño en bytes de cada elemento (de los que voy a leer).

ˆ count : Número de elementos, los cuales tienen un tamaño "size".

ˆ stream: Puntero a objetos FILE, que especica la cadena de entrada.

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:

int fscanf(FILE *archivo, const char *formato, argumento, ...);

Podemos ver un ejemplo de su uso, abrimos el documento "archivo.txt"


en modo lectura y leyendo dentro de el.

33
#include <stdio.h>

int main ( int argc, char **argv )


{
FILE *fp;

char buffer[100];

fp = fopen ( "archivo.txt", "r" );

fscanf(fp, "%s" ,buffer);


printf("%s",buffer);

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:

ˆ int fputc(int caracter, FILE *archivo)

ˆ int fputs(const char *buer, FILE *archivo)

ˆ t t t
size fwrite(void *puntero, size tamano, size cantidad, FILE *archivo);

ˆ int fprintf(FILE *archivo, const char *formato, argumento, . . . );

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:

int fputc(int carcter, FILE *archivo);

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>

int main ( int argc, char **argv )


{
FILE *fp;

char caracter;

fp = fopen ( "archivo.txt", "a+t" ); //parametro para escritura al final y para

printf("\nIntroduce un texto al archivo: ");

while((caracter = getchar()) != '\n')


{
printf("%c", fputc(caracter, fp));
}

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:

int fputs(const char *buffer, FILE *archivo)

para ver su funcionamiento mostramos el siguiente ejemplo:

#include <stdio.h>

int main ( int argc, char **argv )

35
{
FILE *fp;

char cadena[] = "Mostrando el uso de fputs en un archivo.\n";

fp = fopen ( "archivo.txt", "r+" );

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:

size_t fwrite(void *puntero, size_t tamano, size_t cantidad, FILE *archivo);

Un ejemplo concreto del uso de fwrite con su contraparte fread y usando


funciones es:

#include <stdio.h>

int main ( int argc, char **argv )


{
FILE *fp;

char cadena[] = "Mostrando el uso de fwrite en un archivo.\n";

fp = fopen ( "archivo.txt", "r+" );

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:

int fprintf(FILE *archivo, const char *formato, argumento, ...);


Podemos ver un ejemplo de su uso, abrimos el documento "archivo.txt"
en modo lectura/escritura y escribimos dentro de el.

#include <stdio.h>

int main ( int argc, char **argv )


{
FILE *fp;

char buffer[100] = "Esto es un texto dentro del archivo.";

fp = fopen ( "archivo.txt", "r+" );

fprintf(fp, buffer);
fprintf(fp, "%s", "\nEsto es otro texto dentro del archivo.");

fclose ( fp );

return 0;
}

17 Ámbitos

Llamamos ámbito a la zona desde que cierto objeto es accesible. En C++


solemos referirnos a dos tipos de ámbitos: temporal y de acceso. Así, el
ámbito temporal indica el intervalo de tiempo en el que un objeto existe

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.

17.1 Ámbitos de las variables


Dependiendo de dónde se declaren las variables, podrán o no ser accesibles
desde distintas partes del programa. Es decir, su ámbito de acceso y temporal
dependerá del lugar en que se declaren. Las variables declaradas dentro de
un bucle, serán accesibles sólo desde el propio bucle , esto es, tendrán un
ámbito local para el bucle. Esto es porque las variables se crean al inciar el
bucle y se destruyen cuando termina. Evidentemente, una variable que ha
sido destruida no puede ser accedida, por lo tanto, el ámbito de acceso está
limitado por el ámbito temporal.
Nota: En compiladores de C++ antiguos, (y en algunos modernos y mal
implementados), no existe este ámbito, que sin embargo está descrito en la
norma ANSI. En estos compiladores, las variables declaradas dentro de un
bucle tienen el mismo ámbito temporal y de acceso que las variables locales.
Es decir, existen y son accesibles desde el punto en que se declaren hasta el
nal de la función. Si usamos uno de esos compiladores no será posible, por
ejemplo, usar varios bucles con declaraciones de variables locales de bucle
con el mismo nombre.

for(int i=0; i < 100; i++) HacerAlgo(i);


for(int i=0; i > -100; i--) DeshacerAlgo(i);

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 EnteroGlobal=10; // Declaracion de una variable global


int Funcion1(int a); // Declaracion de un prototipo

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

17.2 Enmascaramiento de variables


Generalmente no es posible, y no suele ser necesario, declarar dos variables
con el mismo nombre, pero hay condiciones bajo las cuales es posible hacerlo.
Por ejemplo, podemos declarar una variable global con un nombre determi-
nado, y declarar otra variable (del mismo tipo o de otro diferente) de forma
local en una función, usando el mismo nombre. En ese caso decimos que la
segunda declaración (la local), enmascara a la primera (la global). Con eso
queremos decir que el acceso a la variable global está bloqueado o enmas-
carado por la local, que es a la única que podemos acceder directamente.

Ejemplo

int x;

int main() {
int x;

x = 10;
return 0;
}

En este programa, cuando asignamos 10 a x estamos accediendo a la


versión local de la variable x. En la función main, la variable global x está
enmascarada, y no puede accederse a ella directamente. Del mismo modo,
una variable de ámbito de bucle o de ámbito de bloque puede enmascarar a
una variable global o local, o a una de un bloque o bucle más externo:

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.

17.3 Operador de ámbito


Existe un método para acceder a una variable global enmascarada por una
variable local. Se trata del operador de ámbito, que consiste en dos caracteres
de dos puntos seguidos (::). Veremos este operador con más detalle en el
capítulo dedicado a los espacios con nombre, pero veamos ahora cómo lo
podemos usar para acceder a una variable global enmascarada:

int x; // Variable global

int main()
{
int x; // Variable local que enmascara a la global

x = 10; // Accedemos a la variable local


::x = 100; // Mediante el operador de ito accedemos a la global
return 0;
}
El operador de ámbito, usado de este modo, permite acceder al espacio
de variables global. Pero este no es más que un uso restringido del operador,
que tiene muchas más aplicaciones.

18 Fundamento de Clases

18.1 Declaración de una Clase


Ahora va a empezar un pequeño bombardeo de nuevas palabras reservadas
de C++, pero no te asustes, no es tan complicado como parece. La primera
palabra que aparece es lógicamente class que sirve para denir una clase y
para declarar objetos de esa clase. Su uso es parecido a la ya conocida
struct:

class <identificador de clase> [<:lista de clases base>] {


<lista de miembros>
} [<lista de identificadores de objetos>];

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

void pareja::Lee(int &a2, int &b2) {


a2 = a;
b2 = b;
}

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

Nuestra clase "pareja" tiene dos miembros de tipo de datos: a y b. Y dos


funciones, una para leer esos valores y otra para modicarlos. En el caso
de la función "Lee" la hemos declarado en el interior de la clase y denido
fuera, observa que en el exterior de la declaración de la clase tenemos que
usar la expresión:

void pareja::Lee(int &a2, int &b2)

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.

// Esto define la clase CRender


class CRender {
char buffer[256];
public:
void m_Renderizar();
};
Veamos más de cerca esta declaración de la clase.
Todos los miembros de CRender son declarados dentro de la declaración
'class'. La variables miembro de CRender es buer. La función miembro es
m Renderizar .
NOTA: Por defecto los miembros de una clase son privados.
Una clase puede contener tanto miembros privados como públicos. Por
defecto, todos los elementos denidos en una clase son privados. Por ejemplo
la variable buer es privada. Esto signica que sólo pueden acceder a ella
otros miembros de la clase CRender, cosa que no podrá hacer ninguna otra
parte del programa. Es una forma de lograr la encapsulación: se puede
controlar el acceso a ciertos elementos de datos manteniéndolos privados.
Aunque no hay ninguna en este ejemplo, se pueden denir funciones privadas,
las cuales pueden ser llamadas solamente por otros miembros de la clase.
Para hacer pública una parte de la clase ( accesible a otras partes del
programa ), se debe declarar con la palabra clave public. Todas las variables
o funciones denidas después de la declaración pública son accesibles por
todas las demás funciones en el programa. En nuestra clase CRender, la
función m Renderizar () es pública. Típicamente, su programa accederá a los
miembros privados de una clase a través de sus funciones públicas. Note
que la palabra clave public es seguida con : . Mantenga en mente que un
objeto forma una relación entre código y datos. Una función miembro tiene
acceso a los elementos privados de su clase. Esto signica que m Renderizar
tiene acceso a buer en nuestro ejemplo. Para añadir una función miembro
a la clase, debe especicar su prototipo en la denición de la misma.
Una vez que se ha denido una clase, se puede crear un objeto de ese
tipo usando el nombre de la clase. El nombre de la clase se convierte en

44
un especicador del nuevo tipo. Por ejemplo la siguiente declaración crea 2
objetos llamados render1 y render2 del tipo CRender.

CRender render1, render2;

Cuando un objeto de la clase es creado, éste tendrá su propia copia de las


variables miembros que contiene la clase. Esto signica que render1 y render2
tendrán su propia e independiente copia de buer. Los datos asociados con
render1 son distintos y separados de los datos asociados con render2.
Recordemos: En C++, una clase es un nuevo tipo de dato que puede ser
usado para crear objetos. Especícamente, una clase crea una consistencia
lógica que dene una relación entre sus miembros. Cuando se declara una
variable de una clase, se está creando un objeto. Un objeto tiene existencia
física, y es una instancia especíca de una clase. ( Esto es, un objeto ocupa
espacio de memoria, pero una denición de tipo no ). Además, cada objeto
de una clase tiene su propia copia de los datos denidos dentro de esa clase.
Dentro de la declaración de CRender, el prototipo de una función es
especicado. Ya que las funciones miembros son prototipadas dentro de la
denición de la clase, no necesitan ser prototipadas en otro lugar cualquiera.
Para implementar una función que es un miembro de una clase, debe
indicarle al compilador a cual clase pertenece la función calicando el nombre
de la función con el nombre de la clase. Por ejemplo, esta es una manera de
codicar la función m Renderizar ().
void CRender::m_Renderizar()
{
strcpy(buffer, "hola C++");
return;
}

18.3 Resolución del ámbito


El :: es llamado el operador de resolución de ámbito. Esencialmente le dice al
compilador que esta versión de m_Renderizar pertenece a la clase CRen-
der. Dicho de otra forma, :: declara que m_Renderizar lo se encuentra
en el ámbito de CRender. Varias clases diferentes pueden usar los mismos
nombres de función. El compilador sabe cuál función pertenece a cuál clase
y esto es posible por el operador de resolución de ámbito y el nombre de la
clase.

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

La invocación de objeto1.m_Renderizar() causa a m_Renderizar()


operar en los datos de la copia de objeto1. Mantenga en mente que objeto1
y objeto2 son 2 objetos eparados. Esto signica, por ejemplo, que inicializar
objeto1 no causa que objeto2 sea inicializado, La única relación que objeto1
tiene con objeto2 es que es un objeto del mismo tipo.
Cuando una función miembro llama a otra función miembro de la misma
clase, puede hacerlo directamente, sin usar un objeto y el operador '.' En
este caso, el compilador ya conoce en cuál objeto se está operando. Sola-
mente cuando una función miembro es llamada por código que se encuentra
fuera de la clase es cuando debe utilizarse el nombre del objeto y el operador
'.' Por la misma razón, una función miembro puede referirse directamente a
una variable miembro, pero código fuera de la clase debe referenciarse a la
variable a través de un objeto y el operador '.'

El programa siguiente muestra aquí todas las piezas juntas y detalles


perdidos, e ilustra la clase CRender.

#include <iostream>
#include <cstring>

using std::cout;
using std::endl;

// Esto define la clase CRender


class CRender {
public:
char buffer[256];
void m_Renderizar(const char *cadena);

46
};

/* implementar m_Renderizar() para la c;*/


void CRender::m_Renderizar(const char *cadena){
strcpy(buffer, cadena);//copia la cadena
return;
}

int main (int argc, char **argv){


// crear 2 objetos CRender
CRender render1, render2;

render1.m_Renderizar("Inicializando el objeto render1");


render2.m_Renderizar("Inicializando el objeto render2");

cout << "buffer en render1: ";


cout << render1.buffer << endl; // tenemos acceso a buffer ya que es publico.

cout << "buffer en render2: ";


cout << render2.buffer << endl;

return (0);
}

18.5 Miembros de una clase (métodos y atributos)


En el lenguaje coloquial de la programación orientada al objeto es común
escuchar términos tales como: métodos, atributos, herencia, polimorsmo,
etc. En esta sección nos encargaremos de hablar de los dos primeros.
{itemize}

Métodos: En comparación con la programación tradicional, un método es


lo mismo que una función cualquiera, salvo que como los métodos se declaran
para pertenecer a una clase especíca, se dice que todos los métodos de dicha
clase son miembros de la misma. Por lo demás, la declaración y denición
de los métodos es exactamente igual que declarar y denir cualquier otra
función.

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

18.6 Visibilidad de los miembros de una clase


Por visibilidad se entiende al acto de acceder a los miembros de una clase.
En este sentido, los miembros de una clase pueden ser: públicos, privados y
protegidos.

ˆ Un miembro público signica que el acceso al mismo puede darse dentro


del interior de la clase, dentro de una subclase, y desde un objeto
instanciado de cualquiera de estas. Por ejemplo, los miembros de la
clase CRender son accesibles dentro de la misma y podrán accederse
desde cualquier otra clase que se derive de CRender, así como desde
cualquier objeto instanciado de estas.

ˆ Un miembro privado signica que el acceso al mismo puede darse so-


lamente dentro del interior de la clase que lo posee. Normalmente, el
programador creador de una clase declara a los atributos de la clase
como privados y a los métodos como públicos, esto con la idea de que
el usuario de la clase no pueda tener acceso a los atributos sino es a
traves de los métodos denidos para el caso.

ˆ Un miembro protegido se comporta de manera parecida a un miembro


privado, salvo que estos son accesibles dentro de la clase que lo posee
y desde las clases derivadas, pero no desde los objetos instanciados a
raiz de dichas clases.

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

// implementacion de los metodos de la clase Pareja


//
double Pareja::getA() { return a; }
double Pareja::getB() { return b; }
void Pareja::setA(double n) { a = n; }
void Pareja::setB(double n) { b = 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.

Nota: Observe que la sintaxis para crear una subclase es:


class hija : [public | private] padre // \T1\textquestiondown Que significan public y p

49
{
...
};

Donde padre es la clase base e hija es la subclase.

class Suma : public Pareja


{
// atributos de Suma
double resultado;

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

18.9 Introduciendo la Herencia.


C++ soporta herencia permitiendo a una clase incorporar otra clase dentro
de su declaración. Antes de discutir los detalles y la teoría, se procede a
comenzar por un ejemplo de herencia. La siguiente clase, llamada 'Vehicu-
loRodante', dene muy ampliamente a vehículos que viajan por la carretera.
Este almacena el numero de ruedas que un vehículo tiene y el numero de
pasajeros que puede llevar.
Un regla sencilla para recordar esto es: "Una clase derivada hereda de
una clase base"

// Definicion de una clase base para vehiculos


class VehiculoRodante
{
public:
//CICLO DE VIDA
//En este lugar se situan los constructores, los destructores, y/o los constructores co

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

void set_ruedas(int 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;
};

Como 'Camion' hereda de 'VehiculoRodante', 'Camion' incluye todo de

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

class ClaseDerivada : acceso ClaseBase


{
//cuerpo de la nueva clase
}

Aquí, 'acceso' es opcional. Sin embargo, si se encuentra presente, debe


ser 'public', 'private', o 'protected'. Se aprenderá más sobre estas opciones
más tarde, por ahora para todas las clases heredadas se usará el acceso
'public'. Usar public signica que todos los miembros públicos de la clase
base serán también miembros públicos de la clase derivada. Por tanto, en el
ejemplo anterior, miembros de la clase 'Camion' tienen acceso a los miembros
públicos de 'VehiculoRodante', justo como si ellos hubieran sido declarados
dentro de 'Camion'. Sin embargo, 'camion' no tiene acceso a los miembros
privados de 'VehiculoRodante'. Por ejemplo, 'Camion' no tiene acceso a
ruedas. He aquí un programa que usa herencia para crear dos subclases de
'VehiculoRodante'. Una es 'Camion' y la otra es 'Automovil'.

// Programa que demuestra la herencia.

// INCLUDES DE SISTEMA
//
#include <iostream>

// INCLUDES DEL PROYECTO


//

// INCLUDES LOCALES

53
//

// DECLARACIONES
//

// Definicion de una clase base para vehiculos


class VehiculoRodante
{
public:
// CICLO DE VIDA
/* 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

/*
* 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;
};

// Definicion de una clase 'Camion' derivada de la clase base 'VehiculoRodante'.


class Camion : public VehiculoRodante
{

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

enum tipo {deportivo, berlina, turismo};

// Definicion de una clase 'Automovil' derivada de la clase base 'VehiculoRodante'.


class Automovil : public VehiculoRodante
{
public:
// CICLO DE VIDA
/* 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_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'
*/

enum tipo get_tipo(void)


{
return this->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;
}

La salida de este programa se muestra a continuación:


ruedas: 18
pasajeros: 2

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.

18.10 Control de acceso de la clase base


Cuando una clase hereda de otra, los miembros de la clase base se convierten
en miembros de la clase derivada. El estado de acceso de los miembros de
la clase base dentro de la clase derivada es determinado por el especicador
de acceso usado para heredar la clase base. El especicador de acceso de
la clase base debe ser 'public', 'private' o 'protected'. Si el especicador de
acceso no es usado, entonces se usara private por defecto si la clase derivada
es una clase. Si la clase derivada es una 'struct' entonces 'public' es el acceso
por defecto por la ausencia de un expecicador de acceso explícito. Exam-
inemos las ramicaciones de usar accesos public o private. ( El especicador
'protected' se describe en la próxima sección.) Cuando una clase base es
heredada como 'public', todos los miembros públicos de la clase base se con-
vierten en miembros de la clase derivada. En todos los casos, los elementos
privados de la clase base se mantienen de esa forma para esta clase, y no son

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

class derivada : public base {

int k;

public:
derivada(int x) { k = x; }
void mostrar_k() { cout << k << "\n"; }
};

int main()
{
derivada obj(3);

obj.set(1, 2); // accesar a miembro de base


obj.mostrar(); // accesar a miembro de base

obj.mostrar_k(); // usa miembro de la clase derivada

return 0;
}

Aquí, porque 'base' es heredada por 'derivada' como pública, y porque


j e i son declaradas como protegidas, la función setk() en 'derivada' puede
acceder a ellas. Si j e i hubieran sido declaradas como privadas por 'base',

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:

void set(int a, int b) { i = a; j = b; }


void mostrar() { cout << i << " " << j << "\n"; }
};

// j e i se heredan como 'protected'


class derivada1 : public base {
int k;

public:

void setk() { k = i*j; } // legal


void mostrark() { cout << k << "\n"; }
};

// j e i se heredan indirectamente a traves de derivada1


class derivada2 : public derivada1 {
int m;

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

18.11 CONTROL DE ACCESO DE LA CLASE BASE.


Cuando una clase hereda de otra, los miembros de la clase base se convierten
en miembros de la clase derivada. El estado de acceso de los miembros de
la clase base dentro de la clase derivada es determinado por el especicador
de acceso usado para heredar la clase base. El especicador de acceso de
la clase base debe ser 'public', 'private' o 'protected'. Si el especicador de
acceso no es usado, entonces se usara private por defecto si la clase derivada
es una clase. Si la clase derivada es una 'struct' entonces 'public' es el acceso
por defecto por la ausencia de un expecicador de acceso explícito. Exam-
inemos las ramicaciones de usar accesos public o private . ( El especicador
'protected' se describe en la próxima sección.)
Cuando una clase base es heredada como 'public', todos los miembros
públicos de la clase base se convierten en miembros de la clase derivada. En
todos los casos, los elementos privados de la clase base se mantienen de esa

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

class derivada : public base {

int k;

public:
derivada(int x) { k = x; }
void mostrar_k() { cout << k << "\n"; }
};

int main()
{
derivada obj(3);

obj.set(1, 2); // accesar a miembro de base


obj.mostrar(); // accesar a miembro de base

obj.mostrar_k(); // usa miembro de la clase derivada

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

// Este programa no compilara.


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

// Miembros publicos de 'base' son privados en 'derivada'


class derivada : private base {

int k;

public:

derivada(int x) { k = x; }
void mostrar_k() { cout << k << "\n"; }
};

int main()
{

derivada obj(3);

obj.set(1, 2); // Error, no se puede acceder a set()


obj.mostrar(); // Error, no se puede acceder a mostrar()

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.

18.12 USANDO MIEMBROS PROTEGIDOS.


En adición a public y private, un miembro de la clase puede ser declarado
como protegido. Además, una clase base puede ser heredada como prote-
gida. Ambas de estas acciones son cumplidas por el especicador de acceso
'protected'. La palabra clave 'protected' esta incluida en C++ para proveer
gran exibilidad para el mecanismo de herencia.
Cuando un miembro de una clase es declarado como 'protected', ese
miembro, no es accesible a otros elementos no-miembros de la clase en el
programa. Con una importante excepción, el acceso a un miembro protegido
es lo mismo que el acceso a un miembro privado, este puede ser accedido solo
por otros miembros de la clase de la cual es parte. La única excepción a esta
regla es cuando un miembro protegido es heredado. En este caso, un miembro
protegido diere sustancialmente de uno privado.
Como debe conocer, un miembro privado de una clase base no es accesible
por cualquier otra parte de su programa, incluyendo cualquier clase derivada.
Sin embargo, los miembros protegidos se comportan diferente. Cuando una
clase base es heredada como publica, los miembros protegidos en la clase base
se convierten en miembros protegidos de la clase derivada, y 'son' accesibles a
la clase derivada. Además, usando 'protected' usted puede crear miebros de
clases que son privados para su clase, pero que aun asi pueden ser heredados
y accedidos por una clase derivada.
Considere este programa de ejemplo:

#include <iostream>

using namespace std;

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

class derivada : public base {

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.set(2, 3); // OK, conocido por derivada.


obj.mostrar(); // OK, conocido por derivada.

obj.setk();
obj.mostrark();

return 0;
}

Aquí, porque 'base' es heredada por 'derivada' como pública, y porque


j e i son declaradas como protegidas, la función setk() en 'derivada' puede
acceder a ellas. Si j e i hubieran sido declaradas como privadas por 'base',
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

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:

void set(int a, int b) { i = a; j = b; }


void mostrar() { cout << i << " " << j << "\n"; }
};

// j e i se heredan como 'protected'


class derivada1 : public base {
int k;

public:

void setk() { k = i*j; } // legal


void mostrark() { cout << k << "\n"; }
};

// j e i se heredan indirectamente a travde derivada1


class derivada2 : public derivada1 {
int m;

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

Cuando una clase base es heredada como 'private', miembros protegidos


de la clase base se convierten en miembros privados de la clase derivada.
Además, en el ejemplo anterior, si 'base' fuera heredada como 'private', en-
tonces todos los miembros de 'base' se hubieran vuelto miembros privados
de derivada1, signicando que ellos no podrían estar accesibles a derivada2.
( Sin embargo, j e i podrían aun ser accesibles a derivada1.) Esta situación
es ilustrada por el siguiente programa, el cual es un error ( y no compilara
). Los comentarios describen cada error.

// Este programa no compilara.


#include <iostream>
using namespace std;

class base {
protected:
int i, j;

public:

void set(int a, int b) { i = a; j = b; }


void mostrar() { cout << i << " " << j << "\n"; }
};

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

// Acceso a j, i, set() y mostrar no heredado


class derivada2 : public derivada1 {
int m;

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;

obj1.set(1, 2); // Error, no se puede usar set()


obj1.mostrar(); // Error, no se puede usar show()

obj2.set(3, 4); // Error, no se puede usar set()


obj2.mostrar(); // Error, no se puede usar show()

return 0;
}

Incluso aunque 'base' es heredada como privada por derivada1, derivada2


aun tiene acceso a los elementos publicos y protegidos de 'base'. Sin embargo,
no puede sobrepasar este privilegio a todo lo largo. Esta es la razon de las
partes 'protected' del lenguaje C++. Este provee un medio de proteger
ciertos miembros de ser modicados por funciones no-miembros, pero les

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

18.13 USANDO PROTECTED PARA LA HERENCIA DE


UNA CLASE BASE.
Como adición a especicar el estado protegido para los miembros de una
clase, la palabra clave 'protected' tambien puede ser usada para heredar una
clase base. Cuando una clase base es heredada como protected, todos los
miembros publicos y protegidos de la clase base se convierten en miembros
protegidos de la clase derivada. Aqui hay un ejemplo:

// Demuestra la herencia de una clase base protegida


#include <iostream>
using namespace std;

class base {
int i;

protected:
int j;

public:
int k;
void seti(int a) { i = a; }
int geti() { return i; }
};

// heredar 'base' como protected.


class derivada : protected base {

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;

/* La proxima linea es ilegal porque seti() es


un miembro protegido de derivada, lo cual lo
hace inaccesible fuera de derivada. */
// obj.seti(10);

// cout << obj.geti(); // ilegal -- geti() es protected.


// obj.k = 10; // tambien ilegal porque k es protected.

// estas declaraciones son correctas


obj.setk(10);
cout << obj.getk() << " ";
obj.setj(12);
cout << obj.getj() << " ";

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.

18.14 Revisando public, protected, y private


Ya que los permisos de acceso que son denidos por 'public', 'protected',
y 'private' son fundamentales para la programación en C++, volvamos a
revisar sus signicados.
Cuando una clase miembro es declarada como 'public', esta puede ser
accedida por cualquier otra parte del programa. Cuando un miembro es

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.

18.15 HEREDANDO MULTIPLES CLASES BASE


Es posible para una clase derivada heredar dos o mas clases base. Por ejem-
plo, en este corto programa, derivada hereda de ambas clases base1 y base2:

// Un ejemplo de multiples clases base


#include <iostream>
using namespace std;

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

// Heredar multiples clases base.


class derivada : public base1, public base2 {

public:
void set(int i, int j) { x= i; y = j; };
};

int main()
{

derivada obj;

obj.set(10, 20); // proveida por derivada.


obj.showx(); // desde base1
obj.showy(); // desde base2

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.

18.16 CONSTRUCTORES, DESTRUCTORES, Y HEREN-


CIA
Existen dos importantes preguntas relativas a los constructores y destruc-
tores cuando la herencia se encuentra implicada. Primera, ¾dónde son lla-
mados los constructores y destructores de las clases base y clases derivadas?
Segunda, ¾como se pueden pasar los parámetros al constructor de una clase
base? Esta sección responde estas preguntas.
Examine este corto programa:

#include <iostream>
using namespace std;

class base {

74
public:
base() { cout << "Construyendo base\n"; }
~base() { cout << "Destruyendo base\n"; }
};

class derivada : public base {


public:
derivada() { cout << "Construyendo derivada\n"; }
~derivada() { cout << "Destruyendo derivada\n"; }
};

int main()
{
derivada obj;

// no hacer nada mas que construir y destruir obj

return 0;
}

Como puede ver, el constructor de 'base' es ejecutado, seguido por el


constructor en 'derivada'. A continuación ( ya que obj es inmediatamente
destruido en este programa), el destructor en 'derivada' es llamado, seguido
por el de 'base'.
El resultado del experimento anterior puede ser generalizado como lo
siguiente: Cuando un objeto de una clase derivada es creado, el construc-
tor de la clase base es llamado primero, seguido por el constructor de la
clase derivada. Cuando un objeto derivada es destruido, su destructor es
llamado primero, seguido por el destructor de la clase base. Viéndolo de
otra manera, los constructores son ejecutados en el orden de su derivación.
Los destructores son ejecutado en orden inverso de su derivación.
Si lo piensa un poco, tiene sentido que las funciones constructor sean
ejecutadas en el orden de su derivación. Porque una clase base no tiene
conocimiento de las clases derivadas, cualquier inicialización que necesite
realizar es separada de, y posiblemente un prerequisito a, cualquier ini-
cialización realizada por la clase derivada. Además, esta debe ejecutarse
primero.
Asimismo, es bastante sensible que los destructores sean ejecutados en or-

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:

base() { cout << "construyendo base\n"; }


~base() { cout << "Destruyendo base\n"; }
};

class derivada1 : public base {

public:
derivada1() { cout << "Construyendo derivada1\n"; }
~derivada1() { cout << "destruyendo derivada1\n"; }
};

class derivada2 : public derivada1 {

public:
derivada2() { cout << "Construyendo derivada2\n"; }
~derivada2() { cout << "Destruyendo derivada2\n"; }
};

int main()
{
derivada2 obj;

// construir y destruir obj

76
return 0;
}

La misma regla general se aplica en situaciones en que se ven implicadas


multiples clases base. Por ejemplo, este programa

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

class derivada : public base1, public base2 {

public:
derivada() { cout << "Construyendo derivada\n"; }
~derivada() { cout << "Destruyendo derivada\n"; }
};

int main()
{
derivada obj;

// construir y destruir 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"; }
};

class derivada : public base {


int j;

public:
// derivada usa x, y es pasado en consjunto a base
derivada(int x, int y) : base(y)
{ j = x; cout << "Construyendo derivada\n"; }

~derivada() { cout << "Destruyendo derivada\n"; }


void mostrar() { cout << i << " " << j << "\n"; }

78
};

int main()
{
derivada obj(3, 4);

obj.mostrar(); // muestra 4 3

return 0;
}

79

Вам также может понравиться