apuntes ple

241
P r o g r a m a c i ó n e n L e n g u a j e s E s t r u c t u r a d o s Ciclo de Desarrollo de Aplicaciones Informáticas

Upload: saul-velasco-garcia

Post on 25-Jun-2015

1.224 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Apuntes PLE

PPPPrrrrooooggggrrrraaaammmmaaaacccciiiióóóónnnn

eeeennnn

LLLLeeeennnngggguuuuaaaajjjjeeeessss

EEEEssssttttrrrruuuuccccttttuuuurrrraaaaddddoooossss

Ciclo de

Desarrollo de Aplicaciones Informáticas

Page 2: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 3: Apuntes PLE

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

[email protected] Apuntes de clase

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 L ENGUAJE 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

Page 4: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 5: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 6: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 7: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 8: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 9: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 10: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 11: Apuntes PLE

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

[email protected] Apuntes de clase

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 ORDE NADOR -> 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.

Page 12: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 13: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 14: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 15: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 16: Apuntes PLE

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

[email protected] Apuntes de clase

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.

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.

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

Inicio Fin

Entrada / Salida

Page 17: Apuntes PLE

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

[email protected] Apuntes de clase

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.

N

Page 18: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 19: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 20: Apuntes PLE

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

[email protected] Apuntes de clase

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:

Page 21: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 22: Apuntes PLE
Page 23: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 24: Apuntes PLE

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

[email protected] Apuntes de clase

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:

Page 25: Apuntes PLE

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

[email protected] Apuntes de clase

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:

c+3)+(b+a

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)

Page 26: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 27: Apuntes PLE

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

[email protected] Apuntes de clase

-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 A Y B 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 A O B 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'.

Page 28: Apuntes PLE

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

[email protected] Apuntes de clase

y)+5(x

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:

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

w)+y(z

x

a

w+n

y+x

2a

4ac-bb-=x

22 yx +

Page 29: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 30: Apuntes PLE
Page 31: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 32: Apuntes PLE

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

[email protected] Apuntes de clase

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:

PRECIO

Si aplicamos la sentencia de asignación

PRECIO � 8312

obtendremos un nuevo valor en el objeto 'PRECIO':

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

1253

8312

Page 33: Apuntes PLE

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

[email protected] Apuntes de clase

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:

a � 4 b � 25 c � a * 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.

Page 34: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 35: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 36: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 37: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 38: Apuntes PLE

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

[email protected] Apuntes de clase

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 :

Ni = parte entera Vf Vi

In

+ 1

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

Page 39: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 40: Apuntes PLE

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

[email protected] Apuntes de clase

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:

Page 41: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 42: Apuntes PLE

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

[email protected] Apuntes de clase

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 :

ei N

= = + + + + +∑1 1

0

1

1

1

2

1

3

1

! ! ! ! !...

!

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 ?

IC R

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.

Page 43: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 44: Apuntes PLE
Page 45: Apuntes PLE

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

[email protected] Apuntes de clase

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

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

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

Programa Subprograma

Programa Subprograma 1 Subprograma 1.1

Subprograma 2

2

1 ) (

x x

x f +

=

Page 46: Apuntes PLE

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

[email protected] Apuntes de clase

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)

Page 47: Apuntes PLE

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

[email protected] Apuntes de clase

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:

Page 48: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 49: Apuntes PLE

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

[email protected] Apuntes de clase

5.- Comunicación con subprogramas: paso de parámetr os. 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.

Page 50: Apuntes PLE

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

[email protected] Apuntes de clase

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

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.

)!(!

!

nmn

m

n

m

−=

Page 51: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 52: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 53: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 54: Apuntes PLE
Page 55: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 56: Apuntes PLE

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

[email protected] Apuntes de clase

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)

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+49 32 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+49 32 near (pointer) 32 bits no aplicable far (pointer) 32 bits no aplicable

Page 57: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 58: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 59: Apuntes PLE

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

[email protected] Apuntes de clase

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 ++i

Mientras i < n

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

Page 60: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 61: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 62: Apuntes PLE

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

[email protected] Apuntes de clase

5.- Precedencia y orden de evaluación de los operad ores.

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

Page 63: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 64: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 65: Apuntes PLE

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

