View
239
Download
2
Category
Preview:
DESCRIPTION
Citation preview
TEMA 1 -‐.-‐ INTRODUCCIÓN 1.1-‐ Programación e ingeniería del software ¿Qué es un algoritmo? Conjunto de pasos necesarios para resolver un determinado problema. Los algoritmos deben ser: -‐ Correctas -‐ Claros -‐ Eficientes ¿Qué es un programa? Es la implementación o codificación de un determinado algoritmo en uso de algún lenguaje de programación. Lenguaje de programación: Conjunto de símbolos y reglas para crear programas. ¿Programación e ingeniería del software? Programación = En general se encarga de desarrollar programas a pequeña escala. Ingeniería del software = Conjunto de etapas necesarias para desarrollar programas a gran escala y de gran calidad. Etapas de la ingeniería del software: Análisis Diseño Codificación Pruebas y Mantenimiento -‐ Compiladores y interpretes ¿Qué es un traductor? Es un programa que traduce el código fuente de un programa a un código que pueda entender la CPU. Hay dos tipos de traductores:
• Compiladores • Intérpretes
-‐ Modelos abstractos de programación Actualmente existen diferentes modelos de programación (o paradigmas de programación): -‐ Programación funcional -‐ Programación lógica -‐ Programación imperativa -‐ Modelo de flujo de datos -‐ Programación orientada a objetos (POO) -‐Objetivos de la programación
• CORRECIÓN: Un programa debe realizar el tratamiento esperado, y no producir resultados erróneos.
• CLARIDAD: Prácticamente todos los programas han de ser modificados después de haber sido desarrollados inicialmente. Es fundamental que sus descripciones sean claras y fácilmente inteligibles por otras personas.
• EFICIENCIA: Una tarea de tratamiento de información puede ser programada de muy diferentes maneras sobre un computador determinado, es decir, habrá muchos programas distintos que producirán los resultados deseados.
TEMA 2 -‐.-‐ ELEMENTOS_BASICOS 2.1 -‐ Notación BNF (backups-‐Naur Form) Es el conjunto de reglas gramaticales en las que se basa un lenguaje de programación. Para describir estas reglas se utilizan una seria de meta símbolos:
::= Meta símbolo de definición | Meta símbolo de alternativa { } Meta símbolo de repetición [ ] Meta símbolo de opción
( ) Meta símbolo de agrupación
Las reglas BNF tendrán dos tipos de elementos: • Elemento terminal: Son elementos que forman parte del lenguaje C+-‐ • Elemento no terminal: Están definidos a partir de los elementos terminales a partir de
las reglas de producción.
2.2 Tipos predefinidos • Tipo entero (int) Este tipo puede tomar valores positivos y negativos. El rango de
valores que puede tomar depende de la plataforma (combinación de procesador, sistema operativo y compilador) utilizada. Para poder controlar los valores máximo y mínimo que podemos utilizar no es preciso recordar estos valores, podemos utilizar las constantes INT_MAX y INT_MIN que están en la librería <limits.h>
• Tipo real (float)
Este tipo puede tomar valores positivos y negativos, y puede tener una parte decimal. Es una representación no exacta. El rango de valores que puede tomar depende de la plataforma (combinación de procesador, sistema operativo y compilador) utilizada.
• Tipo carácter (char)
Lo utilizaremos para manejar caracteres imprimibles: "A", "a", "B", "b", ... ."@","{", .... y no imprimibles: RETURN, ESCAPE, F1, F2, .... Incluye la práctica totalidad de caracteres utilizados en el Mundo Para hacer referencia a un carácter de la mesa del código ascii podemos hacer uso de la función char(x): char(13) à Tecla Return char(27) à tecla ESCAPE char(65) à letra "A" También podemos hacer referencia al lugar de la tabla que ocupa un carácter en uso de la función int(c):
int ("Z")à90
Si hacemos uso de la librería <ctype.h> podemos utilizar las siguientes funciones para a manejar caracteres: -‐isalpha(c)àComprueba si c es una letra o no isascii(c)àComprueba si c es un carácter ASCII -‐isblank(c)àComprueba si c es un espacio en blanco o tabulación iscntrl(c)àComprueba si c es un carácter de control o no isdigit(c)àComprueba si c es un dígito decimal (0 .. 9) o no -‐-‐-‐-‐tolower(c)àPasa a minúscula el carácter c -‐toupper(c)àPasa a mayúscula el carácter c
2.3 Expresiones aritméticas Si no se utilizan paréntesis el orden de prioridad en una expresión es:
1. Operadores multiplicativos * / % 2. Operadores aditivos + -‐
2.4 Operaciones de escritura simples El procedimiento printf está en la librería <stdio.h> y sirve para mostrar o escribir por pantalla un valor: Ejemplos: printf ("Introduce tu nombre:"); printf ("-‐-‐-‐-‐ Bienvenido a este programa -‐-‐-‐ \ n "); Cuando a parte de texto queremos mostrar valores numéricos, la función printf necesita saber el tipo de datos que tiene que escribir para saber cómo las ha de escribir. El formato o forma de utilizar esta función es: printf ("cadena con formatos", valor1, valor3, ... valorN); Nota: no es obligatorio el signo de % de principio del especificador, y el tipo de datos que se quiere mostrar. Esto es lo más normal y lo que se ve en esta asignatura. En caso de que se quiera especificar más los datos de salida se utilizan los demás parámetros, que están entre "corchetes". Los tipos habituales:
Una cadena de caracteres: s Un carácter: c Valor real con / o sin notación exponencial, con un número dado de decimales de precisión .Los ceros y puntos decimal salen si es necesario: g Valor real con notación exponencial e [+/-‐] ddd. e Valor real (punto fijo) de la forma [-‐] dddd.dddd. f Entero decimal (un entero): d Nota: %10.3f real en coma fija que reserva 10 espacios y una precisión de 3
decimales
2.5 -‐ ESTRUCTURA DE UN PROGRAMA EN C / C + -‐ CABECERA DEL PROGRAMA / * Nombre del programa. Autor * / / * Descripción del programa * / LLAMADAS EN LIBRERÍAS Y #include <nombre_libreria.h> DEFINICIONES DE CONSTANTES const tipo nombre_constante = valor DECLARACIÓN DE FUNCIONES tipo_dato función A (parámetros) tipo_dato función B (parámetros) DECLARACIÓN DE VAIABLES GLOBALES int a; float b; FUNCIÓN PRINCIPAL tipo_dato main () { instrucciones }
TEMA 3 -‐.-‐ CONSTANTES Y VARIABLES 3.1-‐ IDENTIFICADORES:
Cualquier dato que se utiliza en un programa tiene asociada los siguientes atributos:
Identificador Tipo Valor Reglas para identificador: -‐Palabra formada por caracteres alfabéticos o numéricos -‐No valen los espacios en blanco ni signos de puntuación -‐Es necesario que comience por una letra -‐Pueden utilizarse las 52 letras mayúsculas y minúsculas del alfabeto inglés, la barra de subrayado y los dígitos de 0 a 9.
3.2 -‐ CONSTANTES: Son datos que mantienen su valor durante toda la ejecución del programa. La forma de declarar una constante es: Const tip nombre =valor;
3.3-‐ VARIABLES Son datos que pueden ir variando su valor durante la ejecución del programa. La forma de declarar la variable es: tipoDeVariable nombreVariable;
3.4-‐ LECTURA DE VARIABLES (SCANF) Para leer variables desde el teclado hacemos uso de la función scanf que está en la librería <stdio.h>
Scanf (“%d”,&a); à lee un valor entero por teclado, el valor se asigna a la variable int a;
TEMA 4 -‐.-‐ METODOLOGÍA 1 4.1 – DESCOMPOSICIÓN EN SUBPROBLEMAS(REFINAMIENTOS SUCESIVOS)
A la hora de resolver problemas complejos lo mejor es intentar descomponer el problema en subproblemas más pequeños y más fáciles de resolver. Consiste en expresar inicialmente el programa a desarrollar como una acción global, que si es necesario se irá descomponiendo en acciones más sencillas hasta llegar a acciones simples que puedan ser expresadas directamente como sentencias del lenguaje de programación. Cada paso de refinamiento consiste en descomponer cada acción compleja en otras más simples. Esta descomposición exige:
o Identificar las acciones componentes. o Identificar la manera de combinar las acciones componentes para conseguir el
efecto global.
4.2 – ASPECTOS DE ESTILO • Encolumnado • Comentarios • Elección de nombres • Uso correcto de mayúsculas y minúsculas • Constantes con nombre.
TEMA 5 -‐.-‐ ESTRUCTURAS DE CONTROL 5.1 – Programación estructurada
Es una metodología de programación que fundamentalmente trata de construir programas que sean fácilmente comprensibles. Un programa no solo debe funcionar correctamente, sino que ademças debe estar escrito de manera que se facilite su compresión posterior. -‐Representación de la estructura de un programa La estructura de los programas imperativos se representa tradicionalmente mediante diagramas de flujo llamados en ingles flow-‐chart. Estos diagramas contienen dos elementos básicos, correspondientes a acciones y condiciones. Las acciones son rectángulos y las condiciones son rombos. Las condiciones equivalen a preguntas a las que se puede responder “SI” o “NO”. El flujo de control durante la ejecución del programa se refleja mediante líneas o vías que van de un elemento a otro. Las acciones tienen una sola vía de entrada o comienzo y una de terminación o salida. Las condiciones tienen una vía de entrada, y dos vías de salida marcadas son “SI” y “NO”. La programación estructurada recomienda descomponer las acciones usando las estructuras más sencillas posibles, que son:
• Secuencia: secuencia de acciones. • Selección: consiste en ejecutar una acción u otra. • Iteración: repetición de una acción mientras que se cumpla una determinada
condición. (Bucle)
5.2 -‐ ESTRUCTURAS BÁSICAS EN C+-‐ • Sentencia IF • Sentencia WHILE • Sentencia FOR
TEMA 6 -‐.-‐ METODOLOGÍA 2 6.1 – DESARROLLO CON ESQUEMAS DE SELECCIÓN E ITERACIÓN
Se tiente 3 posibilidades a la hora de refinar una acción compuesta: • Organizarla como secuencia de acciones • Organizarla como selección entre acciones alternativas. • Organizarla como iteración de acciones.
Acción SI NO
1.1 ESQUEMAS DE SELECCIÓN Consiste en plantear una acción compuesta como la realización de una acción entre varias posibles, dependiendo de ciertas condiciones. Para desarrollar un esquema de selección debemos identificar sus elementos componentes. Por tanto habrá que:
• Identificar cada una de las alternativas del esquema, y las acciones correspondientes.
• Identificar las condiciones para seleccionar una alternativa u otra.
1.2 ESQUEMAS DE ITERACIÓN Una iteración o bucle consiste en la repetición de una acción o grupo de acciones hasta conseguir el resultado deseado.
• Identificar las acciones útiles a repetir, y las variables necesarias. Precisar el significado de estas variables al comienzo y final de cada repetición.
• Identificar cómo actualizar la información al pasar de cada iteración a la siguiente. • Identificar la condición de terminación. • Identificar los valores iniciales de las variables, y si es necesaria alguna acción
para asignárselos antes de entrar en el bucle.
6.2– VERIFICACIÓN DE PROGRAMAS Un programa es correcto si produce siempre los resultados esperados de acuerdo con la especificación del programa. Una forma de verificar un programa es mediante ensayos. Consiste en ejecutar el programa con unos datos preparados de antemano y a los que sabemos cuál será el resultado correcto. Si con la ejecución del programa con estos datos se obtienen los resultados esperados no podemos afirmar que el programa sea correcto ya que puede tener un caso concreto y no probado que dé resultados inesperados. La única forma de verificar un programa es con una demostración formal utilizando la lógica de predicados.
6.3-‐ EFICIENCIA DE PROGRAMAS. COMPLEJIDAD. El coste que tiene un programa va a estar en función de los recursos que consume. Recursos son la memoria, el tiempo de procesador y el tiempo que tarda en ejecutarse. Nosotros sólo nos fijaremos en el tiempo que tarda un algoritmo en ejecutarse, que vendrá en función del número de instrucciones que ejecuta.
TEMA 7 -‐.-‐ FUNCIONES Y PROCEDIMIENTOS 7.1 CONCEPTO DE SUBPROGRAMA
Es una parte de un programa. Como mecanismo de programación, un subprograma es una parte de un programa que se desarrolla por separado y se utiliza invocándolo mediante un nombre simbólico.
7.2 FUNCIONES Una función permite agrupar un conjunto de instrucciones en un bloque que típicamente realizará una tarea concreta. Su estructura en C tiene dos partes:
• El encabezamiento de la función • El cuerpo de la función
//encabezamiento de la función (descripción) tipo_resultado nombre_funcion (lista_argumentos o parámetros) { declaración de variables; instrucciones…. } Al encabezamiento de una función se define el nombre de la función y el modo en el que se le va a transferir información. Tipo_resultado representa el tipo de dato (void, int, char, float…) que devuelve la función al módulo que le ha llamado. Nombre_funcion Hace referencia al nombre que utilizaremos para llamar a la función desde otro mòdulo. Lista_argumentos Es un listado de variables o de valores que el modulo que llama le pasa a la función para que realice operaciones con ellos. Algunas funciones predefinidas Librería ctype.h char toupper (char c)àConvierte es mayúscula char tolower (char c)àConvierte es minúscula bool isblank (char c)àComprueba si c es un espacio en blanco o tabulación bool isdigit (char c)àComprueba si c es un número entre 0 y 9 bool isalpha (char c)àComprueba si c es un carácter o no Librería math.h float Tanf (float x)àTangente (x) float cosf (float x)àCoseno (x) float senf (float x)àSeno (x) float powf (float x, float y)àPotencia x ^ float sqrtf (float x)àRaíz cuadrada de x
7.3 – PROCEDIMIENTOS Son subprogramas que realizan una cierta tarea o acción. No devuelven ningún valor.
7.4 – PASO DE ARGUMENTOS O PARAMETROS A FUNCIONES Y PROCEDIMIENTOS Cuando una función llama a otra función para que se ejecute hay un intercambio de información entre las dos funciones. Este intercambio de información se lleva a cabo mediante el uso de parámetros o argumentos. Se pueden clasificar en:
• Parámetros actuales: son variables locales que pertenecen a la función que realiza la llamada y el valor (contenido) o la dirección de memoria de esta variable es enviada a la función invocada.
• Parámetros formales: son variables locales, que pertenecen al módulo invocado, que reciben el valor o la dirección de memoria de los parámetros actuales del módulo que lo invoca en el momento de ser ejecutada la llamada.
Tenemos dos formas de pasarle los argumentos a una función o un procedimiento • Por valor: Se envía una copia del valor que tienen cada uno de los parámetros
actuales en el momento de realizar la llamada. Esta valoración son recogidos por los parámetros formales del módulo invocado. En este caso el módulo invocado no puede modificar el valor original de las variables actuales.
• Por referencia: en este caso, el módulo invocado, en lugar de trabajar con los valores de las variables actuales, trabaja sobre las direcciones de las variables locales. Por tanto en este caso el módulo invocado puede modificar los valores de las variables actuales del módulo que hace la llamada. Este uso ofrece menos seguridad que el paso por valor.
7.5 – VARIABLES LOCALES Y GLOBALES Variable local: Es aquella que su uso está restringido a la función que la ha declarado, se dice entonces que la variable es local a esa función. Esto implica que esa variable sólo va a poder ser manipulada en esa sección, y no se podrá hacer referencia fuera de esa sección. Cualquier variable que se define dentro de las claves del cuerpo de una función se interpreta como una variable local en esa función. Variable global: es aquella que se define fuera del cuerpo de cualquier función, normalmente al principio del programa, tras la definición de los archivos de biblioteca, de la definición de constantes y antes de cualquier función. El ámbito de una variable global son todas las funciones que componen el programa, cualquier función puede acceder a esas variables para leer y escribir en ellas.
7.6 – RECURSIVIDAD DE SUBPROGRAMAS Cuando un subprograma hace una llamada a sí mismo se dice que es un subprograma recursivo. (algoritmo del factorial) Int FactorialRecursivo (int n) { If (n<=1){ Return 1; } else { return n * FactorialRecursivo(n-‐1); } }
7.7 Problemas al utilizar subprogramas El uso inadecuado de las variables que pasamos como argumentos puede dar lugar a algunos problemas:
• Efectos secundarios. Cuando un subprograma modifica alguna variable externa, se dice que está produciendo efectos secundarios o laterales. El concepto al efecto lateral sería la transparencia referencial. Siempre que llamamos a un subprograma con los mismo parámetros se debe obtener el mismo resultado.
• Redefinición de elementos. Cuando a un subprograma se define una variable local da igual el nombre que elegimos, ya que al ser local sólo afecta al propio subprograma. El problema viene cuando el nombre de la variable local que elegimos es lo mismo que una variable global. Aparte de sacar resultados incorrectos, se pierde mucho en claridad.
• Doble referencia(aliasing) Se produce cuando una misma variable se referencia con dos nombres diferentes. Se puede dar los siguientes casos: 1. Cuando un subprograma utiliza una variable externa que también se le pasa
como argumento. 2. Cuando para utilizar un subprograma se pasa la misma variable en dos o más
argumentos.
TEMA 8 -‐.-‐ METODOLOGÍA 3 8.1-‐OPERACIONES ABSTRACTAS Los subprogramas constituyen un primer paso hacia la metodología de programación basada en abstracciones. Los subprogramas permiten definir operaciones abstractas. Una abstracción es una visión simplificada de una cierta entidad, de la que sólo consideramos sus elementos esenciales, prescindiendo en lo posible de los detalles. Las entidades que podemos abstraer para materializarlas como subprogramas osn, en genral, oeraciones ( acción o función). Especificación: Que hace la operación ( punto de vista de quien la invoca) Realización: Cómo se hace la operación (punto de vista de quien la ejecuta). La especificación consiste en indicar cuál es el nombre de la operación y cuales son sus argumentos. La realización debe suministrar toda la información necesaria para poder ejecutar la operación (código).
8.2-‐DESARROLLO USANDO ABSTRACCIONES Desarrollo descendente: desarrollo por refinamientos sucesivos, teniendo en cuenta además la posibilidad de definir operaciones abstractas. En cada etapa de refinamiento de una operación habrá que optar por una de las alterativas siguientes:
• Considerar la operación como operación terminal, y codificarla mediante sentencias del lenguaje de programación
• Considerar la operación como operación compleja, y descomponerla en otras más sencillas.
• Considerar la operación como operación abstracta, y especificarla, escribiendo más adelante el subprograma que la realiza.
Resultará ventajoso definir una operación como abstracta si se consigue alguna de las ventajas siguientes:
• Evitar mezclar en un determinado fragmento de programa operaciones con un nivel de detalle muy diferente.
• Evitar escribir repetidamente fragmentos de código que realicen operaciones análogas.
Reutilización: Si la operación identificada como operación abstracta tiene un cierto sentido en sí misma, es muy posible que resulte de utilidad en otros programas, además de en aquél para el cual se ha desarrollado. La escritura de otros programas que se utilicen esa misma operación resulta mas sencilla, ya que se aprovecha el código de su definición, que ya estaba escrito. Los procedimientos y funciones cuanto mas genéricos sean en su comportamiento, más reutilizables son. Un procedimiento o función es reutilizable si podemos aplicarlo no sólo al programa para el que está diseñado, sino que también a otros programas en los que se requiere un procedimiento o función similar. Desarrollo ascendente: Consiste en ir creando subprogramas que realicen operaciones significativas de utilidad para el programa que se intenta construir, hasta que finalmente sea posible escribir el programa principal, de manera relativamente sencilla, apoyándose en los subprogramas desarrollados hasta ese momento.
8.3 – PROGRAMAS ROBUSTOS Cualquier programa deber ser:
o Correcto: Los resultados que dé son los esperados. o Claro: Cualquier programador pueda entender como está estructurado. o Eficiente: Que consume los mínimos recursos en cuanto a tiempo y memoria. Se dice
que un programa es robusto si controla sus operaciones aunque le introducimos datos incorrectos y erróneos.
-‐Programación a la defensiva: Es una forma de diseño defensivo aplicada al diseño de programas que busca garantizar el comportamiento de todo elemento de una aplicación ante cualquier situación de uso por incorrecta o imprevisible que ésta pueda parecer. En general, esto supone multiplicar las comprobaciones que hacen en todos los módulos programados, con la consiguiente penalización en carga de procesador, tiempo y aumento de la complejidad del código. Las técnicas de programación defensiva se utilizan especialmente en componentes críticos, en los que un mal funcionamiento, ya sea por descuido o por un ataque malicioso, podría tener consecuencias graves. La programación defensiva es un enfoque que busca mejorar el software y el código fuente, en términos de:
o Calidad, reduciendo el número de fallos de software y, en consecuencia, problemas. o Haciendo el código fuente comprensible (el código fuente debe ser legible y
comprensible, a prueba de una auditoría de código. o Hacer que el software se comporte de una manera predecible a pesar de entradas o
acciones de usuario inesperadas.
8.3.1 Tratamiento de excepciones: Ante la posibilidad de errores con los datos que se opera a un programa, hay que considerar dos actividades diferentes:
a) Detección de situación de error-‐ b) Corrección de la situación de error.
En C+-‐ la sentencia throw provoca una terminación de un subprograma para excepción: void operacion (argumentos) { ... .... } accion1 if (error1) { throw excepcion1; / * Finaliza con una excepción * / } ... accion2 if (error2) { throw excepcion2; / * Finaliza con una excepción * / } ... }
TEMA 9 -‐.-‐ DEFINICIÓN DE TIPOS A parte de los tipos de datos predefinidos en C+-‐ (int, char, float..) el programador también puede definirse sus propios tipos de datos con el uso de la instrucción typedef, por ejemplo
• Typedef int TipoEdad; • Typedef char TipoSexo; • Typedef float TipoAltura;
Una vez declarados los tipos se pueden definir y declarar variables de esos tipos que acabamos de definir, por ejemplo sería valido:
• TipoEdad edad1, edad2; • TipoSexo sexo; • TipoAltura altura;
Y podríamos utilizar estas variables como las variables que hemos visto hasta ahora: • Edad2= edad1 + 10; • Sexo = ‘H’; • Altura = 1,72;
9.1 TIPO ENUMERADO Podemos definir una lista de valores con los tipos predefinidos y con los tipos que el programador cree. Esta lista tomar valores numéricos automáticamente empezando por el valor 0 y hasta n-‐1.
• Typedef enum {Lunes,Martes,Miercoles,Jueves,Viernes,Sabado,Domingo}; Los tipos enumerados se consideran tipos ordinales (al igual que los int y char), ya que tienen un orden establecido de antemano. Como los tipos enumerados son ordinales, podemos utilizarlos en comparaciones como por ejemplo: If ( mes >= Julio) { } Para hacer referencia al lugar que ocupa un elemento en la lista lo hacemos así: Int (abril)>3 Podemos ir pasando de un elemento a otro de una lista enumerada de la siguiente manera: Dia= jueves; Dia = tipodia(int(dia)+1); Tipo predefinido bool El tipo predefinido bool se puede considerar como un tipo enumerado con dos valores: Typedef enum bool {false,true}; Se cumple que false=0 y true=1.
9.2 TIPOS ESTRUCTURADOS Un tipo estructurado de datos, o estructura de datos, es un tipo cuyos valores se construyen agrupando datos de otros tipos más sencillos. Los elementos de información que integran un valor estructurado se denominan componentes. Todos los tipos estructurados se definen, en último término, a partir de tipos simples combinados.
9.3 TIPO FORMACION Y SU NECESIDAD Estas estructuras se denominan genéricamente formaciones (en ingles array) y permiten la generalización de la declaración, referencia y manipulación de colecciones de datos todos del mismo tipo.
9.4 TIPO VECTOR Está constituido por una serie de valores, todos ellos del mismo tipo, a los que se les da un nombre común que identifica a toda la estructura globalmente. Cada valor concreto dentro de la estructura se distingue por su índice o número de orden que ocupa en la serie. DECLARACIÓN: Se declara de la siguiente forma:
typedef TipoElemento TipoVector{NumElemtos}
donde TypoVector es el nombre del nuevo tipo de vector que se declara y NumElementos es un valor constante que indica el número de elementos que constituyen el vector. El tamaño del array estará comprendido entre 0 y NumElementos-‐1. TipoElemento corresponde al tipo de dato de cada uno de los elementos del vector y puede ser cualquier tipo de dato predefinido del lenguaje o definido por el programado. INICIALIZACIÓN: En el caso de un vector la inicialización afecta a todos sus elementos y por tanto la notación es algo especial y en ella se indica el valor inicial de todos los elementos agrupándolos entre llaves {..} y separándolos por comas (,). TipoAgenda agendaUno = { Lunes, Martes, Miércoles, Jueves, Viernes…..}; OPERACIONES CON VECTORES: La mayoría de las operaciones interesantes con vectores hay que realizarlas operando con sus elementos uno por uno. La referencia a un elemento concreto de un vector se hace mediante el nombre del vector seguido, entre corchetes, del índice del elemento referenciado. VectorUno[0]; // frase[13]; Un elemento de un vector puede formar parte de cualquier expresión con constantes, variables u otros elementos.
9.5 VECTOR DE CARACTERES Las cadenas de caracteres son en realidad vectores de caracteres. En C+-‐ cualquier tipo vector cuya declaración sea de la forma: Typedef char Nombre [N]; se considera una cadena o string, con independencia de su longitud particular, esto es, del valor de N. Es un vector en el que se pueden almacenar textos de diferentes longitudes (si caben). Para distinguir la longitud útil en cada momento se reserva siempre espacio para un carácter más, y si hace que toda cadena termine con carácter nulo ‘\0’ situado al final. Typedef char Cadena20[21]; FUNCIONES:
• Strcpy( c1,c2 ) à Copia c2 en c1 • Strcat ( c1,c2 ) à Concatena c2 a continuación de c1 • Strlen( c1 ) à Devuelve la longitud de c1 • Strcmp ( c1,c2 ) à Compara c1 y c2
9.6 TIPO TUPLA Y SU NECESIDAD Otra forma de construir un dato estructurado consiste en agrupar elementos de información usando el esquema de tupla o agregado. En este esquema el dato estructurado está formado por una colección de componentes, cada uno de los cuales puede ser de un tipo diferente. Por ejemplo, una fecha se escribe habitualmente como un dato compuesto de los elementos, día, mes y año. Tupla: Colección de elementos componentes, de diferentes tipos, cada uno de los cuales se identifica por un nombre. Un aspecto importante del empleo de datos estructurados corresponde al punto de vista de abstracción. Una tupla, como cualquier otro dato compuesto, puede verse de forma abstracta como un todo, prescindiendo del detalle de sus componentes. La posibilidad de hacer referencia a toda la colección de elementos mediante un nombre único correspondiente al dato compuesto simplifica en muchos casos la escritura del programa que lo maneja.
9.7 TIPO REGISTRO (STRUCT) Los esquemas de tupla pueden usarse en programas en C+-‐ definiéndolos como estructuras del tipo registro o struct. Un registro struct es una estructura de datos formada por una colección de elementos de información llamados campos. DEFINICIÓN DE REGISTROS: se hace utilizando la palabra clave struct: Typedef struct Tipo-‐registro { Tipo-‐campo1 nombre-‐campo1; Tipo-‐campo2 nombre-‐campo2; …. }; Cada una de las parejas Tipo-‐campo y nombre-‐campo, separadas por punto y coma, define un campo o elemento componente y su correspondiente ipo. Además hay que tener en cuenta que la estrucutra acaba siempre con punto y coma: Typedef enum TipoMes { Enero, febrero …… }; VARIABLES DE TIPO REGISTRO Y SU INICIALIZACIÓN: Para declara variables de tipo registro es necesario haber realizado previamente la deinición del tipo del registro. No se permite declarar variables de tipo anónimo. TipoFecha ayer, hoy; TipoPunto punto1, punto2; TipoFecha hoy = {12 ,Marzo,2009}; USO: Al manejar datos estructurados de tipo registro se dispone de dos posibilidades. Operar con el dato completo, o bien operar con cada campo por separado. Las posibilidades de operar con el dato completo son bastante limitadas. La única operación admisible es la de asignación. En estas asignaciones debe cumplirse la compatibilidad de tipos. No es suficiente la compatibilidad estructural, es decir, dos estructuras con los mismos campos no son compatibles si sus definiciones se hacen por separado. ACCESO A UNA ESTRUCTURA: Dado que los campos de una estructura se procesa generalmente de forma individual, deberá poder acceder a cada uno de estos campos individualmente. Para acceder a un determinado campo para poder leer o escribir en él haremos: variable.campo
TEMA 10-‐.-‐ AMPLIACIÓN DE ESTRUCTURAS DE CONTROL 10.1 ESTRUCTURAS COMPLEMENTARIAS DE ITERACIÓN
10.1.1 Repetición: sentencia DO A veces resulta mas natural comprobar la condición que controla las iteraciones al finalizar cada una de ellas, en lugar de hacerlo al comienzo de las mismas. La condición que controla las repeticiones es una expresión cuyo resultado es un valor de tipo bool. Si el resultado es true se vuelve a ejecutar la acción y cuando el resultado es false finaliza la ejecución de la estructura. Do { Operaciones…. Printf (“otra operación”); Scanf(“%” &tecla); } while (tecla== ‘s’);
10.1.2 Sentencia continue Esta sentencia dentro de cualquier bucle (while, for o do) finaliza la iteración en curso e inicia la siguiente iteración. A veces dependiendo de la evolución de los cálculos realizados en una iteración, no tiene sentido completar la iteración que se está realizando y resulta más adecuado iniciar una nueva. For (int i = 0; i < N; i++){ If (vectorCoeficientes[i] == 0){ Continue; } calculo = calculo / vectorCoeficientes[i]; }
10.2 ESTRUCTURAS COMPLEMENTARIAS DE SELECCIÓN El if es una estructura de selección pero cuando tenemos que hacer muchos if anidades se vuelve inestable y difícil de entender para resolver este problema utilizaremos el switch.
10.2.1 Sentencia SWITCH Cuando la selección entre varios casos alternativos depende del valor que toma una determinada variable o del resultado final de una expresión, es necesario realizar comparaciones de esa misma variable o expresión con todos los valores que puede tomar, uno por uno, para decidir el camino a tomar.
Switch (expresion) { Case valor1: Acción A;
Break; Case valor2: Case valor3: Accion B;
Break; ……… }
La sentencia comienza con la palabra clave switch y a continuación, entre paréntesis, se indica la expresión, después, para cada valor que puede tomar la variable utilizaremos la palabra clave case seguido del valor que pueda tomar y dos puntos (:) Y después la sentencia a realizar, y para cerrar el case utilizaremos un break. El manual de estilo de C+-‐ impone que cada acción finaliza siempre con la sentencia break para que finalice la sentencia swith después de cada acción. El uso del break es obligatorio para C+-‐ y opcional para C/C++.
TEMA 11-‐.-‐ ESTRUCTURAS DE DATOS
11.1 ARGUMENTOS DE TIPO VECTOR ABIERTO Los vectores con un tamaño indefinido se denominan vectores abiertos. En C+-‐ los argumentos de tipo vector abierto se especifican de manera similar a una declaración de tipo vecotr, omitiendo el tamño explicito pero no los corchetes (^*): const int V[ ]; El precio que hay que pagar por disponer de esat facilidad es tener que pasar siempre la longitud concreta del vector como argumento, en cada llamada. EscribirVectorAbierto (vectorDos, NumeroElementos);
11.2 FORMACIONES ANIDADAS. MATRICES
11.2.1 Declaración de matrices y uso de sus elementos Las matrices son estructuras de tipo formación (array) de dos o más dimensiones. Una forma sencilla de plantear la definición de estas estructuras es considerarlas como vectores cuyos elementos son a su vez vectores. Los vectores multidimensionales más utilizados son los bidimensionales o matrices, los cuales vendrán definidos por dos índices ( normalmente, fila y columna). DEFINICIÓN: const int NFILAS=4;
Const int NCOL=5; Typedef int TipoElemento; Typedef TipoElemento TipoFila [NCOL]; Typedef TipoFila TipoMatriz [NFILAS];
Una vez tenemos definido el TipMatriz ya podemos definir una variable de ese tipo: TipoMatriz tabla1, tabla2; La definición de tipos que hemos hecho la podríamos simplificar: Typedef int TipoMatriz [4] [5]; TipoMatriz tabla1,tabla2; OPERACIONES: Las operaciones con elementos individuales de una matrid pueden hacerse directamente, de forma análoga a la operación con variables simpes de ese tipo. En cambio las operaciones globales con matrices han de plantearse de manera similar a las operaciones globales con vectores. En general se operará elemento a elemento, o a lo sumo por filas completas.
11.3 EL ESQUEMA UNIÓN Hay aplicaciones en las que resultaría deseable que el tipo de un dato variase según las circunstancias. Si las posibilidades de variación son un conjunto finito de tipos, entocnes se puede decir que el tipo del dato corresponde a un esquema que es la unión de los tipos particulares posibles. Cada uno de los tipos particulares constituye una variante o alternativa del tipo unión. Como situaciones típicas en las que se pueden aplicar los esquemas unión tenemos:
• Datos que pueden representarse de diferentes maneras. • Programas que operan indistintamente con varias clases de datos. • Datos estructurados con elementos opcionales.
Ejemplo: numero_general = entero | fracción | real
11.3.1 El tipo unión Un tipo unión se define como una colección de campos alternativos, de tal manera que cada dato particular sólo usará uno de esos campos en un momento dado, dependiendo de la alternativa aplicable. La definición es similar a la de un agregado o struct, usando ahora la palabra clave unión: Typedef unión TipoNumero { Int valorEntero; Float valorReal; TipoFraccion valorRacional; }; La referencia a los elementos componentes se hace también como en los tipos struct: Tiponumero numero, otro, fraccion1, fraccion2;
Numero.valorentero = 33; Otro.valorreal = float (numero.valorentero); Fraccion2.valorracional = fraccion1.valorracional; Como se ha dicho, sólo una de las variantes puede estar vigente en un momento dado. Si asignamos valor a una de ellas será ésta la que exista a partir de ese momento, al tiempo que dejan de existir las demás.
11.3.2 Registros con Variantes El hecho de que un dato de tipo unión deba ir acompañado de información complementaria para saber cuál es la variante aplicable hace que los tipos unión aparezcan casi siempre formando parte de estructuras mas complejas. Un ejemplo es lo que se denomina como registros con variantes. Se trata de agregados o tuplas en los que hay una colección de campos fijos, aplicables en todos los casos, y campos variantes que se definen según el esquema unión. Además suele reservarse un campo fijo para indicar explícitamente cual es la variante aplicable en cada momento, a esto se le llama discriminante.
typedef enum ClaseNumero {Enter, Real, Fracción}; typedef struct TipoFraccio { int numerador; int denominador;
} typedef union TipoValor { int valorEnter; float valorReal; TipoFraccio valorracional; };
typedef struct TipoNumero { ClaseNumero clase; ßCampo discriminante TipoValor valor; }
void EscriureNumero (TipoNumero n) { switch (n.clase) {
case Enter: printf ("% d", n.valor.valorEnter); break; case Real: printf ("% f", n.valor.valorReal); break; case fracción: printf ("% d /% d", n.valor.valorRacional.numerad oro, n.valor.valorRacional.denominador); break; default: printf («?????»); } }
TEMA 12 -‐.-‐ ESQUEMAS TIPICOS DE OPERACIÓN CON FORMACIONES
12.1 ESQUEMAS DE RECORRIDO Consiste en realizar cierta operación con todos y cada uno de los elementos de una formación. Aplicable a vector con cualquier dimensión y mátrices. El forma más general del esquema de recorrido sería: Iniciar operación While (quedan elementos sin tratar){ Elegir uno de ellos y tratarlo } completar operación La terminación del elemento while está garantizada ya que el número de elementos que faltan por tratar es un valor finito no negativo, que va disminuyendo en cada iteración.
Para el recorrido de matrices necesitaremos dos for por ejemplo: For (int=0; i<N;i++){ For (int j=0; j<N; j++){ Z [i][j]=0; } }
12.1.1 Recorrido no lineal En ciertos casos el elemento a procesar debe elegirse realizando ciertos cálculos y el contador de iteraciones sirve fundamentalmente para contabilizar el avance del recorrido y detectar el final del bucle.
12.2 BUSQUEDA EN UN VECTOR Una operación de búsqueda de un elemento en un vector consiste en:
1-‐ Determinar si el elemento pertenece o no al vector. 2-‐ En caso de que el elemento pertenezca al vector determinar cuál es su lugar o posición
en el vector. Dos de los métodos más usuales de búsqueda en vectores son: à Búsqueda secuencial o lineal à Búsqueda binario o dicotómica. BUSQUEDA SECUENCIAL: consiste en explorar secuencialmente (recorrer) un vector desde el primer elemento hasta el último y comprobar si alguno de los elementos del vector contiene el valor buscado (comparar cada elemento con el elemento a buscar). BUSQUEDA BINARIO o dicotómicos: requiere, para el peor de los casos ( cuando el elemento a buscar es el último o no se encuentra) recorrer todo el vector y hacer un número de comparaciones igual al tamaño del vector. Para vectores con muchos elementos esta búsqueda quizás no sea muy conveniente. La búsqueda binaria requiere menos comparaciones (iteraciones) que la secuencial, pero para realizar la búsqueda es necesario que el vector esté previamente ordenado. La búsqueda binario consiste en:
• En la primera repetición analizamos el elemento central del vector • Si el valor a buscar coincide con este central ya se ha acabado de buscar. • Si el valor a buscar es menor que el central se buscará en el tramo izquierdo al
central en uso de la misma técnica, y sino se buscará en el tramo derecho. • En la segunda repetición el tramo a buscar es la mitad (izquierda o derecha al
central) del vector y el elemento a evaluar es el central de este nuevo tramo. Estos pasos se repetirán hasta que encontremos el elemento o hasta que el tramo a buscar se reduce a un elemento y basta.
12.3 INSERCION El problema que se plantea aquí es insertar un nuevo elemento en una colección de elementos ordenados, manteniendo el orden de la colección. Tenemos elementos almacenados en un vector, ocupando posiciones desde el principio hasta el final quedando libre algunos elementos del final. La operación se puede realizar de forma iterativa, examinando los elementos empezando por el final hasta encontrar uno que sea inferior o igual al que se quiere insertar. Los elementos mayores que el que se quiere insertar se van moviendo una posición hacia delante, con lo que va quedando un huevo en medio del vector. Al encontrar un elemento menor que el nuevo, se
copia el nuevo elemento en el
hueco que hay en ese momento.
12.4 ORDENACIÓN POR INSERCIÓN DIRECTA Aquí se aborda una solución para la ordenación de datos almacenados en un vector. El método más sencillo es el de ordenación por inserción directa. Está basado en el esquema de inserción mostrado en el apartado anterior. Ejemplo: queremos ordenar un vector v de diez elementos (0 a 9) y que inicialmente esta desordenado, {21,5,3,12,65,9,36,7,2,45} Para comenzar el primer elemento (21) ya está ordenado consigo mismo. Luego extraemos el segundo elemento (5) y se genera un hueco , que se puede utilizar para ampliar la parte del vector ya ordenada. El método de ordenación consiste en insertar el elemento extraído en su lugar correspondiente entre los elementos ya ordenador. Este proceso se repite con el tercero y sucesivamente.
12.5 SIMPLIFICACIÓN DE LAS CONDICIONES DE CONTORNO La programación de operaciones con vectores, realizadas elemento a elemento, exige con frecuencia realizar un tratamiento especial de los elementos extremos del vector o, en general, de los elementos del contorno de una formación. A continuación veremos algunas técnicas particulares para evitar la necesidad de detectar de manera explicita si se ha llegado a un elemento del contorno y/o realizar con él un tratamiento especial.
12.5.1 Técnica del centinela Por ejemplo, en el procedimiento general de búsqueda es necesario comprobar en cada iteración una condición doble: si no se ha alcanzado todavía el final del vector, y si se encuentra el elemento buscado. La doble condición del bucle complica el código y supone un tiempo adicional en la ejecución de cada operación. La técnica centinela consiste en incluir el dato a buscar en el vector antes de comenzar la búsqueda. El vector se amplia en +1. Se colocal al final si la búsqueda es hacia delante y viceversa. Este actuará de centinela y asegura que la búsqueda nunca acaba de manera infructuosa. El esquema de búsqueda se simplifica a una condición : Inicar operación(colocar centinela) While (no se encuentre un elemento aceptable) { Elegir siguiente elemento y ver si es aceptable } completar operación ( si se ha encontrado el centinela, indicare fallo en la búsqueda)
TEMA 13 -‐.-‐ PUNTEROS Y VARIABLES DINÁMICAS Los tipos de datos y variables vistas hasta ahora se llaman estáticas, porque su tamaño se especifica al inicio por el programador. El compilador reserva un espacio en memoria constante para la variable estática durante toda la ejecución del programa. En ocasiones no se puede
conocer el tamaño de las variables, en estos casos habrá que hacer uso de variables dinámicas. Estas estructuras se caracerizan por la reserva y liberación de memoria que se realiza durante la ejecución del programa, y este espacio puede variar.
13.1 LA ESTRUCTURA SECUENCIA Puede definirse como un esquema de datos del tipo iterativo, pero con un número variable de componentes. La estructura secuencia resulta parecida a una formación con número variable de elementos. Hay 2 tipos de operaciones: Operaciones de construcción:
• Añadir o retirar componentes al principio de la secuencia. • Añadir o retirar componentes al final de la secuencia. • Añadir o retirar componentes en posiciones intermedias de la secuencia.
Operaciones de acceso: • Acceso secuencial: las componentes deben tratarse una por una, en el orden en
que aparecen en la secuencia. • Acceso directo: se puede acceder a cualquier componente directamente
indicando su posición, como en una formación o vector. Para el caso de acceso secuencial, el tratamiento de una secuencia se realiza empleando un cursor. El cursor es una variable que señala a un elemento de la secuencia. El acceso, inserción o eliminación de componentes de la secuencia se hace actuando sobre el elemento señalado por el cursor. Para actuar sobre el cursor se suelen plantear las siguientes operaciones:
• Iniciar: Pone el cursor señalando al primer elemento • Avanzar: El cursor pasa a señalar al siguiente elemento. • Fin: Es una función que indica si el cursor ha llegado al final de la secuencia.
13.2 VARIABLES DINAMICAS. Una manera de realizar estructuras de datos ilimitadas es mediante el empleo de variables dinámicas. No se declara como tal, sino que se crea en el momento necesario, y se destruye cuando ya no se necesita. No tienen nombre, sino que se designan mediante otras variables llamadas punteros o referencias.
13.2.1 Punteros Son variables simples cuyo contenido es precisamente una referencia a otra variable. El valor de un puntero no es representable como número o texto. En su lugar usaremos una representación gráfica en la que utilizaremos una flecha para enlazar una variable de tipo puntero con la variable a la que hace referencia.
Declaración de punteros: El tipo de puntero especifica en realidad el tipo de variable a la que puede apuntar. Typedef tipo-‐de-‐variable* tipo-‐puntero; Una vez declarado el tipo, se pueden declarar variables puntero de dicho tipo. Una variable puntero se puede usar para designar la variable apuntada mediante la notación: *puntero Ejemplo: Typedef int* tp_entero; Tp_entero pe; *pe = 33; printf (“%d”,*pe);
Puntero Variable apuntada
Estas sentencias asignan el valor 33 a la variable dinámica señalada por el puntero pe, y luego la imprime. Para poder detectar si un puntero señala realmente o no a otra variable, existe el valor especial NULL (no es una palabra clave, esta dentro de la librería stdlib.h) Este valor es compatible con cualquier tipo de puntero, e indica que el puntero no señala a ninguna variable. Se usará para inicializar las variables de tipo puntero al comienzo del programa: If (*pe ¡= NULL) { *pe = 33; printf (“%d”, *pe); }
13.2.2 Uso de variables dinámicas Las variables dinámicas no tienen reservado el espacio en memoria que tienen las demás variables, sino que se crean a partir de punteros en el momento en que se indique. Una vez creadas siguen existiendo incluso después de que termine la ejecución del subprograma donde se crean. Se crean así: Typedef Tipo-‐de-‐variable* tipo-‐puntero; Tipo-‐puntero puntero; Puntero = new tipo-‐de-‐variable; El operador new crea una variable dinámica del tipo indicado y devuelve una referencia que puede asignarse aun puntero de tipo compatible. La variable dinámica se crea a base de reservarle el espacio necesario en una zona general de memoria gestionada dinámicamente. En principio no se puede asumir que la variable recién creada tenga un valor concreto, igual que las variables normales que se declaran sin un valor inicial explicito. Una vez creadas siguen existiendo hasta que el programador no especifique que ya no se necesita. Para ello existe la sentencia delete, que permite destruir la variable dinámica: Delete puntero;
13.3 REALIZACIÓN DE SECUENCIAS MEDIANTE PUNTEROS o Lista enlazada Los punteros son un elemento de programación de muy bajo nivel. Los lenguajes de programación simbólicos deberían evitar su empleo, sustituyendo por mecanismos mas potentes de declaración de estructuras de datos, que permitiesen definir directamente estructuras dinámicas ilimitadas. Para crear una secuencia ilimitada tendremos que recurrir al empleo de variables dinámicas y punteros, enlazando cada elemento de la secuencia con el siguiente. Cada elemento de la secuencia se materializa como un registro con dos campos: el primero contiene el valor de una componente, y el segundo es un puntero que señala al siguiente. El último apuntará a NULL. Una lista es una esctructura de datos dinámica formada por un conjunto de elementos, llamados nodos, del mismo tipo y almacenados en la memoria principal siguiendo una secuencia lógica. Podemos distinguir:
• Listas contiguas: Son aquellas en las que los elementos se guardan en posiciones de memoria contiguas, de forma que equivalen a tablas o vectores unidimensionales . Las operaciones que podemos hacer en una lista contigua son: o Ordenarla o Buscar un elemento o Añadir un elemento o Eliminar un elemento o Borrar la lista completa
o Copiar una lista origen a un destino
o Concatenar varias listas o Dividir una lista en sublistas
• Listas enlazadas: Son aquellas en la que los elementos se encuentran almacenados en posiciones de memoria no contiguas. Cada nodo es equivalente a un struct (registro) y está formado por dos campos: o Campo valor, que es el campo que contiene el dato o Campo siguiente: es el campo que actúa de enlace con el siguiente nodo de la lista en
secuencia lógica. Para definir un nodo: Typedef struct tiponodo { Tipo_elemento valor: Tiponodo* siguiente; };
13.4 PUNTEROS Y PASO DE ARGUMENTOS Como cualquier otro dato, un puntero puede pasarse como argumento a un subprograma. Por defecto, los datos de tipo puntero se pasan como argumentos por valor. Si se desea usar un subprograma para modificar datos de tipo puntero, entonces habrá que pasar el puntero por referencia.
13.4.1 Paso de argumentos mediante punteros En general el valor de un puntero en sí mismo no es significativo, sino que el puntero es sólo un medio para designar la variable apuntada. Desde un punto de vista conceptual el paso de un puntero como argumento puede ser considerado equivalente a pasar como argumento la variable apuntada. La dificultad reside en el hecho de que pasar un puntero por valor no evita que el subprograma pueda modificar la variable apuntada.
TEMA 14-‐.-‐ TIPOS ABSTRACTOS DE DATOS (TAD) Concepto: Si identifica el concepto de tipo de dato con el del conjunto de valores que pueden tomar los datos de este tipo. Un enfoque mas moderno de la programación trata de asociar la idea de tipo de datos con la clase de valores, abstractos, que pueden tomar los datos. Esto quiere decir que la representación o codificación particular de los valores no cambia, el tipo del dato considerado. En el enfoque actual de la programación se identifican los tipos de datos de forma completamente abstracta, llegando a la idea de tipo abstracto de datos (TAD). Esto quiere decir que un programa que use ese tipo de datos no debería necesitar ningún cambio por el hecho de modificar la representación o codificación de los valores de ese tipo. Si analizamos con cuidado que necesita un programa para poder usar datos de un tipo, encontramos que hace falta:
o Hacer referencia al tipo en sí, mediante un nombre, para poder definir variables, subprogramas, etc.
o Hacer referencia a algunos valores particulares, generalmente como constantes con nombre.
o Invocar operaciones de manipulación de los valores de ese tipo, bien usando operadores en expresiones aritméticas o bien mediante subprogramas.
El conjunto de todos estos elementos constituye el tipo abstracto de datos (TAD):
Un tipo abstracto de datos es una agrupación de una colección de valores y una colección de operaciones de manipulación. Es importante comprender que estas colecciones son cerradas, es decir sólo se deben poder usar los valores abstractos y las operaciones declaradas para ese tipo. Además los detalles de cómo se representan los valores y cómo se implementan las operaciones pueden estar ocultos para quien utiliza el tipo abstracto.
14.1 REALIZACIÓN DE TIPOS ABSTRACTOS EN C+-‐ DEFINICION: De esta manera se pueden definir tipos abstractos de datos, ya que:
o Los campos de datos sirven para almacenar el contenido de información del dato abstracto.
o Los subprogramas permiten definir operaciones sobre esos datos. o La posibilidad de declarar ciertos elementos como privados permite ocultar detales de
implementación, y dejar visible sólo la interfaz del tipo abstracto.
Ejemplo de TAD typedef struct TipoPunto { float x; float y; void Leer (); void Escribir (); float Distancia (TipoPunto p);
};
A parte de definir dentro del registro los procedimientos y funciones estos subprogramas deben implementarse fuera del registro (fuera del .h) de la siguiente manera: void TipoPunto::Leer( ) { printf ("Coordenada X?"); scanf ("% f", & x); printf ("Coordenada Y?"); scanf ("% f", & y); } void TipoPunto::Escribir( ) { printf (“(%f, %f)”, x, y); } float TipoPunto::Distancia (TipoPunto p) { float deltaX, deltaY; deltaX = xp.x; deltaY = yp.y; } return sqrt (deltaX * deltaX + deltaY * deltaY); }
14.2 OCULTACIÓN Para que un tipo sea realmente abstracto haría falta que los detalles de implemtenación no fueran visibles. Tanto en los campos como en los procedimientos. En el caso anterior del TipoPunto los campos x e y son visibles en todo momento, y pueden funcionar consultados y modificados por el código que usa el tipo.
Para permitir esta ocultación los tipos struct admiten la posibilidad de declara ciertos elementos componentes como privados, utilizando la palabra clave private para delimitar una zona de declaraciones privadas dentro de la estructura. Ejemplo typedef struct TipoData { void pone (int día, int mes, int año); int Día (); int Mes (); int Año (); void Leer (); bool EsCorrecta (int día, int mes, int año); ........ private: int día, mes, año; }; Las variables dia, mes y año slo son visibles (accesibles) desde las funciones y procedimientos que están definidos dentro de la estructura, ya que están definidas con el modificador private.
14.3 METODOLOGÍA BASADA EN ABSTRACCIONES La técnica de programación estructurada, basada en refinamientos sucesivos, puede ampliarse para contemplar la descomposición modular de un programa. La metodología de desarrollo será esencialmente la misma que se ha presentado anteriormente. La diferencia es que ahora disponemos también de un nuevo mecanismo de abstracción, que son los tipos abstractos de datos. En cualquier caso el desarrollo deberá atender tanto a la organización de las operaciones como a la de los datos sobre los que operan, de manera que habrá que ir realizando simultáneamente las siguientes actividades:
o Identificar las operaciones a realizar, y refinarlas. o Identificar las estructuras de información y refinarlas.
14.3.1 Desarrollo por refinamiento basado en abstracciones En cada etapa de refinamiento de una operación hay que optar por una de las alternativas siguientes:
o Considerar la operación como operación terminal, y codificarla mediante sentencias del lenguaje de programación
o Considerar la operación como operación compleja, y descomponerla en otras más sencillas.
o Considerar la operación como operación abstracta, y especificarla, escribiendo más adelante el subprograma que la realiza.
Ahora podemos reformular estas opciones para las estructuras de datos a utilizar: o Considerar el dato como un dato elemental, y usar directamente un tipo predefinido
del lenguaje para representarlo. o Considerar el dato como un dato complejo, y descomponerlo en otros más sencillos
(como registro, unión o formación). o Considerar el dato como un dato abstracto y especificar su interfaz, dejando para
más adelante los detalles de su implementación.
TEMA 15 -‐.-‐ MÓDULOS CONCEPTO: un módulo es un fragmento de programa desarrollado de forma independiente (diseñado y compilando de forma independiente del programa que lo va a utilizar). El concepto
de módulo está íntimamente asociado a la abstracción de datos. Cuando utilizamos un módulo a un programa, solo hay que saber que hace el módulo y sus funciones, pero no tenemos por qué saber cómo esta implementado. La razón de exigir compilación por separado para los distintos módulos de un programa obedece a la necesidad de limitar la complejidad de aquello que está siendo elaborado por una persona en un momento dado. El programador podrá concentrarse en el módulo prescindiendo en parte de cómo se utiliza ese modulo desde el resto del programa. El concepto de módulo esta íntimamente ligado al concepto de abstracción. ESPECIFICACIÓN Y REALIZACIÓN: Al igual que en cualquier elemento abstracto, en un módulo podemos distinguir:
o La especificación del modulo es todo lo que se necesita para poder usar los elementos definidos en él. (que hace el módulo) (interfaz)
o La realización del modulo consistirá en la realización de cada uno de los elementos abstractos contenidos en dicho modulo.(como lo hace).
15.1.1 Compilación separada Los lenguajes de programación que permiten programar usando módulos pueden emplear diversas técnicas para definirlos e invocar los elementos definidos en ellos.
o Compilación separada: El programa está formado por varios ficheros fuente, cada uno de los cuales se compila por separado.
o Compilación segura: Al compilar un fichero fuente el compilador comprueba que el uso de elementos de otros módulos es consistente con la interfaz.
o Ocultación: Al compilar un fichero fuente el compilador no usa información de los detalles de realización de los elementos de otros módulos.
15.1.2 Descomposición modular La posibilidad de compilar módulos de forma separada permite repartir el trabajo de desarrollo de un programa, a base de realizar su descomposición modular. Los diferentes módulos pueden ser encargados a programadores diferentes y gracias a ello todos pueden trabajar al mismo tiempo. De esta forma se puede desarrollar en un tiempo razonable los grandes programas correspondientes a las aplicaciones de hoy dia, que totalizan cientos de miles o millones de sentencias. La descomposición modular puede reflejarse en un diagrama de estructura. En este diagrama se representa cada módulo como un rectángulo, con el nombre del módulo en su interior. Las líneas indican las relaciones de uso, pueden llevar punta de flecha si es necesario indicar expresamente cuál es el sentido de la relación. Normalmente no es necesario, pues, como en este caso, un módulo que usa otro se dibuja por encima de él. Las líneas se interpretan en la dirección de arriba abajo. El objetivo de la ingeniería de software es facilitar el desarrollo de una aplicación de forma organizada, de manera que muchas personas puedan colaborar simultáneamente en un mismo proyecto. Para que la descomposición en módulos sea adecuada, desde un punto de vista, conviene que los módulos resulten tan independientes unos de otros como sea posible. Esta independencia se analiza según dos criterios, denominados acoplamiento y cohesión.
C D
B
A
o El acoplamiento entre módulos indica cuántos elementos distintos o características de uno o varios módulos han de ser tenidos en cuenta a la vez al usar un módulo desde otro. Este acoplamiento debe reducirse a un mínimo.
o La cohesión indica el grado de relación que existe entre los distintos elementos de un mismo módulo, y debe ser lo mayor posible. Esto quiere decir que dos elementos íntimamente relacionados deberían ser definidos en el mismo módulo, y que un mismo módulo no debe incluir elementos sin relación entre sí.
15.2 MÓDULOS EN C+-‐ Un programa descompuesto en módulos se escribe como un conjunto de ficheros fuente relacionados entre sí, y que pueden compilarse por separado. Cada fichero fuente constituye así una unidad de compilación.
15.2.1 Proceso de compilación simple Un fichero fuente es un fichero de texto que contiene el código de una unidad de compilación, es decir, es posible invocar el compilador dándole como entrada solo ese fichero fuente. La compilación de un fichero fuente produce un fichero objeto que contiene la traducción del código C+-‐ a instrucciones de máquina. Los ficheros fuente tienen la extensión .cpp, los ficheros objeto la extensión .o.
15.2.2 Módulo principal Cuando se descompone un programa en módulos uno de ellos debe ser el módulo principal uno de ellos ha de ser el programa principal (main).
15.2.3 Módulos no principales No permiten generar un programa ejecutable por sí solos ( sin main). Están destinados a ser usados por el programa principal. Hay que distinguir los elementos públicos y los privados. La distinción de estos elementos se hace repartiendo el código del modulo en dos ficheros fuente separados: un fichero interfaz (.h) o fichero de cabecera, y un fichero de implementación (.cpp). Los ficheros de cabecera (.h) se deben incluir en los ficheros de implenteación (cpp) y esto se hace con la directiva #include que sirve para hacer referencia a un fichero fuente desde otro, y tiene como parámetro el nombre dl fichero físico (.h) incluyendo la extensión.
15.2.4 Uso de módulos Para usar los elementos públicos definidos en un módulo hay que incluir la interfaz de ese módulo en el código donde se vaya a utilizar, con la directiva #include. La novedad ahora es que los nombres de los ficheros de la propia aplicación deben escribirse entre comillas (“….”) y no entre ángulos (<…>). Con esto se indica al compilador que debe buscar dichos ficheros en donde reside el código fuente de la aplicación y no donde está instalada.
15.2.5 Declaración y definición de elementos públicos En la declaración de un elemento hay que especificar lo necesario para que el compilador pueda compilar correctamente el código que usa dicho elemento.
..cpp .o .exe compilador Montador
En la definición de un elemento hay que especificar lo necesario para que el compilador genere el código del propio elemento.
o Los tipos y constantes se especifican totalmente en el fichero de interfaz. No hay declaración y definición separadas.
o Las variables se definen de la manera habitual en el fichero de implementación, incluyendo la especificación de valor inicial en su caso. En el fichero de interfaz se pone además una declaración que indica el tipo y el nombre de la variable, sin indicar valor inicial, y precedida de la palabra clave extern.
o Los subprogramas se definen de la manera habitual en el fichero de implementación y permiten al compilador generar el código objeto del subprograma. En el fichero de interfaz se pone además una declaración en forma de prototipo o cabecera de subprograma sólo con el tipo, nombre y argumentos.
15.3 DEPENDENCIAS ENTRE FICHEROS Las relaciones de uso entre módulos se corresponden, con las directivas #include usadas en un fichero fuente para hacer visibles los elementos de otro, y que pueden aparecer en el fichero .cpp y/o en el .h. La recomendación es:
o Un fichero xxx.h debe incluir otros yyy.h que use directamente. o Un fichero xxx.cpp debe incluir su propio xxx.h y otros yyy.h que use directamente.
15.3.1 Datos encapsulados Cuando definimos un tipo abstracto de datos, luego hay que declarar variables de ese tipo para poder trabajar con ellas. Los datos encapsulados nos permiten declarar dentro de un módulo una variable y evitar una declaración explicita externa. Por ejemplo, el módulo de implementación pondríamos: Static Untipo valorInterno; Static void Operacion3 ( ) { …..valor interno….. } void Operacion1 ( ) { …..valor interno…. } void Operacion2 ( ) { ….valor interno…. } Hay que recordar que por defecto las variables y subprogramas definidos en el nivel más externo de un módulo son globales por defecto. Para que sean tratados como locales hay que poner delante de ellos la palabra static.
Recommended