Está en la página 1de 42

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 e intérpretes ¿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 i

• sascii(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É2CAS

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 aparte 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 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 simplesque 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:

• Identificar las acciones componentes.

• 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ás 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 ylas
condiciones son rombos. Las condiciones equivalen a preguntas a las que se puede responder
“SI” o “NO”.

SI NO

Acción

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.

6.1.1 ESQUEMAS DE ITERACIÓ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.

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

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 módulo 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 PARÁMETROS 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 es recogida 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 de la factorial)
Int FactorialRecursivo (int n) {

If (n<=1){

Return 1;

} else {

return n * FactorialRecursivo(n-1);

Int main () {

For (int i = 0; i <= 10; i++) {

Printf ( “%2d! vale:%10d/n”, i, FactorialRecursivo( i ) );

7.7 PROBLEMAS EN EL USO DE 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 mismos
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 son, en general, operaciones ( 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 cuáles 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 más sencilla, ya que se aprovecha el código de su definición, que ya estaba escrito. Los
procedimientos y funciones cuanto más 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:

• Correcto: Los resultados que dé son los esperados.

• Claro: Cualquier programador pueda entender como está estructurado.

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

• Calidad, reduciendo el número de fallos de software y, en consecuencia, problemas.

• Haciendo el código fuente comprensible (el código fuente debe ser legible y
comprensible, a prueba de una auditoría de código.

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

• Detección de situación de error-

• 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, miércoles, jueves, viernes, sábado, 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 DE FORMACIÓN Y SU NECESIDAD

Estas estructuras se denominan genéricamente formaciones (en inglés 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 tipo. Además, hay que tener en cuenta
que la estructura 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 procesan
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 más natural comprobar la condición que controla las iteraciones al finalizar
cada una de ellas, en lugar de hacerlo al comienzo de estas. 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 unidades 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
vector, omitiendo el tamaño explicito, pero no los corchetes (^*): const int V[ ];

El precio que hay que pagar por disponer de esta 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 ESQUEMA 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 más complejas.

Un ejemplo es lo que se denomina 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. La 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 binaria 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 para 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 binaria 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 si no 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 es 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.

Vector original

2 8 15 23 56

2 8 15 23 56

Vector final

2 8 15 64 23 56
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 explícita 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 amplía en +1. Se coloca 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 caracterizan 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.

puntero

Variable
apuntada
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
puntera 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);

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, está 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 más
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 estructura 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:

• Ordenarla • Borrar la lista completa

• Buscar un elemento • Copiar una lista origen a un destino

• Añadir un elemento • Concatenar varias listas

• Eliminar un elemento • 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:

• Campo valor, que es el campo que contiene el dato

• 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 más 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:

• Hacer referencia al tipo en sí, mediante un nombre, para poder definir variables,
subprogramas, etc.

• Hacer referencia a algunos valores particulares, generalmente como constantes con


nombre.

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

• Los campos de datos sirven para almacenar el contenido de información del dato
abstracto.

• Los subprogramas permiten definir operaciones sobre esos datos.

• 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 declarar 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:

• Identificar las operaciones a realizar, y refinarlas.

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

• 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
delante el subprograma que la realiza.

Ahora podemos reformular estas opciones para las estructuras de datos a utilizar:

• Considerar el dato como un dato elemental, y usar directamente un tipo predefinido


del lenguaje para representarlo.

• Considerar el dato como un dato complejo, y descomponerlo en otros más sencillos


(como registro, unión o formación).

• 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 qué 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 está í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:

• La especificación del módulo es todo lo que se necesita para poder usar los elementos
definidos en él. (que hace el módulo) (interfaz)

• La realización del módulo 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.

• Compilación separada: El programa está formado por varios ficheros fuente, cada uno
de los cuales se compila por separado.

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

• 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 suinterior.
C D
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.

• El acoplamiento entre módulos indica cuántos elementos distintos o características de


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

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

…cpp compilador .o Montador .exe

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 módulo 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 elcó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.

En la definición de un elemento hay que especificar lo necesario para que el compilador


genere el código del propio elemento.

• Los tipos y constantes se especifican totalmente en el fichero de interfaz. No hay


declaración y definición separadas.

• 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 externa.
• 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:

• Un fichero xxx.h debe incluir otros yyy.h que use directamente.

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

También podría gustarte