[email protected] Apuntes de clase

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 derech a. Rellena a blancos. printf(":%-15s:\n",Cadena); // Justifica a la izqui erda. 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 derech a. printf(":%015d:\n",Entero); // Justifica a la derec ha. Rellena con ceros. printf(":%-15d:\n",Entero); // Justifica a la izqui erda. printf(":%15x:\n",EnteroH); // Hexadecimal minúscul as. printf(":%-15X:\n",EnteroH); // Hexadecimal mayúscu las 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

Page 66: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 67: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 68: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 69: Apuntes PLE

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

[email protected] Apuntes de clase

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 (a > b) z = a; else z = b;

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

Page 70: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 71: Apuntes PLE

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

[email protected] Apuntes de clase

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íne a 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>

Page 72: Apuntes PLE

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

[email protected] Apuntes de clase

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 con versiones: 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; }

Page 73: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 74: Apuntes PLE

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

[email protected] Apuntes de clase

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

... }

Page 75: Apuntes PLE

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

[email protected] Apuntes de clase

Sentencia goto.

Produce un salto incondicional a una etiqueta.

Goto <Etiqueta> Etiqueta:

Page 76: Apuntes PLE
Page 77: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 78: Apuntes PLE

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

[email protected] Apuntes de clase

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;

Page 79: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 80: Apuntes PLE

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

[email protected] Apuntes de clase

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; pun tero 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.

Page 81: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 82: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 83: Apuntes PLE

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

[email protected] Apuntes de clase

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.

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.

m

n= m! / (n!* (m-n)!)

Page 84: Apuntes PLE
Page 85: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 86: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 87: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 88: Apuntes PLE

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

[email protected] Apuntes de clase

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 dato s 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:

Page 89: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 90: Apuntes PLE

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

[email protected] Apuntes de clase

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

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.

Page 91: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 92: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 93: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 94: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 95: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 96: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 97: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 98: Apuntes PLE

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

[email protected] Apuntes de clase

int LimiteInferior = 0; int LimiteSuperior = ELTOS - 1; int ComponenteCentral = LimiteSuperior / 2; while (Valor != V[ComponenteCentral] && LimiteInfer ior < LimiteSuperior) { if (Valor > V[ComponenteCentral])

LimiteInferior = ComponenteCentral + 1; else

LimiteSuperior = ComponenteCentral - 1; ComponenteCentral = (LimiteInferior + LimiteSuperio r) / 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; }

}

Page 99: Apuntes PLE

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

[email protected] Apuntes de clase

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 d e 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.

Page 100: Apuntes PLE

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

[email protected] Apuntes de clase

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:

Page 101: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 102: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 103: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 104: Apuntes PLE

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

[email protected] Apuntes de clase

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 anidado s.

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 component e *(array + 1), array[1] Contenido de la segunda com ponente *(array + i), array[i] Contenido de la componente i

- Trabajando con puntero a entero

*p_array, p_array[0] Contenido de la primera compo nente *(p_array + 1), p_array[1] Contenido de la segunda componente *(p_array + i), p_array[i] Contenido de la componen te i &array[i], array + i p_array + i, &p_array[i]

Page 105: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 106: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 107: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 108: Apuntes PLE

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

[email protected] Apuntes de clase

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*COL UMNAS+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>

Page 109: Apuntes PLE

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

[email protected] Apuntes de clase

#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*siz eof(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_ar ray + 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);

}

Page 110: Apuntes PLE

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

[email protected] Apuntes de clase

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

}

Page 111: Apuntes PLE

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

[email protected] Apuntes de clase

Longitud variable con un puntero:

Prototipo: tipo funcion(tipo *array, int filas, in t 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, i nt 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;

}

Page 112: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 113: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 114: Apuntes PLE

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

[email protected] Apuntes de clase

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 cara cteres. 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", "Jue ves", "Viernes", "Sab", "Dom"}; char *cad4[]={"Lunes", "Martes", "Miercoles", "Juev es", "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; }

Page 115: Apuntes PLE

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

[email protected] Apuntes de clase

- 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", "D iaz", "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.

Page 116: Apuntes PLE

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

[email protected] Apuntes de clase

5.- Funciones de estándar para la manipulación de c adenas.

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.

Page 117: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 118: Apuntes PLE

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

[email protected] Apuntes de clase

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)

Page 119: Apuntes PLE

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

