You are on page 1of 241

Programación

en
Lenguajes
Estructurados

Ciclo
de
Desarrollo de Aplicaciones Informáticas
© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: i

1.- RESOLUCIÓN DE PROBLEMAS CON ORDENADOR..................................................... 1


1.- Introducción. ....................................................................................................................................1
2.- Concepto de algoritmo.....................................................................................................................1
3.- Análisis del problema. .....................................................................................................................2
4.- Diseño del algoritmo. .......................................................................................................................3
4.1.- Escritura inicial del algoritmo. .....................................................................................................4
5.- Herramientas de programación. ....................................................................................................6
5.1.- Diagramas de flujo........................................................................................................................6
5.2.- Pseudocódigo................................................................................................................................7
6.- Los lenguajes de programación. .....................................................................................................7
6.1.- Instrucciones del ordenador..........................................................................................................8
6.2.- Lenguajes máquina. ......................................................................................................................8
6.3.- Lenguajes de bajo nivel. ...............................................................................................................8
6.4.- Lenguajes de alto nivel. ................................................................................................................9
6.5.- Traductores de lenguaje................................................................................................................9
6.5.1.- Compiladores.........................................................................................................................9
6.5.2.- Intérpretes. .............................................................................................................................9
7.- Resolución del problema mediante ordenador. ..........................................................................10
7.1.- Codificación del algoritmo en un programa. ..............................................................................10
7.2.- Ejecución de un programa. .........................................................................................................10
7.3.- Verificación y depuración de un programa. ...............................................................................10

2.- ELEMENTOS GENERALES DE UN PROGRAMA.......................................................... 13


1.- Datos y tipos de datos. ...................................................................................................................13
1.1.- Datos numéricos. ........................................................................................................................13
1.2.- Datos carácter o alfanumérico. ...................................................................................................13
1.3.- Datos lógicos o booleanos. .........................................................................................................13
2.- Identificadores, constantes y variables. .......................................................................................14
2.1.- Identificador................................................................................................................................14
2.2.- Constante. ...................................................................................................................................14
2.3.- Variable.......................................................................................................................................14
3.- Expresiones.....................................................................................................................................15
3.1.- Expresiones aritméticas. .............................................................................................................15
3.2.- Expresiones lógicas (booleanas).................................................................................................16
3.3.- Operaciones alfanuméricas.........................................................................................................17
3.4.- Orden de evaluación de los operadores. .....................................................................................18

3.- INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURADA. ..................................... 21


1.- Concepto de programa. .................................................................................................................21
2.- Tipos de instrucciones. ..................................................................................................................21
2.1.- Instrucciones de declaración.......................................................................................................21
2.2.- Instrucciones primitivas..............................................................................................................22
2.2.1.- Instrucción de asignación. ...................................................................................................22
2.2.2.- Instrucción de lectura de datos. Entrada. .............................................................................23
2.2.3.- Instrucción de salida de datos. .............................................................................................23
2.3.- Instrucciones de control..............................................................................................................24
2.3.1.- Instrucciones alternativas.....................................................................................................24
2.3.1.1.- Alternativa simple.........................................................................................................25

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: ii

2.3.1.2.- Alternativa doble. .........................................................................................................25


2.3.1.3.- Alternativa múltiple. .....................................................................................................25
2.3.2.- Instrucciones repetitivas. Bucles. ........................................................................................26
2.3.2.1.- Estructura MIENTRAS. ...............................................................................................27
2.3.2.2.- Estructura REPETIR.....................................................................................................27
2.3.2.3.- Estructura PARA. .........................................................................................................28
2.3.3.- Instrucciones de ruptura de secuencia. ................................................................................28
3.- Variables auxiliares de un programa...........................................................................................29
3.1.- Contadores. .................................................................................................................................29
3.2.- Acumuladores. ............................................................................................................................29
3.3.- Interruptores................................................................................................................................29
4.- Escritura de programas.................................................................................................................30
4.1.- Cabecera del programa. ..............................................................................................................30
4.2.- Declaración de objetos................................................................................................................30
4.3.- Cuerpo del programa. .................................................................................................................30

4.- SUBPROGRAMAS. ......................................................................................................... 35


1.- Introducción a los subprogramas.................................................................................................35
2.- Funciones. .......................................................................................................................................35
2.1.- Declaración de funciones............................................................................................................36
2.2.- Invocación a las funciones..........................................................................................................36
3.- Procedimientos o subrutinas.........................................................................................................37
4.- Ámbito: variables locales y globales.............................................................................................38
5.- Comunicación con subprogramas: paso de parámetros. ...........................................................39
5.1.- Paso por valor. ............................................................................................................................39
5.2.- Paso por referencia. ....................................................................................................................39

5.- CARACTERÍSTICAS GENERALES DE LOS PROGRAMAS EN LENGUAJE C. .......... 41


1.- Estructura de un programa C. .....................................................................................................41
1.1.- Comentarios................................................................................................................................41
1.2.- Cabecera. ....................................................................................................................................41
1.3.- Entorno. ......................................................................................................................................42
1.4.- Función main()............................................................................................................................42
2.- Elementos del lenguaje C ..............................................................................................................42
2.1.- Léxico de C.................................................................................................................................42
2.2.- Sintaxis de C. ..............................................................................................................................43
3.- Ejemplo de programa en lenguaje C............................................................................................43

6.- TIPOS DE DATOS, OPERADORES Y EXPRESIONES .................................................. 45


1.- Tipos de datos.................................................................................................................................45
2.- Constantes.......................................................................................................................................46
2.1.- Constantes enteras. .....................................................................................................................46
2.2.- Constantes reales. .......................................................................................................................47
2.3.- Constantes de un sólo carácter....................................................................................................47
2.4.- Constantes de cadena..................................................................................................................47
2.5.- Expresiones constantes. ..............................................................................................................47
2.- Variables: declaración...................................................................................................................48
3.- Operadores. ....................................................................................................................................48

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: iii

3.1.- Operadores aritméticos. ..............................................................................................................48


3.2.- Operadores relacionales y lógicos. .............................................................................................48
3.3.- Operadores de incremento y decremento. ..................................................................................49
3.4.- Operadores de asignación y expresiones. ...................................................................................49
3.5.- Operador condicional..................................................................................................................50
4.- Conversión de tipos........................................................................................................................51
5.- Precedencia y orden de evaluación de los operadores................................................................52

7.- ENTRADA Y SALIDA POR PANTALLA ......................................................................... 53


1.- Acceso a la librería estándar.........................................................................................................53
2.- Funciones de entrada y salida estandar.......................................................................................53
2.1.- Salida con formato. Función printf(). .........................................................................................53
2.2.- Entrada con formato. Función scanf(). .......................................................................................55
3.- Función getchar()...........................................................................................................................57
4.- Función putchar(). .........................................................................................................................58
5.- Función gets(). ................................................................................................................................58
6.- Función puts(). ...............................................................................................................................58
7.- Funciónes getch() y getche()..........................................................................................................58

8.- SENTENCIAS DE CONTROL DE PROGRAMA.............................................................. 59


1.- Introducción. ..................................................................................................................................59
2.- Sentencias y bloques. .....................................................................................................................59
3.- Sentencias de selección o condicionales. ......................................................................................59
3.1.- Sentencia if-else..........................................................................................................................59
3.2.- Sentencia switch. ........................................................................................................................60
4.- Sentencias de iteración o bucles....................................................................................................61
4.1.- Sentencia while...........................................................................................................................61
4.2.- Sentencia for. ..............................................................................................................................63
4.3.- Sentencia do-while......................................................................................................................64

9.- TIPOS DE VARIABLES, PUNTEROS Y FUNCIONES .................................................... 67


1.- Tipos de variables. .........................................................................................................................67
1.1.- Variables locales.........................................................................................................................67
1.2.- Variables globales.......................................................................................................................67
1.3.- Variable estáticas. .......................................................................................................................67
1.4.- Variables registro........................................................................................................................67
2.- Teoría básica de punteros. ............................................................................................................68
2.1.- Variables puntero........................................................................................................................68
2.2.- Operadores de punteros. .............................................................................................................68
3.- Funciones. .......................................................................................................................................70
3.1.- Declaración de funciones............................................................................................................70
3.2.- Definición de funciones..............................................................................................................71
3.3.- Llamada a funciones. ..................................................................................................................71
3.4.- La función main(). ......................................................................................................................71
3.5.- Retorno de valores. .....................................................................................................................72
3.6.- Paso de parámetros. ....................................................................................................................72
3.6.1.- Paso de parámetro por valor. ...............................................................................................72
3.6.2.- Paso de parámetros por referencia.......................................................................................72

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: iv

10.- ESTRUCTURAS DE DATOS ESTÁTICAS.................................................................... 75


1.- Introducción a las estructuras de datos. ......................................................................................75
2.- Tablas: conceptos, definiciones y representación. ......................................................................75
3.- Tipos de tablas................................................................................................................................76
3.1.- Tablas unidimensionales o vectores. ..........................................................................................76
3.1.1.- Operaciones sobre vectores. ................................................................................................77
3.2.- Tablas bidimensionales o matrices. ............................................................................................80
3.2.1.- Operaciones sobre matrices. ................................................................................................81
4.- Búsqueda lineal. .............................................................................................................................85
4.1.- Búsqueda lineal en un vector no ordenado. ................................................................................85
4.2- Búsqueda lineal en un vector ordenado. ......................................................................................86
4.3.- Búsqueda lineal en una matriz....................................................................................................87
5.- Búsqueda binaria o dicotómica en un vector. .............................................................................87
6.- Ordenación de tablas. ....................................................................................................................88
6.1.- Ordenación por inserción directa................................................................................................88
6.2.- Ordenación por selección directa................................................................................................89
6.3.- Ordenación por intercambio directo o método de la burbuja. ....................................................89
7.- Mezcla de vectores. ........................................................................................................................90

11.- USO DE PUNTEROS COMO ARRAYS......................................................................... 93


1.- Arrays unidimensionales y punteros............................................................................................93
2.- Arrays bidimensionales y punteros. .............................................................................................95
3.- Reserva de memoria. .....................................................................................................................98
4.- Paso de parámetros array a funciones.........................................................................................99
4.1.- Arrays unidimensionales. ...........................................................................................................99
4.2.- Arrays bidimensionales. ...........................................................................................................100

12.- CADENAS DE CARACTERES. ................................................................................... 103


1.- Introducción .................................................................................................................................103
2.- Representación de las cadenas....................................................................................................103
3.- Inicialización de arrays de caracteres........................................................................................104
4.- Ejemplos de funciones con cadenas............................................................................................104
5.- Funciones de estándar para la manipulación de cadenas. .......................................................106
6.- Funciones de conversión..............................................................................................................107
7.- Funciones de caracteres...............................................................................................................108

13.- ESTRUCTURAS, CAMPOS DE BITS Y UNIONES ..................................................... 109


1.- Definición de registro o estructura.............................................................................................109
2.- Declaración de estructuras..........................................................................................................109
3.- Manipulación de estructuras. .....................................................................................................111
4.- Arrays de estructuras. .................................................................................................................112
5.- Tipos de datos definidos por el usuario .....................................................................................113
6.- Estructuras y punteros. ...............................................................................................................114
7.- Paso de estructuras a funciones..................................................................................................115
8.- Uniones..........................................................................................................................................116

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: v

9.- Campos de bits. ............................................................................................................................117


10.- Enumeraciones. ..........................................................................................................................118

14.- ESTRUCTURAS DE DATOS EXTERNAS................................................................... 121


1.- Introducción. ................................................................................................................................121
2.- Conceptos, definiciones. ..............................................................................................................121
3.- Clasificación de los archivos. ......................................................................................................122
3.1.- Clasificación desde el punto de vista del usuario. ....................................................................122
3.2.- Clasificación según su uso........................................................................................................122
3.3.- Clasificación según el almacenamiento físico en el soporte. ...................................................123
3.4.- Clasificación según su método de acceso.................................................................................123
4.- Operaciones sobre archivos. .......................................................................................................123
5.- Operaciones sobre registros........................................................................................................124
6.- Organización de los archivos. .....................................................................................................124
6.1.- Organización secuencial. ..........................................................................................................125
6.2.- Organización relativa................................................................................................................126
6.2.1.- Organización directa..........................................................................................................126
6.2.2.- Organización relativa o indirecta.......................................................................................127
6.3.- Organización secuencial indexada............................................................................................127
7.- Archivos en lenguaje C................................................................................................................129
7.1.- Creación y apertura de un archivo. ...........................................................................................129
7.2.- Cerrar archivo. ..........................................................................................................................131
7.3.- Lectura y escritura de caracteres...............................................................................................131
7.4.- Lectura y escritura de cadenas..................................................................................................132
7.5.- Entrada y salida con formato. ...................................................................................................133
7.6.- Lectura y escritura de bloques de información.........................................................................135
7.7.- Detectar fin de fichero. .............................................................................................................135
7.8.- Posicionamiento en el fichero: e/s de acceso directo................................................................136
7.9.- Desplazamiento al comienzo del archivo. ................................................................................137
7.10.- Posición actual de la cabeza de E/S........................................................................................137
7.11.- Detección de errores. ..............................................................................................................137
7.12.- Vaciar el buffer. ......................................................................................................................137
7.13.- Reasignación de archivo. reapertura.......................................................................................137
7.14.- Cambiar el nombre de un archivo...........................................................................................138
7.15.- Borrar un archivo....................................................................................................................138
8.- Búsqueda en archivos. .................................................................................................................139
8.1.- Búsqueda en archivos desordenados. .......................................................................................139
8.2.- Búsqueda en archivos ordenados..............................................................................................139
9.- Partición de archivos. ..................................................................................................................140
9.1.- Partición por contenido.............................................................................................................140
9.2.- Partición en secuencias. ............................................................................................................141
10.- Mezcla de archivos.....................................................................................................................142
10.1.- Mezcla de archivos desordenados. .........................................................................................142
10.2.- Mezcla de archivos ordenados................................................................................................142
11.- Clasificación de archivos. ..........................................................................................................143
11.1.- Clasificación por mezcla directa.............................................................................................143
11.2.- Clasificación por mezcla equilibrada......................................................................................145
12.- Rupturas de control. ..................................................................................................................145

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: vi

15.- ESTRUCTURAS DINÁMICAS LINEALES DE DATOS ............................................... 157


1.- Introducción. ................................................................................................................................157
2.- Listas enlazadas. ..........................................................................................................................157
2.1.- Implementación. .......................................................................................................................158
2.2.- Operaciones básicas para la manipulación de listas. ................................................................159
2.2.1.- Inicialización......................................................................................................................159
2.2.2.- Lista vacía..........................................................................................................................159
2.2.3.- Añadir nodos a la lista. ......................................................................................................159
2.2.3.1.- Insertar al principio de la lista. ...................................................................................160
2.2.3.2.- Insertar en medio o al final de la lista.........................................................................160
2.2.3.3.- Inserción en una lista ordenada...................................................................................161
2.2.4.- Recorrido de una lista. .......................................................................................................162
2.2.5.- Borrado de nodos de la lista...............................................................................................163
3.- Listas enlazadas circulares..........................................................................................................164
3.1.- Operaciones sobre listas enlazadas circulares. .........................................................................165
3.1.1.- Insertar en la lista...............................................................................................................165
3.1.2.- Borrar un nodo de la lista...................................................................................................166
3.1.3.- Recorrido de la lista. ..........................................................................................................167
4.- Listas doblemente enlazadas.......................................................................................................167
4.1.- Operaciones sobre listas doblemente enlazadas .......................................................................168
4.1.1.- Funciones de inicializar, vacía, crear un nuevo nodo........................................................168
4.1.2.- Insertar en lista doble.........................................................................................................169
4.1.2.1.- Insertar al principio de la lista. ...................................................................................169
4.1.2.2.- Insertar al final de la lista............................................................................................169
4.1.2.3.- Insertar entre dos nodos. Detrás del apuntado. ...........................................................170
4.1.2.4.- Insertar entre dos nodos. Delante del apuntado. .........................................................171
4.1.3.- Borrar un nodo de la lista...................................................................................................171
4.1.4.- Recorrido de la lista. ..........................................................................................................173
5.- Pilas. ..............................................................................................................................................174
6.- Colas..............................................................................................................................................174

16.- ESTRUCTURAS DINÁMICAS NO LINEALES DE DATOS......................................... 178


1.- Recursividad.................................................................................................................................178
2.- Árboles. .........................................................................................................................................182
2.1.- Definición, elementos y representación de los árboles.............................................................182
2.2.- Definición de árbol binario y su representación. ......................................................................183
2.2.1.- Operaciones con árboles binarios. .....................................................................................184
2.2.1.1. - Creación de un árbol..................................................................................................184
2.2.1.2.- Ver si el árbol está vacío.............................................................................................184
2.2.1.3.- Construcción un árbol binario equilibrado. ................................................................184
2.2.1.4.- Recorrido de un árbol. ................................................................................................185
2.2.1.5.- Buscar en árboles ordenados. .....................................................................................186
2.2.1.6.- Inserción en árboles ordenados...................................................................................187
2.2.1.7.- Borrado en árboles ordenados. ...................................................................................187

17.- LENGUAJE C++ COMO UN C MEJORADO............................................................... 189


1.- Extensión del nombre de los ficheros.........................................................................................189
2.- Comentarios. ................................................................................................................................189
3.- Declaración simplificada de variables estructura y enum. ......................................................189

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: vii

4.- Flexibilidad en la declaración de variables................................................................................189


5.- Operador de resolución de visibilidad. ......................................................................................189
6.- Especificador const para variables.............................................................................................190
7.- Especificador const para punteros. ............................................................................................190
8.- Conversiones explícitas de tipo. moldeo. ...................................................................................190
9.- Especificador inline para funciones. ..........................................................................................191
10.- Sobrecarga de funciones............................................................................................................191
11.- Valores por defecto de parámetros de una función. ...............................................................192
12.- Variables de tipo referencia. .....................................................................................................192
13.- Operadores new y delete. Gestión dinámica de memoria .......................................................194
14.- Nueva forma de realizar la entrada y salida. ..........................................................................195
15.- Sobrecarga de operadores.........................................................................................................195

18.- PROGRAMACIÓN ORIENTADA A OBJETOS. .......................................................... 199


1.- Introducción. ................................................................................................................................199
2.- Características de la poo. ............................................................................................................199
2.1.- Abstracción. ..............................................................................................................................200
2.2.- Encapsulamiento.......................................................................................................................200
2.3.- Modularidad..............................................................................................................................201
2.4.- Jerarquía....................................................................................................................................201
3.- Clases y objetos. ...........................................................................................................................201
3.1.- Objeto. ......................................................................................................................................202
3.1.1.- Estado del objeto................................................................................................................202
3.1.2.- Comportamiento del objeto. ..............................................................................................203
3.1.3.- Identidad del objeto. ..........................................................................................................203
3.2.- Clase. ........................................................................................................................................204
3.2.2.- Identificador.......................................................................................................................204
3.2.2.- Componentes o miembros de la clase................................................................................204
3.2.3.- Nivel de acceso. .................................................................................................................205
3.3.- Declaración de clases................................................................................................................206
3.4.- Instanciación de objetos............................................................................................................207
3.5.- Control de acceso a miembros..................................................................................................208
3.5.1.- Campo de clase ..................................................................................................................208
3.5.2.- Acceso entre clases amigas................................................................................................209
3.6.- Sobrecarga de operadores en clases..........................................................................................210
3.7.- Creación e inicialización de objetos. ........................................................................................211
3.7.1.- Constructor por defecto. ....................................................................................................211
3.7.2.- Constructores generales. ....................................................................................................212
3.7.3.- Destructor. .........................................................................................................................213
3.7.4.- Constructor copia y el operador de asignación..................................................................213
3.8.- Objetos dinámicos. ...................................................................................................................215
3.8.1.- Vectores de objetos............................................................................................................215
3.8.2.- Punteros a objetos. .............................................................................................................215
4.- Relaciones entre clases.................................................................................................................217
4.1.- Relación de uso.........................................................................................................................217
4.2.- Relación de asociación. ............................................................................................................217
4.3.- Relación de agregación.............................................................................................................217
4.4.- Relación de herencia.................................................................................................................218

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: viii

4.4.1.- Acceso a miembros heredados y tipos de acceso. .............................................................220


4.4.2.- Herencia múltiple y clases virtuales. .................................................................................222
4.4.3.- Conversiones entre objetos de clases base y clases derivadas...........................................224
5.- Polimorfismo. ...............................................................................................................................224
5.1.- Sobrecarga de métodos y operadores. ......................................................................................225
5.2.- Herencia de clases.....................................................................................................................225
5.2.1.- Enlace estático. ..................................................................................................................225
5.2.2.- Enlace dinámico.................................................................................................................227
6.- Clases abstractas. .........................................................................................................................228
7.- Lenguajes de POO. ......................................................................................................................229
7.1.- Lenguaje Smalltalk. ..................................................................................................................230
7.2.- Lenguaje C++. ..........................................................................................................................230
7.3.- Lenguaje Java. ..........................................................................................................................230

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: ix

INTRODUCCIÓN.

Teniendo en cuenta las capacidades terminales asociadas al módulo y las características


generales del ciclo formativo, se entiende que el proceso educativo ha de organizarse en torno a los
procedimientos, es decir, que el aprendizaje se orienta hacia los modos de saber hacer. Por tanto, el
eje del proceso de aprendizaje, el contenido organizador, será de tipo procedimental.

El contenido organizador debe comprender y aglutinar todas las capacidades que pretendemos
que desarrolle el alumno y que vienen expresadas como capacidades terminales del módulo en el
Real Decreto que define el título.

Analizando las capacidades terminales del módulo, teniendo en cuenta el perfil profesional
del título y considerando las responsabilidades asignadas a la función de administración en el
entorno de los sistemas, hemos optado por el siguiente enunciado para el contenido organizador:

Elaborar, adaptar y probar programas para mejorar la explotación del sistema y las aplicaciones.

A este procedimiento general y global está asociado un amplio conjunto de conocimientos de


carácter conceptual que constituyen los contenidos soporte de las habilidades y destrezas que los
alumnos deben adquirir.

OBJETIVOS.

· Adquirir el concepto de programa como método para resolver problemas generales.


· Conocer los elementos básicos de un programa: datos, operaciones sobre los datos e
instrucciones de control y manipulación.
· Aplicar las técnicas de programación estructurada empleando la metodología más
frecuentemente utilizada para programar.
· Entender y manejar las estructuras de datos internas, tanto estáticas como dinámicas.
· Entender y manejar las estructuras de datos externas.
· Conocer las características generales de los lenguajes de tercera generación.
· Codificar programas en lenguajes de programación de tercera generación.
· Utilizar el lenguaje de programación C para la codificación de los programas.
· Adquirir el concepto de programación orientada a objetos y ser capaz de codificar programas
en lenguaje C++ con las características de la POO.
· Conocer y usar las utilidades para desarrollo y puesta a punto de programas, incluidas y no
incluidas en el entorno de desarrollo integrado, que habitualmente proporcionan los lenguajes
de programación.
· Integrar y enlazar módulos de programación, rutinas y utilidades, siguiendo las
especificaciones del diseño y el diagrama de estructuras.
· Documentar las instrucciones y las estructuras de datos utilizadas para diseñar los programas y
redactar la documentación técnica y la guía de uso de los programas.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 1

1.- RESOLUCIÓN DE PROBLEMAS CON ORDENADOR

1.- Introducción.
El propósito fundamental de éste módulo es la resolución de problemas mediante un ordenador.
Los objetivos que se pretender conseguir son los siguientes:

· Aplicar las técnicas de programación estructurada usando la metodología más frecuentemente


utilizada para programar.
· Entender y manejar las estructuras de datos: internas (estáticas y dinámicas) y externas.
· Programar en lenguajes estructurados de tercera generación: Lenguaje C.
· Introducir al alumno en la programación orientada a objetos.
· Conocer y usar las utilidades para desarrollo y puesta a punto de programas, incluidas y no
incluidas en el entorno de desarrollo integrado, que habitualmente proporcionan los lenguajes
de programación.
· Documentar los programas.

2.- Concepto de algoritmo.


El eje central de la metodología gira en torno al concepto de algoritmo. Un algoritmo es un
método general de resolución de todos los problemas del mismo tipo; es decir, todo problema
puede ser expresado en forma de algoritmo, siendo éste valido para todos los problemas de iguales
características. Por ejemplo, si queremos calcular el área de un rectángulo, el algoritmo realizado debe
ser el mismo para calcular el área de cualquier rectángulo, independientemente de que tengan una
altura o una base mayor o menor; el algoritmo que estamos utilizando para resolver este problema
consta un conjunto de pasos que nos han enseñado en clase de matemáticas.

Para poder llegar a realizar un programa es necesario el diseño previo de un algoritmo, de


forma que sin un algoritmo no puede existir un programa. Los pasos necesarios para resolver un
problema son los siguientes:

· Diseño del algoritmo: describe la secuencia ordenada de pasos que conducen a la solución de
un problema dado.
· Expresar el algoritmo como un programa en un lenguaje de programación adecuado: C, C++,
Pascal, etc..
· Validación y ejecución del programa por el ordenador: comprobar si el programa resuelve o no
el problema planteado y es válido para todo el conjunto de datos posibles.

PROBLEMA -> DISEÑO DEL ALGORITMO -> PROGRAMA DE ORDENADOR -> EJECUCIÓN

Cualquier algoritmo es independiente del lenguaje de programación en el que se expresa el


problema y de la máquina que ejecuta el programa, de la misma forma que una receta de cocina puede
ser expresada en cualquier idioma, pero los pasos para la elaboración del plato se realizarán sin
importar el cocinero. Por tanto, debemos tener en cuenta que el lenguaje de programación es, en reali-
dad, un mero medio de expresar un algoritmo, y un ordenador es sólo un procesador, una máquina,
para ejecutarlo.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 2

Se pueden citar infinidad de ejemplos de algoritmos de la vida cotidiana, por ejemplo el manual
de instalación de un televisor, manual de uso de una radio, manual de reparación de una bicicleta,
pasos para la instalación de un juego en el ordenador, etc. En definitiva, un algoritmo es cualquier
manual de instrucciones que describa paso a paso las acciones que debemos realizar para conseguir
una meta final.

Las características fundamentales de un algoritmo son las siguientes:

· Un algoritmo debe ser preciso. Debe indicar el orden correcto de la realización de cada paso.
Cada uno de estos pasos debe realizarse en el orden en que se indican para poder solucionar el
problema. No debemos encender el ordenador sin haberlo conectado previamente a la red
eléctrica o no debemos tirar de la palanca de cambio de marcha del coche sin haber pisado
antes el embrague por que los resultados son imprevisibles.

· Un algoritmo debe estar bien definido. Si un algoritmo se sigue dos veces se debe obtener el
mismo resultado cada vez; en caso de no obtener el mismo resultado quiere decir que no está
bien diseñado y, por tanto, hay que corregir su diseño. Por ejemplo, el camino que seguimos
todos los días para ir a clase nos conduce al mismo sitio, está bien definido. Esto no implica
que el camino (algoritmo) para cumplir el objetivo sea único; puede haber, de hecho hay,
distintos caminos para llegar al mismo lugar.

· Un algoritmo debe ser finito. Si un algoritmo se sigue, este debe terminar en algún momento;
o sea, debe tener un número finito de pasos. Podemos plantearnos una pregunta: ¿Llegamos o
no llegamos a clase?

3.- Análisis del problema.


Es la primera fase de la resolución de un problema con un ordenador. Se requiere una
definición clara del problema: contemplar exactamente que debe hacer el programa y cual es el
resultado o solución deseada. Para poder definir correctamente un problema, necesitamos de tres requi-
sitos indispensables:

1) ¿Cuales y cuantas son las entradas? Son las premisas (datos) que proporciona el enunciado
del problema. Por ejemplo, para calcular la velocidad a la que se desplaza un cuerpo pueden
darnos la siguiente descripción: calcular la velocidad a la que corre un atleta los cien metros
lisos si tarda en realizarlos diez segundos. Las entradas que nos proporciona el enunciado son
cien metros y diez segundos.

2) ¿Cuales y cuantas son las salidas? Son los resultados (datos) que nos pide el enunciado. En el
ejemplo anterior nos indica que debemos calcular la velocidad de desplazamiento.

3) ¿Cual es el método que produce las salidas a partir de las entradas? Habitualmente, para
resolver cualquier problema matemático (y cualquier problema en general) combinamos
adecuadamente las premisas proporcionadas por el enunciado para obtener los resultados. El
método es, entonces, el conjunto de operaciones realizadas con los datos de entrada para poder
ofrecer una solución, unos resultados. En el ejemplo, el método consiste en la aplicación de la
fórmula para calcular la velocidad (v= s/t).

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 3

Ejemplos:

1.- Calcular el área del rectángulo de base B y altura H.

Entradas: - Base del rectángulo (B).


- Altura del rectángulo (H).

Salidas: - Área del rectángulo (A).

Método: - Fórmula para calcular el área del rectángulo. A= B x H.

Las entradas son la Base y la Altura. El enunciado no indica cuales son las medidas de los
lados del rectángulo; somos nosotros quienes debemos proporcionar los valores numéricos para esas
entradas.

2.- Leer el radio de un círculo y calcular e imprimir su superficie y circunferencia.

Entradas: - Radio del círculo.

Salidas: - Superficie del circulo.


- Circunferencia del circulo.

Método: - Fórmula para calcular la superficie.


- Fórmula para calcular el perímetro.

4.- Diseño del algoritmo.


Es sabido que un ordenador no es capaz de solucionar un problema por sí mismo; debemos
proporcionarle los pasos sucesivos que tiene que realizar para resolver el problema. El conjunto de
estos pasos sucesivos es lo que anteriormente hemos denominado algoritmo.

Cuando un problema es demasiado grande para solucionarlo como un bloque único,


desglosamos el problema en otros más pequeños que tengan una solución más fácil; a su vez, estos
pueden ser divididos en otros más pequeños y fáciles de solucionar. Esta técnica se llama comúnmente
"divide y vencerás". No existe ninguna norma fija de como realizar estas divisiones, por ello es
responsabilidad del programador, debiendo tener una gran habilidad para descomponer un problema en
subproblemas.

Normalmente los pasos diseñados en un primer esbozo son incompletos, con muy pocos pasos;
posteriormente se desarrolla otro, basado en el anterior, más completo, con mayor número de pasos y
mucho más específicos. Esta técnica de desarrollo de algoritmos con diferentes niveles de complejidad
se denomina "refinamiento del algoritmo".

Ejemplos:

1.- Calcular el área del rectángulo de base 3 y altura 5.

Subproblema Refinamiento
introducir la BASE introducir la BASE

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 4

introducir la ALTURA introducir la ALTURA


calcular el ÁREA ÁREA <-- BASE * ALTURA
visualizar ÁREA visualizar ÁREA

2.- Ecucación de segundo grado.

Subproblema Refinamiento
Dar coeficientes introducir A
Introducir B
Introducir C
Obtener raices x1=-b+sqrt(b*b-4*a*c)/(2*a)
X2=-b-sqrt(b*b-4*a*c)/(2*a)
¿Errores?
Sacar resultados Visualizar x1
Visualizar x2
¿Errores?

Tras haber realizado la división del problema en subproblemas, hay que representar el
algoritmo con una determinada herramienta de programación como es el diagrama de flujo o el
pseudocódigo.

4.1.- Escritura inicial del algoritmo.


Como hemos visto anteriormente, un algoritmo consiste en realizar una descripción paso a paso
del problema en cuestión. Para una correcta descripción de un algoritmo simple basado en una serie de
cálculos elementales, existen unas reglas que tienen las siguientes propiedades:

· Deben estar seguidas de alguna secuencia definida de pasos hasta que se obtenga un resultado
diferente.
· Sólo puede ejecutarse una operación a la vez.

El flujo de control de un algoritmo es usualmente secuencial; veamos el algoritmo


correspondiente a la siguiente pregunta: ¿Qué hacer para ver la película “Casablanca"?. Una primera
aproximación en forma de algoritmo que soluciona el problema sería:

ir al cine
comprar la entrada
ver la película
regresar a casa

Como puede observarse, el algoritmo es muy simple; consta de cuatro acciones que se realizan
una a continuación de otra y en un orden estricto, de forma que una acción no se realiza hasta que no
ha sido realizada la anterior: sin embargo, este algoritmo, se puede descomponer en pasos más simples
siguiendo el método de refinamiento sucesivo. Así, en un primer refinamiento, el algoritmo anterior se
puede escribir de la siguiente forma:

Inicio.
ver la cartelera del cine
si no proyectan "Casablanca"
entonces decidir otra actividad

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 5

si proyectan "Casablanca" ir al cine


si hay cola entonces ponerse en ella
mientras haya personas delante hacer
avanzar en la cola
si existen asientos disponibles entonces
comprar una entrada
encontrar el asiento correspondiente
mientas proyectan la película hacer
ver la película
sino protestar
abandonar el cine
volver a casa
fin.

Existen dos aspectos importantes a considerar en el ejemplo:

· Hay palabras reservadas que describen las estructuras de control fundamentales y procesos de
toma de decisión. Incluyen los conceptos de selección (si_entonces_sino) y los de repetición
(mientras_hacer, repetir_hasta).
· Se ha empleado la identación (sangrado o justificación) para la escritura del algoritmo, es decir,
las acciones que van dentro de las estructuras fundamentales se han desplazado a la derecha.

Como se ha dicho en puntos anteriores, es posible un refinamiento sucesivo, así, vamos a


aplicar esta técnica a la acción: "encontrar el asiento correspondiente".

inicio
caminar hasta llegar a la primera fila
repetir
comparar número fila con número billete
si no son iguales entonces
pasar a la siguiente fila
hasta que se localice la fila correcta
mientras número asiento no igual número billete hacer
avanzar en fila al siguiente asiento
sentarse en el asiento
fin.

Como puede observarse, podemos seguir aplicando el método de refinamiento sucesivo sobre
el algoritmo, todo depende de la imaginación del programador. Otro ejemplo de algoritmo es el que
responde a "cambiar una rueda de un coche", prodría ser el siguiente:

inicio
si el gato del coche está averiado entonces
llamar a la estación de servicio
sino poner el gato en su alojamiento
repetir
aflojar los tornillos de las ruedas
hasta que todos los tornillos estén flojos
repetir
levantar el gato
hasta que la rueda pueda girar

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 6

quitar los tornillos


quitar la rueda
poner rueda de repuesto y tornillos
bajar el gato
repetir
apretar los tornillos
hasta que estén apretados todos los tornillos
fin

5.- Herramientas de programación.


Las dos herramientas más utilizadas para diseñar algoritmos son diagramas de flujo y
pseudocódigo.

5.1.- Diagramas de flujo.


Un diagrama de flujo es una representación gráfica de un algoritmo. Es una técnica muy
antigua aunque todavía en uso. Utiliza símbolos (cajas) estándar y tiene los pasos del algoritmo
escritos en las cajas; están unidas por flechas, denominadas líneas de flujo, e indican la secuencia en
que deben ejecutarse. Los símbolos más utilizados en los diagramas de flujo son:

Terminal. Representa el INICIO y el FINAL del programa.

Inicio Fin

Entrada/Salida. Introducción y visualización de datos. Este símbolo se utiliza siempre que


entra o sale una información a o desde el ordenador. Dentro se sitúa un comentario que indica la
operación específica que se ejecuta.

Entrada / Salida

Proceso. Se utiliza siempre que los datos son manipulados o procesados. Los dos tipos
generales de operaciones de proceso son operaciones aritméticas y operaciones de transferencia de
datos.

Una operación aritmética es una suma, resta, multiplicación, división, exponenciación o


cualquier combinación de éstas.

Una operación de transferencia de datos es el cambio de valores entre elementos de datos.

PROCESO

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 7

Decisión. Indica operaciones lógicas entre los datos y en función del resultado determina cual
de los distintos caminos debe seguir el programa.

Conectores. Se usan para unir partes lejanas de un diagrama de flujo; por ejemplo cuando un
algoritmo no cabe en una página y debemos unir acciones de una a otra página.

Ejemplos:
1.- Cálculo del área de un triángulo.
2.- Cálculo del área y de la circunferencia de un círculo.

5.2.- Pseudocódigo.
El pseudocódigo es un lenguaje de especificación de algoritmos. El uso de tal lenguaje hace el
paso de codificación final (traducción a un lenguaje de programación) relativamente fácil.

Nació como un lenguaje similar al inglés y era un medio para representar básicamente las
estructuras de control de programación estructurada. Se considera un primer borrador, dado que ha de
traducirse posteriormente a un lenguaje de programación. Su ventaja es que el programador se puede
concentrar en la lógica y en las estructuras de control y no preocuparse de las reglas de un lenguaje
específico.

El uso del pseudocódigo se ha extendido ampliamente con términos en español como inicio,
fin, leer, escribir, visualizar, repetir_hasta, si_entonces_sino, mientras_finmientras, etc. Usar esta
terminología en español facilita enormemente el aprendizaje y uso diario de la programación.

En su momento veremos todas y cada una de las estructuras usadas en pseudocódigo.

Ejemplos:
1.- Cálculo del área de un triangulo.
2.- Cálculo del área y de la circunferencia de un círculo.

6.- Los lenguajes de programación.


Cuando el procesador que debe interpretar un algoritmo es un ordenador, el algoritmo se ha de
expresar en un formato que se denomina programa. Un programa se escribe en un lenguaje de
programación y las operaciones que conducen a expresar un algoritmo en forma de programa se
denomina programación.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 8

Los principales tipos de lenguajes de programación utilizados en la actualidad son:

· Lenguaje máquina
· Lenguaje de bajo nivel
· Lenguajes de alto nivel

6.1.- Instrucciones del ordenador.


Los diferentes pasos (acciones) de un algoritmo se expresan en los programas como
instrucciones, sentencias o proposiciones (el primero se usa para lenguajes de bajo nivel y máquina y
el segundo para los lenguajes de alto nivel). Por tanto, un programa consta de una secuencia de
instrucciones cada una de las cuales especifica ciertas operaciones que debe ejecutar el ordenador.

La elaboración de un programa requerirá conocer el juego de instrucciones del lenguaje así


como la sintaxis y la semántica del propio lenguaje.

Los tipos fundamentales de instrucciones se verán posteriormente.

6.2.- Lenguajes máquina.


Los lenguajes máquina son aquellos que están escritos en lenguajes directamente inteligibles
por el ordenador, ya que sus instrucciones son cadenas binarias (0 y 1) que especifican una operación y
las posiciones de memoria implicadas en la operación. Estas cadenas binarias se denominan
instrucciones de máquina o código máquina. El código máquina es el código binario.

Las instrucciones en lenguaje máquina dependen del hardware del ordenador y, por tanto,
diferirán de un ordenador a otro.

La principal ventaja de programar en este lenguaje es que la velocidad de ejecución es superior


a la de cualquier otro lenguaje; los inconvenientes son muchos: dificultad y lentitud de codificación,
poca fiabilidad, dificultad para verificar el código, sólo son ejecutables en máquinas del mismo tipo
que para la que se escribió el programa, etc.

6.3.- Lenguajes de bajo nivel.


Son más fáciles de usar pero siguen dependiendo de una máquina en particular. El principal
lenguaje de bajo nivel es el ensamblador y sus instrucciones son conocidas como nemotécnicos (ADD
M, N, P).

Un programa escrito en este lenguaje no puede ser ejecutado directamente por el ordenador,
sino que necesita una fase de traducción a lenguaje máquina.

Las ventajas e inconvenientes son los mismos que los del lenguaje máquina, aunque más
relajados.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 9

6.4.- Lenguajes de alto nivel.


Son los más utilizados por estar diseñados para que las personas escriban y entiendan los
programas de un modo mucho más fácil que los anteriores, además de ser independientes de la
máquina (son portables a otros ordenadores).

Ventajas:
· Formación corta.
· La escritura de programas se basa en reglas sintácticas similares a los lenguajes
humanos (READ, PRINT, ...).
· Fácil modificación y puesta a punto.
· Reducción del coste de los programas.
· Transportabilidad.

Inconvenientes:
· Incremento del tiempo de puesta a punto de los programas al necesitarse diferentes
traducciones del programa.
· No se aprovechan los recursos internos de la máquina que se explotan mejor en los
lenguajes máquina y ensambladores.
· Aumento de la ocupación de memoria.
· El tiempo de ejecución de los programas es mucho mayor.

Al igual que en los lenguajes ensambladores, los programas escritos en lenguajes de alto nivel
deben ser traducidos a lenguaje máquina.

6.5.- Traductores de lenguaje.


Debido a que el lenguaje máquina es demasiado elemental, es difícil y tedioso
utilizarlo. Para solucionar este problema, se diseña un nuevo conjunto de instrucciones (Lenguaje de
alto nivel) más conveniente para las personas que el ya incorporado en la máquina.

Una vez diseñado el lenguaje de alto nivel, como la máquina no reconoce mas que el lenguaje
máquina, es necesario convertir los programas escritos en el primero al segundo. Esta conversión
puede ser realizada de dos formas diferentes: compilación e interpretación.

6.5.1.- Compiladores.

Los compiladores son programas que toman cada instrucción del lenguaje de alto nivel y la
convierten en una secuencia equivalente de instrucciones del lenguaje máquina. El resultado es un
nuevo programa escrito en lenguaje máquina inteligible por misma. Esta técnica se denomina
traducción y al programa compilador.

6.5.2.- Intérpretes.

Los intérpretes son programas que toman programas escritos en lenguaje de alto nivel como
datos de entrada y los lleva a cabo examinando cada instrucción por turno y ejecutando directamente la
secuencia equivalente de instrucciones en lenguaje máquina. A diferencia del anterior, no requiere la

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 10

generación de un nuevo programa. Esta técnica se denomina interpretación y el programa se llama


interprete.

7.- Resolución del problema mediante ordenador.


Una vez que el algoritmo ha sido diseñado y representado mediante una herramienta de
programación, ya sea diagrama de flujo o pseudocódigo, pasamos a la resolución práctica del problema
con el ordenador.

Esta fase la descomponemos en varias subfases que se describen a continuación.

7.1.- Codificación del algoritmo en un programa.


Es una fase totalmente mecánica, siempre que el algoritmo haya sido diseñado correctamente.
Codificación es la escritura del algoritmo desarrollado anteriormente en un lenguaje de programación,
obteniendo lo que se denomina programa fuente. Dado que el diseño de un algoritmo es independiente
del lenguaje de programación utilizado para su implementación, el código puede ser escrito con igual
facilidad en un lenguaje o en otro.

Para realizar la conversión del algoritmo en programa se deben sustituir las palabras reservadas
en español por sus homónimos en inglés, y las operaciones/instrucciones indicadas en el lenguaje
natural expresarlas en el lenguaje de programación correspondiente.

7.2.- Ejecución de un programa.


Una vez que hemos obtenido el programa fuente, es necesario traducirlo en un nuevo programa
que el ordenador sea capaz de entender, al lenguaje de la máquina. Este proceso se realiza con el
compilador y el sistema operativo. Si tras la compilación se presentan errores, es preciso corregirlos y
volver a compilar. Este proceso se repite hasta que no se produzcan errores, obteniéndose el programa
objeto, que aún no es ejecutable. Para obtener el programa ejecutable debemos realizar el montaje o
enlace (link) del programa objeto con las librerías del programa compilador. Si se producen errores, se
corrigen y volvemos a realizar la compilación y montaje de programa.

Una vez obtenido el programa ejecutable, ya se puede ejecutar con solo teclear su nombre. Si
no existen errores de ejecución se obtendrán los resultados, en otro caso volvemos a corregir sobre el
programa fuente y realizamos los pasos anteriores.

7.3.- Verificación y depuración de un programa.


La verificación o comprobación de un programa es el proceso de ejecución del programa con
una amplia variedad de datos de entrada, que determinarán si el programa tiene errores.

La depuración es el proceso de encontrar los errores y corregirlos. Se pueden dar tres tipos de
errores:

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 11

· Errores de compilación: Se producen por el uso incorrecto de las reglas del lenguaje de
programación.
· Errores de ejecución: producidos por instrucciones que el ordenador puede comprender pero no
ejecutar. Ejemplo, división por cero o raíz negativa.
· Errores lógicos: se producen en la lógica del programa y la fuente del error suele ser el
algoritmo. Son los más frecuentes y los más difíciles de detectar.

Ejercicios.

1. Algoritmo para extraer los números de la Primitiva.


2. Suma, resta y producto de dos números.
3. Decir si un número es +, - ó nulo.
4. Decir cual es el mayor de dos números o si son iguales.
5. Leer una calificación numérica entre 0 y 10 y transformarla en una alfabética.
6. Un comercio realiza un descuento dependiendo del precio del producto. Si es inferior a 1.000
pts. no hay descuento; si está entre 1.000 pts. y menor a 100.000 pts. es un 5% de descuento; y
si es mayor a 100.000 pts es un 10%. Calcular el precio final del producto con el 15% de
impuestos.
7. Calcular la media de edad de los alumnos de la clase.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 13

2.- ELEMENTOS GENERALES DE UN PROGRAMA.

1.- Datos y tipos de datos.


El primer objetivo de todo ordenador es el manejo de la información o de los datos. Un dato es
la expresión general que describe los objetos con los cuales opera un ordenador, es decir, son los
valores con los que opera. La acción de las instrucciones ejecutables de los ordenadores se reflejan en
los cambios en los valores de las partidas de datos. Los datos de entrada se transforman, después de las
etapas intermedias, en los datos de salida. Existen dos tipos de datos: Simples y Compuestos. Los
simples son los siguientes: numérico, carácter y lógico. Los datos compuestos o estructurados son
conjuntos de datos simples relacionados entre sí.

1.1.- Datos numéricos.


El tipo numérico es el conjunto de los valores numéricos. Pueden representarse de dos formas:

· Numérico entero: es un subconjunto finito de los números enteros. Son números


completos, sin parte fraccionaria y pueden ser positivos y negativos. El rango normal en los
ordenadores va de -32768 a 32767.

Ejemplos de datos numéricos enteros son: 5, 7, 23, 123, 214, 0, -8, -45, -98 etc.

· Numérico real: es un subconjunto de los números reales. Tienen parte decimal (y punto
decimal) y también pueden ser positivos y negativos.

Ejemplos de datos numéricos reales son: -0.234, -67.0, 87.34, 3.1416, etc.

1.2.- Datos carácter o alfanumérico.


Es el conjunto finito y ordenado de los caracteres del ordenador. Los ordenadores suelen reco-
nocer los siguientes tipos de caracteres:

· Alfabéticos: A, B, ... , a, b, ...


· Numéricos: 0, 1, 2, ...
· Especiales: ., +, *, <, %, ...

Una cadena de caracteres es una sucesión de caracteres que se encuentran delimitados por una
comilla (apóstrofe) o dobles comillas. Su longitud es el número de caracteres que están encerrados
entre las comillas.

Ejemplos de datos de tipo alfanumérico son: 'Pepe', "Luís", '1 de Enero de 1993', etc.

1.3.- Datos lógicos o booleanos.


Son aquellos que pueden tomar uno de dos valores posibles: cierto o verdadero (true) o falso
(false).Este tipo de dato se usa para representar las alternativas a determinadas condiciones. Por

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 14

ejemplo, cuando se pide determinar si un valor entero es par, la respuesta es verdadera o falsa según
sea par o impar.

2.- Identificadores, constantes y variables.


Los programas de ordenador contienen ciertos valores que no pueden cambiar durante la
ejecución del programa. Tales valores se llaman constantes. De igual forma, existen otros valores que
si pueden cambiar durante la ejecución del programa; a estos valores se les llama variables. Para
nombrarlos, lo hacemos mediante identificadores.

2.1.- Identificador.
En un ordenador los datos necesitan ser manejados mediante herramientas que permitan
almacenarlos en memoria, leerlos, operar con ellos, etc. En los lenguajes de alto nivel se necesitan
nombres para identificar los objetos que se deseen manipular: variables, constantes, procedimientos,
etc. Los identificadores son los nombres que se designan para dicho propósito. Estos permiten elegir
nombres significativos que sugieran lo que representan. Los identificadores se construyen de acuerdo a
las reglas de sintaxis del lenguaje específico. Estas básicamente son:

· Deben comenzar con una letra (A a Z).


· El segundo carácter y posteriores puede ser letra o dígito (también el carácter de subrayado).
· No hay límites en cuanto a su extensión.
· No se puede usar como identificador una palabra clave del lenguaje.

2.2.- Constante.
Es una partida de datos (objetos) que permanecen sin cambios durante todo el desarrollo del
algoritmo o durante toda la ejecución del programa. Existen constantes lógicas, enteras, reales, carácter
y cadena.

Ejemplos de constantes son los siguientes:


angulo_llano <-- 180 (Constante entera).
pi <-- 3.1416 (Constante real).
ciudad <-- '28905 Getafe' (Constante alfanumérica).

2.3.- Variable
Es un objeto o partida de datos cuyo valor puede cambiar durante el desarrollo del algoritmo o
ejecución del programa. Dependiendo del lenguaje, hay diferentes tipos de variables, pero
generalmente son enteras, reales, carácter, lógicas y cadena. Una variable que es de un cierto tipo
puede tomar valores únicamente de ese tipo. Una variable carácter, por ejemplo, puede tomar como
valor sólo caracteres, mientras una variable entera puede tomar sólo valores enteros.

Para que una variable esté bien definida, hay que especificar: Nombre, tipo de dato y valor
inicial que toma. Los nombres de variables elegidos deben ser significativos, como pueden ser los
casos siguientes:

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 15

NOMBRE representa nombres de personas.


PRECIOS representa precios de diferentes artículos.
NOTAS representa las notas de una clase.

Ejemplos de variables son las siguientes:

contador <-- 23 (Variable entera).


area <-- 803.45 (Variable real).
saludo <-- '¿Qué tal, R2D2?' (Variable alfanumérica).
fin <-- verdadero (Variable lógica).
Letra <-- ‘x’ (Variable carácter)

3.- Expresiones.
Las expresiones son combinaciones de constantes, variables, símbolos de operación, paréntesis
y nombres de funciones especiales.

Las mismas ideas son utilizadas en notación matemática tradicional. Por ejemplo:
a + (b + 3) + c
Aquí los paréntesis indican el orden de cálculo y c representa la función raíz cuadrada de 'c'.

Cada expresión toma un valor que se determina tomando los valores de las variables y
constantes implicadas y la ejecución de las operaciones indicadas. Una expresión consta de operandos
y operadores. Según sea el tipo de objetos que manipulan, se clasifican en:

· Aritméticas: construidas con los operadores aritméticos.


· Lógicas: construidas con operadores lógicos y relacionales.
· Carácter: construidas con operadores alfanuméricos.

El resultado de la expresión aritmética es de tipo numérico; el de una expresión lógica es de


tipo lógico; el de una expresión carácter es de tipo carácter.

3.1.- Expresiones aritméticas.


Las expresiones aritméticas son análogas a las fórmulas matemáticas. Las variables y
constantes son numéricas (real o entera) y las operaciones son las aritméticas.

Los operadores aritméticos son los siguientes:

+ Suma
- Resta
* Multiplicación
/ División
**, ↑ Potencia
div División entera
mod Módulo (resto de la división)

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 16

Ejemplos de operaciones aritméticas son las siguientes:

Expresión Resultado
(3+2)*5 25
6-2 36
40 div 6 6
23 mod 2 1

3.2.- Expresiones lógicas (booleanas).


Una expresión lógica es aquella que sólo puede tomar dos valores diferentes: verdadero o falso.
Se forman combinando constantes y variables lógicas y los operadores relacionales.

- Operadores de relación: los operadores relacionales permiten realizar comparaciones de tipo


numérico o carácter. Sirven para expresar las condiciones en los algoritmos.

Los operadores relacionales son los siguientes:

< Menor que


> Mayor que
= Igual que
<= Menor o igual que
>= Mayor o igual que
<> Distinto de

El formato general para una operación de comparación es:

EXPRESION1 OPERADOR RELACIONAL EXPRESION2

y el resultado de la operación será verdadero o falso.

La aplicación de los operadores de relación es evidente. Por ejemplo si 'A=6' y 'B=5' entonces
'A > B' es verdadero, mientras '(A-2) < (B-4)' es falso.

Para realizar comparaciones de datos tipo carácter, se requiere una secuencia de ordenación de
los caracteres, similar al orden creciente o decreciente. Esta ordenación suele ser alfabética, tanto
mayúsculas como minúsculas, considerándolas de modo independiente, pero si se consideran
caracteres mixtos, se debe recurrir a un código normalizado como es el ASCII.

· Los valores de los caracteres que representan a los dígitos están en su orden natural. Esto es
'0'<'1'<'2'<...<'9'.
· Las letras mayúsculas 'A' a 'Z' siguen el orden alfabético. ('A'<'B'<'C'<...<'Z').
· Las letras minúsculas están en el mismo orden alfabético. ('a'<'b'<'c'<...<'z').

Ej.:Si A='PEPE' y B='LUIS' entonces A<B es falso y B<A es verdadero.

- Operadores lógicos: los operadores lógicos o booleanos básicos son NOT (no, negación),
AND (y, intersección) y OR (o, unión). La definición de los operadores lógicos se resume en las
siguientes tablas, denominadas tablas de verdad.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 17

-Tabla de verdad de NOT (no):

A NO A
FALSO CIERTO
CIERTO FALSO

NOT o NO es un operador unario, afecta a un operando. Actúa sobre la expresión cambiando


su estado lógico: si era verdad lo transforma en falso; y al revés. Por ejemplo: no es de día. No 5>3.

-Tabla de verdad de AND (y):

A B AYB
FALSO FALSO FALSO
FALSO CIERTO FALSO
CIERTO FALSO FALSO
CIERTO CIERTO CIERTO

AND o Y un operador binario, afecta a dos operandos. La expresión formada es cierta cuando
ambos operandos son ciertos al mismo tiempo. Por ejemplo: es de día y hace sol. 5>3 y 5>4.

-Tabla de verdad de OR (o):

A B AOB
FALSO FALSO FALSO
FALSO CIERTO CIERTO
CIERTO FALSO CIERTO
CIERTO CIERTO CIERTO

OR u O es un operador binario. La expresión que forma es cierta cuando al menos uno de sus
operandos es cierto. Por ejemplo: es de día o es de noche.

En las expresiones lógicas se pueden mezclar operadores relacionales y operadores lógicos.


Así, por ejemplo:

(1<5) y (5<10) es verdadera


(5>10) o ('A'<'B') es verdadera.

3.3.- Operaciones alfanuméricas.


Son las que producen resultados de tipo alfanumérico. Se construyen mediante los operadores
alfanuméricos. Sólo tenemos un operador alfanumérico, '+', usado para unir variables o expresiones
alfanuméricas; esta operación se denominada concatenación.

Ejemplos: 'Saca' + 'puntas' ----> 'Sacapuntas'.


'3' + '.' + '1416' ----> '3.1426'.
Si a='Juan' y b=' Antonio', entonces a+b='Juan Antonio'.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 18

3.4.- Orden de evaluación de los operadores.


Los operadores de una expresión se evalúan en el siguiente orden:

1º Paréntesis (primero los más internos).


2º Signo (-).
3º Potencias (↑).
4º Productos y divisiones (/, div, *, mod).
5º Sumas y restas (+, -).
6º Concatenación (+).
7º Relacionales (<, >, =, <=, >=, <>).
8º Negación (NOT, no).
9º Conjunción (AND, y).
10º Disyunción (OR, o).

La evaluación de operadores de igual orden se realiza siempre de izquierda a derecha.

Ejercicios.

1.- Evaluar las siguientes expresiones:

a) 4 * 3 + 5 17
b) 7 * 10 - 15 mod 3 * 4 + 9 79
c) (7 * (10 - 5) mod 3) * 4 + 9 17
d) - 4 * 7 + 2 ** 3 / 4 - 5 -31

2.- Convertir en expresiones aritméticas de algoritmos las siguientes expresiones algebraicas:

5(x + y)

x2 + y2
x
y(z + w)

x+ y
w
n+
a

- b ± b2 - 4ac
x=
2a

3.- Evaluar las siguientes expresiones aritméticas:


a) (a + b) / PVP + 2 4
Valores: a = 6, b = 8, PVP = 7
b) iva * iva - 4 * tasa * pvp 25
Valores: iva = 7, tasa = 3, pvp = 2
c) (a + 7 * c) / (b + 2 - a) + 2 * b 18.2

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 19

Valores: a = 3, b = 6, c = 4
d) (a + 5) * 3 / 2 * b - b 66
Valores: a = 3, b = 6

4.- Evaluar las siguientes expresiones:


Valores: a= 1, b = 4, c = 10.
a) a > 3 y b = 4 ó a + b <= c V
c) a > 3 y (b = 4 ó a + b <= c) F
d) (1 > 0) y (3 = 3) V
e) no (z > 14) V si Z<=14

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 21

3.- INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURADA.

1.- Concepto de programa.


Un programa de ordenador es un conjunto de instrucciones y datos lógicamente ordenados que
producirán la ejecución de una determinada tarea; es decir, un programa es un medio para conseguir un
fin: información necesaria para solucionar el problema. Todo programa contiene dos bloques bien
diferenciados:

a) Bloque de declaraciones: se especifican todos los objetos que intervienen en el programa,


tanto las variables como las constantes.

b) Bloque de instrucciones: contiene el conjunto de operaciones que se van a realizar para la


obtención de los resultados pedidos por el problema. Se realizará el tratamiento de los datos
de entrada para la obtención de los datos de salida.

2.- Tipos de instrucciones.


Las instrucciones consisten, en general, en acciones de manipulación de los objetos del
programa, desde su estado inicial hasta otro estado final que contendrá los resultados del proceso.

2.1.- Instrucciones de declaración.


Su misión es anunciar la utilización de objetos en un programa indicando qué identificador,
tipo y otras características corresponden a cada uno de ellos. En casi todos los lenguajes hay
establecidas declaraciones por defecto, se denominan declaraciones implícitas. Un formato usado en
pseudocódigo para la declaración de objetos puede ser el siguiente:

Entorno:
Nombre Tipo Valor inicial
Lista de objetos Tipo de los objetos

Cuando se declaran en una misma sección variables y constantes, estas se situarán delante de la
variables. Ejemplos:

Entorno:
Nombre Tipo Valor inicial
AREA N. Real
BASE N. Entero
ALTURA N. Entero

Entorno:
Nombre Tipo Valor inicial
PI N. Real 3.14159
AREA_CIRCULO N. Real
LONG_CIRCUNF N. Real

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 22

2.2.- Instrucciones primitivas.


Son aquellas que ejecuta el procesador de modo inmediato. Es decir, no dependen de otra cosa
que de su propia aparición en el programa para ser ejecutadas.

2.2.1.- Instrucción de asignación.

Usada para darle el valor que le corresponda a una variable. La operación de asignación se
representa con el símbolo u operador ““ (flecha con sentido a la izquierda). Esta instrucción es
conocida como sentencia de asignación. El formato general de su uso es

IDENTIFICADOR  EXPRESIÓN

donde IDENTIFICADOR es cualquier nombre de objeto válido, previamente declarado, y


EXPRESIÓN es cualquier expresión válida (bien construida) cuyo tipo de resultado se corresponda
con el tipo de la variable; es decir, para que una sentencia de asignación sea correcta, debe existir una
concordancia de tipo de dato entre el identificador y la expresión.

La sentencia de asignación es destructiva, pues el valor que tuviera la variable antes de la


asignación se pierde y es reemplazado por el valor resultante de evaluar la expresión.

Ejemplo:
El valor inicial del objeto denominado 'PRECIO' es el siguiente:

1253 PRECIO

Si aplicamos la sentencia de asignación

PRECIO  8312

obtendremos un nuevo valor en el objeto 'PRECIO':

8312 PRECIO

La sentencia de asignación es ejecutada por el ordenador en dos pasos bien definidos:

a) Evalúa la expresión situada a la derecha del operador de asignación, y obtiene, por tanto, un
valor de un tipo de dato específico.
b) El valor obtenido se almacena en la variable cuyo identificador está a la izquierda del
operador de asignación, sustituyendo el valor que tenía anteriormente.

Ejemplo:
x  (- b + (b * b - 4 * a * c) ** (1 / 2)) / (2 * a)

Valor inicial de 'x': 0 (o cualquier otro)


Valor de 'a': 1
Valor de 'b': 2
Valor de 'c': 1

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 23

Los objetos que forman parte de la expresión (a, b y c) permanecen invariables.

Las sentencias de asignación se clasifican según el tipo de dato de las expresiones. Por tanto,
pueden ser aritméticas, lógicas y de carácter. Ejemplos válidos de sentencias de asignación en
pseudocódigo son las siguientes:

a4
b  25
ca*b
nombre  'José' + 'Luis’
continuar  verdadero
fin  a > b and continuar

2.2.2.- Instrucción de lectura de datos. Entrada.

En ocasiones, los cálculos realizados por el ordenador requieren de entrada de datos externos
para realizar las acciones necesarias para obtener los resultados. Consiste en darle al ordenador un dato
(valor) desde el dispositivo estándar de entrada (teclado) y este lo almacenará en memoria principal en
el objeto cuyo identificador aparece en la propia instrucción.

Como la sentencia de asignación, la instrucción de entrada es destructiva; el valor previo que


tuviera el objeto se pierde y es reemplazado por el valor introducido desde el teclado. El formato
general de su uso es

Introducir Objeto/Lista_de_Objetos

donde “Introducir” es una palabra reservada que indica el tipo de instrucción a realizar y
“Objeto/Lista_de_Objetos” es el conjunto de elementos (objetos) donde se van a depositar en memoria
principal los datos leídos. Si hay más de un elemento, se separan por comas.

Ejemplo:
Introducir BASE, ALTURA

Si damos los valores 5 y 6 desde teclado, el objeto “BASE” tomará el valor 5 y el objeto
“ALTURA” el valor 6.

La sentencia de entrada de datos suele (debe, según los casos) ir precedida de una sentencia de
salida de datos (se verá a continuación) que nos identifique el tipo de entrada que se nos pide por
pantalla.

2.2.3.- Instrucción de salida de datos.

La sentencia se salida de datos es la forma principal de comunicación del ordenador con las
personas. Consiste en que el ordenador envía datos (información procesada) al dispositivo estándar de
salida (el monitor); estos datos pueden ser:

a) Valores de objetos depositados en memoria principal.


b) Valores definidos, de alguna forma, en la propia instrucción: constantes y/o expresiones.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 24

El formato general de uso es

Visualizar Expresión/Lista_de_Expresiones

donde “Visualizar” es una palabra reservada que indica el tipo de instrucción a realizar y
“Expresión/Lista_de_Expresiones” es el conjunto de informaciones y datos que queremos mostrar. Los
argumentos de la instrucción son los siguientes:

· Objetos: su valor -el contenido almacenado para el objeto en memoria- se visualizará por
el monitor, no se visualizará el identificador del objeto.
· Valores: datos constantes que se visualizarán por pantalla tal como están definidos en la
propia instrucción. En caso de ser de tipo carácter, irán encerrados entre comillas o
apóstrofes.
· Expresiones: son todo tipo de expresiones posibles de construir. La expresión es evaluada
y únicamente su valor final resultará mostrado por pantalla.

Los tres tipos de argumentos pueden entrar a formar parte de la instrucción de salida, debiendo
ir separados por comas.

Ejemplos:
Visualizar 'El área de rectángulo de base ', BASE , ' y altura ', ALTURA , ' es ', BASE *
ALTURA
Visualizar 'El área de rectángulo es ', AREA

Ejercicios :
1. Programa que calcule y escriba el cuadrado del número 243.
2. Programa que lea un número y escriba su cuadrado.
3. Programa que calcule y escriba el perímetro y el área de un rectángulo cuya base y altura se
leen desde teclado.
4. Algoritmo que calcule la hipotenusa de un triángulo rectángulo cuyos catetos se dan por
teclado.
5. Programa que intercambie los valores de dos variables numéricas.
6. Programa que lee una cantidad menor de 1000 y la desglosa en unidades, decenas y centenas.
7. Programa que lee una cantidad de dinero y la desglosa en las monedas de 1, 5, 25 y 100
pesetas.

2.3.- Instrucciones de control.


Son instrucciones que no realizan trabajo efectivo salvo la evaluación de expresiones,
generalmente lógicas, con el objetivo de controlar la ejecución de otras instrucciones o alterar el orden
de ejecución de las instrucciones de un programa.

2.3.1.- Instrucciones alternativas.

Una instrucción de selección o decisión es aquella que controla la ejecución de uno o varios
bloques de instrucciones, dependiendo del cumplimiento o no de alguna condición o valor final de
alguna expresión; es decir, se evalúa una condición o expresión y en función de su resultado se bifurca
a un determinado punto del programa. Las estructuras selectivas se utilizan para tomar decisiones
lógicas. Pueden ser de tres tipos: simples, dobles y múltiples.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 25

Simple Doble Múltiple

2.3.1.1.- Alternativa simple.

Se evalúa una condición y si esta es verdadera se realiza una acción o un bloque de acciones ;
si, por el contrario, es falsa, no se realiza ninguna acción. El formato es el siguiente:

si CONDICIÓN
Instrucción/es
fin si

2.3.1.2.- Alternativa doble.

Esta estructura nos permite elegir entre la realización de dos posibles acciones. Si la condición
es verdadera se realiza una o varias acciones y si es falsa se realiza otro grupo de acciones. La
alternativa simple es un caso particular de la alternativa doble. El formato de la sentencia alternativa
doble es el siguiente:

si CONDICIÓN
Instrucción/es si CONDICION ES CIERTA
si no
Instrucción/es si CONDICION ES FALSA
fin si

2.3.1.3.- Alternativa múltiple.

La alternativa de decisión múltiple evalúa una expresión que podrá tomar N valores diferentes.
Según se elija uno de estos valores en la condición, se realizará una de las N acciones posibles. Las
distintas opciones deben ser disjuntas entre sí, es decir, sólo puede cumplirse una de ellas ya que los
valores de cada opción no se pueden repetir. El formato es el siguiente:

Evaluar Expresión_Evaluar
Cuando VALOR_1
Instrucción/es 1
Cuando VALOR_2
Instrucción/es 2
Cualquier otro
Instrucción/s N Bloque opcional
Fin evaluar

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 26

Para cada valor de la expresión se pueden ejecutar una o varias acciones. Los valores de la
expresión no tienen por qué ser consecutivos pero sí únicos; se pueden considerar rangos de constantes
numéricas o de caracteres (según los lenguajes).

Ejercicios :
1. Decir si un número es positivo, negativo o nulo.
2. Algoritmo que nos diga si un número N es o no múltiplo de 2.
3. Programa que calcula las raíces de la ecuación de segundo grado.
4. Programa que lee una calificación numérica entera comprendida entre 0 y 10 y la transforma en
una calificación alfabética.
5. Programa que toma como entrada el número de un mes y nos indica el número de días que
tiene.

2.3.2.- Instrucciones repetitivas. Bucles.

Un bucle es un fragmento de algoritmo o programa cuyas instrucciones se repiten un número


determinado de veces : mientras se cumple una determinada condición o hasta que la condición se
cumpla, dependiendo del tipo de estructura que se use. Una iteración es el hecho de repetir la ejecución
de una secuencia de acciones. Un bucle consta de tres partes bien diferenciadas:

· Decisión: Se sitúa una condición que puede ser verdadera o falsa y se comprueba una vez a
cada paso o iteración del bucle, determinando la continuidad o finalización del mismo. La
condición determina el número de veces que se deben ejecutar las acciones.

· Cuerpo del bucle: Fragmento de programa o conjunto de instrucciones que se repiten. Se


ejecutan una vez en cada paso o iteración del bucle. Aquí es fundamental que el valor de la
condición sea afectado por las instrucciones para asegurar la finalización del bucle en algún
momento.

· Salida del bucle: Finalización del bucle. A continuación están situadas el resto de las
instrucciones que componen el programa.

Mientras Repetir

En un programa pueden existir varios bucles; estos pueden ser independientes o anidados. Son
anidados cuando están dispuestos de tal modo que unos son interiores a otros.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 27

Independientes Anidados Cruzados. No válidos

Disponemos de tres tipos de estructuras repetitivas dependiendo de su configuración.

2.3.2.1.- Estructura MIENTRAS.

Es aquella en la que el cuerpo del bucle se repite mientras se cumple la condición. Su formato
es el siguiente :

Mientras CONDICION
Instrucción/es
Fin mientras

Los pasos para la ejecución son los relacionados a continuación :


a) Evaluar la condición.
b) Si es falsa continúa en el paso f).
c) si es verdadera continúa en el paso d).
d) Instrucciones del cuerpo del bucle.
e) Regresa al paso a).
f) Instrucciones siguientes al bucle.

Al ser la condición de finalización la primera acción que se realiza del bucle, puede darse el
caso de que el cuerpo del bucle no se ejecute ninguna vez. En ocasiones esta opción es necesaria.

Ejercicio: Visualizar los números menores que N, siendo N un número introducido por teclado.

2.3.2.2.- Estructura REPETIR.

Se ejecuta hasta que se cumpla una condición determinada que se comprueba al final del bucle.
El formato es el siguiente :

Repetir
Instrucción/es
Hasta CONDICION

Los pasos para su ejecución son los siguientes :


a) Comienzo del bucle.
b) Ejecución de cuerpo del bucle.
c) Evaluar la condición.
d) Si es falsa continúa en el paso a).
e) Si es verdadera continúa en el paso f).
f) Instrucciones siguientes al bucle.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 28

Como la última acción que se realiza en el bucle es la evaluación de la condición, el cuerpo del
bucle se realiza siempre, al menos una vez.

Las diferencias principales entre los bucles mientras y repetir son estas:
a) El bucle mientras finaliza cuando la evaluación de la condición da como resultado falso y el
repetir cuando es verdadero.
b) El cuerpo del bucle repetir se realiza una o más veces y el cuerpo del bucle mientras se
ejecuta 0 o más veces.

2.3.2.3.- Estructura PARA.

Controla la ejecución del conjunto de instrucciones que configuran su rango, de tal forma que
estas se ejecutan un número determinado de veces que queda definido en la cabecera del bucle. En ella
se define un identificador de variable que va a actuar como contador asociado y que se denomina
variable de control del bucle (Vc), definiéndose al mismo tiempo su valor inicial (Vi), su valor final
(Vf) y el incremento (In) que esta variable de control va a adquirir en cada repetición. El formato es el
siguiente :

Para Vc de Vi a Vf [con Incremento In] hacer


Instrucción/es
Fin para

La variable de control tomará el valor inicial y va incrementándose en In en cada iteración de


bucle, de tal forma que el proceso finaliza cuando la variable de control supera el valor final (en caso
de incremento negativo se invierten los términos).

Los valores Vi, Vf e In pueden estar expresados por un valor constante, una variable o una
expresión ; entendemos que si son variables, estas no se modificarán en el cuerpo del bucle. De igual
forma, la Vc tampoco debe ser modificada en el cuerpo del bucle. El número de iteraciones que se
realizarán en una instrucción para viene dado por la expresión :

 Vf − Vi 
Ni = parte entera   +1
 In 

2.3.3.- Instrucciones de ruptura de secuencia.

El desarrollo lineal de un programa se interrumpe cuando se ejecuta una bifurcación. Las


bifurcaciones pueden ser, según el punto del programa a donde se bifurca: hacia adelante o hacia atrás.
Estas instrucciones no deben usarse.

Instrucción 1 Instrucción 1
Instrucción 2 Instrucción 2
Instrucción 3 Instrucción 3
... ...
Instrucción 9 Instrucción 9
... ...
Salto hacia delante Salto hacia atrás

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 29

Bifurcación incondicional:
Se realiza siempre que el flujo del programa pase por la instrucción sin necesidad del
cumplimiento de ninguna condición.

Ir a <etiqueta>

Bifurcación condicional:
La bifurcación depende del cumplimiento de una determinada condición. Si la
condición es cierta se realiza la bifurcación, y si es falsa no se realiza.

Si CONDICION ir a <etiqueta>

3.- Variables auxiliares de un programa.


3.1.- Contadores.
Los procesos repetitivos necesitan normalmente contar los sucesos o acciones internas de
bucle, es decir, el número de iteraciones que realiza el bucle.

Un contador es una variable cuyo valor se incrementa o decrementa en una cantidad constante
en cada iteración. El valor de esta constante no tiene por que ser la unidad; puede ser cualquier valor,
pero en todo el programa debe conservar siempre dicho valor. Antes de usarlo, debemos darle un valor
inicial (0, 1).

VAR_CONTADOR  VAR_CONTADOR ± CONSTANTE_INCREMENTO

Ejemplo: Carrera de coches VUELTAS  0, FALTA <- 0


VUELTAS  VUELTAS + 1
FALTAN  FALTAN - 1

3.2.- Acumuladores.
El acumulador o totalizador es una variable cuya misión es almacenar cantidades variables
resultantes de sumas sucesivas. Realiza la misma función que un contador con la diferencia de que el
incremento o decremento de cada suma es una variable en lugar de una constante. Antes de usarlo,
debemos darle un valor inicial.

VARIABLE_ACUM  VARIABLE_ACUM ± VARIABLE_INCREMENTO

Ejemplo: Cuenta bancaria SALDO  SALDO + IMPOSICIÓN


SALDO  SALDO - REINTEGRO

3.3.- Interruptores.
Son objetos que sólo pueden tomar uno de dos valores posibles CIERTO o FALSO, 1 ó -1,
ETC.) Se usan dándoles un valor inicial y en el lugar que corresponda del algoritmo se niega, de tal

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 30

forma que si después de negarlo examinamos su valor, será en cada caso contrario al anterior. Son
usados en casos como los siguientes:

· Ejecutar alternativamente acciones diferentes dentro de un bucle.


· Recordar en puntos determinados del programa la ocurrencia o no de un suceso anterior.
· Decidir en instrucciones alternativas cual es la acción a seguir.

SW = FALSO

SW = NO SW

?
NO SI

4.- Escritura de programas.


Un programa consta de tres partes diferentes:

· Cabecera de programa
· Declaración de objetos
· Cuerpo del programa

4.1.- Cabecera del programa.


En ella se expresa una palabra reservada que indica el comienza del programa seguida de un
identificador que da nombre al mismo.

La cabecera de programa es omitida habitualmente y en su lugar se escribe un comentario


relacionado con el programa.

Programa NOMBRE DEL PROGRAMA o COMENTARIO.

4.2.- Declaración de objetos.


Se describen todos los objetos usados en el programa, escribiendo el identificador del objeto, su
tipo y su valor inicial (siempre que lo consideremos necesario). El formato se ha visto anteriormente.

4.3.- Cuerpo del programa.


En esta sección se escriben todas las sentencias que debe realizar el programa. Debe comenzar
por la palabra ‘inicio‘ y acabar con ‘Fin Programa’; entre ellas se escriben las sentencias. El formato
general del programa es el siguiente:

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 31

Programa NOMBRE DEL PROGRAMA


// Descripción del programa

Entorno:
Nombre Tipo Valor inicial
Declaración de los objetos

Inicio
Descripción de las sentencias
Fin Programa.

Ejercicios:

1.- ¿Qué resultado se obtiene de los siguientes programas?

A) Programa EJEMPLO_1
Entorno:
Nombre Tipo
X Nº Entero
Y Nº Entero
Z Nº Entero
Inicio
X <-- 5
Y <-- 20
Z <-- X + Y
Visualizar X, Y, X * Y
X <-- X * X
Visualizar X, Z
Fin Programa

B) Programa EJEMPLO_2
Entorno:
Nombre Tipo V. Inicial
TRES Nº Entero 3
NX Nº Entero
DOBLE Nº Entero
Inicio
NX <-- 25
DOBLE <-- NX * TRES
Visualizar NX
Visualizar DOBLE
Fin Programa

2.- Algoritmo que nos diga si un número N es o no divisor de otro número M.


D = d * c + R ----> R=D-d*c
M=N*c+R ----> R=M-N*c
c = Cociente entero de (M / N)

3.- Algoritmo que decida cual de tres número es el mayor.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 32

4.- Algoritmo que lee dos números enteros positivos distintos y diga si el mayor es múltiplo del menor.

5.- Algoritmo que calcula las siguientes características de los alumnos de la clase:
a) ¿Cuantos alumnos tienen menos de 18 años?
b) ¿Cuantos alumnos tienen 18 años?
c) ¿Cuantos alumnos tienen 19 años?
d) ¿Cuantos alumnos tienen más de 19 años?

6.- Suma de los primeros números enteros positivos.

7.- Calcular el factorial de N.

8.- Suma de los N primeros números pares enteros positivos.

9.- Suma del dinero de los alumnos de una clase.

10.- Edad media de los alumnos de una clase.

11.- Tabla de multiplicar de un número N (1..10).

12.- Producto de dos números por sumas sucesivas.

13.- División (cociente y resto) de dos números por restas sucesivas.

14.- Calcula A elevado a B por productos sucesivos (+ y -).

15.- Número de dígitos que tiene un número N entero positivo.

16.- Invertir un número N entero positivo.

17.- Valor del número e como suma de la serie :

1 1 1 1 1 1
e=∑ = + + + + ...+
i ! 0! 1! 2 ! 3! N!

18.- Escribir las tablas de multiplicar de 1 a 10.

19.- Imprimir y sumar los cuadrados de los números menores que N.

20.- Un capital C se coloca a un rédito R. ¿Al cabo de cuantos años de duplicará el capital ?

C×R
I=
100

21.- Obtener la raíz cúbica de todos los números leídos por teclado hasta que encontremos el 0.

22.- Programa que obtenga e imprima la lista de los intereses producidos y el capital acumulado
anualmente por un capital inicial C impuesto un rédito R durante N años a interés compuesto. El
capital se incrementa cada año con los intereses producidos en el mismo.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 33

23.- Calcular la sucesión de Fibonacci. Cada término es igual a la suma de los dos anteriores. Por
definición el 1º es 0 y el 2º es 1.

24.- Programa que lee una secuencia de números no nulos, terminada con la introducción de un cero, y
obtiene e imprime el mayor, visualizando un mensaje de si ha aparecido o no un número negativo.

25.- Igual al anterior. Dar el mayor, el menor y el número de veces que ha aparecido cada uno de ellos.

26.- Decir si un numero N es primo.

27.- Generar la lista de los N primeros números primos.

28.- Números perfectos menores que 1000. Un número es perfecto si la suma de sus divisores, excepto
él mismo, es igual a propio número.

29.- Determinar si dos números enteros positivos son amigos. Son amigos si la suma de los divisores
del primero excepto él mismo es igual al segundo y viceversa.

30.- Generar la lista de los divisores de un número N.

31.- Programa que lee el nombre y sueldo de cien personas y escribe el nombre y sueldo de la persona
que más cobra y de la que menos.

32.- Indicar si un número N es capicúa.

33.- Calcular los múltiplos de N inferiores a M que sean capicúas.

34.- Programa que lee una secuencia de calificaciones numéricas (0..10) que finaliza con el valor –1 y
calcula y escribe la media aritmética, el número y porcentaje de aprobados y de suspensos.

35.- Programa que lee una secuencia de nombres y escribe el número de veces que se repite el primero
de ellos.

36.- Programa que calcula el mayor número primo menor que N.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 35

4.- SUBPROGRAMAS.

1.- Introducción a los subprogramas.


Para solucionar un problema complejo debemos dividirlo en subproblemas más simples y
sencillos de solucionar; a su vez, estos se pueden dividir también en otros más sencillos aún. Este
método de diseñar la solución de un problema (principal) obteniendo soluciones parciales
(subprogramas) se denomina “Diseño descendente” ya que partimos de un problema general a
soluciones específicas de problemas puntuales. Cada uno de estos subproblemas se desarrolla de forma
independiente del resto, de forma que unos no influyen sobre los otros, en cuanto a lo que al desarrollo
se refiere.

Llamadas
Programa
Subprograma

Retornos

Un subprograma puede realizar las mismas acciones que un programa principal: entrada de
datos, salida de datos, procesos. Sin embargo, un subprograma lo utiliza el programa para un propósito
específico: recibir datos desde el programa y devolverle los resultados. El subprograma ejecuta una
tarea y cuando la finaliza retorna el control al programa, al punto desde donde se realizó la llamada;
esta operación se puede realizar tantas veces como sea necesario y desde cualquier punto del
programa. A su vez, un subprograma también puede realizar llamadas a otros subprogramas,
comportándose de la misma que lo hace que cuando la llamada proviene del programa principal.

Existen dos tipos de subprogramas: funciones y procedimientos o subrutinas.

Programa Subprograma 1 Subprograma 1.1

Subprograma 2

2.- Funciones.
Matemáticamente una función es una operación que toma uno o más valores llamados
argumentos y produce un valor denominado resultado (valor de la función para los argumentos dados).
Así, por ejemplo
x
f ( x) =
1+ x
2

donde f es el nombre de la función y x es el argumento. El argumento ‘x’, denominado


parámetro formal, no lleva asociado ningún valor y es utilizado en la definición de la función. Para

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 36

evaluar la función debe dársele un valor real a ‘x’, denominado parámetro actual, y con él calcular el
resultado.

Cada lenguaje de programación implementa sus propias funciones: cos(x), sin(x). Cada función
se invoca utilizando su nombre en una expresión con los argumentos actuales, los valores reales,
encerrados entre paréntesis.

Existen dos tipos de funciones: las funciones incorporadas en el propio sistema, llamadas
funciones internas, y las definidas por el usuario o funciones externas (usadas cuando las funciones
internas no satisfacen las necesidades de los cálculos).

El programa llama a la función con el nombre de esta en una expresión seguida de una lista de
argumentos y la función, después de realizar los cálculos, devuelve un único valor.

2.1.- Declaración de funciones.


Una función tiene una constitución similar a la de un programa; consta de cabecera, bloque de
declaraciones, cuerpo con las sentencias ejecutables y un final.

La declaración de una función se hace de la siguiente forma:

FUNCION IdentificadorDeFuncion([[Parámetro es Tipo], ...]) Tipo de Retorno


Entorno:
Declaraciones de objetos
Inicio
Sentencias ejecutables
Fin de función

En el cuerpo de la función debe existir una sentencia de asignación -o más de una- en la que se
asigne un valor (el de retorno) al nombre de la función; es decir debe haber una sentencia como esta:
IdentificadorDeFuncion <--- Expresión.

Para el ejemplo anterior podemos escribir el siguiente código de función:

Función F( X es entero) es real


Inicio
F  x/(1 + Cuadrado(X))
Fin de función

Los argumentos de la declaración de la función se denominan parámetros formales; son


identificadores de variables con su tipo de dato correspondiente y que sólo se utilizan dentro del
cuerpo de la función, es lo que después conoceremos como variables locales.

2.2.- Invocación a las funciones.


Una función puede ser llamada desde cualquier parte del programa principal o desde otro
subprograma de la forma siguiente:

IdentificadorDeFuncion (Lista de parámetros actuales)

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 37

Los parámetros utilizados en la llamada a la función se denominan parámetros actuales, que, a


su vez, pueden ser constantes, variables, llamadas a otras funciones, etc. Cada vez que se llama a una
función se establece una correspondencia entre los parámetros actuales y los parámetros formales de la
siguiente manera:

· Debe haber el mismo número de parámetros actuales que de parámetros formales.


· Hay una correspondencia uno a uno de izquierda a derecha.
· El parámetro actual debe ser del mismo tipo que su correspondiente parámetro formal o un tipo
de dato compatible.

Una llamada a una función implica que se realicen los siguientes pasos:

· A cada parámetro formal se le asigna el valor real de su parámetro actual.


· Se ejecuta el cuerpo de sentencias ejecutables de la función de la forma que lo hace cualquier
programa; entre estas sentencias debe estar la asignación de un valor al nombre de la función.
· Se devuelve el valor de la función previamente asignado y se retorna al punto desde el que se
realizó la llamada a la función.

Ejemplo: Calcular Y= Xn
Función Potencia(x es real, n es entero) es real
Entorno:
I es entero
y es real
Inicio:
y  1
para i de 1 a absoluto(n) hacer
y  y*x
fin para
si n<0 entonces
y  1/y
fin si
Potencia= y
Fin de función

Ejercicios: ¿Dos números son amigos?


¿Es capicúa un número?
Cálculo del factorial.

3.- Procedimientos o subrutinas.


Cuando necesitemos subprogramas que retornen más de un valor al lugar desde donde se
invocaron, no pueden utilizarse funciones y debemos recurrir a la utilización de procedimientos o
subrutinas.

Un procedimiento o subrutina es un subprograma que ejecuta un proceso específico. No tiene


ningún valor asociado al nombre y, por tanto, no puede formar parte de una expresión. La subrutina se
invoca desde cualquier parte del programa con sólo indicar su nombre y los parámetros asociados; una
vez ejecutadas todas las sentencias que la componen se devuelve el control al programa que la llamó.

Un subrutina se diferencia de una función en los siguientes puntos:

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 38

· Una vez ejecutada la subrutina, el control pasa a la siguiente sentencia desde la que se
invocó; la función continúa en la misma sentencia.
· La subrutina no tiene asociado ningún valor a su nombre y puede devolver 0, 1 o más
valores en forma de lista de parámetros; la función tiene asociado un valor y sólo puede
retornar un valor.

La declaración de una subrutina se realiza de la siguiente forma:

SUBRUTINA IdentificadorDeSubrutina([[Parámetro es Tipo], ...])


Entorno:
Declaraciones de objetos
Inicio
Sentencias ejecutables
Fin de función

La llamada a una subrutina se realiza mediante la sentencia

IdentificadorDeSubrutina(Lista de parámetros actuales)

Los parámetros formales y los parámetros actuales tienen el mismo significado que en las
funciones. Cuando se realiza una llamada a la subrutina, los parámetros formales toman el valor o la
dirección de su correspondiente parámetro actual.

4.- Ámbito: variables locales y globales.


Disponemos de dos tipos de variable dependiendo del lugar en el que se declaren: variables
locales y variables globales.

· Una variable local es aquella que está declarada dentro de un subprograma y es distinta de
otras variables con el mismo nombre declaradas en cualquier otro lugar del programa,
subprograma o programa principal.
· Una variable global es aquella que está declarada para el programa completo.

La parte del programa en que está declarada la variable se conoce como ámbito de la variable y
es el fragmento de programa donde puede ser utilizada; es decir, el ámbito es la parte del programa
donde la variable es visible.

El uso de variables locales hace posible la independencia de los subprogramas; esta se consigue
con la comunicación a través de los parámetros entre el subprograma y el programa principal. Para
utilizar un subprograma sólo necesitamos conocer lo que hace, en ningún caso cómo lo hace ni cómo
está programado. Por el contrario, las variables globales tienen la ventaja de compartir información de
diferentes subprogramas sin una correspondiente entrada de lista de parámetros.

El ámbito de un identificador es la parte del programa donde se conoce el identificador. Si un


subprograma está definido localmente a otro subprograma -no todos los lenguajes de programación lo
permiten-, tendrá significado sólo dentro del ámbito de ese subprograma. A las variables y constantes
les sucede lo mismo: sólo pueden utilizarse dentro del subprograma donde fueron definidas.

Ejercicio: Lista de números primos.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 39

5.- Comunicación con subprogramas: paso de parámetros.


Cuando un programa llama a una subrutina, la información se comunica a través de los
parámetros y se establece una correspondencia automática entre los parámetros formales y los actuales.
Los actuales son sustituidos o utilizados en lugar de los parámetros formales. La correspondencia entre
los parámetros se establece aparejando los parámetros actuales y formales según su posición en la lista
de parámetros.

Los parámetros se pueden clasificar como:

· Entradas: proporcionan valores desde el lugar que llama y son utilizados dentro del
subprograma. El subprograma no se comunica con el exterior salvo que utilicemos una función,
que tiene asociado un valor. P.P ---> S.P.
· Salidas: producen los resultados del subprograma. El subprograma se comunica con el exterior
a través de los parámetros. P.P <--- S.P.
· Entradas/Salidas: los parámetros se utilizan tanto para mandar argumentos a un subprograma
como para devolverlos. P.P <---> S.P.

Existen dos métodos principales para pasar los parámetros a subprogramas: paso por valor y
paso por variable o referencia.

5.1.- Paso por valor.


Son parámetros unidireccionales utilizados para proporcionar información al subprograma pero
en ningún caso se realizará de forma contraria. Los parámetros se tratan como variables locales y los
valores iniciales se proporcionan copiando los valores de los correspondientes argumentos.

Los parámetros formales -locales a la función- reciben como valores iniciales los valores de los
parámetros actuales y con ellos se ejecutan las acciones descritas en el subprograma. Cualquier cambio
que se realice en los parámetros formales (variables locales) no se verá reflejado en el programa
llamante; por tanto, los parámetros son sólo de entrada.

En la cabecera del subprograma no ha habido ningún cambio con respecto a las declaraciones
anteriores: sólo se declaran los parámetros formales con su correspondiente tipo de dato. En la llamada
al subprograma los parámetros actuales son expresiones, ya que sólo importa el valor del argumento,
no el argumento en sí.

5.2.- Paso por referencia.


Cuando requerimos que ciertos parámetros sirvan como parámetros de salida, que se devuelvan
resultados al lugar llamante, utilizamos el método de paso por referencia, variable o dirección. La
unidad que llama pasa a la unidad llamada la dirección del parámetro actual. Una referencia al
correspondiente parámetro formal se trata como una referencia a la posición de memoria, cuya
dirección se ha pasado. Entonces una variable pasada como parámetro real es compartida, se puede
modificar directamente por el subprograma.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 40

El lugar de la memoria en que se almacena la variable correspondiente al parámetro actual se


utiliza para pasar información de entrada y/o salida, en ambas direcciones. Por ello, el parámetro actual
pasado por referencia tiene que ser una variable, con una dirección de memoria, no puede ser una
expresión.

En seudocódigo la forma de indicar que un parámetro va a ser pasado por referencia consiste en
anteponerle la palabra “REFERENCIA” o simplemente indicándolo como un comentario.

Los dos métodos se aplican tanto a las funciones como a los procedimientos.

· Una función puede devolver los valores al programa principal como valor asociado a la
función y a través de argumentos por referencia.
· Un procedimiento sólo puede devolver valores por medio de los parámetros por referencia.

Ejercicios:

1.- División de dos números por restas sucesivas. Calcular el cociente y el resto.

2.- Calcular las raíces de la ecuación de segundo grado.

3.- Calcular el coeficiente del binomio


m m!
  =
 n  n! ( m − n )!

4.- Calcular el máximo común divisor por el “Algoritmo de Euclides”.


Dividir el mayor por el menor.
Dividir el divisor por el resto.
Seguir dividiendo el último divisor por el último resto hasta que la división sea exacta.
El último divisor es el máximo común divisor.

5.- Calcular el producto de dos números por el “Algoritmo ruso del producto”. Consiste en duplicar el
primer factor y dividir (cociente entero) por dos el segundo, obteniendo un producto equivalente, salvo
si el segundo factor es impar, en cuyo caso es necesario acumular previamente el primero en donde se
va a obtener el resultado. El proceso finaliza cuando el segundo factor se hace cero.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 41

5.- CARACTERÍSTICAS GENERALES DE LOS PROGRAMAS EN


LENGUAJE C.

1.- Estructura de un programa C.


1.1.- Comentarios.
Hay dos formas de escribir comentarios en un programa:

· Como una tira de caracteres encerrados entre “/*” (comienzo del comentario) y “*/” (fin del
comentario). Pueden ocupar una o más líneas y no se pueden anidar.

· Como una tira de caracteres precedida por “//”. Sólo actúa desde donde se encuentra hasta
el final de la línea.

1.2.- Cabecera.
En ella se sitúan las directivas usadas por el preprocesador de C. Las directivas son palabras
reservadas que comienzan por el carácter ‘#’ y son detectadas por el preprocesador al llamar al
compilador. El preprocesador tiene la función de leer el programa fuente y sustituir las definiciones de
las directrices por los valores correspondientes dentro del programa. Las más habituales son las dos
siguientes :

#define : Usada para la definición de constantes simbólicas. El signo ‘#’ debe ir en la primera
columna seguido de la palabra ‘define’ y a continuación el identificador (habitualmente en
mayúsculas) seguido de su valor.

#define IDENTIFICADOR valor

#define PI 3.14159
#define ANGULORECTO 90

#include : Incluye archivos externos en el programa fuente actual. Contienen declaraciones de


funciones, variables y constantes globales.

#include <ArchivoCabecera.h>
#include “ArchivoCabecera.h”

#include <stdio.h>
#include “c:\datos\miarch.h”

Si el nombre del archivo cabecera está encerrado entre los signos de mayor y menor, dicho
archivo se buscará en el directorio por omisión en el que se encuentran los archivos cabecera ; si va
encerrado entre comillas, el archivo debe encontrarse en el directorio indicado explícitamente o en el
directorio actual de trabajo.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 42

1.3.- Entorno.
Se definen las variables globales del programa y los prototipos de las funciones, siempre y
cuando, tanto unas como otras, se consideren necesarias. Un formato muy general de definición de
variables, tanto globales como locales, es el siguiente :

TipoDeDato Identificador [= Valor] ;

1.4.- Función main().


Un programa C es una colección de una o más funciones. La forma genérica de definición de
una función en C es:

TipoDeDato NombreDeLaFuncion (TipoDeDato Argumento...)


{
Definición de objetos locales a la función.
Instrucciones
}

La función ‘main()’ es una función especial ya que es la primera que se llama cuando se ejecuta
el programa; indica el comienzo del mismo. Puede encontrase en cualquier lugar del programa aunque
generalmente está situada en la zona superior del mismo (por razones de claridad y estructuración).

En un programa C siempre debe existir esta función, y además única, para que el compilador
sepa donde empezar la ejecución. La llave que cierra la función indica el final del programa. Como
toda función, esta puede recibir parámetros de entrada y retornar un valor de salida, aunque en esta
ocasión se realizará desde y hacia el sistema operativo.
#include <stdio.h>
main()
{ printf(“El primer programa en lenguaje C”) ;
}

2.- Elementos del lenguaje C


2.1.- Léxico de C.
El lenguaje C dispone de diversos elementos básicos necesarios para la construcciones de
programas:

· Palabras clave : juego de instrucciones del lenguaje (for, while).

· Separadores : usados para separar los diferentes elementos que forman el lenguaje (espacio
en blanco, tabulador, cambio de línea y comentario).

· Operadores : usados para formar expresiones de distintos tipos.

· Identificadores : nombres de objetos (variables, constantes, etc.) definidos por el


programador. El lenguaje C hace distinción entre las letras mayúsculas y las minúsculas.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 43

2.2.- Sintaxis de C.
Las reglas generales para escribir programas en C son las siguientes :

· Toda declaración y sentencia simple debe finalizar en un punto y coma ; este es usado
como terminador de la instrucción.

· El par de llaves ({}) se usa para la construcción de bloque de programa (sentencias


compuestas). Los bloques de programa son grupos de sentencias lógicamente relacionadas
entre si. A continuación de la llave de cierre no va punto y coma.

3.- Ejemplo de programa en lenguaje C.


#include <stdio.h>
#include <conio.h>
unsigned Factorial(int N) ;
void main()
{ int Numero ;
printf("Calcular el factorial de: ") ;
scanf("%d", &Numero) ;
printf("%d! = %u", Numero, Factorial(Numero)) ;
getche();
}
unsigned Factorial(int Numero)
{ unsigned F = 1;
for ( ; Numero > 0 ; F *= Numero--);
return F ;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 45

6.- TIPOS DE DATOS, OPERADORES Y EXPRESIONES

1.- Tipos de datos.


Hay dos tipos de datos en C: fundamentales y derivados. Los tipos de datos fundamentales son
los siguientes:

char Un octeto (8 bits), capaz de contener un elemento del juego de caracteres


ASCII. Sólo se imprimen los positivos.

[signed|unsigned] char

int Un entero (16 bits o 32 bits), normalmente del tamaño de los enteros de la
máquina donde se ejecuta.

[signed|unsigned] [short|long] int

En general: Tamaño(short) <= Tamaño(int)


Tamaño(int) <= Tamaño(long)

float Un número decimal en punto flotante de precisión normal (32 bits).

float

double Un número decimal en punto flotante de doble precisión (64 bits).

[long] double

enum Una lista de enteros que pueden ser tomados por una variable de ese tipo. Los
valores del tipo enumerado se representan con identificadores, que serán las
constantes del nuevo tipo.

void Usado para la declaración de funciones que no retornan ningún valor o para
declarar punteros a un tipo no especificado.

Existen, además, una serie de cualificadores que se pueden aplicar a los tipos básicos
modificando su rango de valores y/o su tamaño:

short Se aplica al tipo de datos int. Si se usa sólo, se asume que es un tipo de datos int.

long Se aplican a los tipos de datos enteros y reales de doble precisión. Si se usa sólo,
se asume que es un tipo de datos int.

Tanto long como short, pueden proporcionar diferentes tamaños a los tipos de datos.

signed, unsigned Se aplican a los tipos de datos int, char, long y short. Los unsigned son
positivos o 0 y los signed son positivos, negativos o 0. Si se usan sin
ningún tipo de dato, se asume que es int. Por omisión, todos los tipos de
datos son con signo.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 46

Resumen de los tipos de datos de 16 bits.

Tipo Longitud Rango

unsigned char 8 bits 0 to 255


char 8 bits -128 to 127
enum 16 bits -32,768 to 32,767
unsigned int 16 bits 0 to 65,535
short int 16 bits -32,768 to 32,767
int 16 bits -32,768 to 32,767
unsigned long 32 bits 0 to 4,294,967,295
long 32 bits -2,147,483,648 to 2,147,483,647
float 32 bits 3.4 x 10-38 to 3.4 x 10+38
double 64 bits 1.7 x 10-308 to 1.7 x 10+308
long double 80 bits 3.4 x 10-4932 to 1.1 x 10+4932

near (pointer) 16 bits no aplicable


far (pointer) 32 bits no aplicable

Resumen de los tipos de datos de 32 bits.

Tipo Longitud Rango

unsigned char 8 bits 0 to 255


char 8 bits -128 to 127
short int 16 bits -32,768 to 32,767
unsigned int 32 bits 0 to 4,294,967,295
int 32 bits -2,147,483,648 to 2,147,483,647
unsigned long 32 bits 0 to 4,294,967,295
enum 16 bits -2,147,483,648 to 2,147,483,647
long 32 bits -2,147,483,648 to 2,147,483,647
float 32 bits 3.4 x 10-38 to 3.4 x 10+38
double 64 bits 1.7 x 10-308 to 1.7 x 10+308
long double 80 bits 3.4 x 10-4932 to 1.1 x 10+4932

near (pointer) 32 bits no aplicable


far (pointer) 32 bits no aplicable

2.- Constantes.

2.1.- Constantes enteras.


El tipo de una constante entera viene determinado por su valor. En caso de ser negativa, debe
especificarse el signo menos. También se puede especificar el tipo de la constante añadiendo los
sufijos L(long), U(unsigned int) o UL(unsigned long int). Además se puede especificar la base de
representación del número con los prefijos 0(octal), 0X(hexadecimal). Podemos usar el sufijo (tipo) y
el prefijo (base) en el mismo número. Se pueden escribir en mayúsculas o minúsculas.

31(decimal) = 037(octal) = 0x1f(hexadecimal)

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 47

2.2.- Constantes reales.


Está formada por una parte entera, seguida de un punto decimal, y una parte fraccionaria.
También se puede usar la notación científica. Tiene siempre el tipo double, a no ser que se usen los
sufijos F(float) o L(long double).

2.3.- Constantes de un sólo carácter.


Es un único carácter escrito entre comilla simples. Su valor es el número que ocupa el carácter
dentro de la tabla ASCII. Estas constantes participan en las operaciones numéricas como cualquier otro
número.

Algunos caracteres no imprimibles se representan como constantes mediante secuencias de


escape ( \n: nueva linea, \t: tabulador, \0: nulo, \\: barra invertida, \’: comilla simple); aparece como
dos caracteres, aunque sólo es uno.

Cualquier carácter de la tabla ASCII se puede representar de la forma ‘\ooo’(octal, de 1 a 3


dígitos) o '\xhh'(hexadecimal, dos dígitos).
#define FORMFEED '\014'
#define BEEP '\x7'

2.4.- Constantes de cadena.


Es una tira de cero o más caracteres encerrados entre comillas dobles; estas no forman parte de
la tira, sólo delimitan. Se pueden usar también las secuencias de escape.

Una tira es un vector cuyos elementos son caracteres. El compilador sitúa el carácter nulo (\0)
al final de la cadena para conocer cual es el final de la misma. La memoria requerida para almacenar
una cadena es su longitud más uno (el carácter nulo).

"La casa" "A" 'A'


L a C a s a \0 A \0 65

Debemos distinguir entre una contante de tipo carácter y una cadena. La primera representa el
valor numérico del carácter y la segunda es una tira de caracteres finalizada con el carácter nulo.

2.5.- Expresiones constantes.


Es una expresión formada únicamente por constantes. Se evalúa en tiempo de compilación.
#define MINUTOS 60
#define SEGUNDOS 60
...
Tiempo = MINUTOS * SEGUNDOS

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 48

2.- Variables: declaración.


Todas las variables deben declararse antes de que puedan ser utilizadas. Una declaración
especifica un tipo de dato seguido de una o más variables.

El formato general para la definición de objetos en programa C es:

Tipo_de_dato Identificador [,Identificador]...;

int inf, sup, paso; int inf = MINIMO;

int sup = MAXIMO;

int paso = 1;

char c, linea[1000]; char c = '\A';

char linea[1000];

Las variables pueden ser inicializadas en su declaración como en el ejemplo anterior. Las
globales las inicializa el compilador a 0 y las locales tendrán un valor indefinido. La declaración de un
objeto puede ir precedida de la palabra 'const' indicando que este objeto es una contante.
const int Dias = 10; (const Dias = 10;)
const char nombre[] = "Antonio"

3.- Operadores.
3.1.- Operadores aritméticos.
Los operadores aritméticos binarios son: +, -, *, / y %. El % (el resto de la división entera) sólo
puede usarse con operandos enteros.

3.2.- Operadores relacionales y lógicos.


Los operadores relacionales son: >, >=, <, <=, ==, !=.
Los operadores lógicos son: &&(y), ||(o) y !(no).

Cuando en una expresión aparecen los operadores lógicos, su evaluación finaliza tan pronto
como se conozca su resultado.

Mientras Primo && c > n &&

Una expresión lógica retorna un valor 0 si su evaluación es falsa y un 1 (o distinto de 0) si esta


es verdadera. El operador ! convierte un operando distinto de 0 (o valor cierto) en cero y uno con valor
0 (o valor falso) en uno.

Si N % 2
visualizar N, "es impar" /* N % 2 <> 0 */
si no
visualizar N, "es par" /* N % 2 = 0 */

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 49

3.3.- Operadores de incremento y decremento.


Hay dos operadores en C para incrementar o decrementar variables (sólo pueden aplicarse a
variables, nunca a expresiones):

++ Suma uno al operando. A++ es equivalente a A  A + 1.

-- Resta uno al operando. A— es equivalente a A  A - 1.

La característica fundamental de estos dos operadores es que se pueden usar como prefijos
(delante del operando, ++i) o como sufijos (detrás del operando i--). En ambos caso el efecto
producido sobre la variable es el mismo, incremento o decremento de la misma en una unidad; sin
embargo, existe una diferencia: cuando se aplica como prefijo (++i), modifica el valor de la variable
antes de usar su valor y cuando se aplica como sufijo (i--), modifica el valor de la variable después de
usar su valor. Esto significa que en el contexto donde se utilice el valor y no sólo su efecto, ++i e i++
son distintos. En el fragmento de programa siguiente es indiferente usar el operador como prefijo que
como sufijo:

Mientras i < n Mientras i < n


++i i++

En las siguientes expresiones, depende de que se use como prefijo o sufijo para obtener un
resultado u otro:
int j = 1, i = 10;
char c = 'D';
visualizar ++i /* 11 */
visualizar i++ /* 10 */
visualizar ++i, i++ /* Resultados no deseados 12 10 ( de derecha a izquierda)*/
visualizar i++, ++i /* Resultados no deseados 11 11*/
visualizar ++c /* E */
visualizar c++ /* D */
i = c++ /* i <-- ASCII('D'), c <-- 'E' */
i = ++c /* i <-- ASCII('E'), c <-- 'E' */
i = i++ + j /* Expresión no deseada i <-- 12 */

3.4.- Operadores de asignación y expresiones.


El operador de asignación es el signo = y el formato general de la sentencia de asignación es:

Variable = Expresión

Además, en las expresiones donde la variable receptora del valor también forma parte de la
expresión, esta se puede simplificar.

Si “Identificador” es una variable y “Expresión” una expresión válida, entonces

Identificador op= Expresion

es equivalente a

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 50

Identificador = (Identificador) op (Expresion)

Los paréntesis implican asociación:

x *= y + 1 ===> x = x * (y + 1)

La mayor parte de los operadores binarios disponen del correspondiente operador op=, donde
op es: +, -, *, /, %, <<, >>, &, ^, |.

Usar sentencias de asignación de esta forma hace que el compilador genere un código
ejecutable mas eficiente y normalmente ayuda a la comprensión de las sentencias.

Los operadores de asignación pueden aparecer en expresiones, aunque debe evitarse en la


medida de lo posible.

3.5.- Operador condicional.


En la expresión "e1 ? e2 : e3" primero se evalúa e1. Si es cierta, se evalúa la expresión e2,
siendo este el valor de la expresión condicional. En otro caso, se evalúa e3, y éste será su valor.

"E1 ? E2 : E3" es equivalente a "si E1 entonces E2 si no E3".

Si E2 y E3 son de diferente tipo de dato, el tipo del resultado se establece por las reglas de
conversión.
Ejemplos:

z = (a > b) ? a : b;

Si a > b
z=a
si no
z=b
Fin si

Suma = Numero % I ? Suma : Suma += I;


Suma += Numero % I ? 0 : I;

si Numero % I = 0
Suma = Suma + I
Fin si

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


printf("%2d%c", i, (i%5==4 || i==99)?'\n':' ');

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


if (i%5==4 || i==99)
printf("%2d\n",i);
else
printf("%2d ",i);

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 51

4.- Conversión de tipos.


Cuando usamos una expresión aritmética, tal como a + b, cuando a y b son de diferente tipo, en
general, el operando de tipo de dato inferior es ascendido al tipo de dato superior antes de evaluar la
expresión y el resultado es del tipo superior. Sólo se realizan aquellas que tienen sentido. Los
caracteres y enteros (char e int) se pueden mezclar libremente en las expresiones aritméticas: todo char
de una expresión se convierte a int automáticamente.
int Minusculas(int c)
{ if (c >= 'A' && c <= 'Z')
Return(c + 'a' - 'A');
else
Return(c);
}

Las reglas de conversión, por orden, son las siguientes:

Tipo Dato Conversión A Método


char int Zero or sign-extended
unsigned char int Zero-filled high byte (always)
signed char int Sign-extended (always)
short int Same value; sign extended
unsigned short unsigned int Same value; zero filled
enum int Same value
· Si un operando es de tipo long double, el otro se convierte.
· Si un operando es de tipo double, el otro se convierte.
· Si un operando es de tipo float, el otro se convierte.
· Si un operando es de tipo unsigned long, el otro se convierte.
· Si un operando es de tipo long, el otro se convierte.
· Si un operando es de tipo unsigned, el otro se convierte.
· En cualquier otro caso, los dos operandos son de tipo int.

Las conversiones también tienen lugar en asignaciones; el valor de la derecha se convierte al


tipo de la izquierda, que es el tipo del resultado. Cuando el tipo de dato de la variable que recibe el
valor es de un tipo igual o superior, no hay variación en el valor de la variable o expresión asignada. Si
es al contrario, se truncará el valor, redondeando o eliminando los bits más significativos.

· De float a int se trunca la parte fraccionaria.


· double se convierte en float por redondeo.
· Los enteros de mayor magnitud (long) se convierten al tipo short o a caracteres por eliminación
de los bits de orden superior.

Otra forma de realizar una conversión del tipo de dato es indicando explícitamente la
conversión mediante una construcción denominada cast (casting).

(Nombre_de_tipo) expresión (C)


Nombre_de_tipo (expresión) (C++)

La expresión se convierte al tipo de dato citado mediante las reglas de conversión anteriores.

Sqrt((double)n): convierte n al tipo double pero no modifica su valor.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 52

5.- Precedencia y orden de evaluación de los operadores.


La siguiente tabla muestra el orden de precedencia y asociatividad de todos los operadores. Los
operadores situados en la misma línea tienen la misma precedencia y las filas están colocadas en orden
de precedencia decreciente.

Operadores Asociatividad

(), [], ->, ::, . De izquierda a derecha


!, ~, +, -, ++, --, &, *, sizeof, new, delete De derecha a izquierda
.*, ->* De izquierda a derecha
*, /, % De izquierda a derecha
+, - De izquierda a derecha
<<, >> De izquierda a derecha
<, <=, >, >= De izquierda a derecha
==, != De izquierda a derecha
& De izquierda a derecha
^ De izquierda a derecha
| De izquierda a derecha
&& De izquierda a derecha
|| De izquierda a derecha
?: De izquierda a derecha
=, *=, /=, %=, +=, -=, &=, ^=, |=, <<=, >>= De derecha a izquierda
, De izquierda a derecha

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 53

7.- ENTRADA Y SALIDA POR PANTALLA

1.- Acceso a la librería estándar.


Las funciones que proporciona el lenguaje para la entrada y salida estándar no forman parte del
propio lenguaje. Estas pertenecen a la librería estándar de E/S.

Todo fichero fuente que use funciones de la librería estándar de E/S deberá contener la línea
"#include <stdio.h>" al principio del mismo. El fichero stdio.h define ciertas macros y variables
empleadas por la librería estándar de E/S así como el prototipo de las funciones de E/S.

2.- Funciones de entrada y salida estandar.


2.1.- Salida con formato. Función printf().
Básicamente, lo que realiza esta función es imprimir una cadena de caracteres sobre la pantalla
del ordenador (por stdout).

int printf(const char *format[, argument, ...]);


int printf("Cadena de control" [, argumentos, ...]);

printf() convierte (da formato) y visualiza sus argumentos en la salida estándar, bajo la
supervisión de la cadena de control o formato. La cadena de control es una constante de tipo cadena
que contiene dos tipos de objetos: caracteres ordinarios, que se visualizan por pantalla, y los comandos
de control de formato o especificadores de conversión, cada uno de los cuales origina una conversión
del siguiente argumento de printf(). Retorna el número de bytes visualizados.

Cada comando de control comienza por el símbolo de tanto por ciento (%) y finaliza en un
carácter de control. El formato del comando de control por orden de escritura es el siguiente:

% [flags] [ancho] [.precisión] [h|l|L] Carácter_de_control

[flags] Caracteres señalizadores. Son opcionales.


- Justifica el argumento a la izquierda y rellena con blancos por la
derecha. Si no se especifica, se justifica a la derecha y se rellena por la
izquierda con blancos o ceros.
+ Muestra el signo + o - en la salida.

[ancho] Especificador del ancho del campo. Es opcional. Indica el número mínimo de
caracteres que se deben imprimir; se rellena con blancos o ceros (si se escribe un
0 delante del ancho, que no implica base octal).

[.prec] Especificador de precisión. Es opcional. Es una cadena de dígitos (la precisión),


que indica el número máximo de caracteres que se imprimirán de la cadena, o el
número de dígitos que se deben imprimir a la derecha del punto decimal de un
valor de tipo real (float o double). Si es un número entero, indica el número
mínimo de dígitos a imprimir.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 54

[h|l|L] Modificador del tamaño del campo. Es opcional. Este modificador afecta a la
forma en que la función printf() debe interpretar el tipo de dato del argumento
que se escribe a continuación.
h = short int (dioux)
l = Long (dioux)
L = long double (efg)
l = Double (efg). Si es pto. Flotante.

Carácter
de control Carácter de conversión de tipo de dato. Es obligatorio. Indica la forma en que
debe realizarse la conversión del argumento para que sea visualizado por
pantalla. Estos, son los siguientes:

d, i Entero Entero decimal con signo


o Entero Entero octal con signo
u Entero Entero decimal sin signo
x Entero Entero hexadecimal sin signo (a,b,c,d,e,f)
X Entero Entero hexadecimal sin signo (A,B,C,D,E,F)
f P.Flotante Argumento de tipo float o double. Es un real con signo de
la forma [-]dddd.dddd. El número de dígitos decimales
viene indicado por la precisión, 6 por defecto.
e,E P.Flotante Argumento de tipo float o double. Es un real con signo de
la forma [-]d.dddd o e[+/-]ddd. El número de dígitos
decimales viene indicada por la precisión, 6 por defecto.
g,G P.Flotante Usa el más corto de entre %e y %f. La parte decimal se
imprime si es necesario. Redondea dependiendo de la
precisión.
c Carácter Visualiza un carácter simple.
s Cadena Visualiza una tira de caracteres hasta que se encuentre
con el carácter nulo (\0) o alcanzar el número de
caracteres indicados en el especificador de precisión.
% Nada Visualiza el carácter %.

Si el carácter que va detrás del % no es uno de los anteriores, se visualiza. Si el número de


caracteres de control no se corresponden (en número y tipo) con los argumentos, los resultados son
imprevisibles.

También se usan constantes de tipo carácter no imprimibles en la cadena de control. Estos son:

'\a' Beep.
'\b' Backspace (retroceso).
'\n' Nueva línea.
'\t' Tabulador horizontal.
'\\' Visualiza \.
'\"' Visualiza ".
'\'' Visualiza '.
'\ooo' Valor ASCII octal.
'\xhh' Valor ASCCI hexadecimal.
int main(void)
{
char Cadena[10] = "La casa";

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 55

int Entero = 12345;


int EnteroH = 255;
long int LargoEnt = 65432;
float Real = 111111.422222;
printf(":%s:\n",Cadena); // Justifica a la derecha. No deja espacios
printf(":%15s:\n",Cadena); // Justifica a la derecha. Rellena a blancos.
printf(":%-15s:\n",Cadena); // Justifica a la izquierda. Rellena a blancos.
printf(":%-15.4s:\n",Cadena); // Justifica a la izquierda y visualiza los
primeros (4). Rellena a blancos.
printf(":%2d:\n",Entero); // No tiene efecto.
printf(":%15d:\n",Entero); // Justifica a la derecha.
printf(":%015d:\n",Entero); // Justifica a la derecha. Rellena con ceros.
printf(":%-15d:\n",Entero); // Justifica a la izquierda.
printf(":%15x:\n",EnteroH); // Hexadecimal minúsculas.
printf(":%-15X:\n",EnteroH); // Hexadecimal mayúsculas
printf(":%15o:\n",EnteroH); // Octal
printf(":%15ld:\n",LargoEnt); // Largo entero
printf(":%15d:\n",LargoEnt); // Largo entero. Resultado erróneo. visualizado
como entero simple.
printf(":%f:\n",Real); //
printf(":%15f:\n",Real); //
printf(":%-15f:\n",Real); //
printf(":%-.2f:\n",Real); //
printf(":%E:\n",Real); //
printf(":%15G:\n",Real); //
}

Visualización de los resultados.

:La casa:
: La casa:
:La casa :
:La c :
:12345:
: 12345:
:000000000012345:
:12345 :
: ff:
:FF :
: 377:
: 65432:
: -104:
:111111.523438:
: 111111.523438:
:111111.523438 :
:111111.52:
:1.111115E+05:
: 111112:

2.2.- Entrada con formato. Función scanf().


Es equivalente a printf() pero en la entrada de datos desde el puerto estándar de entrada: el
teclado (por stdin).

int scanf(const char *format[, address, ...]);


int scanf("Cadena de control" [, dir. argumentos, ...]);

Lee los caracteres desde el teclado, los interpreta de acuerdo con la cadena de control, y
almacena los resultados en los restantes argumentos. Los argumentos deben ser punteros (dirección de

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 56

memoria del argumento) que indican donde se debe almacenar el resultado; la función scanf() pasa los
argumentos por referencia, no por valor como lo hace printf(), siendo necesario indicarselo
anteponiendo el carácter & (dirección) a cada argumento, excepto a los de tipo cadena. Retorna el
número de argumento convertidos.

La cadena de control es una constante de tipo cadena que contiene los comandos de control de
formato o especificadores de conversión, cada uno de los cuales origina una conversión del siguiente
argumento de scanf().

Cada comando de control comienza por el símbolo de tanto por ciento (%) y finaliza en un
carácter de control. El formato del comando de control por orden de escritura es el siguiente:

% [*] [ancho] [h|l|L] Carácter_de_control

[*] Suspende la asignación de entrada para el siguiente argumento. Es opcional.

[ancho] Especificador del ancho del campo. Es opcional. Indica el número máximo de
caracteres que se deben leer para el argumento.

[h|l|L] Modificador del tamaño del campo. Es opcional. Este modificador afecta a la
forma en que la función scanf() debe interpretar el tipo de dato del argumento
que se escribe a continuación.
h = short int
l = long int (si Carácter Control integer)
l = double (si Carácter Control pto. flt.)
L = long double

Carácter
de control Carácter de conversión de tipo de dato. Es obligatorio. Indica la forma en que
debe realizarse la conversión del argumento para que sea almacenado en el
argumento correspondiente. Estos, son los siguientes:

Carácter Control Argumento


d Decimal integer Puntero a int (int *arg)
f Floating point Puntero a float (float *arg)
o Octal integer Puntero a int (int *arg) (Empieza por 0)
i D, o, h integer Puntero a int (int *arg)
u Unsigned d integer Puntero a unsigned int (unsigned int *arg)
x Hexadecimal integer Puntero a int (int *arg)
s Cadena de caracteres Puntero a array de chars (char arg[])
c Carácter Puntero a char (char *arg). Se suprime el salto
normal sobre el carácter blanco; usar %1s.

Si los caracteres de control d, o, i, u, x son escritos en mayúsculas (equivalentes a poner el


prefijo l), la conversión se realizará a long tipo.

Un carácter de control dirige la conversión del próximo campo de la entrada. El resultado se


almacena normalmente en la variable a la que apunta el correspondiente argumento. Si se indica
supresión de asignación (*), no se toma en cuenta el campo de entrada; no se realiza la asignación.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 57

Un campo de entrada se define como una cadena de caracteres delimitada por espacio en
blanco (blancos, tabuladores, fin de línea); abarca hasta el primer blanco o tantos caracteres como
indique el tamaño del campo.

Ejemplos:

int i; float x; char nombre[50];


scanf("%d %f %s", &i, &x, nombre); Entrada: 25 54.32E-1 Pérez

Asigna a i el valor 25, a x el valor 54.32E-1 y a nombre el valor Pérez.

scanf("%2d %f %*d %2s", &i, &x, nombre); Entrada: 56789 0123 54a72

Asigna 56 a i, 789.0 a x, se salta 0123 y a nombre la tira "54". La próxima llamada a cualquier
rutina de entrada comenzará la exploración en la letra a.

scanf(“%2d%*c%2d%*c%2d%*c”, &d, &m, &a)

scanf() acaba cuando se agota la tira de control o cuando la entrada no coincide con el carácter
de control. Devuelve como valor el número de elementos de entrada que ha podido reconocer y
asignar. Al llegar al final del fichero se devuelve EOF.

Uso de scanf() con Scanset:

Hay que usar corchetes en la cadena de control; estos permiten que scanf() acepte caracteres
que reemplacen el grupo de caracteres que haya dentro de los corchetes. El contenido de los corchetes
se llama scanset. El scanset puede ser un rango de valores ([a-z]), una lista ([01234]) 0 el signo ^
seguido por un rango o lista para indicar no (negación).

[a-z] lee chars mientras sean minúsculas.


[123] lee chars mientras sean 1, 2 ó 3.
[^a-z] lee chars mientras no sean minúsculas.

Scanf("%[^0-9] %d", str, &i); Lee caracteres situándolos en str mientras no sean
dígitos.

Scanf("%*[^0-9] %d", &i); Lee caracteres mientras no sean dígitos, pero


como lleva el *, se eliminan.

3.- Función getchar().


Esta función devuelve el siguiente carácter leído, en forma de entero, de la entrada cada vez
que se invoca. Cuando encuentra el final del fichero en la entrada de datos o cuando se produce un
error devuelve el valor EOF. EOF es una contante simbólica definida en stdio.h con valor -1.

Espera a que se puse una tecla, siempre que no haya caracteres almacenados en el buffer
esperando para ser leídos) y después devuelve su valor. También envía un eco del carácter pulsado a la
pantalla, por lo que no es necesario inprimirlo.

int getchar(void);

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 58

4.- Función putchar().


Envía un carácter a la salida estándar, pero sólo si es un carácter del código de caracteres. Si se
produce algún error retorna EOF.

int putchar(int c);

5.- Función gets().


Pide una cadena de caracteres desde el teclado y la devuelve como un array terminada en el
carácter nulo en el parámetro de la función. La entrada finaliza con el retorno de carro. No se
comprueba la entrada de datos, por lo que la longitud de la cadena debe ser lo suficientemente grande
como para que quepan todos los caracteres tecleados; si no hay espacio suficiente se produce un error.

char *gets(char *s);

#define LONGITUD 20
char Cadena[LONGITUD + 1];
... gets(Cadena);
printf("%s", gets(Cadena));

6.- Función puts().


Escribe el argumento de tipo cadena en la pantalla. Reconoce los códigos de escape (p.e.
\n)para la visualización. Sólo visualiza caracteres, no números, por tanto no puede realizar ninguna
conversión de formato. Se escribe automáticamente el carácter de nueva línea.

int puts(const char *s);

puts(Cadena);

7.- Funciónes getch() y getche().

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 59

8.- SENTENCIAS DE CONTROL DE PROGRAMA

1.- Introducción.
Se van a estudiar las sentencias que afectan al control de flujo del programa. Las sentencias
condicionales if y switch permiten alterar el flujo del programa según ciertas condiciones. Las
construcciones de bucle, while, for y do-while, permiten la repetición una serie de operaciones. Puesto
que se han diseñado los bucles para tener un punto de entrada y un punto de salida, se mencionan las
demás sentencias que afectan al flujo del programa -break, continue y goto- pero no se profundiza en
su uso por no formar parte de la programación estructurada.

2.- Sentencias y bloques.


Una expresión simple como x = 0 ó i++ se convierte en una sentencias cuando va seguida de un
punto y coma. En C el punto y coma indica el final de la sentencia. Las llaves ({}) se usan para
agrupar declaraciones y sentencias, lógicamente relacionadas, en una sentencia compuesta o bloque,
siendo sintácticamente equivalentes a una sentencia simple. Dos ejemplos donde necesariamente
debemos usar las llaves son:

· Para encerrar el cuerpo de una función.


· Cuando en una sentencia múltiple (if, for, ...) debamos ejecutar varias sentencias lógicamente
relacionadas entre sí formando un bloque de acciones. Si sólo hay que ejecutar una sentencia,
no será necesario encerrarla entre llaves.

3.- Sentencias de selección o condicionales.


3.1.- Sentencia if-else.
El formato de la sentencia es el siguiente:

if ( <condición> )
<Sentencia/s 1>;
[else
<Sentencia/s 2>;]

Esta sentencia se usa para tomar decisiones. Se evalúa la expresión y si esta es cierta (distinta
de cero) se realiza el grupo de <sentencias 1>; si es falsa (igual a cero) no se realiza ninguna acción o,
en el caso de que la clausula else esté presente, se realiza el grupo de <sentencias 2>. Si existe más de
una sentencia en cualquiera de los bloques de acciones, deberá encerrarse entre llaves. La cláusula
‘else’ se asocia con el ‘if’ inmediatamente anterior; si no se quiere así, se deben usar las llaves para
forzar la asociación deseada.

if (n > 0) if (n > 0)
if (a > b) { if (a > b)
z = a; z = a;
else }
z = b; else
z = b;

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 60

El sangrado de las sentencias no implica asociación de ningún tipo, sólo se usa para dar una
mayor legibilidad al programa y así evitar la ambigüedad.

Esta sentencia también nos permite realizar construcciones de la siguiente forma:

if (expresión 1)
<Sentencia/s 1>
else if (expresión 2)
<Sentencia/s 2>
else if (expresión 3)
<Sentencia/s 3>
else
<Sentencia/s N>

Es la forma normal de escribir una decisión múltiple. Las expresiones se evalúan en orden; si
alguna es cierta, se ejecuta la sentencia o bloque de sentencias encerradas entre llaves asociada con
ella. El último else, que es opcional, se realizará si no se cumple ninguna de las condiciones anteriores.

/*Cuenta minúsculas, mayúsculas, dígitos y otros caracteres. Falta el bucle.*/


#include <stdio.h>
void main(void)
{ int c;
int CuentaMinusculas = 0;
int CuentaMayusculas = 0;
int CuentaDigitos = 0;
int CuentaOtro = 0;

c = getchar();
if (c >= 'a' && c <= 'z')
++CuentaMinusculas;
else if (c >= 'A' && c <= 'Z')
++CuentaMayusculas;
else if (c >= '0' && c <= '9')
++CuentaDigitos;
else
++CuentaOtro;

printf("Min: %d\n", CuentaMinusculas);


printf("May: %d\n", CuentaMayusculas);
printf("Dig: %d\n", CuentaDigitos);
printf("Otr: %d\n", CuentaOtro);
}

3.2.- Sentencia switch.


Es una sentencia múltiple que comprueba si una expresión es igual a uno de entre varios
valores constantes, bifurcándose de acuerdo con ello.

El formato de la sentencia es el siguiente:

switch ( <Expresión> )
{
case <Expresión constante 1> :
<Sentencia/s 1>
[break;]

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 61

case <Expresión constante 2> :


<Sentencia/s 1>
[break;]
default :
<Sentencia/s N>;
}

Cada opción debe ser etiquetada con un entero, constante carácter o expresión constante; no
admite expresiones variables. Las opciones deben ser todas diferentes, disjuntas.

Se evalúa la expresión entera y compara su valor con todos los casos. En el caso en que un
valor sea igual a la expresión, se ejecuta el grupo de sentencias asociado a él. El ‘default’, que es
opcional, se ejecuta si no se cumple ninguna de las anteriores; si no existe, no se realiza nada. Después
de la ejecución de un bloque de sentencias, dado que cada ‘case’ se comporta como una etiqueta, la
ejecución continúa en el siguiente case, a no ser que se use la sentencia ‘break’ que fuerza una salida
inmediata del ‘switch’.

Ejemplo
switch (Operando)
{
case MULTIPLY: x *= y; break;
case DIVIDE: x /= y; break;
case ADD: x += y; break;
case SUBTRACT: x -= y; break;
case INCREMENT2: x++; /* Continúa en la línea siguiente */
case INCREMENT1: x++; break;
case EXPONENT:
case ROOT:
case MOD: printf("No se hace nada\n"); break;
default: printf("¡Error!\n"); break;
}

4.- Sentencias de iteración o bucles.


En las sentencias de iteración que proporciona el lenguaje C, las sentencias que forman el
cuerpo del bucle se ejecutan mientras la condición de finalización o continuidad sea verdadera. Es
esencial poner especial cuidado en la construcción de un bucle en los siguientes puntos:

· Asegurarse de que hay condiciones de finalización del bucle para evitar que estos se
ejecuten indefinidamente; se debe modificar el valor de la condición de finalización del
bucle dentro del cuerpo del mismo.
· Comprobar que se realizan correctamente la primera y la última iteración; es fácil que el
bucle realice una iteración de más o de menos.

4.1.- Sentencia while.


El formato del bucle es el siguiente:

while ( Condición )
<Sentencia/s>

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 62

La sentencia o grupo de sentencias (entre llaves en este caso) se realizará hasta que la
Condición de valor falso. Como lo primero que se hace es evaluar la condición, puede que el cuerpo
del bucle no se realice ninguna vez.

Aunque el C y otros lenguajes de programación permiten la construcción de bucles infinitos y


finalizarlo por otros medios, no está contemplado en la programación estructurada y, por tanto,
siempre necesitaremos una condición de finalización o continuidad del bucle. La condición es
cualquier expresión lógica válida.

Ejemplo: lee caracteres de la entrada de datos y después de un Enter visualiza lo escrito en


mayúsculas; si ya se ha escrito un * finaliza.
#include <stdio.h>
#include <conio.h>
#include <ctype.h> /*Usada por los caracteres y conversiones: int toupper(int ch);*/

int main(void)
{ int Caracter;
while ((Carácter = getchar()) != '*')
putchar(toupper(Caracter));
}

Ejercicio: Programa que lee una secuencia de números desde teclado y finaliza cuando se
introduce uno mayor que la suma de los dos anteriores. También debe contar el número de entradas.
#include <stdio.h>
int main(void)
{ int Entradas = 3;
int Penul, Ultimo, Numero;
printf("Los tres primeros\n");
scanf("%d %d %d", &Penul, &Ultimo, &Numero)
while (Numero <= Penul + Ultimo);
{ Penul = Ultimo;
Ultimo = Numero;
printf("Nuevo número: ");
scanf("%d", &Numero);
Entradas++;
}
printf("Introducidos %d", Entradas);
}

/*Cuenta minúsculas, mayúsculas, dígitos y otros caracteres*/


#include <stdio.h>
#include <conio.h>

int main(void)
{
int c;
int CuentaMinusculas = 0;
int CuentaMayusculas = 0;
int CuentaDigitos = 0;
int CuentaOtro = 0;

while ((c = getchar()) != EOF)


{
if (c >= 'a' && c <= 'z')
++CuentaMinusculas;
else if (c >= 'A' && c <= 'Z')
++CuentaMayusculas;
else if (c >= '0' && c <= '9')
++CuentaDigitos;
else
++CuentaOtro;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 63

printf("Min: %d\n", CuentaMinusculas);


printf("May: %d\n", CuentaMayusculas);
printf("Dig: %d\n", CuentaDigitos);
printf("Otr: %d\n", CuentaOtro);
return 0;
}

4.2.- Sentencia for.


El formato del bucle es el siguiente:

for ( [<Inicialización>] ; [<Condición>] ; [<Incremento>] )


<Sentencia/s>

Funcionamiento:

· Antes de la primera iteración del bucle, se inicializa la/s variable/s de control del bucle
situada/s en la zona de inicialización.
· La sentencia o grupo de sentencias se ejecuta hasta que la condición sea falsa.
· Después de cada iteración se incrementa el contador, así pues, es indiferente usar la notación
prefija o sufija en los operadores de incremento.

Las expresiones de inicialización y de incremento pueden llevar más de una expresión


(habitualmente sentencias de asignación) separadas por comas (se evalúan de izquierda a derecha). Si
se omiten estas dos expresiones, el for se comporta como un while. Si se omiten las tres expresiones,
se convierte en un bucle infinito. Los puntos y coma siempre deben aparecer. Las variables usadas en
la cabecera del for no se deben modificar dentro del cuerpo del bucle; para eso está el while.

Ejemplos:

Suma los primeros números.


for (I = 1; I < Numero; I++)
Suma +=I;

for (I = 1; I < Numero; Suma +=I, I++);

Visualiza los primeros números.


for (I = 1; I < Numero; printf("%d\t",I++));

Suma de los divisores de un número.


Suma = 0 ;
for (I = 1; I < Numero; ++I)
Suma = Numero % I ? Suma : Suma += I;

Suma = 0 ;
for (I = 1; I < Numero; Suma = Numero % I ? Suma : Suma += I, ++I);

for (Suma=0, I=1; I<Numero; Suma += Numero % I ? 0 : I, I++);

Lee caracteres; igual a while.


For ( ;(c = getchar()) != EOF; )
Putchar(tolower(c));

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 64

For (I=0 ;(c = getchar()) != '*'; I++)


Putchar(tolower(c));
printf("caracteres leídos %d", I);

Bucle for infinito:


for( ; ; );

Invierte una cadena.


#include <string.h> //Librería de cadenas.
For(i=0, j=strlen(s)-1; i<j; i++, j--)
{ c = s[i];
s[i] = s[j];
s[j] = c;
}

4.3.- Sentencia do-while.


El formato del bucle es el siguiente:
do
<Sentencia/s>
while (Condición);

La sentencia o grupo de sentencias (entre llaves en este caso) se realizará hasta que la
Condición de valor falso. Como lo último que se hace es evaluar la condición, el cuerpo del bucle se
realiza al menos vez.

Sentencia Break.

Proporciona una salida forzada e inmediata de un bucle y de la sentencia switch.


For (I=0; I<100; I++)
{
scanf("%d", &Numero);
If (Numero<0)
break;
else
...
}

Sentencia continue.

Obliga a que se ejecute la siguiente iteración del bucle que la contiene. En los bucles while y
do-while obliga a evaluar la condición inmediatamente; en un for, el control pasa a la etapa de
incremento y posterior evaluación de la condición.
For (I=0; I<100; I++)
{
scanf("%d", &Numero);
If (Numero<0)
Continue;
else
...
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 65

Sentencia goto.

Produce un salto incondicional a una etiqueta.

Goto <Etiqueta>
Etiqueta:

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 67

9.- TIPOS DE VARIABLES, PUNTEROS Y FUNCIONES

1.- Tipos de variables.

1.1.- Variables locales.


Son las variables declaradas dentro de una función y su ámbito se reduce a la misma. Esto hace
que en distintas funciones pueda haber definidas variables con el mismo nombre pero todas ellas son
diferentes. Comienzan a existir cuando se llama a la función y desaparecen cuando finaliza; por esta
razón no pueden conservar su valor entre dos llamadas sucesivas a la misma función. Reciben el
nombre de variables automáticas y pueden ser definidas como auto (sólo dentro de una función),
aunque rara vez se usa.

1.2.- Variables globales.


Son variables definidas externamente a todas las funciones y por tanto tienen ámbito en todo el
programa, incluidas las funciones. Sólo puede ser definida una vez en el programa. Si se definen dos
variables con el mismo identificador, una global y otra local, cuando se llame a la función prevalece la
variable local. Una variable global comienza a existir cuando empieza la ejecución del programa y
desaparece cuando este finaliza, es decir, está visible durante toda la ejecución del programa. Estas
variables, y todas las funciones, son externas al programa, entendiendo este como la función main().

1.3.- Variable estáticas.


Son variables locales o globales ; en su definición se le antepone la palabra reservada static. Si
se define como una variable local indica que su existencia es permanente, esto es, que no aparecen y
desaparecen cada vez que se ejecuta la función, si no que en las sucesivas llamadas a la función sigue
manteniendo el valor de la llamada anterior.
Int suma(int a)
{ static int Contador = 0;
Contador++
...
}

La variable Contador se irá incrementando en las sucesivas llamadas a la función y sólo se


inicializará en la primera llamada.

Si está definida como una variable global, indica que la variable sólo es accesible en el
programa fuente en el que está definida, en ningún otro.

1.4.- Variables registro.


Indica al compilador que cuando sea posible, use uno de los registros del microprocesador para
almacenar la variable, de forma que el acceso a la misma es más rápido.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 68

Sólo pueden definirse de tipo register las variables locales automáticas y los parámetros
formales de las funciones.
Int suma(register int a)
{ register int Contador = 0;
Contador++
...
}

2.- Teoría básica de punteros.


Un puntero es una variable que contiene una dirección de memoria. Esa dirección es la
posición de otro objeto (normalmente otra variable) en memoria. Si una variable contiene la dirección
de memoria de otra variable, entonces se dice que la primera variable apunta a la segunda.

Valor 1003 123


Dirección 1001 1002 1003 1004

La posición de memoria 1001 tiene como valor la dirección de memoria 1003, que es la que
contiene el dato, 123; la dirección de memoria 1001 apunta a la 1003.

2.1.- Variables puntero.


Si una variable va a contener un puntero (la dirección de otra variable), entonces debe
declararse como tal. Una declaración de un puntero consiste en un tipo base , el operador * y el
nombre de la variable. El formato general de definición de un puntero es

TipoDeDato* NombreDeVariable;

donde TipoDeDato es el tipo base del puntero y puede ser cualquier tipo válido de C.
NombreDeVariable es un puntero que apunta a un tipo de dato base definido por TipoDeDato.

El tipo base del puntero define el tipo de variables a las que puede apuntar el puntero.
Técnicamente, cualquier tipo de puntero puede apuntar a cualquier lugar de la memoria. Sin embargo,
la aritmética de punteros está hecha en relación a su tipo base, por lo que es importante declarar bien el
puntero.

La declaración de un puntero reserva memoria la necesaria para almacenar una dirección de


memoria, no el dato al que apunta. El tamaño de memoria reservado es de 32 bits el tipo far y 16 ó 32
el near según la máquina.

2.2.- Operadores de punteros.


Hay dos operadores de punteros, ambos unarios: &, operador de dirección, y *, operador de
indirección. El primero (&) es un operador que devuelve la dirección de memoria de su operando. Por
ejemplo,

m = &Cuenta;

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 69

pone en la variable “m” la dirección de memoria de la variable “Cuenta”; es decir, “m” recibe la
dirección de “Cuenta”, no el valor de la variable.

Cuenta = 123;
m = &Cuenta;

Valor 1003 123


Dirección 1001(m) 1002 1003(Cuenta) 1004

El segundo operador de punteros (*) es un complemento de & que devuelve el valor de la


variable localizada en la dirección que le sigue.

Siguiendo el ejemplo anterior, si “m” contiene la dirección de memoria de la variable


“Cuenta”, entonces

q = *m;

pone el valor de “Cuenta” en “q”.

Cuenta = 123;
m = &Cuenta;
q = *m;

Valor 1003 123 123


Dirección 1001(m) 1002(q) 1003(Cuenta) 1004

Esta sentencia significa que “q” recibe el valor almacenado en la dirección “m”.

Los dos operadores tienen mayor prioridad que todos los operadores aritméticos excepto los
unarios (+ y -), respecto de los cuales tienen igual precedencia.

Un puntero no puede ser usado antes de que se haya inicializado, antes de que tome la
dirección de una variable; si se usa sin inicializar es posible reescribir zonas de memoria no reservadas
para almacenar determinados valores, lo que implica que el sistema puede funcionar mal.

Declaración: int variable;


int* puntero;

0xAA00 0xFF00
Puntero Variable

Inicialización: variable = 10;


puntero = 0;

0 10
0xAA00 0xFF00
Puntero Variable

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 70

Asignación: Puntero = &variable ;

0xFF00 10
0xAA00 0xFF00
Puntero Variable

Después de realizar la asignación anterior disponemos de dos accesos al contenido de la


variable, uno a través de la propia variable y otro mediante el puntero usando el operador *.

Ejemplo:
#include <stdio.h>
main()
{ int* puntero;
int numero = 10;
*puntero = numero; // Operación no deseada; puntero no está inicializado.
printf("%d %d\n", numero, *puntero); // 10 10
numero = 20;
printf("%d %d\n", numero, *puntero); // 20 10
*puntero = 30;
printf("%d %d\n", numero, *puntero); // 20 30
puntero = &numero;
printf("%d %d\n", numero, *puntero); // 20 20
numero = 40;
printf("%d %d\n", numero, *puntero); // 40 40
*puntero = 50;
printf("%d %d\n", numero, *puntero); // 50 50
numero = getchar();
}

Ejercicio: ¿Cuál es el resultado de evaluar dos expresiones siguientes? (*Puntero)++


*Puntero++

3.- Funciones.
El lenguaje C no dispone de subrutinas, sólo de funciones; estas se pueden comportar como
cualquiera de los dos tipos de subprogramas.

3.1.- Declaración de funciones.


La declaración o prototipo de una función consiste en describir la función antes de que sea
invocada; de esta forma, el compilador crea los enlaces correspondientes a la función para que esté
localizado el lugar de su definición y también conoce los parámetros formales y el tipo de dato de
retorno. El formato de la declaración de una función es el siguiente:

TipoDatoRetorno NombreFunción([TipoParámetro [NombreParámetro]] ...);

Si se invoca a la función sin haber sido declarada, el compilador no puede determinar si la


llamada a la función se realiza correctamente. Se puede eludir la declaración de la función si se realiza
la definición delante de cualquier llamada a la misma.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 71

El tipo de dato de retorno de una función puede ser declarado de tipo void, indicando que la
función no retorna nada. Si no se especifica tipo dato de retorno, el compilador asume que es un tipo
de dato int.

3.2.- Definición de funciones.


En la definición de la función es donde se escribe el código de la función: las sentencias que
forman el cuerpo ejecutable de la función y que describen la acción que realiza. El formato general de
la definición de una función es el siguiente:

Tipo_Dato_Retorno Nombre_Función(Tipo_Parámetro Nombre_Parámetro ...)


{
Declaración de variables locales;
Sentencias ejecutables;
}

El nombre de la función y los tipos de los parámetros formales de la cabecera se deben


corresponder con los del prototipo.

3.3.- Llamada a funciones.


Para poder usar una función hay que invocarla. Para llamar a una función hay que poner el
nombre de la función, seguida de los parámetros actuales, dentro de una expresión o de una sentencia.

char palabra[11] = "murciélago";


g = contar_vocales(11, palabra);

El compilador se encarga de comprobar si los argumentos coinciden en número y tipo con los
del prototipo de la función.

3.4.- La función main().


Cuando ejecutamos un programa desde el sistema operativo, realmente se está haciendo una
llamada a la función main(). Hay varias formas de declarar la función main():

· void main(): No recibe nada ni devuelve nada.


· int main() o main(): Devuelve un entero al sistema operativo; si finaliza normalmente
retorna un 0 y cualquier otro valor implica algún tipo de error.
· int main(int argc,char* argv[]): Recibe argumentos y devuelve un entero al sistema
operativo.
· void main(int argc, char* argv[]): Recibe argumentos y no devuelve nada.

En las dos últimas formas existe comunicación desde el sistema operativo hacia el programa.
Los argumentos de main() significan lo siguiente:

· argc: Número de parámetros que pasa el sistema operativo al programa incluido el nombre
del programa.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 72

· argv[]: Se almacenan los argumentos pasados.

Ejemplo:
Llamada desde el S.O.: C:\copiar a: b:

Los valores de los argumentos son: Argc = 3


Argv[0] = "copiar"
Argv[1] = "a:"
Argv[2] = "b:"

3.5.- Retorno de valores.


El lenguaje C dispone de una sentencia para devolver el valor de una función; su formato es:

return [<Expresión>];

Expresión es del mismo tipo que el tipo de retorno de la función. Return provoca la salida
inmediata de la función y puede haber cualquier número de ellas en el cuerpo de la función. La
sentencia return no es necesaria cuando la función no retorna ningún valor (tipo void), aunque puede
usarse sin la expresión para finalizar la función (equivalente a break en un bucle).

3.6.- Paso de parámetros.

3.6.1.- Paso de parámetro por valor.

Por omisión, todos los parámetros de una función en C se pasan por valor, es decir, se pasa una
copia temporal del parámetro. Esto significa que la función no puede modificar el valor de las
variables externas a ella, ya que se le pasa una copia, no el valor original. Un parámetro por valor se
puede entender como una variable local, de modo que dentro de la función puede cambiar su valor
pero los cambios no tienen efecto fuera de ella.

Los parámetros actuales pueden ser expresiones, no necesariamente variables.

La única comunicación hacia el exterior de una función que pasa todos sus parámetros por
valor es a través del tipo de dato de retorno de una función usando la sentencia return.

3.6.2.- Paso de parámetros por referencia.

Como en muchas ocasiones queremos que los parámetros pasados a la función cambien de
valor o devolver más de un dato, se debe usar el paso de parámetros por referencia. El paso de
parámetros por referencia consiste en pasar la dirección de memoria donde está almacenado el
argumento, no una copia de su valor. De esta forma, el parámetro actual debe ser una dirección de
memoria de una variable y, por tanto, lo que recibe el parámetro formal es esa dirección de memoria.
Cada vez que se cambia de valor ese parámetro en la función, se cambia el contenido de una dirección
de memoria.

La forma que tiene C de pasar por referencia consiste en usar los punteros.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 73

void Cambia(int* a, int* b)


{ int aux;
aux = *a;
*a = *b;
*b = aux;
}
void main()
{ int x = 100, y = 200;
Cambia(&x, &y);
printf("x = %d y = %d", x, y);
}

Los parámetros formales se declaran como punteros al tipo de dato base y, en la llamada se les
asigna la dirección de los parámetros actuales.

Según el ejemplo, al llamar a la función estamos haciendo

a = &x y b = &y

y luego modificamos el contenido de los punteros a y b usando para ello el operador de indirección *;
*a y *b, como ya se ha visto, se tratan igual que si fueran variables enteras.

Ejercicios:

1.- Función que calcule las raíces de la ecuación de segundo grado:

ax2 + bx + c = 0

2.- Función que visualice un rectángulo de * en pantalla. El tamaño del mismo se introduce por
teclado; no superior ni inferior a la pantalla.

3.- Combinaciones de m elementos tomados de n en n.

 m
  = m! / (n!* (m - n)!)
 n

4.- Función que pase un número en base diez a cualquier otra base.

5.- Calcular el número del tarot de una persona a partir de la fecha de su nacimiento. Consiste
en sumar el día, mes y año; del resultado obtenido se suman sus dígitos y así sucesivamente hasta
reducir a un sólo dígito.

6.- Indicar si una fecha es correcta. Un año es bisiesto cuando es múltiplo de cuatro, salvo los
múltiplos de cien a no ser que los sean de cuatrocientos.

7.- Se introduce la hora del día en Horas, Minutos y Segundos. Se desea escribir la hora
correspondiente al siguiente segundo.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 75

10.- ESTRUCTURAS DE DATOS ESTÁTICAS.

1.- Introducción a las estructuras de datos.


Una estructura de datos es una colección de datos que pueden ser caracterizados por su
organización y las operaciones que se definen en ella.

Los tipos de datos más frecuentemente usados son:

Simples Primitivos Entero


Real
Carácter
Lógico

Definido por el usuario Subrango


Enumerado

Estructurados Estáticos Tablas o Arrays


Registros
Ficheros
Conjuntos
Cadenas o String

Dinámicos Listas
Listas enlazadas
Árboles
Grafos

Los tipos de datos primitivos son aquellos que no están compuestos por otras estructuras de
datos y son los más utilizados en los diferentes lenguajes de programación; son los que hemos
trabajado hasta el momento.

Los tipos de datos estructurados pueden ser organizados en diferentes estructuras de datos:
estáticas y dinámicas. Las estructuras de datos estáticas son aquellas en las que el tamaño ocupado en
memoria se define antes de que el programa se ejecute y no puede modificarse dicho tamaño durante la
ejecución del mismo. Las estructuras de datos dinámicas no tienen limitaciones o restricciones en el
tamaño de memoria ocupada.

2.- Tablas: conceptos, definiciones y representación.


Una tabla o array es una estructura de datos interna -se almacena en memoria principal- que
consta de un número fijo, finito y ordenado de elementos, todos del mismo tipo y referenciados bajo
un nombre común.

Fijo: Es necesario definir su tamaño en tiempo de compilación, es decir, el tamaño de


la tabla se da al escribir el programa.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 76

Finito: Al ser una estructura de datos interna, su tamaño viene limitado por la capacidad
de la memoria principal o por el propio lenguaje.

Ordenado: El primero, segundo, tercero, ... elemento de un array puede ser identificado, no
es así en las estructuras dinámicas.

El nombre de una tabla es un identificador común para todos sus elementos, distinguiéndose
cada uno de ellos por una lista de índices que complementan a dicho nombre para referenciarlos.

Definiciones:

Componente: Denominación de los elementos de la tabla.


Dimensión: Número de índices necesarios para referenciar una componente de la tabla.
Longitud: Número de componentes que contiene la tabla.
Tipo: Tipo de las componentes.
Índice: Es una expresión de tipo numérico entero (constante, variable o expresión) que
referencia cada componente en la tabla, es decir, indica la posición de cada
componente. Sus valores varían de 0 a Longitud - 1.

Las tablas, igual que cualquier otro objeto del programa, se debe definir en su lugar
correspondiente indicando su identificador, su tipo, su longitud y su número de dimensiones.

Los límites del array no se comprueban en tiempo de compilación, por tanto es posible
sobrescribir datos fuera de dichos límites.

Las operaciones que se realicen en el array se harán sobre cada componente individualmente,
no pueden realizarse sobre el array en su conjunto.

3.- Tipos de tablas.


3.1.- Tablas unidimensionales o vectores.
Las tablas unidimensionales o vectores tienen una única dimensión y por tanto constan de un
sólo índice. Cada componente del vector se direcciona con el nombre de la tabla seguido del número
correspondiente al índice entre corchetes.

Un vector se representa de la siguiente forma:


EDAD
20 18 31 20 17 21
Elto 1º Elto 2º Elto 3º Elto 4º Elto 5º Elto 6º
Pos. 0 Pos. 1 Pos. 2 Pos. 3 Pos. 4 Pos. 5

Nombre: EDAD.
Componentes: EDAD[0], EDAD[1], EDAD[2],..., EDAD[5].
Índices: Valores numéricos de 0 a 5 que direccionan cada componente.
Dimensión: Una.
Longitud: 6
Tipo: Numérico entero.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 77

El vector se almacena en la memoria principal como un conjunto de posiciones continuas; la


dirección más baja corresponde al primer elemento (dirección almacenada en el propio nombre del
array) y la más alta al último.

En el caso anterior, la memoria ocupada por la tabla EDAD es la siguiente:

EDAD
2 bytes 2 bytes 2 bytes 2 bytes 2 bytes 2 bytes
20 18 31 20 17 21
Elto 1º Elto 2º Elto 3º Elto 4º Elto 5º Elto 6º
Pos. 0 Pos. 1 Pos. 2 Pos. 3 Pos. 4 Pos. 5

Formato general de definición de una tabla:

TipoDeLasComponentes Identificador[LONGITUD_DIMENSIÓN];

Identificador[LONGITUD_DIMENSIÓN] es de tipo TipoDeLasComponentes.

Ejemplo:
int EDAD[6];
EDAD[6] es de tipo Numérico entero.

Habitualmente la longitud del array se define en una directiva #define:


#define TAMANHO 6
int EDAD[TAMANHO];

3.1.1.- Operaciones sobre vectores.

Cada elemento es un dato que está almacenado en una posición dentro del vector, y las
operaciones que se pueden realizar son las mismas que la usadas con cualquier otra variable, pero se
realizarán sobre cada componente individualmente.

Asignación:
Identificador = Nombre_Tabla[Indice];
Nombre_Tabla[Indice] = Expresión;
Edad[0] = 20;
Edad[1] = 18;

Entrada:
Introducir Nombre_Tabla[Indice]
Scanf("%d", &Edad[0]);
Scanf("%d", &Edad[1]);

Salida:
Visualizar Edad[Indice]
Printf("%d", Edad[0]);
printf("%d", Edad[1]);

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 78

Recorrido secuencial de un vector.

Si queremos conocer o simplemente tratar cada una de las posiciones de un array, debemos
realizar un recorrido secuencial del mismo; para ello usaremos la estructura de control 'para' (bucle
for), ya que conocemos el número de componentes que contiene el vector. Un tratamiento secuencial
de una tabla consiste en recorrerla completamente, componente a componente, y realizar las
operaciones necesarias con la componente tratada en cada caso. El tratamiento puede realizarse en
sentido creciente o decreciente:

Creciente:
Para Indice de 0 a TAMANHO
TRATAR Tabla[Indice]
Fin Para

for (Indice = 0; Indice < TAMANHO; Indice++)


TRATAR Tabla[Indice];

Decreciente:
Para Indice de TAMANHO a 0 con incremento -1
TRATAR Tabla[Indice]
Fin Para

for (Indice = TAMANHO - 1; Indice >= 0; Indice--)


TRATAR Tabla[Indice];

Ejemplos:

1.- Cargar el array de edades y visualizar el contenido:


#include <stdio.h>
#define MAXIMO 5

void main()
{ int i;
int Edad[MAXIMO];
// Carga el vector
for (i = 0; i < MAXIMO; i++)
{ printf("Elemento %d: ", i+1);
scanf("%d", &Edad[i]);
}
// Visualiza el contenido en sentido creciente
for (i=0; i<MAXIMO; i++)
printf("Elemento %d:\n", Edad[i]);
// Visualiza el contenido en sentido decreciente
for (i=MAXIMO - 1; i>=0; i--)
printf("Elemento %d: %d\n", i, Edad[i]);
}

2.- Cargar un array con las notas de los alumnos de la clase y posteriormente calcular su media.
#define ALUMNOS 30
Float TotalNotas, Medias, Notas[ALUMNOS];
... // Cargar la tabla y demás definiciones de datos y sentencias
for (i = 0; i < ALUMNOS; i++)
TotalNotas += Notas[i];
printf("La media es: %f\n", TotalNotas / ALUMNOS);

3.- Intercambiar el contenido de dos componentes de una tabla:

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 79

Aux = Tabla[i];
Tabla[i] = Tabla[j];
Tabla[j] = Aux;

Inicialización del vector:

Un array, igual que cualquier otra variable, debe inicializarse cuando sea necesario y en el
lugar del programa oportuno con los valores que deba almacenar. Hay dos formas de inicializar un
array:

1.- Recorriendo secuencialmente el array y almacenando el valor de inicialización en cada


componente:
//Inicialización de un vector con el valor 0
Int Vector[MAXIMO];
For (Indice = 0; Indice < MAXIMO; Indice++)
Vector[Indice] = 0;

2.- Inicializar el array en la propia definición del mismo. No es necesario indicar el número
de elementos, en los corchetes, pues el propio compilador cuenta el número de valores
que hay entre las llaves, separados por comas, y dará la longitud del vector. Si se
especifica la longitud, habrá que poner tantos valores como marque la misma.

// Definición e inicialización de un array de longitud 7.


Int Vector1[] = {0,0,0,0,0,0,0};
Int Vector2[7] = {0,0,0,0,0,0,0};

Insertar un elemento en un vector:

Consiste en introducir un elemento en el interior del vector. Es necesario un desplazamiento


previo para colocar el elemento nuevo en su posición relativa. Esta posición debe ser conocida a no ser
que el contenido del vector esté ordenado y, por tanto, busquemos el lugar que le corresponde al nuevo
elemento.

Ejemplo: Insertar la edad 19 en el array EDAD en la posición 2.

EDAD
17 18 20 22
0 1 2 3 4 5
Las operaciones que se deben realizar desglosadas por pasos sucesivos, son las siguientes:

Edad[5] = Edad[4]
Edad[4] = Edad[3]
Edad[3] = Edad[2]

El resultado es el siguiente:

EDAD
17 18 19 20 22
0 1 2 3 4 5

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 80

Debe observarse que si el vector está lleno en su totalidad, la última componente del mismo se
pierde, a no ser que este hecho impida la inserción de la nueva componente.

Algoritmo general de inserción:

para Indice de MAXIMO - 1 a Posición con incremento - 1


Vector[Indice] = Vector[Indice - 1]
fin para
Vector[Posicion] = Elemento

for (Indice = MAXIMO - 1; Indice >= Posición; Indice--)


Vector[Indice] = Vector[Indice - 1];
Vector[Posicion] = Elemento;

Por tanto, en el ejemplo anterior el fragmento de programa es:


#define MAXIMO 6
Posición = 2;
Elemento = 19;

for (Indice = MAXIMO - 1; Indice >= Posición; Indice--)


Vector[Indice] = Vector[Indice - 1];
Vector[Posicion] = Elemento;

Borrar un elemento en un vector:

for (Indice = Posición; Indice < MAXIMO - 1; Indice++)


Vector[Indice] = Vector[Indice + 1];
Vector[???] = Vacío;

3.2.- Tablas bidimensionales o matrices.


Existen grupos de datos que no se pueden representar como una secuencia de datos (vectores),
si no que es mejor representarlos en forma de tabla financiera o matriz: informes de ventas anuales
(día/mes), tablas de distancias kilométricas entre ciudades, cuadros horarios de salidas de trenes, etc.

El array bidimensional o matriz se puede considerar como un vector de vectores. Es, por
consiguiente, un conjunto de elementos, todos del mismo tipo, en el cual el orden de los componentes
es significativo y en el que se necesitan dos índices para poder identificar a cada elemento del array.
Esta referencia a un elemento del array, se realiza de la misma forma que en un vector; el nombre de la
matriz seguido, en este caso, de dos índices encerrados, por separado, entre corchetes.

Una matriz está compuesta de filas y columnas, de forma que el primer índice se corresponde
con las filas y el segundo con las columnas, independientemente del identificador usado para
nombrarlos.

Si se visualiza un vector, se puede considerar como una fila de datos; una matriz como un
grupo de filas.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 81

En la figura siguiente se representa una matriz, MATRIZ, de cuatro filas y seis columnas:

MATRIZ
Fila 0
Fila 1 M[1][3]
Fila 2
Fila 3
Columna 0 Columna 1 Columna 2 Columna 3 Columna 4 Columna 5

Una matriz, por tanto, necesita un valor para cada indice para poder identificar un elemento
individual. La expresión MATRIZ[1][3] accede a la componente situada en la fila 1 y columna 3 (el
primer índice indica la fila y el segundo la columna). Los dos índices son expresiones numéricas
enteras.

El número de componentes de una tabla viene dado por la expresión

Número_Componentes = Número_Filas * Número_Columnas

El formato general para la definición de una matriz es:

TipoDeLasComponentes Identificador[LONG_FILA] [LONG_COLUMNA];

Identificador[FILAS, COLUMNAS] es de tipo TipoDeLasComponentes.

3.2.1.- Operaciones sobre matrices.

Recorrido secuencial de una matriz.

Un tratamiento secuencial de una matriz se realiza anidando dos bucles; este puede realizarse
de dos formas distintas: por filas y por columnas.

Por filas: El bucle más externo recorre las filas y el mas interno todas las componentes de
la fila, es decir, el externo se posiciona en una fila de la tabla y el interno recorre
todas sus componentes, todas las columnas de esa fila.

Algoritmo en seudocódigo:

Para F de 1 a FILAS
Para C de 1 a COLUMNAS
Tratar TABLA[F][C]
Fin para
Fin para

Fragmento de programa en C:
For(F = 0; F < FILAS; F++)
For(C = 0; C < COLUMNAS; C++)
Tratar TABLA[F][C];

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 82

Por columnas: El bucle más externo recorre las columnas y el mas interno todas las
componentes de la misma.

Algoritmo en seudocódigo:

Para C de 1 a COLUMNAS
Para F de 1 a FILAS
Tratar TABLA[F][C]
Fin para
Fin para

Fragmento de programa en C:
For(C = 0; C < COLUMNAS; C++)
For(F = 0; F < FILAS; F++)
Tratar TABLA[F][C];

Inicialización de matrices

Un array, igual que cualquier otra variable, debe inicializarse cuando sea necesario y en el
lugar del programa oportuno con los valores que deba almacenar. Hay dos formas de inicializar un
array:

1._ Recorriendo secuencialmente el array y almacenando el valor de inicialización en cada


componente:
#define FILAS 10
#define COLUMNAS 5
//Inicialización de un vector con el valor 0
Int Matriz[FILAS][COLUMNAS];
For (F = 0; F < FILAS; F++)
For (C = 0; C < COLUMNAS; C++)
Matriz[F][C] = 0;

2._ Inicializar el array en la propia definición del mismo. Es necesario indicar el número de
elementos de todas las dimensiones excepto de la primera para permitir que el
compilador indexe el array adecuadamente.
// Definición e inicialización de un array
Int Matriz1[][4] = {{0,0,0,0},{0,0,0,0},{0,0,0,0}};
Int Matriz2[2][7] = {0,0,0,0,0,0,0, 0,0,0,0,0,0,0};

El resto de operaciones que pueden realizarse sobre los elementos de una matriz son las
mismas que las que pueden realizarse con los vectores.

Ejemplo:

a) Cargar una matriz de 30 filas y 3 columnas que contiene la nota de cada alumno por
cada módulo del ciclo; las filas corresponden al alumno y las columnas a los módulos.
Calcular e imprimir la nota media de cada alumno y de cada asignatura.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 83

b) Suponiendo que las notas son enteras y cuyo valor varía de 1 a 10, calcular el total de
alumnos para cada nota y asignatura.

Ejercicios.

1. Programa que lee 50 número enteros sobre un vector y obtiene e imprime cuales son el mayor y
el menor número almacenado y cuantas veces se repiten ambos.

2. Con el vector anterior, programa que suma las componentes de índice par y las de índice impar
independientemente.

3. Cargar un vector de N componentes y obtener otro vector resultado de haber multiplicado el


primero por un número dado por teclado.

4. Contar los números positivos almacenados en un array.

5. Programa que invierte el contenido de un vector.

6. Dado un número entero positivo con un máximo de N dígitos, comprobar es capicúa usando un
vector de N componentes.

7. Programa que lee un vector de N componentes y las rota un lugar a la derecha, teniendo en
cuenta que la última componente pasa a ser la primera.

8. Frecuencia de calificaciones. Se introduce por teclado una secuencia de calificaciones (1..10)


que finaliza cuando se introduce una nota errónea. Se pide obtener e imprimir la lista de
frecuencia, número de repeticiones, de cada una de las notas.

9. Bolas de la urna, número de extracciones, la más y la menos repetida. Contar Random y


randomize, rand y srand.
srand((unsigned) time(NULL)) randomize()
rand() % Rango random(Rango)

10. Cargar un vector de N componentes y dividirlo en otros dos de forma que uno contenga las
componentes cuyo valor es primo y el otro con las de valor no primo.

11. Programa que carga una matriz de 5 filas y 10 columnas con números enteros y almacena los
valores máximo y mínimo de cada fila en un array. Visualizar los resultados.

12. Programa que calcula la matriz traspuesta de una matriz de N filas y M columnas.

13. Programa que genera e imprime una matriz unitaria de orden N. Es aquella que tiene todas sus
componentes a 0 excepto las de la diagonal principal que están a 1.

14. Programa que imprime un cuadrado latino de orden N. La primera fila contiene los N primeros
números naturales y la siguientes N - 1 filas contienen la rotación de la fila anterior un lugar a
la derecha.

15. Programa que carga una matriz cuadrada y nos dice si es mágica. Es mágica si la suma
individual de cada una de sus filas, columnas, diagonales principal y secundaria son iguales.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 84

16. Una empresa dispone de las hojas de caja diarias de un año. En ellas se refleja el total en
pesetas de las ventas producidas en el día de su fecha. Se pide realizar un programa en el que se
introduzcan por teclado cada una de las cantidades y calcule:
A) Capital total de ventas producidas en cada mes.
B) Capital total anual.
C) Día y mes que se hizo la mejor caja.

17. Queremos crear una tabla (NxM) que contenga el número de votos de una serie de candidatos
de unas elecciones, estando distribuídos por distritos. Se pide:
A) Número de votos totales.
B) Votos de cada candidatos.
C) Votos de cada distrito.
D) Candidato y distrito más votados y número de votos obtenidos.

18. Decir si dos vectores son disjuntos.

19. Genera e imprime un cuadrado mágico de orden N (positivo e impar). Un cuadrado mágico de
orden N es una matriz cuadrada que contiene los números naturales de 1 a N elevado a 2 tal
que coincide la suma de los números de cualesquiera de sus filas, columnas o diagonales
principales. Se construye mediante las siguientes reglas:

· El número 1 se coloca en la casilla central de la primera fila.


· Cada número siguiente se coloca en la casilla correspondiente a la fila anterior y columna
siguiente.
· Si el número sigue a un múltiplo de N, no se aplica la regla anterior, si no que se coloca en
la casilla de la fila posterior e igual columna.
· Se considera que la fila anterior a la primera es la última y la columna posterior a la última
es la primera.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 85

4.- Búsqueda lineal.


La búsqueda lineal en un array consiste en determinar si un valor cualquiera, X, del mismo tipo
que los componentes del array, está o no almacenado en dicho array y en que posición se encuentra
dentro de este.

4.1.- Búsqueda lineal en un vector no ordenado.


Se realiza un recorrido secuencial del vector, comenzando por la primera componente y
finalizando cuando el valor buscado coincida con una de las componentes del array o bien, estemos
situados en el final del mismo y no hayamos encontrado dicho valor. En este caso, debemos indicar la
no existencia del valor buscado en el array.

El algoritmo de búsqueda se puede estudiar de dos formas:

A) El resultado es la primera componente que coincida con el valor buscado, aunque


existan componentes repetidas. Por tanto el recorrido del vector se realiza desde el
principio hasta que se encuentra el valor en el array o, en el caso más desfavorable,
hasta el final del array. El algoritmo de búsqueda es el siguiente:

Dado un vector, Vector, de N componentes, determinar si existe un valor igual


que 'X' en el mismo.
i = 0;
While (X != Vector[i] && i < N - 1)
i++;
if (X == Vector[i])
printf("El valor %d está en la posición %d", X, i);
else
printf("El valor %d no está en el vector", X);

B) El tratamiento del array es completo, desde la primera componente a la última; el


resultado consiste en dar todas las posiciones del array en que se encuentra almacenado
el elemento que buscamos. De la misma forma que en el caso anterior, debe
determinarse si el valor buscado no se encuentra en el vector. El algoritmo de búsqueda
es el siguiente:

Dado un vector, Vector, de N componentes, determinar todas las ocurrencias de


un valor igual que 'X' en el mismo.
i = 0; Encontrado = 0;
While (i < N)
{ If (X == Vector[i])
{ printf("El valor %d está en la posición %d", X, i);
Encontrado = 1;
}
i++;
}
if (!Encontrado)
printf("El valor %d no está en el vector", X);

Aunque el algoritmo se implementa con un bucle while, puede hacerse con un bucle for.

Ejercicio: Generar la combinación de la lotería primitiva.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 86

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#define N 8
#define RANGO 49
#define RREIN 9
int Buscar(int, int *);
void Visualizar(int *);
void Generar(int *);
main()
{ int Tabla[N] = {0, 0, 0, 0, 0, 0, 0, 0};
Generar(Tabla);
Visualizar(Tabla);
getch();
}
void Generar(int *V)
{
int Numeros = 0, Valor;
randomize();
while (Numeros < N - 1)
{ do
{ Valor = random(RANGO) + 1;
}while(Buscar(Valor, V));
V[Numeros++] = Valor;
}
V[N - 1] = random(RREIN) + 1;
}
int Buscar(int Valor, int *V)
{ int i = 0;
while (Valor != V[i] && i < N - 1)
i++;
return V[i] == Valor ? 1 : 0;
}

void Visualizar(int *V)


{ int i;
printf("\nCombinación :");
for(i=0; i < N -2; i++)
printf(" %d\t", V[i]);
printf("\nComplementario: %d", V[N - 2]);
printf("\nReintegro : %d", V[N - 1]);
}

4.2- Búsqueda lineal en un vector ordenado.


Como se ha visto, en la búsqueda lineal en un vector no ordenado, en el caso más desfavorable,
que sería no encontrar el valor buscado en el vector, debemos recorrer todo el vector hasta la última
componente para poder determinar que dicho valor no está almacenado en el vector. En el segundo
caso, independientemente de que se encuentre o no el valor en el vector, debemos recorrerlo hasta el
final.

En un vector ordenado, el tiempo de búsqueda se reduce considerablemente, dado que no es


necesario recorrer el vector hasta el final si el valor buscado no se encuentra en él. Igual que en el
método anterior la búsqueda comienza en el primer elemento y se realiza de izquierda a derecha. El
recorrido finaliza cuando el valor ha sido encontrado o cuando se encuentra una componente cuyo
valor es mayor o menor que el buscado, según el criterio de ordenación. Los dos métodos a estudiar
son los siguientes:

a) El resultado es la primera componente que coincida con el valor buscado. El algoritmo es el


siguiente:

Dado un vector ordenado ascendentemente de N componentes, determinar si existe un


valor X en el mismo.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 87

I = 0;
while (X > Vector[i] && i < N - 1) //En Pascal daría fuera de rango
I++;
if (X == Vector[i])
printf("El valor %d está en la posición %d", X, i);
else
printf("El valor %d no está en el vector", X);

b) Es igual que el anterior pero damos todas la posición que ocupan todas las componentes
iguales a X.
i = 0; Encontrado = FALSO;
// Esta expresión en otro lenguaje de programación puede dar un error de fuera de rango.
While (i < N && X >= Vector[i])
{ If (X == Vector[i])
{ printf("El valor %d está en la posición %d", X, i);
Encontrado = CIERTO;
}
i++;
}
if (!Encontrado)
printf("El valor %d no está en el vector", X);

4.3.- Búsqueda lineal en una matriz.


Igual que en el recorrido secuencial de una matriz anidábamos dos bucles, en la búsqueda
también debemos hacerlo. Las técnicas que debemos aplicar son idénticas a las usadas para los
vectores. Dada una matriz de M filas y N columnas y un valor X a buscar:
F = 0;
while (X != Matriz[F][C] && F < FILAS - 1)
{ C = 0;
While (X != Matriz[F][C] && C < COLUMNAS - 1)
C++;
If (X != Matriz[F][C])
F++;
}
if (X == Matriz[F][C])
printf("El valor %d está en la posición %d, %d", X, F, C);
else
printf("El valor %d no está en la matriz", X);

5.- Búsqueda binaria o dicotómica en un vector.


Ejercicio: piensa un número y haz un programa para que el ordenador lo adivine en el menor
número de intentos posibles. Cada vez que el ordenador te proporcione un valor, debes indicarle si es
menor, mayor o igual al que pensaste.

Igual que la búsqueda lineal, la búsqueda binaria determina si un valor se encuentra


almacenado o no en un array. La búsqueda binaria sólo puede aplicarse a vectores cuyos elementos
están ordenados. Si existen valores repetidos en el vector, la componente devuelta es una de ellas
aleatoriamente. Los pasos del algoritmo son los siguientes:

1. Comparamos el valor buscado con la componente central del vector.


2. Si no son iguales se reduce el intervalo de búsqueda a la mitad derecha o a la mitad
izquierda, dependiendo de donde pueda encontrarse el valor buscado.
3. Se repiten los pasos 1 y 2 hasta que se encuentre el elemento buscado o se anule el ámbito
de búsqueda.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 88

int LimiteInferior = 0;
int LimiteSuperior = ELTOS - 1;
int ComponenteCentral = LimiteSuperior / 2;
while (Valor != V[ComponenteCentral] && LimiteInferior < LimiteSuperior)
{ if (Valor > V[ComponenteCentral])
LimiteInferior = ComponenteCentral + 1;
else
LimiteSuperior = ComponenteCentral - 1;
ComponenteCentral = (LimiteInferior + LimiteSuperior) / 2;
}

if (Valor == V[ComponenteCentral])
ComponenteCentral;
else
Indicar que no está;

6.- Ordenación de tablas.


Se puede definir una ordenación como la reagrupación de un conjunto de elementos en una
secuencia específica. Los tipos de ordenación son los siguientes: ascendente o creciente y descendente
o decreciente.

6.1.- Ordenación por inserción directa.


El método de ordenación por inserción directa o método de la baraja, consiste en repetir el
siguiente proceso desde la segunda componente hasta la última: se toma dicha componente y se inserta
en el lugar que le corresponda entre las componentes situadas a su izquierda (estas ya estarán
ordenadas). Las componentes superiores a la tratada se desplazarán un lugar a la derecha.

Ejemplo de evolución de la secuencia:

Secuencia inicial: 3 2 4 1 2
Primer paso: 2 3 4 1 2
Segundo paso: 2 3 4 1 2
Tercer paso: 1 2 3 4 2
Cuarto paso: 1 2 2 3 4

Algoritmo:
void InsercionDirecta(int *V)
{ int i, j, Auxiliar;
for (i = 1; i < ELTOS; i++)
{ Auxiliar = V[i];
j = i - 1;
while (V[j] > Auxiliar && j > 0)
{ V[j + 1] = V[j];
j--;
}
if (V[j] > Auxiliar)
{ V[j + 1] = V[j];
V[j] = Auxiliar;
}
else
V[j + 1] = Auxiliar;
}
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 89

6.2.- Ordenación por selección directa.


El método consiste en repetir el siguiente proceso desde la primera componente hasta la
penúltima: se selecciona la componente de menor valor de las situadas a la derecha de la componente
tratada y se intercambia con esta.

Ejemplo de evolución de la secuencia:

Secuencia inicial: 3 2 4 1 2
Primer paso: 1 2 4 3 2
Segundo paso: 1 2 4 3 2
Tercer paso: 1 2 2 3 4
Cuarto paso: 1 2 2 3 4
void SeleccionDirecta(int *V)
{ int i, j, k, Auxiliar;
for (i = 0; i < ELTOS - 1; i++)
{ k = i;
Auxiliar = V[i];
for (j = i + 1; j < ELTOS; j++)
if (V[j] < Auxiliar)
{ k = j;
Auxiliar = V[j];
}
V[k] = V[i];
V[i] = Auxiliar;
}
}

Otro algoritmo, semejante al anterior pero realizando el intercambio al finalizar la búsqueda; se


usa el índice sólo para marcar la posición pero no se almacena su valor en una variable temporal dado
que está almacenado en el vector en dicho lugar.
void SeleccionDirecta(int *V)
{ int i, j, k, Auxiliar;
for (i = 0; i < ELTOS - 1; i++)
{ k = i;
for (j = i + 1; j < ELTOS; j++)
if (V[j] < V[k])
k = j;
Auxiliar = V[k];
V[k] = V[i];
V[i] = Auxiliar;
}
}

6.3.- Ordenación por intercambio directo o método de la burbuja.


El método consiste en recorrer sucesivamente el vector comparando los elementos
consecutivos e intercambiándolos cuando estén desordenados. El método de la burbuja tiene dos
versiones:

Recorrido de izquierda a derecha: consiste en colocar en cada pasada el elemento mayor de


los tratados en la última posición quedando colocado y por lo tanto excluido de los elementos a
tratar en la siguiente pasada al vector.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 90

Ejemplo de evolución de la secuencia:

Secuencia inicial: 3 2 4 1 2
Primer paso: 2 3 1 2 4
Segundo paso: 2 1 2 3 4
Tercer paso: 1 2 2 3 4
Cuarto paso: 1 2 2 3 4
void BurbujaID(int *V)
{ int i, j, Auxiliar;
for (i = ELTOS - 2; i >= 0; i--)
for (j = 0; j <= i; j++)
if (V[j] > V[j + 1])
{ Auxiliar = V[j];
V[j] = V[j + 1];
V[j + 1] = Auxiliar;
}
}

Recorrido de derecha a izquierda: esta versión es igual que la anterior pero desplazando los
elementos menores a la izquierda.

Ejemplo de evolución de la secuencia:

Secuencia inicial: 3 2 4 1 2
Primer paso: 1 3 2 4 2
Segundo paso: 1 2 3 2 4
Tercer paso: 1 2 2 3 4
Cuarto paso: 1 2 2 3 4
void BurbujaDI(int *V)
{ int i, j, Auxiliar;
for (i = 1; i < ELTOS; i++)
for (j = ELTOS - 1; j >= i; j--)
if (V[j - 1] > V[j])
{ Auxiliar = V[j - 1];
V[j - 1] = V[j];
V[j] = Auxiliar;
}
}

7.- Mezcla de vectores.


La operación de mezcla de dos arrays unidimensionales consiste en que dados dos vectores
ordenados, TABLA1 y TABLA2, se obtenga un tercer array ordenado, TABLA3, que contenga los
valores de los dos primeros arrays. Suponiendo que el tamaño de los arrays iniciales es LON1 y LON2
respectivamente, el tamaño del array resultante es, por tanto, LON1 + LON2. La forma más sencilla de
obtener resultados consiste en copiar TABLA1 y TABLA2 en TABLA3 y ordenar esta por cualquier
método de ordenación. Sin embargo, esta forma no usa la ventaja de la ordenación previa de los arrays
originales.

El proceso es el siguiente:

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 91

· Se comparan los elementos situados en la primera posición de TABLA1 y TABLA2 y el


menor se coloca en la primera posición de TABLA3.

· Se compara el elemento situado en la posición dos del array, del cual se pasó el elemento a
TABLA3, con el primero del otro array. El más pequeño se coloca en la posición dos de
TABLA3, y así sucesivamente.

· Llegará un momento en que se hayan pasado todos los elementos de un array a TABLA3,
quedando solamente elementos del otro array. Dichos elementos se pasarán directamente a
TABLA3.

El algoritmo de mezcla es el siguiente:


void Mezcla(int *V1, int *V2, int *V3)
{ int Indice1 = 0, Indice2 = 0, Indice3 = 0;
while (Indice1 < ELTOS && Indice2 < ELTO2)
{ if (V1[Indice1] < V2[Indice2])
{ V3[Indice3] = V1[Indice1];
Indice1++;
}
else
{ V3[Indice3] = V2[Indice2];
Indice2++;
}
Indice3++;
}
for (Indice1; Indice1 < ELTOS; Indice1++)
{ V3[Indice3] = V1[Indice1];
Indice3++;
}
for (Indice2; Indice2 < ELTO2; Indice2++)
{ V3[Indice3] = V2[Indice2];
Indice3++;
}
}

Usando mejor el lenguaje C:


void Mezcla(int *V1, int *V2, int *V3)
{ int Indice1 = 0, Indice2 = 0, Indice3 = 0;
while (Indice1 < ELTOS && Indice2 < ELTO2)
{ if (V1[Indice1] < V2[Indice2])
V3[Indice3++] = V1[Indice1++];
else
V3[Indice3++] = V2[Indice2++];
}
while (Indice1 < ELTOS)
V3[Indice3++] = V1[Indice1++];
while (Indice2 < ELTO2)
V3[Indice3++] = V2[Indice2++];
}

Ejercicios :

1. Programa que lee una secuencia de 50 números cargándolos en un vector y a continuación


encuentra la posición que ocupa el primer número negativo en caso de existir. Si no hay
números negativos, escribirá un mensaje indicándolo.

2. Programa que carga 100 nombres en un vector y a continuación permite consultas sucesivas
para ver si un nombre, introducido por teclado, se encuentra o no dentro del vector.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 92

3. Cargar dos vectores de N componentes cada uno; el primero almacena nombres de personas y
el segundo sus números de teléfono. Realizar un programa que permita consultas sucesivas de
teléfonos para nombres introducidos por teclado.

4. Igual que el ejercicio anterior. En lugar de dos vectores, una matriz de 2*N.

5. Programa que carga dos vectores de 35 componentes cada uno, almacenando en el primero una
lista de nombres de personas y en el segundo las edades correspondientes a cada una de ellas. A
continuación permite consultas sucesivas de edades para nombres introducidos por teclado.
Suponiendo que el vector de nombres está ordenado ascendentemente, usar el método de
búsqueda binaria.

6. Programa que realiza una ordenación de un vector imprimiendo todos los estados intermedios
del mismo.

7. Programa que clasifica simultáneamente dos vectores numéricos de igual dimensión, el primero
en orden creciente y el segundo en orden decreciente.

8. Programa que carga una matriz alfanumérica de 80*2 conteniendo en cada un nombre de
persona y su número de teléfono. A continuación realiza una clasificación ascendente por orden
alfabético de nombres y, finalmente, imprime la lista ordenada de nombres y teléfonos.

9. Permitir consultas sucesivas de los teléfonos correspondientes a nombres introducidos por


teclado por el método de búsqueda binaria en la tabla ordenada del ejercicio anterior.

10. Programa que carga un vector numérico de 100 componentes y obtiene e imprime los 10
valores menores y los 10 mayores.

11. Dado un vector numérico de 100 componentes. Programa que clasifica simultáneamente en
orden creciente sus componentes pares y en orden decreciente las impares.

12. Programa que carga una matriz de 100 filas y 3 columnas, con primer apellido, segundo
apellido y nombre de 100 personas, realizando una clasificación alfabética completa e
imprimiendo la lista de nombres clasificada.

13. Programa que realiza la clasificación completa de una matriz numérica en orden creciente (de
izquierda a derecha y de arriba a abajo).

14. Programa que carga dos vectores, V1 y V2, de N componentes cada uno; los ordena
ascendentemente y realiza una mezcla de los dos. El resultado es otro vector V3 ordenado.

15. Programa que genere la combinación de la lotería primitiva.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 93

11.- USO DE PUNTEROS COMO ARRAYS.

1.- Arrays unidimensionales y punteros.


En el lenguaje C existe una estrecha relación entre punteros y arrays; de hecho, el nombre del
array es un puntero a la primera componente del mismo. Por tanto, cualquier operación que se pueda
realizar con indexación, también se puede realizar mediante punteros, siendo este método más rápido,
en términos generales, que la indexación.

La declaración
#define LONGITUD 20
int array[LONGITUD];

define un array de LONGITUD componentes consecutivas de tipo entero (el compilador reserva la
memoria necesaria para poder almacenar todos los datos) denominados array[0], array[1], array[2], ...,
array[LONGITUD - 1]. La notación array[i] significa que el elemento del array se encuentra a i
posiciones del comienzo del mismo. Si declaramos un puntero a un entero como

int *p_array;

entonces podemos asignar la dirección del primer elemento del array a ese puntero

p_array = &array[0]

o bien, dado que el nombre del array es un puntero a la primera componente del mismo, podemos
hacer la asignación

p_array = array

que es equivalente a la asignación anterior.

p_array array
Dirección --------->
0 1 2 3 4 5

Ahora, asignaciones como


int x, y = 10;
x = *p_array;
*p_array = y;

copiarán el contenido de array[0] en la variable x y array[0] tomará el valor de la variable y


respectivamente.

Si el puntero p_array apunta al primer elemento de array, entonces por definición p_array + 1
apunta al siguiente elemento. En general, p_array - i apunta a i elementos delante de p_array y
p_array + i apunta a i elementos detrás de p_array.

La expresión
x = *(p_array + 1) ;

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 94

da el contenido del segundo elemento de array a x.


#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#define LONGITUD 10
main()
{ int array[LONGITUD];
int *p_array;
int i;

p_array = array; // Toma la dirección de array


// Carga el vector con números aleatorios
for (i=0; i<LONGITUD; i++)
*(p_array + i) = random(1000);

for (i=0; i<LONGITUD; i++)


printf("%d\t", *(p_array++));
// No necesita paréntesis. Asociatividad de derecha a izquierda
// Se pierde la dirección. Hay que asignarla de nuevo para comenzar.
// Cuidado en las funciones y bucles anidados.
getch();
}

NOTA IMPORTANTE: p_array++ modifica el contenido de la variable puntero (si su


valor era FF, ahora su valor cambia a FF + 1). Esta operación no puede realizarse sobre
una variable de tipo array puesto que este es un puntero constante, y como tal, no
puede modificarse.

Esta modificación, incremento o decremento en enteros, del valor del puntero es independiente
del tipo de dato de las componentes del array. Cuando incrementamos en una unidad el puntero al
array (p_array + 1), estamos realizando un salto a la siguiente componente del array, calculado por el
tamaño de los datos almacenados en las componentes; no estamos accediendo a la siguiente posición
de memoria. En definitiva, siempre que modifiquemos un puntero el incremento o decremento se
adecua al tamaño en memoria del objeto apuntado. El tamaño de un objeto se conoce mediante el
operador sizeof(); este retorna el número de bytes de memoria usados por dicho objeto.

Es posible restar punteros, sumar o restar enteros a punteros y compara punteros.

printf("\n%d %d", sizeof(array), sizeof(i));


// 40 4 Depende de la longitud de los enteros de la máquina

Como el compilador convierte toda referencia a un array en un puntero al comienzo del mismo,
también son válidos los acceso al array de la siguiente manera:

- Trabajando con la definición del array


*array, array[0] Contenido de la primera componente
*(array + 1), array[1] Contenido de la segunda componente
*(array + i), array[i] Contenido de la componente i

- Trabajando con puntero a entero


*p_array, p_array[0] Contenido de la primera componente
*(p_array + 1), p_array[1] Contenido de la segunda componente
*(p_array + i), p_array[i] Contenido de la componente i

&array[i], array + i
p_array + i, &p_array[i]

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 95

Como se ve, con todo puntero que apunta a un array puede usarse la indexación de elementos y
con todo array pueden usarse los punteros para acceder a las componentes. Sin embargo, existen
diferencias entre el nombre de un array y un puntero a un array:

· Un puntero es una variable, por lo que se puede modificar su valor con operaciones como
p_array++ o --p_array mientras que el nombre de un array es una constante, no pudiendo
modificar su valor ( no podemos hacer array++, array1 = array2, array1 = &array2, son
sentencias ilegales).
· La declaración de una variable de tipo puntero no lleva implícita la asignación o reserva de
memoria por el compilador con lo que no puede usarse mientras no se realice una asignación a
una zona de memoria. Esta asignación de memoria se realiza como se ha visto antes (p_array =
array o p_array = &array[0]) o bien, mediante la petición directa de memoria.

2.- Arrays bidimensionales y punteros.


Los arrays bidimensionales necesitan dos índices para poder referenciar los elementos que
contienen por lo que no pueden equivaler a un único puntero si no a un array de punteros. La
definición de un array de punteros es la siguiente:

tipo_dato *identificador[LONGITUD]; //Crea un array de punteros

La siguiente definición declara un array de LONGITUD punteros a enteros; en cada uno de sus
elementos podemos almacenar la dirección de otra variable, tal como la dirección de una variable de
tipo int (dato) o la dirección de un vector de enteros (notas). En definitiva, la longitud del dato al que
apunta cada componente del array de punteros es irrelevante.
main()
{ int *p_array[LONGITUD];
int dato = 20, i;
int notas[ELTOS] = {0,1,2,3,4,5,6,7,8,9};
p_array[0] = &dato;
p_array[1] = notas;
printf("%d\n", *p_array[0]);
for (i=0; i<ELTOS/2; i++)
printf("%d\t", p_array[1][i]);
for (i=ELTOS/2; i<ELTOS; i++)
printf("%d\t", *(p_array[1] + i));
}

p_array dato
---> 20
---> 0 1 2 3 4 5 6 7 8 9
Notas

Se puede ver como una posición de un array de punteros referencia a una variable. La cuestión
es que pasaría si cada elemento de un array apuntara a la dirección de otro puntero. Esto se denomina
doble indirección, que hay que definir como los demás objetos del programa. La declaración

int **puntero_puntero; //Crea una var. ptr. que apunta a otra var. ptr.

indica que se está declarando una variable puntero que almacena la dirección de otro puntero a un
entero.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 96

Puntero Variable
dirección -------------------> Valor
Indirección simple

Puntero Puntero Variable


dirección -------------------> dirección -------------------> Valor
Indirección múltiple
int Variable ;
int *p, **q ;
p= &Variable ; // p toma la dirección de Variable
q= &p ; // q toma la dirección de p (que tiene la de Variable)
**q= 9 ; // En Variable se almacena un 9

Desde este punto de vista, un array multidimensional se puede expresar como un array de
punteros. Cada puntero indica el comienzo de un array con un tamaño (N - 1). Por tanto un array
bidimensional se puede definir como un array unidimensional de punteros de la forma:

tipo_dato *identificador[LONGITUD];

p_array Vectores de enteros


F ----------> F F+sizeof F+sizeof*2 F+sizeof*3
C ----------> C C+sizeof C+sizeof*2
A ---------->
Esquema de un array de punteros a vectores.

en lugar de la definición convencional:

tipo_dato identificador[FILAS][COLUMNAS];

array
FF FF + sizeof() FF + sizeof()*2 FF + sizeof()*3
FF + sizeof()*4

Esquema de un array convencional.

NOTA:Los contenidos de las celdas no son reales a excepción de los de p_array que si
almacenan una dirección; el resto, sólo representan la dirección de memoria que
ocupan, no el dato almacenado.

Usar un array de punteros para definir un array multidimensional presenta ventajas e


inconvenientes frente al uso de la definición convencional:

· Utiliza más memoria.


· El compilador no reserva memoria y por tanto necesita una inicialización explícita para
obtenerla. Si reserva espacio para el array de vectores.
· El acceso a los elementos es más rápido usando la indirección.
· Las filas a las que apunta cada puntero pueden ser de diferentes longitudes.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 97

Expresiones propias de arrays de punteros, que también pueden usarse con arrays
bidimensionales normales, son las siguientes:

p_array[i] + j &array[i][j] Dirección del elemento [i][j].

*(p_array + i) Array[i] Dirección de la fila [i].

p_array[i] + j *(array + i) + j Dirección del elemento [i][j].

*(p_array[i] + j) array[i][j] Valor del elemento [i][j].

*(*(p_array + i) + j) Array[i][j] Valor del elemento [i][j].


main()
{ int *p_array[FILAS];
int i, j;

for (i=0; i<FILAS; i++)


p_array[i] = (int *) malloc(COLUMNAS * sizeof(int));

for (i=0; i<FILAS; i++)


for (j=0; j<COLUMNAS; j++)
*(p_array[i] + j) = random(100);

for (i=0; i<FILAS; i++)


{ printf("\n");
for (j=0; j<COLUMNAS; j++)
printf("%d\t",*(*(p_array + i) + j));
}
for (i=0; i<FILAS; i++)
for (j=0; j<COLUMNAS; j++)
*(p_array[i] + j) *= CONSTANTE;

for (i=0; i<FILAS; i++)


{ printf("\n");
for (j=0; j<COLUMNAS; j++)
printf("%d\t",*(p_array[i] + j));
}
getch();
}

También, a un array multidimensional definido de forma convencional, se puede acceder a sus


elementos mediante punteros sin usar la indexación:
int array[3][4];

*(*array + 4) array[1][0] Valor del elemento [1][0]


*(*array + 7] array[1][3] Valor del elemento [1][3]
*array + 9 &array[2][1] Direcc. del elemento [2][1].
array + i array[i] Dirección de la fila i

En general, para acceder a un elemento del array se puede aplicar la expresión:

índice_fila * COLUMNAS + índice_columna

Así el elemento [1][3] del array anterior, se puede expresar como

1 * COLUMNAS(4 elementos) + 3 = 7 (Octavo elemento de la tabla)

Lo anterior sólo es válido para arrays bidimensionales definidos con sus longitudes dado que
todas las componentes se almacenan en posiciones consecutivas de memoria reservadas por el

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 98

compilador en tiempo de compilación; no es válido para un array de punteros a vectores puesto que
cada vector se almacenará en posiciones de memoria no necesariamente consecutivas asignadas en
tiempo de ejecución, no de compilación.
main()
{ int array[FILAS][COLUMNAS];
int i, j;

for (i=0; i<FILAS; i++)


for (j=0; j<COLUMNAS; j++)
*(*array + i*COLUMNAS+j) = i+3;

for (i=0; i<FILAS; i++)


{ puts("\n");
for (j=0; j<COLUMNAS; j++)
printf("V:%d\tD:%d\t", array[i][j], *array + i*COLUMNAS+j);
}
getch();
}

3.- Reserva de memoria.


En C y otros lenguajes de programación se puede asignar y liberar memoria dinámicamente; en
lenguaje C las funciones usadas para tal propósito son calloc(), malloc() y free(). La sintaxis de estas
funciones es la siguiente:

#include <stdlib.h>
puntero = void (tipo *) calloc(Numero_Elementos, Tamaño_Elemento);
puntero = void (tipo *) malloc(Tamaño_Total_Necesario);
void free(puntero);

El tamaño de memoria se calcula con el operador sizeof(nombre_tipo) y si no hay memoria


disponible retorna el puntero NULL.
#include <stdio.h>
#include <stdlib.h>
#define LONGITUD 10
main()
{ int *p_array, i;
p_array = (int *) calloc(LONGITUD, sizeof(int));
// Reserva memoria para LONGITUD elementos de tipo int.
if (p_array == NULL)
{ puts("Sin memoria");
exit(0); //stdlib.h
}
for (i=0; i<LONGITUD; i++)
*(p_array + i) = random(1000);

for (i=0; i<LONGITUD; i++)


printf("%d\t", p_array[i]);
free(p_array);
}

Una vez que no se necesita la memoria reservada con las funciones calloc() y/o malloc() hay
que liberarla, devolvérsela al sistema, con la función free() para un uso posterior.

Es necesario reservar memoria para los punteros antes de hacer uso de ellos.

Array de dos dimensiones creado dinámicamente.


#include <stdio.h>

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 99

#include <stdlib.h>
#include <conio.h>
#define FILAS 3
#define COLUMNAS 2

main()
{ int **p_p_array;
int i, j;

p_p_array = (int **) malloc(FILAS*sizeof(int*));


for (i=0; i<FILAS; i++)
{ *(p_p_array + i) = (int *) malloc(COLUMNAS*sizeof(int));
for (j=0; j<COLUMNAS; j++)
*(*(p_p_array + i) + j) = rand() % 100;
}

printf("Puntero: p_p_array= %d\n", p_p_array);


printf("Vector de punteros: &p_p_array[0]= %d\n", &p_p_array[0]);
for (i=0; i<FILAS; i++)
{ puts("\n");
printf("Fila: *(p_p_array + %d)= %d\n", i, *(p_p_array + i));
for (j=0; j<COLUMNAS; j++)
{ printf("Componente: *(p_p_array +%d)+%d=%d\t",i,j,*(p_p_array+i)+j);
printf("Valor: *(*(p_p_array + i) + j)= %d\n", *(*(p_p_array+i)+j));
}
}
getch();
for (i=0; i<FILAS; i++)
free(*(p_p_array + i));
free(p_p_array);
}

4.- Paso de parámetros array a funciones.


Cuando se pasa un array a una función, se está realizando una transferencia por variable o
referencia; el parámetro actual es el propio nombre del array, que es la dirección del mismo.

4.1.- Arrays unidimensionales.


Longitud fija:

El encabezamiento de una función con un vector como parámetro formal puede ser de tres
formas diferentes:

Tipo identificador(tipo vector[LONGITUD]);


Tipo identificador(tipo vector[]);
Tipo identificador(tipo *vector);

La forma habitual de pasar vectores a funciones es la última. El hecho de especificar la


longitud no tiene más interés que el meramente simbólico, facilitar la lectura, ya que el compilador no
comprueba los límites de un array. En cualquier caso, realmente se está indicando al compilador que
va a recibir un puntero. Independientemente del encabezamiento empleado, en el cuerpo de la función
se puede usar el método de indexación o aritmética de punteros para acceder a los elementos del array.
void visualizar(int *v)
{ int i,*q;
for (i=0; i<ELEMENTOS; i++) printf("%d", v[i]);
for (i=0, i<ELEMENTOS; i++) printf("%d", *(v+i));
for (i=0; q=v; i<ELEMENTOS; i++, q++) printf("%d", *q);
for (q=v; q<v+ELEMENTOS; q++) printf("%d", *q);
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 100

main()
{ int vector[ELEMENTOS]={1,2,3,4,5,6};
visualizar(vector);
}

Genéricos:

Visto del modo anterior, las funciones no son independientes; dependen de la constante
simbólica definida anteriormente (excepto las cadenas, que acaban con el nulo). Para solventar esta
dependencia, sólo hay que pasar a la función un parámetro más: la longitud del array.

Función que inserta un elemento en una posición del array:


* posición: contando desde 1, no desde 0.
* longitud: elementos actuales, no totales.
int insertar(int *tabla, int longitud, int elemento, int posicion)
{
for (i=longitud - 1; i>=posicion - 1; i--)
tabla[i + 1] = tabla[i];
tabla[posicion - 1] = elemento;
longitud++;
return longitud;
}

Ejercicios: Indicar si dos vectores disjuntos.


Indicar si el contenido de un vector es capicúa.

4.2.- Arrays bidimensionales.

Longitud fija:

Son necesarios dos índices para referenciar un elemento de una matriz. Cuando pasamos una
matriz a una función, el compilador necesita conocer, por lo menos, la segunda dimensión de la
misma, no puede contentarse con el puntero al comienzo del primer elemento.

Prototipo: tipo funcion(tipo array[][COLUMNAS]);


Definición: tipo matriz[F][C];
Llamada: var = funcion(matriz);
main()
{ void cargar(int [][COLUMNAS], int);
void visualizar(int [][COLUMNAS], int);
int array[FILAS][COLUMNAS];
cargar(array, FILAS);
visualizar(array, FILAS);
}
void cargar(int tabla[][COLUMNAS], int f)
{ int i, j;
for (i=0; i<f; i++)
for (j=0; j<COLUMNAS; j++)
tabla[i][j] = rand() % 100;
}
void visualizar(int tabla[][COLUMNAS], int f)
{ int i, j;
puts("\n");
for (i=0; i<f; i++)
for (j=0; j<COLUMNAS; j++)
printf("Valor= %d\n", *(*(tabla + i) + j));
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 101

Longitud variable con un puntero:

Prototipo: tipo funcion(tipo *array, int filas, int columnas);


Definición: tipo matriz[F][C];
Llamada: var = funcion(matriz, f, c); //Con error warning.
var = funcion(&matriz[0][0], f, c);

En el cuerpo de la función sólo podemos usar un índice dadas las características del parámetro
formal: puntero a entero.
main()
{ void cargar(int *, int, int);
void visualizar(int *, int, int);
int array[FILAS][COLUMNAS];
cargar(&array[0][0], FILAS, COLUMNAS);
printf("Puntero: array= %d\n", array);
printf("Vector de punteros: &array[0]= %d\n", &array[0]);
visualizar(&array[0][0], FILAS, COLUMNAS);
}
void cargar(int *tabla, int f, int c)
{ int i, j;
for (i=0; i<f; i++)
for (j=0; j<c; j++)
*(tabla + i * COLUMNAS + j) = rand() % 100;
}
void visualizar(int *tabla, int f, int c)
{ int i, j;
for (i=0; i<f; i++)
for (j=0; j<c; j++)
printf("Valor= %d\n", *(tabla + i * c + j));
}

Longitud variable con reserva explícita de memoria:

Prototipo: tipo funcion(tipo **array, int filas, int columnas);


Definición: tipo *matriz[];
tipo **matriz;
Llamada: var = funcion(matriz, filas, columnas);
main()
{ void reservar_memoria(int **, int, int);
void cargar(int **, int, int);
void visualizar(int *[], int, int);
int *p_array[FILAS], i;
reservar_memoria(p_array, FILAS, COLUMNAS);
cargar(p_array, FILAS, COLUMNAS);
visualizar(p_array, FILAS, COLUMNAS);
for (i=0; i<FILAS; i++)
free(*(p_array + i));
// free(p_array); Sólo si definido como **matriz.
}
void reservar_memoria(int *tabla[], int f, int c)//Si definido *matriz[]
{ int i;
for (i=0; i<f; i++)
*(tabla + i) = (int *) malloc(c*sizeof(int));
}
int **reservar_memoria(int f, int c) // Si definido **matriz
{ int **tabla, i;
tabla = (int **) malloc(f*sizeof(int*));
for (i=0; i<f; i++)
*(tabla + i) = (int *) malloc(c*sizeof(int));
return tabla;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 102

void cargar(int **tabla, int f, int c)


{ int i, j;
for (i=0; i<f; i++)
for (j=0; j<c; j++)
*(*(tabla + i) + j) = rand() % 100;
}

void visualizar(int *tabla[], int f, int c)


{ int i, j;
for (i=0; i<f; i++)
for (j=0; j<c; j++)
printf("%d\t", *(*(tabla + i) + j));
}

El primer array es de punteros; hay que reservar memoria para el tamaño del puntero.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 103

12.- CADENAS DE CARACTERES.

1.- Introducción
En lenguaje C no existe ningún tipo de dato para definir variables de tipo cadena. El medio
para almacenar esta información es un array de caracteres. Existe una convención de representación de
las cadenas:

· por parte del compilador, para representar las cadenas constantes (escritas entre comillas);
· por un cierto número de funciones, que permiten realizar:
· lecturas o escrituras de cadenas;
· tratamientos como concatenación, copia, comparación, ...

2.- Representación de las cadenas.


Una cadena de caracteres se representa por una serie de bytes correspondientes a cada uno de
sus caracteres o código ASCII que finaliza con carácter nulo('\0'). Esto significa que una cadena de N
caracteres de longitud consta de N-1 caracteres (la cadena propiamente dicha) más el carácter nulo. De
hecho, cuando escribimos una cadena constante ("El hotel de los líos"), en su representación interna, el
compilador añade al final el carácter nulo; de esta forma es posible conocer el final del array sin
necesidad de conocer su tamaño.
#define LONGITUD 10
char cadena[LONGITUD + 1];

cadena
E l h o t e l . . \0
0 1 2 3 4 5 6 7 8 . N-1

Además, el compilador traduce esta notación de cadena constante en un puntero (a char) a la


zona de memoria correspondiente.
#include <stdio.h>
main()
{ char *cadena; //Puntero a char

cadena = "Cadena constante"; //Se asigna un puntero a char


while (*cadena)
printf("%c", *cadena++);
}

Es posible realizar la asignación anterior

cadena = "Cadena constante";

porque cadena es un puntero y "Cadena constante" ha sido traducido por el compilador a un puntero a
la memoria donde se almacena; sin embargo, la siguiente asignación no es válida porque, aunque el
nombre de un array es un puntero al mismo, este es constante y no se puede modificar.
char cadena[LONGITUD + 1];
cadena = "Cadena constante";

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 104

Si, en lugar de asignar una cadena constante al puntero, queremos usar una de las funciones de
entrada de datos por teclado o copiar el contenido de otra variable, es necesario reservar la memoria
suficiente para almacenar los datos; como todo puntero, no se reserva memoria.
cadena=(char*)malloc(LONGITUD);
scanf("%s", cadena);
printf("%s", cadena);

3.- Inicialización de arrays de caracteres.


La inicialización de una cadena es semejante a la inicialización de un array. En la inicialización
convencional hay que añadir el carácter nulo al final:
char cadena[10]={'E', 'l', ' ', 'h', 'o', 't', 'e', 'l', '.', '\0'}

Además de la inicialización anterior, también está permitida la siguiente:


char cadena[10] = "Una cadena."; // Contar los caracteres. No puede haber más.
char cadena[] = " Una cadena.";
char *cadena = " Una cadena.";

En cualquiera de las dos inicializaciones anteriores, con longitud explícita o sin ella, el
compilador añade el carácter nulo al final de la cadena.

También se pueden inicializar arrays con varias cadenas:


#include <stdio.h>
void ver34(char **);
void ver12(char [][4]);
main()
{ char cad1[7][4]={"Lun", "Mar", "Mie", "Jue", "Vie", "Sab", "Dom"};
char cad2[][4]={"Lun", "Mar", "Mie", "Jue", "Vie", "Sab", "Dom"};
char *cad3[7]={"Lunes", "Martes", "Miercoles", "Jueves", "Viernes", "Sab", "Dom"};
char *cad4[]={"Lunes", "Martes", "Miercoles", "Jueves", "Viernes", "Sab", "Dom"};

ver12(cad1); ver12(cad2); ver34(cad3); ver34(cad4);


getch();
}
void ver12(char cadena[][4])
{ int i;
for (i=0; i<7; i++)
puts(*(cadena + i));
}
void ver34(char **cadena)
{ int i;
printf("\n");
for (i=0; i<7; i++)
printf("%s\t", cadena[i]);
}

4.- Ejemplos de funciones con cadenas.


En todos los ejemplos se supone que hay memoria reservada para las cadenas usadas.

- Calcular la longitud de una cadena:


int cadlong(char *cadena)
{ int i;
for (i=0; *cadena; cadena++, i++);
return i;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 105

- Comparar alfabéticamente dos cadenas:

Si cadena1 > cadena2 entonces retorna > 0


Si cadena1 < cadena2 entonces retorna < 0
Si cadena1 = cadena2 entonces retorna = 0
int cadcomp(char *cadena1, char *cadena2)
{ while (*cadena1 == *cadena2 && *cadena1)
{ cadena1++;
cadena2++;
}
return *cadena1 - *cadena2;
}

- Copiar una cadena en otra:


void cadcopi(char *cadena1, char *cadena2)
{ while (*cadena1++ = *cadena2++);
}

- Encontrar un carácter en una cadena: devuelve un puntero a la posición ocupada por el


carácter en la cadena o NULL si no se encuentra.
char *cadcar(char *cadena, int caracter)
{ while (caracter != *cadena && *cadena)
cadena++;
return *cadena == caracter ? cadena : NULL;
}

- Ordenar alfabéticamente una lista de apellidos:


int cadcomp(char *, char *);
void ordenar(char **, int);
void visualizar(char **, int);
main()
{ char *apellidos[] = {"Sanchez", "Perez", "Diaz", "Lozano", "Garcia"};
ordenar(apellidos, 5);
visualizar(apellidos, 5);
}
void ordenar(char **lista, int Longitud)
{ int i, j, k;
char *Auxiliar;
for (i = 0; i < Longitud - 1; i++)
{ k = i;
for (j = i + 1; j < Longitud; j++)
if (cadcomp(*(lista + j), *(lista + k)) < 0)
k = j;
Auxiliar = *(lista + k);
*(lista + k) = *(lista + i);
*(lista + i) = Auxiliar;
}
}
void visualizar(char **lista, int Longitud)
{ int i;
for (i=0; i < Longitud; i++)
printf("%s\t",lista[i]);
}

Ejercicios: Comprobar si una frase es o no un Palíndromo.


Juego del ahorcado.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 106

5.- Funciones de estándar para la manipulación de cadenas.


Estas funciones pertenecen a la librería <string.h>.

** char *strcat(char *destino, const char *fuente);

Añade a destino el contenido de fuente. Retorna un puntero a la primera cadena o un NULL si


no se ha realizado la concatenación. La primera cadena debe tener suficiente espacio para las dos.
int main(void)
{ char destino[30] = "El hotel";
char *blanco = " "; *segunda = "de los lios";

strcat(destino, blanco);
strcat(destino, segunda);
printf("%s\n", destino);
}

** char *strncat(char *destino, const char *fuente, size_t maxlon);

Es igual que la anterior pero copiando de la segunda cadena sólo el número de caracteres
especificado por maxlon.
strcat(destino, blanco);
strncat(destino, segunda, 4);
printf("%s\n", destino);

** int strcmp(const char *cadena1, const char *cadena2);

Compara dos cadenas de las que recibe la dirección. Retorna un valor entero definido como:

- Positivo: cadena1 > cadena2


- Negativo: cadena1 < cadena2
- Cero: cadena1 = cadena2

** int strncmp(const char *s1, const char *s2, size_t maxlon);

Es igual que la anterior pero comparando sólo el número de caracteres especificado por
maxlon.

** int stricmp(const char *s1, const char *s2);


int strnicmp(const char *s1, const char *s2, size_t maxlon);

Son similares a strcmp y strncmp pero sin tener en cuenta la diferencia entre caracteres
mayúsculas y minúsculas. No incluye los caracteres europeos, tal como la 'Ñ'.

** char *strcpy(char *destino, const char *fuente);

Copia una cadena en otra. Debe haber espacio suficiente.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 107

** char *strncpy(char *destino, const char *fuente, size_t maxlon);

Es igual que la anterior pero copiando sólo el número de caracteres especificado por maxlon. Si
la longitud de la cadena origen es inferior a la longitud máxima, el carácter nulo de fin de cadena se
copia; en caso contrario no se copiará.
char *c1 = "xxxxxxxxxxxxxxxxxxxx";
char c2[20] = "bueno";
char c3[20] = "buenos";
strncpy(c1, c2, 6);
printf("%s", c1); //"bueno";
c1 = "xxxxxxxxxxxxxxxxxxxx";
strncpy(c1, c3, 6);
printf("%s", c1); //"buenosxxxxxxxxxxxxxx";

** char *strchr(const char *cadena, int carácter);

Busca la primera posición que ocupa un carácter en una cadena. Retorna un puntero a esa
posición o uno NULL.

** char *strrchr(const char *s, int c);

Busca la última posición que ocupa un carácter en una cadena. Retorna un puntero a esa
posición o uno NULL.

** char *strstr(const char *cadena, const char *subcadena);

Busca la primera ocurrencia completa de una subcadena en una cadena. Retorna un puntero a
esa posición o uno NULL.

** char *strpbrk(const char *cadena1, const char *cadena2);

Busca la primera ocurrencia de cualquier carácter de una cadena en otra cadena. Retorna un
puntero a esa posición o uno NULL.

** char *strlwr(char *s);

Convierte una cadena a minúsculas. Sólo de a-z.

** char *strupr(char *s);

Convierte una cadena a mayúsculas. Sólo de a-z.

Otras funciones no-ANSI: strspn, strcspn, strtok

6.- Funciones de conversión.


Pertenecen a la <stdlib.h>.

** int atoi(const char *s); Convierte una cadena a un int.


** long atol(const char *s); Convierte una cadena a un long.
** double atof(const char *s); Convierte una cadena a un double.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 108

Todas reconocen los caracteres adecuados a su tipo de dato. Ignoran los espacios iniciales,
devuelven el valor cero si el primer carácter es erróneo y la exploración finaliza con el primer carácter
no válido.

** double strtod(const char *s, char *siguiente);


** long strtol(const char *s, char *siguiente, int base);
** unsigned long strtoul(const char *s, char *siguiente, int base);

Convierten una cadena a número double, long y unsigned long respectivamente. El puntero
siguiente es la dirección del siguiente carácter a tratar. Base es la base numérica en la que se trabaja.

7.- Funciones de caracteres.


Pertenecen a la librería <ctype.h>.

int tolower(int ch) Carácter a minúsculas o ch. Sólo de a-z.


int toupper(int ch) Carácter a mayúsculas o ch. Sólo de a-z.
int isalnum(int) ¿Alfanumérico (a-z, 0-9)? (CIERTO/FALSO)
int isalpha(int) ¿Alfabético (a-z)? (CIERTO/FALSO)
int iscntrl(int) 0-31 y 0127 (CIERTO/FALSO)
int isdigit(int) ¿Dígito? (CIERTO/FALSO)
int isgraph(int) Carácter imprimible menos es blanco. (CIERTO/FALSO)
int islower(int) ¿Minúsculas? (CIERTO/FALSO)
int isprint(int) Carácter imprimible incluído el blanco.(CIERTO/FALSO)
int ispunct(int) Imprimible menos blanco y alfanumécos. (CIERTO/FALSO)
int isspace(int) ¿Blanco, nueva línea, return, tab? (CIERTO/FALSO)
int isupper(int) ¿Mayúsculas? (CIERTO/FALSO)
int isxdigit(int) ¿Dígito hexadecimal? (CIERTO/FALSO)

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 109

13.- ESTRUCTURAS, CAMPOS DE BITS Y UNIONES

1.- Definición de registro o estructura.


Un registro es un tipo de datos estructurado que está constituido por un conjunto de elementos,
de igual o diferente tipo de dato, con alguna relación entre ellos; cada uno de estos elementos recibe el
nombre de campo o atributo del registro; un archivo es un conjunto de registros similares. Cada
elemento a su vez puede estar constituido por un conjunto de subelementos. Aquellos elementos que
son individuales reciben el nombre de elementos simples, átomos o escalares. Los nombres asignados
a los elementos reciben el nombre de identificadores.

Aunque un registro es un conjunto de elementos, se diferencia de un array lineal por lo


siguiente:

· Un registro puede ser un conjunto de datos no homogéneo, es decir, los elementos de un


registro pueden ser de diferente tipo de dato.
· Los elementos de un registro se referencian a través del nombre de cada atributo o campo,
por lo que no existe un orden natural de sus elementos.

Dentro de la relación entre grupo de elementos y subelementos, los elementos de un registro


forman una estructura jerárquica que puede describirse a través de números de nivel.

Los registros de los estudiantes de una clase pueden organizarse como:

1._ Estudiante
2._ Nombre
3._ Primer apellido
3._ Segundo apellido
3._ Nombre
2._ Fecha nacimiento
3._ Día
3._ Mes
3._ Año
2._ Examen(3)
2._ Final
2._ Calificación

2.- Declaración de estructuras.


Por lo general, una estructura se declara de la siguiente forma:

struct [<tipo_estructura>]
{ <tipo1> <identificador_campo_1>;
<tipo2> <identificador_campo_2>;
...
<tipoN> <identificador_campo_N>;
}[<variable_estructura>[,...]];

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 110

Los campos de la estructura pueden ser de cualquier tipo de dato permitido en C: arrays,
punteros, tipos simples, otras estructuras, ...

<variable_estructura> y <tipo_estructura> son opcionales, pero uno de los dos, o los dos, debe
aparecer en la declaración. Si en la declaración aparece <tipo_estructura>, estamos creando un tipo de
estructura, siendo necesario crear una variable de ese <tipo_estructura> (si está <variable_estructura>
no es necesario crearla pues se crea en la propia declaración). El formato para crear una variable
estructura es el siguiente:

struct <tipo_estructura> <identificador_variable>[,...];

Cuadro resumen de la declaración de una estructura en C.

Formato 1 Formato 2 Formato 3


struct s_persona struct struct s_persona
{ { {
char nombre[15]; char nombre[15]; char nombre[15];
char apelli[30]; char apelli[30]; char apelli[30];
int expediente; int expediente; int expediente;
int notas[3]; int notas[3]; int notas[3];
} la_persona; } persona; };

struct s_persona per1; ----------------------- struct s_persona per2;

- Crea un tipo estructura - Crea una variable estructura - Crea un tipo estructura
(s_persona). (persona). (s_persona)

- Crea una variable estructura - No se pueden definir más - Se pueden definir más
(la_persona). variables estructura de ese variables de tipo s_persona.
tipo, salvo en la declaración. Para poder usar el nuevo tipo
- Se pueden definir más hace falta definir al menos
variables de tipo s_persona. una variable.

A los campos de una estructura también se les pueden asignar valores iniciales en la
declaración. Estos valores deben aparecer en el orden en que serán asignados a los correspondientes
campos de la estructura, encerrados entre llaves y separados por comas. Permite inicializaciones
parciales.
struct s_persona v_persona = {"Antonio","Pérez",4321};

La representación en memoria interna es semejante a la del array; los campos que componen la
estructura se sitúan en direcciones contiguas y cada uno ocupa lo que le corresponde en función de su
tipo.

Estudiante
Nombre Fecha Nacimiento Examen Final Calif.
P.Ape. S.Ape. Nombre Día Mes Año 0 1 2 Final Calif.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 111

3.- Manipulación de estructuras.


Los campos de la estructura se referencian usando el operador punto(.) o selector de campo
directo, precedido por el nombre de la variable estructura y seguido por el campo que queremos
referenciar.

<variable_estructura> . <identificador_campo>

Para acceder a los campos de la estructura anterior podemos hacerlo asignando valores
constantes con el operador de asignación o dando valores introducidos por la entrada estándar (igual
que cualquier otra variable); también se puede asignar toda la información contenida en una estructura
a otra del mismo tipo en lugar de hacerlo campo a campo.

Ejemplo:
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#define TN 3

struct SPersona
{ char Nombre[15];
char Apelli[30];
int Ntas[TN];
int Expediente;
};

main()
{ struct SPersona UnaPersona, OtraPersona;
int i;

gets(UnaPersona.Nombre);
gets(UnaPersona.Apelli);
for (i=0; i<TN; i++)
scanf("%d", &UnaPersona.Notas[i]);

OtraPersona = UnaPersona;
OtraPersona.Notas[0] /= 2;

puts(OtraPersona.Nombre);
puts(OtraPersona.Apelli);
for (i=0; i<TN; i++)
printf("%d\t", OtraPersona.Notas[i]);
getch();
}

De la misma forma que un array (que es un tipo estructurado de datos) puede ser miembro de
una estructura, otra estructura también puede serlo. Cuando esto ocurre, la declaración de la estructura
interna debe aparecer antes que la declaración de la estructura que la contiene.

Para referirse a un campo de la estructura interna debe realizarse con el operador punto; así
mismo, se debe tener en cuenta que esta es un campo de la estructura externa y, por tanto, para accede
a ella hay que usar también el operador punto.

Variable_Externa . Variable_Interna . Identificado_Campo

#include<stdio.h> #include<stdio.h>
#include<stdlib.h> #include<stdlib.h>
#include<conio.h> #include<conio.h>
#define TN 3 #define TN 3

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 112

struct Nombre struct Estudiante


{ char Apellido1[10]; { int Expediente;
char Apellido2[10]; struct
char Nombre[10]; { char Apellido1[10];
}; char Apellido2[10];
struct Fecha char Nombre[10];
{ short Dia; } sNombre;
short Mes; struct
short Anho; { short Dia;
}; short Mes;
struct Estudiante short Anho;
{ int Expediente; }Fecha_Naci;
struct Nombre El_Nombre; short Notas[TN];
struct Fecha Fecha_Naci; short Final;
short Notas[TN]; short Calificacion;
short Final; };
short Calificacion;
};

main()
{ int i;
struct Estudiante Alumno, Otro;
struct Nombre vNombre;
struct Fecha vFecha;
gets(Alumno.El_Nombre.Nombre);
gets(Alumno.El_Nombre.Apellido1);
gets(Alumno.El_Nombre.Apellido2);
for (i=0; i<TN; i++)
scanf("%d", &Alumno.Notas[i]);
Otro = Alumno;
Alumno.Fecha_Nacimiento= vFecha; //Registro completo, E.interna
}

Se puede usar cualquiera de las dos formas usadas para la declaración de la estructura; todo
depende de las necesidades futuras que tengamos (definir o no variables de las estructuras internas).

4.- Arrays de estructuras.


Como ya vimos, las componentes de un array podían ser de cualquier tipo de dato de C. Como
las estructuras son un tipo de dato admitido por el compilador de C, estas pueden ser el tipo de las
componentes de un array.
struct Estudiante Alumno[NUMERO_ALUMNOS];

Para acceder a un elemento de un array de estructuras, se debe aplicar el índice sobre el nombre
de la variable estructura seguido del operador punto y el campo de la estructura que queremos
referenciar.
Alumno[3].Expediente = 1234;

Si además queremos acceder a un carácter del campo Apellido1 o a una nota:


Alumno[3].El_Nombre.Apellido1[2] = 'a';
Alumno[3].Notas[1] = 6;

Ejercicios/Ejemplos:

1.- Se desea escribir un programa para controlar los pasajes de una línea aérea que tiene un
vuelo a ocho ciudades distintas. Para ello se necesita almacenar la capacidad de cada vuelo y el
número de plazas libres de cada vuelo, así como el número de reservas que quedan en lista de espera.
Diseña y escribe la estructura de datos necesaria.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 113

struct Pasajes
{ char Ciudad[20];
int Capacidad;
int Plazas_Libres;
int Reserva_Espera;
} Vuelo[8];

2.- Diseña y escribe una estructura de datos para almacenar la información del problema
anterior, suponiendo que puede haber varios vuelos a una misma ciudad.
struct Tiempo
{ int Hora, Minutos;
};
struct Vuelo
{ int Capacidad;
int Plazas_Libres;
int Reserva_Espera;
struct Tiempo Hora;
};
struct Pasajes
{ char Ciudad[20];
struct Vuelo DatosVuelo[NV];
} DatosPasaje[8];

3.- Se desea escribir un programa para controlar las ventas (en número y pesetas) de una
agencia de automóviles que tiene en plantilla 10 vendedores y distribuye 5 marcas de coche cada una
con 3 modelos diferentes. Diseña y escribe la estructura de datos necesaria para ello.
struct Venta
{ int Numero;
long Valor;
};
struct Agencia
{ int Codigo_Vendedor;
struct Venta Coches[MARCA][MODELO];
} Vendedores[NUMERO];

4._ Programa que carga el nombre y número de teléfono de varias personas y realiza una
clasificación ascendente por orden alfabético de nombres y, finalmente, imprime la lista ordenada de
nombres y teléfonos.

5.- Tipos de datos definidos por el usuario


Es posible definir un alias para los tipos de dato de C con la palabra reservada 'typedef'. Una
vez establecido el alias, las nuevas variables pueden ser declaradas en términos de ese nuevo tipo/alias.
El formato de definición del alias es el siguiente:

typedef <TipoDeDato> <Identificador>;

Por tanto la siguiente sentencia:


typedef float real;

significa que se reconoce la palabra real como otro nombre de float, siendo equiparable en todos los
aspectos.
float Temperatura;
real Temperatura;

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 114

La característica más útil de typedef es cuando se definen estructuras, ya que elimina la


necesidad de escribir repetidamente la palabra struct <estructura> en todos los lugares donde se
referencia la estructura.

typedef struct typedef struct <estructura> <alias>;


{ <tipo1> <identificador_campo_1>;
<tipo2> <identificador_campo_2>;
...
<tipoN> <identificador_campo_N>;
}<identificador_nuevo_tipo>;

Nota: Esta declaración puede combinarse con la anterior, usando y sin usar typedef. Es
posible definir más de un nuevo tipo.

Ejemplo:

typedef struct struct Venta


{ int Numero; { int Numero;
long Valor; long Valor;
} tVenta; };
tVenta Ventas1, Ventas2[10]; typedef Venta tVenta;
tVenta Ventas1, Ventas2[10];

6.- Estructuras y punteros.


Cuando se define un puntero a una estructura, el puntero trabaja de la misma forma que en los
casos de punteros anteriores.

La declaración de un puntero a una estructura se realiza de la siguiente forma:

<tipo_estructura>* <identificador_estructura>

Para acceder a los campos de una estructura referenciada por un puntero no se usa el operador
'.' por que se espera, como primer operando, el identificador de una estructura y no una dirección. Por
tanto, tenemos dos formas de acceder a un campo de la estructura referida por un puntero:

1. Adoptar la notación (*estructura).Campo para referenciar un campo de la estructura;

2. Usar el nuevo operador '->' o selector de campo indirecto que permite acceder a los distintos
campos de la estructura a partir de su dirección de comienzo.
struct Estudiante Alumno, *Otro;
gets(Alumno.Expediente);
gets(Alumno.El_Nombre.Nombre);
gets(Alumno.Final);
Otro = &Alumno;
printf("%d\n", Otro->Expediente);
printf("%d\n", (*Otro).Final);
printf("%s\n", Otro->El_Nombre.Nombre);

El operador '' puede combinarse con el operador '.' para acceder a campos de estructuras
incluidas en otras más externas.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 115

Como todas las definiciones de punteros, la declaración de un puntero a una estructura no


reserva memoria, siendo necesario reservarla explícitamente con la función 'malloc()' o dándole la
dirección de otra variable del mismo tipo, como en el ejemplo anterior.
Otro = &Alumno;
Otro = (struct Estudiante *) malloc(sizeof(struct Estudiante));
typedef Estudiante tEstudiante;
Otro = (tEstudiante *) malloc(sizeof(tEstudiante));

7.- Paso de estructuras a funciones.


Cuando pasamos estructuras a funciones existen dos posibilidades: pasar un campo de la
estructura o pasar una estructura completa, todos los campos. En cualquiera de los dos casos podemos
usar las dos formas que tenemos para pasar parámetros: por valor y por referencia.

Pasando un campo:

Prototipo: <tipo> Ident_Funcion(<tipo_campo> [*]P_formales);


Llamada: [<Variable> =] Ident_Funcion([&]V_estructura.Campo);

Pasando la estructura completa:

Prototipo: <tipo> Ident_Funcion(<tipo_estruct> [*]P_formales);


Llamada: [<Variable> =] Ident_Funcion([&]V_estructura);

Si se pasan los parámetros por valor, los cambios realizados sobre estos en el cuerpo de la
función no se ven reflejados en la función llamante y los campos se referencian con el operador '.'; si
se pasan por referencia, los cambios si se ven reflejados y los campos se referencian con el operador '-
>'.

El tipo de dato que retorna una función también puede ser una estructura.

Trabajo con estructuras.

Retorna una estructura. Paso por referencia.


Alumnos = alta_alumno(); Alta_alumno(&alumnos);
s_estudiante alta_alumno()
{ s_estudiante alumno; void alta_alumno(s_estudian *alumno)
scanf("%d",&alumno.expediente); { scanf("%d",&alumno->expedient);
return alumno; }
}

Pasando componentes de un array.

Estructura por valor. Estructura por referencia.


alta(*(alumnos + i)); alta(alumnos + i);
void alta(s_estudiante alumno) void alta(s_estudiante *alumno)
{ scanf("%d", &alumno.expedinte); { scanf("%d", &alumno->expedint);
} }

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 116

Ejemplo : Creación de una agenda de teléfonos.


#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
#define NPERSONAS 5

typedef struct
{ int telefono;
char nombre[30];
} agenda;

void alta(agenda *);


void ordenar(agenda *persona, int);
void visualizar(agenda *persona);

main()
{ int i;
agenda las_personas[NPERSONAS];

for (i=0; i<NPERSONAS; i++)


alta(las_personas + i);

ordenar(las_personas, NPERSONAS);

for (i=0; i<NPERSONAS; i++)


visualizar(las_personas + i);
getch();
}

void alta(agenda *persona)


{ printf("Nombre: ");
scanf("%s", &persona->nombre);
fflush(stdin);
printf("Número de telefono de %s: ", persona->nombre);
scanf("%d", &persona->telefono);
fflush(stdin);
}

void visualizar(agenda *persona)


{ printf("%s\n", persona->nombre);
printf("%d\n", persona->telefono);
}

void ordenar(agenda *persona, int Longitud)


{ int i, j, k;
agenda Auxiliar;

for (i = 0; i < Longitud - 1; i++)


{ k = i;
for (j = i + 1; j < Longitud; j++)
if (strcmp(persona[j].nombre, persona[k].nombre) < 0)
//if (strcmp((*(per + j)).nombre, (per + k)->nombre) < 0)
k = j;
Auxiliar = persona[k];
persona[k] = persona[i];
persona[i] = Auxiliar;
}
}

8.- Uniones.
Una unión es una estructura en la que sus elementos ocupan la misma posición de memoria. El
tamaño de la memoria es el correspondiene al elemento mayor de la unión y los demás elementos
comparten dicha memoria.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 117

union [<tipo_union>]
{ <tipo1> <identificador_campo_1>;
<tipo2> <identificador_campo_2>;
...
<tipoN> <identificador_campo_N>;
}[<variable_union>[,...]];

El tratamiento es igual al de la estructura. Sólo se reserva espacio en memoria para almacenar


el mayor campo, de forma que siempre hay espacio para almacenar cualquiera de los campos restantes.
Hay que saber que campo es el que está almacenado en cada instante. Suele usarse para campos de bits
o conversión de tipos. Podrían simularse registros variables.
enum Clases {Libro, Revista};
struct TBbi
{ int Referencia;
char Titulo[30];
char Autor[20];
char Editorial[25];
enum Clases Tipo;
union
{ struct
{ int Edicion;
int Anho;
} DatosEd;
struct
{ int Dia;
int Mes;
int Anho;
} Fecha;
} Publicacion;
};

main()
{ TBbi Bbi;
scanf("%d", &Bbi.Tipo);
switch (Bbi.Tipo)
{ case Libro:
puts("Libro");
scanf("%d", &Bbi.Publicacion.DatosEd.Edicion);
break;
case Revista:
puts("Revista");
scanf("%d", &Bbi.Publicacion.Fecha.Dia);
break;
}
}

9.- Campos de bits.


El lenguaje C permite acceder a bits individuales. Los campos de bits pueden ser útiles por
varias razones :

· Si el almacenamiento es limitado.
· Los dispositivos transmiten información codificada en bits.
· Ciertas rutinas de cifrado necesitan acceder a los bits de un byte.

Todas estas tareas pueden ser realizadas usando operadores para el manejo de bits pero un
campo de bits realza la estructura del código del programa. El método usado en C para acceder a los
bits es la estructura, donde un campo de bits es un tipo especial de campo que define su longitud en
bits. El formato general es el siguiente :

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 118

struct [<tipo_estructura>]
{ <tipo1> <identificador_campo_1> : longitud_1;
<tipo2> <identificador_campo_2> : longitud_2;
...
<tipoN> <identificador_campo_N> : longitud_N;
}[<variable_estructura>[,...]];

El tipo de un campo de bits debe ser int, unsigned o signed, depende del compilador. Longitud
indica el número de bits del campo.

Las restricciones del empleo de campos de bits son las siguientes :

· No se puede tomar la dirección de una variable de campos de bits.


· No se pueden construir arrays de campos de bits, deben ser de un tipo integral.
· No se puede saber de una máquina a otra si los campos de bits se distribuyen de izquierda a
derecha o al revés. Tienen alguna dependencia de la máquina.

Ejemplos :
// Empleado de una empresa
struct Emp
{ struct Dir Dirección ;
float Paga ;
unsigned baja :1 ;
unsigned PoHoras :1 ;
unsigned Deducciones :3 ;
} ;

// Estado de un adaptador de comunicaciones serie


struct TipoEstado
{ unsigned Delta_cts :1 ; // Cambio en la línea listo-para-enviar
unsigned Delta_dsr :1 ; // Cambio en datos-listos
unsigned Tr_final :1 ; // Detección final
unsigned Delta_rec :1 ; // Cambio en la línea de recepción
unsigned cts :1 ; // Listo-para-enviar
unsigned dsr :1 ; // Datos-listos
unsigned ring :1 ; // Llamada telefónica
unsigned Linea :1 ; // Señal recibida
} ;

10.- Enumeraciones.
Una enumeración es un conjunto de constantes enteras con nombre que especifica todos los
valores válidos que puede tomar una variable de ese tipo. El formato de definición de enumeraciones
es el siguiente:

enum [<Etiqueta>] {<IdentificadorDeConstante> [= <Valor>], ...} [ListaDeVariables];

· <Etiqueta> : Es una etiqueta opcional que da nombre al conjunto.


· <IdentificadorDeConstante> : Es el nombre de una constante a la que opcionalmente se le
puede asignar un valor. Son llamadas constantes enumeradas.
· <Valor> : Debe ser un valor entero. Si se omite esta inicialización, se asume que toma el
valor del la constante anterior más uno. El valor de la primera constante de la lista, por
omisión, es cero.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 119

· <ListaDeVariables> : Es una lista opcional de variables de este tipo enum.

La sentencia
enum Semana {Lunes,Martes,Miércoles,Jueves,Viernes,Sábado,Domingo}
enum Semana Hoy, Maniana ;

define una enumeración denominada Semana y declara dos Hoy y Maniana como dos variables
de ese tipo; estas sólo pueden tomar los valores del tipo semana enumerados.
main()
{ Hoy= Lunes;
cout << Hoy << '\n';
Maniana= Hoy + 1;
cout << Maniana << '\n';
for(Hoy= Lunes;Hoy<=Domingo;Hoy++)
cout << Hoy << '\n';
for(Hoy=Lunes; Hoy<=Domingo;Hoy++)
switch (Hoy)
{ case Lunes: cout << "Lunes" << '\n'; break;
case Martes: cout << "Martes" << '\n'; break;
case Miercoles:cout << "Miércoles" << '\n'; break;
}
printf(“%s”, Hoy) ;
//Es un error. Hoy es un entero, no una cadena
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 121

14.- ESTRUCTURAS DE DATOS EXTERNAS

1.- Introducción.
Hasta ahora, los objetos tratados por un programa estaban limitados por la cantidad de memoria
principal del ordenador y por que su existencia se limita al tiempo de ejecución del programa, son
volátiles. Cada vez que se ejecuta el programa es necesario introducir los datos por teclado. Ahora,
para subsanar esto problemas, los datos se deben manipular de forma que puedan ser usados por
futuros programas utilizando estructuras de datos externas o ficheros. Estos nos permitirán almacenar
grandes cantidades de información de forma permanente para, como se dijo antes, recuperarla y
tratarla en futuras operaciones de modificación o actualización.

Los archivos no se almacenan en memoria principal/central del ordenador, si no que usan


memoria externa, es decir, soportes externos de información (cinta, disco, ...) que son capaces de
almacenar la información incluso cuando no se esté ejecutando ningún programa o el ordenador no
reciba corriente eléctrica. Para realizar una semejanza entre las estructuras de datos que se usarán en
este capítulo y la vida real, imaginemos una pequeña biblioteca:

· En la biblioteca hay una serie de cajones o archivadores: archivo o fichero.


· Cada cajón contiene fichas referentes a un libro, con toda su información: registro.
· Cada ficha contiene información puntual de cada publicación: campo.

Las características principales de los archivos son:

· Independencia de la información almacenada respecto al programa.


· Acceso a un archivo por distintos programas en distintos momentos o simultáneos.
· La información almacenada es permanente.
· Gran capacidad de almacenamiento.

2.- Conceptos, definiciones.


Archivo o fichero.
Colección de información (datos relacionados entre sí) almacenada como una unidad en alguna
parte del ordenador, dispositivo de almacenamiento externo. Este conjunto de datos puede ser
usado posteriormente mediante el programa adecuado. Están limitados por la capacidad del
soporte externo.

Registro.
Tipo de datos estructurado que consta de un conjunto de elementos que pueden ser de igual o
distinto tipo. Es la unidad lógica mínima/ básica de acceso a un fichero.

Campo.
Cada uno de los elementos o informaciones individuales que componen un registro. Un campo
puede estar dividido en subcampos.

Un archivo es, por tanto, un conjunto de datos estructurados en una colección de entidades
elementales o básicas denominadas registros que son de igual tipo y que constan, a su vez, de
diferentes entidades de nivel más bajo denominados campos.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 122

Clave.
Campo especial de un registro que es capaz de identificarlo de forma unívoca. El valor de este
campo es único, es decir, no puede ser tomado por ningún otro campo clave de otro registro.

Registro físico.
Bloque o cantidad mínima de información que se transfiere en cada operación de
Entrada/Salida sobre un archivo. Su tamaño depende de las características físicas del ordenador
y del sistema operativo (buffers). Puede contener uno o más registros lógicos. Un registro
lógico puede ocupar menos de un registro físico, uno completo o más de uno.

Factor de bloqueo.
Número de registros lógicos que contiene cada registro físico. Se supone que el registro físico
tendrá un tamaño, en bytes, mayor o igual al del registro lógico.

ARCHIVO
Reg. 1 REGISTRO CAMPO

Reg. 2
Reg. 3 Nombre Profesión Dirección Teléfono Ciudad Comunidad

3.- Clasificación de los archivos.


La clasificación de los archivos puede realizarse desde distintos puntos de vista: del usuario, su
uso, el almacenamiento físico en el soporte y según su método de acceso.

3.1.- Clasificación desde el punto de vista del usuario.


· Ejecutables.
· Código.
· Datos.

3.2.- Clasificación según su uso.


· Permanentes. La información que almacenan no varía o varía poco.
· Constantes. Los datos que almacenan permanecen prácticamente constantes. Por ejemplo,
archivo de nombres de provincias.
· Maestros. Contienen la información actualizada en un determinado momento. Archivo de
artículos.
· Históricos. Contienen información ya actualizada y que se mantienen con fines de consulta o
estadísticos. Por ejemplo, archivo de transacciones de cuentas bancarias.

· Temporales.
· De movimiento. Contienen la información que se utilizará para actualizar los archivos
maestros; indican cuando realizar altas, bajas, etc. Suelen desaparecer una vez cumplido su
cometido.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 123

· De maniobra o trabajo. Son archivos auxiliares que se crean temporalmente y sólo existen en
tiempo de ejecución del programa. Por ejemplo, archivos intermedios de ordenación.

3.3.- Clasificación según el almacenamiento físico en el soporte.


· Organización contigua. Todos los sectores que forman un archivo están contiguos. Esta
organización tiene el problema de desaprovechar los huecos que se producen al borrar un registro.
La ventaja es que permite usar cualquier tipo de método de acceso u organización lógica del
mismo.
· Organización encadenada. El directorio tiene un puntero al primer sector y cada sector a su
siguiente. Soluciona el problema de los huecos, pero no permite saber la longitud del archivo.
· Organización indexada. Se guarda una lista de los sectores que forman el archivo y cuando hace
falta, se consulta.

3.4.- Clasificación según su método de acceso.


· Secuenciales.
· Directos.
· Secuenciales indexados.

4.- Operaciones sobre archivos.


Las operaciones que podemos realizar sobre los archivos son: creación, consulta, actualización,
clasificación, reorganización, borrado, fusión, partición.

· Creación. Para poder realizar cualquier operación sobre un fichero es necesario que exista
previamente, que haya sido creado grabando sobre el soporte externo la información requerida para
su posterior tratamiento.

· Apertura. Para poder acceder a los datos y así permitir realizar las operaciones necesarias de
lectura y escritura de los mismos sobre el fichero, debemos abrirlo. Un fichero no debe permanecer
abierto más tiempo del necesario para operar sobre él.

· Cierre. Una vez efectuadas las operaciones sobre el fichero, debe cerrarse para limitar el acceso a
los datos y evitar la corrupción de los mismos.

· Actualización. Permite modificar -actualizar- la información almacenada en el fichero, ya sea


introduciendo información nueva o bien cambiando o eliminando la ya existente.

· Ordenación o clasificación. Consiste en reagrupar los registros contenidos en el archivo en un


orden ascendente o descendente, dependiendo del criterio utilizado, atendiendo al valor de uno o
varios de los campos del registro.

· Copiado o duplicado. Crea un nuevo fichero con la misma estructura y contenido de un fichero
fuente.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 124

· Concatenación. Consiste en que dados dos ficheros de igual estructura, se obtiene un tercer
fichero que tiene la misma estructura y contenido la suma de los dos anteriores.

· Fusión o mezcla. Esta operación permite obtener de dos o más ficheros con la misma clasificación
y estructura interna de sus datos, un nuevo fichero que contenga los registros de todos los
anteriores sin alterar la ordenación que éstos tenían establecida.

· Partición. Permite obtener varios ficheros de uno inicial en función de alguna de las características
internas de sus campos.

· Compactación o empaquetamiento. Permite la reorganización de los registros de un fichero


eliminando los huecos libres intermedios existentes entre ellos, normalmente ocasionados por la
eliminación de registros.

· Consulta. A través de las consultas es posible acceder a los registros del fichero y conocer el
contenido de sus campos.

· Borrado. Es la operación inversa a la creación de un fichero y en consecuencia una vez efectuada


dicha operación, se pierde toda la información y la posibilidad de acceder a los datos previamente
almacenados.

5.- Operaciones sobre registros.


· Altas. Consiste en la adición o inserción de uno o varios registros en el fichero. Esta operación
sólo se permite para en archivos que existen previamente, que han sido creados.

· Bajas. Consiste en eliminar uno o varios registros del fichero. Esta operación requiere un proceso
previo de lectura o consulta para la localización del registro que se pretende eliminar. El borrado
de registros se puede realizar de dos formas diferentes:

· Marcando de alguna manera (flag o bandera) la posición ocupada por dicho registro y
permitiendo así su posterior recuperación en el caso de que haya habido algún error.
· Eliminando físicamente el registro del fichero haciendo una compactación del mismo o
desplazando todos los registros posteriores una posición y eliminando, por tanto, el hueco que
hubiera generado. Con esta forma de borrado de registros se pierde toda posibilidad de
recuperar la información.

· Modificaciones. Consiste en realizar un cambio total o parcial de uno o varios campos de los
registros de un fichero. Requiere, igual que la baja, un proceso previo de lectura para la
localización del registro que se pretende modificar y un segundo proceso de escritura para la
actualización del registro.

· Consultas. Esta operación permite acceder a uno o varios registros con la intención de visualizar
su contenido por pantalla, impresora u otro periférico.

6.- Organización de los archivos.


Al hablar de ficheros se hace necesario conocer dos conceptos fundamentales:

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 125

· Organización. Se define como la forma en la que los datos se estructuran y almacenan


internamente en el fichero y más concretamente sobre el soporte de almacenamiento. Esta
estructura depende mucho de las características del soporte utilizado. Básicamente existen tres
tipos de organización: secuencial, directa e indexada.

· Acceso. Es el procedimiento necesario que debemos seguir para situarnos sobre un registro
concreto con la intención de realizar una operación de lectura o escritura sobre el mismo. También
existe una estrecha dependencia entre el tipo de soporte utilizado y el tipo de acceso. Los tipos de
acceso son secuencial, directo y por índice.

6.1.- Organización secuencial.


En una organización secuencial los registros ocupan siempre posiciones consecutivas de
memoria en el soporte externo -en consecuencia no hay huecos entre los registros y, por tanto, no se
desaprovecha el espacio- y sólo se puede acceder a los registros de uno en uno a partir del primero; es
decir, para tratar el registro situado en la posición N del fichero, es necesario tratar los N - 1 registros
anteriores.

Archivo
Registro 1 Registro 2 ... Registro N-1 Registro N EOF

Los registros se graban consecutivamente cuando el archivo se crea y se debe acceder


consecutivamente cuando se leen dichos registros.

La organización secuencial tiene la particularidad de que no puede encontrarse nunca en el


estado de Lectura/Escritura, si no que tiene que estar en el estado de lectura o en el estado de escritura;
es decir, cuando leemos de una organización secuencial no podemos realizar escrituras y cuando
escribimos no podemos leer. Tampoco se pueden realizar actualizaciones directamente sobre el
fichero, debemos servirnos de un fichero secuencial auxiliar de movimientos.

Los ficheros organizados secuencialmente contienen un registro particular (el último) que
contiene la marca fin de archivo (EOF o FF).

Este tipo de organización se soporta sobre todos los tipos de dispositivo de memoria auxiliar
existentes.

Ventajas:
· Rapidez en el acceso a un bloque de registros.
· No deja espacios vacíos intermedios entre registro y registro.

Inconvenientes:
· Para consultar un registro concreto es necesario consultar todos anteriores.
· No permite la inserción de nuevos registros, sólo nos permite añadirlos al final del
fichero creando un nuevo fichero.
· No permite borrar registros, sólo marcarlos creando un fichero nuevo.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 126

6.2.- Organización relativa.


Son aquellos caracterizados por:

· Deben almacenarse en un soporte direccionable; cada posición se localiza por una dirección
absoluta (el programador sólo gestiona direcciones relativas).
· El almacenamiento físico de los registros sobre el soporte seleccionado se realiza a través de una
clave que indica la posición de almacenamiento del registro dentro del fichero.
· La dirección de almacenamiento del registro en el dispositivo se obtiene siempre de la clave del
propio registro:
· Si la clave no es numérica se aplican algoritmos o fórmulas de transformación para obtener
valores enteros positivos que facilitan su posterior manejo y tratamiento.
· Si la clave es numérica se aplica un algoritmo de transformación para obtener un rango de
valores comprendidos dentro del intervalo de valores de las direcciones de memoria
disponibles, estableciendo así una relación directa entre dirección lógica y dirección física.

El algoritmo de transformación de claves debe cumplir tres características principales:

· Aprovechar al máximo el espacio disponible en el soporte externo.


· Establecer una correspondencia directa entre dirección lógica (clave) y dirección física
(memoria).
· Producir el menor número de registros que con diferentes claves generan idénticas
direcciones de almacenamiento tras aplicar el algoritmo de transformación.

Los algoritmos de transformación de claves reciben el nombre de “Hashing” y hay gran


variedad de ellos.

Existen dos variantes de la organización relativa: directa e indirecta o aleatoria.

6.2.1.- Organización directa.

Esta organización se da en cuando la clave es numérica y además tiene un valor que está dentro
del rango de direcciones válidas, estableciéndose así una relación directa entre la clave y la dirección
que ocupa el registro dentro del fichero. El valor de la clave siempre está en relación con la
capacidad máxima del soporte físico, por lo que nunca podemos almacenar un registro cuya clave esté
por encima de los límites máximos del fichero.

Ventajas:
· Permite un acceso inmediato, directo, a los datos.
· Permite realizar operaciones de entrada y salida simultáneamente.
· Son muy rápidos en el tratamiento individual de registros.
· Permiten acceder a los datos tanto directamente (por su clave) como secuencialmente.

Inconvenientes:
· Al realizar una lectura secuencial del fichero podemos encontrarnos con huecos vacíos
de forma que hay que tratarlos convenientemente y como consecuencia hay una perdida
de tiempo en el procesamiento de la información.
· Desaprovechamiento del soporte debido a la gran cantidad de huecos que pueden
existir.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 127

6.2.2.- Organización relativa o indirecta.

Esta organización se da en aquellos casos en que la clave –ya sea de tipo numérico entero o de
tipo cadena- debe sufrir una transformación de la clave para obtener un valor numérico entero que esté
comprendido dentro del rango de direcciones válidas y así establecer la correspondencia directa entre
la clave y la dirección de memoria que ocupa el registro en el soporte externo en el que se almacena el
fichero.

En este tipo de organización, cada dirección puede ser ocupada por más de un registro debido a
que el algoritmo de transformación aplicado a claves con valores diferentes ha generado la misma
posición de almacenamiento en memoria. Este hecho se conoce con el nombre de “sinónimo” o
“colisión”.

Ventajas:
· Permite un acceso inmediato a los registros haciendo sólo referencia a su clave.
· No requiere procesos de ordenación.
· Permite realizar operaciones de entrada y salida a la vez.
· Son muy rápidos en el tratamiento individual de registros.
· Permiten acceder a los datos secuencialmente.

Inconvenientes.
· Los mismo que la organización directa.
· Se necesita programar la relación que existe entre la clave y la posición de memoria que
ocupa el registro y también hay que programar el tratamiento de sinónimos. Se debe
procurar que estos algoritmos generen el menor número posible de sinónimos y de
huecos vacíos.

6.3.- Organización secuencial indexada.


Un archivo secuencial indexado es una mezcla de las dos anteriores. Los elementos se
encuentran almacenados de forma secuencial pero existe una forma de conocer la posición de un
determinado elemento. Consta de un archivo de índice y un archivo de datos.

Un archivo está organizado de forma secuencial indexada si:

· El tipo de sus registros contiene un campo clave.


· Los registros están situados en un soporte direccionable por el orden de los valores
indicados por la clave.
· Un índice para cada posición direccionable, la dirección de la posición y el valor de la
clave.

Un archivo con organización secuencial indexada consta de las siguientes parte:

· Área de datos o primaria: contiene los registros en forma secuencial y está organizada en
secuencia de claves sin dejar huecos intercalados.
· Área de índices: es una tabla que contiene los niveles de índice, la existencia de varios
índices enlazados se denomina nivel de indexación.
· Área de desbordamiento: utilizada, si fuese necesario, para las actualizaciones.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 128

Ventajas.
· Acceso rápido.
· El sistema de gestión de archivos se encarga de relacionar la posición de cada registro
con su contenido mediante la tabla de índices.

Inconvenientes.
· Desaprovechamiento del espacio por quedar huecos intermedios cada vez que se
actualiza el archivo.
· Se necesita espacio adicional para el área de índices.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 129

7.- Archivos en lenguaje C.


Un archivo en lenguaje C es una estructura de datos que se declara con la palabra reservada
FILE. Se accede a ella mediante un puntero y las funciones almacenadas en la librería stdio.h. La
estructura FILE contiene la información relativa al archivo, como la localización, posición a la que se
está accediendo en ese momento, tipo de operación que se está realizando sobre el archivo, etc. Esta
información no es necesario conocerla porque las funciones de stdio.h se encargan de gestionarla.

La composición de la estructura y la declaración de un archivo son las siguientes:

typedef struct
{
short level;
unsigned flags;
char fd;
unsigned char hold;
short bsize;
unsigned char *buffer, *curp;
unsigned istemp;
short token;
} FILE;

FILE <*Puntero_archivo>;

FILE *alumnos;

La sentencia anterior define un flujo (stream) que posteriormente se asociará a un fichero


externo. Además, define el área de buffer para la descripción del fichero y para las transferencias de
datos. En lenguaje C no existe la noción de registro; un fichero no es más que una serie ordenada de
bytes, sin ninguna estructuración más.

7.1.- Creación y apertura de un archivo.


La función usada para la creación y/o apertura de un archivo, independientemente de su
organización y modo de acceso, es fopen() y cuya sintaxis es la siguiente:

FILE *fopen(const char *Nombre_Fichero, const char *Modo_Apertura);

Nombre_Fichero:
Cadena de caracteres que especifica el nombre físico –externo- del archivo de
datos. Opcionalmente puede contener el path con su ruta de acceso; en este caso, hay
que recordar que la barra invertida de path (\) debe expresarse como dos caracteres (\\)
para que no indique sólo el carácter de escape.

Modo_Apertura:
Es una cadena que especifica el modo en que se abrirá el archivo; es decir, si se
van a realizar sobre el fichero operaciones de entrada, de salida o ambas
simultáneamente. Los caracteres que indican dicho modo son los siguientes:

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 130

-r Abre el archivo para lectura. El archivo debe existir previamente, si no se produce un error.

-w Abre un archivo para escritura. Si no existe se crea, y si existe, se destruye el contenido del
anterior.

-a Abre el archivo para escritura desde el final del mismo, para añadir. Si el archivo no existía,
se crea uno nuevo.

- r+ Abre el archivo para Lectura/Escritura por el comienzo. El archivo debe existir previamente
o se produce un error.

- w+ Abre el archivo para Lectura/Escritura por el comienzo. Si no existía se crea uno nuevo, y si
existe, se destruye el contenido del anterior.

- a+ Abre el archivo para Lectura/Escritura por el final. Si no existía se crea uno nuevo.

-t Son dos modificadores que pueden acompañar como sufijo a las seis modalidads anteriores
b con el fin de especificar si se trabaja en modo texto –dado por omisión- (t) o en modo
binario (b).
Ejemplos:

FILE *datos;
char *archivo = "c:\\trabajo\\lc\infor.dat";
datos = fopen("c:\\trabajo\\lc\infor.dat", "w");
datos = fopen(archivo, "r");
datos = fopen(archivo, "wb");

La función fopen() asigna un stream (canal por el que leer y escribir) y un buffer (memoria
intermedia temporal) a un archivo, devolviendo un puntero a dicho canal. Existen varios stream
predefinidos en C que se abren automáticamente al iniciar el programa; estos son:

stdin Entrada estandar. Teclado.


stdout Salida estandar. Pantalla.
stderr Salida estandar de errores. Pantalla.
stdaux Periférico auxiliar estandar. COM2.
stdprn Impresora estandar. PRN.

En el caso de producirse un error, la función fopen() devuelve un puntero a NULL. Si se


producen errores en el instante de abrir un archivo puede ser por alguna de estas situaciones:

· Si se supera el número máximo de archivos abiertos a la vez permitido por el sistema.


· No tener permiso para abrir el archivo.
· Intentar abrir un archivo que no existe usando los modos r o r+.
· El nombre externo del archivo de datos es incorrecto.
#include <stdio.h>
main()
{ FILE *archivo;
archivo = fopen("datos.dat", "r");
if (archivo == NULL)
{ puts("Error al abrir el archivo");
exit(-1);
}//Tratamiento del archivo.
fclose(archivo);
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 131

7.2.- Cerrar archivo.


Cuando finaliza el tratamiento del archivo es preciso cerrarlo, entre otras razones para
actualizar su contenido y la tabla de asignación de archivos. La función que realiza esta operación es la
siguiente:

int fclose(FILE *Puntero_Archivo);

Retorna un 0 si el archivo se cierra satisfactoriamente y EOF si se produce algún error.

7.3.- Lectura y escritura de caracteres.


El lenguaje C dispone de la función fputc() para escribir caracteres en un archivo y de la
función fgetc() para leerlos del mismo. El formato general de estas dos funciones es el siguiente:

Escritura de caracteres:
int fputc(int c, FILE *fichero); Función
int putc(int c, FILE *fichero); Macro

Lectura de caracteres:
int fgetc(FILE *fichero); Función.
int getc(FILE *fichero); Macro.

La función fputc() escribe un carácter en la posición actual de la cabeza de lectura/escritura


avanzándola a la siguiente posición (la cabeza de E/S puede considerarse como el puntero). La función
retorna el carácter escrito o EOF si se ha producido algún error.

La función fgetc() realiza la operación inversa a fputc(); lee un carácter del archivo, el situado
en la posición actual de la cabeza de E/S, y avanza al siguiente carácter. Si se produce algún error
retorna EOF.

Ejemplo: programa que visualiza un archivo de texto.


#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
main()
{ FILE *archivo;
char nombre[30];
int caracter;
puts("Nombre del archivo a visualizar");
gets(nombre);
archivo = fopen(nombre, "rt");
if (!archivo)
{ puts("Error de apertura");
exit(1);
}
//carácter = (int)fgetc(archivo);
// while (caracter !=EOF)
// { printf("%c", caracter);
// carácter = (int)fgetc(archivo);
// }
while ((caracter = fgetc(archivo))!=EOF)
printf("%c", caracter);
fclose(archivo);
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 132

Ejemplo: copiar un archivo de texto en otro carácter a carácter.


#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
FILE *abrir_archivo(char *nombre, char *modo);
main()
{ FILE *entrada, *salida;
char origen[30], destino[30];
int caracter;
puts("Nombre del archivo origen"); gets(origen);
puts("Nombre del archivo destino"); gets(destino);
entrada = abrir_archivo(origen, "rt");
salida = abrir_archivo(destino, "wt");

while ((caracter = fgetc(entrada)) !=EOF)


fputc(caracter, salida);
fclose(entrada);
fclose(salida);
}
FILE *abrir_archivo(char *nombre, char *modo)
{ FILE *archivo;
archivo = fopen(nombre, modo);
if (!archivo)
{ printf("Error de apertura del archivo %s", nombre);
exit(1);
}
return archivo;
}

Ejemplo: igual que el anterior pero pasando los nombres de los ficheros como
argumentos desde el S.O.
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
FILE *abrir_archivo(char *nombre, char *modo);
main(int argc, char *argv[])
{ FILE *entrada, *salida;
int caracter;

entrada = abrir_archivo(argv[1], "rt");


salida = abrir_archivo(argv[2], "wt");
while ((caracter = fgetc(entrada)) !=EOF)
fputc(caracter, salida);
fclose(entrada);
fclose(salida);
}

7.4.- Lectura y escritura de cadenas.


Disponemos de dos funciones para realizar las operaciones de entrada/salida de cadenas. La
escritura de una cadena en un archivo se realiza con la función

int fputs(const char *cadena, FILE *p_archivo);

que retorna el último carácter escrito en el archivo si no se ha producido ningún error (un número no
negativo) o EOF si se ha producido. La copia de la cadena se realiza desde el principio de la misma
hasta el carácter nulo, sin escribir este en el archivo; el carácter de fin de línea tampoco se escribe en el
archivo ; si se necesita debemos especificarlo.

La lectura de una cadena de un archivo se realiza con la función

char *fgets(char *cadena, int longitud, FILE *p_archivo);

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 133

que retorna una cadena de caracteres si la lectura ha sido correcta o NULL si se ha producido algún
error. Lee LONGITUD - 1 caracteres o hasta que encuentra carácter de fin de línea (leyéndolo y
añadiéndolo) y posteriormente añade el carácter nulo a la cadena.

Estas funciones son equivalentes a gets() y puts(), pero estas realizan la entrada/salida por stdin
y stdout.

Ejemplo : Copiar un fichero de texto.


#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
FILE *abrir_archivo(char *nombre, char *modo);
main(int argc, char *argv[])
{ FILE *entrada, *salida;
char cadena[256];
if (argc != 3)
{ puts("Faltan argumentos");
exit(1);
}
entrada = abrir_archivo(argv[1], "r");
salida = abrir_archivo(argv[2], "w");

while ((fgets(cadena, 256, entrada)) !=NULL)


fputs(cadena, salida);

fclose(entrada);
fclose(salida);
}

Ejercicios:
1. Contar el número de veces que aparecen en un archivo de texto cada uno de los caracteres
ASCII.
2. Leer un texto por teclado y escribirlo en un fichero de texto.

7.5.- Entrada y salida con formato.


Existen dos funciones semejantes a scanf() y printf() para realizar una entrada/salida en
archivos de texto. Esta operación se realizará con el formato especificado en los argumentos de las
funciones.

La sintaxis de estas funciones es la siguiente:

int fprintf(FILE *p_archivo, const char *formato[, argumentos, ...]);


int fscanf(FILE *p_archivo, const char *formato[, direcciones, ...]);

fprintf() escribe la lista de elementos especificada en el formato indicado; fscanf() lee del
fichero las variables de la lista con el formato especificado.

fscanf() retorna el número de argumentos que se han leído o 0 si no se ha leído ninguno; si se


ha intentado leer más allá del fin de fichero retorna EOF. Por su parte, fprinf() retorna el número de
bytes escritos o EOF.

Ejemplo: contar las palabras almacenadas en un archivo de texto.


while ((fscanf(entrada, "%*s")) != EOF)
contador++;
printf("Numero de palabras: %d", contador);

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 134

Ejemplo: leer el nombre y tres notas del alumnos y escribirlos en un archivo.


#define NALUM 10
#define NOTAS 3
typedef struct
{ char nombre[30];
int notas[NOTAS];
} ALUMNO;
FILE *abrir_archivo(char *, char *);
void leer_alumno(ALUMNO *);
void escribe_alumno(FILE *, ALUMNO);
main()
{ FILE *salida;
ALUMNO alumno;
int i;
salida = abrir_archivo("c:\\trabajo\\alumno.txt", "w");
for (i=0; i<NALUM; i++)
{ leer_alumno(&alumno);
escribe_alumno(salida, alumno);
}
fclose(salida);
}
void leer_alumno(ALUMNO *a)
{ int j;
printf("Nombre: ");
fflush(stdin); gets(a->nombre);
for (j=0; j<NOTAS;j++)
{ printf("Nota %d: ", j);
fflush(stdin);
scanf("%d", &a->notas[j]);
}
}
void escribe_alumno(FILE *salida, ALUMNO a)
{ int j;
fprintf(salida, "%-30s", a.nombre);
for (j=0; j<NOTAS;j++)
fprintf(salida, "%d\t", a.notas[j]);
fprintf(salida, "\n");
}

Ejemplo: Leerlo y visualizarlo.


FILE *abrir_archivo(char *, char *);
void leer_alumno(FILE *, ALUMNO *);
void escribe_alumno(ALUMNO);

main()
{ FILE *entrada;
ALUMNO alumno;
int i;
entrada = abrir_archivo("c:\\trabajo\\alumno.txt", "r");
for (i=0; i<NALUM; i++)
{ leer_alumno(entrada, &alumno);
escribe_alumno(alumno);
}
fclose(entrada);
}
void leer_alumno(FILE *f, ALUMNO *a)
{ int j;
fscanf(f, "%30s", a->nombre); // fgets(a->Nombre, 30, f);
for (j=0; j<NOTAS;j++)
fscanf(f, "%d", &a->notas[j]);
}
void escribe_alumno(ALUMNO a)
{ int j;
printf("%-30s", a.nombre);
for (j=0; j<NOTAS;j++)
printf("%d\t", a.notas[j]);
printf("\n");
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 135

7.6.- Lectura y escritura de bloques de información.


Hay dos funciones que tratan bloques de bytes independientemente de la información que
contengan, de los dato almacenados.

El formato de la función de lectura es el siguiente:

size_t fread(void *p_bloque, size_t tam_b, size_t num_b, FILE *p_arch);

La función fread() lee un número “num_b” de bloques de tamaño “tam_b” bytes del archivo
'p_arch' y lo almacena en la posición indicada por el puntero 'p_bloque'. La memoria donde se
almacenarán los datos debe estar reservada. Retorna el número de bloques leídos del fichero; así, este
valor nos permite conocer si se ha producido un error en la lectura.

El formato de la función de escritura es el siguiente:

size_t fwrite(const void *p_bl, size_t ta_b, size_t nu_b, FILE *p_arch);

La función fwrite realiza la acción contraria a fread(); escribe un numero “num_b” de bloques
de tamaño “tam_b” bytes tomados de la posición indicada por el puntero 'p_bloque' en el archivo
'p_arch'. Retorna el número de bloques escritos en el archivo. También pueden detectarse posible
errores cometidos en la escritura.

Tanto en la lectura como en la escritura, el número de bytes transferidos viene dado por
“num_b * tam_b” (número de bloques a transferir multiplicado por el tamaño del bloque). El tamaño
del bloque se calcula, como siempre, con la función sizeof().

*p_bloque: puntero a la zona de memoria donde se realizará la transferencia de E/S.


tam_b: tamaño de cada bloque.
num_b: número de bloques que se tranferirán del/al archivo.
*p_arch: puntero a archivo FILE.
t_size: tipo de datos definido en stdio.h (typedef unsigned size_t).

Ejemplo: Crear y listar un archivo con el nombre y las notas de los alumnos. Usar las
funciones del ejercicio anterior.
void escribe_alumno(FILE *salida, ALUMNO a)
{ fwrite(&a, sizeof(a), 1, salida);
}
void leer_alumno(FILE *f, ALUMNO *a)
{ fread(a, sizeof(*a), 1, f);
}

7.7.- Detectar fin de fichero.


El final de un archivo se detecta con la función feof() y cuyo formato es el siguiente:

int feof(FILE *p_archivo);

Esta función detecta el final del archivo cuando se realiza una lectura después del último
registro o byte; es decir, para detectar el final del fichero es necesario realizar una lectura del fichero,

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 136

no basta con abrirlo. Retorna 0 si no es el final del fichero o un valor distindo si estamos en el final del
mismo. La función (MACRO) puede usarse con archivos binarios y archivos de texto.

Ejemplo: copiar archivo:


caracter = getc(entrada);
while (!feof(entrada))
{ putc(caracter, salida);
caracter = getc(entrada);
}

Ejemplo: recorrido secuencial del archivo:


leer_alumno(entrada, &alumno);
while (!feof(entrada))
{ escribe_alumno(alumno);
leer_alumno(entrada, &alumno);
}

7.8.- Posicionamiento en el fichero: e/s de acceso directo.


Básicamente se pueden realizar tres tipos de desplazamiento dentro del fichero: desplazamiento
al principio del fichero, al final del fichero y a una posición concreta dentro del fichero. Para realizar
estas tres acciones se dispone de la función fseek().

int fseek(FILE *p_archivo, long desplazamiento, int origen);

origen: Posición relativa desde donde se desea comenzar el desplazamiento. Es una


constante simbólica definida stdio.h y que tiene tres valores posibles:
SEEK_SET 0 : Desplazamiento desde principio del fichero.
SEEK_CURT 1 : Desplazamiento desde la posición actual.
SEEK_END 2 : Desplazamiento desde el final del fichero.

desplaza: Número de bytes a partir de la posición seleccionada (origen) que queremos que
se desplace la cabeza de E/S. Puede ser tanto positivo como negativo.

Retorna un 0 si el puntero se ha posicionado correctamente en el fichero y otro valor si se ha


producido algún error. fseek() puede usarse en múltiplos del tamaño de cualquier dato para recorrer el
archivo o acceder a un registro concreto.

sizeof(tipo_dato) * posicion_relativa_registro
do
{ scanf("%ld", &p);
if (p>0)
{ if(leer_alumno(entrada, &alumno, p))
escribe_alumno(alumno);
else
puts("No está");
}
}while (p>0);
int leer_alumno(FILE *f, ALUMNO *a, long posicion)
{ fseek(f, sizeof(ALUMNO)*(posicion-1), SEEK_SET);
fread(a, sizeof(*a), 1, f);
return !feof(f);
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 137

7.9.- Desplazamiento al comienzo del archivo.


La función rewind() rebobina la cabeza de E/S al comienzo del archivo. Es equivalente a
fseek(f, 0, SEEK_SET).

void rewind(FILE *p_archivo);

7.10.- Posición actual de la cabeza de E/S.


La función ftell() indica la posición actual de la cabeza de E/S en el archivo. Retorna el número
de bytes desde el comienzo del archivo; este valor puede ser usado posteriormente por la función
fseek(). (-1 error)

long int ftell(FILE *p_archivo);

Puede ser usado para prevenir el intento de posicionamiento más allá del final del fichero.

Ejemplo: numero de registros de un fichero.


long numero_registros(FILE *f, ALUMNO a)
{ fseek(f, 0, SEEK_END);
return ftell(f) / sizeof(a);
}

7.11.- Detección de errores.


Para detectar si se ha producido algún error en el archivo durante una lectura o escritura, se
dispone de la función (MACRO) ferror().

int ferror(FILE *p_archivo); Retorna != 0 si hay error.

void clearerr(FILE *p_archivo);

El EOF (error) persite hasta que se ejecuta una de estas funciones: clearerr(), rewind() o
fclose().

7.12.- Vaciar el buffer.


Vacía el buffer del archivo correspondiente. Retorna 0 o EOF si se detecta algún error.

int fflush(FILE *p_archivo);

7.13.- Reasignación de archivo. reapertura.


La función freopen() tiene tres formas de uso:
· Abrir un archivo en un modo diferente del que se abrió inicialmente.
· Cambiar el archivo de nombre externo.
· Redireccionar un fichero estandar a otro externo.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 138

FILE *freopen(const char *f_externo, const char *modo, FILE *p_archivo);

Retorna un NULL si se ha producido algún error.

7.14.- Cambiar el nombre de un archivo.


Cambia el nombre de un archivo o directorio.

int rename(const char *actual, const char *nuevo);

Retorno:
0 Correcto.
EACCES Permiso denegado. Ya existe o path inválido.
ENOENT No existe el fichero o directorio.
ENOTSAM No es el mismo periférico.
if (rename(viejo, nuevo) == 0)
printf("Renombrado.\n");
else
perror("Rename"); //Rename: descripción del error

7.15.- Borrar un archivo.


Borra un archivo cerrado.

int remove(const char *f_externo);

Retorna:
0 Correcto
EACCES Permission denied
ENOENT No such file or directory

Cuando las funciones rename() o remove() retornan un error, puede invocarse a la función
perror() para que visualice cual es el error cometido.

void perror(const char *cadena);

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 139

8.- Búsqueda en archivos.


En archivos secuenciales, la lectura de un registro para consulta, modificación o supresión va
precedida necesariamente de la búsqueda del mismo. La situación es análoga a la anterior, para los
archivo directos y los secuenciales indexados, cuando se desconoce la posición del registro dentro del
archivo o cuando se desea acceder a un registro por un campo que no es clave.

Se usa un algoritmo de búsqueda cuando se desea obtener el contenido de un registro de un


archivo, a partir de uno de sus campos o subcampos, que denominaremos clave de búsqueda. Los
algoritmos consisten en un recorrido lineal del archivo, variando la condición de finalización según si
el archivo está ordenado o no por la clave de búsqueda.

La lógica de búsqueda de los algoritmos es válida para cualquier lenguaje de programación,


aunque, según en que instante se detecte el final del archivo, tendremos que variar la condición de
terminación del bucle y el lugar en que se realiza la lectura del archivo.

8.1.- Búsqueda en archivos desordenados.


Se recorre el archivo desde el primer registro hasta encontrar aquel cuyo valor de su campo
clave coincide con el buscado o se acabe el archivo. Si existen varios registros con el mismo valor
clave, se obtendrá el primero de ellos.

Sea un archivo F, cuyos registros R contienen un campo C, que es la clave de búsqueda, y un


valor X a buscar en C.
void Buscar(FILE *F, CLAVE X)
{ REGISTRO R;
int Hallado = 0;
fseek(F,0, SEEK_SET); // o bien abrir el archivo, rewind(F)
fread(&R, sizeof(REGISTRO), 1, F);
while (!Hallado && !feof(F))
{ if (X == R.C)
Hallado = 1;
else
fread(&R, sizeof(REGISTRO), 1, F);
}
if (hallado) Tratar(R);
else Indicar que no se encuentra en el archivo;
}

Si se desea realizar el mismo tratamiento para todos los registros que tienen el mismo valor de
campo clave sólo hay que suprimir el interruptor 'Hallado' de la condición de terminación del bucle y
realizarlo dentro del cuerpo del bucle.

8.2.- Búsqueda en archivos ordenados.


El algoritmo anterior es válido para archivos ordenados; no obstante conviene usar la
ordenación para optimizar el tiempo de ejecución, ampliando la condición de terminación de búsqueda
al caso de sobrepasar el valor de la clave buscada.

Algoritmo que busca el primer registro que cumple la condición. Este algoritmo podría dar
algún problema; conviene usar el interruptor.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 140

void Buscar(FILE *F, CLAVE X)


{ REGISTRO R;
fseek(F,0, SEEK_SET); // o bien abrir el archivo, rewind(F)
fread(&R, sizeof(REGISTRO), 1, F);
while (!feof(F) && X > R.C)
fread(&R, sizeof(REGISTRO), 1, F);
if (X == R.C) // Si ff y no encontrado. ¿Valor de R.C.?
Tratar(R);
else
Indicar que no se encuentra en el archivo;
}

Algoritmo que busca el todos los registros que cumplen la condición.


void Buscar(FILE *F, CLAVE X)
{ REGISTRO R;
int Hallado = 0;
fseek(F,0, SEEK_SET); // o bien abrir el archivo, rewind(F)
fread(&R, sizeof(REGISTRO), 1, F);
while (!feof(F) && X >= R.C)
{ if (X == R.C)
{ Hallado = 1;
Tratar(R);
}
fread(&R, sizeof(REGISTRO), 1, F);
}
if (!Hallado)
Indicar que no se encuentra en el archivo;
}

9.- Partición de archivos.


La partición de un archivo consiste en repartir los registros del mismo en dos o más,
dependiendo de una determinada condición dada por uno o más campos del registro.

9.1.- Partición por contenido.


Sea el archivo F, que se desea dividir en dos, F1 y F2, copiando en el primero los registros de F
que contienen en el campo C el valor X, y en el segundo los demás.
FICHERO F;
FICHERO F1;
FICHERO F2;
REGISTRO R;
CLAVE X;
F = fopen(F_EXTERNO, "rb");
F1 = fopen(F_EXTERNO1, "wb");
F2 = fopen(F_EXTERNO2, "wb");
fread(&R, sizeof(REGISTRO), 1, F);
while (!feof(F))
{ if (X == R.C)
fwrite(&R, sizeof(REGISTRO), 1, F1);
else
fwrite(&R, sizeof(REGISTRO), 1, F2);
fread(&R, sizeof(REGISTRO), 1, F);
}
fclose(F);
fclose(F1);
fclose(F2);

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 141

9.2.- Partición en secuencias.


Los registros se distribuyen en secuencias alternativas, de igual o diferente longitud según los
casos.

Sea el archivo F, que se desea dividir en dos, F1 y F2, copiando en el primero los registros de F
que ocupan posiciones impares, y en el segundo los que ocupan posiciones pares (longitud de
secuencia 1).
int SW = -1
...
fread(&R, sizeof(REGISTRO), 1, F);
while (!feof(F))
{ SW = -SW;
if (SW==1) fwrite(&R, sizeof(REGISTRO), 1, F1);
else fwrite(&R, sizeof(REGISTRO), 1, F2);
fread(&R, sizeof(REGISTRO), 1, F);
}

Sea el archivo F, que se desea dividir en dos, F1 y F2, copiando alternativamente en uno y otro
secuencias de registros de longitud N.
int SW = 1;
int C = 0;
...
fread(&R, sizeof(REGISTRO), 1, F);
while (!feof(F))
{ if (SW)
fwrite(&R, sizeof(REGISTRO), 1, F1);
else
fwrite(&R, sizeof(REGISTRO), 1, F2);
fread(&R, sizeof(REGISTRO), 1, F);
if(N == ++C)
{ SW = -SW;
C = 0;
}
}

Función para copiar una secuencia de N registros.


void copiar_secuencia(FILE *temporal, FILE *fichero, int longitud)
{ REGISTRO R;
fread(&R, sizeof(REGISTRO), 1, temporal);
while (longitud > 0 && !feof(temporal))
{ fwrite(&R, sizeof(REGISTRO), 1, fichero);
if(--longitud)
fread(&R, sizeof(REGISTRO), 1, temporal);
}
}
main()
{ ... // Declaraciones
int N = 4;
... // Aperturas

while (!feof(F))
{ copiar_secuencia(F, F1, N);
if (!feof(F))
copiar_secuencia(F, F2, N);
}
}

Ejercicio: Una compañía organiza una encuesta para determinar el éxito de sus productos
musicales. La población encuestada se va a dividir en cuatro categorías según sexo y edad (por
ejemplo menores o iguales a veinte años y mayores de veinte años). A cada persona se le pide que dé
el nombre de cinco éxitos según su orden de preferencia. Los éxitos se identifican por los números de

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 142

1 a N. Los resultados se almacenan en un fichero, donde cada elemento del fichero representa a un
encuestado que incluye los datos de su nombre y apellidos, además de los mencionados anteriormente.
· Crear el archivo con los datos de las encuestas.
· Listar los éxitos en orden de popularidad. Para cada uno se lista el número del éxito y el
número de veces que ha sido mencionado.
· Obtener cuatro archivos de texto con las listas de los nombres y apellidos de todos los
encuestados, según su categoría, que habían mencionado en primer lugar uno de los tres éxitos
más populares.

10.- Mezcla de archivos.


Es la operación contraria a la partición. La mezcla de archivos consiste en reunir en un único
archivo los registros de dos o más archivos, manteniendo el posible orden que hubiese establecido. Los
algoritmos de mezcla sobre archivos ordenados ascendentemente, se realizan por el valor de un campo
clave.

10.1.- Mezcla de archivos desordenados.


Una mezcla de archivos desordenados consiste en intercalar secuencias de registros de una
determinada longitud alternativamente en el archivo destino. Los algoritmos para hacerlo son los
inversos a los de la partición.
void copiar_secuencia(FILE *temporal, FILE *fichero, int longitud)
{ REGISTRO R;
fread(&R, sizeof(REGISTRO), 1, temporal);
while (longitud > 0 && !feof(temporal))
{ fwrite(&R, sizeof(REGISTRO), 1, fichero);
if(--longitud)
fread(&R, sizeof(REGISTRO), 1, temporal);
}
}
main()
{ ... // Declaraciones
int N = 4;
F = fopen(F_EXTERNO, "w");
F1 = fopen(F_EXTERNO1, "r");
F2 = fopen(F_EXTERNO2, "r");
while (!feof(F1) || !feof(F2))
{ if(!feof(F1)) copiar_secuencia(F1, F, N);
if(!feof(F2)) copiar_secuencia(F2, F, N);
}
fclose(F);
fclose(F1);
fclose(F2);
}

10.2.- Mezcla de archivos ordenados.


Sean dos archivos F1 y F2, con registros de igual estructura y ordenados ascendentemente por
el campo clave C. Se desea obtener el archivo F ordenado por el mismo campo que contenga los
registros de ambos. El algoritmo consiste en el recorrido simultáneo de F1 y F2, copiando el registro
menor de ellos en F y avanzando en el archivo correspondiente. Se controla el final de los archivos
comprobando el final del fichero. A la salida del bucle de fusión, uno de los dos archivos no habrá sido
copiado en su totalidad, por lo que es necesario copiarlo en F.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 143

#include <stdio.h>
#include <stdlib.h>
#define NO "c:\\temporal\\numeros.txt"
#define NO1 "c:\\temporal\\numeros1.txt"
#define NO2 "c:\\temporal\\numeros2.txt"

typedef struct
{ int C;
} NUME;
void copiar_resto(FILE *desde, FILE *hasta, NUME R);

main()
{ FILE *F;
FILE *F1;
FILE *F2;
NUME R1;
NUME R2;
F = fopen(NO, "w");
F1 = fopen(NO1, "r");
F2 = fopen(NO2, "r");
fread(&R1, sizeof(NUME), 1, F1);
fread(&R2, sizeof(NUME), 1, F2);
while (!feof(F1) && !feof(F2))
if(R1.C < R2.C)
{ fwrite(&R1, sizeof(NUME), 1, F);
fread(&R1, sizeof(NUME), 1, F1);
}
else
{ fwrite(&R2, sizeof(NUME), 1, F);
fread(&R2, sizeof(NUME), 1, F2);
}
copiar_resto(F1, F, R1);
copiar_resto(F2, F, R2);
fclose(F);
fclose(F1);
fclose(F2);
}
void copiar_resto(FILE *temporal, FILE *fichero, NUME R)
{ while (!feof(temporal))
{ fwrite(&R, sizeof(R), 1, fichero);
fread(&R, sizeof(R), 1, temporal);
}
}

Ejercicio: Se dispone de dos archivos secuenciales, ambos con los campos


CLAVE alfanumérico
RESTO DE CAMPOS
Los archivos están ordenados ascendentemente por el campo CLAVE, que es único en cada
archivo, existiendo registros comunes a uno y otro. Programa que obtenga el archivo F, intersección de
los dos anteriores, conteniendo los registros comunes una sola vez.

11.- Clasificación de archivos.


11.1.- Clasificación por mezcla directa.
Se trata de la realización sucesiva de una partición y una mezcla que produce secuencias
ordenadas de longitud cada vez mayor. La primera partición se hace en secuencias de longitud 1, y la
fusión correspondiente produce sobre el archivo inicial secuencias ordenadas de longitud 2. A cada
nueva partición y fusión, se duplica la longitud de las secuencias ordenadas. El proceso finaliza cuando
la longitud de la secuencia ordenada excede la longitud del archivo a ordenar.

F: 15, 18, 7, 75, 14, 13, 43, 40, 51, 93, 75, 26, 64, 27, 13
F1: 15 7 14 43 51 75 64 13
F2: 18 75 13 40 93 26 27

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 144

F: 15 18, 7 75, 13 14, 40 43, 51 93, 26 75, 27 64, 13


F1: 15 18 13 14 51 93 27 64
F2: 7 75 40 43 26 75 13

F: 7 15 18 75, 13 14 40 43, 26 51 75 93, 13 27 64


F1: ...
F2: ...
#include <stdio.h>
#include <stdlib.h>
#define NO "c:\\temporal\\numeros.txt"
#define NO1 "c:\\temporal\\numeros1.txt"
#define NO2 "c:\\temporal\\numeros2.txt"
typedef struct
{ int C;
} REGISTRO;
void copiar_secuencia(FILE *desde, FILE *hasta, int);
void partir(FILE *F, FILE *F1, FILE *F2, int S);
void mezclar(FILE *F, FILE *F1, FILE *F2,int S);

main()
{ int S = 1, lon;;
FILE* F, F1, F2;
F = fopen(NO, "r");
fseek(F, 0, SEEK_END);
lon = ftell(F) / sizeof(REGISTRO);
fclose(F);
while(S < lon)
{ F = fopen(NO, "r");
F1 = fopen(NO1, "w");
F2 = fopen(NO2, "w");
partir(F, F1, F2, S);
F = freopen(NO, "w", F);
F1 = freopen(NO1, "r", F1);
F2 = freopen(NO2, "r", F2);
mezclar(F, F1, F2, S);
S *=2;
fclose(F);
fclose(F1);
fclose(F2);
}
}
void partir(FILE *F, FILE *F1, FILE *F2,int S)
{ while (!feof(F))
{ copiar_secuencia(F, F1, S);
copiar_secuencia(F, F2, S);
}
}
void mezclar(FILE *F, FILE *F1, FILE *F2,int S)
{ REGISTRO R1, R2;
int N1, N2;
fread(&R1, sizeof(REGISTRO), 1, F1);
fread(&R2, sizeof(REGISTRO), 1, F2);
while(!feof(F1))
{ N1 = N2 = 0;
// Copia una secuencia. Finaliza una de las dos.
while ((!feof(F1) && N1 < S) && (!feof(F2) && N2 < S))
if(R1.C < R2.C)
{ fwrite(&R1, sizeof(REGISTRO), 1, F);
fread(&R1, sizeof(REGISTRO), 1, F1);
N1++;
}
else
{ fwrite(&R2, sizeof(REGISTRO), 1, F);
fread(&R2, sizeof(REGISTRO), 1, F2);
N2++;
}
// Copia el resto de una secuencia.
while(!feof(F1) && N1++ < S)
{ fwrite(&R1, sizeof(REGISTRO), 1, F);
fread(&R1, sizeof(REGISTRO), 1, F1);
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 145

// Copia el resto de otra secuencia.


while(!feof(F2) && N2++ < S)
{ fwrite(&R2, sizeof(REGISTRO), 1, F);
fread(&R2, sizeof(REGISTRO), 1, F2);
}
}
}

11.2.- Clasificación por mezcla equilibrada.


Es una optimización del anterior. Consiste en realizar la partición tomando las secuencias
ordenadas de máxima longitud posible y realizando la fusión de secuencias ordenadas alternativamente
sobre dos archivos, lo que hace que la siguiente partición quede realizada.

Utiliza tres archivos auxiliares, junto con el original, para la realización simultánea de fusión y
partición. Durante el proceso de fusión-partición, dos o más secuencias ascendentes, que estén
consecutivas, pueden constituir una única secuencia para el paso siguiente. El proceso termina cuando,
en la realización de la fusión-partición, el segundo archivo queda vacío. El archivo totalmente
ordenado estará en el primero.

F: 15, 18, 7, 75, 14, 13, 43, 40, 51, 93, 75, 26, 64, 27, 13
F1:

Partición inicial.
F2: 15 18, 14, 40 51 93, 26 64, 13
F3: 7 75, 13 43, 75, 27

Primera fusión-partición.
F: 7 15 18 75, 26 27 64
F1: 13 14 40 43 51 75 93, 13

Segunda fusión-partición.
F2: 7 13 14 15 18 40 43 51 75 75 93
F3: 13 26 27 64

Tercera fusión-partición.
F: 7 13 13 14 15 18 26 27 40 43 51 64 75 75 93
F1:

12.- Rupturas de control.


La ruptura de control en la generación de listados consiste en la modificación de la línea de
detalle del informe debido a que los datos dejan de estar relacionados entre sí, porque varía el valor de
alguno de los campos. Es necesario que el fichero esté ordenado por el/los campo/s que producen la
ruptura de control.

Ejemplo: si se listan las ventas realizadas de unos grandes almacenes:

Dpto. Mes Vendedor Producto Ventas V. T. TOTAL


A 1 Juan ... ...
Concha ... ... ...

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 146

2 Juan ... ...


Concha ... ... ... ...
TOT. DEP. ....

B 1 Julio ... ...


Nieves ... ... ...
2 Julio ... ...
Nieves ... ... ... ...
VENTAS TOTALES .....

Se produce una ruptura de control por Departamento, dentro de éste por Mes y dentro de éste
por Vendedor.
fread(&R, sizeof(R), 1, F);
while (! feof(F))
{ auxRC_1 = R.RC1;
// Otras inicializaciones
while(!feof(F) && auxRC1 == R.RC1)
{ auxRC2 = R.RC2;
// Otras inicializaciones
...
while(!feof(F) && auxRC1==R.RC1 &&...&& auxRCN==R.RCN)
{ TRATAR R
fread(&R, sizeof(R), 1, F);
}
// Operaciones tras ruptura de control
...
}
// Operaciones tras ruptura de control
}
// Operaciones tras ruptura de control

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 147

Práctica 1:
Las ventas de artículos de una determinada cadena de almacenes se encuentran almacenadas en
un fichero, de tal forma que cada registro, que contiene las ventas de un determinado almacén de la
cadena, tiene la siguiente estructura:
REGISTRO almacen
Codigo ES CADENA DE 5
Ventas_articulos ES ARRAY DE 5 ENTEROS
FIN_REGISTRO
Por otra parte, en otro fichero, que contiene uno o más registros, se almacena el nombre de
cada artículo y su precio.
De pide:
a) Programa que cree el fichero de artículos.
b) Programa que cree el fichero de ventas.
c) Listado del total de ventas de cada almacen (en pesetas).
d) Listar las ventas (en cantidad y pesetas) de los artículos.

// Ventas de artículos. CREACIÓN DEL ARCHIVO ARTICULO


// Suponemos un único registro.
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#define NARTICULOS 5
#define EX_ARTICULO "C:\\TRABAJO\\ARTICULO.FIC"
typedef struct
{ char descripcion[NARTICULOS][30];
int precio[NARTICULOS];
}ARTICULOS;
main()
{ FILE *f_articulo;
ARTICULOS s_articulo;
int i;
for (i=0; i<NARTICULOS; i++)
{ printf("Descripción del artículo: ");
fflush(stdin); gets(s_articulo.descripcion[i]);
printf("Precio: ");
fflush(stdin); scanf("%d", &s_articulo.precio[i]);
}
f_articulo = fopen(EX_ARTICULO, "w");
fwrite(&s_articulo, sizeof(s_articulo), 1, f_articulo);
fclose(f_articulo);
}

// Ventas de artículos. CREACIÓN DEL ARCHIVO ARTICULO


// Suponemos un único registro.
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#define NARTICULOS 5
#define EX_ARTICULO "C:\\TRABAJO\\ARTICULO.FIC"
typedef struct
{ char descripcion[NARTICULOS][30];
int precio[NARTICULOS];
}ARTICULOS;
main()
{ FILE *f_articulo;
ARTICULOS s_articulo;
int i;
for (i=0; i<NARTICULOS; i++)
{ printf("Descripción del artículo: ");
fflush(stdin); gets(s_articulo.descripcion[i]);
printf("Precio: ");
fflush(stdin); scanf("%d", &s_articulo.precio[i]);
}
f_articulo = fopen(EX_ARTICULO, "w");
fwrite(&s_articulo, sizeof(s_articulo), 1, f_articulo);
fclose(f_articulo);
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 148

// Ventas de artículos. CREACIÓN DEL ARCHIVO ALMACEN


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

#define NARTICULOS 5
#define EX_ALMACEN "C:\\TRABAJO\\ALMACEN.FIC"
#define EX_ARTICULO "C:\\TRABAJO\\ARTICULO.FIC"
typedef struct
{ char codigo[5];
int ventas[NARTICULOS];
}ALMACEN;
typedef struct
{ char descripcion[NARTICULOS][30];
int precio[NARTICULOS];
}ARTICULOS;
main()
{ FILE *f_articulo;
ARTICULOS s_articulo;
FILE *f_almacen;
ALMACEN s_almacen;
int i, seguir;
f_articulo = fopen(EX_ARTICULO, "r");
fread(&s_articulo, sizeof(s_articulo), 1, f_articulo);
fclose(f_articulo);
f_almacen = fopen(EX_ALMACEN, "w");
do
{ do
{ printf("Entrar almacen(s/n)");
fflush(stdin);
seguir=getchar();
}while(toupper(seguir)!='S' && toupper(seguir)!='N');
if(toupper(seguir)=='S')
{ printf("Código de almacen: ");
fflush(stdin);
gets(s_almacen.codigo);
for (i=0; i<NARTICULOS; i++)
{ printf("Ventas %s: ", s_articulo.descripcion[i]);
fflush(stdin);
scanf("%d", &s_almacen.ventas[i]);
}
fwrite(&s_almacen, sizeof(s_almacen), 1, f_almacen);
}
}while(toupper(seguir)=='S');
fclose(f_almacen);
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 149

// Ventas de artículos. LISTADOS


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

#define NARTICULOS 5
#define EX_ALMACEN "C:\\TRABAJO\\ALMACEN.FIC"
#define EX_ARTICULO "C:\\TRABAJO\\ARTICULO.FIC"

typedef struct
{ char codigo[5];
int ventas[NARTICULOS];
}ALMACEN;
typedef struct
{ char descripcion[NARTICULOS][30];
int precio[NARTICULOS];
}ARTICULOS;

void lista_almacenes(ALMACEN, ARTICULOS);


void lista_articulos(ARTICULOS articulo, int *);
void acumula(int *, int *);

main()
{ FILE *f_almacen;
ALMACEN s_almacen;
FILE *f_articulo;
ARTICULOS s_articulo;
int ventas_articulos[NARTICULOS], i;

f_articulo = fopen(EX_ARTICULO, "r");


fread(&s_articulo, sizeof(s_articulo), 1, f_articulo);
fclose(f_articulo);
f_almacen = fopen(EX_ALMACEN, "r");
for(i=0; i<NARTICULOS; i++)
ventas_articulos[i]=0;
while (!feof(f_almacen))
{ fread(&s_almacen, sizeof(s_almacen), 1, f_almacen);
if (!feof(f_almacen))
{ lista_almacenes(s_almacen, s_articulo);
acumula(s_almacen.ventas, ventas_articulos);
}
}
fclose(f_almacen);
lista_articulos(s_articulo, ventas_articulos);
}
void lista_almacenes(ALMACEN almacen, ARTICULOS articulo)
{ int i, total=0;

for (i=0; i<NARTICULOS; i++)


total += almacen.ventas[i] * articulo.precio[i];
printf("%-10s %d\n", almacen.codigo, total);
}
void lista_articulos(ARTICULOS articulo, int *total)
{ int i;
for(i=0; i<NARTICULOS; i++)
printf("%-30s%10d%10d%10d\n",articulo.descripcion[i], total[i],
articulo.precio[i],total[i]*articulo.precio[i]);
}
void acumula(int *venta, int *total)
{ int i;
for (i=0;i<NARTICULOS; i++)
total[i] += venta[i];
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 150

// Ventas de artículos. ACTUALIZA EL ARCHIVO ALMACEN CON EL DE MOVIMIENTOS


#define NARTICULOS 5
#define EX_ALMACEN "C:\\TRABAJO\\ALMACEN.FIC"
#define EX_MOVIMIENTO "C:\\TRABAJO\\MOVIMIE.FIC"
#define EX_FINAL "C:\\TRABAJO\\FINAL.FIC"
#define EX_ERROR "C:\\TRABAJO\\ERRORES.FIC"
typedef struct
{ char codigo[5];
int ventas[NARTICULOS];
}ALMACEN;
typedef struct
{ char accion;
ALMACEN mover;
}MOVIMIENTO;
main()
{ FILE *f_movimiento, f_almacen, f_final, f_errores;
MOVIMIENTO s_movimiento;
ALMACEN s_almacen;
int i;
f_almacen = fopen(EX_ALMACEN, "r");
f_movimiento = fopen(EX_MOVIMIENTO, "r");
f_final = fopen(EX_FINAL, "w");
f_errores = fopen(EX_ERROR, "w");
fread(&s_almacen, sizeof(ALMACEN), 1, f_almacen);
while (fread(&s_movimiento, sizeof(MOVIMIENTO), 1, f_movimiento))
{ while(!feof(f_almacen)&& strcmp(s_movimiento.mover.codigo,s_almacen.codigo)>0)
{ fwrite(&s_almacen, sizeof(ALMACEN), 1, f_final);
fread(&s_almacen, sizeof(ALMACEN), 1, f_almacen);
}
if(strcmp(s_movimiento.mover.codigo,s_almacen.codigo)==0)
switch (s_movimiento.accion)
{ case 'M':
for (i=0; i<NARTICULOS; i++)
s_movimiento.mover.ventas[i]+= s_almacen.ventas[i];
fwrite(&s_movimiento.mover, sizeof(ALMACEN), 1, f_final);
case 'B':
fread(&s_almacen, sizeof(ALMACEN), 1, f_almacen);
break;
default:
fwrite(&s_movimiento, sizeof(MOVIMIENTO), 1, f_errores);
// Escribe en errores pero también el registro de almacén
break;
}
else
if (s_movimiento.accion=='A')
fwrite(&s_movimiento.mover, sizeof(ALMACEN), 1, f_final);
else
fwrite(&s_movimiento, sizeof(MOVIMIENTO), 1, f_errores);
}
while (!feof(f_almacen))
{ fwrite(&s_almacen, sizeof(ALMACEN), 1, f_final);
fread(&s_almacen, sizeof(ALMACEN), 1, f_almacen);
}
fclose(f_almacen);
fclose(f_movimiento);
fclose(f_final);
fclose(f_errores);
//remove(EX_ALMACEN);
//rename(EX_FINAL, EX_ALMACEN);
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 151

Práctica 2: MANTENIMIENTO DE UN ARCHIVO DIRECTO.


typedef struct
{ char nombre[40];
char calle[20];
char ciudad[25];
char cpostal[10];
int edad;
char activo;
} ALUMNOS;
void dibujapantalla()
{ clrscr();
gotoxy(20,5);printf("Nombre :");
gotoxy(20,6);printf("Calle :");
gotoxy(20,7);printf("Ciudad :");
gotoxy(20,8);printf("Codigo Postal :");
gotoxy(20,9);printf("Edad :");
}
void reintroducir()
{ gotoxy(40,5);clreol();
gotoxy(40,6);clreol();
gotoxy(40,7);clreol();
gotoxy(40,8);clreol();
gotoxy(40,9);clreol();
}
int confirmar(char *texto)
{ int car;
gotoxy(25,24);printf("%s ",texto);
do
{ fflush(stdin);
car = toupper(getchar());
}while (car != 'S' && car != 'N');
gotoxy(1,24);delline();
return (car == 'S');
}
FILE *activar(char *f_externo, char *modo)
{ FILE *fichero;
if ((fichero=fopen(f_externo, modo))==NULL)
if((fichero=fopen(f_externo, "w+"))==NULL)
{ printf("Error de apertura");
exit(1);
}
return (fichero);
}
int posicion(char *n, FILE *f)
{ ALUMNOS estudiante;
int hallado = 0;
fseek(f,0, SEEK_SET);
while (!hallado && fread(&estudiante, sizeof(ALUMNOS), 1, f))
hallado = (stricmp(estudiante.nombre, n)==0) && estudiante.activo;
if (hallado)
return (ftell(f)/sizeof(ALUMNOS)) ? 1;
else
return (EOF);
}
void visualizar(FILE *f, int registro)
{ ALUMNOS estudiante;
fseek(f,registro*sizeof(ALUMNOS), SEEK_SET);
if (fread(&estudiante, sizeof(ALUMNOS), 1, f) && estudiante.activo)
{ gotoxy(40,5);printf("%s", estudiante.nombre);
gotoxy(40,6);printf("%s", estudiante.calle);
gotoxy(40,7);printf("%s", estudiante.ciudad);
gotoxy(40,8);printf("%s", estudiante.cpostal);
gotoxy(40,9);printf("%d", estudiante.edad);
}
}
void leerclave(char *n)
{ reintroducir();
fflush(stdin);gotoxy(40,5);gets(n);
}
void leerdatos(ALUMNOS *estudiante)
{ fflush(stdin);gotoxy(40,6);gets(estudiante?>calle);
fflush(stdin);gotoxy(40,7);gets(estudiante?>ciudad);
fflush(stdin);gotoxy(40,8);gets(estudiante?>cpostal);
fflush(stdin);gotoxy(40,9);scanf("%d", &estudiante?>edad);
estudiante?>activo = 1;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 152

void insertar(FILE *f, char *clave)


{ ALUMNOS estudiante;
do
{ leerdatos(&estudiante);
}while (!confirmar("Es correcto (S/N)"));
strcpy(estudiante.nombre, clave);
fseek(f, 0, SEEK_END);
fwrite(&estudiante, sizeof(ALUMNOS), 1, f);
}
void modificar(FILE *f, char *clave, int registro)
{ ALUMNOS estudiante;
do
{ leerdatos(&estudiante);
}while (!confirmar("Es correcto (S/N)"));
strcpy(estudiante.nombre, clave);
fseek(f, registro*sizeof(ALUMNOS), SEEK_SET);
fwrite(&estudiante, sizeof(ALUMNOS), 1, f);
}
void borrar(FILE *f, int registro)
{ ALUMNOS estudiante;
if (confirmar("borrar el registro (S/N)"))
{ estudiante.activo = 0;
fseek(f,registro*sizeof(ALUMNOS), SEEK_SET);
fwrite(&estudiante, sizeof(ALUMNOS), 1, f);
}
}
void cuerpo(FILE *f)
{ int opcion, correcto, registro;
char clave[40], *menu;
dibujapantalla();
do
{ leerclave(clave);
registro = posicion(clave,f);
if (registro == EOF)
menu = "I?nsertar, R?int., F?in ";
else
{ menu="M?odificar,B?orrar, S?iguiente,A?nterior, R?int., F?in ";
visualizar(f, registro);
}
do
{ gotoxy(10,24);printf("%s", menu);
correcto = 0;
do
{ fflush(stdin); opcion = toupper(getchar());
}while (strchr("IBMRASF", opcion)==NULL);
gotoxy(1,24);delline();
switch (opcion)
{ case 'I': if (registro == EOF)
{ insertar(f,clave);
correcto = 1;
}
break;
case 'M': if (registro != EOF)
{ modificar(f,clave, registro);
correcto = 1;
}
break;
case 'B': if (registro != EOF)
{ borrar(f,registro);
correcto = 1;
}
break;
case 'A': visualizar(f,??registro);
break; //Incompleto
case 'S': visualizar(f,++registro);
break;//Incompleto
case 'R': reintroducir();
correcto = 1;
break;
default: correcto = 1;
}
}while(!correcto);
}while (opcion != 'F');
}
main()
{ FILE *archivo= activar("c:\\trabajo\\alumnos.ver", "r+");
cuerpo(archivo);
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 153

Ejercicio:

El fichero PERSONAL tiene la siguiente estructura de registro:


NOMBRE de tipo alfanumérico
EDAD de tipo numérico entero
DIRECCIÓN de tipo alfanumérico
SUELDOBRUTO de tipo numérico entero
ESTADOCIVIL de tipo numérico entero
CATEGORÍA de tipo alfanumérico

El número de registros es el que necesite el usuario.


La categoría está comprendida entre A y J.
Los valores posibles del estado civil(EC) son 0,1 ó 2.

El fichero CATEGORÍA tiene la siguiente estructura de registro:


TIPOCATEGORIA de tipo numérico entero
DESCUENTO de tipo numérico entero
DESCRIPCIÓN de tipo alfanumérico
Hay 10 categorías, de la A a la J.

Dados los ficheros anteriores y suponiendo PERSONAL ordenado en orden creciente de


categoría y CATEGORÍA desordenado, obtener un listado con el siguiente formato:

Página: ...
Listado de cobros de la categoría DESCRIPCIÓN
NOMBRE EDAD DIRECCIÓN SUELDO NETO
__________________________________________________________________
.................. .. .................. ......
.................. .. .................. ......
...
SUELDO NETO TOTAL ......

Teniendo en cuenta que el sueldo neto se calcula mediante la siguiente operación:


SUELDONETO=SUEDOBRUTO-DESCUENTO.
El descuento se extrae del fichero CATEGORÍA (el campo CATEGORÍA se corresponde con
el campo TIPOCATEGORIA).
Cada vez que se cambie de categoría o escribamos 55 líneas(registros), escribir la cabecera del
listado e incrementar el número de página; así mismo, cuando se cambie de categoría escribir el sueldo
neto total para la categoría.
Imprimir el número de personas que cumplen la condición de edad mayor que una determinada
introducida por teclado y su sueldo neto sea mayor que una cantidad también introducida por teclado.

Obtener el número de empleados y el sueldo bruto total de cada categoría, dando un listado de
la categoría, número de empleados y sueldo bruto ordenado por número de empleados, y en caso de ser
iguales, por sueldo bruto.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 154

entorno:
PERSONAL es archivo de RPERSONAL
RPERSONAL es registro compuesto de
NOMBRE es de tipo alfanumérico
EDAD es de tipo numérico entero
DIRECCION es de tipo alfanumérico
SUELDOBRUTO es de tipo numérico entero
ESTADOCIVIL es de tipo numérico entero
CATEGORIA es de tipo alfanumérico
fin registro
FCATEGORIA es archivo de RCATEGORIA
RCATEGORIA es registro compuesto de
TIPOCATEGORIA es de tipo alfanumérico
DESCUENTO es de tipo numérico entero
DESCRIPCION es de tipo alfanumérico
fin registro
SUELDONETO,NETOTOAL,NR son de tipo numérico entero
MAYOREDAD,MAYORNETO son de tipo numérico entero
LINEA,PAGINA son de tipo numérico entero
AUXCAT es de tipo alfanumérico
algoritmo:
escribir 'EDAD MAYOR A :'
escribir 'SUELDO MAYOR A :'
leer MAYOREDAD, MAYORNETO
PAGINA=0
abrir PERSONAL,FCATEGORIA para lectura
leer PERSONAL,RPERSONAL
mientras no ff(PERSONAL) hacer
NR=0
repetir
NR=NR+1
leer FCATEGORIA,RCATEGORIA,NR
hasta TIPOCATEGORIA=CATEGORIA
AUXCAT=CATEGORIA, NETOTOTAL=0
mientras no ff(PERSONAL) y AUXCAT=CATEGORIA hacer
PAGINA=PAGINA+1
escribir 'Página : ',PAGINA
escribir 'Listado de .....', DESCRIPCION
escribir 'NOMBRE EDAD DIRECCION SUELDO'
escribir '____________________________________'
LINEA=0
mientras no ff(PERSONAL) y AUXCAT=CATEGORIA y
LINEAS<=55 hacer
LINEA=LINEA+1
SUELDONETO=SUELDOBRUTO-DESCUENTO
NETOTOTAL=NETOTOTAL+SUELDONETO
escribir NOMBRE,EDAD,DIRECCION,SUELDONETO
si EDAD>MAYOREDAD y SUELDONETO>MAYORNETO
NUMPER=NUMPER+1
fin si
leer PERSONAL,RPERSONAL
fin mientras
fin mientras
escribir 'NETO TOTAL... ',NETOTOTAL
fin mientras
cerrar PERSONAL, FCATEGORIA
escribir 'Los empleados con edad mayor a ',MAYOREDAD,
'y sueldo mayor a ',MAYORNETO,' son ',NUMPER

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 155

PRÁCTICA:
En el fichero secuencial CLIENTES.DAT se guardan los datos personales de los clientes de un
banco, cuyos campos son:

NCC NOMBRE DIRECCIÓN POBLACIÓN PROVINCIA SALDO

donde SALDO almacena la cantidad actual del cliente: puede ser un saldo deudor.
También se dispone de otro fichero secuencial con los clientes que han tenido movimiento en
sus cuentas corrientes, cuyos formatos son:

NCC MOVIMIENTO TIPO


donde:
MOVIMIENTO almacena la cantidad.
TIPO puede tomar los valores - Cero: imposición
- Uno: reintegro

Hacer un programa para resolver los siguientes puntos:


A) Crear el fichero de clientes con un mínimo de 25 registros.
B) Crear el fichero de movimientos; cada cliente tendrá de 0 a 5 movimientos.
C) Ordenar ascendentemente por número de cuenta corriente el archivo de movimientos.
Opcionalmente se ordenará también el fichero de clientes.
D) Obtener un listado ordenado de clientes cuyas cuentas corrientes han tenido
movimiento; cada cliente aparecerá en una hoja (pantalla) distinta con el siguiente
formato:

CUENTA CORRIENTE : NCC


NOMBRE :
DIRECCIÓN :
POBLACIÓN :
PROVINCIA :
MOVIMIENTOS TIPO CANTIDAD
Tipo movimiento
Tipo movimiento
.....
SALDO saldo(No modificar fichero)

E) Obtener un listado ordenado de clientes con el saldo actual (éste se actualizará en el


fichero de clientes) con el siguiente formato:

NCC NOMBRE DIRECCIÓN POBLACIÓN PROVINCIA SALDO

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 157

15.- ESTRUCTURAS DINÁMICAS LINEALES DE DATOS

1.- Introducción.
Las estructuras estáticas necesitan un espacio de asignación de memoria fijo para almacenar los
datos. En la práctica suelen darse casos en los que una estructura de datos puede crecer y/o disminuir
sin conocer previamente la extensión o tamaño de los datos. Por ello es necesario un mecanismo que
no realice una asignación fija de memoria, si no que sea variable con el tiempo; es lo que se conoce
como asignación dinámica de memoria. El mecanismo principal de esta asignación dinámica de
memoria para almacenar ciertos elementos de una estructura es el puntero, que es en sí una variable
dinámica, y se utiliza para construir elementos de este tipo. Por tanto, no existen por sí mismas, si no
que se basan en una implementación con variables de tipo puntero, siendo necesario reservar la
memoria suficiente para almacenar los datos.

Una estructura de datos dinámica es una colección de elementos denominados nodos de la


estructura que son enlazados juntos. Las estructuras dinámicas de datos crecen a medida que se ejecuta
el programa. Una estructura de datos dinámica es una colección de elementos -nodos- normalmente
registros. Al contrario que un array, que contiene un espacio para almacenar un número fijo de
elementos, las estructuras dinámicas se amplían o contraen durante la ejecución del programa basada
en los registros de almacenamiento de datos del programa. Las estructuras de datos dinámicas se
dividen en dos grupos:

Listas enlazadas
Lineales Pilas
Colas

Árboles
No lineales
Grafos

2.- Listas enlazadas.


Una lista enlazada es un conjunto de elementos, lógicamente almacenados uno a continuación
de otro, no físicamente, donde cada uno de ellos contiene la posición -dirección- del siguiente
elemento de la lista. Los elementos de la lista se denominan nodos y deben estar formados, al menos,
por dos campos: la información propiamente dicha (valor del nodo) y un campo enlace que contiene la
posición del siguiente nodo de la lista.

Información Enlace
Representación gráfica de un nodo con dos campos

LISTA Representación gráfica de una lista

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 158

6 4 3 9
Representación gráfica de los elementos de un array.

Una lista tiene las siguientes características:

· El campo enlace de cada nodo apunta al siguiente nodo de la lista.


· El primer nodo es apuntado por un puntero cabecera que indica el inicio de la lista.
· El campo enlace del último nodo se representa por la palabra NIL/NULL (nulo), una barra
inclinada (/) o el símbolo eléctrico de tierra o masa indicando que es el final de la lista, no
apunta a nada.

Los nodos no se almacenan necesariamente en posiciones contiguas de memoria ya que el


campo enlace es un puntero que guarda la dirección de memoria, cualquier lugar disponible, donde se
almacena el siguiente nodo.

Una lista enlazada viene definida por los siguientes elementos:

· El tipo de sus elementos. Registro compuesto por al menos dos campos:


- Uno o varios campos de información (los datos propiamente dichos).
- Uno o varios campos enlace (punteros) dependiendo del tipo de lista.
· Un puntero de cabecera que permite acceder al primer nodo de la lista; es decir, es un
puntero externo que guarda la dirección de memoria donde comienza la lista (primer nodo
de la misma). El resto de los nodos es accesible mediante el campo enlace que le precede.
· Un medio para detectar el último elemento de la lista: puntero nulo (NULL).

2.1.- Implementación.
Para realizar la implementación del tipo lista enlazada es necesario usar estructuras
autoreferenciadas o recursivas: el nodo contiene elementos de su misma categoría; es decir, estructuras
en las que uno de sus campos es un puntero (enlace) que referencia a la propia estructura. El formato
general para la declaración de una estructura de este tipo es el siguiente:

struct <IdEstructura>
{ <tipo> <IdCampo>; //Uno o varios campos de datos.
struct <IdEstructura> *<IdCampoEnlace>;//Campo enlace
};
typedef struct <IdEstructura> *<Identificador>;

Y la componente de una lista, según el formato anterior, quedaría implementada del siguiente
modo:

//Declaración del campo de información; o //una variable simple o una estructurada.


typedef ... Tinformacion;
//Declaración del nodo
struct Nodo
{ Tinformacion Dato;
struct Nodo *Siguiente;
};

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 159

y la lista completa sería un puntero a una estructura de este tipo:

// Declaración de un tipo de dato del tipo estructura nodo.


typedef struct Nodo *Tlista;

Por ejemplo, para describir una lista de números enteros, se puede escribir lo siguiente:
typedef int TInformación; //Redefinición de un entero
struct Nodo
{ TInformacion Dato;
struct Nodo *Siguiente;
};
typedef struct Nodo *TLista;
main()
{ TLista LaLista;//Variable cabecera de la lista.
}

2.2.- Operaciones básicas para la manipulación de listas.


Todas las operaciones se basan en la estructura definida anteriormente, es decir se va a
manipular una lista cuyo campo información es un entero.

2.2.1.- Inicialización.

Esta crea una lista vacía. La variable cabecera se inicializa con el valor NULL, un puntero
vacío.
void Inicializa(tlista *Lista)
{ *Lista= NULL;
}

2.2.2.- Lista vacía.

Esta función indica si la lista contiene elementos o no. Se pregunta por el puntero cabecera de
lista.
bool EstaVacia(TLista Lista)
{ return Lista == NULL;
}

2.2.3.- Añadir nodos a la lista.

La inserción de un nodo en la lista consta de tres pasos:

· Reservar dinámicamente la memoria necesaria para almacenar el nuevo nodo. Para hacerlo usamos
la función ‘malloc()’, ‘calloc()’ u otras que proporcione el lenguaje.
· Asignar la información (los datos) al/los campo/s de datos de la estructura.
· Encadenar el nuevo nodo a la lista sin perder los enlaces existentes de los restantes nodos. Este
encadenamiento se realizará de diferentes maneras dependiendo del modo de inserción. El orden de
las operaciones de encadenamiento del nuevo nodo es el siguiente:

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 160

a) Se encadena el nuevo nodo a los nodos existentes en la lista para no perder los enlaces ya
establecidos.
b) Se encadenan los nodos de la lista al nuevo nodo.

Las formas de inserción de un nodo que vamos a ver son las siguientes:

· Inserción al principio de la lista.


· Inserción en medio o al final de la lista.
· Inserción en una lista ordenada.

2.2.3.1.- Insertar al principio de la lista.

La siguiente función inserta un nuevo nodo en la lista, independientemente de que esté o no


vacía.

Dato 1 Dato 1
Lista

Lista vacía Nuevo Lista


Reserva de memoria Primer elemento encadenado

Representación gráfica de la inserción del primer elemento en una lista

Dato 1 Dato 2 Dato 2 Dato 1

Lista Nuevo Lista


Reserva de memoria Segundo elemento encadenado

Representación gráfica de la inserción del segundo elemento en una lista

void InsertaAlPrincipio(TLista *Lista, TInformacion Info)


{ TLista Nuevo;
Nuevo= (TLista) malloc(sizeof(Nodo));
Nuevo->Dato= Info;
Nuevo->Siguiente= *Lista;// Nuevo apunta a inicio de la lista
*Lista= Nuevo; // Nuevo pasa a ser cabecera de lista
}

Como estamos insertando al principio de la lista, delante del primer nodo, el nodo que se crea
se convierte ahora en cabecera de la misma (el puntero cabecera de lista debe apuntar al nuevo nodo);
por ello, el parámetro 'TLista *Lista' se pasa por puntero o referencia y se corresponde con el
comienzo de la lista.

2.2.3.2.- Insertar en medio o al final de la lista.

Inserta un nodo detrás del nodo apuntado. Si la lista está vacía, la inserción es como la anterior.
Si la lista no está vacía, debemos conocer la posición del nodo tras el que queremos insertar el nuevo

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 161

nodo. Los pasos que damos, por orden, son: primero encadenar el nuevo nodo con el nodo siguiente al
apuntado “Nuevo->Siguiente= (*Lista)->Siguiente” y, segundo, encadenar el nodo apuntado al nuevo
“nodo(*Lista)->Siguiente= Nuevo”.

El paso (1) hay que hacerlo necesariamente en primer lugar para no perder la dirección del
siguiente nodo al apuntado.

Dato 1 Dato 2 Dato3 Dato


Lista

Posicion Nuevo
Lista encadenada. Situación inicial. Reserva de memoria.

SEGUNDO PASO: Posicion->Enlace= Nuevo

Dato 1 Dato 2 Dato3 Dato

Lista
Posicion Nuevo
PRIMER PASO: Nuevo->Enlace= Posicion->Enlace
Representación gráfica de la inserción de un nodo entre otros dos

void InsertarEnMmedio(TLista *Lista, TInformacion Info)


{ TLista Nuevo;
Nuevo= (TLista) malloc(sizeof(Nodo));
Nuevo->Dato= Info;
if (Vacia(*Lista))
{ //Nuevo->Siguiente= *Lista;
//*Lista= Nuevo ;
*Lista= Nuevo;
Nuevo->Siguiente= NULL;
}
else
{ Nuevo->Siguiente= (*Lista)->Siguiente;
(*Lista)->Siguiente= Nuevo;
}
}

2.2.3.3.- Inserción en una lista ordenada.

Una lista ordenada es aquella cuyos nodos están ordenados ascendente o descendentemente
según el valor de uno o varios campos de datos del nodo. Para insertar en una lista ordenada
ascendentemente, es necesario conocer la posición (mediante un puntero) del nodo cuya información
es inmediatamente inferior que la del nodo que queremos insertar. Para ello tenemos que implementar
un nueva función que realice un recorrido secuencial de la lista para determinar en que lugar de la lista
hay que insertar; ésta, devuelve un puntero a:

· NULL, si la lista está vacía o es la menor información almacenada en la lista. La acción que
debemos realizar es la de insertar al principio de la lista.
· Dirección del nodo tras el que queremos insertar; si la información que queremos insertar el la
mayor, retorna un puntero al último nodo. La información que almacena el nodo devuelto tiene
valor inferior o igual a la que queremos insertar. La acción que realizamos es la de insertar en
medio o al final de la lista.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 162

void InsertarOrdenado(TLista *Lista, TInformacion Info)


{ TLista Posicion= Buscar(*Lista, Info);
if (Posicion)
InsertarEnMedio(&Posicion, Info);
else
InsertarAlPrincipio(Lista, Info);
}
TLista Buscar(TLista Lista, TInformacion Info)
{ TLista Anterior= NULL;
// Condición como está o poner interruptor
while (Lista != NULL && Info > Lista->Dato)
{ Anterior= Lista; //Se almacena la dir. del nodo anterior
Lista= Lista->Siguiente; //Se toma el siguiente nodo
}
return Anterior;
}

Otra función que inserta un nodo en la lista ordenado en sentido ascendente según el valor de
uno de los campos de información del nodo y que no utiliza las funciones de inserción creadas con
anterioridad es la siguiente:
void InsertaOrdenado(TLista *Lista, TInfo Info)
{ TLista Nuevo= (TLista)malloc(sizeof(Nodo));
Nuevo->Dato= Info;
if (Vacia(*Lista) || Info <= (*Lista)->Dato)
{ Nuevo->Siguiente= *Lista;
*Lista= Nuevo;
}
else
{ TLista Aux= *Lista;
while(!Vacia(Aux->Siguiente) && Info > Aux->Siguiente->Dato)
Aux= Aux->Siguiente;
Nuevo->Siguiente= Aux->Siguiente;
Aux->Siguiente= Nuevo;
}
}

Tiene tres bloques diferenciados:

· Reserva de memoria y asignación de la información.


· Si la lista está vacía o la información a añadir es menor que las almacenadas en esta, se
añade el nodo al principio de la lista.
· En otro caso se recorre la lista buscando el nodo que contiene la información
inmediatamente inferior a la que queremos insertar e insertamos el nuevo nodo detrás de él.

2.2.4.- Recorrido de una lista.

El recorrido de una lista se realiza secuencialmente, comenzando en la cabecera de la lista y


continuando por los nodos restantes, a través de su campo enlace, hasta alcanzar el final de la misma
(cuando el campo enlace del último nodo es NULL).
void Visualizar(TLista Lista)
{ while(!Vacia(Lista))
{ printf("%d\t", Lista->Dato); // TRATAR (Lista->Dato)
Lista= Lista->Siguiente;
}
}

El mismo tratamiento recursivo es el siguiente:


void Visualizar(TLista Lista)
{ if (!Vacia(Lista))
{ printf("%d\t", Lista->Dato);
Visualizar(Lista->Siguiente);
}
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 163

La implementación de un recorrido que busca un elemento en una lista ordenada es el


siguiente:
TLista BuscarOrdenado(TLista Lista, TInformacion Info)
{ while (!Vacia(Lista) && Info > Lista->Dato)
Lista= Lista->Siguiente;
if(Vacia(Lista) || Info != Lista->Dato)
return NULL; //Final de lista o no se encuentra la información.
else
return Lista;//Información encontrada.
}

2.2.5.- Borrado de nodos de la lista.

La operación de borrado de un nodo de la lista, igual que la inserción/creación de una lista


ordenada, requiere que se conozca la posición del nodo que se desea eliminar. Los pasos para eliminar
un nodo son los siguientes:

· Buscar el elemento en la lista. Se necesitan dos punteros, uno recorre la lista buscando la
información y el otro guarda la posición anterior.
· Si el elemento se ha encontrado, caben dos posibilidades:
· Es el primero de la lista: la nueva lista apunta a la segunda componente.

Primer paso: Lista= Lista->Enlace

Dato 1 Dato 2 Dato 3 Dato 4


Lista
Segundo paso: free(Auxiliar)
Auxiliar

Borrar el primer nodo de la lista.

· No es el primero de la lista: el elemento actual (anterior al que queremos borrar) apunta


a la siguiente componente del elemento que queremos borrar.

Primer paso: Anterior->Enlace= Auxiliar->Enlace

Dato 1 Dato 2 Dato 3 Dato 4


Lista
Segundo paso: free(Auxiliar)
Anterior Auxiliar

Borrar un nodo intermedio o el último de la lista.

· Liberar la memoria ocupada por el nodo borrado.


void Borrar(TLista *Lista, TInformacion Info)
{ TLista Anterior, Auxiliar= *Lista;
int Encontrado= 0;

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 164

while (!Encontrado && !Vacia(Auxiliar))


if (Auxiliar->Dato == Info)
Encontrado= 1;
else
{ Anterior= Auxiliar;
Auxiliar= Auxiliar->Siguiente;
}
if (Encontrado)
{ if (*Lista == Auxiliar) // Principio de lista
*Lista= (*Lista)->Siguiente;
else
Anterior->Siguiente= Auxiliar->Siguiente;
free(Auxiliar);
}
}

Ejercicios propuestos:
1. Crear una lista lineal de números enteros.
2. Dada la lista anterior, realizar una función para crear otra lista que contenga sólo los números
pares que hay en la primera lista. La lista de pares debe quedar ordenada ascendentemente y la
lista original continuará teniendo su orden primitivo.
3. Dadas las listas LISTA1 y LISTA2, obtener LISTA3 como unión de las dos anteriores.
4. Cargar en una lista el fichero de datos de artículos, visualizar la lista y escribirla de nuevo en el
archivo.

3.- Listas enlazadas circulares.


Este tipo de listas son una variante de las anteriores. Consisten en que el último elemento
(nodo) de la lista, en lugar de apuntar a NULL, apunta al primer elemento de la misma, al primer nodo.
Las listas circulares presentan las siguientes ventajas con respecto a las listas enlazadas simples:

· Cada nodo es accesible desde cualquier otro nodo de la lista dado que podemos realizar una
vuelta completa de la lista sin necesidad de estar en la cabecera de la lista. En una lista
enlazada simple sólo pueden recorrerse todos los nodos si estamos en la cabecera de lista.
· Las operaciones de concatenación y división de listas son más sencillas.

Lista circular
Además de este tipo de lista circular existe otro tipo semejante con la diferencia de que
contiene un nodo falso, que siempre está en la lista (nunca está vacía) para indicar la cabecera de la
lista. Este nodo cabecera contiene información nula o especial para distinguirlo de los nodos que tienen
información real.

Lista circular con nodo cabecera

Las operaciones que vamos a ver se realizarán sobre una lista circular sin nodo cabecera.
Queda como ejercicio realizar las mismas operaciones sobre una lista circular con nodo cabecera.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 165

3.1.- Operaciones sobre listas enlazadas circulares.

3.1.1.- Insertar en la lista.

Inserta un nodo detrás del primero insertado; el puntero cabecera se mantiene fijo a dicho
nodo.
*Lista= Nuevo;
Nuevo->Siguiente= *Lista; //Lista ha tomado la dirección de nuevo

Dato 1 Dato 1
Lista

Lista vacía Nuevo Lista


Reserva de memoria Primer elemento encadenado

Representación gráfica de la inserción del primer elemento en una lista circular

La inserción del primer nodo hace que su campo enlace apunte al propio nodo, es una inserción
al principio de la lista salvo por este matiz.

Paso 3 Paso 2 Paso 1

Dato 4 Dato 3 Dato 2 Dato 1


Lista

Inserción de los nodos 2, 3, y 4 en la lista circular.

La inserción de un nuevo nodo, cuando la lista no está vacía, se hace apuntando el campo
enlace de éste al último elemento de la lista, es decir, al nodo que apunta el nodo cabecera
Nuevo->Siguiente= (*Lista)->Siguiente;

y el primero (nodo cabecera de lista) al nuevo insertado.


(*Lista)->Siguiente= Nuevo;

El algoritmo es el siguiente:
void Insertar(TLista *Lista, TInformacion Info)
{ TLista Nuevo;
Nuevo= (TLista) malloc(sizeof(Nodo));
Nuevo->Dato= Info;
if (Vacia(*Lista))
{ *Lista= Nuevo;
Nuevo->Siguiente= *Lista; //Nuevo->Siguiente= Nuevo;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 166

else
{ Nuevo->Siguiente= (*Lista)->Siguiente;
(*Lista)->Siguiente= Nuevo;
// Podemos variar el nodo cabecera para realizar una inserción
// al final de la lista, detrás del último insertado en la lista
//*Lista= Nuevo;
}
}

3.1.2.- Borrar un nodo de la lista.

La operación de borrado de un nodo de una lista circular, igual que en operaciones de borrado
anteriores, requiere que se conozca la posición del nodo que se desea eliminar. Los pasos para eliminar
un nodo son los siguientes:
· Buscar el elemento en la lista. Se necesitan dos punteros, uno recorre la lista buscando la
información y el otro guarda la posición anterior.
· Si el elemento se ha encontrado, se pueden dar las siguientes posibilidades:
· Es el único nodo de la lista: hay que inicializar la lista.

Lista= NULL
Dato 1 Lista Free(Auxiliar)

Lista, Auxiliar, Anterior

· No es el único nodo de la lista:


o Es el primero de la lista: La nueva lista apunta a la siguiente componente o a la
anterior.
(2) Anterior->Siguiente= Auxiliar->Siguiente

Dato 4 Dato 3 Dato 2 Dato 1 (3) free(Auxiliar)

(1) Lista= Anterior

Anterior Lista, Auxiliar

o Sea o no el primero de la lista: el elemento actual (anterior al que queremos borrar)


apunta a la siguiente componente del elemento que queremos borrar.

(1) Anterior->Siguiente= Auxiliar->Siguiente

Dato 4 Dato 3 Dato 2 Dato 1


(2) free(Auxiliar)

Anterior Auxiliar Lista

· Liberar la memoria ocupada por el nodo borrado.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 167

void Borrar(TLista *Lista, TInformacion Info)


{ TLista Anterior, Auxiliar;
int Encontrado= 0;
if (!Vacia(*Lista))
{ Auxiliar = *Lista;
do
{ Anterior= Auxiliar;
Auxiliar= Auxiliar->Siguiente;
if (Auxiliar->Dato == Info)
Encontrado= 1;
}while (!Encontrado && Auxiliar != *Lista);
if (Encontrado)
{ if(*Lista == (*Lista)->Siguiente)
Inicializa(Lista); // Si sólo hay un nodo.
else
{ if (*Lista == Auxiliar) //Si es el primero
*Lista= Anterior;
Anterior->Siguiente= Auxiliar->Siguiente;
}
free(Auxiliar);
}
}
}

La función se puede complementar retornando el valor de 'Encontrado' para indicar a la función


llamante si el borrado ha sido satisfactorio.

3.1.3.- Recorrido de la lista.

El recorrido de la lista es similar al realizado para una lista enlazada simple salvo que para la
lista circular se necesita un puntero auxiliar. El recorrido comienza en el siguiente nodo a la cabecera
de lista y finaliza cuando el puntero auxiliar (previamente inicializado a la lista) coincide con la
cabecera de lista.
void Visualizar(TLista Lista)
{ TLista Auxiliar= Lista;
if (!Vacia(Lista))
do
{ Auxiliar= Auxiliar->Siguiente;
printf("%d\t", Auxiliar->Dato);
}while(Auxiliar != Lista);
}

4.- Listas doblemente enlazadas.


En las listas anteriores el recorrido sólo puede hacerse en un único sentido: de izquierda a
derecha (de principio a fin). Las listas que pueden recorrerse en ambas direcciones se denominan listas
doblemente enlazadas. En estas, cada nodo consta del campo información y dos campos de enlace: uno
apunta al nodo anterior de la lista y el otro al nodo siguiente; por tanto, ocupa más memoria que una
lista enlazada simple para una misma cantidad de información. Para poder realizar recorridos en los
dos sentidos, la lista puede llevar asociados dos punteros externos que identifiquen la CABECERA y
el FINAL de la lista respectivamente.

Puntero Puntero
Información
Anterior Siguiente
Nodo de lista doble

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 168

Cabecera Lista Doblemente enlazada Fin

La variable CABECERA y el puntero SIGUIENTE permiten recorrer la lista en sentido normal


y la variable FINAL y el puntero ANTERIOR permiten recorrerla en sentido inverso. Para una lista
doble siempre se cumple que: SIGUIENTE(ANTERIOR(NODO)) = NODO

La estructura del nodo en lenguaje C es la siguiente:


typedef int TInformacion;
struct Nodo
{ TInformacion Dato;
struct Nodo *Anterior;
struct Nodo *Siguiente;
};
typedef struct Nodo *TLista;
void main()
{ TLista Cabecera, Final;
...
}

También puede implementarse una lista doblemente enlazada circular, que consiste en que los
enlaces siguiente y anterior del último y primer nodo de la lista respectivamente no apunten a NULL,
si no que apunten uno al último nodo y el otro al primer nodo de la lista. En esta lista sólo es necesario
un puntero externo para identificar la lista.

Cabecera Lista doblemente enlazada circular

4.1.- Operaciones sobre listas doblemente enlazadas

4.1.1.- Funciones de inicializar, vacía, crear un nuevo nodo.


void Inicializa(TLista *Cabecera, TLista *Final)
{ *Cabecera= *Final= NULL;
}
int Vacia(TLista Cabecera) //Puede pasarse ‘Cabecera’, ‘Final’ o ambos
{ return Cabecera == NULL;
}
Tlista NuevoNodo(TInformacion Info)
{ TLista Nuevo= (TLista) malloc(sizeof(Nodo));
Nuevo->Dato= Info;
return Nuevo;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 169

4.1.2.- Insertar en lista doble.

Se dispone de varias formas de inserción en una lista doblemente enlazada dependiendo del
lugar en el que haya que insertar el nuevo nodo. Las diferentes maneras se ven a continuación.

4.1.2.1.- Insertar al principio de la lista.

· Si la lista está vacía: insertamos el primer nodo de la lista. CABECERA y FINAL apuntan
al nuevo nodo y los enlaces anterior y siguiente a NULL.

Dato 1 Cabecera= Nuevo ;


Cabecera Final Final= Nuevo ;
Nuevo->Anterior= NULL ;
Nuevo->Siguiente= NULL

Lista vacía Cabecera Final

· Si la lista no está vacía: el enlace Nuevo.Anterior apunta a NULL, el Nuevo.Siguiente a la


cabecera, el Cabecera.Anterior y Cabecera a Nuevo. Esta última asignación cambia el
puntero Cabecera al nuevo nodo, convirtiéndose éste en el primer nodo de la lista.

(1) (2) (1) Nuevo->Anterior= NULL;


Dato 1 (2) Nuevo->Siguiente= Cab;
Dato 2
(3) (3) Cab->Anterior= Nuevo;
(4) Cab= Nuevo;
(4)

Nuevo Cabecera Final

void InsertarAlPrincipio(TLista *Cabecera, TLista *Fina, TInformacion Info)


{ TLista Nuevo= NuevoNodo(Info);
if (*Cabecera == NULL)
{ *Cabecera= *Final= Nuevo;
Nuevo->Anterior= Nuevo->Siguiente= NULL;
}
else
{ Nuevo->Anterior= NULL;
Nuevo->Siguiente= *Cabecera;
(*Cabecera)->Anterior= Nuevo;
*Cabecera= Nuevo;
}
}

4.1.2.2.- Insertar al final de la lista.

· Si la lista está vacía: Insertamos el primer nodo de la lista. CABECERA y FINAL apuntan
al nuevo nodo y los enlaces a NULL. Es el mismo proceso que la inserción al principio.

· Si la lista no está vacía: El enlace Nuevo.Siguiente apunta a NULL, el Nuevo.Anterior al


Final, el Final.Siguiente y Siguiente a Nuevo.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 170

(3) (1) (1) Nuevo->Siguiente= NULL;


(2) Nuevo->Anterior= Fin;
Dato 1 Dato 2
(2) (3) Fin->Siguinete= Nuevo;
(4) Fin= Nuevo;
(4)
Cabecera Final Nuevo

void InsertarAlFinal(TLista *Cabecera, TLista *Final, TInformacion Info)


{ TLista Nuevo= NuevoNodo(Info);
if (*Final == NULL)
{ *Cabecera= *Final= Nuevo;
Nuevo->Anterior= Nuevo->Siguiente= NULL;
}
else
{ Nuevo->Siguiente= NULL;
Nuevo->Anterior= *Final;
(*Final)->Siguiente= Nuevo;
*Final= Nuevo;
}
}

4.1.2.3.- Insertar entre dos nodos. Detrás del apuntado.

· Si la lista está vacía o el lugar donde debemos insertar no viene dado o es el último
nodo de la lista insertamos al final de la lista.

· Si la lista no está vacía: El enlace Nuevo.Anterior apunta a donde apunta Posición;


Nuevo.Siguiente a donde apunta Posición.Siguiente; Posición.Siguiente.Anterior y
Posición.Siguiente a donde apunta el nuevo nodo.

(1) Nuevo->Ant= Pos;


(2) Nuevo->Sig= Pos->Sig;
Dato 1 Dato 2 (3) Pos->Sig->Ant= Nuevo;
(4) Pos->Sig= Nuevo;
(4) (3)
Cabecera Posicion Final

(1) Dato 3 (2)

Nuevo

void InsertarMedioDetras(TLista *Cabecera, TLista *Final, TLista Posicion,


TInformacion Info)
{ TLista Nuevo;
if (Vacia(*Cabecera) || Posicion == NULL || Posicion->Siguiente == NULL)
InsertarAlFinal(Cabecera, Final, Info);
else
{ Nuevo= NuevoNodo(Info);
Nuevo->Anterior= Posicion;
Nuevo->Siguiente= Posicion->Siguiente;
Posicion->Siguiente->Anterior= Nuevo;
Posicion->Siguiente= Nuevo;
}
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 171

4.1.2.4.- Insertar entre dos nodos. Delante del apuntado.

· Si la lista está vacía o el lugar donde debemos insertar no viene dado o es el último
nodo de la lista insertamos al principio de la lista.

· Si la lista no está vacía: El enlace Nuevo.Anterior apunta a la dirección apuntada por


Posición.Anterior, el enlace Nuevo.Siguiente a donde apunta Posicion,
Posición.Anterior.Siguiente y Posición.Anterior a donde Nuevo.

(1) Nuevo->Ant= Pos->Ant;


(2) Nuevo->Sig= Pos;
Dato 1 Dato 2 (3) Pos->Ant->Sig= Nuevo;
(4) Pos->Ant= Nuevo;
(3) (4)
Cabecera Posición, Final

(1) Dato 3 (2)

Nuevo

void InsertarMedioDelante(TLista *Cabecera, TLista *Final, TLista Posicion,


TInformacion Iinfo)
{ TLista Nuevo;
if (Vacia(*Cabecera) || Posicion == NULL || Posicion->Anterior == NULL)
InsertarAlPrincipio(Cabecera, Final, Info);
else
{ Nuevo= NuevoNodo(Info);
Nuevo->Anterior= Posicion->Anterior;
Nuevo->Siguiente= Posicion;
Posicion->Anterior->Siguiente= Nuevo;
Posicion->Anterior= Nuevo;
}
}

4.1.3.- Borrar un nodo de la lista.

La operación de borrado de un nodo de una lista doble, igual que en operaciones borrado
anteriores, requiere que se conozca la dirección que ocupa el nodo que se desea eliminar. Los pasos
para eliminar un nodo son los siguientes:

· Buscar el elemento en la lista. Se necesita un puntero que recorre la lista buscando la


información. No son necesarios dos punteros dado que disponemos de los dos enlaces del
nodo, uno al nodo anterior y el otro a su siguiente.

· Si el elemento se ha encontrado, se pueden dar las siguientes posibilidades:


· Si el único nodo. Inicializar la lista.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 172

(3) Dato 1 (1) Cabecera= NULL;


(2) Final= NULL;
(3) free(Auxiliar);
(1) (2)

Cabecera, Auxiliar, Final

· Si es el primero de la lista. Borrar el primer nodo de la lista y desplazar la cabecera al


segundo.

(2) (1) Cab= Cab->Siguiente;


(2) Cab->Anterior= NULL;
Dato 1 Dato 2 (3) free(Auxiliar);

(1)
Auxiliar Cabecera Final

· Es el último de la lista. Borrar el último nodo y desplazar la cabecera al penúltimo.

(2) (1) Fin= Fin->Anterior ;


(2) Fin->Siguiente= NULL ;
Dato 2 Dato 1 (3) free(Auxiliar) ;

(1)
Cabecera Final Auxiliar

· Es un nodo intermedio. Enlazar el nodo anterior con siguiente del que hay que borrar.

Dato 1 Dato 2 Dato 3

Cabecera Auxiliar Final


(1) Aux->Ant->Sig= Aux->Sig ;
(2) Aux->Sig>Ant= Aux->Ant ;
(3) free(Auxiliar) ;

· Liberar la memoria ocupada por el nodo que contiene la información que queremos
eliminar.
void Borrar(TLista *Cabecera, TLista *Final, TInformacion Info)
{ TLista Auxiliar= *Cabecera;
int Encontrado= 0;
while (!Encontrado && Auxiliar != NULL)
if (Auxiliar->Dato == Info)
Encontrado= 1;
else
Auxiliar= Auxiliar->Siguiente;

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 173

if (Encontrado)
{ if (*Cabecera == *Final) // Si sólo hay un nodo
Inicializa(Cabecera, Final);
else if(auxiliar == *cab) // Es el primero de la lista
{ *Cabecera= (*Cabecera)->Siguiente;
(*Cabecera)->Anterior= NULL;
}
else if(Auxiliar == *Final) // Es el último de la lista
{ *Final= (*Final)->Anterior;
(*Final)->Siguiente= NULL;
}
else // Es un nodo intermedio
{ Auxiliar->Anterior->Siguiente= Auxiliar->Siguiente;
Auxiliar->Siguiente->Anterior= Auxiliar->Anterior;
}
free(Auxiliar);
}
}

4.1.4.- Recorrido de la lista.

El recorrido de la lista puede realizarse en los dos sentidos: de izquierda a derecha usando el
puntero cabecera y de derecha a izquierda usando el puntero final.
// Recorrido normal; de principio a fin. Usamos el enlace SIGUIENTE
void VisualizarID(TLista Lista)
{ while(Lista != NULL)
{ printf("%d\t", Lista->Dato);
Lista= Lista->Siguiente;
}
}

// Recorrido inverso; de final a principio. Usamos el enlace ANTERIOR


void VisualizarDI(TLista Lista)
{ while(Lista != NULL)
{ printf("%d\t", lista->dato);
Lista= Lista->Anterior;
}
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 174

5.- Pilas.
Una pila es una estructura dinámica lineal de datos en la que los elementos sólo pueden ser
accedidos por un extremo, cima de la pila. Esto quiere decir que el último elemento que se añade es el
primero que se elimina, por tanto, de acuerdo con este concepto las pilas son llamadas "último en
entrar, primero en salir" (LIFO, Last Input, First Output).

Dato 3
Dato 3 Dato 2 Dato 1
Dato 2
Dato1
Cima
Representación interna y gráfica de una pila.

Esta característica de que los añadidos y extracciones de elementos de una estructura se


realicen sólo por un extremo es lo que determina el que la estructura sea una pila. Así, puede definirse
una pila como una secuencia de elementos (e1, e2, ..., eN), N>=0, en la que las operaciones sobre sus
elementos sólo se realizan por uno de sus extremos; este extremo se denomina cima de la pila.

Este tipo de estructura de datos suele usarse cuando se quiere recordar una secuencia de objetos
o sucesos en orden inverso al que sucedieron. Una pila queda definida con sus atributos y las
operaciones que se pueden realizar con los mismos:

· Información almacenada en los nodos.


· Puntero externo que indica la cima de la pila.
· Creación de una pila vacía. Pone la cima apuntando a NULL.
· Poner una información en la cima de la pila. Inserta un nodo al comienzo de la pila; este apunta
a la cima actual de la pila convirtiéndose así en la nueva cima. Esta operación se denomina
habitualmente como ‘Push’.
· Tomar la información almacenada en la cima de la pila. Da la información almacenada en la
cima de la pila. Esta operación se denomina habitualmente como ‘Top’.
· Eliminar la información almacenada en la cima de la pila. Extrae el nodo situado en la cima de
la pila y libera el espacio ocupado por este. La nueva cima de la pila es el siguiente nodo de la
misma. Esta operación se denomina habitualmente como ‘Pop’.

IMPLEMENTACIÓN DE UNA PILA DE ENTEROS.

//Declaración de pila.
typedef int TInformacion;
struct Pila
{ TInformacion Dato;
struct Pila *Siguiente;
};
typedef struct Pila *TPila;
//Crea una pila vacía.
void Crear(TPila *Cima)
{ *Cima = NULL;
}
//Comprueba si la pila está o no vacía.
int Vacia(TPila Cima)
{ return Cima == NULL;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 175

void Push(TPila *Cima, TInformacion Info)


{ TPila Nodo= (TPila) malloc(sizeof(Pila));
Nodo->Dato= Info;
Nodo->Siguiente= *Cima;
*Cima= Nodo;
}
TInformacion Pop(TPila *Cima)
{ TInformacion Info= -1;
TPila Auxiliar= *Cima;
if (!Vacia(*Cima))
{ Info= (*Cima)->Dato;
*Cima= (*Cima)->Siguiente;
free(Auxiliar);
}
return Info;
}
*/
TInformacion Top(TPila Cima)
{ TInformacion Info= -1;
if (!Vacia(Cima))
Info= Cima->Dato;
return Info;
}

6.- Colas.
Una cola es una estructura dinámica lineal de datos en la que los elementos sólo pueden ser
introducidos por un extremo, final de la cola, y extraídos por el extremo contrario, frente de la cola.
Esto quiere decir que el primer elemento añadido es el primero que se puede extraer y el último tiene
que aguardar su turno, por tanto, de acuerdo con este concepto las colas son llamadas "primero en
entrar, primero en salir" (FIFO, First Input, First Output).

Dato 1 Dato 2 Dato 3 Dato 3 Dato 2 Dato 1

Frente Final

Representación interna y gráfica de una pila.

Esta característica de que los añadidos y extracciones de elementos de una estructura se


realicen por los extremos opuestos es lo que determina el que la estructura sea una cola. Así, puede
definirse una cola como una secuencia de elementos (e1, e2, ..., eN), N>=0, en la que las operaciones
sobre sus elementos se realizan por los extremos opuestos; por un extremo las extracciones,
denominado frente de la cola, y por el otro las inserciones, denominado final de la cola.

Este tipo de estructura de datos suele usarse cuando se quiere recordar una secuencia de objetos
o sucesos en el mismo orden al que sucedieron. Una cola queda definida con sus atributos y las
operaciones que se pueden realizar con los mismos:

· Información almacenada en los nodos.


· Se necesitan dos punteros externos: uno al principio de la cola, para extraer, y otro al final de la
misma, para insertar; estos son el frente y el Final respectivamente.
· Creación de una cola vacía. Pone el frente y el final apuntando a NULL.
· Poner una información en el final de la cola. Inserta un nodo al final de la cola; este apunta a
NULL, el final de la cola le apunta a él y pasa a ser el final de la cola.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 176

· Tomar la información almacenada en el frente de la cola. Da la información almacenada en el


frente de la cola.
· Eliminar la información almacenada en el frente de la cola. Extrae el nodo situado en el frente
de la cola y libera el espacio ocupado por este. El nuevo frente de la cola es el siguiente nodo
de la misma.

IMPLEMENTACIÓN DE UNA COLA DE ENTEROS.


typedef int TInformacion;
struct Nodo
{ TInformacion Dato;
struct Nodo *Siguiente;
};
typedef struct Nodo *TNodo;
struct Cola
{ struct Nodo *Frente;
struct Nodo *Final;
};
typedef struct Cola TCola;
void Inicializa(TCola *Cola)
{ Cola->Frente= Cola->Final= NULL;
}
int Vacia(tcola cola)
{ return Cola.Frente == NULL;
}
void Insertar(tcola *cola, tinformacion infor)
{ TNodo Nuevo= (TNodo) malloc(sizeof(Nodo));
Nuevo->Dato= Info;
Nuevo->Siguiente= NULL;
if (Vacia(*Cola))
Cola->Frente= Nuevo;
else
Cola->Final->Siguiente= Nuevo;
Cola->Final= Nuevo;
}
TInformacion Tomar(TCola Cola)
{ TInformacion Info= -1;
if(!Vacia(Cola))
Info= Cola.Frente->Dato;
return Info;
}
TInformacion Extraer(TCola *Cola)
{ TNodo Auxiliar;
TInformacion Info= -1;
if(!Vacia(*Cola))
{ Auxiliar= Cola->Frente;
Info= Cola->Frente->Dato;
Cola->Frente= Cola->Frente->Siguiente;
free(Auxiliar);
}
return Info;
}

Ejercicios:

1. Dado un archivo de texto con una frase, comprobar si dicha frase es un palíndromo. La frase
puede contener espacios, mayúsculas, minúsculas, etc. Usar una pila y una cola.

2. El director de un hotel desea registrar el nombre de cada cliente en el orden de llegada a su


hotel, junto con el número de la habitación que ocupa. También desea disponer en cualquier
momento de un listado de clientes por orden alfabético. Solucionar el problema del director del
hotel usando una única lista enlazada.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 177

Pérez 10 López 18 Hidalgo 13

Orden de entrada Orden alfabético

Funciones necesarias :

a) Definición de los tipos de datos.


b) Inserción de un cliente en la lista enlazada que debe estar actualizada en todo momento -
orden de entrada y alfabéticamente-.
c) Listado de cliente por orden de llegada.
d) Listado de clientes ordenados alfabéticamente.
e) Eliminación de un cliente de la lista.

Escribir el programa principal con el siguiente menú:

1. Inicializar la lista.
2. Alta de cliente.
3. Salida de cliente.
4. Listado de clientes por orden de llegada.
5. Listado de clientes por orden alfabético.
6. Fin de la sesión.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 178

16.- ESTRUCTURAS DINÁMICAS NO LINEALES DE DATOS.

1.- Recursividad.
Antes de comenzar a estudiar las estructuras dinámicas no lineales de datos es necesario
conocer el concepto de recursividad dada la estrecha relación que existe entre ambas. Se dice que una
función es recursiva (un elemento que contiene otro de su misma categoría) si forma parte de sí misma,
es decir, si se invoca a sí misma. La recursión es un medio particularmente poderoso en operaciones de
cálculo. Puede ser utilizada en lugar de la repetición o iteración (estructura repetitiva).

El uso de la recursión es idóneo para resolver aquellos problemas que pueden definirse en
forma natural en términos recursivos. Conceptualmente puede resultar más clara la aplicación de una
función recursiva que una iterativa pero su seguimiento es bastante más complejo y los resultados no
son siempre satisfactorios, en términos de velocidad de ejecución y consumo de memoria.

La escritura de una función recursiva es idéntica a la escritura de una no recursiva ; sin


embargo, para evitar que continúe indefinidamente, es preciso incluir una condición de terminación
(igual que en un bucle). Se dispone de dos tipos de recursividad :

· Recursividad directa: si la función se invoca a sí misma. La función A llama a la función A.

· Recursividad indirecta: si una función A tiene una referencia a una función B que, a su vez,
contiene una referencia a la función A.

Cada vez que se activa recursivamente una función, se crea una copia de la variables locales a
la función, incluidos los parámetros formales, de forma que los valores de las variables que tienen
validez son los más recientemente creados.

Ejemplo 1. Cálculo del factorial de un número.

La función factorial se define de la siguiente forma :


n!=1 si n = 0 (0 ! = 1)
n ! = n*(n - 1)*(n - 2)*...*3*2*1 si n > 0

En términos generales podemos transformar las expresiones anteriores como


n!=1 si n= 0 (0 ! = 1)
n ! = n*(n - 1) ! si n > 0

La función FACTORIAL de N expresada en términos recursivos sería :

FACTORIAL = N * FACTORIAL(N - 1)
long factorial(int);
main()
{ int Numero = 3;
printf("%d", factorial(Numero));
}
long factorial(int N)
{ if (N == 0)
return 1;
else
return N * factorial(N - 1);
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 179

El funcionamiento de una función recursiva se realiza almacenando las llamadas pendientes,


junto con sus argumentos, en una pila ; estos se irán procesando en sentido contrario a como se
realizaron las llamadas (LIFO).

Si suponemos que la llamada a la función factorial es factorial(3), el proceso de llamadas hasta


obtener el resultado es el siguiente :

Empieza llamada 1 :
N = 3. N es distinto de 0 ; se ejecuta el else.
Return 3 * (Segunda llamada con N = 2)

Empieza llamada 2 :
N = 2. N es distinto de 0 ; se ejecuta el else.
Return 2 * (Tercera llamada con N = 1)

Empieza llamada 3 :
N = 1. N es distinto de 0 ; se ejecuta el else.
Return 1 * (Cuarta llamada con N = 0)

Empieza llamada 4 :
N = 0. N es igual a 0.
Return 1

Fin de llamada 4 : RETORNA 1


Return 1

Fin de llamada 3 : RETORNA 1


Return 1 * 1

Fin de llamada 2 : RETORNA 2


Return 2 * (1)

Fin de llamada 1 : RETORNA 6


Return 3 * (2 * 1)

Ejemplo 2. Cálculo de la serie de Fibonacci.

La serie es de la forma siguiente : 1, 1, 2, 3, 5, 8, 13, 21, 34, ... y se expresa así :

FIB(1) = 0, FIB(2) = 1
FIB(N) = FIB(N - 1) + FIB(N - 2) para N > 2

Una función recursiva para el cálculo de esta serie es la siguiente :


int Fibonacci(int);
main()
{ int Numero;
for (Numero = 1; Numero < 20; Numero++)
printf("%d\t", Fibonacci(Numero));
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 180

int Fibonacci(int N)
{ if (N == 0)
return 0;
else if (N == 1)
return 1;
else //Cada llamada produce dos más
return (Fibonacci(N - 1) + Fibonacci(N - 2));
}

Esta solución repite los cálculos para varios términos de la sucesión, por tanto, lo mejor es
optar por la solución iterativa. Otra solución recursiva para este ejercicio, ahora imprimiendo la
sucesión, es la siguiente:
void Fibonacci(int T, int P, int U)
{ printf(“%d\n”, P);
if (T > 0)
Fibonacci(T-1, U, U+P);
}

Ejemplo 3. Visualizar la inversa de una cadena.


#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
void palindromo(char *, int);
main()
{ char *cadena= "Esto es una cadena";
palindromo(cadena, strlen(cadena));
getch();
}
void palindromo(char *c, int n)
{ if (n == 0)
putchar(c[0]);
else
{ putchar(c[n]);
palindromo(c, n - 1);
}
}
//También:
void Inversa(char *c)
{ if(*c)
Inversa(c + 1);
//Inversa(++c);
putchar(*c);
}

Ejemplo 4. Producto de dos números.


int Producto(int A, int B)
{ if (A==0 || B==0)
return 0;
else
return A + Producto(A, B-1);
}

Ejemplo 5. Indicar si un número es o no primo.


bool Primo(int N, int D)
{ if(N%D == 0)
return false;
else if(D <= N/2)
return true && Primo(N, D+1);
}
//Llamada: Primo(Numero, 2);

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 181

Ejemplo 6. Las torres de HANOI.

Cuenta la leyenda tibetana que a los sacerdotes del templo de Brahma les fue asignada en la
creación del mundo la siguiente tarea :

Se les dio una plataforma con tres soportes verticales consistentes en tres agujas de diamante. En el
primer soporte había 64 discos de oro, cada uno de los cuales era un poco más pequeño que el de
debajo. Los sacerdotes debían mover los discos de oro desde el primer soporte al tercero, sujetos a la
condición de que sólo se podía mover un disco cada vez, y que no estaba permitido colocar un disco
colocar un disco encima de otro más pequeño. En el momento en que los sacerdotes acabaran de
mover los 64 discos significaría que había llegado el fin del mundo.

La solución se da de la siguiente forma :

1.- Mover los discos desde la barra inicial hasta la barra final usando la barra auxiliar.
MoverTorre(n, 1, 2, 3)
1.- Pasar los n - 1 discos superiores de la barra 1 a la 2.
MoverTorre(n - 1, 1, 3, 2)
2.- Pasar el disco inferior de la barra 1 a la 3.
printf("Desde: %d, Hasta: %d\n", Uno, Tres);
3.- Pasar los n - 1 discos de la barra 2 a la 3.
MoverTorre(n - 1, 2, 1, 3)
#include <stdio.h>
#include <conio.h>
void MoverTorre(int N, int Uno, int Dos, int Tres)
{ if (N > 0)
{ MoverTorre(N - 1, Uno, Tres, Dos);
printf("Desde: %d, Hasta: %d\n", Uno, Tres);
MoverTorre(N - 1, Dos, Uno, Tres);
}
}
void main()
{ int NumDiscos = 3;
MoverTorre(NumDiscos, 1, 2, 3);
getch();
}

Ejercicios de recursividad:
1.- Buscar en una lista.
2.- Insertar al final de una lista.
3.- Insertar en una lista ordenada.
4.- Borrar un elemento de una lista.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 182

2.- Árboles.
2.1.- Definición, elementos y representación de los árboles.
Un árbol es una lista no lineal, formada por un conjunto de elementos del mismo tipo,
jerarquizados del modo siguiente :

· Cada uno de sus elementos tiene un solo antecesor y puede tener un sucesor, varios o
ninguno; es decir, se puede considerar, a su vez, como el origen de un subárbol.
· Existe un elemento llamado raíz, que no tiene ningún antecesor.

Esta estructura de datos se denomina árbol porque se representa en forma de árbol invertido; es
decir, con las raíces situadas en la parte superior del árbol y las ramas descienden hacia las hojas que
están situadas en su zona inferior. En el concepto de árbol entran los siguientes términos:

- Raíz. Es el elemento sin antecesor. De él se derivan los demás.


- Nodo. Es cada uno de los elementos que componen el árbol.
- Antecesor, ascendiente o padre. Es el elemento del que se derivan otros.
- Sucesor, descendiente o hijo. Es el elemento derivado del padre.
- Hoja o nodo terminal. Es el elemento que no tiene ningún sucesor.
- Camino. Es una secuencia de enlaces entre nodos, desde el raíz a un nodo cualquiera..
- Rama. Es el camino que termina en una hoja; es decir, una rama viene dada por la
secuencia de enlaces que hay entre el nodo raíz y una hoja.
- Nivel. Es el número de orden asignado a un nodo. Se determina del modo siguiente:
 El nodo raíz tiene nivel cero.
 Un nodo sucesor tiene el nivel de su antecesor más uno.
- Altura o profundidad. Es el número de nodos que se encuentran en el camino más largo
desde la raíz hasta una hoja. Es decir, es el número de nodos de la rama más larga. Dicho
número es igual a número de nivel del último nodo más uno.

Ejemplo :

B C D
E F G H I J K

L M

A, B, C, ..., M son nodos del árbol. A es el nodo raíz.


B tiene por antecesor a A y por sucesores a E y F.
A, B, E, L es una rama.
F, G, H, I, J, K, L, M son las hojas del árbol.
La profundidad del árbol es 4.
Los niveles son :
nivel 0 : A
nivel 1 : B, C, D
nivel 2 : E, F, G, H, I, J, K
nivel 3 : L, M

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 183

El conjunto de nodos dependientes de B, C y D respectivamente forman tres subárboles, cuyo


origen está en B, C y D.

Existen varios tipos de árboles : se dice que un árbol es n-ario cuando el número máximo de
hijos por nodo es n, siendo n un número entero positivo. En particular estudiaremos el árbol binario,
donde cada nodo puede tener un máximo de dos hijos.

2.2.- Definición de árbol binario y su representación.


Un árbol binario es una lista no lineal, formada por un conjunto de cero o más elementos del
mismo tipo, organizados de siguiente modo :

· Existe un nodo sin ascendientes, denominado raíz.


· Cada nodo puede tener 0, 1 ó 2 descendientes o hijos subárboles, que se denominan
subárbol (descendiente) izquierdo y subárbol (descendiente) derecho.

Se dice que un árbol binario está equilibrado si las alturas de los dos subárboles de cada nodo
son iguales o difieren en una unidad. Se denomina lleno cuando todos sus nodos tienen dos sucesores o
hijos, a excepción de las hojas.

La representación de un árbol binario requiere que cada nodo contenga, además de la


información, dos punteros: uno que enlace con el hijo izquierdo y otro con el hijo derecho.

Puntero al Puntero al
subárbol Información almacenada en el subárbol
izquierdo nodo derecho

typedef int TInformacion;


struct Nodo
{ TInformacion Dato;
struct Nodo *AIz; // Puntero al árbol izquierdo
struct Nodo *ADe; // Puntero al árbol derecho.
};
typedef struct Nodo *TArbol;

Datos

Datos Datos

Datos Datos Datos Datos

Representación gráfica de un árbol binario.

Igual que en las listas lineales, hablamos de operaciones de inserción, borrado, búsqueda, etc.,
como las más usuales de los árboles. Todas son recursivas ya que lo que se tiene a derecha e izquierda

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 184

son árboles binarios. La condición para terminar la profundidad de la recursión es que el nodo sea
terminal, que apunte a NULL.

2.2.1.- Operaciones con árboles binarios.

2.2.1.1. - Creación de un árbol.

void Crear(TArbol *Raiz)


{ *Raiz = NULL;
}

2.2.1.2.- Ver si el árbol está vacío.

int Vacio(TArbol Raiz)


{ return Raiz == NULL;
}

2.2.1.3.- Construcción un árbol binario equilibrado.

Este procedimiento va generando un árbol a medida que se va introduciendo la información. El


árbol estará equilibrado (el número de nodos en el subárbol izquierdo y el número de nodos en el
subárbol derecho difieren como mucho en una unidad) pero no ordenado. La forma recursiva es la
mejor para expresar esta regla de distribución equitativa de un número N de nodos :

1.- Usar un nodo para raíz.


2.- Generar el subárbol izquierdo con ni = n / 2 nodos.
3.- Generar el subárbol derecho con nd = n - ni - 1 nodos.

Esta forma de creación de un árbol no es satisfactoria por que no siempre conocemos el número
de nodos con los que se debe crear.
TArbol Construir(int NumeroNodos)
{ TArbol Arbol;
int NIzda, NDcha, Info;
if (NumeroNodos == 0)
return NULL;
else
{ NIzda= NumeroNodos / 2;
NDcha= NumeroNodos - NIzda - 1;
printf("Valor: ");
scanf("%d", &Info);
Arbol= (TArbol) malloc(sizeof(Nodo));
Arbol->Dato= Info;
Arbol->AIz= Construir(NIzda);
Arbol->ADe= Construir(NDcha);
return Arbol;
}
}

Para visualizar el árbol podemos usar la siguiente función recursiva :


void Imprimir(TArbol Arbol, int Nivel= 0) //DRI
{ int i;
if (!Vacio(Arbol))
{ Imprimir(Arbol->ADe, Nivel + 1);
for (i=1; i<=Nivel;i++) printf("\t");
printf("%d\n", Arbol->Dato);
Imprimir(Arbol->AIz, Nivel + 1);
}
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 185

2.2.1.4.- Recorrido de un árbol.

Una tarea corriente que se realiza con un árbol es ejecutar una determinada operación con cada
uno de los elementos del mismo. Esta tarea se considera como un parámetro de una tarea más general
que es la visita de todos los nodos o recorrido del árbol.

Si se considera la tarea como un proceso secuencial, entonces los nodos individuales se visitan
en un orden específico, y pueden considerarse como organizados según una estructura lineal.

Hay tres órdenes de los elementos que están asociados de forma natural con la estructura de los
árboles. Tal como sucede con la estructura del árbol en sí, estos pueden expresarse de forma recursiva.
Las tres organizaciones son:

Preorden: R, I, D. Visitar la raíz antes que los subárboles.


Inorden: I, R, D. Visitar los nodos de izquierda a derecha.
Postorden: I, D, R. Visitar la raíz después que los subárboles.

Como puede observarse, al hablar de estos tres tipos de recorrido, nos referimos a si
procesamos en primer lugar el nodo central, el de la derecha o el de la izquierda. Por ejemplo, en el
caso del Recorrido en Preorden, una vez procesado el nodo central en que nos encontramos se debe
seguir recorriendo el árbol por la izquierda, pero lo que queda de árbol por la izquierda es
precisamente un árbol binario, por lo que se vuelve a llamar a la función de recorrido para profundizar
por la izquierda y una vez que se acaba de profundizar por esa rama se profundiza por la rama de la
derecha.

2 5

3 4 6

Ejemplo de árbol binario para usar en el recorrido.

El código en C para realizar los tres tipos de recorrido descritos sobre un árbol es el siguiente :

El resultado de aplicar el recorrido en preorden es el siguiente : 1, 2, 3, 4, 5, 6


void Preorden(TArbol Arbol) // RID
{ if (!Vacio(Arbol))
{ printf("%d\t", Arbol->Dato); //TRATAR (Arbol->Dato)
Preorden(Arbol->AIz);
Preorden(Arbol->ADe);
}
}

El resultado de aplicar el recorrido en inorden es el siguiente: 3, 2, 4, 1, 6,5


void Inorden(TArbol Arbol)// IRD
{ if (!Vacio(Arbol))
{ Inorden(Arbol->AIz);
printf("%d\t", Arbol->Dato); //TRATAR (Arbol->Dato)
Inorden(Arbol->ADe);
}
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 186

El resultado de aplicar el recorrido en postorden es el siguiente: 3, 4, 2, 6, 5, 1


void Postorden(TArbol Arbol) // IDR
{ if (!Vacio(Arbol))
{ Postorden(Arbol->AIz);
Postorden(Arbol->ADe);
printf("%d\t", Arbol->Dato); //TRATAR (Arbol->Dato)
}
}

2.2.1.5.- Buscar en árboles ordenados.

Los árboles binarios se usan frecuentemente para representar conjuntos de datos cuyos
elementos se identifican por una clave única. Si el árbol está organizado de tal manera que, para todo
nodo ‘A’, todas las claves de subárbol izquierdo son menores que la clave de ‘A’, y todas aquellas
claves del subárbol derecho de ‘A’, son mayores que la clave de ‘A’, se dice que este árbol es un árbol
de búsqueda.

Puede localizarse una clave arbitraria en un árbol de búsqueda empezando por la raíz, y
avanzando por un camino de forma que la decisión de continuar por el subárbol izquierdo o derecho se
toma en base únicamente al valor de la clave de dicho nodo. Como esta búsqueda sigue un camino
único desde la raíz hasta el nodo deseado, puede programarse fácilmente por medio de iteración
aunque también, por supuesto, lo hacemos de forma recursiva. El código en C de la función no
recursiva es el siguiente :
TArbol BuscarOrdenado(TArbol Arbol, TInformacion Info)
{ int Encontrado = 0;
while (Arbol != NULL && !Encontrado)
if (Arbol->Dato == Info)
Encontrado= 1;
else if (Arbol->Dato > Info)
Arbol= Arbol->AIz;
else
Arbol= Arbol->ADe;
if (Encontrado) //Sólo hace falta poner RETURN arbol
return Arbol;
else
return NULL;
}

La función recursiva para el mismo propósito es la siguiente :


TArbol BuscarOrdenadoRecursivo(TArbol Arbol, TInformacion Info)
{ if (Arbol == NULL)
return NULL;
else if (Arbol->Dato == Info)
return Arbol;
else if (Arbol->Dato > Info)
BuscarOrdenadoRecursivo(Arbol->AIz, Info); // return ...
else
BuscarOrdenadoRecursivo(Arbol->ADe, Info); // return ...
}

Ejercicios:

1.- Función que decida si un valor, dado como parámetro, está o no en el árbol.
2.- Función que retorne el número de nodos que tiene un árbol.
3.- Función que calcule la profundidad de un árbol (número de nodos de la rama más larga).

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 187

4.- Función que retorne el número de hojas de un árbol.


5.- Función que calcule el número de nodos que hay en un nivel dado.

2.2.1.6.- Inserción en árboles ordenados.

Las aplicaciones en que se construye un conjunto de datos, y después éste no se modifica, no


son buenas para apreciar las capacidades de asignación dinámica. Son más apropiadas aquellas en las
que la estructura del árbol varía (crece o disminuye) durante la ejecución del programa.

El algoritmo, además de insertar un nuevo nodo en el árbol, realiza una búsqueda previa de la
información para localizar la rama donde debe ser insertada. No crea un árbol equilibrado.
void InsertarBusqueda(TArbol *Arbol, TInformacion Info)
{ if (Vacio(*Arbol))
{ *Arbol= (TArbol) malloc(sizeof(Nodo));
(*Arbol)->Dato= Info;
(*Arbol)->AIz = (*Arbol)->ADe= NULL;
}
else if (Info < (*Arbol)->Dato)
InsertarBusqueda(&(*Arbol)->AIz, Info);
else if (Info > (*Arbol)->Dato)
InsertarBusqueda(&(*Arbol)->ADe, Info);
else
(*Arbol)->Dato= Info; //Modifica la información del nodo
}

En el algoritmo no se tienen en cuenta los valores repetidos de las claves, sólo se modifica el
contenido del resto de campos de la estructura TInformacion. Esto es así por que una clave debe
identificar plenamente el conjunto de datos asociados a ella. Podemos optar por añadir un campo
contador a la estructura de datos que nos indique el número de apariciones de dicha clave o bien, a
pesar de todo, por añadir claves al árbol repetidas tratando de la misma forma Info == (*Arbol)->Dato
que Info > (*Arbol)->Dato.

El puntero externo árbol se pasa por referencia para que cuando se presente el caso de inserción
se pueda asignar un nuevo valor a la variable que contenía el valor NULL ; así los cambios se
reflejarán en la función que realizó la llamada.

2.2.1.7.- Borrado en árboles ordenados.

La tarea consiste en borrar el nodo con clave X de un árbol que tiene las claves ordenadas. Es
fácil si el elemento a borrar es un nodo terminal o tiene un único descendiente. La dificultad está en el
borrado de un elemento que tiene dos descendientes. En esta situación, el elemento a borrar debe ser
reemplazado, bien por el elemento más a la derecha del subárbol izquierdo, o bien por el elemento más
a la izquierda del subárbol derecho, los dos como máximo con un único descendiente. Aquí optamos
por reemplazar con la información almacenada en el nodo situado más a la derecha del subárbol
izquierdo.

En la función borrar se distinguen tres casos :

1.- No hay ningún elemento con clave igual a X.


2.- El elemento con clave X tiene un descendiente como máximo.
3.- El elemento con clave X tiene dos descendientes.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 188

void BorrarOrdenado(TArbol *Arbol, TInformacion Info)


{ TArbol Auxiliar;
if (!Vacio(*Arbol)) // El elemento no está en el árbol
if (Info < (*Arbol)->Dato)
BorrarOrdenado(&(*Arbol)->AIz, Info);
else if (Info > (*Arbol)->Dato)
BorrarOrdenado(&(*Arbol)->ADe, Info);
else
{ Auxiliar= *Arbol;
if (Vacio(Auxiliar->ADe))
*Arbol= Auxiliar->AIz;
else if (Vacio(Auxiliar->AIz))
*Arbol= Auxiliar->ADe;
else
Elimina(&Auxiliar->AIz, &Auxiliar);
free(Auxiliar);
}
}

La función anterior borrar se apoya en la función eliminar, que se activa sólo en el tercer caso,
y cuya misión consiste en descender a lo largo de la rama más a la derecha del subárbol izquierdo del
elemento a borrar, y reemplaza la información de este por la encontrada en el nodo más a la derecha en
ese subárbol izquierdo.
void Elimina(TArbol *Arbol, TArbol *Nodo)
{ if (!Vacio((*Arbol)->ADe))
Elimina(&(*Arbol)->ADe, Nodo);
else
{ (*Nodo)->Dato= (*Arbol)->Dato;
*Nodo= *Arbol;
*Arbol= (*Arbol)->AIz;
}
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 189

17.- LENGUAJE C++ COMO UN C MEJORADO.

1.- Extensión del nombre de los ficheros.


La extensión de los ficheros en lenguaje C++ es .cpp (de C Plus Plus). Es importante utilizarla
pues determina que se invoque al compilador de C++ o al compilador de C (en caso de utilizar la
extensión .c).

2.- Comentarios.
En C los comentarios empiezan por los caracteres “/*” y terminan por “*/”. Pueden
comprender varias líneas y estar distribuidos de cualquier forma. En C++ se admite el mismo tipo de
comentario y además también es un comentario todo aquello que esté a continuación de los caracteres
“//” hasta el fin de línea.

3.- Declaración simplificada de variables estructura y enum.


No es necesario anteponer la palabra clave struct o enum para declarar una variable del tipo
estructura o enumerada respectivamente definida por el usuario.
struct TRegistro
{ int Codigo;
...
};
struct TRegistro Registro; //Declaración en lenguaje C.
Tregistro Registro; // Declaración en lenguaje C++.

4.- Flexibilidad en la declaración de variables.


La declaración de variables en C++ es similar a la de C. Sin embargo, en C tienen que ser
declaradas al comienzo del bloque, siempre delante de cualquier sentencia ejecutable, y en C++
pueden ser declaradas en cualquier lugar de un bloque. Esto, obviamente, afecta a la visibilidad: una
variable declarada dentro de un bloque tiene una visibilidad restringida a ese bloque, desde el punto de
definición hasta el final del bloque.

5.- Operador de resolución de visibilidad.


El operador de resolución de visibilidad “::” permite acceder a variables globales, nunca a
variables locales, con un ámbito solapado con el de otra variable local de igual nombre; ésta última
oculta a la primera.
int x;
main()
{ int x= 3;
x= 5; // Afecta a la variable local.
::x= 8; // Afecta a la variable global.
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 190

6.- Especificador const para variables.


En C++ el especificador const se puede utilizar tanto con punteros como con variables. Estas variables
no son constantes simbólicas aunque tienen cierta similitud; si una variable se declara como const se
tiene la garantía de que su valor no va a cambiar durante la ejecución del programa, es detectado por el
compilador. La diferencia con respecto a las constantes simbólicas es que las variables const están
sometidas a las mismas reglas de duración y visibilidad que el resto de variables.
const i= 3;
i++; // Error de compilación. No permitido.

La constantes declaradas así pueden utilizarse para definir la longitud de vectores en la


declaración de éste, cosa que no está permitida en C con la declaración de una variable:
const int TAMANO= 5;
char Array[TAMANO];

Esta declaración no está permitida en lenguaje C. De cualquier forma, el array debe tener una
longitud conocida en tiempo de compilación a no ser que utilicemos reserva explícita de memoria.

7.- Especificador const para punteros.


Hay que distinguir entre dos formas de aplicar el cualificador const a los punteros:

1. Un puntero variable apuntando a una variable constante. Un puntero a una variable const no
puede modificar el valor de esa variable (el compilador lo detecta) pero el puntero no tiene por qué
apuntar siempre a esa variable.
const char *Nombre= “Antonio”;
Nombre= “Pedro”; // Permitido. El puntero cambia de dirección.
Nombre[0]= ‘M’; // No permitido. No puede cambiar su valor.

2. Un puntero constante apuntando a una variable cualquiera. Un puntero const apunta siempre a
la misma posición de memoria pero el valor de la variable se puede modificar.
char* const Nombre= “Antonio”;
Nombre= “Pedro”; // No Permitido. El puntero es constante.
Nombre[0]= ‘M’; // Permitido. Puede cambiar su valor.

8.- Conversiones explícitas de tipo. moldeo.


En lenguaje C la conversión explícita de tipo se realiza anteponiendo a la expresión el tipo de
dato al que la queremos convertir encerrado entre paréntesis.
double x, y;
return (int) x/y;

En lenguaje C++ la conversión de tipo es similar a la utilizada en C pero encerrando entre


paréntesis el valor que deseamos convertir.
double x, y;
return int (x)/y;

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 191

9.- Especificador inline para funciones.


El lenguaje C++ permite sustituir, en tiempo de compilación, la llamada a una función por el
código correspondiente en el punto en que se realiza la llamada. De esta manera la ejecución es más
rápida, pues no se pierde tiempo transfiriendo el control y realizando conversiones de parámetros.
Como contrapartida, el programa resultante ocupa más memoria, pues es posible que el código de una
misma función se introduzca muchas veces, con las repeticiones consiguientes. Las funciones inline
resultan interesantes en el caso de funciones muy breves, que aparecen en pocas líneas de código pero
que se ejecutan muchas veces (en un bucle for, por ejemplo). Existen 2 formas de definirlas:

1. Una primera forma de utilizar funciones inline es anteponer dicha palabra en la declaración
de la función, como por ejemplo:
inline void permutar(int &a, int &b);

2. Otra forma de utilizar funciones inline sin necesidad de utilizar esta palabra es introducir el
código de la función en la declaración (convirtiéndose de esta manera en definición),
poniéndolo entre llaves { } a continuación de ésta. Este segundo procedimiento suele
utilizarse por medio de ficheros cabecera (*.h), que se incluyen en todos los ficheros fuente
que tienen que tener acceso al código de las funciones inline. Considérese el siguiente
ejemplo consistente en una declaración seguida de la definición:
void permutar (int *i, int *j) { int temp; temp = *i; *i = *j; *j = temp; }

En cualquier caso, la directiva inline es sólo una recomendación al compilador, y éste puede
desestimarla por diversas razones, como coste de memoria excesivo, etc.

10.- Sobrecarga de funciones.


La sobrecarga (overload) de funciones consiste en declarar y definir varias funciones distintas
que tienen un mismo nombre. Dichas funciones se definen de forma diferente. En el momento de la
ejecución se llama a una u otra función dependiendo del número y/o tipo de los argumentos actuales de
la llamada a la función. Por ejemplo, se pueden definir varias funciones para calcular el valor absoluto
de una variable, todas con el mismo nombre abs(), pero cada una aceptando un tipo de argumento
diferente y con un valor de retorno diferente.

La sobrecarga de funciones no admite funciones que difieran sólo en el tipo del valor de
retorno, pero con el mismo número y tipo de argumentos. De hecho, el valor de retorno no influye en
la determinación de la función que es llamada; sólo influyen el número y tipo de los argumentos.
Tampoco se admite que la diferencia sea el que en una función un argumento se pasa por valor y en
otra función ese argumento se pasa por referencia.
#include <math.h>
int absoluto(int x) { return abs(x); }
double absoluto(double x) { return fabs(x); }
long double absoluto(long double x) { return fabsl(x); }
long absoluto(long int x) { return labs(x);}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 192

11.- Valores por defecto de parámetros de una función.


En ANSI C se espera encontrar una correspondencia biunívoca entre la lista de argumentos
actuales (llamada) y la lista de argumentos formales (declaración y definición) de una función. Por
ejemplo, supóngase la siguiente declaración de una función para calcular el módulo de un vector x con
n elementos:
double modulo(double x[], int n); //Divisor común

En C esta función tiene que ser necesariamente llamada con dos argumentos actuales que se
corresponden con los dos argumentos formales de la declaración. En C++ la situación es diferente pues
se pueden definir valores por defecto para todos o algunos de los argumentos formales en la
declaración de la función (si no hay prototipo se hace en la definición). Después, en la llamada, en el
caso de que algún argumento esté ausente de la lista de argumentos actuales, se toma el valor asignado
por defecto a ese argumento. Por ejemplo, la función modulo() podía haberse declarado del siguiente
modo:
double modulo(double x[], int n=3);

La función modulo() puede ser llamada en C++ de las formas siguientes:


v = modulo(x, n);
v = modulo(x);

En el segundo caso se utiliza el valor por defecto n=3 incluido en la declaración.

En C++ se exige que todos los argumentos con valores por defecto estén al final de la lista de
argumentos. En la llamada a la función pueden omitirse alguno o algunos de los últimos argumentos
de la lista. Si se omite un argumento deben de omitirse todos aquellos que se encuentren detrás de él.
Ej.: void Particion(FILE* f1, FILE* f2, int Secuencia= 1);

12.- Variables de tipo referencia.


C++ ofrece una nueva forma de pasar argumentos por referencia a una función, que no obliga
a utilizar –dentro de la función– el operador indirección (*) para acceder al valor de la variable que se
quiere modificar. Esto se hace por medio de un nuevo tipo de dato –que no existe en C– llamado tipo
referencia. Las variables referencia se declaran por medio del carácter (&). Por lo demás, son
variables normales que contienen un valor numérico o alfanumérico. Por ejemplo la función
permutar() utilizando variables referencia en lugar de punteros seria como sigue:
#include <stdio.h>
void permutar(int &a, int &b); // los argumentos son referencias
void main(void)
{ int i = 1, j = 2;
printf("\ni = %d, j = %d", i, j);
permutar(i, j); // los argumentos no llevan (*) ni (&)
printf("\ni = %d, j = %d", i, j);
}
void permutar(int &a, int &b) // los argumentos son referencias
{ int temp;
temp = a; // no hace falta utilizar
a = b; // el operador indirección (*)
b = temp;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 193

Este programa tiene la ventaja de que no hay que utilizar el operador indirección dentro de la
función permutar(). C++ permite pasar argumentos por referencia sin más que anteponer el carácter
(&) a los argumentos correspondientes, tanto en el prototipo como en el encabezamiento de la
definición. En la llamada a la función los argumentos se ponen directamente, sin anteponerles ningún
carácter u operador.

Las variables de tipo referencia se declaran con el operador (&) y deben ser inicializadas a
otra variable o a un valor numérico. Por ejemplo:
int i=2;
int& iref = i; // declaración de referencia válida
int& jref; // declaración de referencia no válida

La variable i es una variable normal tipo int. La variable iref es una variable referencia que se
asocia con i, en el sentido de que ambas variables comparten la misma posición de memoria: si se
modifica i se modifica iref, y viceversa. En este sentido, iref es un alias de i. La diferencia con un
puntero que apuntase a la dirección de i está en que, una vez que una variable referencia ha sido
declarada como alias de i no puede ser declarada como alias de otra variable. Siempre se referirá a la
misma posición de memoria. En la función permutar() los argumentos formales, que son referencias,
se inicializan y se convierten en alias de los argumentos actuales, que son variables ordinarias.

Otros ejemplos son los siguientes:


// Insertar en una lista ordenada.
void insertaOrden(tLista &lista, tInfo info)
{ if (Vacia(lista) || info < lista->dato)
lista= new nodo(lista, info);
else
insertaOrden(lista->siguiente, info);
}

// Borrar todos los nodos de una lista


void destruyeLista(tLista& lista)
{ if(!Vacia(lista))
{ destruyeLista(lista->siguiente);
delelte lista;
lista= NULL;
}
}

El principal uso de las variables referencia es como valor de retorno o argumentos de


funciones. Los arrays no pueden ser declarados como variables referencia, porque ya tienen una forma
propia y natural de ser pasados como argumentos a una función.

El que una función tenga como valor de retorno una variable tipo referencia permite utilizarla
de una manera un poco singular. Considérese el siguiente ejemplo:
int& maxref(int& a, int& b)
{ if (a >= b)
return a;
else
return b;
}

La función maxref() tiene referencias como valor de retorno y como argumentos. Esto permite
utilizarla, por ejemplo, del siguiente modo:

maxref(i, j) = 0;

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 194

Ésta es una forma un poco extraña de utilizar una función: la llamada está a la izquierda del
operador de asignación, en vez de aparecer a la derecha en una expresión aritmética o de otro tipo. El
resultado de esta llamada también es un poco extraño: el valor de retorno es una referencia, esto es un
alias del argumento de valor máximo. Cuando la llamada a la función se sustituye por su valor de
retorno, el resultado de la sentencia anterior es que la variable pasada como argumento que tiene
mayor valor se hace igual a cero. Este mismo efecto puede conseguirse mediante punteros, pero con
referencias resulta mucho más sencillo.

En C++ las referencias son muy utilizadas para pasar argumentos a funciones (y como valores
de retorno), no sólo para poderlos modificar dentro de la función, sino también por motivos de
eficiencia, pues es mucho más rápido pasar un puntero o un alias de una variable que una copia del
valor de esa variable. Si además la variable es una estructura, las ventajas de eficiencia son todavía
mucho más palpables.

13.- Operadores new y delete. Gestión dinámica de memoria


Con los operadores new y delete el programador tiene entera libertad para decidir crear o
destruir sus variables cuando las necesite. Una variable creada con el operador new dentro de cualquier
bloque, perdura hasta que es explícitamente borrada con el operador delete.

Un aspecto diferente con la función malloc() es que ésta devuelve un puntero a void (*void)
que es después convertido al tipo de variable que se desea. Esa conversión se evita con new.

Se puede utilizar el operador new para crear variables de cualquier tipo. New devuelve, en
todos los casos, un puntero a la variable creada. También se pueden crear variables de tipos definidos
por el usuario.
struct usuario {
..........
};
usuario* Un_Usuario;
Un_Usuario = new usuario;

Cuando una variable ya no es necesaria se destruye con el operador delete para poder utilizar la
memoria que estaba ocupando, mediante una instrucción del tipo:
delete p;

La sintaxis de los operadores new y delete es la siguiente:


new TipoDeDato <(ValorInicial)>
new TipoDeDato[Tamaño]
delete <Variable>
delete [ ] <Variable>

Ejemplo que reserva memoria dinámica para un vector de caracteres:


#include <iostream.h>
#include <string.h>
void main()
{ char Nombre[50];
cout << "Introduzca su Nombre:";
cin >> Nombre;
char *CopiaNombre = new char[strlen(Nombre)+1];

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 195

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


cout << CopiaNombre;
delete [] CopiaNombre;
}

Ejemplo que crea una matriz dinámica:


main()
{ const int F= 5;
const int C= 10;
int **matriz;
m= new int*[F];
for(int f= 0; f<F; f++)
m[f]= new int[C];
for(int f= 0; f<F; f++)
delete []m[f];
delete []m[f];
}

14.- Nueva forma de realizar la entrada y salida.


En C++ además de las funciones printf() y scanf() se pueden utilizar los operadores cin y cout.
Para utilizar estos nuevos operadores es necesario incluir la librería iostream.h con la instrucción
#include <iostream.h>. Así en un programa en C habría que hacer algo de este estilo:

char nombre[20];
int num=2;
printf ("Introduzca el nombre del fichero %d: ", num);
scanf (" %s", nombre)

En C++ podría escribirse así:


char nombre[20];
int num=2;
cout << "Introduzca el nombre del fichero " << num << ": ";
cin >> nombre;

Es importante darse cuenta de que ahora ya no hace falta especificar el tipo de dato que va a
ser impreso o leído, asociándolo con un formato determinado. Es el propio programa el que decide el
tipo de dato en tiempo de ejecución. Estos operadores están sobrecargados de tal manera que admiten
tanto los tipos predefinidos como aquellos tipos de datos definidos por el usuario.

15.- Sobrecarga de operadores


Los operadores de C++, al igual que las funciones, pueden ser sobrecargados (overloaded). La
sobrecarga de operadores quiere decir que se pueden redefinir algunos de los operadores existentes
en C++ para que actúen de una determinada manera, definida por el programador, con los objetos de
un mismo tipo estructurado (nunca sobre datos simples). Esto puede ser muy útil por ejemplo, para
definir operaciones matemáticas con elementos tales como vectores y matrices. Así, sobrecargando
adecuadamente los operadores suma (+) y asignación (=), se puede llegar a sumar dos matrices con
una sentencia tan simple como:

C = A + B;

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 196

El objetivo último de la sobrecarga de operadores es simplificar al máximo el código a escribir,


a cambio de complicar algo la definición. Las ventajas de la sobrecarga de operadores terminan cuando
se utiliza de modo que añade complejidad o confusión a los programas. Por ejemplo, aunque esté
permitido por el lenguaje, no se deberá nunca utilizar el operador (-) para multiplicar matrices o el (+)
para imprimir vectores.

La sobrecarga de operadores tiene dos limitaciones teóricas y una práctica:

· Se puede modificar la definición de un operador pero no su gramática, es decir, el número


de operandos sobre los que actúa, la precedencia y la asociatividad.
· Es necesario que al menos un operando sea un objeto de la clase en la que se ha definido el
operador sobrecargado.
· La sobrecarga de operadores puede resultar bastante ineficaz, desde el punto de vista de
utilización de los recursos del ordenador.

Un operador puede estar sobrecargado o redefinido varias veces, de tal manera que actúe de un
modo distinto dependiendo del tipo de objetos que tenga como operandos. Es precisamente el tipo de
los operandos lo que determina qué operador se utiliza en cada caso.

Sintaxis:

<Estructura> operator <operador>(Parámetros);

Ejemplo: tratamiento de números complejos.


#include <iostream.h>
struct Complejo
{ double Real;
double Imaginaria;
};
Complejo Crear(double Real, double Imaginaria);
void Imprimir(const Complejo a);
Complejo Restar(const Complejo a, const Complejo b);
Complejo Sumar(const Complejo a, const Complejo b);
Complejo operator - (const Complejo a, const Complejo b);
Complejo operator + (const Complejo a, const Complejo b);

Complejo Crear(double Real, double Imaginaria)


{ Complejo Temp;
Temp.Real= Real;
Temp.Imaginaria= Imaginaria;
return Temp;
}
void Imprimir(const Complejo a)
{ cout << a.Real << "+" << a.Imaginaria << "i" << endl;
}
Complejo Restar(const Complejo a, const Complejo b)
{ Complejo Temp;
Temp.Real= a.Real - b.Real;
Temp.Imaginaria= a.Imaginaria - b.Imaginaria;
return Temp;
}
Complejo Sumar(const Complejo a, const Complejo b)
{ Complejo Temp;
Temp.Real= a.Real + b.Real;
Temp.Imaginaria= a.Imaginaria + b.Imaginaria;
return Temp;
}
Complejo operator - (const Complejo a, const Complejo b)
{ Complejo Temp;
Temp.Real= a.Real - b.Real;
Temp.Imaginaria= a.Imaginaria - b.Imaginaria;
return Temp;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 197

Complejo operator + (const Complejo a, const Complejo b)


{ Complejo Temp;
Temp.Real= a.Real + b.Real;
Temp.Imaginaria= a.Imaginaria + b.Imaginaria;
return Temp;
}
main()
{ Complejo a, b, c;
a= Crear(1,1);
b= Crear(1,2);
c= Sumar(a, b);
Imprimir(c);
a= c - b;
Imprimir(a);
c= a + b - c; //c= Sumar(a, Restar(b, c));
Imprimir(c);
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 199

18.- PROGRAMACIÓN ORIENTADA A OBJETOS.

1.- Introducción.
La POO intenta solucionar los problemas de una forma más natural. Puede describirse como un
conjunto de disciplinas que desarrollan programas para facilitar la construcción de sistemas complejos
a partir de componentes individuales (Khoshafian, 1990). Este conjunto de disciplinas proveen
conceptos, métodos, técnicas y herramientas para:

· Representar y modelar el mundo real tan fielmente como sea posible a la perspectiva del
usuario. Como vivimos en un mundo lleno de objetos que existen por naturaleza, habitualmente
los clasificamos, describimos, combinamos, manipulamos, creamos y destruimos. De ahí que
se pretenda dar una visión orientada a objetos para la creación de software.

· Construir componentes de software reutilizables y librerías de módulos de software fácilmente


extensibles obteniendo como consecuencia un desarrollo de software más rápido, de mejor
calidad y más fácil de mantener debido a su estructura inherentemente descompuesta. Así, se
pueden modificar y extender fácilmente las implementaciones de los componentes sin tener
que recodificar los programas desde el inicio. (Khoshafian y Abnouz, 1990)

En la POO los datos (atributos) y el código que actúa sobre los datos (métodos) se convierten
en una única entidad, el objeto (se estudiará más tarde). Estos objetos se pueden construir partiendo de
otros creados anteriormente, permitiendo así construcciones complejas por partes. Los objetos se
comunican entre sí por medio de mensajes. Así, pasamos de la ecuación tradicional en programación
estructurada (Wirth, 1980)

Algoritmos + Estructuras de Datos = Programas

a una nueva ecuación en POO

Atributos + Métodos = Objetos


Objetos + Mensajes = Programas

Unos autores (Khoshafian, 1990) se centran en tres elementos fundamentales para configurar la
OO: tipos abstractos de datos, herencia e identidad de objetos. Otros (Meyer, 1988) en siete: estructura
modular basada en objetos, abstracción de datos, gestión automática de memoria, clases, herencia,
polimorfismo y herencia múltiple.

2.- Características de la poo.


La POO se fundamenta en las siguientes cuatro propiedades: abstracción, encapsulamiento,
modularidad y jerarquía. Estas características no son propias de la POO si no que se dan también en
otras formas de programación. Los cuatro elementos deben existir para que el modelo sea orientado a
objetos.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 200

2.1.- Abstracción.
Es la propiedad que permite representar las características esenciales de un objeto ignorando
determinados aspectos de la realidad para facilitar la realización de la tarea. Se centra en la vista
externa de un objeto y sirve para separar su comportamiento de su implementación; es decir, no
debemos fijarnos en la implementación concreta de las estructuras de datos desde el punto de vista de
la programación (arrays, listas enlazadas, ficheros...) si no en las características que tiene en la vida
real.

Si consideremos una calculadora, lo que nos interesa de ella es que se puede encender y
apagar, es capaz de realizar cálculos aritméticos, mostrar los resultados de dichos cálculos entre
otras características de la misma. Lo que no nos interesa son los mecanismos internos que utiliza para
proporcionar unos resultados óptimos: estado de transistores, LEDs, sumadores, acumuladores, etc.

Para definir un objeto y establecer las relaciones de este con otros objetos sólo necesitamos
conocer QUE es lo que hace el objeto (que operaciones necesitan realizarse y que información resulta
de la aplicación de dichas operaciones) y no COMO lo hace.

La implementación de la representación interna de la estructura y de los servicios del TAD


incorpora el concepto de encapsulación. Para que un TAD funcione bien, la implementación de su
representación y de los servicios debe estar encapsulada (Liskov, 88).

2.2.- Encapsulamiento.
Es la propiedad que permite empaquetar las características y comportamiento de un objeto en
un envase independiente, oculto y seguro del resto del sistema; es decir, estamos realizando un
ocultamiento de la información, almacenándola en el interior de una cápsula, para evitar la corrupción
de los datos de un objeto y protegiéndolos de un uso arbitrario.

Ninguna parte de un sistema complejo debería depender de la implementación, de los detalles


internos, de otra parte de ese mismo sistema. Por tanto, encapsulación y abstracción son términos
complementarios: ésta se centra en el exterior de los objetos y aquella oculta a los demás las
variaciones de su implementación. Así, cada objeto tiene dos partes diferenciadas:

· Interfaz de comunicación con el exterior. Captura la visión externa del objeto y es conocida
por los demás objetos del sistema permitiendo la comunicación entre ellos. Es, por tanto, el
resultado de aplicar la abstracción al objeto.

· Implementación. Es el producto de aplicar el encapsulamiento al objeto. En esta parte se


explicitan y codifican los mecanismos necesarios para responder a la interfaz.

La encapsulación establece una barrera conceptual que indica si los datos y operaciones del
objeto encapsulado son o no accesibles por el resto de objetos del sistema. Así, ocultando información,
se nos permite ocultar una parte del sistema del resto, permitiendo que el código sea revisado sin
afectar a la integridad del sistema.

Yo, como objeto, me comunico con la calculadora a través del interfaz de comunicación que
ofrece al exterior: un display, un interruptor de encendido y apagado, un conjunto de teclas
numéricas, operacionales y de comandos. El resto, todos los chips y circuitos adicionales, el

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 201

operando, el acumulador, toda la implementación, está encerrado dentro de una caja para que no se
pueda manipular; es su implementación interna.

2.3.- Modularidad.
Es la propiedad que permite subdividir, descomponer, un sistema en partes más pequeñas,
denominadas módulos, que se adaptan, ciñen, a la estructura lógica elegida en el proceso de diseño. De
ésta manera, en cierta medida, se reduce la complejidad del sistema. Se suele aplicar un método de
refinamiento progresivo de los módulos; cada módulo puede dividirse en otros más pequeños a su vez.
Ya Dijkstra en 1979 dice: “la técnica de dirigir ésta complejidad ha sido conocida desde antiguo, y
consiste en ‘divide et impera’ (divide y vencerás)”. Cada uno de los módulos debe ser tan
independiente como sea posible del sistema y de las otras partes del mismo. Deben ser cohesivos y
débilmente acoplados.

Evidentemente la calculadora no es una pieza homogénea. Está formada por un conjunto de


componentes –circuitos electrónicos, botones, LEDs, plásticos- que han sido fabricados cada uno
independientemente del otro; de esta forma no sólo sirven para ésta calculadora, si no que sirven para
cualquier equipo electrónico que los precise.

2.4.- Jerarquía.
Es la propiedad que permite clasificar u ordenar las abstracciones. Las jerarquías más
importantes son:

- Jerarquía de clases: se implementa mediante relaciones de herencia y define una relación


entre las clases donde se comparte la estructura o comportamiento definido en la clase.

Si pensamos en una clase superior como “Máquina de Calcular”, las clases “Calculadora”,
“Calculadora Científica” y “Calculadora programable” comparten el comportamiento de la clase
“Máquina de Calcular”. Se establece una jerarquía de clases.

- Jerarquía de estructura de objetos: se implementa mediante relaciones de


agregación/composición que permiten el agrupamiento físico de las estructuras relacionadas
lógicamente.

En la calculadora todos los componentes están relacionados y perfectamente acoplados entre


sí componiendo el objeto calculadora. Hay una relación de composición.

3.- Clases y objetos.


Antes de comenzar a tratar con rigor estos dos elementos imprescindibles de la POO,
conveniente, dada la estrecha relación existente entre ambos, dar una definición sencilla de los
mismos. Por objeto entendemos cualquier cosa del mundo real y por clase un marco, molde o
prototipo que permite crear objetos con la misma estructura. Un ejemplo utilizado habitualmente es el
del molde de galletas; el molde para hacer galletas sería la clase, y las galletas que hacemos a partir de
ese molde ya son objetos concretos creados a partir de las características definidas en el molde.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 202

3.1.- Objeto.
Desde la perspectiva del conocimiento humano, un objeto es una de las siguientes cosas:

· Algo tangible y/o visible.


· Alguna cosa que puede ser comprendida intelectualmente.
· Algo hacia lo que se dirige el pensamiento o la acción.

Un objeto, por tanto, representa un elemento individual e identificable, una unidad o entidad,
real o abstracta, con un comportamiento bien definido en el dominio del problema (Smith, 88).

Desde la perspectiva de la POO, un objeto es un elemento autónomo de información que


proporciona una serie de servicios sobre la estructura de datos en la que se implementa. Es decir, un
objeto es una instancia de una clase que incorpora, en una estructura autónoma, su interfaz y la
implementación de su representación interna y de sus servicios.

Definición: un objeto es una instancia de una clase que incorpora una interfaz de comunicación
con el exterior y una implementación interna de sus servicios.

La calculadora tiene todas esas características: es visible, tangible, identificable, tiene un


comportamiento definido, tiene una implementación interna, dispone de un interfaz. La calculadora es
un objeto.

En el contexto de POO un objeto posee un estado, un comportamiento y una identidad que


lo diferencian de los demás objetos. La estructura y comportamiento del conjunto de objetos similares
se definen en una clase común. Por tanto una clase podrá tener muchos objetos de su tipo pero un
objeto sólo podrá pertenecer a una clase.

3.1.1.- Estado del objeto.

El estado del objeto viene determinado por el conjunto de propiedades que tiene -estas suelen
ser estáticas, no cambian-, junto con los valores que pueden asumir cada una de estas propiedades -que
son dinámicas, cambian con el tiempo-.

Así pues, en la calculadora -y pasando del objeto tangible a una supuesta programación de la
misma con un lenguaje de programación orientado a objetos- el operando y el acumulador, además de
otros componentes, pueden configurar la parte estática del objeto: no admiten letras, sólo pueden
almacenar un valor en un instante determinado, admite un máximo de dígitos, ...; estas son sus
características y no pueden cambiar. Por otra parte, estos componentes tienen unos valores reales
para cada objeto que van variando con la actuación del objeto en el transcurso de su uso (toman
diferentes valores: 1, 345, 123.98). Estas son sus características dinámicas.

El hecho de que cada objeto tenga un estado implica que ocupa un espacio, en el mundo real o
en la memoria del ordenador, existe durante un tiempo, cambia el valor de sus atributos, es instanciado
y se puede crear y destruir.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 203

3.1.2.- Comportamiento del objeto.

Los objetos se relacionan entre sí, deben comunicarse y actuar unos sobre otros. El
comportamiento de un objeto viene determinado por la forma en que actúa al recibir un mensaje desde
otro objeto o desde el entorno; es decir, reacciona a un mensaje recibido del sistema y dependiendo de
éste cambia de estado. En éste sentido, el estado de un objeto, no es más que el resultado acumulado de
su comportamiento.

Los mensajes son la forma que tienen de comunicarse distintos objetos entre sí. Un mensaje es
una acción que se manda a un objeto para que realice una operación con un propósito específico.

Existen cinco tipos de operaciones que suele realizar un objeto (Booch, 94):

- Modificar. Altera el estado del objeto.

Si consideramos que “Esta Calculadora” es un objeto de la clase “Calculadora” y


“Sumar()” una de las operaciones que es capaz de realizar “Esta Calculadora” -forma parte
de la interfaz-, entonces “Esta Calculadora.Sumar()” sería un mensaje que se manda sobre el
objeto “Esta Caculadora” para que calcule la suma de sus atributos “Acumulador” y
“Operando” -forman parte de la implementación-, dejando el resultado en “Acumulador” y
borrando el “Operando” -en líneas generales-, cambiando con ello el estado del objeto.

- Seleccionar. Accede al estado del objeto, pero no lo altera.

Mostrar el contenido del “Acumulador” no cambia el estado del objeto.

- Iterar. Permite que todas las partes del objeto sean accedidas en un orden bien definido.

Si se hubiera definido la operación “Mostrar Estado”, que visualice


secuencialmente la memoria, el acumulador y el operando en la calculadora, hubiera
sido una operación de este tipo.

- Constructor. Crea el objeto e inicializa su estado.

- Destructor. Libera el estado del objeto y lo destruye (ocasinalmente).

3.1.3.- Identidad del objeto.

Es la propiedad que caracteriza a un objeto, la que le distingue de los demás objetos


(Khoshafian, 91); se distinguen entre si por su propia existencia (inherente), no por sus propiedades
descriptivas. En general, y en programación orientada a objetos, esto se hace con el nombre
(identificador) del objeto y/o con direcciones o referencias al objeto almacenado en la memoria.
Utilizando ésta segunda manera, y dependiendo del lenguaje de programación, podemos nombrar –
acceder- a un mismo objeto con varios aliases. A través de su nombre o alias podemos realizar todas
las operaciones permitidas con el objeto.

La calculadora de la mesa está perfectamente identificada por su marca y número de serie o


modelo. Así, “Esta Calculadora” es el nombre del objeto de la clase “Calculadora” definida
anteriormente. También podemos crear el alias “Mi Calculadora” para identificar el objeto y a través
de él o del nombre podemos realizar las operaciones pertinentes.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 204

La identidad de un objeto es una característica fundamental del mismo, y en su implementación


a través de direcciones, referencias y nombres de objeto genera mucha controversia debido a que no
son mecanismos adecuados para identificar unívoca y permanentemente el objeto.

3.2.- Clase.
Una clase es una generalización de un tipo determinado de objeto: marco, molde o prototipo
que permite crear objetos con una estructura y comportamiento comunes. Especifica una estructura de
datos y los métodos operativos que se aplican a cada uno de los objetos; es decir, una clase contiene
una completa y detallada descripción de los datos que contendrá el objetos y de las operaciones que
podrá realizar. Así, en programación, una clase es un tipo de dato y un objeto es una variable de ese
tipo de dato.

Un ejemplo utilizado habitualmente para relacionar y diferenciar un objeto y una clase es el del
molde de galletas; el molde para hacer galletas sería la clase, y las galletas que hacemos a partir de ese
molde ya son objetos concretos creados a partir de las características definidas en el molde.

Una clase se caracteriza por un identificador, unos componentes o miembros y por un nivel de
acceso a sus componentes.

3.2.2.- Identificador

Es el nombre que se da a la clase para poder especificar a cual pertenecen los objetos.

3.2.2.- Componentes o miembros de la clase.

Existen dos tipos de componentes en una clase: atributos y métodos.

Datos miembro, atributos, propiedades.

Conforman las estructuras de datos, de cualquier tipo de dato (simple o estructurado), que van
a utilizar los objetos asignándoles unos valores que permitan diferenciarlos unos de otros. Constituyen,
en definitiva, la representación interna de la clase y mantienen el estado del objeto.

Métodos o funciones miembro.

Operaciones que se pueden realizar con los atributos y que son utilizables por todos los objetos
pertenecientes a la clase; por tanto, los métodos definen el comportamiento del objeto.

Khosafian (90) clasifica los métodos en tres tipos, básicamente las mismas operaciones que
pueden realizar los objetos (Booch, 94):

· De acceso. Recuperan el valor de los atributos del objeto.


· De actualización. Cambian el estado del objeto.
· Constructores y Destructores. Son invocados automáticamente para crear, inicializar, copiar y
destruir el objeto.

· Un constructor se llama automáticamente en el instante de creación del objeto, le asigna la


memoria necesaria para almacenar el objeto y lo inicializa con los valores por defecto -si es

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 205

un constructor por defecto creado por el propio compilador-, o con los valores especificados
en un constructor general definido por el programador. También se pueden copiar unos
objetos en otros.
· Un destructor también se invoca automáticamente justo antes de que el objeto sea
eliminado de la memoria y lo incorpora el lenguaje de manera automática. Según las
operaciones realizadas con el objeto, y dependiendo del lenguaje, será necesario que el
programador cree su propio destructor.

3.2.3.- Nivel de acceso.

Los componentes de la clase tienen distintos niveles de acceso para proteger la información del
exterior de la clase (encapsulación). Este nos permite diferenciar entre el interior y el exterior de la
clase. La representación interna es la implementación de la clase, es la visión del diseñador de la clase
y comprende la implementación de los métodos. La externa es el interfaz de una clase, proporciona las
operaciones que se pueden realizar sobre los objetos de la clase, es la visión del usuario –la declaración
de los atributos y métodos de la clase-. Se puede dividir según su visibilidad en tres partes:

- Pública. Declaraciones visibles a todos los usuarios de la clase.


- Protegida. Declaraciones visibles a la propia clase y a sus subclases.
- Privada. Declaraciones visibles únicamente a la propia clase.

Con esta definición de clase, un objeto es una particularización o instancia de una clase que
incorpora una interfaz y una implementación interna de sus servicios, siendo por tanto un concepto
físico y concreto que tiene una existencia real y determinada. Así, mediante las clases podemos
instanciar objetos de un mismo tipo que se distinguen entre sí a través de su estado, del valor de sus
atributos.

Siguiendo con el ejemplo de la calculadora tenemos que:


- El identificador de la clase es “Calculadora”
- Sus atributos son Acumulador, Memoria y Operando que son privados a la clase y que
habitualmente los atributos deben serlo.
- Sus Métodos son, entre otros, Encender() –constructor-, Apagar() –destructor-,
DarOperando() –de acceso- y PonerOperando(Parámetro) –de actualización- que son
públicos, aunque no necesariamente.
- Se puede declarar el interfaz de la clase de la siguiente manera:

Clase Calculadora
Atributos y Métodos privados
Acumulador es de tipo real
Operando es de tipo real
Atributos y Métodos públicos
Encender()
Apagar()
DarOperando()
PonerOperando(Parámetro real)
Sumar()
Restar()
...

- Su implementación sería la codificación de los métodos declarados.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 206

Metaclase.

El lenguaje permite instanciar clases desde otras clases; es decir son clases cuyas instancias son
también clases. Proporcionan la ventaja de que pueden almacenar información común a todas las
instancias de la clases mediante “variables de clase”. Los “métodos de clase” se pueden utilizar para
actualizar o recuperar el contenido de estas.

3.3.- Declaración de clases.


La sintaxis utilizada en C++ para declarar una clase es la siguiente:

class Nombre De Clase


{ Nivel De Acceso:
Componentes: datos y funciones
Nivel De Acceso:
Componentes: datos y funciones
}[Lista de objetos];

Existen tres niveles o especificadores de acceso que pueden estar situados en cualquier lugar
dentro de la definición de la clase y aparecer tantas veces como sea necesario; su ámbito actúa desde
que se pone hasta que aparece otro. Estos son los siguientes:

· Public. Los componentes son accesibles directamente por cualquier objeto perteneciente a la
clase, es decir, se puede acceder a los datos y funciones miembro desde otras partes del
programa.
· Private. Es el nivel de acceso por defecto a los componentes de una clase. Los componentes se
ocultan para todos los objetos de la clase, siendo sólo accesibles desde las funciones de la
clase, es decir, sólo pueden acceder a ellos los otros miembros de la clase.
· Protected. Los componentes son accesibles a través de los miembros de la clase, pero tienen
características especiales relacionadas con la herencia.

Ejemplo. Implementación de una pila de enteros con un array.


class Pila
{ private:
int Datos[1000];
int Indice;
bool EstaLlena()
{ return !Indice;
}
public:
void Construir()
{ Indice= 1000;
}
void Destruir()
{ Construir();
}
void Push(int v)
{ if(!EstaLlena())
Datos[--Indice]= v;
}
void Pop()
{ if (!EstaVacia())
Indice++;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 207

int Top()
{ if(!EstaVacia())
return Datos[Indice];
else
return -1;
}
bool EstaVacia()
{ return Indice==1000;
}
};

Los métodos se han definido dentro de la propia declaración de la clase: métodos internos. Si
suponemos que tenemos muchas funciones y muy grandes, esta forma es impracticable y sería
deseable que la declaración de la clase contuviera únicamente los atributos y los prototipos de las
funciones: métodos externos. Esto se consigue extrayendo de la clase el código de las funciones de la
siguiente forma :
int Pila::Top()
{ if(!EstaVacia())
return Datos[Indice];
else
return -1;
}
bool Pila::EstaVacia()
{ return Indice==1000;
}

La sintaxis para definir las funciones externas es la siguiente :

TipoRetorno NombreClase::NombreFuncion(Parámetros) ;

Donde el operador de campo :: quiere decir que la función pertenece al campo de la clase.

La diferencia entre definir métodos internos y externos está en que estos se comportan como
funciones normales y aquellos se comportan como funciones inline.

3.4.- Instanciación de objetos.


El formato para crear objetos o instancias de esa clase es el siguiente :

NombreClase ListaObjetos ;

Ejemplo: Pila Pila1, Pila2 ;

El formato para el acceso a los miembros de la clase es el mismo que para las estructuras. En
general es el siguiente :

Objeto.Miembro ;

Ejemplo.
Pila1.Construir() ;
Pila1.Push(1) ;
Cima = Pila1.Top() ;
Pila1.Pop() ;
Pila1.Destruir() ;

Sólo podemos acceder desde el programa a los miembros definidos como públicos, es decir, las
siguientes sentencias darían errores de compilación :

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 208

Pila1.Datos[1]= 2 ;
Pila1.Indice= 100 ;

Si observamos como invocamos los métodos y lo comparamos como lo haríamos en


programación tradicional, podemos darnos cuenta de que falta un parámetro, la propia pila. En
programación estructurada la definición y la llamada de una de las funciones anteriores es la siguiente :
void Push(Pila *, int) ;
Push(Pila1, 1) ;

Pero al introducirlo dentro de una clase eliminamos el primer parámetro, de manera que cuando
hacemos
Pila1.Push(1) ;

es como si estuviéramos haciendo


Push(Pila1, 1) ;

por lo que estamos pasando implícitamente el parámetro.

En C++ existe el puntero, denominado this, al propio objeto que llama al método. Así, la
función anterior podría haberse escrito como
void Push(int v)
{ if(!(this->EstaLlena()))
this->Datos[--this->Indice]= v;
}

La declaración formal del parámetro this es la siguiente

TipoClase *const this ;

y como ‘this’ es un puntero constante no se puede modificar.

Ejercicios.

1. Programa que permita sumar y restar números complejos.


2. Programa que simule una cuenta bancaria. Permitir crear la cuenta, petición de saldos, ingresos y
reintegros desde un menú.
3. Programa que calcule la distancia entre dos puntos.
Sqrt(sqr(x1-x2)+sqr(y1-y2))

3.5.- Control de acceso a miembros.

3.5.1.- Campo de clase

Dentro de una función miembro tenemos un conflicto entre tres campos con igual identificador:
la variable global, el campo/atributo de la clase y la variable local/parámetro formal del método ;
cuando coinciden los tres identificadores se tomará primero el campo local de la función, después el
campo de clase y por último, el global :

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 209

int a ;
class Clase
{ int a ;
void f(int a)
{ a= 1 ; //Parámetro formal de la función
Clase::a= 2 ; //Campo de la clase
::a= 3 ; //Variable global. Operador de resolución de visibilidad.
... //Operador de resolución de visibilidad
}
} ;

3.5.2.- Acceso entre clases amigas

Cuando se definen miembros como private el acceso a miembros entre clases distintas es
imposible. Desde una clase no se puede acceder a atributos o métodos privados a otra clase. Tampoco
lo tenemos desde una función normal.

Por ejemplo, la siguiente clase Punto dispone de una función que calcula la distancia entre dos
puntos :
class Punto
{ double x, y;
public:
void Pon(double xx, double yy)
{ x= xx;
y= yy;
}
double Distancia(const Punto &b)
{ return sqrt(pow(b.x - x, 2) + pow(b.y - y, 2));
}
};

si declaramos dos puntos, su distancia se hallaría de la siguiente forma:


main()
{ Punto p1, p2;
double d;
p1.Pon(2, 2);
p2.Pon(1, 1);
d= p1.Distancia(p2);
}

El método Distancia() accede tanto a los miembros privados de p1 como a los de p2 porque
forma parte de la clase punto y, por tanto, tiene acceso a todos los miembros de la clase a la que
pertenece.

La forma de hallar la distancia es poco ortodoxa; dado que es un método que está operando
sobre dos puntos deberíamos pasarle como parámetros los dos puntos, pero uno de ellos va implícito
en la propia llamada, el parámetro this. El prototipo lógico sería
double Distancia(const Punto &a, const Punto &b) ;

y la llamada para calcular la distancia es


d= Distancia(p1, p2) ;

El problema está en que ahora la función Distancia() no es un método de Punto si no que es una
función independiente, a no ser que la llamada sea de la forma
d= p1.Distancia(p1, p2) ;

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 210

pero igual que en el primer caso es muy poco elegante e incluso puede dar lugar a confusión.

Para solucionar el problema debemos definir la función Distancia() como función amiga de la
clase punto usando la palabra reservada friend.
class Punto
{ double x, y;
public:
void Pon(double xx, double yy)
{ x= xx;
y= yy;
}
friend double Distancia(const Punto &a, const Punto &b) ;
};
double Distancia(const Punto &a, const Punto &b) //
{ return sqrt(pow(b.x - a.x, 2) + pow(b.y - a.y, 2));
}

Ahora no se pone el operador de campo :: puesto que Distancia() no es un método de la clase


Punto y por tanto tampoco tiene el parámetro this.

Si queremos que todos los métodos de una clase sean amigos de otra, entonces declaramos toda
la clase como amiga: friend class Clase ;

3.6.- Sobrecarga de operadores en clases.


En la sobrecarga de funciones en C++ con estructuras se pasan dos parámetros a la función. Por
contra, cuando se sobrecargan los métodos de una clase se pasa un único parámetro; esto es así por que
existe el parámetro implícito this que se comporta como el primer operando. Como ejemplo creamos
una clase que calcula la suma y resta de números complejos :
class Complejo
{ private:
double Real, Imaginaria;
public:
void Crear(double Real, double Imaginaria);
void Imprimir();
Complejo operator + (const Complejo &C);
Complejo operator - (const Complejo &C);
};
void Complejo::Crear(double Real, double Imaginaria)
{ Complejo::Real= Real;
Complejo::Imaginaria= Imaginaria;
}
void Complejo::Imprimir()
{ cout << Real << "+" << Imaginaria << "i" << endl;
}
Complejo Complejo::operator + (const Complejo &C)
{ Complejo Temp;
Temp.Real= Real + C.Real;
Temp.Imaginaria= Imaginaria + C.Imaginaria;
return Temp;
}
Complejo Complejo::operator - (const Complejo &C)
{ Complejo Temp;
Temp.Real= Real - C.Real;
Temp.Imaginaria= Imaginaria - C.Imaginaria;
return Temp;
}
main()
{ Complejo a, b, c;
a.Crear(1,1);
b.Crear(1,2);
c= a.operator+(b); //Llamada extendida. No se usa.
a= c - b; //Llamada normal.
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 211

También podemos declarar el operador + como friend, teniendo en cuenta que ahora no existe
el parámetro this:
class Complejo
{ ...
friend Complejo operator + (const Complejo& C1, const Complejo &C2);
...
};

Complejo operator + (const Complejo &C1, const Complejo &C2)


{ Complejo Temp;
Temp.Real= C1.Real + C2.Real; //No existe this.
Temp.Imaginaria= C1.Imaginaria + C2.Imaginaria;
return Temp;
}

Las llamadas pueden ser de la forma siguiente :


c= operator+(a, b); //Extendida. No se usa.
c= a + b ; //Normal.

3.7.- Creación e inicialización de objetos.

En el momento de definir (construir) un objeto podemos darle unos valores iniciales usando
constructores. No podemos inicializar los objetos como lo hacemos con las estructuras a no ser que
cumpla las siguientes restricciones :

· La clase no puede tener miembros privados.


· La clase no puede tener constructores.
· La clase no puede tener funciones virtuales.
· La clase no puede ser derivada.

que nos deja prácticamente sin posibilidades.

3.7.1.- Constructor por defecto.

Cada vez que se define un objeto, estática o dinámicamente (new), se está realizando una
llamada a un método, constructor por defecto, que ha creado el propio compilador si no ha encontrado
un constructor definido explícitamente en la clase. El constructor por defecto no tiene argumentos ni
tampoco retorna ningún valor, ni siquiera void. Para identificarlo se usa el nombre de la clase como
nombre del constructor.
class Punto
{ double x, y;
public:
Punto() //Igual que lo crea el compilador.
{
}
void Pon(double xx, double yy)
{ x= xx;
y= yy;
}
friend double Distancia(const Punto &a, const Punto &b) ;
};

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 212

En esta clase el constructor no hace nada pero podemos modificarlo para que si haga algo, por
ejemplo inicializar x e y a 0, 0 respectivamente:
class Punto
{ double x, y;
public :
Punto()
{ x= 0.0 ;
y= 0.0 ;
}
...
};

Ahora, cada vez que se define un nuevo objeto (Punto p) de la clase punto, tomará el valor (0.0,
0.0).

3.7.2.- Constructores generales.

Podemos definir todos los constructores que queramos en una clase, todos con el mismo
nombre, el de la clase, usando las reglas de la sobrecarga de funciones. Siguiendo con la clase Punto la
definimos de la siguiente forma :
class Punto
{ double x, y;
public:
Punto()
{ x= y= 0.0;
}
Punto(double xx, double yy)
{ x= xx;
y= yy;
}
Punto(double xx)
{ x= y= xx;
}
friend double Distancia(const Punto &a, const Punto &b);
};

Ahora no es necesario el método Pon() puesto que construimos los objetos de otra forma. Pero,
¿qué sucede si eliminamos el constructor por defecto Punto(){...}? Habría que modificar el constructor
general de la siguiente forma:
Punto(double xx= 0, double yy= 0){…}

Definiciones correctas e incorrectas de objetos son las siguientes :

Punto p ; Llaman al constructor por defecto. La segunda


Punto p= Punto() ; forma no suele usarse.

Punto p() ; Incorrectas.


Punto p= Punto ;

Punto p(3, 2) ; Llaman al constructor con dos parámetros


Punto p= Punto(3, 2) ; Punto(double, double). La segunda no se usa.

Punto p(3) ; Llaman al constructor con un parámetro


Punto p= Punto(3) ; Punto(double). La segunda no se usa.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 213

Si eliminamos el constructor por defecto manteniendo los restantes, la primera definición es


incorrecta puesto que al existir constructores generales el compilador no crea el constructor por
defecto.

3.7.3.- Destructor.

El destructor es, al igual que el constructor, un método especial de la clase. No lleva parámetros
ni retorna ningún valor y su identificador es el mismo que el de la clase precedido por el signo ~
(Alt+126). Se invoca automáticamente justo antes de que el objeto que lo contiene sea destruido.
class Punto
{ double x, y;
public:
~Punto() ; //Destructor. No hace nada.
Punto() ;
Punto(double xx, double yy) ;
Punto(double xx) ;
friend double Distancia(const Punto &a, const Punto &b);
};

Su uso es habitual y se hace necesario cuando el objeto reserva memoria explícitamente pero
también puede finalizar asuntos pendientes, cerrar ficheros, etc.
#include <iostream.h>
#include <string.h>
#define NULO '\0'
class Cadena
{ private:
char *LaCadena;
public:
Cadena();
~Cadena();
Cadena(char *Tira);
void Print();
};
Cadena::Cadena()
{ LaCadena= new char(NULO); //Inicializa a NULO. No arrays.
}
Cadena::Cadena(char *Tira)
{ LaCadena= new char[strlen(Tira)+1]; //Esta memoria no se libera
strcpy(LaCadena, Tira); //cuando se destruye el objeto.
}
Cadena::~Cadena()
{ delete []LaCadena; //Hay que destruirla explícitamente.
}
void Cadena::Print()
{ cout << LaCadena;
}
main()
{ Cadena c("asdasdasdasd"), d;
c.Print();
}

3.7.4.- Constructor copia y el operador de asignación.

Se denomina constructor copia a un constructor que tiene un único parámetro, una referencia,
del mismo tipo de la clase donde definimos el constructor. Su finalidad es copiar los datos de un objeto
a otro. Si no definimos un constructor copia explícitamente, el compilador crea uno por omisión. Las
llamadas al constructor copia son:
Punto p ; //Primer constructor.
Punto p(2, 3), r(2) ;
Punto s(p) ; //Crea s y con el constructor copia s=(2,3) ;

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 214

p= r ; //Operador de asignación p= (2, 0)


Punto t= r ; //Llama al constructor copia ;

Que la última sentencia llame al constructor copia es por que este dispone de dos notaciones
(Punto p(q) y Punto p= q) mientras que el operador de asignación sólo dispone de una (p= q).

Hay otro método que implementa el compilador por omisión : el operador de asignación. Como
el constructor copia, realiza una copia de los atributos del objeto fuente. Es decir, podemos usar el
constructor copia y el operador de asignación para copiar los datos de un objeto en otro como si se
tratase de cualquier otra variable. La diferencia entre usar uno u otro está en que el primero crea un
nuevo objeto y después copia los datos y el segundo sólo copia los datos, trabaja sobre un objeto ya
creado. Por ejemplo, en la clase punto podemos añadir el constructor copia y el operador de
asignación:
class Punto
{ double x, y;
public:
~Punto() ; //Destructor. No hace nada.
Punto(double xx= 0, double yy= 0) ;
Punto(const Punto &p) //Constructor copia.
{ x= p.x ; y= p.y ;
}
Punto operator=(const Punto &p) //Operador de asignación
{ x= p.x ; y= p.y ;
}
void Desplazar(double a, double b)
{ x+= a ; x+= b ;
}
friend double Distancia(const Punto &a, const Punto &b);
};

En la clase Cadena creada antes, también se incorporan automáticamente el constructor copia y


el operador de asignación ; sin embargo si la estudiamos más a fondo vemos que los efectos no son los
deseados. Cuando copiamos o asignamos un objeto Cadena a otro se realiza una copia de sus atributos,
un puntero a char (char *), es decir, no se copia la cadena propiamente dicha si no sólo su dirección.
Cadena c("1234567890");
Cadena s(c);

una sentencia como s.Poner(“00000”) hará que cambie el atributo de s y también el de c puesto
que ambos tienen la misma dirección de memoria. Por tanto, el constructor copia por omisión no nos
sirve para solucionar este problema. Una solución es la siguiente :
Cadena::Cadena(const Cadena &c)
{ LaCadena= new char[strlen(c.LaCadena)+1];
strcpy(LaCadena, c.LaCadena);
}

Con el operador de asignación sucede exactamente lo mismo. La solución es la que se da a


continuación :
Cadena Cadena::operator = (const Cadena &c)
{ if (this != &c) //Sólo si son distintos objetos (c= c). Compara direcciones
//(*this != c) Compara objetos llamando al operador !=.
{ delete []LaCadena; //Borra el espacio actual
LaCadena= new char[strlen(c.LaCadena)+1]; //Crea nuevo espacio
strcpy(LaCadena, c.LaCadena); //Copia la cadena
}
return *this;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 215

Ejercicio: Crear una clase cadena con los siguientes elementos sin usar las funciones del
archivo cabecera string.h:
· Constructor por defecto, generales y copia.
· Operador de asignación de objetos y de asignación convencional.
· Concatenación de cadenas sobrecargando el operador +.
· Visualizar la cadena.
· Todas aquellas operaciones con cadenas que se quieran : longitud, comparación, etc.

Cadena Cadena::operator +(const Cadena &OCadena)


{ Cadena OAuxiliar;
char *Auxiliar;
int i, j;
Auxiliar= new char[Tamanho + OCadena.Tamanho + 1];
for (i= 0;i<Tamanho; i++) //strcpy()
Auxiliar[i]= LaCadena[i];
for (j= 0;j<=OCadena.Tamanho; j++) //strcat()
Auxiliar[i + j]= OCadena.LaCadena[j];
OAuxiliar.Tamanho= Tamanho + OCadena.Tamanho;
OAuxiliar.LaCadena= Auxiliar;
return OAuxiliar;
}

3.8.- Objetos dinámicos.

3.8.1.- Vectores de objetos.

Se pueden definir arrays de objetos igual que se definen de cualquier otro tipo de dato: el tipo
de dato base de las componentes del array seguido del identificador y entre corchetes su longitud y
número de dimensiones.
Punto Triangulo[3] ;

Esta definición llama al constructor por defecto y por tanto inicializa las tres componentes a
cero; si el constructor por defecto no está definido, da error.

También podemos inicializar el array de la siguiente forma :


Punto Triangulo[3]= {Punto(1, 1), Punto(2, 2), Punto(3, 3)} ;

y podemos, además, realizar inicializaciones parciales como la siguiente:


Punto Triangulo[3]= {Punto(1, 1), Punto(2, 2)} ;

donde las componentes no inicializadas llaman al constructor por defecto.

3.8.2.- Punteros a objetos.

La declaración de punteros a objetos es semejante a la declaración de punteros a otras variables


o tipo de datos. Podemos declarar un puntero a un objeto de la siguiente forma:
Punto *p;

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 216

que crea un puntero a un objeto. En esta declaración no se realiza la llama al constructor puesto que es
la declaración de un puntero, no de un objeto, y, por tanto, tampoco se reserva memoria para el objeto,
sólo los cuatro bytes para el puntero.
Punto P ;
Punto *Puntero ;
Puntero= &P ;
Puntero->Desplaza(2, 1) ;
Puntero->Punto::~Punto() ;

Los ejemplos anteriores muestran que el uso de punteros a objetos es el mismo de siempre. La
última sentencia elimina el objeto de la memoria (llama al destructor) pero el puntero sigue apuntando
la misma posición de memoria (no se inicializa a NULL); cualquier acceso a ese objeto, que ya no
existe, tiene consecuencias imprevisibles.

También podemos reservar y destruir memoria dinámicamente usando los operadores new y
delete respectivamente.
Punto *Puntero ;
Puntero= new Punto ; // Reserva memoria y llama al constructor
delete Puntero ; // Llama al destructor y libera la memoria
Punto *Puntero= new Punto(2, 3) ; //Construye con 2, 3
Punto *Puntero= new Punto[100] ; //Puntero a array de objetos
delete [100]Puntero ;
delete []Puntero ;

Ejercicio : CREACIÓN DE UNA PILA


#include <stdio.h>
#include <stdlib.h>
typedef int TInfo;
class cPila;
class cNodo
{ friend class cPila;
private:
TInfo Dato;
cNodo *Sig;
};
class cPila
{ private:
cNodo *Cima;
public:
cPila();
~cPila();
bool Vacia();
void Push(TInfo);
TInfo Top();
void Pop();
};
cPila::cPila()
{ Cima= NULL;
}
cPila::~cPila()
{ while (!Vacia())
Pop();
}
bool cPila::Vacia()
{ return Cima == NULL;
}
void cPila::Push(TInfo Info)
{ cNodo *Nodo= new cNodo;
Nodo->Dato= Info;
Nodo->Sig= Cima;
Cima= Nodo;
}
TInfo cPila::Top()
{ if (!Vacia())
return Cima->Dato;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 217

void cPila::Pop()
{ if (!Vacia())
{ cNodo *Aux= Cima;
Cima= Cima->Sig;
delete Aux;
}
}

main()
{ int i;
cPila Pila;
for (i=0; i<3; i++)
Pila.Push(i);
printf("\n");
while (!Pila.Vacia())
{ printf("%d\t", Pila.Top());
Pila.Pop();
}
getch();
}

4.- Relaciones entre clases.


Una clase por sí sola no ofrece gran funcionalidad. Para que ofrezca una mayor funcionalidad y
pueda realizar mayor número de tareas y con más complejidad es necesario que haya una relación
entre las clases. Un ejemplo podría ser una bicicleta. Las piezas que la forman, pedales sillín, cuadro,
etc. no realizan ninguna tarea importante por sí solas; es su unión, la relación que se establece entre
ellas, lo que componen la bicicleta.

Una clase se puede relacionar con otra de varias formas diferentes. Según G. Booch existen tres
clases básicas de relaciones: generalización (la conocemos como herencia), agregación (y/o
composición) y asociación. Otros autores añaden además la relación de uso.

4.1.- Relación de uso.


Una clase se relaciona con otra a través de los mensajes que le envía. Esto se consigue pasando
un objeto de la clase como uno de los parámetros del método invocado por el mensaje. Si disponemos
de una clase Pantalla con el método Dibujar() y queremos dibujar un objeto de la clase Rectángulo,
deberíamos hacer “Pantalla.Dibujar(Rectángulo)”. De esta forma, el Rectángulo está utilizando
Pantalla para dibujarse.

4.2.- Relación de asociación.


Especifica una conexión semántica, en cuanto a su significado, entre objetos no relacionados.
P.e. las flores y las velas son objetos independientes pero representan cosas que pueden adornar una
mesa y las teclas y el visor se asocian, entre otros objetos, para formar la calculadora.

4.3.- Relación de agregación.


Una clase puede estar compuesta de otras clases. Esto se consigue implementando los atributos
de la clase como objetos de otra; es decir, una clase se compone de atributos que son objetos de otra
clase, entre otros atributos diferentes.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 218

Algunos autores, a ésta relación se le denomina composición cuando los objetos de una clase
contenidos en otra clase no pueden ser independientes El ejemplo de la bicicleta sirve para este caso. Y
a la relación se le denomina agregación cuando los objetos de una clase contenidos en otra clase
pueden ser independientes. Por ejemplo, la clase persona está formada por objetos de la clase alumno y
objetos de la clase profesor. Cada uno de estos, alumno y profesor, tienen autonomía propia.
Utilizaremos indistintamente un término u otro.

La clase Calculadora vista con anterioridad puede componerse del objeto Display -de la clase
Visor- y de un número determinado de objetos Teclas –de la clase Tecla-. La relación entre estas
clases viene dada como: la clase Calculadora se compone, relación de composición, de la clase Visor
y de la clase Tecla.

4.4.- Relación de herencia.


Los términos generalización y herencia se refieren a aspectos de la misma idea y habitualmente
se utilizan indistintamente; sin embargo, la generalización hace alusión a la relación existente entre las
clases, de lo más general a lo más particular, y la herencia al mecanismo empleado para compartir
atributos y métodos usando la relación de generalización. Aquí usaremos el término herencia.

La herencia es el mecanismo fundamental para implementar la reutilización y extensibilidad del


software. Es, sin duda, la característica más importante de la POO. A través de ella se pueden construir
nuevas clases partiendo de una jerarquía de clases ya existente (comprobadas y verificadas) evitando
con ello el rediseño, recodificación y verificación de la parte (de las clases) ya implementada. Es decir,
es un mecanismo que sirve para definir una nueva clase a partir de otra, pudiendo añadir nuevas
características sin tener que modificar toda la clase de nuevo.

La clase de la que se hereda se denomina clase base o superclase y la que hereda se denomina
clase derivada o subclase. Esta, a su vez, puede ser heredada por otra clase convirtiéndose en clase
base. En general, en una jerarquía de clases, todas las clases serán base y derivadas excepto las
primeras, que sólo serán bases y las que están abajo, que sólo serán derivadas.

La herencia, por tanto, consiste en que la subclase hereda todos los atributos y métodos de la
clase base sin necesidad de cambiar nada de esta. Simplemente los utiliza.

Por ejemplo, si disponemos de una clase persona que define las características y
comportamiento de los objetos persona y creamos una clase alumno, no es necesario implementar
todos sus atributos y métodos puesto que un alumno es una persona; sólo es necesario heredar de la
clase persona, utilizar lo que nos ofrezca –sus atributos y métodos-, y añadirle las nuevas
características y comportamiento del alumno –Expediente, Notas, Faltas-.

Dependiendo del lenguaje de programación utilizado para la implementación, las subclases


pueden acceder o no aquellos atributos y métodos que se indiquen en el interfaz de su clase base; es
decir, generalmente, existe una “herencia o visibilidad selectiva” de los componentes de la clase base
en la subclase, a unos se tendrá acceso y a otros no. Dependiendo de cual sea la visibilidad de los
componentes declarados en la clase base (públicos, privados o protegidos) y según el tipo de acceso –
forma de heredar- de la subclase, ésta tendrá o no acceso a determinados componentes.

Ahora, una vez que nuestra calculadora está implementada, puede que necesitemos añadirle
una nueva funcionalidad y con ello ampliar su comportamiento: cambiar de pesetas a euros y
viceversa. Para ello, sólo tenemos que declarar una nueva subclase de Calculadora

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 219

“EuroCalculadora” y añadirle las nuevas características: un atributo que indica si los cálculos se
están realizando en pesetas o en euros, una nueva tecla que la declaramos como una nueva subclase
de Tecla o de Comando y un método para cambiar entre euros y pesetas.

Clase Euro : HEREDA de la Clase Tecla


Atributos y métodos públicos
Pulsar()(Obligados a implementarlo si era un Método abstracto)
Fin de la clase Euro

Clase EuroCalculadora : HEREDA de Calculadora


Atributos y métodos privados
Muestra es de tipo numérico (un valor 0 indica pesetas)
Euros es de tipo Clase Euro
Atributos y métodos públicos
CambiaEurosPesetas()
Fin de la clase EuroCalculadora

El formato general de declaración de una clase derivada en C++ es el siguiente :

class NombreClaseDerivada : Acceso NombreClaseBase


{ Nivel De Acceso:
Componentes: datos y funciones
Nivel De Acceso:
Componentes: datos y funciones
}[Lista de objetos];

Ejemplo :
#include <iostream.h>
class Base
{ int B1, B2;
public:
void Poner(int a, int b)
{ B1= a;
B2= b;
}
void MostrarB()
{ cout << B1 << " " << B2 << endl;
}
};
class Derivada : public Base
{ int D;
public:
Derivada(int a)
{ D= a;
}
void MostrarD()
{ cout << D << endl;
}
};
void main()
{ Derivada ob(3);
ob.Poner(1, 2);
ob.MostrarB();
ob.MostrarD();
}

La clase derivada hereda todos los miembros de la clase base aunque seguimos sin tener acceso
a los miembros privados de la clase base dentro o fuera de la clase derivada. Si podemos modificar los
miembros privados de la base usando los métodos públicos disponibles para ello.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 220

Para construir una clase derivada se pueden definir constructores de la siguiente forma :

ConstructorDerivado(Tipos Parámetros) : ConstructorBase(Parámetros) ;

Ejemplo :
class Base
{ ...
Base(int a, int b)
{ B1= a;
B2= b;
}
...
};
class Derivada : public Base
{ ...
Derivada(int a, int b, int c):Base(b, c)
{ D= a;
}
...
};
void main()
{ Derivada ob(1, 2, 3);
}

De esta forma inicializamos miembros de la clase base que hemos heredado. Si no ponemos
explícitamente la llamada al constructor de la clase base, se llama al constructor por defecto de la
misma, que debe existir (creado por el compilador o por nosotros).

Los constructores se ejecutan en orden de derivación, primero se llama al constructor de la


clase base y después al de la derivada. Sin embargo, los destructores se llaman en sentido inverso al de
derivación, es decir, primero se llama al destructor de la clase derivada y después al de la clase base.

Una clase derivada no hereda de la clase base los constructores, el destructor y los operadores
de asignación por que son los que definen los fundamentos de la clase.

4.4.1.- Acceso a miembros heredados y tipos de acceso.

Como ya se vio, los miembros de una clase pueden tener tres tipos de acceso: público, privado
y protegido.

private: están protegidos de todo acceso fuera de la clase en la que están definidos.
public: son accesibles desde cualquier lugar.
protected: están protegidos contra todo acceso fuera de la clase donde fueron definidos y de las
clases derivadas. Es decir, los miembros protegidos si son accesibles desde la clase derivada.

En el ejemplo anterior, como los atributos miembros están definidos como privados a la clase
base, no pueden ser accedidos desde la clase derivada :
class Derivada : public Base
{ ...
void PonerD(int a, int b, int c)
{ D= a ;
B1= b ;
B2= c ;//Error. Privados de la clase base.
}
...
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 221

La solución podría consistir en definir como públicos los miembros B1 y B2, pero entonces,
pueden ser modificados desde cualquier lugar, rompiendo la encapsulación. El problema se soluciona
definiéndolos como protegidos, pasando a ser privados de la clase derivada.
class Base
{ protected :
int B1, B2;
...
} ;

Ahora si podemos acceder a dichos atributos desde la clase derivada pero no desde el exterior,
ya que se comportan como privados de esta clase.

Al derivar la clase también podemos definir el tipo de acceso a la clase base, variando también
el nivel de acceso de los miembros. El cuadro siguiente muestra la visibilidad de los miembros según
el tipo de acceso a la clase a la clase base:

Clase BASE Clase DERIVADA


Públicos Públicos
pública Protegidos Protegidos
Privados sin acceso
Públicos Protegidos
protegida Protegidos Protegidos
Privados sin acceso
Públicos Privados
privada Protegidos Privados
Privados sin acceso

class A ;
class B:protected A ; // Acceso a los miembros no privados de A
class C:protected B ; // Acceso a los miembros no privados de A
class D:privated B ; //Acc. a miembros no priv. y los convierte en privados.
class E:public D ; // No tiene acceso a los miembros de A.
#include <iostream.h>
#include <math.h>
class Punto
{ double x, y;
public:
~Punto(){};
Punto(double xx= 0, double yy= 0)
{ x= xx;
y= yy;
}
double operator - (const Punto &a)
{ return sqrt(pow(x - a.x, 2) + pow(y - a.y, 2));
}
friend double Distancia(const Punto &a, const Punto &b)
{ return sqrt(pow((a.x - b.x), 2) + pow((a.y - b.y), 2));
}
};
class Figura
{ protected:
int NPuntos;
Punto *Puntos;
public:
Figura(int N, Punto *Ptos)
{ NPuntos= N;
Puntos= Ptos;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 222

double Perimetro()
{ int i;
double Peri= 0;
Peri= Distancia(Puntos[NPuntos - 1], Puntos[0]);
for(i= 0; i<NPuntos-1; i++)
Peri+= Distancia(Puntos[i], Puntos[i + 1]);
return Peri;
}
};
class Circulo:public Figura
{ private:
double Radio;
public:
Circulo(int R, Punto *Ptos):Figura(1, Ptos)
{ Radio= R;
}
double Perimetro()
{ return 2*3.1416*Radio;
}
double Area()
{ return 3.1416*Radio*Radio;
}
};
class Rectangulo:public Figura
{ public:
Rectangulo(Punto *Ptos):Figura(4, Ptos)
{
}
double Area()
{ return (Puntos[0]-Puntos[1])*(Puntos[1]-Puntos[2]);
}
};
main()
{ Punto p[4]= {Punto(2, 2), Punto(2, 3), Punto(1, 3), Punto(1 ,2)};
Rectangulo r(p);
cout << r.Perimetro();
cout << r.Area();
getch();
}

4.4.2.- Herencia múltiple y clases virtuales.

Una clase puede heredar los atributos y los métodos de una o más clases base. En el caso de
que herede los componentes de una única clase se habla de herencia simple y en el caso de que herede
los componentes de varias clases base se trata de un caso de herencia múltiple.

Como ejemplo se puede presentar el caso de que se tenga una clase para el manejo de los datos
de la empresa. Se podría definir la clase C_CuentaEmpresarial como la herencia múltiple de dos
clases base: la clase C_Cuenta y nueva clase llamada C_Empresa, que se muestra a continuación:
#include <iostream.h>
class C_Cuenta {
private:
char *Nombre; // Nombre de la persona
double Saldo; // Saldo Actual de la cuenta
double Interes; // Interés aplicado
public:
C_Cuenta(const char *unNombre,double unSaldo=0,double unInteres=0)
{ Nombre = new char[strlen(unNombre)+1];
strcpy(Nombre, unNombre);
SetSaldo(unSaldo);
SetInteres(unInteres);
}
~Cuenta()
{ delete [] Nombre;
}
inline char *GetNombre()
{ return Nombre;
}

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 223

inline double GetSaldo()


{ return Saldo;
}
}

class C_Empresa {
private:
char *NomEmpresa;
public:
C_Empresa(const char*laEmpresa)
{ NomEmpresa = new char[strlen(laEmpresa)+1];
strcpy(NomEmpresa, laEmpresa);
}
~C_Empresa()
{ delete [] NomEmpresa;
}
// Otros métodos ...
};
class C_CuentaEmpresarial : public C_Cuenta, public C_Empresa {
public:
C_CuentaEmpresarial(
const char *unNombre,
const char *laEmpresa,
double unSaldo=0.0,
double unInteres=0.0
):C_Cuenta(unNombre,unSaldo, unInteres), C_Empresa(laEmpresa)
// se llama a los constructores de las clases base
{ // Constructor
}
// Otros métodos
};

Al utilizar la herencia múltiple puede suceder que, indirectamente, una clase herede varias
veces los miembros de otra clase. Es decir, si de la clase Calculadora heredan las clases Calculadora
de Euros y Calculadora Científica, y de estas hereda la clase Calculadora de Euros Científica, en esta
los componentes de la clase Calculadora se encontrarán duplicados.

Para evitar este problema los lenguajes lo hacen de tres formas:

- No está permitida la herencia múltiple.


- Hay que cualificar las componentes.
- Se crea una copia única de la clase base. (clase base virtual en C++)

Si la clase Madre_1 y la clase Madre_2 heredan los miembros de la clase Abuela y la clase
Hija hereda, a su vez, los miembros de las clases Madre_1 y Madre_2, los miembros de la clase
Abuela se encontrarán duplicados en la clase Hija. Para evitar este problema las clases Madre_1 y
Madre_2 deben derivar de la clase Abuela declarándola clase base virtual. Esto hace que los
miembros de una clase de ese tipo se hereden tan sólo una vez. Un ejemplo de declaración de una clase
base virtual es el que se presenta a continuación:
class Madre_1 : virtual public Abuela {
...
}

También puede darse otro problema: la duplicidad de componentes, es decir, varios


componentes con igual nombre (cada lenguaje que lo resuelva, en C++ se cualifica:
objeto.Clase::componente).

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 224

4.4.3.- Conversiones entre objetos de clases base y clases derivadas.

Es posible realizar conversiones o asignaciones de un objeto de una clase derivada a un objeto


de la clase base. Es decir se puede ir de lo más particular a lo más general, aunque en esa operación se
pierda información, pues haya variables que no tengan a qué asignarse (el número de variables
miembro de una clase derivada es mayor o igual que el de la clase de la que deriva).

Por el contrario las conversiones o asignaciones en el otro sentido, es decir de lo más general a
lo más particular, no son posibles, porque puede suceder que no se disponga de valores para todas las
variables miembro de la clase derivada.

Así pues, la siguiente asignación sería correcta:

Objeto_clase_base = Objeto_clase_derivada // Asignación válida

Objeto_clase_derivada = Objeto_clase_base // Asignación incorrecta

En el siguiente ejemplo se pueden ver las distintas posibilidades de asignación (más bien de
inicialización, en este caso), que se presentan en la clase C_CuentaEmpresarial.
void main()
{ // Válido
C_CuentaEmpresarial *c1 = new C_CuentaEmpresarial("Juan",
"Jugos SA", 100000.0, 10.0);
// Válido. Se utilizan los valores por defecto
C_Cuenta *c2 = new C_CuentaEmpresarial("Igor", "Patata CORP");

// NO VÁLIDO
C_CuentaEmpresarial *c3 = new C_Cuenta("Igor", 100.0, 1.0);
// ...
}

De forma análoga, se puede guardar la dirección almacenada en un puntero a una clase


derivada en un puntero a la clase base. Esto quiere decir que se puede hacer referencia a un objeto de
la clase derivada con su dirección contenida en un puntero a la clase base.

Al igual que sucede con los nombres de los objetos, en principio cuando se hace referencia a un
objeto por medio de un puntero, el tipo de dicho puntero determina la función miembro que se
aplica, en el caso de que esa función se encuentre definida tanto en la clase base como en la derivada.
En definitiva, un puntero a la clase base puede almacenar la dirección de un objeto perteneciente a
una clase derivada. Sin embargo, se aplicarán los métodos de la clase a la que pertenezca el puntero,
no los de la clase a la que pertenece el objeto.

5.- Polimorfismo.
Por definición, según el diccionario de la RAE, polimorfismo “es la propiedad que tienen
algunos cuerpos para cambiar de forma sin variar su naturaleza”; es decir, polimorfismo representa a la
capacidad de adoptar formas distintas. En el ámbito de la POO es la capacidad de llamar a métodos
distintos con el mismo nombre. Estos pueden actuar sobre objetos distintos dentro de una jerarquía de
clases, sin tener que especificar el tipo exacto de los objetos. Así, el polimorfismo adquiere una
relevancia especial, debido a que también los datos pueden presentar cierto tipo de polimorfismo: un
identificador puede hacer referencia a objetos de distintas clases jerárquicamente relacionadas,

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 225

dependiendo del contexto, del mismo modo que un identificador de función puede representar distintas
secciones de código.

5.1.- Sobrecarga de métodos y operadores.


Cuando el polimorfismo sobre una función u operador (no todos los lenguajes OO lo permiten)
se presenta en situaciones en las que el nombre de la misma coincide, pero sus distintos
comportamientos no tienen ninguna relación, utilizamos el término “sobrecarga” de funciones o de
operadores. Por ejemplo, la operación “sumar” se puede aplicar tanto a números enteros como reales,
pero no hay relación entre ellas. La diferencia entre éstas está en el número y/o tipo de sus parámetros.
#include <iostream>
using namespace std;

class Base{
int B1, B2;
public:
Base(int a, int b)
{ B1= a;
B2= b;
}
void Mostrar()
{ cout << B1 << " " << B2 << endl;
}
};

class Derivada : public Base{


int D;
public:
Derivada(int a, int b, int c):Base(b, c)
{ D= a;
}
void Mostrar()
{ cout << D << endl;
Base::Mostrar(); // Mostrar de la clase base
}
};

int main(){
Base base(1, 2);
Derivada derivada(3, 4, 5);
base.Mostrar(); // Llama a Mostrar() de la clase base
derivada.Mostrar(); // Llama a Mostrar() de la clase derivada
}

5.2.- Herencia de clases.

5.2.1.- Enlace estático.

Aunque la sobrecarga de métodos es una forma de polimorfismo, el término polimorfismo se


reserva para aquellos casos en los que la sobrecarga sobreviene como consecuencia de la herencia de
clases. La declaración del método debe ser idéntico y cada uno tendrá un comportamiento similar pero
no idéntico en diferentes clases de objetos y cada uno seguirá una implementación específica.

Si todos los objetos son asociados estáticamente a una clase, y no se tienen en cuenta los
posibles cambios de referencia, el método a aplicar puede determinarse en tiempo de compilación, sin
que el sistema sufra ningún tipo de sobrecarga. Esto se conoce como vinculación o enlace estático.
Esto se debe a que el compilador comprueba el tipo del objeto y dependiendo de cual sea llama al
método correspondiente.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 226

Rectángulo R;
R.Perimetro(); //Invoca a Perimetro() de R
Circulo C;
C.Perimetro(); //Invoca a Area() de C

Lo mismo sucede cuanto trabajamos con punteros o referencias a clases aunque no está exento
de problemas. Supongamos el siguiente fragmento de programa:
Figura F, *FP;
Circulo C;
FP= &C;
FP->Perímetro();

Por conversión entre clases un objeto o puntero a un objeto de una clase base acepta objetos o
punteros a objetos de sus clases derivadas. La última línea invoca al método Perímetro() de la clase
base, de Figura, produciéndose un error en los cálculos dado que dicho método no sabe calcular el
perímetro de un círculo. Las cosas se nos pueden poner peor si queremos calcular el área del circulo
puesto que Figura no dispone de ese método. Para solucionar el problema podemos recurrir a moldear
(cast) el objeto en cuestión:
((Circulo* )FP)->Perímetro();

De esta manera llamamos al método Perímetro() de Circulo aplicado a C.


class Figura
{ protected:
int NPuntos;
Punto *Puntos;
public:
Figura() { }
Figura(int N, Punto *Ptos)
{ NPuntos= N;
Puntos= Ptos;
}
virtual double Perimetro()
{ int i;
double Peri= 0;
Peri= Distancia(Puntos[NPuntos - 1], Puntos[0]);
for(i= 0; i<NPuntos-1; i++)
Peri+= Distancia(Puntos[i], Puntos[i + 1]);
return Peri;
}
};
class Circulo:public Figura
{ private:
public:
double Radio;
public:
Circulo(int R, Punto *Ptos):Figura(1, Ptos)
{ Radio= R;
}
double Perimetro()
{ return 2*3.1416*Radio;
}
double Area() const
{ return 3.1416*Radio*Radio;
}
};
class Rectangulo:public Figura
{ public:
Rectangulo(Punto *Ptos):Figura(4, Ptos)
{ }
double Area() const
{ return (Puntos[0]-Puntos[1])*(Puntos[1]-Puntos[2]);
}
};

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 227

5.2.2.- Enlace dinámico.

Cuando el método que se debe ejecutar no se puede determinar en tiempo de compilación, si no


que debe hacerse en tiempo de ejecución, se denomina vinculación o enlace dinámico. Para que se de
este tipo de vinculación no se puede trabajar con objetos estáticos si no que hay que trabajar con
objetos dinámicos: referencias o punteros a ellos. En la vinculación dinámica el método invocado no
depende del tipo del objeto usado para referenciarlo, si no que dependerá de la clase del objeto a la que
pertenece. La implementación del código para que exista vinculación dinámica depende del lenguaje
OO.

Para las funciones que vayan a tener un puntero para ligar el objeto con su método se definiran
funciones virtuales. Una función virtual el un método especial que se llama a través de un puntero o
una referencia a su clase base y se enlaza dinámicamente en tiempo de ejecución. La sintaxis para
definir un método virtual es el siguiente:

virtual <Tipo de retorno> <Nombre de método>(<Parámetros>);

El polimorfismo facilita la reutilización de software, haciendo posible implementar software


genérico, que se pueda aplicar no sólo a objetos existentes, si no también a objetos que sean añadidos
en etapas futuras del desarrollo.
class Figura
{ protected:
int NPuntos;
Punto *Puntos;
public:
Figura(){}
Figura(int N, Punto *Ptos)
{ NPuntos= N;
Puntos= Ptos;
}
virtual double Perimetro()
{ int i;
double Peri= 0;
Peri= Distancia(Puntos[NPuntos - 1], Puntos[0]);
for(i= 0; i<NPuntos-1; i++)
Peri+= Distancia(Puntos[i], Puntos[i + 1]);
return Peri;
}
virtual double Area() const= 0;
};

class Circulo:public Figura


{ private:
double Radio;
public:
Circulo(int R, Punto *Ptos):Figura(1, Ptos)
{ Radio= R;
}
virtual double Perimetro()
{ return 2*3.1416*Radio;
}
virtual double Area() const
{ return 3.1416*Radio*Radio;
}
};

class Rectangulo:public Figura


{ public:
Rectangulo(Punto *Ptos):Figura(4, Ptos){}
virtual double Area() const
{ return (Puntos[0]-Puntos[1])*(Puntos[1]-Puntos[2]);
}
};

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 228

int MayorArea(const Figura &f1, const Figura &f2)


{ int Area1, Area2;
Area1= f1.Area();
Area2= f2.Area();
if(Area1 < Area2)
return -1;
else if(Area1 > Area2)
return 0;
else
return 1;
}

main()
{ Punto p= Punto(2, 2);
Circulo c(2, &p);
Figura *pf;
pf= &c;
pf->Perimetro(); // Llama a Perimetro() de Círculo sin hacer el moldeo.
cout << MayorArea(r, c);
}

Ahora podemos trabajar con punteros o referencias a objetos con la seguridad de que llamarán
a las funciones correctas. Esto es muy útil cuando funciones que tienen como parámetros formales
punteros o referencias a una clase base reciben como parámetros actuales punteros o referencias a
objetos de clases derivadas. En el ejemplo anterior se ve claro este uso.

6.- Clases abstractas.


Una clase abstracta es aquella que sólo sirve como clase base para otras clases y no puede ser
instanciada (no puede haber objetos de esa clase) aunque si puede haber punteros o referencias a ella;
se crea para que contenga aquellos atributos y métodos comunes a todas las clases que deriven de ella.
Normalmente se trata de clases que representan conceptos abstractos que no pueden ser instanciados.

Una clase abstracta se construye incluyendo un método (método virtual puro) abstracto dentro
de su definición o indicando que la clase es abstracta –depende del lenguaje-. Las clases derivadas de
una clase abstracta deben declarar todos los métodos abstractos heredados, si los hay. En el caso de
que no se redefina alguno, o se redefina también como abstracto, la clase heredada también será
abstracta.

Un método abstracto define el protocolo o interfaz para que una operación polimórfica pueda
ser redefinida mediante métodos específicos correspondientes a sus subclases.

La sintaxis de declaración de un método virtual es la siguiente:

virtual <Tipo de retorno> <Nombre de método>(<Parámetros>)= 0;

La clase Figura es una clase abstracta por que no conocemos exactamente cual es el
comportamiento del método Area(), cada figura calcula el área de una forma diferente. Por ello,
dejamos que sea la subclase que herede de Figura la que implemente su comportamiento concreto, que
además está obligada a implementar para poder instanciar sus objetos. Sirve como clase base de otras
subclases.
class Figura
{ protected:
int NPuntos;
Punto *Puntos;
public:

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 229

Figura()
{
}

Figura(int N, Punto *Ptos)


{ NPuntos= N;
Puntos= Ptos;
}
virtual double Perimetro()
{ double Peri= 0;
Peri= Distancia(Puntos[NPuntos - 1], Puntos[0]);
for(int i= 0; i<NPuntos-1; i++)
Peri+= Distancia(Puntos[i], Puntos[i + 1]);
return Peri;
}
virtual double Area() const = 0; //Método virtual puro.
};

7.- Lenguajes de POO.


Según Wegner los lenguajes de POO se pueden clasificar en tres categorías: los asociados con
Ada, CLU y Smalltalk, respectivamente.

· Los lenguajes basados en objetos son aquellos que soportan la funcionalidad de los
objetos, es decir, aquellos que utilizan objetos. Esta familia de lenguajes incluye Ada, CLU,
Simula, Smalltalk, Eiffel, C++, Java y excluye a lenguajes como Fortran, Cobol o Pascal,
que no proporcionan objetos como primitiva del lenguaje (aunque existen variantes de estos
lenguajes que si soportan objetos).

· Los lenguajes que además incluyen el concepto de clase de objetos como entidad de
primera clase se denominan basados en clases. Estos siguen incluyendo a CLU, Simula,
Smalltalk, Eiffel, C++ y Java, pero no a Ada, debido a que sus clases de objetos no se
identifican con un tipo, no pueden ser pasadas como parámetros, ni ser componentes de un
registro.

· Por último, la clase de lenguajes realmente orientados a objetos es la más restrictiva, y


contiene a los lenguajes que soportan la funcionalidad de los objetos, su manipulación
mediante clases, y el manejo de éstas mediante herencia. Se pueden catalogar como
lenguajes OO: Simula, Smalltalk, Eiffel, C++ y Java. Y queda excluido CLU que, aunque
está basado en clases, éstas no pueden relacionarse mediante un mecanismo como la
herencia.

Las características mencionadas en esta clasificación en relación a la orientación a objetos


(clases + herencia) no son suficientes para poder obtener los beneficios reales de la orientación a
objetos, en términos de reutilización y extensibilidad. Hay otros aspectos que son también
trascendentales para poder utilizar un lenguaje orientado a objetos con la expresividad que caracteriza
a éstos: polimorfismo, vinculación dinámica, clases genéricas, clases abstractas, ...; en fin, todas las
características expuestas anteriormente.

Los lenguajes de POO pueden clasificarse, una vez eliminados los anteriores, en dos grandes
grupos: lenguajes puros y lenguajes híbridos. Los primeros sólo trabajan con programación orientada a
objetos (clases, objetos, métodos y mensajes) –Smalltalk, Java- y los segundos mezclan la POO con la
programación procedimental -C++-.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 230

Puros Híbridos
Ventajas · Más potencia. · Más rápido
· Flexibilidad para modificar el lenguaje. · Pasar de prog. procedimental a
· Trabajan con un único modelo. POO es fácil.
· Más fácil de aprender y enseñar.

Inconvenientes · Más lento. · Trabaja con dos modelos a la vez.


· Modificar el lenguaje es difícil.

Los tres lenguajes de POO que vamos a ver, Smalltalk, C++ y Java, tienen unas características
muy similares: encapsulación, polimorfismo, vinculación dinámica, herencia clases genéricas, clases
abstractas, etc.

7.1.- Lenguaje Smalltalk.


Es el lenguaje de POO por antonomasia. Creado por Alan Kay en los ochenta. Su versión
conocida como Smalltalk-80 apareció en 1983.

· Es interpretado lo que hace que resulte relativamente lento.


· Es un lenguaje de POO puro. Sólo trabaja con clases, objetos métodos y mensajes.
· Los objetos ocultan su información (encapsulación); sólo puede obtenerse por los cauces
adecuados.
· Las clases también son objetos.
· Tiene una librería jerárquica de clases bien definida y comienza en la clase Object.
· Sólo permite herencia simple.

7.2.- Lenguaje C++.


Es el lenguaje de POO posiblemente más utilizado en la actualidad. Creado por Bjarne
Stroustrup en los ochenta.

· Es compilado lo que hace que resulte muy rápido.


· Es un lenguaje de POO híbrido. Mezcla la programación en C con clases, objetos métodos
y mensajes.
· Los objetos ocultan su información (encapsulación) dependiendo de niveles de acceso; sólo
puede obtenerse por los cauces adecuados.
· Tiene una jerarquía de clases cambiante. Cada compilador de C++ tiene su propia jerarquía
de clases lo que hace muy difícil su aprendizaje. Algunas clases están estandarizadas.
· Permite herencia simple y múltiple.

7.3.- Lenguaje Java.


Es un lenguaje de POO creado por Sun Microsystems en los noventa (1995), tiene una sintaxis
semejante a la de C++ y su importancia se encuentra en la utilización dentro del marco de internet.

· Es seudocompildor lo que hace más lento que un compilador y más rápido que un
intérprete.

alozanoi@teleline.es Apuntes de clase


© A.L.I. D.A.I. Programación en lenguajes estructurados. Página: 231

· Es un lenguaje de POO puro. Sólo utiliza clases, objetos métodos y mensajes. Sin embargo
dispone de los tipos de datos simple (int, char, ...) aunque también están implementados en
clases.
· Los objetos ocultan su información (encapsulación) dependiendo de niveles de acceso; sólo
puede obtenerse por los cauces adecuados.
· Tiene una librería jerárquica de clases bien definida y estándar.
· Sólo permite herencia simple.
· Dispone de un recolector de basura.

alozanoi@teleline.es Apuntes de clase

You might also like