[email protected] Apuntes de clase

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>[,...]];

Page 120: Apuntes PLE

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

[email protected] Apuntes de clase

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 {

char nombre[15]; char apelli[30]; int expediente; int notas[3];

} la_persona;

struct {

char nombre[15]; char apelli[30]; int expediente; int notas[3];

} persona;

struct s_persona {

char nombre[15]; char apelli[30]; int expediente; int notas[3];

};

struct s_persona per1;

-----------------------

struct s_persona per2;

- Crea un tipo estructura (s_persona). - Crea una variable estructura (la_persona). - Se pueden definir más variables de tipo s_persona.

- Crea una variable estructura (persona). - No se pueden definir más variables estructura de ese tipo, salvo en la declaración.

- Crea un tipo estructura (s_persona) - Se pueden definir más variables de tipo s_persona. Para poder usar el nuevo tipo hace falta definir al menos 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",432 1};

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.

Page 121: Apuntes PLE

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

[email protected] Apuntes de clase

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<stdlib.h> #include<conio.h> #define TN 3

#include<stdio.h> #include<stdlib.h> #include<conio.h> #define TN 3

Page 122: Apuntes PLE

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

[email protected] Apuntes de clase

struct Nombre { char Apellido1[10]; char Apellido2[10];

char Nombre[10]; }; struct Fecha { short Dia;

short Mes; short Anho;

}; struct Estudiante { int Expediente;

struct Nombre El_Nombre; struct Fecha Fecha_Naci;

short Notas[TN]; short Final;

short Calificacion; };

struct Estudiante { int Expediente;

struct { char Apellido1[10];

char Apellido2[10]; char Nombre[10];

} sNombre; struct { short Dia;

short Mes; short Anho; }Fecha_Naci; short Notas[TN]; 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 co mpleto, 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.

Page 123: Apuntes PLE

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

[email protected] Apuntes de clase

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;

Page 124: Apuntes PLE

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

[email protected] Apuntes de clase

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 { <tipo1> <identificador_campo_1>; <tipo2> <identificador_campo_2>; ... <tipoN> <identificador_campo_N>; }<identificador_nuevo_tipo>;

typedef struct <estructura> <alias>;

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 { int Numero;

long Valor; } tVenta; tVenta Ventas1, Ventas2[10];

struct Venta { int Numero;

long Valor; }; 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.

Page 125: Apuntes PLE

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

[email protected] Apuntes de clase

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 E studiante)); 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. Alumnos = alta_alumno(); s_estudiante alta_alumno() { s_estudiante alumno;

scanf("%d",&alumno.expediente); return alumno;

}

Paso por referencia. Alta_alumno(&alumnos); void alta_alumno(s_estudian *alumno) { scanf("%d",&alumno->expedient); }

Pasando componentes de un array.

Estructura por valor. alta(*(alumnos + i)); void alta(s_estudiante alumno) { scanf("%d", &alumno.expedinte); }

Estructura por referencia. alta(alumnos + i); void alta(s_estudiante *alumno) { scanf("%d", &alumno->expedint); }

Page 126: Apuntes PLE

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

[email protected] Apuntes de clase

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

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].no mbre) < 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.

Page 127: Apuntes PLE

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

[email protected] Apuntes de clase

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 :

Page 128: Apuntes PLE

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

[email protected] Apuntes de clase

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 li sto-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.

Page 129: Apuntes PLE

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

[email protected] Apuntes de clase

· <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'; bre ak;

case Miercoles:cout << "Miércoles" << '\n'; break; }

printf(“%s”, Hoy) ; //Es un error. Hoy es un entero, no una cadena

}

Page 130: Apuntes PLE
Page 131: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 132: Apuntes PLE

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

[email protected] Apuntes de clase

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 usu ario.

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

Page 133: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 134: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 135: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 136: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 137: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 138: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 139: Apuntes PLE

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

[email protected] Apuntes de clase

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:

Page 140: Apuntes PLE

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

[email protected] Apuntes de clase

- 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 b

Son dos modificadores que pueden acompañar como sufijo a las seis modalidads anteriores 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);

}

Page 141: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 142: Apuntes PLE

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

[email protected] Apuntes de clase

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(destin o); 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", nom bre);

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

Page 143: Apuntes PLE

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

[email protected] Apuntes de clase

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[, d irecciones, ...]);

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

Page 144: Apuntes PLE

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

[email protected] Apuntes de clase

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, 3 0, 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"); }

Page 145: Apuntes PLE

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

[email protected] Apuntes de clase

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,

Page 146: Apuntes PLE

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

[email protected] Apuntes de clase

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

}

Page 147: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 148: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 149: Apuntes PLE

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

[email protected] Apuntes de clase

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, r ewind(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.

Page 150: Apuntes PLE

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

[email protected] Apuntes de clase

void Buscar(FILE *F, CLAVE X) { REGISTRO R;

fseek(F,0, SEEK_SET); // o bien abrir el archivo, r ewind(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, r ewind(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);

Page 151: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 152: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 153: Apuntes PLE

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

[email protected] Apuntes de clase

#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, NU ME 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

Page 154: Apuntes PLE

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

[email protected] Apuntes de clase

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

}

Page 155: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 156: Apuntes PLE

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

[email protected] Apuntes de clase

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.R CN) { TRATAR R

fread(&R, sizeof(R), 1, F); } // Operaciones tras ruptura de control ...

} // Operaciones tras ruptura de control

} // Operaciones tras ruptura de control

Page 157: Apuntes PLE

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

[email protected] Apuntes de clase

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 ARTICU LO // 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_ar ticulo); 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_ar ticulo); fclose(f_articulo); }

Page 158: Apuntes PLE

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

[email protected] Apuntes de clase

// 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_art iculo); 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(segu ir)!='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.descr ipcion[i]); fflush(stdin); scanf("%d", &s_almacen.ventas[i]); } fwrite(&s_almacen, sizeof(s_almacen), 1, f_al macen);

} }while(toupper(seguir)=='S'); fclose(f_almacen); }

Page 159: Apuntes PLE

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

[email protected] Apuntes de clase

// 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_art iculo); 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_alm acen); 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 art iculo) { 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.descripc ion[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]; }

Page 160: Apuntes PLE

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

[email protected] Apuntes de clase

// Ventas de artículos. ACTUALIZA EL ARCHIVO ALMACE N 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_errore s; 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.m over.codigo,s_almacen.codigo)>0) { fwrite(&s_almacen, sizeof(ALMACEN), 1, f_fina l); fread(&s_almacen, sizeof(ALMACEN), 1, f_ almacen); }

if(strcmp(s_movimiento.mover.codigo,s_almacen.codig o)==0) switch (s_movimiento.accion) { case 'M': for (i=0; i<NARTICULOS; i++) s_movimiento.mover.ventas[i]+= s_a lmacen.ventas[i]; fwrite(&s_movimiento.mover, sizeof(ALMA CEN), 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(ALMA CEN), 1, f_final); else

fwrite(&s_movimiento, sizeof(MOVIMIENTO), 1, f_err ores); } while (!feof(f_almacen)) { fwrite(&s_almacen, sizeof(ALMACEN), 1, f_fin al); fread(&s_almacen, sizeof(ALMACEN), 1, f_alma cen); } fclose(f_almacen); fclose(f_movimiento); fclose(f_final); fclose(f_errores); //remove(EX_ALMACEN); //rename(EX_FINAL, EX_ALMACEN); }

Page 161: Apuntes PLE

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

[email protected] Apuntes de clase

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(ALUMN OS), 1, f)) hallado = (stricmp(estudiante.nombre, n)==0) && e studiante.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) && e studiante.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.cposta l); 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?>ciu dad); fflush(stdin);gotoxy(40,8);gets(estudiante?>cpo stal); fflush(stdin);gotoxy(40,9);scanf("%d", &estudia nte?>edad); estudiante?>activo = 1; }

Page 162: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 163: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 164: Apuntes PLE

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

[email protected] Apuntes de clase

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 ',MAYOREDA D,

'y sueldo mayor a ',MAYORNETO,' son ',NUMPER

Page 165: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 166: Apuntes PLE
Page 167: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Representación gráfica de un nodo con dos campos

LISTA Representación gráfica de una lista

Información Enlace

Page 168: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 169: Apuntes PLE

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

[email protected] Apuntes de clase

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 ente ro 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 l istas.

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:

Page 170: Apuntes PLE

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

[email protected] Apuntes de clase

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.

void InsertaAlPrincipio(TLista *Lista, TInformacion Info) { TLista Nuevo;

Nuevo= (TLista) malloc(sizeof(Nodo)); Nuevo->Dato= Info; Nuevo->Siguiente= *Lista;// Nuevo apunta a inicio d e la lista *Lista= Nuevo; // Nuevo pasa a ser cabecera de l ista

}

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

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 1

Lista Nuevo Lista Reserva de memoria Segundo elemento encadenado

Representación gráfica de la inserción del segundo elemento en una lista

Dato 1 Dato 1 Dato 2 Dato 2

Page 171: Apuntes PLE

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

[email protected] Apuntes de clase

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.

void InsertarEnMmedio(TLista *Lista, TInformacion I nfo) { 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.

Lista

Posicion Nuevo Lista encadenada. Situación inicial. Reserva de memoria.

Dato 1 Dato 2 Dato3 Dato

SEGUNDO PASO: Posicion->Enlace= Nuevo

Lista

Posicion Nuevo PRIMER PASO: Nuevo->Enlace= Posicion->Enlace

Representación gráfica de la inserción de un nodo entre otros dos

Dato 1 Dato 2 Dato3 Dato

Page 172: Apuntes PLE

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

[email protected] Apuntes de clase

void InsertarOrdenado(TLista *Lista, TInformacion I nfo) { 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 a nterior

Lista= Lista->Siguiente; //Se toma el siguient e 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->Sigui ente->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->D ato)

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

} }

Page 173: Apuntes PLE

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

[email protected] Apuntes de clase

La implementación de un recorrido que busca un elemento en una lista ordenada es el

siguiente:

TLista BuscarOrdenado(TLista Lista, TInformacion In fo) { 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.

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

· Liberar la memoria ocupada por el nodo borrado.

void Borrar(TLista *Lista, TInformacion Info) { TLista Anterior, Auxiliar= *Lista;

int Encontrado= 0;

Primer paso: Lista= Lista->Enlace

Lista Segundo paso: free(Auxiliar)

Auxiliar

Borrar el primer nodo de la lista.

Dato 1 Dato 2 Dato 3 Dato 4

Primer paso: Anterior->Enlace= Auxiliar->Enlace

Lista Segundo paso: free(Auxiliar)

Anterior Auxiliar

Borrar un nodo intermedio o el último de la lista.

Dato 1 Dato 2 Dato 3 Dato 4

Page 174: Apuntes PLE

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

[email protected] Apuntes de clase

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.

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.

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.

Lista circular

Lista circular con nodo cabecera

Page 175: Apuntes PLE

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

[email protected] Apuntes de clase

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 dire cción de nuevo

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.

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 ;

}

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

Dato 1 Dato 1

Paso 3 Paso 2 Paso 1 Lista

Inserción de los nodos 2, 3, y 4 en la lista circular.

Dato 4 Dato 3 Dato 2 Dato 1

Page 176: Apuntes PLE

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

[email protected] Apuntes de clase

else { Nuevo->Siguiente= (*Lista)->Siguiente;

(*Lista)->Siguiente= Nuevo; // Podemos variar el nodo cabecera para realizar un a inserción // al final de la lista, detrás del último insertad o 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.

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

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.

· Liberar la memoria ocupada por el nodo borrado.

Lista= NULL Lista Free(Auxiliar) Lista, Auxiliar, Anterior

Dato 1

(2) Anterior->Siguiente= Auxiliar->Siguiente (3) free(Auxiliar) (1) Lista= Anterior

Anterior Lista, Auxiliar

Dato 4 Dato 3 Dato 2 Dato 1

(1) Anterior->Siguiente= Auxiliar->Siguiente

(2) free(Auxiliar)

Anterior Auxiliar Lista

Dato 4 Dato 3 Dato 2 Dato 1

Page 177: Apuntes PLE

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

[email protected] Apuntes de clase

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 primer o *Lista= Anterior; Anterior->Siguiente= Auxiliar->Siguient e; }

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 Anterior

Información Puntero Siguiente

Nodo de lista doble

Page 178: Apuntes PLE

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

[email protected] Apuntes de clase

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.

4.1.- Operaciones sobre listas doblemente enlazadas

4.1.1.- Funciones de inicializar, vacía, crear un n uevo nodo.

void Inicializa(TLista *Cabecera, TLista *Final) { *Cabecera= *Final= NULL; } int Vacia(TLista Cabecera) //Puede pasarse ‘Cabecer a’, ‘Final’ o ambos { return Cabecera == NULL; } Tlista NuevoNodo(TInformacion Info) { TLista Nuevo= (TLista) malloc(sizeof(Nodo));

Nuevo->Dato= Info; return Nuevo;

}

Cabecera Lista Doblemente enlazada Fin

Cabecera Lista doblemente enlazada circular

Page 179: Apuntes PLE

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

[email protected] Apuntes de clase

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.

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

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.

Cabecera Final

Lista vacía Cabecera Final

Dato 1 Cabecera= Nuevo ; Final= Nuevo ; Nuevo->Anterior= NULL ; Nuevo->Siguiente= NULL

(1) (2)

(3) (4)

Nuevo Cabecera Final

Dato 1 (1) Nuevo->Anterior= NULL; (2) Nuevo->Siguiente= Cab; (3) Cab->Anterior= Nuevo; (4) Cab= Nuevo;

Dato 2

Page 180: Apuntes PLE

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

[email protected] Apuntes de clase

void InsertarAlFinal(TLista *Cabecera, TLista *Fina l, 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 apun tado.

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

void InsertarMedioDetras(TLista *Cabecera, TLista * Final, TLista Posicion, TInformacion Info)

{ TLista Nuevo; if (Vacia(*Cabecera) || Posicion == NULL || Posicio n->Siguiente == NULL)

InsertarAlFinal(Cabecera, Final, Info); else { Nuevo= NuevoNodo(Info);

Nuevo->Anterior= Posicion; Nuevo->Siguiente= Posicion->Siguiente; Posicion->Siguiente->Anterior= Nuevo; Posicion->Siguiente= Nuevo;

} }

(3) (1)

(2) (4) Cabecera Final Nuevo

Dato 2 (1) Nuevo->Siguiente= NULL; (2) Nuevo->Anterior= Fin; (3) Fin->Siguinete= Nuevo; (4) Fin= Nuevo;

Dato 1

(4) (3) Cabecera Posicion Final

(1) (2) Nuevo

Dato 2 (1) Nuevo->Ant= Pos; (2) Nuevo->Sig= Pos->Sig; (3) Pos->Sig->Ant= Nuevo; (4) Pos->Sig= Nuevo;

Dato 1

Dato 3

Page 181: Apuntes PLE

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

[email protected] Apuntes de clase

4.1.2.4.- Insertar entre dos nodos. Delante del apu ntado.

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

void InsertarMedioDelante(TLista *Cabecera, TLista *Final, TLista Posicion, TInformacion Iinfo) { TLista Nuevo;

if (Vacia(*Cabecera) || Posicion == NULL || Posicio n->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.

(3) (4) Cabecera Posición, Final

(1) (2) Nuevo

Dato 2 (1) Nuevo->Ant= Pos->Ant; (2) Nuevo->Sig= Pos; (3) Pos->Ant->Sig= Nuevo; (4) Pos->Ant= Nuevo;

Dato 1

Dato 3

Page 182: Apuntes PLE

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

[email protected] Apuntes de clase

· Si es el primero de la lista. Borrar el primer nodo de la lista y desplazar la cabecera al

segundo.

· Es el último de la lista. Borrar el último nodo y desplazar la cabecera al penúltimo.

· Es un nodo intermedio. Enlazar el nodo anterior con siguiente del que hay que borrar.

· Liberar la memoria ocupada por el nodo que contiene la información que queremos eliminar.

void Borrar(TLista *Cabecera, TLista *Final, TInfor macion Info) { TLista Auxiliar= *Cabecera;

int Encontrado= 0; while (!Encontrado && Auxiliar != NULL)

if (Auxiliar->Dato == Info) Encontrado= 1; else Auxiliar= Auxiliar->Siguiente;

(3) (1) (2)

Cabecera, Auxiliar, Final

Dato 1 (1) Cabecera= NULL; (2) Final= NULL; (3) free(Auxiliar);

(2)

(1) Auxiliar Cabecera Final

Dato 2 (1) Cab= Cab->Siguiente; (2) Cab->Anterior= NULL; (3) free(Auxiliar);

Dato 1

(2)

(1) Cabecera Final Auxiliar

Dato 1 (1) Fin= Fin->Anterior ; (2) Fin->Siguiente= NULL ; (3) free(Auxiliar) ;

Dato 2

Cabecera Auxiliar Final

Dato 2 Dato 1 Dato 3

(1) Aux->Ant->Sig= Aux->Sig ; (2) Aux->Sig>Ant= Aux->Ant ; (3) free(Auxiliar) ;

Page 183: Apuntes PLE

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

[email protected] Apuntes de clase

if (Encontrado) { if (*Cabecera == *Final) // Si sólo hay un nodo Inicializa(Cabecera, Final);

else if(auxiliar == *cab) // Es el primero de la li sta { *Cabecera= (*Cabecera)->Siguiente;

(*Cabecera)->Anterior= NULL; } else if(Auxiliar == *Final) // Es el último de la l ista { *Final= (*Final)->Anterior;

(*Final)->Siguiente= NULL; } else // Es un nodo intermedio { Auxiliar->Anterior->Siguiente= Auxiliar->Siguien te;

Auxiliar->Siguiente->Anterior= Auxiliar->An terior; }

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

}

Page 184: Apuntes PLE

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

[email protected] Apuntes de clase

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

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

Dato 3

Dato 2

Dato1

Cima Representación interna y gráfica de una pila.

Dato 3 Dato 2 Dato 1

Page 185: Apuntes PLE

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

[email protected] Apuntes de clase

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

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.

Dato 3 Dato 2 Dato 1

Frente Final

Representación interna y gráfica de una pila.

Dato 1 Dato 2 Dato 3

Page 186: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 187: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 188: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 189: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 190: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 191: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 192: Apuntes PLE

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

[email protected] Apuntes de clase

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 :

A 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

Page 193: Apuntes PLE

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

[email protected] Apuntes de clase

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 subárbol izquierdo

Información almacenada en el

nodo

Puntero al subárbol 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;

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

Datos

Datos Datos

Datos Datos Datos Datos

Page 194: Apuntes PLE

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

[email protected] Apuntes de clase

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

}

Page 195: Apuntes PLE

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

[email protected] Apuntes de clase

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. 1 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->Da to) 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); } }

Page 196: Apuntes PLE

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

[email protected] Apuntes de clase

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->Dat o)

} }

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 In fo) { 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 arbo l return Arbol; else return NULL;

} La función recursiva para el mismo propósito es la siguiente :

TArbol BuscarOrdenadoRecursivo(TArbol Arbol, TInfor macion Info) { if (Arbol == NULL) return NULL;

else if (Arbol->Dato == Info) return Arbol; else if (Arbol->Dato > Info)

BuscarOrdenadoRecursivo(Arbol->AIz, Info); // retu rn ... else

BuscarOrdenadoRecursivo(Arbol->ADe, Info); // retur n ... }

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

Page 197: Apuntes PLE

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

[email protected] Apuntes de clase

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 I nfo) { 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 de l 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.

Page 198: Apuntes PLE

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

[email protected] Apuntes de clase

void BorrarOrdenado(TArbol *Arbol, TInformacion Inf o) { 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; }

}

Page 199: Apuntes PLE

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

[email protected] Apuntes de clase

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 estructur a 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 lengu aje 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. }

Page 200: Apuntes PLE

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

[email protected] Apuntes de clase

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 d e 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 co nstante. Nombre[0]= ‘M’; // Permitido. Puede cambiar su val or.

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;

Page 201: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 202: Apuntes PLE

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

[email protected] Apuntes de clase

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 Secuenc ia= 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 so n 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;

}

Page 203: Apuntes PLE

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

[email protected] Apuntes de clase

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;

Page 204: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 205: Apuntes PLE

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

[email protected] Apuntes de clase

strcpy(CopiaNombre, Nombre); //Copia Nombre en la v ariable 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: ", nu m); 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;

Page 206: Apuntes PLE

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

[email protected] Apuntes de clase

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 Comple jo b); Complejo operator + (const Complejo a, const Comple jo 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" << e ndl; } 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 Comple jo b) { Complejo Temp; Temp.Real= a.Real - b.Real; Temp.Imaginaria= a.Imaginaria - b.Imaginaria; return Temp; }

Page 207: Apuntes PLE

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

[email protected] Apuntes de clase

Complejo operator + (const Complejo a, const Comple jo 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); }

Page 208: Apuntes PLE
Page 209: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 210: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 211: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 212: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 213: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 214: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 215: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 216: Apuntes PLE

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

[email protected] Apuntes de clase

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

}

Page 217: Apuntes PLE

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

[email protected] Apuntes de clase

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 :

Page 218: Apuntes PLE

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

[email protected] Apuntes de clase

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 :

Page 219: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 220: Apuntes PLE

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

[email protected] Apuntes de clase

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 Imaginari a) { 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.

}

Page 221: Apuntes PLE

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

[email protected] Apuntes de clase

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, co nst Complejo &C2); ... }; Complejo operator + (const Complejo &C1, const Comp lejo &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) ;

};

Page 222: Apuntes PLE

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

[email protected] Apuntes de clase

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 Punt o &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 ; Punto p= Punto() ;

Llaman al constructor por defecto. La segunda forma no suele usarse.

Punto p() ; Punto p= Punto ;

Incorrectas.

Punto p(3, 2) ; Punto p= Punto(3, 2) ;

Llaman al constructor con dos parámetros Punto(double, double). La segunda no se usa.

Punto p(3) ; Punto p= Punto(3) ;

Llaman al constructor con un parámetro Punto(double). La segunda no se usa.

Page 223: Apuntes PLE

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

[email protected] Apuntes de clase

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 Punt o &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 memori a no se libera strcpy(LaCadena, Tira); //cuando se destruye el objeto. } Cadena::~Cadena() { delete []LaCadena; //Hay que destruirla explícita mente. } 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) ;

Page 224: Apuntes PLE

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

[email protected] Apuntes de clase

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 asig nación { x= p.x ; y= p.y ; } void Desplazar(double a, double b) { x+= a ; x+= b ; } friend double Distancia(const Punto &a, const Punt o &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 operad or !=. { delete []LaCadena; //Borra el espacio actual

LaCadena= new char[strlen(c.LaCadena)+1]; //Crea nu evo espacio strcpy(LaCadena, c.LaCadena); //Copia la cadena

} return *this;

}

Page 225: Apuntes PLE

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

[email protected] Apuntes de clase

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), Pu nto(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;

Page 226: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 227: Apuntes PLE

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

[email protected] Apuntes de clase

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.

Page 228: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 229: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 230: Apuntes PLE

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

[email protected] Apuntes de clase

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 acce so. 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. } ...

}

Page 231: Apuntes PLE

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

[email protected] Apuntes de clase

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ública

Públicos Protegidos Privados

Públicos Protegidos sin acceso

protegida

Públicos Protegidos Privados

Protegidos Protegidos sin acceso

privada

Públicos Protegidos Privados

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 Punt o &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; }

Page 232: Apuntes PLE

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

[email protected] Apuntes de clase

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]-Pu ntos[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,doub le unInteres=0) { Nombre = new char[strlen(unNombre)+1];

strcpy(Nombre, unNombre); SetSaldo(unSaldo); SetInteres(unInteres);

} ~Cuenta() { delete [] Nombre; } inline char *GetNombre() { return Nombre; }

Page 233: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 234: Apuntes PLE

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

[email protected] Apuntes de clase

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", "Pat ata 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,

Page 235: Apuntes PLE

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

[email protected] Apuntes de clase

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 ba se } }; int main(){ Base base(1, 2); Derivada derivada(3, 4, 5); base.Mostrar(); // Llama a Mostrar() de la clas e base derivada.Mostrar(); // Llama a Mostrar() de la c lase 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.

Page 236: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 237: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 238: Apuntes PLE

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

[email protected] Apuntes de clase

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:

Page 239: Apuntes PLE

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

[email protected] Apuntes de clase

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

Page 240: Apuntes PLE

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

[email protected] Apuntes de clase

Puros Híbridos Ventajas · Más potencia.

· Flexibilidad para modificar el lenguaje. · Trabajan con un único modelo. · Más fácil de aprender y enseñar.

· Más rápido · Pasar de prog. procedimental a

POO es fácil.

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.

Page 241: Apuntes PLE

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

[email protected] Apuntes de clase

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