Introducción a la Programación Procedural
Introducción a la Programación Procedural
Departamento de Informática
2022
1
Programación Procedural
Programación Procedural 2
Programación Procedural
Programación Procedural
La asignatura Programación Procedural pertenece al área “Algoritmos y Lenguajes” y tiene como obje-
tivo introducir los conceptos de la programación procedural, incluir construcciones estándar de progra-
mación, estrategias de resolución de problemas y estructuras fundamentales de datos.
En el desarrollo de la misma se promueve que el estudiante interprete conceptos básicos de diseño e
implementación de los lenguajes imperativos, y por otro lado que realice prácticas intensivas en un len-
guaje imperativo, a través de programas que ofrezcan soluciones óptimas a una amplia gama de proble-
mas. Se incluyen también contenidos inherentes a verificación y derivación de programas iterativos.
Teniendo en cuenta que en la materia correlativa de segundo año trabaja el paradigma orientado a obje-
tos, en particular a través del uso de C#, se elige al lenguaje C para la discusión del modelo de computa-
ción procedural.
Así, través de lenguaje C se analiza la especificación e implementación de distintos tipos de datos, dis-
tintos tipos de operaciones y se desarrollan programas en el marco del diseño modular.
Estos Apuntes de Cátedra son el resultado de muchos años de trabajo de un equipo de cátedra compro-
metido con su trabajo, un especial agradecimiento a Mg. Adriana Valenzuela y Mg. Myriam Llarena por
el invaluable aporte que realizaron a la cátedra Programación Procedural.
Redacción de Prácticos:
Lic. Cristina Vera
Lic. Daniela Villafañe
Lic. Gerardo Barud
Lic. Andrea Ferreyra
Lic. Marcelo Mondre
Compaginación:
Dr. Mario Díaz
Programación Procedural 3
Programación Procedural
Índice
PROGRAMACIÓN PROCEDURAL..................................................................................................................... 3
PROGRAMA DE EXAMEN 2022 .......................................................................................................................... 9
UNIDAD 1: VERIFICACIÓN Y DERIVACIÓN DE PROGRAMAS .............................................................. 11
INTRODUCCIÓN .................................................................................................................................................. 11
PRECONDICIONES Y POSTCONDICIONES .................................................................................................. 11
VERIFICACIÓN VS DERIVACIÓN ................................................................................................................... 12
VERIFICACIÓN .................................................................................................................................................... 13
DERIVACIÓN ........................................................................................................................................................ 19
BIBLIOGRAFÍA .................................................................................................................................................... 32
UNIDAD 1: LENGUAJES PROCEDURALES ................................................................................................... 33
INTRODUCCIÓN .................................................................................................................................................. 33
LENGUAJE DE PROGRAMACIÓN................................................................................................................... 33
LENGUAJES IMPERATIVOS............................................................................................................................. 34
DEFINICIÓN DE UN LENGUAJE DE PROGRAMACIÓN ........................................................................................................... 34
TRADUCTORES Y COMPUTADORAS SIMULADAS POR SOFTWARE ........................................................................................... 35
Simulación de software (interpretación) ......................................................................................................... 35
Traductores ...................................................................................................................................................... 36
Distintos tipos de traductores .......................................................................................................................... 37
Traducción e Interpretación ............................................................................................................................. 38
IMPLEMENTACIÓN DE LENGUAJES ............................................................................................................ 38
CRITERIOS DE DISEÑO DE LENGUAJES DE PROGRAMACIÓN ............................................................ 39
MODELOS DE COMPUTACIÓN ....................................................................................................................... 40
PARADIGMA DE PROGRAMACIÓN ................................................................................................................................. 40
CLASIFICACIÓN DE LOS PARADIGMAS ............................................................................................................................. 40
VARIANTES DE LOS PARADIGMAS DE PROGRAMACIÓN ...................................................................................................... 41
Programación imperativa o procedural ........................................................................................................... 41
Programación declarativa ............................................................................................................................... 41
LENGUAJE C ........................................................................................................................................................ 42
ETAPAS EN LA OBTENCIÓN DE PROGRAMA EJECUTABLE EN LENGUAJE C ................................................................................ 42
BIBLIOGRAFÍA .................................................................................................................................................... 44
UNIDAD 2: OBJETO DE DATOS ....................................................................................................................... 45
INTRODUCCIÓN .................................................................................................................................................. 45
OBJETO DE DATOS ............................................................................................................................................ 45
VARIABLES Y CONSTANTES .......................................................................................................................................... 46
TIEMPO DE VIDA ....................................................................................................................................................... 48
TIPOS DE DATOS ................................................................................................................................................. 49
ESPECIFICACIÓN E IMPLEMENTACIÓN DE UN TIPO DE DATOS ............................................................................................... 49
TIPOS DE DATOS ELEMENTALES ................................................................................................................. 50
DECLARACIONES ....................................................................................................................................................... 52
VERIFICACIÓN DE TIPOS .............................................................................................................................................. 53
Programación Procedural 4
Programación Procedural
Programación Procedural 6
Programación Procedural
Programación Procedural 7
Programación Procedural
Programación Procedural 8
Programa de Examen 2022
Unidad 1
a) Construcción de programas. Introducción. Verificación y Derivación. Verificación de una iteración.
Derivación de algoritmos. Cálculo del Invariante. Ejemplos de Derivación de Programas.
b) Introducción. Lenguaje de programación. Algunas cuestiones de diseño e implementación: computa-
dora distintos tipos. Traductores y computadoras simuladas por software. Modelos de computación.
Lenguajes imperativos: Definición de un lenguaje de programación. Concepto de estado de máquina.
Implementación de un lenguaje de programación. Eficiencia y Regularidad. Ejemplificación en lenguaje
C.
Unidad 2
Objetos de Datos. Variables y Constantes. Tiempo de Vida. Enlace (ligadura) y Tiempo de enlace. Tipos
de Datos. Especificación e Implementación.
a) Tipos de Datos Elementales. Especificación e Implementación. Representación de almacenamiento.
Implementación de algoritmos y procedimientos que definen las operaciones. Declaraciones y Verifica-
ción de tipo. Verificación de tipos en lenguaje C. Conversión y coerción de tipos. Tipo de datos Entero.
Semántica de la operación de Asignación: Valor l y Valor r. Números Reales de punto flotante. Enume-
raciones. Tipos Booleanos. Tipos de datos caracteres.
Tipo Apuntador. Punteros a variables simples en lenguaje C. Operadores de apuntadores. Inicialización.
Asignación de punteros. Ejercicios de Aplicación.
b) Tipos De Datos Estructurados: Introducción. Especificación de Tipo de Datos Estructurados. Imple-
mentación de Tipos de Datos Estructurados. Vectores. Implementación de Operaciones sobre Estructu-
ras de Datos. Declaraciones y Verificaciones de Tipo. Arreglos Bidimensionales y Multidimensionales.
Arreglos y Punteros en Lenguaje C.
Cadenas De Caracteres. Distintas formas. Cadenas de caracteres en C. Uso De Funciones de Cadena de
da Biblioteca Estándar.
Registros: Especificación e Implementación. Operación de Selección de Componentes. Manejo de Struct
en Lenguaje C. Punteros a Struct.
Registros Variantes: Implementación. Union y Struct en Lenguaje C. Ejercicios de Aplicación.
Unidad 3
Encapsulamiento a través de subprogramas. Subprogramas: Especificación e implementación de sub-
programas. Definición, invocación y activación de subprogramas.
Concepto de función en C: Función main(). Declaración y definición de funciones. Organización de
memoria: Almacenamiento Estático, Pila y Montículo. Ejecución de un programa en C.
Pasajes de parámetros: Pasajes de parámetros: valor, constante, por dirección. Variables referenciadas.
Pasaje de parámetro por referencia. Arreglos como parámetros. Funciones que devuelven más de un
valor. Ejercicios de aplicación.
Traducción y Tabla de Símbolos (TS): Compilación de un programa fuente. Lenguaje C como un len-
guaje estructurado en bloque. Importancia de las declaraciones en C. Alcance de un vínculo en lenguaje
C. Generación de la TS en un programa en C. Ejercicios de aplicación.
Clasificación de las variables según alcance y tiempo de vida: Variables automáticas, estáticas y exter-
nas. Ejercicios de aplicación.
Programación Procedural 9
Programa de Examen 2022
Unidad 4
Recursividad. Funciones Recursivas en C. Definición. Caso base y caso general. Distintas combinacio-
nes. Manejo de memoria (pila). Aplicaciones. Recursión e Iteración. Ejercicios de aplicación.
Unidad 5
Estructuras dinámicas. Almacenamiento estático y dinámico. Variables dinámicas. El montículo: Opera-
ciones sobre el montículo o heap. Manejo del montículo en lenguaje C: funciones malloc y free. Varia-
bles dinámicas simples. Basura y referencias desactivadas. Arreglos dinámicos en lenguaje C. Arreglos
dinámicos uni y bi-dimensionales. Mapa de memoria. Cadena de caracteres dinámicas. Arreglo de cade-
nas dinámicas. Ejercicios de Aplicación.
Listas: listas secuenciales y listas enlazadas. Manipulación de listas enlazadas: creación, inserción.
Búsqueda, recorrido, modificación, supresión de elementos. Ordenamiento y eliminación de una lista.
Gestión de almacenamiento. Problemas de gestión de almacenamiento: referencias bamboleantes y basu-
ra. como cola. Recursividad en listas. Ejercicios de Aplicación.
Unidad 6
Archivos. Concepto de archivo en C. Distintas clasificaciones. Archivos secuenciales: Archivos de ca-
racteres y sin formato. Acceso secuencial de archivos: Funciones para la creación y utilización de archi-
vos secuenciales. Acceso directo de archivos secuenciales. Funciones para el uso de archivos. Ejercicios
de aplicación.
Unidad 7
Metodología de Diseño Modular: Concepto de módulo en C. Diseño Modular: Concepto de módulo.
Ventajas del Diseño Modular. Independencia Funcional. Criterios que miden la independencia modular.
Cohesión y Acoplamiento Modular. Distintos Tipos.
Construcción de programas que incluyan manejo avanzado de archivos: Corte de control y merge. Ejer-
cicios de aplicación.
Programación Procedural 10
Unidad 1: Verificación y Derivación de Programas
Introducción
En esta primera parte del Tema 1, profundizaremos los conceptos trabajados sobre verificación de pro-
gramas, vistos en la asignatura Algoritmos y Resolución de Problemas. Aplicaremos estos conceptos a
programas que incluyan estructuras repetitivas e introduciremos los elementos necesarios para abordar el
tema de derivación de programas.
Como se ha visto, para la verificación de programas imperativos se utiliza un sistema de reglas basado
en la tripla de Hoare. La lógica de Hoare es una extensión de la lógica de predicados de primer orden
(reglas de inferencia) para razonar sobre la corrección o construcción de algoritmos imperativos.
Tripla de Hoare: Para especificar formalmente un programa se utiliza una expresión del tipo {P} A
{Q}, para indicar que si P es cierto antes de la ejecución del programa y dicho programa termina, enton-
ces Q es cierto tras la ejecución del programa. En el programa la precondición P y la postcondición Q se
especifican mediante fórmulas denominadas aserciones que relacionan las entradas y salidas del pro-
grama. Se garantiza que si la entrada actual satisface las restricciones de entrada (precondiciones) la
salida satisface las restricciones de salida (postcondiciones).
Un Aserto es un predicado donde se expresa la relación que tienen que cumplir los valores de ciertas
variables en un momento determinado de la ejecución de un algoritmo. La precondición es un aserto que
se debe cumplir al inicio, y la poscondición es un aserto que se debe cumplir cuando finaliza un algorit-
mo. Un aserto se escribe entre los signos "{}".
El Aserto {True} o {Verdadero} representa el conjunto universal de estados, es decir todos los posibles
valores de las variables. El aserto {False} o {Falso} representa el conjunto vacío de estados, esto signi-
fica que no se verifica para ningún valor de las variables.1
Por ejemplo en el siguiente algoritmo que permita sumar los elementos de un arreglo, el aserto {P}
refiere al tamaño del arreglo y la postcondición {Q} expresa formalmente la sumatoria requerida como
salida del algoritmo, S es el algoritmo .
Especificación
Const int N
int a[0..N),m
{P: N>0}
S
{Q: m=< ∑i: 0 ≤ i <N : a[i] >}
Precondiciones y Postcondiciones
Durante el desarrollo de una tarea es importante determinar qué condiciones deben darse al comienzo
o entrada de la tarea para que se cumplan las que se establece que deben ser ciertas al finalizar la misma,
es decir a la salida.
1
M.ª Teresa González de Lena Alonso, Isidoro Hernán Losada y otros (2005) Introducción a la programación: pro-
blemas resueltos en Pascal
Programación Procedural 11
Verificación vs Derivación
Los asertos que deben cumplirse a la entrada de una tarea reciben el nombre de Precondiciones. Si la
operación, tarea o instrucción se realiza sin que la precondición se cumpla no tendremos garantía de los
resultados obtenidos.
Los asertos acerca de los resultados que se esperan a la salida, se llaman postcondiciones.
Verificación vs Derivación
Es importante distinguir entre verificar y derivar o deducir programas. En ambos casos los programas
son tratados como fórmulas lógicas.
Verificar consiste en demostrar que el programa construido es correcto respecto de la especificación
dada.
Derivar un programa permite construir un programa a partir de su especificación, de forma que se ob-
tiene un algoritmo correcto por construcción. –
Se debe distinguir entre corrección total y parcial.
Corrección parcial: se dice que {P} A {Q} es parcialmente correcto si comienza en un estado que
satisface {P} y en caso de que termine satisface {Q}.
Corrección total: Se da cuando un código además de ser correcto parcialmente, termina.
Los algoritmos sin bucles siempre terminan por lo que la corrección parcial implica la corrección total.
Esta distinción es esencial sólo en el caso de códigos que incluyan bucles o recursiones.
En la asignatura Algoritmos y Resolución de Problemas se ha trabajado la verificación de algoritmos
que utilizan estructuras de secuencia y alternativas, como así también sentencias de asignación.
Vimos que dada una especificación {P} A1; A2; A3; …; An {Q} la verificación de la misma se inicia
desde la postcondición, y a partir de ahí se deduce la precondición. Esto es, el código se verifica en sen-
tido contrario a como se ejecuta.
Para {P} A1; A2; A3; …; An {Q} se cumple:
A1;
A3;
…;
An;
Poscond. An = {Q}
Ejecución
Programación Procedural 12
Verificación
El programa A actúa como una función de estados en estados: comienza su ejecución en un estado
inicial válido, descrito por el valor de los parámetros de entrada, y termina en un estado final en el que
los parámetros de salida contienen los resultados esperados
Es necesario recordar que salvo en el caso de la iteración, las otras sentencias utilizan sólo la pre-
condición más débil como método para verificar y derivar programas.
La precondición más débil es el conjunto de todos los estados tales que desde ellos y ejecutando el
programa A se llega a Q.
Cuando se trabaja con algoritmos iterativos, es necesario introducir el concepto de invariante.
Verificación
Comenzaremos a trabajar la verificación de algoritmos iterativos, para luego abocarnos a la derivación
de algoritmos.
Iteración: La instrucción de iteración corresponde a la siguiente sintaxis en seudocódigo:
Mientras (B)
A
finMientras
Siendo A una instrucción (simple o compuesta) y B una expresión booleana.
La instrucción A o cuerpo del ciclo se ejecuta mientras la condición B es verdadera, se vuelve a evaluar
la expresión B, repitiendo el proceso hasta que la condición B es falsa, caso en el que el ciclo termina.
Para verificar un bucle necesitamos un predicado llamado invariante que describa los distintos estados
por los que pasa el bucle, estableciendo la relación que existe entre las variables que participan en él. Es
decir que si bien las variables cambian su valor, se mantienen invariables ciertas relaciones entre
ellas.
El invariante de un bucle es un predicado que captura las características que no varían al ejecutarse un
bucle, es el precursor de la postcondición por lo que debe ser parecido a ella.
Por tanto para especificar una iteración además de la pre y postcondición, es necesario introducir un
nuevo predicado llamado invariante.
{P} precondición
Mientras (B ) {I} invariante
A
finMientras
{Q} postcondición
El invariante es un predicado lógico que tiene la propiedad de ser cierto antes de entrar al bucle, tras
cada iteración (durante el bucle) y después del bucle.
Estas condiciones que debe cumplir el invariante, se pueden expresar como sigue:
1) {P} A0 {I} El invariante se satisface antes de la primer iteración , A0 representa
los valores iniciales de las variables
3) {I ^ ~B} => {Q}El invariante se cumple al salir del bucle (cuando B es falsa) y
debe llevarnos a la postcondición.
Programación Procedural 13
Verificación
4) Además se debe probar que el algoritmo termina. Para ello debemos buscar una función de cota
C que tome valores enteros. Esta función se construye a partir de una expresión con todas o algunas de
las variables que intervienen en la expresión de la condición del bucle y que son modificadas en el cuer-
po del bucle.
La idea es que esa expresión debe dar idea del número de iteraciones que quedan por realizar cada vez
que se ejecuta el ciclo, de forma que se cumpla que:
Esta función C es una cota superior al número de iteraciones que quedan por realizar, de ahí el nombre
de función cota. Sintetizando, la regla para la verificación de una instrucción iterativa es:
{P} A0 {I}
{I ^ B} A {I}
{I ^ ~B} {Q}
{I ^ B} C≥0
{I ^ B ^ C=T} A {C < T}
2
Las variables xy n se llaman variables de programa, ya que describen el estado del programa. La variable i, que
aparece en un cuantificador se llama variable de cuantificación, variable dummy o ficticia.
Programación Procedural 14
Verificación
Se debe probar que la terna {P} n=N; x=0; {I} es correcta, para ello se debe demostrar que
{P} pmd(n=N; x=0, I )
Calculemos la precondición más débil (pmd) utilizando las reglas de verificación de la composición
secuencial y de la asignación.
N
0
pmd(n=N; x=0, I) ≡ ((I ) )
X
n
0 0
(I) ( (0≤n≤N) ^ (x= < ∑ i: n≤i N: V[i]> ) ≡ (0≤n≤N ^ 0= < ∑ i: n ≤i N: V[i]> )
x X
N N
(I ) ≡ (0≤n≤N ^ 0=< ∑ i: n ≤i N: V[i]> )
n n
Por ser el rango vacío, la sumatoria es 0 (elemento neutro) por lo que el segundo término es True
Dado que {P} pmd se ha demostrado que las inicializaciones n=N y x=0 son correctas.
x+v[n-1]
≡ ((0≤n-1≤N ^ x=<∑ i: n-1≤i N: V[i]> ) x
Ahora debemos probar que I ^ B es más fuerte que la aserción (1), es decir
I ^ B ( 1)
Recordemos que I: (0≤n≤N ) ^ (x= <∑ i: n≤i N: V[i] >) y B==n≠0.
Por tanto, de I ^B se puede inferir, considerando en forma separada las condiciones relacionadas con la
variable n y la que corresponde a la variable x:
a) ( 0≤n≤N) ^ (n≠0) ≡ (0<n≤N) ≡ (0≤n-1<N ) 0≤n-1≤N
b) (x= <∑ i: n≤i N:V[i]>) ≡ (x + V[n-1]=V[n-1] +(∑ i: n≤i N: V[i])
Luego, I ^ B (1) como queríamos demostrar.
Esto significa que las acciones que constituyen el cuerpo del ciclo son correctas.
Programación Procedural 15
Verificación
3) El invariante se cumple al salir del bucle (cuando B es falsa) y debe llevarnos a la postcondi-
ción I ^ ~B=> Q.
Para probar que la iteración termina, debe encontrarse la función cota. Como se expuso, esta función
debe definirse en función de las variables que aparecen en la condición B. Como en B la única variable
que aparece es n, entonces podemos tomar:
C(n)=n; n natural n≥0
Se elige n como cota ya que decrece en cada iteración e indica el número de iteraciones que quedan por
realizar en una iteración.
A partir de los 4 pasos anteriores hemos verificado que este algoritmo que suma los elementos de un
arreglo de N enteros, a partir del último elemento, cumple con la especificación dada.
Programación Procedural 16
Verificación
b -y
pmd( p=p *x; y=y-1, I) ≡ ((x=a ^ y≥0 ^ p=a ) yy-1 )p p*x
b –(y -1) b –(y -1)
≡ ((x=a ^ y-1≥0 ^ p=a ) p ≡ ((x=a ^ y-1≥0 ^ p*x=a
p*x
) ≡
b –y+1) b –y
≡ ((x=a ^ y≥1 ^ p*x=a )≡(x=a ^ y≥1 ^ p*x=a*a )
b –y b –y
pmd ≡ (x=a ^ y≥1 ^ p*x= x*a ) ≡ (x=a ^ y≥1 ^ p= a ) (1)
Debemos demostrar que{I ^ B} (1)
b -y
{I^B} ≡(x=a ^ y≥0 ^ p=a ) ^(y≠0) ≡(x=a ^ y>0 ^ p=ab -y)≡
b –y
(x=a ^ y≥1 ^ p=a ) ≡ (1)
Por tanto las acciones que están dentro del cuerpo del bucle son correctas.
Programación Procedural 17
Verificación
3) El invariante se cumple al salir del bucle (cuando B es falsa) y debe llevarnos a la postcondi-
ción I ^ ~B=> Q.
b -y b
I ^~B I ^( y=0) ≡ (x=a ^ y≥0 ^ p=a )^( y=0) (x=a ^ y=0 ^ p=a ) p=ab {Q}
Para probar que la iteración termina, debe encontrarse la función cota. Ésta debe definirse en función de
las variables que aparecen en la condición B y debe determinar la cantidad de iteraciones que falta reali-
zar en cada iteración. Como en B la única variable que aparece es y, que cumple con las condiciones
señaladas, podemos tomar:
C=y
(y-1<T ) (1)
Debemos probar {I ^ B ^ C=T} => (1)
(I ^ y≠0 ^ C=T) (x=a ^ y≥0 ^ p=ab -y ) ^ (y≠0) ^( y=T) ( y=T) ( y-1<T)
Programación Procedural 18
Derivación
Derivación
La derivación de un algoritmo es un proceso que permite construir las instrucciones a partir de la especi-
ficación preocupándose a lo largo de este proceso de su corrección. Así, al terminar la derivación el al-
goritmo encontrado será correcto o sea cumple su especificación, por construcción.
En la especificación, la postcondición describe el estado que se desea alcanzar, de ahí que el proceso de
construcción está guiado por la postcondición.
Si recordamos, la regla de verificación de algoritmos es la siguiente:
{P} A0 {I} El invariante se satisface antes de la primera iteración
{I ^ B} A {I} El invariante se mantiene al ejecutar el cuerpo A del bucle
{I ^ ~B} {Q} El invariante se cumple al salir del bucle
{I ^ B} C≥0 La función cota es ≥ 0 cuando se cumple condición B
{I ^ B ^ C=T} A {C < T} La función cota decrece al ejecutar el cuerpo del bucle
Para el caso de la derivación, se conoce únicamente la precondición y la postcondición, por tanto debe
determinarse:
1- El invariante I.
2- La condición B del ciclo.
3- Las sentencias de inicialización de variables, esto es las condiciones iniciales A0.
4- El cuerpo A del ciclo: el cuerpo está constituido por dos instrucciones fundamentales A1 y A2. La
Instrucción A1 es parte del cuerpo del bucle y se encarga de mantener cierto el invariante, A2 re-
presenta la instrucción que hace que las variables que intervienen en la expresión B avancen hacia
la condición de salida del bucle.
5- La función cota.
3
Adaptación de Narciso Martí Oliet
Programación Procedural 19
Derivación
Existen 2 posibilidades:
a.) Si la postcondición está en forma conjuntiva, se pueden ensayar distintas posibilidades eligiendo
una parte ella como invariante y el resto como negación de la condición del bucle.
b.) Se puede introducir una nueva variable que sustituya en la postcondición a una constante. En
este caso el invariante será la postcondición generalizada con esa nueva variable y la condición del bucle
será la negación de la igualdad entre la variable y la constante sustituida
Programación Procedural 20
Derivación
I ^ B=> C≥0
{I ^ B ^ C =T} A{C<T}
Asegura que la guarda B sea falsa en algún momento, lo que garantiza la finalización del ciclo.
Para ejemplificar estos pasos veamos cómo realizar la derivación del algoritmo que calcula la suma de
los elementos de un arreglo, con un recorrido de derecha a izquierda, del cual fue realizada la verifica-
ción.
Ejemplo: Derivar un programa que calcule la suma de los elementos de un arreglo que contiene N com-
ponentes enteras, recorriendo el arreglo de derecha a izquierda.
Especificación
constante entero N
entero x, V[0..N]
{P: N 0}
..............
Mientras (B)
A
finMientras
{Q: x= <∑i: 0 ≤ i < N: V[i]>}
Programación Procedural 21
Derivación
Por tanto se elige como guarda n ≠ 0. Por otra parte los valores de i están definidos en el rango 0 ≤ i <
N, esto permite reforzar el invariante definiendo los valores que puede tomar la variable n a fin de evitar
indefiniciones.
B: n≠ 0
I : x=< ∑i: n ≤ i< N: V[i]> ^ ( 0 ≤ n ≤ N)
2. Inicializar las variables, es decir diseñar A0 de modo que el invariante sea cierto; esto es
{P} A0 {I}
Se debe inicializar x y n, variables que aparecen en el invariante, de tal manera que la terna {P} A0 {I}
sea correcta.
Para ello se debe elegir expresiones enteras E y F con las cuales inicializaremos a x y n, de modo que se
garantice que
{P} pmd (x= F, n=E, I)
pmd (x= F, n=E, I) ≡ (( I)nE) xF ≡ F=< ∑ i: E≤ i<N: V[i]> ^ (0≤ E≤ N) (1)
Como E representa el valor del índice del arreglo y debemos recorrerlo de derecha a izquierda se ini-
cializa en N E=N
reemplazando en (1) F= ∑ i: N ≤ i < N: V[i]> ^ (0≤ N) (2)
≡ Por rango vacío, el elemento neutro de la suma es 0
Si F=0, en (2) true ^ (0≤ N) ≡ (0≤ N )≡{P}
Hemos probado que {P} pmd (x,n= F, E, I) , y por tantos son válidas las inicializaciones n=E=N, y
x=F= 0
Por tanto n = N, y x = 0 son los valores iniciales de las variables n y x
{P: N 0}
n= N,
x=0
Mientras (n ≠ 0)
A1
A2
finMientras
{Q: x= <∑i: 0 ≤ i < N: V[i]>}
Programación Procedural 22
Derivación
La función A2 depende de la variable n, la cual deberá decrecer por ejemplo en 1 unidad para garantizar
la finalización del bucle, entonces se elige A2: n=n-1.
Programación Procedural 23
Derivación
5. Probar que el algoritmo termina, es decir la cota decrece dentro del ciclo
Para probar que la función cota decrece al ejecutar el cuerpo A del bucle, debe ser correcta la siguiente
especificación: {I ^ B ^ C=T} A {C < T}
Para probar que esta terna es correcta se debe demostrar que
{I ^ B ^ C=T} pmd(A, n<T)
Utilizando las reglas de verificación de la composición secuencial y de la asignación, se tiene:
pmd(A, n<T) pmd( x = x + v[n-1]; n=n-1, n < T) ((n<T ) nn-1 ) x x+v[n-1]
(n<T) n n-1 (n-1<T ) (1)
De esta manera se demuestra la corrección total del algoritmo, es decir que además de cumplir las espe-
cificaciones, el ciclo termina.
constante entero N
entero x,n, a[0..N]
n=N,
x=0
mientras (n≠ 0)
x= x + a[n-1]
n =n-1
finMientras
Ejemplo: Dados dos números enteros no negativos a y b, derivar un programa que calcule la potencia a b
{ P a ≥0 ^ b≥0, a , b ϵ Z}
A
b
{Q: p=a }
Programación Procedural 24
Derivación
Como la postcondición no está en forma conjuntiva, optamos por introducir una nueva variable que
sustituya en la postcondición a una constante. Como en la postcondición aparecen los valores a y b,
debemos determinar cuál se reemplaza por la variable.
De ellas, a no modifica su valor dentro del proceso iterativo, por lo que reemplazamos b por la variable
n , b=n y se refuerza el invariante determinado el rango para n. Por tanto el invariante y la guarda son:
I :( p=an)^ (0≤ n ≤ b )
B: n≠ b
2. Inicializar las variables, es decir diseñar A0 de modo que el invariante sea cierto; esto es
{P} A0 {I}
Se debe inicializar las variables p y n, que aparecen en el invariante, de tal manera que la terna {P} A0
{I} sea correcta.
Para ello se debe elegir expresiones enteras E y F tales que se garantice que
{P} pmd (p = F; n= E, I)(1)
pmd (p = F; n= E, I) ≡ ((I) nE) pF≡ ( ( p=an)^( (0≤ n ≤ b ))nE)pF
≡( p=aE)^( (0≤ E ≤ b ))pF≡ ( F=aE)^( (0≤ E ≤ b )
Si tomamos el valor inicial del rango de variación de E, entonces E=0, por tanto F=1 ya que ( 1=a0 )
Entonces los valores iniciales para las variables n y p podrían ser: n=0 y p=1, para demostrarlo se debe
verificar (1)
pmd≡( 1=a0 )^( (0≤b) (2)
Como { P a ≥0 ^ b≥0 } (2) entonces n=0 y p=1 es una inicialización correcta
Entonces hasta ahora el algoritmo queda:
n=0
p=1
Mientras (n≠ b)
Programación Procedural 25
Derivación
Por tanto {I ^ B} > (1) y son válidas las siguientes asignaciones dentro del cuerpo del ciclo:A2: n=
n+1; A1: p=p*a
n=0
p=1
Mientras (n≠ b)
p=p*a
n= n+1
finMientras
5. Probar que el algoritmo termina, es decir la cota decrece dentro del ciclo
Para probar que la función cota decrece al ejecutar el cuerpo A del bucle, debe ser correcta la siguiente
especificación: {I ^ B ^ C=T} A {C < T}
Para probar que esta terna es correcta se debe demostrar que
{I ^ B ^ C=T} pmd(A, b-n<T)
pmd(A, b-n<T) pmd(p=p*a ; n=n+1; b-n<T) ((b-n<T ) n n+1) p p*a (b-(n+1))< T) (1)
Programación Procedural 26
Derivación
Una de las pautas que se proponen para construir el invariante y la condición del bucle B, es analizar la
postcondición y si es una conjunción tomar un miembro como el invariante y el otro como la negación
de B.
Por lo tanto escribamos la poscondición de la siguiente forma:
Esta expresión dice: que el valor de x varía en el intervalo [0..N); y para todo índice menor que x la co-
rrespondiente componente no es k y para el índice x la componente es k o x es N.
Programación Procedural 27
Derivación
Como la postcondición está expresada como una conjunción de dos expresiones elegiremos a una como
invariante y la otra como la negación de la condición del bucle.
I: (0≤x≤N) ^ (∀ j: 0≤ j<x : v[j]≠k)
B: (x≠N)^(V[x]≠k)
Programación Procedural 28
Derivación
5. Probar que el algoritmo termina, es decir la cota decrece dentro del ciclo
Para probar que la función cota decrece al ejecutar el cuerpo A del bucle, debe ser correcta la siguiente
especificación: {I ^ B ^ C==T} A {C < T}
Para probar que esta terna es correcta se debe demostrar que
{I ^ B ^ C=T} pmd(A, N – x < T )
pmd(A, N– x < T) pmd( x=x+1, N – x <T) (N – x <T) xx+1 (N– (x +1) <T) (1)
Se debe probar que {I ^ B ^ C==T} (1)
Si N-x==T N-(x+1) <T
entero v[N],i
{P: N≥0}
S
{x= (#i: 0≤i<N : v[i] mod 2==0)}
Es decir un algoritmo que permita contar los elementos pares de una arreglo.
Programación Procedural 29
Derivación
Como la postcondición no está en forma conjuntiva, optamos por introducir una nueva variable que
sustituya en la postcondición a una constante. En este caso el invariante será la postcondición generali-
zada con esa nueva variable y la condición del bucle será la negación de la igualdad entre la variable y
la constante sustituida.
En este ejemplo, el cuantificador que aparece en la postcondición involucra las constantes 0 y N. Por
tanto, utilizando la técnica de reemplazo de constantes por variables, reemplazando la constante N por
la variable n, dado que se va a recorrer el arreglo comenzando desde la posición 0, se obtiene:
I :x= (#i: 0 ≤ i < n : v[i] mod 2==0) ^ (0 ≤ n ≤ N )
2. Inicializar las variables, es decir diseñar A0 de modo que el invariante sea cierto; esto es {P}
A0 {I}
Se debe inicializar las variables de programa x y n, que aparecen en el invariante, de tal manera que la
terna {P} A0 {I} sea correcta.
Para ello se debe elegir expresiones enteras E y F tales que se garantice que
{P} pmd (x= F,n= E, I) x= (#i: 0≤i<n : v[i] mod 2==0) ^ (0≤n≤N) ) nE) xF
(1) (2)
Teniendo en cuenta el rango de E, expresado en (2), lo inicializamos E=0, entonces en (1) el rango es
vacio y el elemento neutro de un contador es 0.
Por tanto si F=0 ; (1) es true ; entonces
true ^ ( 0≤E<N) ( 0≤E<N) N ≥0
pmd N ≥0 {P}
Entonces los valores iniciales para las variables son x=0, n=0
Programación Procedural 30
Derivación
{I ^ B} (x=E ;n = n+1) {I}. Para que esta terna sea correcta, debemos demostrar que
I ^ B pmd (x=E; n = n+1, I) (1)
pmd(x=E; n=n+1, I) ≡ ((I)n n+1
) x ≡ (x=(#i: 0≤i<n : v[i] mod 2==0) ^ 0≤n≤N) ) n n+1 x E
E
5. Probar que el algoritmo termina, es decir la cota decrece dentro del ciclo
Para probar que la función cota decrece al ejecutar el cuerpo A del bucle, debe ser correcta la siguiente
especificación: {I ^ B ^ C==T} A {C < T}
Es decir que {I ^ B ^ C=T} pmd(A, N – n<T )
pmd(A,N– n<T) pmd( Si (v[n] mod 2=0) entonces x=x+1 finsi; n=n+1, N – n<T)
pmd (( N – n< T)n n+1 ( N - ( n+1) ) < T (1)
Se debe probar que ( N – n==T) (1)
( N – n==T) ( N – (n+ 1) )<T ) (1)
Por lo tanto el algoritmo completo es:
entero v[N],i
{P: N≥0}
n=0
x=0
Mientras ( n ≠N)
Si (v[n] mod 2==0 )
entonces x=x+1
finsi
n=n+1
finMientras
{Q: x= (#i: 0≤i<N : v[i] mod 2==0)}
Programación Procedural 31
Bibliografía
Bibliografía
Blanco, J.; Smith, S. y Barsotti, D. (2008) Cálculo de Programas. Facultad de Matemática, As-
tronomía y Física. Universidad Nacional de Córdoba.
Martí Oliet, N.; Segura Díaz, C. y Verdejo López, J. (2006) Especificación, derivación y análisis
de algoritmos. Pearson Educación.SA. Madrid.
Silva Ramírez, E. (2010) Verificación Formal de Algoritmos. Ejercicios Resueltos. Universidad
de Cádiz. Servicio de Publicaciones.
M.ª Teresa González de Lena Alonso, Isidoro Hernán Losada y otros (2005) Introducción a la
programación: problemas resueltos en Pascal. Editorial Centro de Estudios Ramón Areces SA.
España. Recuperado de:
https://books.google.com.ar/books?id=25jqDQAAQBAJ&pg=PA517&lpg=PA517&dq=un+ase
rto+es+un+predicado&source=bl&ots=f1oeCmkhAh&sig=8RT4t_rYMBbrZ6hFgDClmVRKPP
c&hl=es-
419&sa=X&ved=0ahUKEwja3qaf07vVAhVEf5AKHQuQBkMQ6AEIKzAB#v=onepage&q=u
n%20aserto%20es%20un%20predicado&f=false
Programación Procedural 32
Unidad 1: Lenguajes Procedurales
Introducción
Para 1969 ya se habían contabilizado más de 120 lenguajes de programación, hoy en día hay quienes
afirman que la lista supera los 2000. Wikipedia nombra y describe alrededor de 6714.
Si bien un programador profesional usa efectivamente no más de tres lenguajes, en función de las posi-
bilidades y las tendencias de la empresa o medio en que está inserto, es importante que se explore más
profundamente en el diseño de los lenguajes y en su efecto sobre la implementación, por muchas razo-
nes, entre ellas:
Mejorar la habilidad en el desarrollo de algoritmos eficaces.
Mejorar el uso de lenguaje de programación que el profesional utiliza.
Hacer posible una mejor elección del lenguaje de programación, cuando del profesional dependa.
Facilitar el estudio de nuevos lenguajes
Permitir el diseño de nuevos lenguajes.
Concepto de Computación
Desde un punto de vista amplio, que incluya la definición, diseño e implementación de lenguajes de
programación, el concepto de computación se puede interpretar como "cualquier proceso que puede
ser realizado por una computadora". No solo cálculos matemáticos sino también manipulación de datos,
procesamiento de textos, almacenamiento y recuperación de la información.
Lenguaje de Programación
Para que exista comunicación, debe existir una comprensión mutua de cierto conjunto de símbolos y
reglas del lenguaje. En un lenguaje natural, el significado de los símbolos se establece por la costumbre
y se aprende mediante la experiencia.
Los lenguajes de programación tienen como objetivo la construcción de programas, normalmente escri-
tos por personas.
Estos programas se ejecutarán sobre una computadora que realizará las tareas descritas.
La utilización de un lenguaje de programación requiere, por tanto, una comprensión mutua por parte de
personas y máquinas.
Un lenguaje de programación es un sistema notacional para describir computaciones en una forma
legible tanto para la máquina como para el ser humano.5
Abstracciones procedimentales
Es una secuencia nombrada de instrucciones que tienen una función específica y limitada. (Ejemplo de
esto es el uso de funciones en lenguaje C).
Abstracciones de datos
4
Enlace Web https://es.wikipedia.org/wiki/Anexo:Lenguajes_de_programación - 01/08/2017.
5
Louden, Kenneth C. (2004) Lenguajes de Programación Principios y Práctica. Editorial Thomson. México, D.F. Pág 3.
6
Ibidem. Pág 26.
Programación Procedural 33
Lenguajes Imperativos
Técnica de inventar nuevos tipos de datos que sean más adecuados a una aplicación y, por consiguiente,
facilitar la escritura del programa. (Ejemplo: uso de struct en lenguaje C).
Abstracciones de control
Resume propiedades de la transferencia de control, es decir, la modificación de la trayectoria de ejecu-
ción de un programa en una determinada situación. (Ejemplo: for, if, while, etc son ejemplo de abstrac-
ciones de control en lenguaje C).
Lenguajes Imperativos
Un lenguajes procedural o imperativo es un lenguaje controlado por mandatos o instrucciones.
El proceso de ejecución de programas procedurales por la computadora, tiene lugar a través de una serie
de estados, cada uno definido por el contenido de la memoria, los registros internos y los almacenes
externos durante la ejecución.
El almacenamiento inicial de estas áreas define el estado inicial de la computadora. Cada paso en la
ejecución transforma este estado en otro nuevo, a través de la modificación de alguna de estas áreas.
Esta transformación se denomina transición de estado. Cuando termina la ejecución del programa, el
estado final viene dado por el contenido final de las áreas antes mencionadas. Por lo tanto, la ejecución
de un programa puede verse como una serie de transiciones de estados que realiza la computadora.
En este modelo de computación, un programa es un conjunto de enunciados y la ejecución de cada
enunciado hace que cambie el valor de una o más localidad de almacenamiento en memoria, es decir que
la memoria pase a un nuevo estado.
Si se considera a la memoria como un conjunto de cajas, la construcción de un programa consiste en
construir los estados de máquina sucesivos (canicas en cajas o valores en variables) que se necesitan
para llegar a la solución.
Este tipo de modelo de computación, tiene características propias del modelo de Von Newmann, varia-
bles que representan valores de memoria y asignación que permite que el programa opere sobre dichos
valores.
Existen otros paradigmas que no son tan dependientes del modelo de computadora de Von Newmann, y
serán estudiados en cursos superiores.
El lenguaje C, lenguaje que se usará en este curso responde al paradigma imperativo.
Programación Procedural 34
Lenguajes Imperativos
Para que el computador pueda comprender un lenguaje humano, es necesario diseñar métodos que tra-
duzcan tanto la estructura de las frases como su significado a código máquina. Los diseñadores de len-
guajes de programación construyen lenguajes que saben cómo traducir o que creen que serán capaces de
traducir. Si las computadoras fuesen la única audiencia de los programas, estos se escribirían directa-
mente en código máquina o en lenguajes mucho más mecánicos.
Sin embargo, el programador debe ser capaz de leer y comprender el programa que está construyendo y
las personas no son capaces de procesar información con el mismo nivel de detalle que las máquinas.
Los lenguajes de programación son, por tanto, una solución de compromiso entre las necesidades
del emisor (programador - persona) y del receptor (computadora - máquina).
Las declaraciones, tipos, nombres simbólicos, etc. son concesiones de los diseñadores de lenguajes para
que los humanos podamos entender mejor lo que se ha escrito en un programa. Por otro lado, la utiliza-
ción de un vocabulario limitado y de unas reglas estrictas son concesiones para facilitar el proceso de
traducción.
La definición de un lenguaje de programación necesita una descripción precisa y completa, además de
un traductor que permita aceptar el programa en el lenguaje en cuestión y que, lo ejecute directamente o
bien lo transforme en una forma adecuada para su ejecución.
Dos partes se pueden distinguir en la definición de un lenguaje. La estructura o sintaxis y la semántica
o significado.
La sintaxis estudia las reglas de formación de frases. Las reglas de sintaxis nos dicen cómo se escriben
los enunciados, declaraciones y otras construcciones del lenguaje.
La semántica, sin embargo, hace referencia al significado de esas construcciones.
7
Cueva Lovelle, Juan (1998) Conceptos Básicos de Procesadores de Lenguajes. Ed. SERVITEC. España. Capítulo 2. Pág. 8.
Programación Procedural 35
Lenguajes Imperativos
Programa
(Lenguaje Fuente)
Datos Intérprete
Resultados
Traductores
Por Traductor se denota a cualquier “procesador de lenguajes”8 que acepta programas en cierto lengua-
je fuente (que puede ser de alto o bajo nivel) como entrada y produce programas funcionalmente equiva-
lentes en otro lenguaje objeto.
8
Procesador de lenguaje es un nombre genérico que se aplica a traductores, compiladores, intérpretes, y otros programas
que realizan operaciones con los lenguajes.
9
Pratt Terrence y Marvin Zelkowitz (1987) Lenguajes de Programación Diseño e Implementación. Editorial Prentice Hall. His-
pano americana, S.A. 2° Edición. Capítulo 2. Pág. 41.
Programación Procedural 36
Lenguajes Imperativos
Ensamblador
El término ensamblador (del inglés assembler) se refiere a un tipo de programa que se encarga de tradu-
cir un archico fuente escrito en un lenguaje ensamblador, a un fichero objeto que contiene código
máquina, ejecutable directamente por el microprocesador.
Computadora
Instrucciones escritas Instrucciones escritas
en lenguaje en codigo de máquina
ensamblador Programa
Ensambla-
dor
Compilador
Para traducir las instrucciones de un programa escrito en un lenguaje de alto nivel a instrucciones de
un lenguaje máquina, hay que utilizar un programa llamado compilador. Así pues, el compilador es un
programa que recibe como datos de entrada el código fuente de un programa escrito por un programa-
dor, y genera como salida un conjunto de instrucciones escritas en el lenguaje binario de la computadora
donde se van a ejecutar.
Computadora
Instrucciones escritas Instrucciones escritas
en lenguaje de alto en un lenguaje de
nivel Programa máquina
Compilador
Fases de la Compilación
10
Ibidem. Pratt. - Capítulo 3. Pág. 72.
Programación Procedural 37
Implementación de lenguajes
Analizador léxico (o revisor) es un programa que lee la secuencia de caracteres del programa fuente, de
izquierda a derecha y agrupa esas secuencias de caracteres en unidades con significado propio (compo-
nentes léxicos o “tokens” en inglés). Es decir que identifica el tipo de cada o elemento léxico: identifi-
cador, número, operador, delimitador y adjunta una marca de tipo, para enviárselo al analizador sintácti-
co.
Analizador sintáctico o parsing identifica estructuras más grandes del programa como enunciados,
declaraciones, llamadas a funciones, expresiones, etc., a partir de los elementos léxicos o tokens produ-
cidos por el analizador léxico. Analiza las relaciones estructurales entre los componentes léxicos, esto
es semejante a realizar el análisis gramatical sobre una frase en lenguaje natural
Analizador semántico: programa que detecta la validez semántica de las sentencias aceptadas por el
analizador sintáctico. Las tareas básicas a realizar son la verificación de tipos en asignaciones y expre-
siones, la declaración del tipo de variables y funciones antes de su uso, el correcto uso de operadores, el
ámbito de las variables y la correcta llamada a funciones.
Se procesan las estructuras sintácticas reconocidas por el analizador sintáctico y comienza a tomar forma
la estructura del código ejecutable
En este curso se observará al análisis semántico estático (en tiempo de compilación), utilizando la Tabla
de Símbolos.
Traducción e Interpretación
Los traductores y los interpretes proporcionan ventajas diferentes, por ejemplo una ventaja de los traduc-
tores es que los códigos referentes a ciclos que se repiten gran cantidad de veces y son ejecutados mu-
chas veces se traducen sólo una vez a diferencia de los los interpretes que tienen que decodificarlo la
misma cantidad de veces que se ejecute.
Una ventaja para la interpretación es que en caso de un error proporcionan más información acerca de
donde fue la falla que en un código objeto traducido.
Por lo tanto, si el programa de entrada está en un lenguaje de alto nivel, el traductor procesará los enun-
ciados del programa en el orden físico de entrada, una vez cada uno. El intérprete podrá procesar los
enunciados siguiendo el orden lógico de control y algunos enunciados podrá procesarlos más de una vez
y otros omitirlos (según que sean parte de un bucle o que el control no los alcance).
No siempre se dan por separado, la traducción y la interpretación. Generalmente se mezclan estos dos
mecanismos; según cuál de ellos prevalezca se habla de lenguajes compilados cuando prevalece la tra-
ducción (ejemplo: C, C++, Pascal, Fortran, Ada, Cobol) o interpretados cuando predomina la simula-
ción (ejemplo: LISP, PROLOG, SmallTalk, ML).
En general cuando hay enunciados repetitivos es mejor la traducción pues se decodifica una vez, aunque
se ejecuten muchas veces. La desventaja es la pérdida de información acerca del programa.
Implementación de lenguajes
Cuando se implementa un lenguaje de programación, las estructuras de datos y algoritmos que se utili-
zan en tiempo de ejecución de un programa escrito en ese lenguaje, definen un modelo de ejecución
definido por la implementación del lenguaje.
Por ejemplo, para la implementación de las operaciones de suma de enteros y raíz cuadrada, el imple-
mentador puede optar por representar la suma de enteros a partir de la suma de enteros proporcionada
directamente por el hardware, y para la operación de raíz cuadrada puede optar por una simulación por
software.
De ahí que es importante entender que la implementación de un lenguaje está determinada por múltiples
decisiones que debe tomar el implementador en función de los recursos de hardware y software disponi-
bles en la computadora subyacente y los costos de su uso.
Programación Procedural 38
Criterios de diseño de lenguajes de programación
Eficiencia
La eficiencia debe evaluarse en distintas dimensiones (código, traducción, etc.):
Eficiencia de código: Hace referencia a un diseño del lenguaje que permite que el traductor genere
un código ejecutable eficiente. Por ejemplo el uso de declaraciones anticipadas de las variables.
Eficiencia de traducción: Hace referencia a un diseño del lenguaje que permite que el código fuen-
te se traduzca con rapidez y con un traductor de tamaño razonable. Ejemplo: C standard permite un
compilador de una sola pasada pues las variables se declaran antes de usarse. C++, el compilador
debe realizar una segunda pasada para resolver referencias del identificador.
Eficiencia de implementación: Hace referencia a la eficiencia con la que se puede escribir un tra-
ductor. Esto está relacionado con la eficiencia de la traducción y con la complejidad de la definición
del lenguaje.
Eficiencia de la programación: Hace referencia a la facilidad para escribir programas en el lengua-
je. Entre otras, esto involucra la capacidad de expresión del lenguaje y la capacidad de darle mante-
nimiento al programa.
Regularidad
La regularidad es una cualidad que implica que hayan pocas restricciones en el uso de sus constructores
particulares, pocas interacciones raras entre dichos constructores, y menos sorpresas en la forma en la
que se comportan las características del lenguaje.
Distintos principios básicos miden la regularidad, entre otros la generalidad, la ortogonalidad y la uni-
formidad.
Generalidad: Un lenguaje alcanza generalidad evitando casos especiales en cuanto a disponibilidad
o uso de constructores.
La no generalidad está ligada a restricciones de los constructores con independencia del contexto en
que estén.
En lenguaje C el operador de igualdad == carece de generalidad. Dos arreglos no pueden comparar-
se a través de este operador, por ejemplo.
En cuanto a la longitud de los arreglos, lenguaje C admite generalidad pues maneja arreglos de lon-
gitud variable, mientras que esta generalidad no está presente en lenguaje Pascal, que admite sólo
arreglos de longitud fija.
Ortogonalidad: Se refiere al atributo de combinar varias características de un lenguaje de manera
que la combinación tenga significado.
Ejemplo: Si un lenguaje provee de expresiones que pueden devolver un valor y también posee un
enunciado condicional que compara valores; estas dos características son ortogonales si se puede
usar dentro del enunciado condicional cualquier expresión.
Uniformidad: Hace referencia a la consistencia de la apariencia y comportamiento de los construc-
tores del lenguaje.
Programación Procedural 39
Modelos de computación
Modelos de computación11
Entendemos por una computación a cualquier proceso que puede ser realizado por una computadora,
con esto nos estamos refiriendo no solo a cálculos matemáticos sino que incluimos la manipulación de
datos, el procesamiento de textos y el almacenamiento y recuperación de la información.
Estos modelos de computación dan lugar a distintas familias de lenguajes o paradigmas. En informática,
es posible observar varias comunidades, cada una hablando su propio lenguaje y utilizando sus propios
paradigmas. De hecho los lenguajes de programación suelen fomentar el uso de ciertos paradigmas y
disuadir el uso de otros.
Paradigma de Programación12
Un paradigma de programación "consiste en un método para llevar a cabo cómputos y la forma en la
que deben estructurarse y organizarse las tareas que debe realizar un programa".
Se trata de una propuesta tecnológica adoptada por una comunidad de programadores, y desarrolladores
cuyo núcleo central es incuestionable en cuanto que únicamente trata de resolver uno o varios problemas
claramente delimitados; la resolución de estos problemas debe suponer consecuentemente un avance
significativo en al menos un parámetro que afecte a la ingeniería de software. Representa un enfoque
particular o filosofía para diseñar soluciones.
Los paradigmas difieren unos de otros, en los conceptos y la forma de abstraer los elementos involucra-
dos en un problema, así como en los pasos que integran su solución del problema, en otras palabras, el
cómputo.
Tiene una estrecha relación con la formalización de determinados lenguajes en su momento de defini-
ción. Es un estilo de programación empleado.
Un paradigma de programación está delimitado en el tiempo en cuanto a aceptación y uso, porque nue-
vos paradigmas aportan nuevas o mejores soluciones que lo sustituyen parcial o totalmente.
En la programación imperativa se describe paso a paso un conjunto de instrucciones que deben ejecu-
tarse para variar el estado del programa y hallar la solución, es decir, un algoritmo en el que se describen
los pasos necesarios para solucionar el problema.
En la programación declarativa las sentencias que se utilizan lo que hacen es describir el problema que
se quiere solucionar; se programa diciendo lo que se quiere resolver a nivel de usuario, pero no las ins-
trucciones necesarias para solucionarlo. Esto último se realizará mediante mecanismos internos de infe-
rencia de información a partir de la descripción realizada.
11
Ibidem. Louden. Capítulo 1. “Paradigmas de computación”. Pág. 12 a 18.
12
https://es.wikipedia.org/wiki/Lenguaje_de_programaci%C3%B3n#Paradigma_de_programaci%C3%B3n
Programación Procedural 40
Modelos de computación
Programación dinámica: está definida como el proceso de romper problemas en partes peque-
ñas para analizarlos y resolverlos de forma lo más cercana al óptimo, busca resolver problemas
en O(n) sin usar por tanto métodos recursivos. Este paradigma está más basado en el modo de
realizar los algoritmos, por lo que se puede usar con cualquier lenguaje imperativo.
Programación declarativa
está basada en describir el problema declarando propiedades y reglas que deben cumplirse, en lugar de
instrucciones. Hay lenguajes para la programación funcional, la programación lógica, o la combinación
lógico-funcional. La solución es obtenida mediante mecanismos internos de control, sin especificar
exactamente cómo encontrarla (tan solo se le indica a la computadora qué es lo que se desea obtener o
qué es lo que se está buscando). No existen asignaciones destructivas, y las variables son utilizadas con
transparencia referencial. Los lenguajes declarativos tienen la ventaja de ser razonados matemáticamen-
te, lo que permite el uso de mecanismos matemáticos para optimizar el rendimiento de los programas.
Unos de los primeros lenguajes funcionales fueron Lisp y Prolog.
Programación funcional: basada en la definición los predicados y es de corte más matemático,
está representado por Scheme (una variante de Lisp) o Haskell. Python también representa este
paradigma.
Programación lógica: basado en la definición de relaciones lógicas, está representado por Pro-
log.
Programación con restricciones: similar a la lógica usando ecuaciones. Casi todos los lengua-
jes son variantes del Prolog.
Programación multiparadigma: es el uso de dos o más paradigmas dentro de un programa. El
lenguaje Lisp se considera multiparadigma. Al igual que Python, que es orientado a objetos, re-
flexivo, imperativo y funcional.4 Según lo describe Bjarne Stroustrup, esos lenguajes permiten
crear programas usando más de un estilo de programación. El objetivo en el diseño de estos len-
guajes es permitir a los programadores utilizar el mejor paradigma para cada trabajo, admitiendo
que ninguno resuelve todos los problemas de la forma más fácil y eficiente posible. Por ejemplo,
lenguajes de programación como C++, Genie, Delphi, Visual Basic, PHP o D5 combinan el pa-
radigma imperativo con la orientación a objetos. Incluso existen lenguajes multiparadigma que
permiten la mezcla de forma natural, como en el caso de Oz, que tiene subconjuntos (particula-
ridad de los lenguajes lógicos), y otras características propias de lenguajes de programación fun-
cional y de orientación a objetos. Otro ejemplo son los lenguajes como Scheme de paradigma
funcional o Prolog (paradigma lógico), que cuentan con estructuras repetitivas, propias del para-
digma imperativo.
Programación Procedural 41
/Lenguaje C
Programación reactiva: Este paradigma se basa en la declaración de una serie de objetos emi-
sores de eventos asíncronos y otra serie de objetos que se "suscriben" a los primeros (es decir,
quedan a la escucha de la emisión de eventos de estos) y *reaccionan* a los valores que reciben.
Es muy común usar la librería Rx de Microsoft (Acrónimo de Reactive Extensions), disponible
para múltiples lenguajes de programación.
Lenguaje específico del dominio o DSL: se denomina así a los lenguajes desarrollados para re-
solver un problema específico, pudiendo entrar dentro de cualquier grupo anterior. El más repre-
sentativo sería SQL para el manejo de las bases de datos, de tipo declarativo, pero los hay impe-
rativos, como el Logo.
Lenguaje C
Para hablar del lenguaje C, es bueno recordar que los primeros lenguajes de programación se denomina-
ban lenguajes de bajo nivel o lenguajes de máquina (ceros y unos). El uso de éstos implicaba un cono-
cimiento exhaustivo del computador y un manejo preciso de las direcciones de memoria asociadas a las
distintas operaciones y operadores. Para resolver los problemas que surgían de esta tediosa forma de
programar, surgieron los lenguajes de programación ensambladores que usaban símbolos para expresar
las distintas operaciones y las direcciones de memoria donde se almacenaban los valores de las varia-
bles.
Finalmente, surgen los lenguajes de alto nivel, que distan mucho del lenguaje de máquina y se asemejan
más al lenguaje que utilizan las personas para comunicarse (el inglés).
Debido al notable abaratamiento de las computadoras alrededor de los años 70, fue evidente el progreso
de la tecnología de compiladores, dando lugar al surgimiento de muchos lenguajes líderes sobre distintos
dominios: comercial, científico, militar etc. Si bien muchos de ellos ya prácticamente no se usan, y mu-
chos más se han desarrollado, en la actualidad el lenguaje C tiene la característica de estar aún vigente
como consecuencia de las continuas revisiones que en él se han realizado y de su capacidad de reflejar
los cambios que se produjeron en otras áreas de la computación.
El lenguaje C fue desarrollado en 1972 por Dennis Ritchie y Ken Thompson en los laboratorios Bell
Telephone de AT&T.El lenguaje C comienza a desarrollarse como un lenguaje para escribir software y
compiladores, específicamente para desarrollar el sistema operativo UNİX, pero su uso se ha generali-
zado y hoy en día muchos sistemas están escritos en C o en C++. Además, es un lenguaje que no está
ligado a ningún sistema operativo ni máquina, lo que permita la portabilidad entre distintas plataformas.
El lenguaje C es un lenguaje de propósitos generales y, si bien es un lenguaje de alto nivel, provee sen-
tencias de bajo nivel que permite facilidades similares a las que se encuentran en los lenguajes ensam-
bladores.
Edición
Esta etapa consiste en la escritura de un programa en C, utilizando un programa editor. Este programa,
que se conoce como programa en código fuente, deberá ser almacenado en el disco con un nombre que
lo identifique para su posterior compilación. Los programas fuentes se almacenan o graban con exten-
sión .c o .cpp según se utilice lenguaje C ó C++ ( C plus plus).
Este es el Preprocesador, un editor de texto, que toma como entrada una forma ampliada de un lengua-
je fuente y su salida es una forma estándar del mismo lenguaje fuente.
Programación Procedural 42
/Lenguaje C
Compilación
El proceso de compilación consiste en la traducción del programa fuente a código binario, para que la
computadora pueda interpretar las órdenes. Esto se logra dando la orden compilar del ambiente de desa-
rrollo integrado (IDE) o bien desde la línea de órdenes.
Durante esta etapa, se detectan los posibles errores sintácticos cometidos por el programador, cuando
no ha respetado las reglas de sintaxis del lenguaje. En este caso, es necesario volver a editar el progra-
ma, corregir los errores señalados y compilar nuevamente. Este proceso se repite hasta que no se detec-
tan errores, obteniéndose el programa en código objeto que es almacenado en disco.
Se debe tener en cuenta que antes de la fase de traducción mencionada, el compilador invoca automáti-
camente a un programa preprocesador que obedece directrices especiales que indican ciertas operacio-
nes que deben realizarse antes de la compilación. En general, esas operaciones consisten en la inclusión
de otros archivos en el programa a compilar o en el reemplazo de símbolos especiales por textos de pro-
grama.
Enlace
El programa objeto no es ejecutable, esto se debe a que generalmente los programas contienen referen-
cias a funciones definidas en otro lugar, una biblioteca estándar o en bibliotecas definidos por algún
programador, por ejemplo. El enlazador es el que se encarga de vincular el código objeto con las biblio-
tecas, obteniendo así el programa ejecutable, que es almacenado en disco.
Carga
El Cargador es un procesador de lenguajes cuya entrada es un lenguaje objeto, un programa en lenguaje
de máquina en manera reubicable; las modificaciones que realiza son a tablas de datos que especifican
las direcciones de memoria donde el programa necesita estar para ser ejecutable.
El cargador toma el programa ejecutable del disco y los transfiere a memoria.
Finalmente la computadora, bajo el control de la UCP (Unidad Central de Procesamiento) toma cada una
de las instrucciones y las ejecuta.
Programación Procedural 43
Bibliografía
Etapa de
Modulo Fuente
R
A reprocesador
D
U
C aductor
C
Etapa de
I
O tros Módulos
N L
Modulo Objeto Objeto
I
N
K
E
D
Etapa de Linkeditor
I
C
E I
J O
E Módulo Carga Datos N
C
U
C
I
O Ejecución en
N CPU
Salida
Bibliografía
Cueva Lovelle, Juan (1998) Conceptos Básicos de Procesadores de Lenguajes. . Ed. SERVITEC.
España.
Fontela, Carlos (2003) Programación Orientada a Objetos. Técnicas Avanzadas de Programación.
Editorial Nueva Librería. Buenos Aires.
Louden, Kenneth C. (2004)Lenguajes de Programación Principios y Práctica. Editorial Thomson.
México, D.F.
Pratt Terrence y Marvin Zelkowitz (1987) Lenguajes de Programación Diseño e Implementación.
Editorial Prentice Hall. Hispano americana, S.A. 2° Edición. México.
Programación Procedural 44
Unidad 2: Objeto de Datos
Introducción
Si tenemos en cuenta que un programa se puede considerar como un conjunto de operaciones que se van
a realizar sobre ciertos datos en un orden determinado, los puntos sobresalientes a tener en cuenta en el
estudio de los lenguajes de programación son: datos, operaciones y control.
Objeto de Datos
Un objeto de datos es un recipiente que se usa para guardar y recuperar valores de datos
Programación Procedural 45
Objeto de Datos
Variables y Constantes
Variable
Una variable se puede especificar a través de sus atributos, que incluyen nombre, localización, valor y
otros atributos como tipo de datos y tamaño.
Una representación esquemática con diagramas sintácticos (estos son una alternativa gráfica para la
Forma de Backus-Naur (BNF)) es:
Ejemplo
int X=10;
10
X int
1010h
1010h
Si hacemos referencia a los atributos principales de una variable, nombre, localización y valor, podemos
representarla de manera más sencilla como sigue:
Nombre Valor
Localización
Programación Procedural 46
Objeto de Datos
Constante
Una constante es una entidad del lenguaje que tiene un valor fijo mientras existe dentro de un programa.
Una constante es similar a una variable, excepto que no tiene un atributo de localización. Esto no signi-
fica que una constante no se almacene en la memoria en algunas ocasiones.
En C las constantes se declaran con la directiva #define, esto significa que esa constante tendrá el mismo
valor a lo largo de todo el programa.
El identificador de una constante así definida será una cadena de caracteres que deberá cumplir los mis-
mos requisitos que el de una variable (sin espacios en blanco, no empezar por un dígito numérico, etc).
Ejemplo
#include <stdio.h>
#define PI 3.1415926
int main()
{
printf("Pi vale %f", PI);
return 0;
}
Lo cual mostrará por pantalla:
Pi vale 3.1415926
Hay lenguajes como C++ en los que el valor de las constantes se computa en tiempo de carga o al co-
mienzo de la ejecución, por lo tanto su valor necesita ser almacenado en la memoria.
Ejemplo de constantes
#include<stdio.h>
main()
{ const int a=5;
const int b=2 +a;
printf("%d", b);
getchar();
}
La diferencia entre el uso de const y el uso de #define está en que mediante const se declara una cons-
tante que tiene un tratamiento similar a una variable (por ejemplo, la constante es de un tipo de dato)
mientras que mediante #define se indica que escribir el nombre especificado equivale a escribir el valor,
con una correspondencia directa y sin tratamiento análogo al de una variable.
Ejemplo
const int JUGADORES = 5; … #define JUGADORES 5
En la primera declaración se indica que JUGADORES es una constante de tipo int mientras que en la
segunda se indica que donde aparezca en el código la palabra JUGADORES deberá ser reemplazada por
5 directamente. En general, usar #define supone que la compilación sea más rápida al no tener el compi-
lador que realizar el tratamiento y verificaciones propias de variables. Por ello su uso resultará recomen-
dable cuando existan ciertos valores numéricos que tengan un significado especial, valor constante y uso
frecuente dentro del código. Las constantes definidas con #define se denominan constantes simbólicas.
Programación Procedural 47
Objeto de Datos
Tiempo de Vida13
Durante la ejecución de un programa algunos objetos de datos existen desde el comienzo de la ejecu-
ción, otros pueden crearse dinámicamente durante la misma. Algunos objetos persisten durante toda la
ejecución del programa, otros se destruyen durante la ejecución del mismo.
Por lo tanto, definimos como tiempo de vida de un objeto, al tiempo durante el cual el objeto puede
usarse para guardar valores de datos.
13
Capítulo 5. Pág. 114 a 117. Kenneth Louden.
Programación Procedural 48
Tipos de datos
Cuando los enlaces de un lenguaje se realizan antes de la ejecución se dicen que son enlaces tempranos
o ligaduras estáticas14, mientras que cuando se realizan en tiempo de ejecución hablamos de enlaces
tardíos o ligaduras dinámicas. Las ventajas y desventajas de unos y otros giran en torno al conflicto
entre eficiencia y flexibilidad.
De todos los tipos de enlaces expuestos, salvo los enlaces en tiempo de ejecución, todos los otros enla-
ces son ligaduras estáticas.
En lenguajes como C o Pascal, donde la eficiencia de ejecución es primordial, es común que la mayoría
de los enlaces sean tempranos.
En lenguaje LISP, donde la flexibilidad es su objetivo, los enlaces se retardan hasta la ejecución para
que los mismos puedan hacerse dependientes de los datos.
Es importante hacer notar, que el traductor debe conservar los enlaces de tal manera que se realice la
operación apropiada durante la traducción y la ejecución. Esto es, el traductor debe conservar las ligadu-
ras de modo que se den significados apropiados a los nombres en tiempo de traducción y ejecución.
Para esto, el traductor crea una estructura de datos, llamada Tabla de Símbolos. A grandes rasgos, se
puede decir que esta estructura contiene una entrada por cada identificador diferente declarado en el
programa fuente, además para cada identificador se introduce información adicional (tipo de variable,
parámetro formal, nombre del subprograma, entorno de referencia, etc.).
Conforme se avanza en la traducción la Tabla de Símbolos va cambiando de modo que quedan siempre
reflejadas la eliminación o inserción de enlaces.
En temas siguientes, una vez desarrollado el concepto de función retomaremos la tabla de símbolos, y
mostraremos ejemplos que permitan ver el funcionamiento de la misma.
Tipos de datos
Tipo de Datos: En un programa, los objetos de datos como un flotante F, un arreglo A, una struct T,
tienen un tipo de datos que está asociado al conjunto de valores que puede tomar dicho objeto. No obs-
tante, para un lenguaje de programación podemos dar una definición más precisa:
Un tipo de datos es una clase de objetos de datos ligados a un conjunto de operaciones para cre-
arlos y manipularlos.
De ahí, podemos hacer referencia a un tipo de datos como la clase de los arreglos, la clase de los flotan-
tes, etc.
Los tipos de datos pueden ser: Primitivos: datos que están integrados al lenguaje o Definidos por el
programador: el lenguaje provee recursos para definir nuevos tipos.
14
Algunos autores hablan de enlaces, otros de ligaduras.
Programación Procedural 49
Tipos de datos elementales
Representación de almacenamiento
Ordinariamente, representación de almacenamiento hace referencia al tamaño de bloque de memoria
que se requiere, a la disposición de los valores y atributos dentro de ese bloque y no a su ubicación de-
ntro de la memoria.
El almacenamiento está influido por la computadora subyacente que va ejecutar el programa. Por ejem-
plo, la representación de almacenamiento para valores enteros o reales generalmente utiliza la represen-
tación binaria que se usa en el hardware para esos valores. Por lo tanto, las operaciones básicas sobre
datos de este tipo se pueden implementar a través de las operaciones de hardware. Caso contrario, las
operaciones deben simularse por software, de manera mucho menos eficiente.
En lenguajes como C, Pascal, Fortran los atributos no forman parte del objeto de datos en tiempo de
ejecución, es decir no se guardan en la representación de almacenamiento. Son determinados por el
compilador. Esto permite mayor eficiencia en el uso del almacenamiento y mayor velocidad de ejecu-
ción, objetivos primordiales de estos lenguajes.
En lenguajes como Lisp y Prolog, los atributos forman parte del objeto de datos en tiempo de ejecución.
Estos atributos se guardan como un descriptor.
Programación Procedural 50
Tipos de datos elementales
Ejemplos
Como operación de hardware: Por ejemplo, si los números enteros usan la representación de hard-
ware para enteros, entonces la suma puede implementarse usando la operación de suma integrada en
el hardware.
Simulada por software a través de subprogramas: Una operación de potenciación generalmente no es
suministrada directamente como una operación de hardware. En este caso se puede implementar un
subprograma que calcule esta operación.
Si los objetos de datos no se representan usando la representación de hardware, entonces las operaciones
se deben simular comúnmente, a través de subprogramas que se suministran a través de bibliotecas.
Ejemplo
#include <stdio.h>
#include <math.h>
main()
{ int base, exponente, r;
scanf("%d %d", &base, &exponente);
r=pow(base, exponente);
printf("\n Potencia: %d", r);
}
En este ejemplo en Lenguaje C, la función potencia (pow) está simulada por software y suministrada por
el lenguaje en el archivo <math.h>.
-Lenguaje C también permite que ciertas operaciones puedan simularse por macros. Esto puede ser be-
neficioso cuando el subprograma es muy corto, directamente se reemplaza por él, cada vez que este
aparece en el programa.
#include <stdio.h>
#include <conio.h>
#define maximo(A,B) A<B?B: A
main()
{
int x=20, y=30,z;
z= maximo(x,y);
printf("\n el mayor es %d",z);
getch();
}
Programación Procedural 51
Tipos de datos elementales
Declaraciones
Una declaración es un enunciado escrito por el programador que sirve para comunicar al traductor in-
formación primordial para establecer ligaduras.
En general, podemos decir que las declaraciones contribuyen a:
Ofrecer al traductor información que permite el almacenamiento para un objeto de datos, reduciendo
el tiempo de ejecución del programa que se está traduciendo.
Cuando hay operadores homónimos, las declaraciones permiten que el traductor, en tiempo de com-
pilación, determinar cuál es la operación particular a la que se hace referencia el operador homóni-
mo.
Nota: Los operadores +,*,-,/, etc. son operadores homónimos pues denotan operaciones genéricas que
tienen distintas definiciones en función de los argumentos de las mismas.
Ejemplo
void main (void)
{ int A, B, C;
float P, Q, R;
A = B + C;
P = Q + R;
………..
}
En este ejemplo, el operador + se ha utilizado para realizar una suma de enteros y una de reales, opera-
ciones que matemáticamente se realizan de manera distinta.
Las declaraciones de las variables permiten al traductor del lenguaje determinar en tiempo de compila-
ción cual es la operación particular que designa el símbolo homónimo +, esto es, o suma de enteros o
suma de flotantes.
Las declaraciones informan sobre el tiempo de vida de un objeto lo que hace más eficiente la gestión de
almacenamiento durante la ejecución de un programa.
Por ejemplo, en lenguaje C, a las variables locales a un subprograma se les asigna espacio en un bloque
de memoria en forma automática al entrar a la ejecución del subprograma. Al terminar ese bloque tam-
bién en forma automática se libera. Sin embargo, para variables dinámicas (creadas con malloc), como
no se declaran explícitamente en el subprograma, se les debe asignar almacenamiento en forma indivi-
dual en un área de almacenamiento por separado, a un costo mayor porque el pedido lo hace el progra-
mador.
double calculo (double a ) La declaración de la función calculo indica una operación que puede
{return a * 1.20;} ser usada en el programa.
La variable a de tipo double, indica que para su almacenamiento se
void main (void) usará 8 bytes. Además por ser una variable local a la función se le
{ asigna memoria automáticamente durante la ejecución del subpro-
double sueldo,s; grama y automáticamente se libera al terminar la ejecución del mis-
............ mo.
s=calculo(sueldo); Las variables sueldo y s, son locales al main y por ser del tipo double
int *p; necesitan para su almacenamiento 8 bytes.
p=malloc(30 * sizeof(int)); La variable p es un puntero a entero, por lo tanto necesita para su
……..
almacenamiento 4 bytes.
……..
Con la función malloc se habilitará el uso de un área de almacena-
free(p);
miento de 30*4 = 120 bytes.
……..
La función free liberará el espacio generado por malloc.
}
Programación Procedural 52
Tipos de datos elementales
Verificación de tipos
La verificación de tipos es el proceso que realiza el intérprete o el compilador para verificar que todas
las construcciones en un programa tengan sentido en términos de los tipos declarados.
Eejemplo
Como la verificación de tipos implica, entre otras, comprobar que cada operación que un programa eje-
cuta recibe el número de argumentos apropiados y del tipo de datos correcto, el caso de la expresión A =
B + C si C es tipo char, las variables A y B son tipo float, entonces el compilador detectará un error de
tipo de argumento en la operación.
Uno de los problemas más significativos respecto a los tipos de datos, es que no hay consenso entre los
diseñadores de lenguajes respecto de hasta qué punto la información de tipos debe explicitarse y utili-
zarse para la verificación de tipos, antes de la ejecución de un programa.
Programación Procedural 53
Tipos de datos elementales
Hay quienes ponen más énfasis, en una mayor flexibilidad en el uso de tipos, y que no procuran una
verificación de tipos estricta, y hay quienes procuran la implementación de la verificación estricta de
tipos en tiempo de traducción.
Un lenguaje fuertemente tipificado es por tanto, un lenguaje con una verificación de tipos estricta. O lo
que es lo mismo, un lenguaje de tipo fuertes detecta en forma estática, los errores de tipo de un pro-
grama.
Verificación de tipos y lenguaje C
Los compiladores de C aplican una verificación estática de tipos durante la traducción, no obstante mu-
chas inconsistencias en los tipos no causan errores de compilación, sino que son automáticamente elimi-
nados, presentando o no un mensaje de advertencia.
La mayoría de los compiladores modernos, tienen configuraciones de nivel de error que aportan un tipi-
ficado más fuerte. C++ agrega una verificación de tipos más fuerte a C, pero bajo la forma de adverten-
cias en vez de errores, con fines de compatibilidad con C. Este tipo de mensaje no impide la ejecución.
Ignorar estas advertencias puede ser un desatino peligroso (Stroustrup, 1994, Página 42).
Conversión de tipos
Cuando en la verificación de tipos hay una discordancia entre el tipo real de un argumento y el tipo es-
perado, el lenguaje puede:
1. marcar el error y ejecutar una acción de error apropiada
2. se puede realizar una conversión de tipos.
Esta conversión toma un objeto de un tipo y produce el objeto de datos correspondiente de un tipo dis-
tinto.
15
K. Louden, en el libro Lenguajes de Programación las denomina coerciones. Pág. 211
Programación Procedural 54
Tipos de datos elementales
cuado. En este caso, estamos frente a una conversión implícita o coerción. En C, las coerciones son
reglas.
Por ejemplo, en expresiones aritméticas el lenguaje C permite utilizar operandos de distintos tipos en
una expresión, teniendo en cuenta las siguientes reglas:
a- Si los dos operandos son de tipo coma flotante con distinta precisión, el operando de menor precisión
se transforma a la precisión del otro operando y el resultado se expresa en esta precisión (la más alta).
Por ejemplo entre un float y un double, el float se convierte a double y el resultado será double.
b- Si un operando es de tipo coma flotante y el otro es un char o un int, el char/int se convertirá al tipo
coma flotante y el resultado se expresará en este tipo. Por ejemplo una operación entre un int y un dou-
ble tendrá como resultado un double.
c- Si ninguno de los operandos es de tipo coma flotante, el compilador convierte los char y short int a int
antes de evaluar, salvo que en la expresión aparezca un long int. En este caso los operandos y el resulta-
do se convertirán a long int.
double
double
En todos los casos la conversión se ha realizado de tal forma que el tipo de datos final puede contener
toda la información que se convierte sin perder información. En este caso la conversión es de ensan-
chamiento o extensión.
No obstante, si la variable result fuese de tipo int, entonces habría una conversión de un tipo real a un
entero, y en tal caso puede ser que se pierda información. Mientras el valor real 15.0 es igual al entero
15, no sucede lo mismo con el real 15.20, el cual no es representable como entero. Este real será conver-
tido al entero 15, perdiendo información. Este tipo de coerción se llama de estrechamiento o restricción.
Conversión explícita o forzada: conjunto de funciones integradas que el programador puede llamar
para realizar una conversión de tipos.
En C, se puede forzar a una expresión a ser de un tipo específico, usando el operador unario cast.
Formato:(tipo) expresión donde tipo, es el tipo al que se desea forzar el resultado
Ejemplo
#include <stdio.h>
void main(void)
{int x=7;
printf("\n %f", x/2);
}
Programación Procedural 55
Tipos de datos elementales
Para este programa la salida es 3.0, sin embargo si forzamos a x a un tipo flotante, tal cual lo expresa el
siguiente programa:
void main(void)
{ int x=7;
printf("\n %f", (float) x/2);
}
La salida es 3.50.
En estos casos de conversión explícita, para la verificación estática de tipos, se inserta código adicional
en el programa compilado para invocar la operación de conversión en el punto apropiado durante la
ejecución.
El lenguaje Pascal casi no provee coerciones, salvo excepciones, todas las discordancias son tratadas
como errores.
En C, las coerciones son la regla. Cuando se encuentra una discordancia de tipo, el compilador busca
una operación de conversión apropiada para insertarla en el código compilado y de ese modo proveer el
cambio de tipo adecuado. Sólo si no se encuentra una conversión posible, la discordancia se presenta
como un error.
En otros capítulos, se presentarán nuevos ejemplos donde revisaremos los distintos tipos de coerciones.
Clasificación
Los tipos de datos elementales (también llamados primitivos) son los tipos de datos originales de un
lenguaje de programación, esto es, aquellos que proporciona el lenguaje y con los que se puede construir
estructuras de datos y tipos de datos abstractos. Estos se pueden clasificar de la siguiente manera:
Carácter
Entero
Real
Booleano o Lógico
Puntero o Apuntador
Algunos lenguajes no proporcionan los tipos de datos booleanos y puntero. A continuación se describirá
cada uno de ellos.
Programación Procedural 56
Tipos de datos elementales
Bit de
signo
Operación de asignación
Dentro de las operaciones antes destacadas, resulta importante entender la asignación. La asignación es
una operación de casi todos los tipos de datos elementales, y es la operación básica para cambiar el valor
de una variable. Ejemplos de asignación son:
X=2+8; para X entera
X=Y; para X e Y enteras.
X int 25
1010h
1010h
Por lo tanto el valor l (X) es 1010h, mientras que el valor r(X) es 25.
Luego de la operación X= 35, cambia el valor r (X) por 35.
El l representa la izquierda (left), aludiendo a que un valor l o lvalue debe estar a la izquierda de una
operación de asignación. Del mismo modo la r representa la derecha (right), aludiendo a que un valor r
o rvalue debe estar a la derecha de una operación de asignación.
Como puede inferirse una constante no tiene lvalue.
La semántica de una operación de asignación puede cambiar en distintos lenguajes. Se analiza a conti-
nuación la operación de asignación en lenguaje Pascal y en lenguaje C.
Programación Procedural 57
Tipos de datos elementales
Esquemáticamente:
En lenguaje Pascal, la sintaxis de la asignación: X:=Y
Localización
devuelve
X
Localización
Programación Procedural 58
Tipos de datos elementales
Ejemplo
int a=10, b, c;
c=b=a*2;
S Exponente Mantisa
Signo: 1 bit
0+, 1-
Conjunción
Negación
Implementación: Si bien se necesita un solo bit para su representación (0 o 1), como los bit no son in-
dividualmente direccionables, se usa un byte o una palabra que es una unidad direccionable.
Programación Procedural 59
Tipos de datos elementales
Hay dos maneras distintas de representar estos valores dentro de la unidad de almacenamiento:
1. Se usa un solo bit del byte para representar con 0 o 1 según sea verdadero o falso. Los bits restantes
se ignoran. (generalmente se usa el bit de signo)
2. Un valor 0 en toda la unidad de almacenamiento representa falso, cualquier otro valor representa
verdadero.
En lenguaje Pascal: Utiliza un byte False se representa con 0, True se representa con 1.
Lenguaje C no provee de tipos boolean pero se usan objetos del tipo de dato entero para representar
valores verdadero o falso. En general con 0 se representa un valor falso, cualquier valor distinto de 0
representa verdadero, aunque lo óptimo es usar el 1 como verdadero para no tener problemas cuando se
realizan operaciones de bits como el &.
Podría suceder por ejemplo:
Bandera=7; en binario es: 0000111
!Bandera; en binario es: 1110000 seguiría siendo verdadero.
El lenguaje C estandar no admite este tipo, pero C++ admite el tipo booleano (bool) y por lo tanto
la siguiente declaración es válida:
bool Band = false; // Band es una variable booleana, el valor verdadero se escribe true.
Una variable de tipo caracter puede tomar uno de los valores de este conjunto, el que se debe escribir
entre apóstrofes para evitar confundirlo con el nombre de una variable, operador o número.
Programación Procedural 60
Tipos de datos elementales
Ejemplo
char sexo
sexo=‟m‟
En el ejemplo, sexo es una variable de tipo caracter que se utiliza para representar el sexo de un atleta; el
carácter 'm' es un valor constante que se asigna a la variable sexo para indicar que el atleta es varón.
Implementación: Generalmente los objetos de datos de tipo carácter son manejados por el hardware y el
Sistema Operativo (S.O) subyacente, debido a su uso en la entrada y salida de información. Generalmen-
te la implementación de un lenguaje usa la misma representación de almacenamiento para caracteres que
el hardware. Si se usa una cadena distinta a la que soporta el hardware (distinta generalmente en el orden
de los caracteres) la implementación del lenguaje debe proveer las conversiones apropiadas a la repre-
sentación del nuevo conjunto o implementar las operaciones relacionales en las cual incide la secuencia
de ordenación.
1002h
Se debe distinguir entre la dirección de memoria de una variable y su contenido.
1000h
1001h
1002h 3 x
1003h
:
1005h
Dirección de Contenido Identificador
memoria
En el caso presentado, la variable x está almacenada en la dirección de memoria 1002h y su valor o con-
tenido es 3.
En la memoria de la computadora, cada dato almacenado ocupa varias celdas contiguas, dependiendo
del tipo de dato de la variable declarada. Así, un entero ocupa 2 ó 4 bytes consecutivos según la repre-
sentación de almacenamiento de entero definido por el hardware, un carácter 1 byte, etc.
Programación Procedural 61
Tipos de datos elementales
Las celdas adyacentes de memoria están numeradas en forma consecutiva desde el principio al final de
la misma. Este número, conocido como dirección de memoria se representa por lo general en sistema
hexadecimal; F96 y EC2 son ejemplos de direcciones válidas de memoria. Por razones didácticas se
simbolizará de ahora en más las direcciones con números decimales consecutivos, seguidos o no de la
letra h de hexadecimal, así 1002h ó 1002 serán direcciones válidas.
Declaración de Punteros
Si se quiere definir un puntero p que contenga la dirección de memoria de una variable entera, los
pasos a seguir son los siguientes:
Como el puntero contiene en este caso la posición de otra variable, se suele decir que la variable x
está apuntada por el puntero p.
Si la variable x tiene asignado el valor 3, lo expuesto se representa:
p x
1000h
1001h
1002h 3 x
1003h
:
1005h
:
.
1010 1002h p
Como el puntero p es también una variable, se almacena en memoria, en este caso en la posición
1010h.
Se debe tener en cuenta que, en la mayoría de los compiladores una variable puntero ocupa 4 bytes.
Formato:
En forma general, la declaración de un puntero es:
< tipo > * < nombre variable >
Programación Procedural 62
Tipos de datos elementales
Donde: nombre variable es el identificador de la variable puntero, y tipo es el tipo de dato al que apunta
el puntero.
Ejemplo
int x , *p;
float puntajes[15] , *punt ;
Operadores de punteros
Existen dos operadores que permiten trabajar con apuntadores: * y &.
Por tanto, en el ejemplo considerado, para acceder al valor de la variable x se puede utilizar directamen-
te su nombre x o la expresión *p . Esto es, x es equivalente a *p.
NOTA: El operador * es unario, por lo que el compilador puede distinguir su aplicación respecto del
operador * que representa la operación producto.
Ejemplo
El siguiente esquema de memoria permitirá visualizar los distintos pasos del algoritmo que se muestra:
#include <stdio.h>
void main (void )
{
int a,b ,*p,*q; 1
p=&a; 2
*p=8; 3
q=&b; 4
*q=23; 5
printf(“%4d %4d \n”,a,b); 6
*p=*q+2; 7
printf(“%4d %4d %4d %4d \n”,*p,*q,a,b); 8
p=q; 9
printf(“%4d %4d %4d %4d\ n”,*p,*q,a,b); 10
}
Programación Procedural 63
Tipos de datos elementales
Paso 1
Se asigna espacio
de memoria para 1013
1001 1005 1009
las variables
a b p q
a, b, p y q.
Paso 2
Se asigna al pun- 1001
tero p, la dirección
de memoria de a,
1001 1005 1009 1013
p apunta a la va-
a b p q
riable a.
Paso 3
Paso 4
Programación Procedural 64
Tipos de datos elementales
Paso 5
Se asigna el valor 23
a la variable apun-
tada por q, 8 23 1001 1005
b vale entonces 23.
1001 1005 1009 1013
a b p q
Paso 6
Muestra en panta-
lla el contenido de
las variables a y b.
8 23 1001 1005
Salida: 8 23
1001 1005 1009 1013
a b p q
Paso 7
A la variable
apuntada por p se
le asigna el resul-
tado de incremen-
tar en 2 el valor de 25 23 1001 1005
la variable a la que
apunta q.
La variable a vale 1001 1005 1009 1013
ahora 25. a b p q
Paso 8
Muestra en pantalla el
contenido de lo apun- 25 23 1001 1005
tado por p, q y el de
las variables a y b. 1001 1005 1009 1013
a b p q
Salida: 25 23 25 23
Programación Procedural 65
Tipos de datos elementales
Paso 9
Al puntero p se le
asigna el valor del
puntero q.
Ambos apuntan aho- 25 23 1005 1005
ra a la misma direc-
ción de memoria, la 1001 1003 1005 1010
variable b. a b p q
Paso 10
Se muestra por pan-
talla los contenidos
de lo apuntado por
los punteros p y q
y el contenido de las 25 23 1005 1005
variables a y b
respectivamente.
1001 1005 1009 1013
a b p q
Salida:
23 23 25 23
En este caso, al puntero q se le asigna el valor del puntero p, por lo tanto ambos punteros apuntan a la
variable x.
Programación Procedural 66
Tipos de datos elementales
x
p 10
q
1234h
Localización
q y
20
q
1238h
Localización
Como puede verse, la asignación de punteros en lenguaje C tiene una semántica distinta de la antes vis-
ta. La asignación entre punteros produce que se copien las direcciones en vez de los valores apuntados.
Este tipo de asignación se llama asignación por compartición, y como se verá más adelante, provoca
efectos colaterales no deseados en la asignación de objetos de datos estructurados del tipo struct, cuando
estos incluyen campos o miembros de tipo apuntador.
Existen otros casos en los cuales a un puntero se puede asignar otro puntero que resulta de invocar a una
función. Por ejemplo, la función malloc, devuelve como resultado un puntero a un bloque de memoria
solicitado en forma dinámica. Este resultado puede ser asignado a otro puntero.
Esto se trata con más profundidad en la unidad 5.
Programación Procedural 67
Tipos de datos estructurados
Comparación de punteros
Los punteros, al igual que las demás variables, se pueden comparar. La comparación es posible siempre
que los punteros apunten al mismo tipo de datos.
Los operadores usados en la comparación, son los operadores relacionales conocidos:
== != <> <= >=
La comparación de punteros es válida siempre y cuando respete una lógica. Existen tres formas para
comparar punteros:
- Dos punteros entre sí.
- Un puntero con la dirección de memoria de una variable, expresada por medio del operador &.
- Un puntero con una dirección de memoria dada directamente.
Ejemplo
#include <stdio.h>
void main (void )
{
int *pa, *pb , x;
:
if ( pa ==&x ) /* comparación de un puntero con una dirección de variable */
printf(“El puntero está apuntando a la variable x”);
Programación Procedural 68
Tipos de datos estructurados
Número de componentes: Las estructura pueden ser de tamaño fijo o tamaño variable. Cuando es de
tamaño fijo el número de componentes es invariable durante su tiempo de vida (arreglos y registros). Si
el número de componentes cambia durante su tiempo de vida la estructura es de tamaño variable. Estas
estructuras usan generalmente un puntero o apuntador para vincular las componentes. (Ej. Pilas, Listas,
Archivos, etc.)
Tipo de componente: Si la estructura tiene componentes del mismo tipo de datos, se dice que es
homogénea (Ejemplo Arreglos y Archivos). Caso contrario se dice que son estructuras heterogéneas
(Ejemplo Registros ).
Nombres que se debe usar para seleccionar una componente: Las estructuras de datos necesitan un
mecanismo de selección para identificar componentes individuales de la estructura. En el caso de los
arreglos, el nombre que lo identifica puede ser definido por el programador, mientras que una compo-
nente individual se identifica través de un subíndice. Para el caso de registros, generalmente el nombre
que lo identifica lo define el programador. Para archivos por ejemplo, sólo se puede acceder a un com-
ponente particular, por ejemplo el componente apuntado en un momento determinado.
Operación de Selección Directa: permite el acceso arbitrario a una componente de una estructura.
Operación de Selección Secuencial: en este caso, el acceso a una componente se realiza en un or-
den predeterminado.
y a través de la subindización y el uso del for, se puede seleccionar una serie de componentes
for(i=0; i<10, i++)
…….a[i];
Programación Procedural 69
Tipos de datos estructurados
Incluye
Descriptor (optativo) que guarda algunos o
todos los atributos de la estructura
Importante: Casi todo el hardware carece de recursos para descriptores de estructuras de datos, mani-
pulación de representaciones vinculadas, gestión de almacenamiento de estructura de datos y facilidades
para el manejo de archivos externos. Por los tanto las estructuras de datos y las operaciones que se rea-
lizan con ellas, deben simularse por software en la implementación del lenguaje de programación. Acá
se observa una gran diferencia con los tipos de datos elementales en los cuales generalmente la represen-
tación de almacenamiento y las operaciones son manejadas por hardware.
Programación Procedural 70
Tipos de datos estructurados
Ejemplo
Sea en el lenguaje C, el arreglo int A[10]; se almacena de la siguiente forma:
desplazamiento
A[1]
A[4]
A[5]
A[6]
A[7]
A[8]
A[9]
b) Representación Vinculada:A[9]En este caso la selección directa implica seguir una cadena de apuntado-
res desde el primer bloque de almacenamiento de la estructura hasta la componente deseada. Este tipo de
representación se analiza cuando se estudian listas enlazadas por punteros.
Programación Procedural 71
Clasificación de los tipos de datos estructurados
Cuando se hace la verificación de tipos la ruta a seguir para la verificación de una componente suele ser
compleja. Además, aunque la verificación de tipo sea correcta en tiempo de compilación, nada nos ase-
gura que un determinado desplazamiento sea válido en la estructura. En tiempo de ejecución, en alguna
operación puede querer accederse a una componente inexistente y si por razones de eficiencia esto no se
válida, entonces se produce un error de verificación de tipos.
Arreglos Unidimensionales
Un arreglo unidimensional (también llamados vectores) es una estructura de datos formada por un
número fijo de componentes del mismo tipo de datos, organizadas como una serie lineal simple.
Número de componentes
Atributos Tipo de dato de las componentes
Subíndice que se puede usar para seleccionar una componente.
El constructor arreglo toma el tipo base int, así como un tamaño ( rango) y construye un tipo de datos
nuevo. Esta construcción, que no tiene asociada un nombre ( tipo anónimo), puede considerarse como
una construcción implícita de un nuevo conjunto de valores, que se describe indicando la forma en que
los valores pueden ser manipulados y representados.
16
Tipo archivo se verá en la unidad 6.
17
En este curso solo se verá el tipo arreglo dinámico y lista, que se desarrollará en la unidad 5
Programación Procedural 72
Arreglos Unidimensionales
Dado que el nombre de un tipo es de gran importancia para la verificación de tipos, para darle un nom-
bre se utilizará typedef, como se verá más adelante.
Li Límite inferior
A[Li]
Representación de
almacenamiento para A[Li +1]
componentes
IMPORTANTE:
A[[Ls]
Programación Procedural 73
Arreglos Unidimensionales
Implementación
En general es sencilla, debido a la homogeneidad de las componentes y el tamaño fijo de la estructura.
La homogeneidad el tamaño fijo
El lenguaje C no necesita descriptores para los arreglos puesto que los índices siempre comienzan en
cero, además el resto de la información no se necesita en tiempo de ejecución, ya que la misma se enlaza
durante la traducción.
Pascal, permite definir arreglos donde los índices pueden tomar, por ejemplo, un subconjunto de valores
enteros. En este lenguaje, el ámbito de subíndices deberá fijarse en tiempo de compilación de modo que
puedan llevarse a cabo todos los cálculos de dirección en esta etapa, y por lo tanto no necesite descrip-
tores durante la ejecución del programa.
En lenguaje C, int a[10], declara un arreglo a de 10 componentes de tipo entero, donde los índices var-
ían de 0 a 9.
En lenguaje Pascal, a: Array [-3..6] of integer; declara un arreglo a de componentes de tipo entero,
donde los índices varían de -3 a 6.
Sea la dirección del primer componente del vector, entonces la dirección de la componente A[I] se
calcula:
Como puede inferirse, en lenguaje C, el Valor L (A[I]]) es una constante que se puede calcular en tiem-
po de traducción y esto hace el acceso a vectores mucho más rápido.
Luego, para el ejemplo en lenguaje C int a[10], si la dirección es 1000h, entonces la dirección de la
componente A[3], se puede calcular como:
Valorl(A[3]= 1000 + 3*4= 1012h
Programación Procedural 74
Arreglos Unidimensionales
int A[10][20] declara en lenguaje C, un arreglo bidimensional de 10 filas y 20 columnas. Los índices
varían de 0..9 para las filas, y de 0..19 para las columnas. A este tipo de arreglos se les llama tabla o
matriz.
Var A: array[3..12, -5..14] of integer; declara en lenguaje Pascal, un arreglo bidimensional de 10 filas y
20 columnas. Los índices varían de 3..12 para las filas, y de -5..14 para las columnas.
Implementación
En general, un arreglo de cualquier dimensión está organizado o tiene una representación en orden por
filas. Esta representación implica que el arreglo se divide en subvectores para cada elemento del interva-
lo del primer subíndice, luego cada uno de estos subvectores se divide en subvectores para cada elemen-
to del intervalo del segundo subíndice, y así sucesivamente.
Para el caso de un arreglo bidimensional int A[10][20], el arreglo A es un vector que tiene 10 subvecto-
res de 20 componentes cada uno de ellos. A es un vector de vectores.
Una representación menos usada es la de orden por columnas, en la cual la matriz es considerada una
sola fila de columnas.
El tamaño que ocupa el arreglo en memoria es el producto del número de sus elementos por el tamaño
de cada uno de ellos.
a) D
0 1 2 i .... .... 29
0 100 230 345 s 25
t
1 67 89 670 r 74
i
Partido
2
3
t
o
8 811 125
9 230 63
int Tabla[10] [30] permite declarar un arreglo bidimensional en lenguaje C, para almacenar la informa-
ción de la tabla.
Programación Procedural 75
Arreglos Unidimensionales
tabla[0][1]
tabla[0][29] tabla[9][0]
100 230 345 ... 25 67 89 670 .... 74 ....... 230 ... ..... 63
0 1 2 ……………….. 9
Teniendo en cuenta que el nombre de un arreglo es un puntero constante que contiene la dirección del
primer elemento del mismo, b almacena una constante, la dirección de b[0].
Por lo tanto la dirección del primer elemento del arreglo puede expresarse como &b[0] o simplemente
b, la dirección del segundo elemento es &b[1] ó b+1
En general la dirección del elemento ubicado en la posición i del arreglo, puede expresarse
como &b[i] ó b+i
Programación Procedural 76
Arreglos Unidimensionales
Esta estrecha relación entre indexación en un arreglo y aritmética de punteros, permite acceder a la di-
rección de un elemento de un arreglo de dos maneras diferentes:
escribiendo el elemento precedido por un &. Por ejemplo &b[i].
escribiendo el nombre del arreglo más una cantidad entera i. Por ejemplo b+i. Esta última expresión
representa la dirección del elemento que está i posiciones después del primero. El valor de i se co-
noce como desplazamiento (offset).
Si &b[i] y b+i representan la dirección del elemento del arreglo ubicado en la i-ésima posición,
entonces el acceso al contenido de esas direcciones se realiza con b[i] o *(b+i)
Por lo tanto, las siguientes expresiones son equivalentes:
Actividad 1
Si b un arreglo de 10 componentes enteras, completar con expresiones equivalentes.
int main(void)
{ int a[N],i;
carga(a, N);
lista (a, N);
for(i=0; i<N;i++)
*(a+i)=*(a+i)*3;
lista (a, N);
printf("\n Valor del puntero %x ",a );
printf("\n La direccion del primer elemento del arreglo es % x", &a[0] );
getch();
}
Programación Procedural 77
Cadenas de caracteres en C
Cadenas de caracteres en C
Una cadena de caracteres es un objeto de datos compuesto de una serie de caracteres.
Especificación y sintaxis: Existen distintos tratamientos de las cadenas, dependiendo del lenguaje18.
En el caso del lenguaje C, el uso de cadenas de caracteres es muy particular. Este lenguaje no provee un
tipo de datos cadena de caracteres. Al igual que en Pascal, utiliza arreglos de caracteres para almacenar
cadenas, estableciendo por convención que el carácter nulo “\0” sigue al último carácter de la cadena. Es
decir, cuando se guarda una cadena de caracteres en un arreglo, el traductor anexa el carácter nulo a la
cadena.
El siguiente ejemplo muestra la forma en que se almacena una cadena de caracteres en un arreglo.
void main(void)
{
char cad[13];
gets (cad); /* suponer que por teclado se ingresa la cadena LUNES 31 */
puts(cad); /* muestra LUNES 31 */
}
En memoria, el almacenamiento de la cadena es el siguiente:
0 1 2 3 4 5 6 7 8 9 11 12
L U N E S 3 1 ´\0´
Para cadenas que responden a los casos 1 y 2 citados, la asignación de almacenamiento para cada objeto
cadena de caracteres se realiza en tiempo de traducción. Para cadenas de longitud no determinada, don-
de las cadenas pueden tener cualquier longitud, y la misma puede variar durante la ejecución del pro-
grama, la asignación de almacenamiento es dinámica, por lo que debe hacerse en tiempo de ejecución.
El lenguaje C también provee la posibilidad de crear cadenas dinámicas, como se analizará más adelan-
te.
18
Pratt, Terence y Marvin Zelkowitz. Lenguajes de Programación: Diseño e implementación.
Programación Procedural 78
Cadenas de caracteres en C
La siguiente tabla muestra algunas de las funciones más usadas para manipular, comparar y buscar ele-
mentos en cadenas, con su correspondiente formato.
Comparación de Cadenas
strcmp int strcmp( const char * S1, const char * S2)
Compara alfabéticamente S1 y S2. El resultado es un número
Positivo si S1 >S2
Negativo si S1 <S2
Cero si S1 =S2
stricmp int stricmp( const char * S1, const char * S2)
Es igual a strcmp, pero sin distinguir entre letras mayúsculas y minúsculas.
strncmp int strncmp( const char * S1, const char * S2, size_t n)
Compara S1 con la subcadena formada por los n primeros caracteres de S2. Al igual
que strcmp devuelve un valor que puede ser positivo, negativo o cero.
Programación Procedural 79
/Registros
NOTA
Las funciones que permiten convertir cadenas en números se encuentran en la biblioteca stdlib.h
El tipo size_t es equivalente, dependiendo del sistema, al tipo unsigned long o unsigned int.
Como se observa en la tabla, algunos argumentos de las funciones aparecen precedidos de la palabra
const, esto permite identificar rápidamente cuales parámetros son de entrada y cuáles de salida.
La cadena origen sólo se utiliza para obtener los caracteres a copiar, ella no cambia, es la cadena desti-
no la que se modifica. Aquellos datos que se utilizan como parámetros de entrada llevan la palabra
const, no así los de salida.
En general, las funciones manipulan los primeros caracteres de una cadena. Sin embargo, a veces es
necesario trabajar con los últimos caracteres, en estos casos se recurre al uso de punteros, como lo
muestra el ejemplo siguiente:
El siguiente algoritmo muestra como a partir de una cadena que contiene el nombre y apellido de una
persona, se extrae la cadena que contiene sólo su apellido.
char c1 [30]=”Darío Pérez” ;
char c2[30];
char *p= c1;
p+= 6; /* apunta a Pérez*/
strcpy(c2,p);
puts(c2);
Registros
Especificación: Un registro es una estructura de datos compuesta por un número fijo de componentes de
distinto o igual tipo. Por lo tanto, es una estructura de datos de longitud fija.
Ejemplo en lenguaje C:
Declaración de tipo
struct empleados
{
char nom[10];
int edad;
}
Programación Procedural 80
/Registros
Declaración de variable
empleados e;
En este caso se ha declarado o definido el tipo de datos empleados y es posible definir variables de
ese tipo, en este caso e
Atributos de un registro
Número de componentes (en el ejemplo anterior, la struct empleados tiene 2 componentes)
Tipo de datos de cada componente (arreglo de caracteres y entero)
Selector que se usa para nombrar cada componente (en el ejemplo anterior nom y edad)
nom
edad
Programación Procedural 81
/Registros
al
struct alumno
{
int dni;
char nom[20];
int legajo;
char carrera;
} al;
Ejemplo
La siguiente estructura permite almacenar información de un determinado partido político:
stuct partido_politico
{
int numero;
int votos [19];
};
Como se puede observar los miembros de una estructura pueden ser variables simples o estructuradas.
Para la estructura:
struct alumno
{
int dni;
char nom[20];
int legajo;
char carrera;
};
Programación Procedural 82
/Registros
Para la estructura:
stuct partido_politico
{
int numero;
int votos [19];
};
struct partido_politico m;
m. numero refiere al número del partido político y m.votos[5] refiere a los votos obtenidos en el sexto
distrito.
Anidamiento de struct
Si un miembro de una struct es otra struct, se está haciendo referencia a un anidamiento de structs.
Ejemplo
La siguiente estructura permite almacenar los datos de la cuenta corriente de un cliente de una determi-
nada empresa.
struct fecha
{
int dia;
int mes;
int anio;
};
struct cliente
{
int num;
char nombre[20];
char domicilio[30];
float saldo;
struct fecha ult_mov;
};
Gráficamente
cli
Programación Procedural 83
/Registros
En este ejemplo, una estructura ha sido definida como miembro de otra estructura. En estas situaciones,
la definición de la estructura interna debe preceder a la definición de la estructura externa que la contie-
ne.
Actividad 2
Considerando la estructura del ejemplo, indicar como se accede al día, mes y año del último movimiento
de un cliente.
struct fecha
{
int dia;
int mes;
int anio; }
struct cliente
{
int num;
char nombre[20];
char domicilio[30];
float saldo;
struct fecha ult_mov;
}
Sea cl una variable de tipo struct cliente, son correctas las siguientes expresiones:
scanf("%f",&cl. saldo);
scanf( "%d",& cl.ult_mov.dia);
gets(cl.nombre);
gets(cl.domicilio);
para ingresar respectivamente al saldo, día del último movimiento, nombre y domicilio de un cliente.
Sea p una variable de tipo struct partido_politico, son correctas las siguientes expresiones:
printf(“%d”, p. numero);
for( i=0;i<19;i++)
printf("%d", p.votos[i]);
para mostrar el número de un partido político y el total de votos obtenidos en cada uno de los 19 distri-
tos.
Programación Procedural 84
/Registros
Asignación de estructuras
La asignación entre estructuras está permitida si ambas son del mismo tipo y no contienen ningún
miembro del tipo puntero. Entonces, dada la declaración:
struct alumno
{
int dni;
char nom[20];
int legajo;
char carrera;
};
struct alumno a, b;
la asignación a =b; equivale a asignar el contenido de cada miembro de la variable b, a los miembros de
la variable a.
Para la declaración
struct datos
{
int e ;
float *p ;
};
struct datos a, b;
Supongamos en que en memoria se almacenan los siguientes valores para cada uno de los componentes
de a y b.
a b
23 45
p 7.5 8.3 p
p 7.5 8.3 p
Esto provoca un efecto colateral no deseado, ya que en el caso que se modifique el valor apuntado por p
desde el registro a, esa modificación afectaría al valor original apuntado por p desde el registro b.
De ahí que, cuando un tipo struct define una estructura donde uno de sus componentes es un puntero, la
asignación tal cual se la interpreta en el capítulo 2, no es una operación válida.
Programación Procedural 85
/Registros
main()
{
empleados a,b;
strcpy(a.nom, "carlos");
a.edad=27;
b=a;
a.edad=30;
printf("Registro a nombre %s edad %d", a.nom, a.edad);
printf("\n Registro b nombre %s edad %d", b.nom, b.edad);
getch();
}
b)
typedef struct
{
char nom[10];
int* edad;
} empleados;
main()
{
empleados a,b;
int e=27;
strcpy(a.nom, "carlos");
a.edad=&e;
b=a;
*(a.edad)= 30
printf("\n Registro b nombre %s edad %d", b.nom, *(b.edad));
getch();
}
Observaciones:
El operador punto (.), pertenece al grupo de mayor precedencia, por lo que tiene prioridad sobre los ope-
radores monarios (++,--,etc).
Programación Procedural 86
/Registros
Punteros a estructuras
Así como un puntero puede apuntar a un entero, a un char, también puede hacerlo a variables estructu-
radas, en particular a una variable struct.
Un puntero a una variable struct contiene la dirección de comienzo de la estructura o valor L de la es-
tructura.
donde a es una estructura del tipo alumno y pstrc es un puntero a una variable del tipo alumno.
Las sentencias,
strcpy(a.nombre, “Ana Gomez”);
a.nota=10;
pstrc
1010h
Programación Procedural 87
/Registros
Ejemplo
El siguiente algoritmo muestra la forma de cargar y mostrar una estructura a través del uso de punteros.
# include <stdio.h>
struct alumno
{ char nombre[20];
int nota;
};
void main(void)
{struct alumno pers,*pstrc ;
pstrc=&pers;
printf("\n ingrese nombre ");
gets( pstrc->nombre); // equivale a gets((*pstrc).nombre)
printf("\n Ingrese nota");
scanf("%d",&(pstrc->nota)); // equivale a scanf("%d", & (*pstrc).nota)
puts( pstrc->nombre);
printf(" obtuvo %d puntos", pstrc->nota);
getchar();}
Para acceder a submiembros de una estructura, se deben usar los operadores -> y punto.
struct egresado
{char nombre[20];
float promedio;
struct fecha egreso; } ;
Actividad 4
Dada la siguiente estructura, que contiene los datos correspondientes a un cliente de una entidad banca-
ria:
struct domicilio;
{ char calle[30];
int nro;
cadena localidad;
};
Programación Procedural 88
Registros variantes
struct cuenta
{ int nrocli;
char tipo;
char nombre[30];
float saldo;
struct domicilio domi;
float movi[10];
};
typedef union
{ asalariados asalariado;
porHoras porHora;
} sueldos;
Selección de una componente: Se realiza de la misma forma que en un registro ordinario. Esto es,
durante la traducción se calcula el desplazamiento del componente dentro del bloque de almacenamiento
y durante la ejecución se suma a ese desplazamiento la dirección base.
Problemas de selección de componentes: Este problema ocurre cuando se desea seleccionar una com-
ponente inexistente, y su tratamiento es similar al error que se produce cuando al trabajar con arreglo
hacemos referencia a un índice cuyo valor no está en el intervalo permitido.
19
typedef permite definir la estructura una única vez y declarar varias variables con el mismo formato.
Programación Procedural 89
Registros variantes
El lenguaje Pascal admite registros variantes con marca, el lenguaje C admite registros variantes sin
marcas (union libre).
union U struct T
{ {
int A; int A;
double B; double B;
char C; char C;
} }
Para su almacenamiento la union U necesita sólo 8 bytes, mientras que la struct T necesita de 13 bytes.
U T
1000h A/B/ C 1000h A
1001h 1001h
1002h :
1003h 1003h
1004h 1004h B
1005 :
:
1007
1011
1012 C
Programación Procedural 90
Bibliografía
La union U tiene tres componentes, A, B y C, pero las tres ocupan la misma localidad de almacenamien-
to en memoria. T tiene también tres componentes, pero cada una de ellas tiene asignado un espacio de
almacenamiento distinto. Por lo tanto, las tres componentes de U tiene el mismo valor L, el cual coinci-
de con el valor L de U.
valorL[A]= valorL[B]= valorL[C]=valorL[U]=1000h
En la estructura, cada componente tiene un valor L distinto, y el valor L de T coincide con el valor L de
A.
Como puede verse en este ejemplo, una componente de una union guarda el último valor asignado a
cualquiera de sus componentes.
Actividad 5: Agregue al código anterior las sentencias que le permitan mostrar la dirección de los cam-
pos de la union.
Bibliografía
Lenguajes de Programación Diseño e Implementación. Pratt Terrence y Marvin Zelkowitz. Editori-
al Prentice Hall. Hispano americana, S.A. Segunda Edición. 1987.
Programación Orientada a Objetos. Técnicas Avanzadas de Programación. Fontela, Carlos. Buenos
Aires. Editorial Nueva Librería. 2003..
Lenguajes de Programación Principios y Práctica. Louden, Kenneth C. Mexico, D.F. Editorial
Thomson. 2004.
Programación Procedural 91
Práctica Unidad 2
Práctica Unidad 2
Lenguaje C –Estructuras de Datos
NOTA: Para resolver los ítems de cada ejercicio, puede hacer uso de subprogramas y, si es necesario,
devolver un resultado al programa principal a través de return.
Ejercicio 1
Generar un arreglo con 20 números enteros y codificar un programa en C que permita:
a) Indicar si alguno de los números generados es un cero.
b) Escribir el contenido de las componentes que se encuentren en las posiciones pares.
c) Indicar cantidad de números pares que contiene.
Ejercicio 2
Dada la frase “Programación Procedural 2022”, leerla desde teclado en una cadena de caracteres y:
a) Reemplazar el 2 por un 0 (Solo cambiar ese carácter)
b) Copiar la palabra “Programación” a una nueva cadena de caracteres.
c) Contar la cantidad de vocales de la frase.
Ejercicio 3
Generar un arreglo de registros que posea la siguiente información de 10 alumnos de procedural: Nom-
bre, Apellido y DNI.
a) Cargar los datos de los alumnos.
b) Listar los alumnos cargados.
c) Indicar cuántos alumnos tiene DNI mayor a 40 millones.
Ejercicio 4
Una tienda de ropa comercializa 50 productos diferentes. Por cada producto se conoce: código (número
entero que varía entre 1 y 50), precio de costo y stock.
La tienda hace compras a 22 proveedores, de los cuales se conoce: Nombre y Número de Proveedor (es
un numero entre 1000 y 1022).
Se pide redactar un algoritmo en C que, usando estructuras de datos óptimas y subprogramas eficientes,
permita:
a) Almacenar en estructuras de datos adecuadas la información de los Producto y de los Proveedores.
b) Procesar las compras realizadas a los Proveedores, sabiendo que por cada compra se conoce el
Número de Proveedor, Código de Producto y Cantidad de unidades compradas. Con la información
de cada compra se debe actualizar el stock del producto y contar para cada proveedor la compra rea-
lizada.
c) Informar cuánto dinero hay invertido en cada producto.
d) Generar una nueva estructura de datos que contenga todos los datos de aquellos Proveedores a quie-
nes se les haya realizado más de 10 compras.
e) Mostrar la estructura de datos generada en el inciso d) ordenada alfabéticamente por Nombre de
proveedor.
f) Ingresar por teclado un Nombre de proveedor e informar su Número y cantidad de compras realiza-
das. Nota: Utilizar la estructura de datos generada en el inciso d).
Ejercicio 5
Cargar aleatoriamente una tabla de 5x4 con números enteros y:
a) Mostrar la suma de cada una de las filas.
b) Calcular el promedio de la tercera columna.
c) Decir cuántos números mayores a 100 se ingresaron.
Programación Procedural 92
Práctica Unidad 2
Ejercicio 6
En la Facultad se realiza un congreso para el cual se destinan 6 salas de conferencias y cada una repre-
senta un área temática. En cada sala se dictan 4 conferencias en distintos turnos. Por cada interesado se
ingresa número del área temática (1-6), y turno al que quiere asistir (1-4). La Facultad desea llevar un
registro de la cantidad de alumnos inscriptos en cada área y en cada turno, para ello realizar los siguien-
tes items:
a) Carga de los datos. La carga es desordenada, cada alumno indica área y turno. No se sabe la canti-
dad de alumnos.
b) Indicar la cantidad de inscriptos en cada turno de cada área.
c) Dada un área temática, indicar el promedio de inscriptos.
Ejercicio 7
Un laboratorio abastece a 30 farmacias de la provincia (las farmacias están codificadas con número entre
1 y 30). Dicho laboratorio comercializa 80 medicamentos (con código desde 100 hasta 179).
En forma ordenada por las farmacias se ingresan las ventas realizadas. Por cada venta se ingresa: código
de medicamento y cantidad de unidades, finalizando con código de medicamento igual a 0 (cero), como
lo muestra el siguiente ejemplo:
Código Medicamento Cantidad Unidades
Farmacia 1 23 12
32 20
41 6
0
Farmacia 2 43 10
25 24
0
Ejercicio 8
Un supermercado ingresa las ventas de los últimos 6 meses, realizadas en los 8 departamentos de venta
que posee. Por cada venta se ingresa mes (1..12), número de departamento (1..8) e importe. Las ventas
no traen ningún orden particular. Realizar un programa en C, que a través de funciones permita:
a) Almacenar la información en una tabla que posea por cada mes, el importe total de ventas de cada
departamento. La carga finaliza con mes igual a cero.
b) Dado un mes, mostrar en el programa principal el departamento que menos vendió (suponer único).
c) Mostrar importe promedio de venta del supermercado.
d) Dado un mes y un departamento, indicar si supera el importe promedio del ítem anterior.
Programación Procedural 93
Unidad 3: Funciones
Unidad 3: Funciones
Introducción
Así como un lenguaje base suministra un tipo de dato primitivo entero y operaciones sobre enteros que
hacen innecesario que el programador se preocupe de los detalles de la representación subyacente de
enteros como serie de bits, del mismo modo se puede definir un tipo nuevo de datos de más alto nivel y
un conjunto de operaciones sobre dicho tipo de dato, sin que el programador necesite ocuparse de los
detalles de la implementación del mismo.
El objetivo actual en los diseños de lenguajes de programación es que los programadores puedan crear
tipos nuevos de datos y operaciones sobre ese tipo, de modo que pueda ser usado como un tipo de dato
provisto por el lenguaje.
Existen distintos mecanismos básicos para crear tipos nuevos de datos y operaciones sobre esos tipos. Es
propósito de este apartado, entender que construcciones proveen los lenguajes procedurales, en particu-
lar C, para definir nuevos tipos de datos y describir la funcionalidad de los mismos.
Definición de tipos
Una definición de tipos proporciona un nombre de tipo junto con una declaración que describe la estruc-
tura de una clase de objetos de datos. Así, el nombre del tipo se convierte en el nombre de esa clase de
objetos de datos, y cuando se necesita trabajar con un objeto de datos particular basta con proporcionar
el nombre del tipo en lugar de repetir la descripción completa de la estructura de datos20.
Dado un grupo de tipos básicos como int, char y float, todo lenguaje ofrece una variedad de formas para
construir tipos más complejos, basándose en los tipos básicos. Estos mecanismos se conocen como cons-
tructores de tipos. Las struct, union y los arreglos son ejemplos de constructores de tipo en lenguaje C.
Los tipos creados por los constructores de tipos se llaman tipos de datos definidos por el usuario.
Ejemplo:
struct alumno
{ char nombre[20];
int nota;
};
struct alumno arre[20];
int Tabla[10][30];
Los nuevos tipos creados con constructores no tienen automáticamente un nombre. Los nombres son
importantes no sólo para documentar el uso de tipos nuevos sino para la verificación de tipos, de la cual
ya se ha hablado antes.
Para asignar nombres a los nuevos tipos de datos se utiliza una declaración de tipos, en algunos lengua-
jes se denomina definición de tipos.
En lenguaje Pascal por ejemplo, si se necesita trabajar con varios registros que tienen una misma estruc-
tura, entonces el programador puede dar la definición de un nuevo tipo de datos a través de la palabra
clave Type:
20
El concepto de definición de tipos fue evolucionando y las primeras definiciones de tipos surgen con Pascal en
1970.
Programación Procedural 94
Definición de tipos
De este modo la definición de la estructura de un objeto de datos del tipo partido_politico se da una sola
vez, en lugar de repetirla tres veces para P1, P2 y P3.
En lenguaje C, la situación es algo distinta, los nuevos tipos de datos sólo se proveen a través de la cons-
trucción struct, luego:
struct partido_politico {
int Numero;
int Votos[19];
};
también con:
typedef struct {
int Numero;
int Votos[19];
} partido_politico;
Una definición de tipo permite separar la definición de la estructura de un objeto de datos, de los puntos
en los cuales se va a declarar el objeto.
Programación Procedural 95
Definición de tipos
Lenguaje C y typedef
En varias ocasiones puede ser útil definir nuevos nombres para tipos de datos, estos nombres o 'alias' nos
hace más fácil la declaración de variables y parámetros. Para esto C dispone de la palabra clave typedef,
cuyo formato es:
typedef <tipo> < identificador>;
Por ejemplo:
typedef int entero,
significa que se ha dado un nuevo nombre al tipo de datos int, lo cual permite declarar variables ente-
ro a, b como si entero fuera un tipo de dato.
En Lenguaje C:
typedef struct
{
float real;
float imaginario;
}complejo;
En Pascal complejo define un nuevo tipo de datos, no obstante, existen diferencias entre el type de
Pascal y el typedef de C.
En lenguaje C la construcción typedef no crea un nuevo tipo de datos. El efecto producido por type-
def es el de una sustitución por macro.
En nuestro ejemplo, cuando en el compilador encuentra la declaración de variables complejo c1,c2; se
sustituye complejo por struct complejo.
Cuando se necesite trabajar con un objeto de datos con esa estructura, basta con proporcionar el nombre
definido "complejo", en lugar de repetir su declaración.
Par el caso de arreglos, typedef permite que la declaración de una variable arre como un arreglo de
20 enteros, pueda ser usada como si fuera un tipo de dato. Lenguaje C utiliza el typedef de la siguiente
forma.
typdedef int arre[20];
Luego la declaracion de variables es:
arre a,b ; //esta es la declaración de a y b como arreglos de 20 componentes enteras.
arre es usado como si fuera un tipo de datos.
main()
{
cadena nombre=”Blanca Flores”;
:
}
Programación Procedural 96
Subprogramas
Sistema de tipos21
Los métodos utilizados para la construcción de tipos, el algoritmo de equivalencia de tipos, y las reglas
de inferencia y corrección de tipos, se conocen de manera colectiva como sistema de tipos.
Si la definición de un lenguaje de programación específica un sistema de tipos completo que pueda apli-
carse estáticamente y que garantiza que todos los errores de corrupción se detectarán lo antes posible,
entonces se dice que el lenguaje es fuertemente tipificado.
Pascal es un lenguaje fuertemente tipificado pese a que existen algunas fallas. No obstante, lenguaje C
es un lenguaje que incluso tiene más fallas por eso a veces se lo conoce como un lenguaje débilmente
tipificado. El lenguaje C++ ha eliminado muchas de la fallas de C, pero por razones de compatibilidad
sigue siendo un lenguaje que no cumple con una tipificación fuerte.
Por ejemplo:
#include <stdio.h>
int factorial (int a)
{
int i, f=1;
for(i=1;i <=a; i++)
f*=i;
return f;
}
21
Para entender mejor este concepto se sugiere leer Pág. 179 - Lenguajes de Programación. Louden. K.
Programación Procedural 97
Subprogramas
void main(void)
{
int c;
int m,n;
printf(" ingrese m y n \n");
scanf("%d",&m);
scanf("%d", &n);
c = factorial(m) / (factorial(n) * factorial(m-n)) ; // uso de la función factorial (invocación)
printf("Combinaciones %d", c) ;
getchar();
}
Especificación de un subprograma
Nombre
Prototipo o signatura: número de argumentos, su orden, y
Especificación tipo de cada uno.
Cuerpo del subprograma: descripción de la tarea que reali-
za o función que calcula
Ejemplo en lenguaje C:
#include <stdio.h>
typedef int vector[10]; permite el uso del identificador vector como si fuera un tipo
de datos
int escalar (vector a, vector b) Operación producto escalar de vectores
{
int e=0,i;
for(i=0; i < 10; i++)
e+=a[i]*b[i];
return e ;
}
void main(void)
{
vector v1, v2;
carga(v1);
carga(v2);
printf(" el producto escalar es %d ", escalar(v1,v2));
getchar();
}
Programación Procedural 98
Subprogramas
En general, en los lenguajes procedurales se utilizan las siguientes denominaciones para los subprogra-
mas:
Si devuelve un resultado función o subprograma
Implementación de un subprograma
Un subprograma es un conjunto de sentencias que realiza una tarea específica. Para su implementación
se utilizan estructuras de datos y operaciones provistas por el lenguaje.
Un subprograma es una operación de la capa de una computadora virtual, construida por el programa-
dor.
En algunos lenguajes procedurales el cuerpo puede incluir otras definiciones de subprogramas también
encapsuladas, en lenguaje C no es posible.
En ambos casos, la verificación de tipos es estática, pues se provee el tipo de datos de los argumentos
y del resultado.
En lenguaje C los subprogramas son funciones.
Es importante notar, que algunos lenguajes también proveen coerción de argumentos para transformar-
los de manera automática al tipo de datos correspondiente.
Caso 1. Transforma un argumento int a float
Programación Procedural 99
Subprogramas
Cuerpo del subprograma printf("Valor del argumento %d", a); /* Enunciados (acciones que se
ejecutan) */
return (); /*no hay resultado, es opcional colocar la sentencia
return*/
}
main()
{
float n=23.40;
calculo (n); /* Invocación*/
getch ();
}
Salida: Valor del argumento 23
float g;
int A[10];
........
.........
}
La traducción de la definición da como resultado una plantilla, a partir de la cual se puede construir una
activación particular de la función cada vez que se ejecuta el subprograma.
Por cada activación esta plantilla completa se podría crear en una nueva área de memoria, sin embargo
la plantilla se divide dos partes con el fin de ahorrar memoria.
El segmento de código es la parte estática compuesta por las constantes y el código ejecutable generado
a partir de los enunciados del cuerpo de la función.
El registro de activación es la parte dinámica, compuesta por:
Parámetros
Resultados de la función
Datos locales
Punto de retorno
Áreas temporales de almacenamiento necesarias para mantenimiento
Vinculaciones para referencias de variables no locales
El registro de activación siempre tienen la misma estructura por cada activación, lo que cambia son los
valores de los datos almacenados.
Los registros de activación se crean cada vez que se invoca un subprograma y destruyen cada vez que el
mismo concluye con un retorno.
El tamaño y estructura del registro de activación se determina en tiempo de traducción, es decir, el
compilador determina cuantos componentes tendrá el registro de activación y la posición de cada uno de
ellos en la estructura.
Por lo tanto, para crear un nuevo registro de activación, solo hace falta conocer el tamaño del bloque, no
la estructura interna en detalle, pues los desplazamientos se computan durante la traducción y en tiempo
de ejecución solo se requiere la dirección base para acceder a un componente del registro.
Prólogo – Epílogo
El prólogo es un bloque de código que el traductor introduce al comienzo del segmento de código, para
permitir tareas de creación del registro de activación, transmisión de parámetros, creación de vínculos y
actividades similares de mantenimiento.
El epílogo, al igual que el prólogo, es un conjunto de instrucciones que el traductor inserta al final del
bloque de código ejecutable, para llevar a cabo acciones que permitan devolver resultados y liberar el
almacenamiento destinado al registro de activación.
Por lo tanto, la estructura de una activación de la función calculo, antes definida es:
float calculo (float f, int i)
{
const float por=10.7
float g;
int A[10];
........
.........
}
Prólogo
Epílogo
Código ejecutable Segmento de código
10.7
Constantes
10
f: parámetro
i: parámetro
g: objeto local
----------------------------------------
---------------------------------- Registro de activación
A: objeto local
Datos de resultado de calculo
Punto de retorno de calculo
Funciones en lenguaje C
Así como el lenguaje C proporciona funciones de biblioteca para realizar distintas operaciones o
cálculos frecuentes, también permite al programador definir y construir funciones que realicen
tareas específicas.
El uso de funciones definidas por el programador permite dividir un programa en un cierto
número de componentes más pequeñas, cada una de éstas con un propósito específico y determi-
nado. Es por ello que se dice que un programa en C se puede modularizar a través del uso correc-
to de funciones. Al trabajar modularmente, descomponiendo un programa en varias funciones, se
logra programas más fáciles de codificar y depurar.
Definición
Una función es un conjunto de sentencias que realiza una determinada tarea, que retorna como resultado
cero o un valor.
Por ejemplo la función getch(), es una función predefinida que devuelve un único valor, un carácter.
char car;
car=getch();
Al invocar una función desde algún punto del programa, se ejecutan las sentencias que forman parte de
ella. Una vez que finaliza la ejecución de esa función, el control se devuelve al punto desde donde fue
invocada, es decir, el control vuelve al main (programa principal) o a la función que la llamó.
Esquemáticamente:
leer();
calcular();
{
p=calcular(); };
Si un programa contiene varias funciones, sus definiciones pueden aparecer en cualquier orden.
Generalmente, una función usará o procesará la información que le es transferida desde el punto del
programa donde es invocada o llamada. Esa información es comunicada a la función a través de identifi-
cadores llamados argumentos o parámetros. El resultado de la función se devuelve a través de la
sentencia return.
La primera línea de la definición de una función contiene la especificación del tipo del valor devuelto,
seguido del nombre o identificador de la función, y opcionalmente un conjunto de argumentos o paráme-
tros, separados por comas y encerrados entre paréntesis. Cada argumento debe ir precedido por su decla-
ración de tipo.
Si la función no incluye parámetros, el identificador de la función puede ir seguido de un par de parénte-
sis vacíos o con la palabra reservada void.
La primera línea de la definición o cabecera de una función, puede indicarse con el siguiente formato:
Formato:
<tipo de dato> <identificador>(< tipo1 arg1, tipo2 arg2, . . ., tipon argn>)
arg1, arg2, …, argn, se denominan argumentos formales o parámetros formales e identifican a los
datos que recibe la función en el momento de su invocación o llamada.
tipo1, tipo2, …., tipon, representan los tipos de datos asociados a cada parámetro formal.
Al momento de invocar la función, los datos que se comunican a la función reciben el nombre de argu-
mentos reales, parámetros actuales o parámetros reales, ya que se refieren a la información que re-
almente se transfiere. Cabe aclarar que los nombres de los parámetros formales y los nombres de los
parámetros reales no necesariamente deben coincidir.
El cuerpo de la función se encierra entre llaves { } y contiene el conjunto de sentencias que se deben
ejecutar cada vez que se la invoca.
Los parámetros formales y las variables declaradas dentro de una función, son locales a la función, pues
no son reconocidos fuera de ella.
Ejemplo :
El siguiente código corresponde a la definición de la función calcula, que devuelve el perímetro de un
terreno, cuyas dimensiones son recibidas en los parámetros formales.
float perimetro( float largo, float ancho) /*largo y ancho son parámetros formales*/
{
float perim; /* perim es una variable local a la función perimetro */
perim = 2 * ( largo + ancho) ;
return perim ;
}
Ejemplo
El siguiente código corresponde a la definición de la función sumatoria, que realiza el cálculo de:
N=b
2N + 1
N=a
para valores de a y b recibidos como parámetros formales. En este caso, la función no devuelve un resul-
tado.
#include <stdio.h>
void sumatoria ( int a, int b )
{
int i, s=0;
for (i=a; i<=b; i++)
s+=2 * i + 1;
printf(" la suma es %d ", s);
}
Ejemplo
La siguiente definición corresponde a la función cabecera, que imprime el membrete de una factura. En
este caso, no recibe información y no devuelve un resultado.
#include <stdio.h>
void cabecera(void)
{
printf(“ EMPRESA UNION S.A \n”);
printf(“ Perú 123 Sur- Te: 02644378990 \n”);
printf(“ San Juan \n”);
return;
}
En casos como los dos últimos ejemplos, puede omitirse la sentencia return en la definición de una fun-
ción, aunque esto no es una manera aconsejable de programar. En estos ejemplos, al llegar al final del
cuerpo de la función, se devuelve el control al punto de llamada de la función sin retornar resultado al-
guno.
Como puede observarse, se coloca la palabra reservada void como especificador de tipo del resultado de
la función y la sentencia return vacía.
También pueden definirse funciones que incluyan varias sentencias return, conteniendo cada una de
ellas, una expresión distinta.
Ejemplo
La siguiente es la definición de una función que devuelve como resultado „p‟,‟n‟ o „c‟, según que el
argumento recibido sea un número positivo, negativo o nulo.
char signo ( int num)
{
if ( num > 0)
return ' p ';
else
if ( num < 0 )
return ' n ' ;
else
return ' c ' ;
}
Invocación o llamada a una función
La llamada o invocación a una función produce la ejecución de las sentencias del cuerpo de la misma,
hasta que encuentra la sentencia return o la llave de final de cuerpo.
Se puede llamar o invocar a una función indicando su nombre seguido de la lista de argumentos ence-
rrados entre paréntesis y separados por coma.
Formato:
<identificador de la función> (<arg1,arg2, . . . ,argn>)
Estos argumentos deben corresponderse con los argumentos o parámetros formales que aparecen en la
primera línea de la definición de la función. Si la llamada de la función no necesita argumentos, a conti-
nuación del identificador de la función se coloca un par de paréntesis vacíos.
La invocación a una función puede formar parte de una expresión cuando esta retorna un valor.
Ejemplo
Construir un programa en C que utilice una función maximo para determinar el mayor valor entre tres
números enteros.
# include < stdio.h >
# include < conio.h >
int maximo ( int x, int y ) /* definición de la función maximo */
{
int z;
z = ( x >= y )? x:y ;
return z;
}
En este ejemplo, se invoca a la función maximo desde dos lugares diferentes en el main:
la primera invocación se efectúa en una asignación y los parámetros reales son las variables a y b.
la segunda invocación se realiza en una escritura y los parámetros reales son las variables c y d.
printf (" \n el máximo de los tres números es:% d", maximo ( c, maximo( a , b ) ) );
En este caso, una de las invocaciones de la función maximo es un argumento para la otra llamada, evi-
tando así el uso de la variable d.
Ejemplo
Para dos números naturales distintos, el siguiente programa escribe un mensaje diciendo si uno de ellos
es divisor del otro.
#include <stdio.h>
#include <conio.h>
int divisor (int x, int y)
{
int res;
((x<y)&& ((y%x)==0))? res=-1:((x>y)&& ((x%y)==0))? res=1: res= 0;
return res;
}
void main(void)
{
int a,b,r;
printf(" ingrese dos números naturales distintos\n");
scanf("%d", &a);
scanf("%d", &b);
r=divisor(a,b);
if (r== -1)
printf("%d es divisor de %d", a,b);
else
if (r== 1)
printf("%d es divisor de %d", b,a);
else
printf("no hay divisores");
getch();
}
ACTIVIDAD 1
Construir una función que permita calcular la suma de todos los números menores o iguales a un número
entero determinado. Codificar el programa principal con la invocación a la función.
Puede observarse, que el prototipo de una función se corresponde con la primera línea de la definición
de la misma, pero finaliza con punto y coma.
No es necesario declarar en ningún lugar del programa los parámetros o argumentos formales del proto-
tipo de una función, pues éstos son argumentos ficticios que sólo se reconocen dentro del prototipo.
Incluso, pueden omitirse los nombres de tales parámetros, ya que lo obligatorio es indicar el tipo de
datos de cada parámetro formal.
Una función puede definirse en cualquier parte del programa, pero antes de su invocación debe estar
presente al menos su declaración o prototipo, a veces llamada declaración por adelantado o declaración
forward. Esta declaración informa al compilador que será accedida antes de que sea definida.
Ejemplo
En este ejemplo, la función divisor se define después de su invocación, por lo tanto su prototipo se in-
cluye en la función main.
#include <stdio.h>
#include <conio.h>
void main(void)
{
int divisor (int x, int y); /* prototipo de la función divisor */
int a,b,r;
printf(" ingrese dos números naturales distintos\n");
scanf("%d", &a);
scanf("%d", &b);
r=divisor(a,b);
if (r== -1)
printf("%d es divisor de %d", a,b);
else
if (r== 1)
printf("%d es divisor de %d", b,a);
else
printf("no son divisbles" );
getch();
}
Organización de la memoria en C
Para la ejecución de un programa, el lenguaje C, al igual que otros lenguajes imperativos, usa una divi-
sión tripartita de la memoria: estas tres áreas corresponden a:
Un área de almacenamiento estático
Un área asignada como pila (stack)
Un área asignada como montículo (heap)
Pila (stack): La pila es un bloque secuencial de memoria que se asigna en tiempo de ejecución. El len-
guaje C utiliza la pila para gestionar las llamadas a las funciones. En este área se almacenan los regis-
tros de activación de las funciones,
La asignación de memoria en la pila se realiza secuencialmente a partir de un extremo, mientras que la
liberación de la misma se realiza en orden inverso.
Generalmente, la pila crece hacia abajo, ocupando así, direcciones altas de memoria.
Programación Procedural 108
/Funciones en lenguaje C
Cuando se invoca una función, el registro de activación correspondiente se almacena en la pila (se api-
la). Una vez que finaliza la ejecución de la misma, ese área se libera (el registro se desapila), retornando
el control a la función llamante.
Montículo (heap): Un montículo es un bloque de almacenamiento dentro del cual se asignan o liberan
segmentos de memoria. Sobre este bloque de almacenamiento se profundiza cuando se trabajen varia-
bles y estructuras dinámicas.
Los espacios destinados a la pila y al montículo se ubican en extremos opuestos de la memoria para que
puedan crecer de manera conveniente.
Segmento de códi-
Almacenamiento gos de funciones y
estático datos del sistema
Almacenamiento
Pila
de registros de
(stack)
activación
Montículo
(heap) Almacenamiento
dinámico
Ejecución de un programa en C
Cuando se inicia la ejecución de un programa, en tiempo de compilación se generan los segmentos de
códigos de las distintas funciones que el programa contiene.
En la pila, el primer registro de activación que se almacena corresponde al main, ya que la ejecución se
inicia por esta función.
Cuando se invoca a una nueva función, se “apila” su correspondiente registro de activación, almacenán-
dose la información necesaria en sus campos. Una vez finalizada la ejecución, ese registro de activación
se “desapila” y queda sólo el registro de activación del main en la pila.
Ejemplo
El siguiente ejemplo, analiza la ejecución de un programa que calcula el perímetro de un terreno.
void main(void)
{
float largo, ancho;
printf(“ingrese el largo y el ancho”);
scanf(“%f”, &largo);
printf(“\n”);
scanf(“%f”, &ancho);
printf(“\n”);
printf(“el perímetro del terreno es: %f ”, perimetro( largo,ancho ));
}
Al iniciarse la ejecución del programa, se genera el registro de activación del main. Este registro no re-
serva espacio para los parámetros ni para el resultado de la función main.
Al encontrar la invocación de la función perímetro, se genera otro registro de activación, en el cual se
destina espacio para guardar: los parámetros formales xa y xl, la variable local perim, almacenamiento
temporal, el resultado de la función y la dirección de retorno.
La figura 2 muestra el estado de la memoria en esta situación, para los valores largo=23.5 y
ancho=13.6.
Almacenamiento Segmento de códigos de las funciones main y
estático perimetro
Registro Activación
largo ancho
main
23.5 13.6
xl xa
23.5 13.6 Parámetros formales
perim
Variables locales Registro Activa-
Pila 74.2 ción
perimetro
2* (23.5 + 13.6) Almacenamiento Temporal
74.2 Resultado
Montículo
Cuando termina la ejecución de la función perímetro, el sistema procede a desapilar el registro de acti-
vación asociado a ella en la pila y el control es devuelto al punto de llamada en el main. Una vez finali-
zado el programa, el sistema desapila el registro de activación del main.
Más adelante, cuando se trabaje el concepto de recursividad, se analizará el modo en que varios registros
de activación de una misma función se pueden apilar consecutivamente.
Tipos de almacenamiento
Existen dos formas de clasificar variables: por su tipo de datos y por su almacenamiento.
El tipo de datos hace referencia al conjunto de posibles valores que puede tomar una variable. Por ejem-
plo una variable puede ser de un tipo de dato int, float, char, etc.
El tipo de almacenamiento hace referencia a la permanencia o tiempo de vida de la variable y a su al-
cance dentro del programa en el cual se reconoce.
Respecto a la permanencia, se puede observar que algunas variables tienen una existencia breve, otras
son creadas y destruidas de manera repetida, y otras existen durante toda la ejecución del programa. El
tiempo de vida de un objeto es la duración de asignación en memoria.
Hay tres tipos de almacenamiento: estático (para variables globales y estáticas), automático (para varia-
bles locales) y dinámico (para variables dinámicas).
La palabras reservada auto (automática) se utiliza para declarar variables de persistencia automática.
Estas variables se crean al generarse el registro de activación de la función en la cual están declaradas,
existen mientras dicho registro está activo, y se destruyen cuando se sale de ese bloque.
Variables Automáticas
Las variables locales de una función, aquellas declaradas en la lista de parámetros o en el cuerpo de la
función, por lo regular tienen una persistencia automática. La palabra reservada auto declara en forma
explícita a las variables de persistencia automática.
Respecto a su alcance, el mismo está restringido a la función en la cual se declaran. De ahí que todas las
variables definidas dentro de una función no necesitan llevar el prefijo auto pues se asumen automáti-
cas. Las variables automáticas se almacenan en la pila en el registro de activación asociado a la función.
Ejemplo
void fucion1( void)
{ doble d;
int x ;
…
}
void fucion2 (void)
{ int y; float d ;
…
}
void main(void)
{ char z;
…
}
En este ejemplo, las variables automáticas de cada una de sus funciones son:
main: z
funcion1: d, x
funcion2: y, d
Como puede inferirse, las variables automáticas definidas en funciones diferentes serán independientes
unas de otras, incluso si tienen el mismo nombre.
Se pueden asignar valores iniciales a las variables automáticas. Tales valores se reasignarán cada vez
que se entre en la función, pues los valores de las variables automáticas se pierden cuando se sale de la
función. Si una variable automática no es inicializada, su valor inicial será impredecible.
Variables Externas
Las variables externas difieren de las automáticas pues su alcance no se restringe a una función. Una vez
definida una variable externa, toda función puede tener acceso a ella a través de su nombre.
Al trabajar con variables externas es necesario distinguir entre definir y declarar la variable.
Una variable externa debe definirse una sola vez, fuera de cualquier función. Esto reserva su espacio de
almacenamiento en el área de almacenamiento estático. Además su definición puede incluir la asigna-
ción de un valor inicial.
Una variable externa también debe declararse en cada función que desee tener acceso a ella. Una decla-
ración de variable externa tiene que comenzar con el especificador de tipo de almacenamiento extern.
Su declaración no reserva espacio de memoria.
El nombre de la variable externa y su tipo de datos tienen que coincidir con su correspondiente defini-
ción de variable externa que aparece fuera de la función.
Si una función altera el valor de una variable externa, este valor está accesible por cualquier otra función
que la utiliza.
Bajo ciertas circunstancias, la declaración de una variable externa puede omitirse. Tal es el caso de que
la definición de la variable externa ocurra dentro del archivo fuente, antes de main. Se las llama tam-
bién variables globales.
ACTIVIDAD 2
Realizar el seguimiento de los siguientes algoritmos para j =30 y mostrar las salidas correspondientes.
a)
#include <stdio.h>
#include <conio.h>
int i=32; /* definición de i como una variable externa y global */
void main(void)
{
int j; /* j es una variable automática de main */
clrscr();
printf("ingrese un entero ");
scanf("%d",&j);
int cambia(int j); /* declaración de la función */
printf("\n Cambio realizado en j en el subprograma: %d",cambia(j));
printf("\n Valor actual de j: %d",j);
printf("\n Valor actual de i: %d",i);
getch();
}
En este ejemplo, la definición de la variable externa precede a la definición de la función, por lo que no
es necesaria su declaración.
Programación Procedural 112
Tipos de almacenamiento
b)
#include <stdio.h>
#include <conio.h>
ACTIVIDAD 3
¿Qué modificaciones debería realizar en el inciso b) de la actividad anterior, si la definición de la varia-
ble externa i se realiza después del main()?
Variables Estáticas
Las variables estáticas tienen el mismo alcance que las variables automáticas, son locales a la función en
la que están declaradas. Sin embargo, respecto a su tiempo de vida, las variables estáticas retienen sus
valores durante toda la vida del programa. Como consecuencia de esto, al invocar nuevamente a la fun-
ción, se puede acceder al valor de la variable correspondiente a la última invocación. Esta característica
permite a las funciones mantener información permanente a lo largo de toda la ejecución del programa.
Las variables static se almacenan en memoria, en el área de almacenamiento estático, a continuación del
segmento de código de la función.
La declaración de una variable estática deberá empezar con la especificación del tipo de almacenamien-
to, para lo cual se utiliza el prefijo static.
Las variables estáticas pueden usarse de la misma manera que las otras variables, sin embargo, no pue-
den ser accedidas fuera de la función en que están definidas.
En la declaración de variables estáticas se pueden incluir valores iniciales. Las reglas asociadas a la
asignación de estos valores son esencialmente las mismas que las asociadas con la inicialización de va-
riables externas, aunque las variables estáticas se definen localmente dentro de una función.
En general:
• Los valores iniciales deben ser expresados como constantes, no como expresiones.
• Los valores iniciales se asignan a sus respectivas variables al comienzo de la ejecución del programa.
Las variables retienen estos valores a lo largo de toda la vida del programa, salvo que se le asignen
valores diferentes en el curso de la ejecución.
• A todas las variables estáticas cuyas declaraciones no incluyan valores iniciales explícitos, se les
asignará el valor cero. De esta manera, las variables estáticas tendrán siempre valores asignados.
Ejemplo
En el siguiente programa muestra el uso de variables estáticas
float promedio (int xa)
{
static int sum=0;
static int cont=0;
sum +=xa;
cont ++;
return (float) sum / cont ;
}
En este programa sum y cont son variables estáticas de la función promedio, por lo tanto retienen su
valor después de cada ejecución de misma.
ACTIVIDAD 4
Realizar el seguimiento del programa anterior para el siguiente lote de prueba: 2, 8, -4, 5,-6,100.
Bloques en lenguaje C.
Un bloque es una secuencia de declaraciones seguidas por una secuencia de enunciados, y rodeados por
marcadores sintácticos como son las llaves o pares inicio-terminación.
En lenguaje C, los bloques se conocen como enunciados compuestos y aparecen como el cuerpo de fun-
ciones en la definición de las mismas. Además, es posible también anidar bloques, como se muestra a
continuación:
void p (void)
{ doble r; /* el bloque de la función p*/
……
{
int x, y; /* un bloque anidado */
x=2 ;
y=3 + x ;
}
……
……
}
El lenguaje C también admite declaraciones externas o globales fuera de cualquier enunciado com-
puesto, como por ejemplo:
void main(void)
{
float f;
int g; /* f y g están asociadas al bloque del main*/
……
……
}
22
Lenguajes de Programación Principios y Prácticas. Kenneth Louden. Pág. 118.
Programación Procedural 115
Declaraciones, Bloques y Alcance
En este ejemplo, las declaraciones de la variable x, y de las funciones p, q, main son globales, es decir
tienen alcance global. Las declaraciones de d, y, z son locales respecto a las funciones p, q, y main res-
pectivamente. Por lo tanto sus declaraciones son solamente válidas para dichas funciones.
Apertura en el alcance:
Una característica de la estructura de bloques es que las declaraciones en los bloques anidados toman
precedencia sobre declaraciones anteriores.
int x;
void p(void)
{ doble d;
int x ;
…
}
void q(void)
{ int y;
…
}
void main(void)
{ char z;
…
}
En síntesis, en el ejemplo anterior la variable global x tiene alcance dentro de la función p, ya que la
ligadura sigue existiendo, no obstante la variable global x está oculta.
ACTIVIDAD 5: Realice el seguimiento del siguiente algoritmo y analice el alcance de las distintas
declaraciones.
int b=10;
void mostrar(int x, int y)
{
printf ("\n en este ejercicio hemos sumado %d + %d y obtuvimos este resultado:%d",x,y,x + y);
printf ("\n Valor de b :%d",b);
}
void main(void)
{
int a=10,b=5;
printf ("\n a + b antes del bloque %d", a+b);
{
int j=5,a=3;
printf ("\n a + b dentro del bloque %d", a+b);
j++;
printf ("\n j dentro del bloque %d", j);
}
printf ("\n a + b al salir del bloque %d", a+b);
mostrar(a,b);
getchar();
}
Ejemplo
int factorial ( int x )
{ int f=1;
while (x)
{
f*=x;
x--;
}
return f;
}
Salida:
Ingrese un valor para a: 9
El factorial de 9 es 362880
Estos resultados muestran que dentro de main, a no se ha modificado, aunque se haya modificado el
valor correspondiente a x en la función factorial.
La ventaja del pasaje por valor es que se protege el valor del parámetro real de posibles alteraciones
dentro de una función.
Como puede inferirse, el pasaje por valor ofrece la posibilidad de proporcionar como parámetro real una
expresión o valor constante, en lugar de que éste sea necesariamente una variable.
x 0 Parámetros formales
Montículo
Nótese en este algoritmo que no hace falta definir una variable para la iteración, en su lugar se va de-
crementando el parámetro formal x hasta llegar a 1.
Pasaje de direcciones
El paso de pasajes de parámetros por valor no implica que no puedan ocurrir cambios fuera de la fun-
ción. Si el parámetro es un puntero, esto es una dirección, puede usarse para cambiar la memoria apun-
tada por fuera de la función. Otra ventaja que ofrece el pasaje de parámetros por dirección es retornar
más de un resultado desde la función.
En C, para pasar un parámetro por dirección, se utiliza el operador de dirección &, al momento de invo-
car a la función. Luego, el operador de indirección * debe utilizarse en la función para acceder al valor
almacenado en esa dirección.
Retomemos la función factorial, pero de tal modo que la variable a se pasa por dirección: en los paráme-
tros formales.
Ejemplo
int factorial ( int * x )
{ int f=1;
while (*x)
{
f*=*x;
--(*x);
}
return f;
}
Salida:
Ingrese un valor para a: 9
Valor de a antes de invocar a la función factorial 9
Valor de a después de invocar a la función factorial 0
El factorial es 362880
Como el operador & permite obtener la dirección de una variable, al definir la función factorial se debe
indicar que el parámetro es un puntero y acceder indirectamente a través de éste a la variable a.
Más adelante, cuando se analiza el pasaje de arreglos como parámetros, se vuelve a trabajar con pasaje
de direcciones.
100h
x Parámetros formales
Pila
362880
f Variables locales
Registro Activación
factorial
….. + 1 Almacenamiento Temporal
Montículo
Ejemplo : La función factorial antes definida, puede redefinirse usando pasaje por referencias:
int factorial ( int &x )
{ int f=1;
while (x)
{ f*=x;
x--;
}
return f;
}
Parámetros formales
Montículo
Salida:
Ingrese un valor para a: 9
Valor de a antes de invocar a la función factorial 9
Valor de a después de invocar a la función factorial 0
El factorial es 362880
En este caso, x hace referencia a la variable a. Esto implica que no hay duplicación de memoria y cual-
quier modificación que se efectúe en el parámetro formal x afectará o se verá reflejado en el parámetro
real a.
Observación: En lenguaje C estándar todas las llamadas son por valor, las llamadas por referencia se
las puede simular haciendo pasaje por dirección.
No obstante en este curso se usará también los pasajes por referencias provistos por C++.
Ejemplo
Supongamos que se necesita definir una función que retorna la suma de los números pares y la suma de
los números impares, menores que un valor ingresado por teclado.
En este caso, uno de los resultados será devuelto por la función y el otro en un parámetro, a través de un
pasaje de referencias.
int acumula_pares_impares (int &si, int xnum)
{
int sp=0;
while (xnum)
{ if ((xnum % 2)==0)
sp+=xnum;
else
si+=xnum;
xnum--;
}
return sp;
}
En este ejemplo, sumai es una variable del main que almacenará la suma de los impares, para ello se
pasa por referencia.
ACTIVIDAD 6
Modificar el programa anterior de manera que el pasaje de la variable sumai se realice por dirección.
¿Qué conclusiones se pueden inferir si compara ambos tipos de pasajes?
Modificar el programa anterior de manera que los dos resultados sean devueltos por la función a
través de los parámetros.
a 0 1 2 .................................... 9
Por lo tanto, al pasar un arreglo como parámetro de una función, no se pasan los valores de las compo-
nentes del mismo, sino la dirección de la primera componente del arreglo. El parámetro formal se trans-
forma entonces en un puntero al primer elemento.
De ahí, si se realiza alguna modificación a cualquier componente del arreglo, esa modificación será re-
conocida en todo el ámbito del arreglo.
Los prototipos de funciones que incluyen arreglos como argumentos, se pueden especificar de distintas
formas:
Colocando el nombre del arreglo en cuestión seguido de un par de corchetes vacíos o con el tamaño
respectivo; todo esto precedido por el tipo de datos de las componentes del arreglo.
Prescindiendo del nombre del arreglo, pero colocando un par de corchetes vacíos o indicando el
tamaño del arreglo, precedidos por el tipo de datos de las componentes del arreglo.
Ejemplo
El siguiente programa muestra la forma de trabajar los arreglos como parámetros de funciones y las
distintas formas de especificar el prototipo de las funciones.
#include <stdio.h>
#define n 20
arre
1234h Parámetros formales
Pila
Registro Activación
i Variables locales
carga
19
Montículo
Observación: Como puede observarse, cuando se pasa un arreglo a una función se realiza un pasaje por
valor de una dirección, la dirección de la primer componente del arreglo.
b) Además, como el nombre del arreglo es un puntero, puede recibirse como un puntero y el tamaño
del arreglo.
ACTIVIDAD 7
Realizar un programa en C que, a través del uso de funciones, permita:
a) Cargar un arreglo de N componentes enteras.
b) Dado un valor entero, indicar si ese valor está o no en el arreglo. Si el valor está en el arreglo
devolver la posición, de lo contrario devolver -1.
c) Mostrar en el programa principal la cantidad de componentes pares del arreglo y la suma de las
mismas.
Ejemplo :
#include < stdio.h >
Cada uno de los tres intentos por modificar las componentes del arreglo, en la función da como resultado
el siguiente error de compilación: cannot modify a const object
Inserción y búsqueda
En lenguaje C, la operación de inserción se realiza cuando se procesa una declaración, ya que una de-
claración es un descriptor inicial de los atributos de un identificador del programa fuente.
Si la TS está ordenada, es decir los nombres de las variables están por orden alfabético, entonces la ope-
ración de inserción llama a un procedimiento de búsqueda para encontrar el lugar donde colocar los
atributos del identificador a insertar. En tales casos la inserción lleva tanto tiempo como la búsqueda.
Si la TS no está ordenada, la operación de inserción se simplifica mucho, aunque también la operación
de búsqueda se complica ya que debe examinar toda la tabla.
En las operaciones de inserción se detectan los identificadores que ya han sido previamente declarados,
emitiéndose, a su vez, el correspondiente mensaje de error.
Ejemplo en lenguaje C: multiple declaration for ‘s’ si s ya estaba en la TS
En las operaciones de búsqueda se detectan los identificadores que no han sido declarados previamente
emitiéndose el mensaje de error correspondiente.
Ejemplo en lenguaje C: Undefined símbolo ‘x,’ si x es una variable que desea usarse pero no se declaró.
Set y Reset
Otras operaciones realizadas sobre una tabla de símbolos SET (activar un bloque) y RESET (desactivar
el bloque). La operación de set se utiliza cuando el compilador detecta el comienzo de un bloque o fun-
ción en el cual se pueden declarar identificadores locales o automáticos. La operación complementaria
reset, se utiliza cuando se detecta el final del bloque o función.
23
Lenguajes de Programación Principios y Prácticas. Kenneth Louden. Pág. 127 - 131
24
Lenguajes y Sistemas Informáticos: Tabla de Símbolos. Universidad de Oviedo. Pág. 9 a 16; 37 – 41.
Programación Procedural 126
Traducción y Tabla de Símbolos en Lenguaje C
Como consecuencia del uso de bloques anidados, la organización más simple para soportar la Tabla de
Símbolos en los lenguajes estructurados en bloques, es una PILA (LIFO Last Input First Outpout).
Existen tablas de símbolos con estructura de árbol implementadas en pilas, Tablas de símbolos con es-
tructura hash implementadas en pilas, pero éstos temas son objeto de estudio en años superiores.
Para comprender el manejo de la TS en lenguaje C podemos considerar una tabla de símbolos como un
conjunto de nombres, cada uno de los cuales tiene una pila de declaraciones asociados con ellos, de ma-
nera que la declaración en la parte superior de la pila es aquella cuyo alcance está actualmente activo.
A la entrada de un bloque todas las declaraciones se procesan y se agregan las vinculaciones correspon-
dientes a las TS; a la salida de un bloque se eliminan todas las ligaduras proporcionadas por las declara-
ciones, restaurando cualquier vínculo anterior que pudiera haber existido.
Podemos ver que la tabla de símbolos cambiará conforme se avanza en la traducción, para reflejar las
inserciones o eliminaciones de ligaduras dentro del programa que se está traduciendo.
Una manera clara para ver el funcionamiento de la tabla de símbolos en lenguaje C, es a través de los
gráficos con que usualmente se representan las variables.
Ejemplo
1) int x;
2) char y;
3) float func1(int n)
4) { float x=10.50;
5) x*=2;
6) {
7) ......
8) int y[10];
9) }
10) …
11) }
12) void func2(void)
13) { int y;
14) …
15) }
16) void main(void)
17) { char x;
18) …
19) }
Los identificadores (nombres) involucrados en este programa son x, y, n, func1, func2 y main, pero x e
y están cada uno de ellos asociados con tres declaraciones diferentes y con alcances distintos.
Después que el procesamiento alcanzó la función func1, la tabla de símbolos en la línea 4 es:
Nombre Ligadura
y char global
Funcion Resulta-
func1 do float
1c1 Parámetro int
Después del procesamiento de la declaración del bloque anidado en func1, con la declaración local de y
en la línea 8, se tiene:
Nombre Ligadura
y Arreglo componentes
int. Dim 10 char global
y
char global
n
int local a func1
Una vez terminado el procesamiento de la función func1 (línea 11), la tabla de símbolos cambia debido
a las extracciones de las declaraciones locales de n, x e y.
Nombre Ligadura
x int global
y
char global
x int global
x int global
y char global
Finalmente, después de de iniciar la función main ,la tabla de símbolos en la línea 17 queda como se
muestra:
Nombre Ligadura
x int global
char local al main
y char global
Observe que este proceso conserva la información apropiada de alcance, incluyendo las aperturas de
alcance para la declaración global de x en el interior de func1 y la declaración global de y en el interior
de func2. Esta representación de la tabla de símbolos supone que la tabla procesa las declaraciones de
manera estática, esto es, antes de su ejecución. Este es el caso, siempre que la tabla de símbolos sea ma-
nejada por un compilador y que las ligaduras de las declaraciones sean todas estáticas.
Si la tabla de símbolos está administrada de esta misma forma pero dinámicamente, esto es, durante su
ejecución, entonces las declaraciones se procesan conforme se van encontrando a través del programa a
lo largo de la trayectoria de ejecución. Esto da como resultado una regla de alcance diferente, lo que por
lo general se conoce como alcance dinámico. La regla de alcance léxico anterior a veces se conoce como
alcance estático.
Manejo de la TS en PILA
La organización más simple, desde el punto de vista conceptual, de una tabla de símbolos de un lenguaje
estructurado en bloques es la pila. En este tipo de organización los registros que contienen los atributos
de los identificadores se van colocando unos encima de otros según se van encontrando las declaracio-
nes de las variables del texto fuente. Una vez que se lee el fin del bloque, todos los registros de los iden-
tificadores declarados en el bloque se sacan de la pila, pues dichos identificadores no tienen validez fue-
ra del bloque.
Resulta importante entender el funcionamiento de la función SET y RESET. Para esto se usa una pila
auxiliar, de tal modo que la operación SET guarda en esta pila un índice de bloque (BLOCK INDEX)
que representa el lugar donde se encuentra almacenado el primer registro del bloque, y que al momento
de la operación ocupa la parte superior (TOP) de la pila.
int x;
char y; Volviendo al ejercicio anterior, en la línea marcada con /****/, la tabla
float func1(int n) de símbolo y la pila auxiliar tiene la siguiente información.
{ float x=10.50;
x*=2; Posición Nombre Atributos
{ ...... 1 x int
int y[10]; /1*****/ 2 y char
char z 3 func1 …. 1
} 4 n int
…/2****/ 5 x float 4
} 6 y Arreglo
void func2(void) 6
7 Z char
{ int y;
…
}
void main(void)
{ char x; /3****/
…
}
En la línea marcada con /2****/, la operación RESET saca de la pila todos los registros de las variables
que se acaban de compilar en el bloque finalizado, para esto el BLOCK INDEX indica hasta que registro
ha de sacar desde la parte superior de la tabla de símbolos.
ACTIVIDAD 8: Muestre la tabla de símbolos y la pila auxiliar en la línea marcada con /3***/.
Bibliografía:
Louden, Kenneth (2004) Lenguajes de Programación Principios y Práctica. Editorial Thomson. Mexico.
Universidad de Oviedo (2006) Lenguajes y sistemas informáticos. Tablas de símbolos.
Deitel, Harvey M. y Deitel, Paul J. (2003) Como programar en C, C++ . Pearson Educación. Cuarta
Edición. Buenos Aires.
Programación en C. Byron S. Gottfried. (1997): McGraw-Hill. Madrid - Buenos Aires.
Joyanes Aguilar, Luís (2001) Programación en C - Metodología, algoritmos y estructura de datos.
McGraw-Hill. Madrid - Buenos Aires.
Documentos y Guías de trabajos Prácticos propuesto por el equipo de cátedra.
Práctica Unidad 3
Lenguaje C - Funciones - Tabla de Símbolo
Ejercicio 1
A partir de un arreglo generado aleatoriamente con 50 números enteros, codificar un programa en C que
permita:
a) Indicar si alguno de los números generados es un cero.
b) Escribir el contenido de las componentes que se encuentren en las posiciones pares.
c) Indicar cantidad de números pares que contiene.
d) Leer un número y si se encuentra en el arreglo indicar su posición (realizar búsqueda óptima).
Ejercicio 2
Un local comercial de ventas de repuestos de automotores desea obtener cierta información sobre todas
las ventas registradas en un periodo de tiempo dado. El comercio cuenta con 250 artículos, almacenados
en una estructura y de los cuales se conocen los siguientes datos: Código, Nombre, Precio Unitario y
Stock.
Los datos ingresados de cada una de las ventas efectuadas son: Nombre del artículo, Cantidad de unida-
des vendidas. El ingreso de ventas termina con nombre “FIN”.
Se pide realizar un programa en C, que utilizando subprogramas óptimos y estructuras adecuadas permi-
ta:
1. Procesar las ventas registradas en ese periodo de tiempo.
2. Mostrar los nombres de aquellos artículos que quedaron con stock nulo.
3. Indicar el stock de un artículo cuyo código se ingresa previamente por teclado.
4. Imprimir los nombres de los 20 artículos que quedaron con mayor stock.
5. Indicar el monto total obtenido por las ventas de los productos.
Ejercicio 3
Un laboratorio abastece a 30 farmacias de la provincia. Dicho laboratorio comercializa 80 medicamentos
(1..80) de los que se debe registrar: Código de medicamento, nombre y precio unitario.
Se ingresan las ventas realizadas ordenada por farmacia. Por cada venta a una farmacia se ingresa: códi-
go de medicamento y cantidad de unidades, finalizando con código de medicamento igual a 0 (cero),
como lo muestra el siguiente ejemplo:
Código Medicamento Cant. Unidades
Farmacia 1 23 12
32 20
41 6
0
Farmacia 2 43 10
25 24
0
Codificar un programa en C, que utilizando funciones permita:
a) Calcular y mostrar total de unidades vendidas de cada uno de los medicamentos.
b) Escribir el/los códigos/s del/los medicamento/s por el que se recaudó mayor importe.
c) Indicar la cantidad de unidades vendidas para un código de medicamento ingresado por teclado.
d) Dado el nombre de un medicamento indicar el importe total recaudado.
e) Indicar la cantidad de unidades vendida a cada farmacia y el importe total que pagó cada una.
Ejercicio 4
Una industria comercializa 70 productos codificados entre 100 y 169. De cada producto se conoce el
código de producto y precio unitario. Además se cuenta con la información de las ventas realizadas du-
rante el fin de semana. Por cada venta se ingresa código de producto y cantidad de unidades, finalizando
el ingreso con código de producto igual a cero.
Se pide realizar un programa en C, que utilizando funciones óptimas y estructuras adecuadas permita:
a) Total de unidades vendidas de cada uno de los productos.
b) Código del producto que recaudó mayor importe.
c) Dado un código de producto de producto, indicar la cantidad de unidades vendidas.
d) En función del total de unidades vendidas, decir de cuantos productos se vendieron 20, 21, 22..
50 unidades.
Ejercicio 5
En la Facultad se realiza un congreso para el cual se destinan 6 salas de conferencias y cada una repre-
senta un área temática. En cada sala se dictaran 4 conferencias en distintos turnos. Para procesar la in-
formación, en un primer momento y por única vez se ingresa el nombre de cada una de las 6 áreas temá-
ticas que se tratarán en el congreso y el cupo de personas para la sala donde se dictará la misma. Por
cada interesado se ingresa su nombre, nombre del área temática, y número correspondiente a la confe-
rencia a la que quiere asistir. La inscripción se realiza previa verificación del cupo de la sala. A partir de
la información ingresada generar una tabla que permita responder los siguientes ítems:
a) Decir para cada área temática qué conferencia tuvo menos asistentes y cuál la mayor
cantidad (Suponer el mayor y el menor como valores únicos).
b) Indicar el nombre del área temática con menos inscriptos.
c) Dado un nombre de área temática decir cuál fue el promedio de inscriptos.
d) Indicar la/s áreas temáticas que en algún turno tuvieron la sala completa, si las hubiera.
Ejercicio 6
Un supermercado ingresa las ventas de los últimos 6 meses, realizadas en los 8 departamentos de venta
que posee. Por cada venta se ingresa mes, departamento e importe. Las ventas no traen ningún orden
particular. Realizar un programa en C, que a través de funciones permita:
a) Almacenar la información en una tabla que posea por cada mes, el importe total de ventas de
cada departamento.
b) Mostrar en el programa principal el departamento que tuvo menor importe de venta (suponer
único).
c) Mostrar importe promedio de venta del supermercado.
d) Mostrar el/los departamento/s que supera/n la venta promedio, indicando el importe total
vendido a lo largo del semestre.
Ejercicio 7
Para el siguiente programa se pide:
a) Mostrar la tabla de símbolos en las líneas con la marca utilizando la pila auxiliar.
b) En cada subprograma indicar variables locales y globales.
c) Hacer el seguimiento de la ejecución y mostrar el estado de la memoria cuando se ejecuta la
función calculo en la última invocación. Lote de prueba: (2,3) (0,1), (3, 5), (0, 1), (8,7) (3,3).
d) Indicar variables automáticas, estáticas y externas según corresponda.
int m=0;
int calculo(int v, int w)
{ static int z=0;
if ((w==1)&& (v==0)) z++;
return z;
}
void main(void)
{
int a,b, y=0;
printf("\n ingrese par de valores a y b");
scanf("%d %d", &a, &b);
while (a<b)
{
int c;
c=a*b;
calculo (a,b);
x+=a;
y+=b;
printf("\n ingrese valor a y b");
scanf("%d %d", &a, &b);
printf("\n ......%d", c);
}
printf("\n ......%d", calculo(a,b));
printf("\n ......%d .....%d", x,y);
}
Ejercicio 8
Para el siguiente programa se pide:
a) Mostrar la tabla de símbolos en las líneas con la marca indicando variables locales y
globales.
b) Hacer el seguimiento de la ejecución y mostrar el estado de la memoria cuando se ejecuta la
función calculo1.
c) Indicar variables automáticas, estáticas y externas según corresponda.
int e=10;
int resuelve (int v,int w, int *z)
{
*z=(v + w)*e;
return *z;
}
Ejercicio 9
Una empresa de seguros procesa la información de las ventas que han realizado sus 10 promotores. De
cada uno de los 10 promotores se conoce el código de sector donde trabaja (número
entre 30 y 37) codificado: 30:Moto - 31:Auto - 32:Camioneta - 33:Camión - 34:Ómnibus de Corta
distancia - 35:Ómnibus de larga distancia - 36:Combis de pasajeros - 37:taxis.
De cada seguro (son 3 tipos de seguros distintos) se conoce el tipo (una letra entre “A” y
“C”), el nombre y su precio. Los tipos de seguro se codifican: “A”: Seguro contra terceros, “B”: Seguro
de Incendio y “C”: Seguro Total.
Nota: Leer la información que se pide, y de acuerdo a eso, ¿Qué estructura es la más adecuada para el
almacenamiento de los datos?
Se pide realizar un programa que permita (utilizando Menú de opciones):
a) Ingresar las ventas de seguros realizadas. Por cada venta se ingresa número de promotor (de
1...10) y tipo de seguro(“A”…“C”). Las ventas no traen ningún orden específico y termina el
ingreso con número de promotor igual a 0.
b) Ingresar un tipo de seguro e indicar en qué sector se lo vende más y cuantos promotores
tiene ese sector.
c) Dado un número de sector, indicar cuál es el seguro que más se consume.
d) Indicar para cada tipo de seguro, el nombre y el importe total de venta.
Ejercicio 10
Una fábrica de ropa comercializa 50 prendas que son vendidas a 35 comercios del país. Por cada venta
realizada se cuenta con los siguientes datos: Código de comercio (60..94), Nombre de la prenda vendida
y cantidad de unidades. Las ventas no traen ningún orden en particular.
En una estructura se registra por cada prenda que se comercializa su nombre y precio unitario, ordenado
alfabéticamente.
Además por cada comercio se almacena su CUIL y Nombre.
Se pide realizar un programa en C, que utilizando funciones óptimas y estructuras adecuadas permita
(utilizar Menú de opciones):
a) Almacenar los datos de las ventas en una estructura que posea por cada comercio la cantidad de
unidades vendidas de cada prenda.
b) Indicar por cada comercio; CUIL, Nombre e importe total a pagar.
c) Realizar un listado que contenga por cada producto, nombre y cantidad de unidades vendidas,
este listado debe estar ordenado descendentemente por cantidad de unidades.
d) Mostrar el nombre de los 5 productos que más se vendieron.
Ejercicios Propuestos
Ejercicio 1
Codificar en C un programa que tenga:
a) Un menú de opciones, una de las cuales debe ser secreta. Propuesta:
1. Opción 1
2. Opción 2
3. Opción 3
99. Opción Secreta
b) Crear una función que reciba tres valores enteros ingresados por el usuario, y que calcule el
cuadrado de cada número. El pasaje de los parámetros debe ser por valor, por referencia y por
dirección. Mostrar los valores de las variables antes del llamado a la función, dentro de la misma
y al salir. Esta función debe ser la Opción 1 del menú.
c) Mostrar mapa de memoria de la ejecución de la función del punto anterior.
d) Crear una función que genere una tabla de NxM componentes enteras, y lo llene con números
aleatorios entre 100 y 199. (Opción 2)
Ayuda: https://blog.martincruz.me/2012/09/obtener-numeros-aleatorios-en-c-rand.html
e) Crear dos funciones que procese la tabla anterior. Una, que busque el máximo de la fila 0
(enviar sólo la fila a procesar); y la otra, que cuente todas las componentes de la columna M.
(Opción 3)
1. ¿Se puede enviar como parámetro una sola columna de la tabla? Justifique
2. A nivel de memoria, ¿Qué diferencia hay entre mandar una fila o la tabla completa? ¿Cuál
forma es la más óptima?
Nota: Responder usando mapa de memoria
Ejercicio 2
Realizar un programa en C que permita:
a) Leer datos enteros hasta que se llegue a 1000 datos o hasta que el dato ingresado sea igual a –
50.
b) Imprimir cantidad de elementos ingresados.
c) Imprimir porcentaje de datos pares leídos.
d) Calcular e imprimir promedio de todos los datos ingresados.
Ejercicio 3
Se tienen los datos relacionados a un censo de pacientes de un hospital. Por cada paciente se ingresa
número de paciente, edad y peso. El ingreso finaliza cuando se lee un peso negativo o cuando la canti-
dad de pacientes supere los 400.
Realizar un programa en C, que permita:
a) Calcular la cantidad de pacientes cuya edad este comprendida entre 7 y 11 años inclusive.
b) Determinar el porcentaje de pacientes mayores de 11 años cuyo peso no supera los 50kg.
c) Imprimir el número de paciente y edad con menor peso.
.
Ejercicio 4
Una empresa desea realizar un control de 700 productos distintos que comercializa, de los mismos po-
see: Código de identificación del producto, cantidad de productos con ese código y precio unitario.
Realizar un programa en C que permita:
a) Almacenar los datos de todos los productos.
b) Calcular el monto total del stock por producto (precio por cantidad).
c) Mostrar el código de identificación de los productos con mayor precio unitario y cuyo código de
identificación esté comprendido entre 250 y 300.
d) Ingresar un valor correspondiente a una cantidad de stock mínima y generar un arreglo que
contenga los códigos de aquellos productos cuya cantidad sea menor que la ingresada.
Ejercicio 5
Se tienen las notas de 10 materias de 25 alumnos que cursan 3er año de secundaria. Además se cuenta
con los nombres de los alumnos almacenados en un arreglo.
Realizar un programa en C, que permita:
a) Cargar y mostrar por cada alumno la calificación obtenida en cada materia.
b) Calcular la nota promedio por cada alumno.
c) Calcular la nota máxima y mínima en cada materia.
d) Imprimir el nombre del alumno con mejor promedio.
Ejercicio 6
Realizar un programa en C, que permita:
a) Cargar una matriz de n x m, con elementos enteros desde el 1 al 100.
Buscar los números primos que contenga la matriz e indicar la posición en la que se encuentra cada uno
de ellos.
Unidad 4: Recursividad
Introducción
La recursividad es una alternativa a la iteración, es un proceso mediante el cual se puede definir una
función en términos de sí misma.
Generalmente, la solución recursiva a un problema planteado suele resultar menos eficiente en cuanto al
tiempo de ejecución y al almacenamiento en memoria. A pesar de esta dificultad, en muchos casos si el
algoritmo está definido recursivamente; se elige la solución recursiva por ser más simple y natural que la
solución iterativa correspondiente, permitiendo así, reducir su complejidad y ocultar detalles de progra-
mación.
Funciones Recursivas
El factorial de un número natural, está definido iterativamente de la siguiente manera:
n ! = n * ( n – 1 ) * ( n – 2 ) * . . . * 2 * 1 , si n > 0
y
n!=1 , si n = 0
Ese mismo cálculo puede expresarse de manera recursiva, así:
n ! = n * ( n – 1 ) ! , si n > 0
y
n!=1 , si n = 0
Puede observarse que la función factorial se llama así misma de manera recursiva, con un argumento
real (i – 1), que decrece en cada llamada recursiva. Estas llamadas recursivas finalizan cuando el pará-
metro real toma el valor 0.
Cuando se ejecute este programa, se invocará a la función factorial sucesivamente, una vez desde main y
(i – 1) veces desde dentro de sí misma, aunque no sea advertido por el programador.
Cuando se ejecuta una función recursiva, las sentencias que aparecen después de cada invocación no se
resuelven inmediatamente, éstas quedan pendientes. Por cada invocación recursiva, se genera y se apila
en el stack o pila un registro de activación. Luego, esos registros de activación se van desapilando en el
orden inverso a como fueron apilados, ejecutándose además aquellas sentencias que quedaron pendien-
tes.
Retomando el ejemplo de la función factorial recursiva, las llamadas sucesivas de la función serán:
n!=n*(n–1)!
(n–1)!=(n–1)*(n–2)!
(n – 2 ) ! = ( n – 2 ) * ( n – 3 ) !
.................................................
................................................
2!=2*1!
1!=1*0!
0!=1
Por ejemplo, al calcular 5!, se realizan sucesivas llamadas recursivas y finalmente, al encontrar el caso
base, se retornan los valores obtenidos en cada llamada:
5*4! 5* 24 = 120
24
4*3! 4* 6 = 24
6
3*2! 3* 2= 6
2
2*1! 2*1= 2
1
1* 0! 1*1=1
El orden inverso de ejecución es una característica típica de todos los algoritmos recursivos.
Gráficamente, lo que sucede internamente en la memoria al invocar a la función factorial, por ejemplo
para n = 3, es:
Registro de activación del main
v. local n: 3
Resultado factorial(3)
R i: 3
6
E
G area temporal : 3 -1
R1
I
resultado: i* factorial ( 2)
S.
direcc. de retorno al punto de invocación (main)
A
C i: 2
T 2
I area temporal : 2 -1
V R2 resultado: i * factorial ( 1)
A
C
I direcc. de retorno al punto de invocación (R1)
Ó
N i: 1
area temporal : 1 -1
1
A
resultado: i * factorial ( 0)
P R3
I direcc. de retorno al punto de invocación (R2)
L
A i: 0
D
O R4 resultado: 1
1
S
direcc. de retorno al punto de invocación (R3)
Al invocar a factorial (3), se apila el primer registro de activación R1; en el cual se guarda el parámetro
actual n = 3, la dirección de retorno, el resultado de la función y también un área temporal de almace-
namiento para los cálculos intermedios. Como en el cálculo de factorial (3) está involucrado el cálculo
de factorial(2), se apila el registro de activación R2, que almacena el parámetro actual n = 2, el punto de
retorno, el resultado y un área temporal de almacenamiento para los cálculos intermedios.
Al invocar una función recursiva, se apilan sucesivamente cada uno de los registros de activación, co-
rrespondientes a las distintas invocaciones. Al llegar al caso base, comienzan a desapilarse cada uno de
esos registros de activación, resolviendo tareas pendientes, si es que existen.
Cabe aclarar que al ejecutar una función recursiva, ésta genera un número considerable de auto invoca-
ciones; se corre el riesgo de ocupar gran cantidad de memoria en la pila.
Si una función recursiva posee variables locales, se creará un conjunto diferente de variables por cada
llamada. Obviamente, los nombres de esas variables locales serán siempre los mismos; sin embargo, las
variables representarán un conjunto diferente de valores cada vez que se invoque la función.
Tanto la recursión como la iteración son lógicamente equivalentes; esto es, todo algoritmo recursivo
puede escribirse de manera iterativa y viceversa.
Generalmente, la recursividad presenta mayor grado de simplicidad y síntesis, al momento de codificar.
La desventaja se presenta normalmente en tiempo de ejecución, debido a la cantidad de memoria utiliza-
da.
ACTIVIDAD 1: Completar el programa con una función recursiva “divisor” que recibe dos
números naturales distintos a y b, a >b. La función devuelve un valor 1 al programa principal
para que se escriba un mensaje diciendo que b es divisor de a, caso contrario cuando b no es
divisor de b devuelve cero(0).
Hacer el mapa de memoria para cada una de las soluciones planteadas con el lote de prueba
x=7, y=2.
#include <stdio.h>
#include <conio.h>
main(void)
{
int a,b,r;
printf("Ingrese dos números naturales distintos el primero mayor que el segundo\n");
scanf("%d %d",&a, &b);
if (divisor(a,b)==1)
printf("%d es divisor de %d", a,b);
else
printf("no hay divisores");
getch();
}
#include <conio.h>
#include <stdio.h>
void main()
{
int n;
printf("\n Introduzca un entero positivo: ");
scanf("%d", &n) ;
printf("\n El Numero Decimal %d en binario es ", n);
Binario(n);
getch();
}
ACTIVIDAD 3
Teniendo en cuenta la siguiente definición de una función recursiva:
Ejemplo : La siguiente función calcula, recursivamente, la suma de los números pares menores o igua-
les que un valor ingresado por teclado.
#include <stdio.h>
#include <conio.h>
int acumula_pares ( int xnum)
{
if (xnum)
if ((xnum % 2) = = 0)
return xnum + acumula_pares( xnum - 2);
else return(acumula_pares( xnum - 1));
else return (0);
}
En este caso, la función presenta tres sentencias return que se corresponden con el caso base y los dos
casos generales.
La invocación puede verse en el siguiente algoritmo:
int main()
{ int num;
scanf("%d", &num);
printf("\n suma de pares menores que: %d es %d", num, acumula_pares(num-1));
getch();
}
A continuación se muestra en forma gráfica cómo se apilan los distintos registros de activación en la
llamada de la función para un valor de num igual a 7.
R6
Puede observarse que el valor del parámetro actual num no se modifica, a la salida de la ejecución de la
función acumula_pares.
Ejemplo
Calcular recursivamente la suma de las componentes de un arreglo.
Se declara y se inicializa el arreglo arre:
int arre[5]={2, 3, 6, 7, 10}
se define una función suma que reciba el arreglo arre, el límite inferior (0) y el límite superior (4).
La invocación desde el programa principal será suma(arre, 0,4) y el prototipo de la función es:
int suma (int arr[], int inf, int sup);
Esquema de la solución:
28
if (inf <=sup)
return 2 + suma(arr, 1, 4);
if (inf <=sup) 26
return 3 + suma(arr, 2, 4); 23
if (inf <=sup)
return 6 + suma(arr, 3, 4); 17
if (inf <=sup)
return 7 + suma(arr, 4, 4);
if (inf <=sup) 10
return 10 + suma(arr, 5, 4);
if (inf <=sup
else return 0 0
Código en lenguaje C
#include<stdio.h>
#include<conio.h>
int suma(int arr[], int inf, int sup)
{
if (inf <=sup)
return arr[inf] + suma(arr, inf+1, sup);
else return 0;
}
int main()
{ int arre[5]={2, 3, 6, 7, 10} ;
printf("\n Suma de las componentes %d", suma(arre, 0,4));
getch();
}
Mapa de Memoria
arre 2 3 6 7 10
Registro de activación del
100h main
R arr sup 28
100h inf 0 4
E
G area temporal : 0+1
I R1
S resultado: 2 + suma(arr, 1, 4)
T
R direcc. de retorno al punto de invocación (main)
O
S arr 1 sup 4
100h inf
26
area temporal : 1+1
A R2
P
I resultado: 3 + suma( arr,2,4)
L
A
D direcc. de retorno al punto de invocación (R1)
O
S R3 arr 100h inf 2 sup 4 23
ACTIVIDAD
R6 5
Modifique el algoritmo anterior de modo que la función devuelva la suma de las componentes y el total
de componentes mayores a 5.
Ejemplo : El siguiente programa usa las funciones recursivas carga, busq_sec_rec y escalar, para
realizar las siguientes tareas:
1- Cargar dos vectores de 5 componentes enteras positivas
2- Dado un valor, indicar en cuál de los vectores se encuentra
3- Calcular el producto escalar de los dos vectores
int main(void)
{
int a[n], b[n], valor;
printf ("\n Carga del primer vector \n"); carga(a,0);
printf ("\n Carga del segundo vector \n"); carga(b,0);
printf ("\n Ingrese valor a buscar en los vectores: ");
scanf("%d",&valor);
if (busq_sec_rec(a,0,valor)==1)
printf("\n El valor está en el primer vector\n");
else printf("\n El valor NO está en el primer vector\n");
if (busq_sec_rec(b,0,valor)==1)
printf("\n El valor está en el segundo vector\n");
else printf("\n El valor No está en el segundo vector \n");
printf ("\n Producto escalar de los vectores = %d", escalar(a,b,n -1));
getch();
}
NOTA
a) La función busq_sec_rec recibe como parámetros formales el arreglo, la posición del primer ele-
mento, el tamaño y el elemento a buscar. En esta función el recorrido del arreglo se inicia en la posición
0.
b) La función escalar recibe dos arreglos de igual dimensión y la posición del último elemento de los
arreglos. En este caso el recorrido se realiza desde la ú1tima componente del arreglo hasta la primera
componente (posición 0), por eso no hace falta enviar como parámetro la posición del primer elemento.
ACTIVIDAD 6
1. Construya una función recursiva que invierta los elementos de un arreglo.
2. Construya la función recursiva búsqueda binaria
ACTIVIDAD 7
Realice el seguimiento del siguiente programa cuando se ejecuta la función muestra, y el mapa de me-
moria correspondiente.
void muestra(int arr[], int inf, int sup)
{ if (inf <=sup)
{ muestra(arr, inf+1, sup);
printf("\n %d", arr[inf]);
}
else return;
}
int main(void)
{ int arre[5]={2, 3, 6, 7, 10};
muestra(arre, 0,4);
getch();
}
23 10 20 76 67 45 50
45 50 76
Vacío 20 23
Vacío 50
Vacío 23
Finalmente, el arreglo ordenado es (10 .20 .23 .34 .45 .50 .67 .76)
Ejemplo : El siguiente ejemplo corresponde al método de ordenamiento rápido Quicksort, versión re-
cursiva, para un arreglo de 8 componentes.
#define long 8
void carga(int arr[long],int i)
{
if (i<long)
{
printf("\n Ingrese %dº numero: ",i+1);
scanf("%d",&arr[i]);
carga(arr,i+1);
}
}//fin de la funcion cargar
int main ()
{ int i;
int a[long];
carga(a,0);
quicksort(a,0,long-1);
for(i=0; i<long;i++)
printf(" a[%d]= %d ",i,a[i]);
}
ACTIVIDAD 8
Realice el seguimiento del programa anterior para el arreglo que tiene los siguientes componentes:
50 23 45 34 67 10 76
20
Práctica Unidad 4
Recursividad
Ejercicio 1
a). Realizar el programa principal y la invocación de las siguientes funciones de tal manera que muestre
en el main el resultado en caso de que corresponda.
b). Realizar el Mapa de Memoria de las siguientes funciones.
c). Indicar que hace cada una de ellas.
d). La definición recursiva de la multiplicación de dos números a y b (es decir el número a multiplicado
b veces), se deriva de la definición de la multiplicación como una suma abreviada y la aplicación de la
propiedad asociativa de la suma, es decir:
a*b = a + a + (b veces…) + a
Se tiene que a*b= a+ a*(b-1)
Realizar el seguimiento del siguiente algoritmo para Lote de prueba n=2 y b=3
main()
{
int n, b;
scanf ("%d", &b);
scanf ("%d", &n);
printf ("\n %d * %d=", n, b);
producto (n, b);
getchar ();
}
Ejercicio 2
Si quieres conseguir un buen trabajo vas a necesitar buenas habilidades. Uno de los perfiles profesiona-
les más demandados son los programadores, pero ¿qué lenguaje de programación merece la pena apren-
der?. Aprender a programar te abrirá puertas a otros empleos. Son muchas las empresas las que valoran
esta habilidad, pese a que no sea necesario para el puesto, por la agilidad mental que denota. Por todo
ello, la comunidad de desarrolladores Stack Overflow llevó a cabo encuestas sobre las tendencias del
sector, sobre cuál de los siguientes lenguajes utilizan. Por cada encuestado se ingresa uno de los siguien-
tes nombres:
1. Javascript: A pesar de tener nombres similares, Javascript no está relacionado con Java. Permite a
los desarrolladores crear elementos interactivos en los sitios web, convirtiéndolo en uno de los len-
guajes más omnipresentes de la web y el más popular del mundo.
2. HTML: Aunque técnicamente no es un lenguaje de programación - es un "lenguaje de marcas" -
HTML es la base para la estructura de cada sitio web.
3. Cascading Style Sheets, o CSS: Es el lenguaje de programación más utilizado para diseñar sitios
web y aplicaciones basadas en navegadores.
4. Java: Fue inventado originalmente por Sun Microsystems en 1991 como lenguaje de programación
para sistemas de televisión interactiva. Desde la compra de Sun, Oracle ha convertido a Java en una
potencia. El lenguaje de programación es la forma más común de construir aplicaciones en Android.
5. Python: Python data de 1989 y es amado por sus fans por su código altamente legible. Muchos pro-
gramadores creen que es el lenguaje más fácil de usar.
6. C: Es uno de los lenguajes de programación más antiguos aún en uso común, fue creado a princi-
pios de la década de los 70. En 1978, el legendario manual del lenguaje, "The C Programming Lan-
guage", fue publicado por primera vez.
Ejercicio 3
Escribir la siguiente función iterativa en forma recursiva, para ello
int mcd (int a, int b)
{ int r;
if (a>b)
{ while(b>0)
{ r=b;
b=a%b;
a=r;
}//fin while
return a;
}//fin if
else return 0;
}//fin mcd
Ejercicio 4
Construir un programa en lenguaje C que a través de funciones recursivas resuelva los siguientes ítems:
a) Cargar un arreglo de enteros, de N componentes.
b) Generar un subarreglo con las componentes del arreglo cargado, cuyo valor es mayor al Promedio.
c) Indicar cuantas componentes del subarreglo mayores al promedio y cuantas menores a éste.
d) Ingresar un número y decir si se encuentra en el subarreglo.
e) Realice el ítem anterior si el arreglo original estuviera ordenado ascendentemente.
Ejercicio 5
Realice una función que busque el mayor valor de un arreglo, de modo tal que al llegar al caso base ya
haya encontrado este valor; y en la etapa de volver al punto de invocación vaya mostrando los valores
iguales al mayor.
Ejercicio 6
El Ministerio de Producción de la Nación ha lanzado un Plan de Promoción de Capacitación de Em-
pleados (PPCE) para las PYMES (Pequeñas y Medianas Empresas). La siguiente tabla detalla los mon-
tos financiados para el año 2018 (expresados en millones de pesos) según las distintas categor-
ías/sectores, lo que permite clasificar cada una de las empresas.
Sector
Categoría Agropecuario Industria y Comercio Servicios Construcción
Minería
Micro $2 $7,5 $9 $2,5 $3,5
Pequeña $13 $45,5 $55 $15 $22,5
Mediana $100 $360 $450 $125 $180
a) Indicar el monto total financiado para una categoría ingresada por teclado.
b) Indicar el monto total financiado para el sector de Servicios, sin importar la categoría de la empresa.
c) Emitir un listado con el total financiado, sin importar la categoría/ sector.
d) Emitir un listado con los montos superiores a uno ingresado por teclado, y a continuación los infe-
riores e iguales, indicando sector y categoría.
Ejercicio 7
Dadas dos matrices cuadradas A y B de componentes enteras positivas y de dimensión N, realizar un
algoritmo en C que utilizando funciones recursivas permita:
a) Cargar cada una de las matrices (función reusable).
b) Calcular el producto escalar de una fila de A por una fila de B. Ingresar por teclado el número de
cada fila.
c) Calcular el producto escalar de una columna de A por una columna de B. Ingresar por teclado el
número de las columnas.
Nota: validar el ingreso de datos.
Ejercicio 8
La cadena de comidas rápidas Mostaza necesita información estratégica que permita apoyar la toma de
decisiones en relación a las ventas realizadas en cada una de las 4 sucursales que dispone en la ciudad
Autónoma de Buenos Aires. De cada sucursal se conoce: número, nombre, dirección y teléfono. En
cuanto a los 10 productos distintos que comercializa, se conoce: código de producto, nombre, calorías y
precio. Por cada venta realizada, se ingresa el número de sucursal, código de producto y cantidad. Esta
información no tiene ningún orden en particular, y el ingreso termina con número de sucursal 0. Concre-
tamente necesita conocer:
a) Cantidad de productos vendidos por sucursal.
b) Importe total de productos vendidos por sucursal.
c) Obtener la sucursal (nombre) y el producto (nombre y precio), que registró el mayor importe de
venta.
d) Dado un número de sucursal, indicar el producto (todos los datos) que registró el mayor consumo de
calorías (suponer único).
e) Dado un número de producto, indicar la sucursal (nombre y teléfono) donde se registró el menor
importe vendido.
NOTA: Es importante optimizar el código, por lo tanto cuando deba trabajar sobre una fila de la tabla,
pasar sólo la fila. Realice un menú de opciones y validar los datos de entrada.
Menu
Ventas,
Ventas,
Sucursal
Tabla Tabla Sucursal Tabla Tabla
Productos
Sucursal Sucursal Productos Sucursal Producto
Introducción
Hasta ahora los tipos de datos simples y estructurados con que se ha trabajado son de almacenamiento
fijo o estático. Esto significa que el sistema se encarga de asignar a cada variable el área que ocupará en
tiempo de compilación, la que permanecerá fija durante toda la ejecución de la función que la declara.
No se podrá utilizar por tanto esas posiciones de memoria para otros fines, durante dicha ejecución. En
el caso de variables globales, el almacenamiento se mantiene fijo durante toda la ejecución del programa
y se usa para ellas la zona de memoria que llamamos de Almacenamiento Estático. Para las variables
locales, el almacenamiento que se les asigna corresponde a la Pila (Stack).
Existen situaciones en las que es más apropiado utilizar variables o estructuras que puedan crearse en
tiempo de ejecución, de modo que se permita liberar su espacio de almacenamiento cuando no se las
necesite así como modificar su tamaño durante la ejecución del programa. A este tipo de variables y
estructuras se las denomina dinámicas.
El uso de punteros o apuntadores permite el manejo de objetos de datos dinámicos.
La diferencia entre objetos de datos creados en tiempo de compilación y los creados en tiempo de ejecu-
ción es que:
El objeto creado en tiempo de ejecución no necesita tener nombre
Estos objetos se pueden crear en cualquier punto durante la ejecución de un programa, no sólo al
entrar al subprograma.
Se puede sintetizar diciendo que una variable dinámica se crea y se libera por petición, durante la ejecu-
ción del programa y no en forma automática como las de almacenamiento estático.
El Montículo o Montón(Heap)
Cuando el tamaño de un objeto a colocar en memoria puede variar en tiempo de ejecución, no es posible
su ubicación en memoria estática, ni en la pila. Son ejemplos de este tipo de objetos los arreglos dinámi-
cos, las listas enlazadas, las cadenas de caracteres de longitud variable, etc. Para manejar este tipo de
objetos el compilador debe disponer de un área de memoria de tamaño variable y que no se vea afectada
por la activación o desactivación de subprogramas. Este trozo de memoria se llama montículo o
montón (traducción de heap).
Las operaciones básicas que se realizan sobre el montículo son:
Alojamiento: Se demanda un bloque de memoria para almacenar un objeto de un cierto tamaño.
Desalojo: Se indica que ya no es necesario conservar un objeto de dato, la memoria que ocupa debe
quedar libre para ser reutilizada en caso necesario por otros objetos.
Según sea el programador o el propio sistema el que las invoque, estas operaciones pueden ser explícitas
o implícitas respectivamente. En caso de alojamiento explícito el programador incluye en el código
fuente una instrucción que demanda una cierta cantidad de memoria para la ubicación de un dato (en C
mediante malloc, en PASCAL mediante la instrucción new, etc.).
La cantidad de memoria requerida puede ser calculada por el compilador en función del tipo de dato
correspondiente al objeto que se desea alojar, o puede ser especificado directamente por el programador.
El resultado de la función de alojamiento es por lo general un puntero a un trozo contiguo de memoria
dentro del montículo que puede usarse para almacenar el valor del objeto. Los lenguajes de programa-
ción imperativos utilizan por lo general alojamiento y desalojo explícitos.
La gestión del montículo requiere técnicas adecuadas que optimicen el espacio que se ocupa o el tiempo
de acceso, o bien ambos factores.
Como los apuntadores permiten definir objetos de datos de tamaño variables, el lenguaje debe poseer las
siguientes características:
1. Un tipo elemental de datos apuntador, también llamado tipo de referencia o de acceso (un tipo pun-
tero).
2. Una operación de creación de objetos de tamaño fijo. La operación devuelve el valor L del bloque
de almacenamiento creado y ese valor L se convierte en el valor R de una variable del tipo apunta-
dor.
3. Una operación de desreferenciar para objetos del tipo apuntador, la cual permite acceder al valor del
objeto al cual apunta.
El lenguaje C provee funciones que permiten el manejo de memoria de forma dinámica. La función
malloc (Memory ALLOCation: asignación de memoria) permite asignar espacio en tiempo de
ejecución y la función free permite liberarlo. Los prototipos de estas funciones se encuentran en el
archivo de cabecera <alloc.h> para el caso de C++ y <malloc.h> para el caso de DEV C++.
La función malloc solicita al sistema operativo un bloque de memoria en el montículo, del tama-
ño especificado en el argumento. Su resultado es la dirección de comienzo del bloque asignado o
la constante NULL, si no hay espacio disponible.
Por ejemplo si se debe solicitar espacio para almacenar 10 componentes enteras, la función malloc es:
int *p;
p= (int *) malloc(10* sizeof(int) )
malloc devuelve la dirección de comienzo del bloque en un puntero a void, por ese motivo se debe hacer
una conversión explicita de tipo (cast) para convertirlo en un puntero a entero.
El Sistema Operativo maneja una tabla de direcciones de memoria que indica el espacio de memoria
ocupado (para que un programa no modifique direcciones de memoria que no le corresponden, corrom-
piendo la memoria de otros procesos / programas /información).
malloc indica que se va a cargar en memoria una estructura y por ello el sistema operativo registra en su
tabla de direcciones de memoria que un bloque ha sido ocupado, y envía a malloc la dirección de inicio
de ese bloque.
La función free, libera bloques asignados previamente por malloc. De esta manera existe más espacio de
memoria disponible para posteriores asignaciones.
Por ejemplo si se desea liberar el espacio antes solicitado, la función free es:
free(p);
free indica que el bloque de memoria asignado a través de mallloc se ha dejado de usar, con esta infor-
mación el Sistema Operativo registra en su tabla de direcciones de memoria que dicho bloque ha sido
desocupado. La variable p conserva el valor que tenía antes de free, es decir no cambia su contenido, por
este motivo es que para evitar errores de acceso, resulta ser una buena práctica inicializar la variable
después de su liberación ( p = NULL;).
12T Montículo-
1010
El lenguaje C utiliza para manejo de funciones y recursividad una gestión de almacenamiento basada en
pilas. Cuando se agrega punteros con la operación malloc, es necesaria una ampliación considerable de
la estructura global de la gestión de almacenamiento.
Para objetos de tamaño fijo, el tiempo de vida se inicia con la asignación de una localidad de almacena-
miento para el objeto (enlace del objeto al bloque de almacenamiento) y termina cuando se elimina el
enlace del objeto al bloque de almacenamiento. Cuando se crea el objeto, es decir al inicio de su tiempo
Programación Procedural 159
Lenguaje C: Variables dinámicas simples
de vida, se crea una ruta de acceso al objeto, para que las operaciones del programa puedan tener acceso
al objeto de datos. La ruta de acceso se logra asociando al objeto un identificador o nombre o bien a
través de un puntero al objeto el cual se guarda en otro objeto conocido y de fácil acceso.
Puede haber varias rutas de acceso adicionales, por ejemplo cuando se pasa un objeto como argumento
en un subprograma o cuando se crean varios punteros al objeto. Por lo tanto, existen varios problemas
que se originan por la acción conjunta del tiempo de vida y las rutas de acceso a una estructura de datos,
a saber:
Ejemplo 2
void modifica(int *&p)
{
int *x;
x=(int*)malloc(sizeof(int));
*x=25;
p=x;
free(x);
}
void main(void)
{
int * n;
modifica (n);
:
}
El uso de p después de ejecutar la función modifica puede acarrear problema al apuntar a una dirección
de memoria que ha sido liberada con x.
Con la definición convencional int b[10], el sistema reserva un bloque fijo y consecutivo de diez celdas
de memoria, que permanece durante toda la ejecución de la función en la que el arreglo está declarado.
Por esto también se dice que el almacenamiento es estático.
Gráficamente:
Si se analiza la segunda declaración int *b, surge una primera pregunta ¿es posible, a partir de un punte-
ro a entero, reservar espacio para almacenar diez números?
Como se ha visto, la función malloc solicita al sistema operativo, en tiempo de ejecución, la asignación
de un bloque de memoria en el montículo del tamaño especificado en su argumento.
En el ejemplo considerado, se debe solicitar espacio para almacenar 10 componentes enteras, de la si-
guiente manera:
b= (int *) malloc(10 * sizeof(int));
El puntero b recibe la dirección de comienzo del bloque o la constante NULL si no hay espacio disponi-
ble.
Este modo de creación de arreglos tiene dos momentos:
Declaración de la variable de acceso (nombre del arreglo): int *b;
Asignación dinámica de memoria:b = (int *) malloc (10 * sizeof(int));
La siguiente, es una síntesis de las diferencias entre un arreglo estático y un arreglo dinámico, en cuanto
a la declaración, manipulación y almacenamiento.
Como se observa en la figura 2, el arreglo estático se genera en la pila, y está disponible durante toda la
ejecución de la función que lo declara. El arreglo dinámico se genera en el montículo, por lo tanto su
almacenamiento puede liberase cuando se considere adecuado.
.............. Pila
1110
1010 b) P
P x i
R. carga x R. Carga
i 1011
1010x
1010 l x
x x a
MontículoO- ..............
1011
Ejemplo 3
El Instituto Provincial de la Vivienda ha implementado un sistema que consta de 5 planes de pago dis-
tintos, con el fin de que los adjudicatarios de sus viviendas puedan cancelar sus deudas. Por cada uno de
los 5 planes, se ingresa en forma ordenada la cantidad de adjudicatarios adheridos y por cada uno de
ellos el monto adeudado.
Se necesita realizar un programa, que utilizando de manera óptima funciones, informe:
Para cada plan:
Monto total adeudado por los adjudicatarios adheridos al mismo.
Monto promedio adeudado.
Cantidad de usuarios cuyo monto adeudado es superior al promedio calculado.
El o los números de planes con mayor cantidad de adjudicatarios
Programación Procedural 162
Lenguaje C: Arreglos dinámicos
Un análisis de la situación propuesta, plantea la necesidad de generar las siguientes estructuras: un arre-
glo manejado en forma dinámica para cada uno de los planes, que almacene la deuda de los usuarios
adheridos al mismo y un arreglo estático de 5 componentes que contenga la cantidad de usuarios adheri-
dos a cada plan.
void main(void)
{ int i,cu,totu[5];
float *monto, prom;
for(i=0; i<N;i++)
{ printf("\n Ingrese cantidad de adjudicatarios del plan %3d ",i+1 );
scanf("%d",&cu);
totu[i]=cu;
monto = (float *) malloc(cu * sizeof(float)); /* solicita espacio para almacenar la deuda
carga(monto, cu); de los adjudicat. de un plan*/
prom=total(monto, cu);
printf("\n Promedio adeudado por Adjudicatarios del plan %3d es %5.2f ", i+1, prom );
printf("\n Total adjudic. con monto superior a promedio %4d",cantidad(monto, cu,prom));
free(monto);// libera el espacio asignado a un plan, cuando no se necesita
}
maximo(totu,5);
}
La figura 3 muestra el manejo de memoria cuando se invoca la función carga. Suponiendo que el programa prin-
cipal ejecuta sólo el primer plan y que dicho plan tiene 8 adjudicatarios.
i cu totu[0] ...
totu[4]
0 8 8
Registro de
activación del main prom monto
1100 Pila
a
Registro de activación
1100 8 7
de la función carga t xcu i
[0] …………………….………………..[7]
( Montículo
1100
ACTIVIDAD 1
Codificar nuevamente la función carga, suponiendo que en ella se solicita el espacio de memoria para
almacenar el arreglo monto.
1. ¿Qué cambios realizó en la función?
2. Muestre la organización de la memoria cuando se ejecuta la función carga
ACTIVIDAD 2
Defina la estructura de datos adecuada que le permita resolver la siguiente situación problemática:
El Instituto Provincial de la Vivienda ha implementado un sistema que consta de 5 planes de pago dis-
tintos, con el fin de que los adjudicatarios de sus viviendas puedan cancelar sus deudas. Por cada uno de
los 5 planes, se ingresa en forma ordenada la cantidad de adjudicatarios adheridos y por cada uno de
ellos el DNI y monto adeudado. Se pide:
1. Realice la carga de la información
2. Para un adjudicatario cuyo DNI se ingresa por teclado, indicar el número de plan al cual se adhirió
y el monto adeudado.
3. Muestre el esquema de memoria, después de ejecutar la función que carga los datos
ACTIVIDAD 3
Se ingresa el registro y la nota obtenida (valor entero entre 1 y 10) por los alumnos que rindieron en la
última mesa de examen de la materia Programación Procedural. Escribir un programa en C que permita:
Definir una función carga que almacene toda la información de la mesa de examen. Por teclado se
ingresa la cantidad de alumnos que rindieron el examen.
Mostrar la nota promedio y el número de registro de quienes obtuvieron en el examen una nota ma-
yor o igual al promedio
Construir una función que usando la menor y la mayor nota obtenidas, indique la cantidad de
alumnos que obtuvieron cada nota entera comprendida en ese rango.
Realizar el mapa de memoria después de ejecutar la función carga.
H O L A \0
0 1 .............................. 9
Del mismo modo que a partir de un puntero a un entero, es posible generar un arreglo dinámico de ente-
ros; a partir de un puntero a un carácter puede generase un arreglo dinámico de caracteres.
char *nombre; declara a nombre como un puntero a un carácter. El espacio para almacenar la
cadena se asigna dinámicamente en tiempo de ejecución, según el tamaño de la misma, evi-
tando de ese modo el sobredimensionamiento de la memoria.
El siguiente ejemplo, muestra la carga de una cadena de caracteres como un arreglo dinámi-
co.
void main(void)
{
char *nombre, *aux;
int l;
aux=(char *) malloc( 30* sizeof(char)); // solicita memoria para la variable aux, con un tamaño
suficientemente grande
puts("Ingrese Nombre:\n");
gets(aux);
l = strlen(aux)+1;
nombre=(char *) malloc( l* sizeof(char)); //solicita memoria necesaria para almacenar la variable
Programación Procedural 165
Lenguaje C: Arreglos dinámicos
Si bien para el almacenamiento de una cadena de caracteres dinámica no pareciera conveniente la utili-
zación de una variable auxiliar, su necesidad es más clara para el caso del almacenamiento de varias
cadenas de distinta longitud, como se verá a continuación.
0 1 2 3 ……… 29
partido[0] R a d i c a l \0
partido[1] F P V \0 \0
partido[2] P r o \0
….
partido[20] S o c i a l i s t a \0
En este caso, se ha considerado que el nombre de mayor longitud no alcanza los 30 caracteres.
La declaración de este arreglo se puede hacer de dos maneras:
a) char partido[21][30];
Cada uno de los 21 punteros contendrá la dirección del primer carácter de una cadena. Esto permite, a
diferencia de la declaración anterior, que cada cadena ocupe solo la memoria requerida.
partido R A D I C A L \0
partido[0]
partido [1]
F P V \0
partido [2]
.
P R O \0
.
.
partido [20]
S O C I A L I S T A \0
Ejemplo 4
El siguiente programa muestra una forma de leer y escribir, un conjunto de cadenas de caracteres de
distinta longitud.
#include<stdio.h>
#include<malloc.h>
#include<string.h>
int main(void)
{
char* nomb[21];
carga(nomb,21);
muestra(nomb,21);
}
ACTIVIDAD 4
Suponga que cuenta con los nombres y edad de 50 empleados de una empresa y desea:
a - Mostrar el nombre del empleado de mayor edad.
b - Dado un carácter, decir la cantidad de veces que aparece en los nombres de los empleados
Completar el siguiente programa con las funciones que le permitan resolver la problemática planteada.
#include <stdio.h>
#include <alloc.h>
#include <string.h>
typedef struct
{ char *nom;
int edad;
} empleados;
…. carga (…….)
.. maximo(…)
int main()
{
char e;
empleados *a;
int n = 50;
carga(a,n);
printf("\ mayor edad %s ", maximo(a,n));
printf(" \n ingrese un caracter %c",e);
e = getchar();
mostrar(a,n,e);
muestra(a,n);
libera(a,n);
muestra(a,n);
}
Indicar si es necesario colocar la sentencia x[i].nom= NULL en la función libera.
Listas
Una lista es una estructura de datos compuesta por una serie de objetos de datos vinculados entre sí,
donde se almacenan datos del mismo tipo, con la característica que puede contener un número indeter-
minado de elementos y que, mantienen un orden explícito (es decir que bajo un criterio se organizan sus
posiciones en la estructura).
Las listas son estructuras de datos dinámicos, por tanto, pueden cambiar de tamaño durante la ejecución
del programa, aumentando o disminuyendo el número de nodos.
Definiremos como lista a un conjunto de elementos del mismo tipo, que puede crecer o contraerse sin
restricciones, todos los elementos son accesibles y se puede insertar y suprimir un elemento en cualquier
posición de la misma.
Implementación de listas
Para implementar listas podemos utilizar una estructura de arreglo o bien utilizando variables del tipo apuntador.
Insertar un elemento en la mitad de la lista, obliga al desplazamiento de una posición a todos los elemen-
tos que siguen al nuevo, para concederle espacio. De la misma manera, la eliminación de un elemento,
excepto el último, requiere desplazamientos para llenar el vacío formado.
La mayor desventaja de las listas secuenciales es tener que asignar suficiente espacio de memoria, de
manera de poder almacenar todos los elementos de la lista cuya cantidad no se conoce a priori. Este es-
pacio permanece asignado, aún cuando la lista use una cantidad menor o incluso ninguno.
Además, redimensionar la lista cambiando el tamaño del arreglo cuando está completo, resulta ser una
tarea lenta y tediosa (crear otro arreglo y copiar). Por otro lado, el no asignar más memoria introduce la
posibilidad de desborde.
Este tipo de implementación tiene sus ventajas y desventajas, dependiendo de las operaciones que se
quieran realizar sobre las listas.
Esta implementación se verá en años superiores al igual que otras implementaciones.
Datos Puntero
Es un tipo de dato autoreferenciado porque contienen un puntero o enlace a otro dato del mismo tipo.
Las listas pueden concatenarse entre sí o dividirse en sublistas.
NOTA
En adelante se referirá a lista enlazada simplemente como lista, y a los elementos de la lista como nodos.
Ejemplo 5
a) A continuación se muestra gráficamente como se representa la siguiente lista de números enteros, por
medio de un arreglo de longitud N.
3 4 -8 9 15 23
a
3 4 -8 9 15 23
0 1 2 3 4 5 N -1
Fig. 5 Lista como arreglo de enteros
En este ejemplo, hemos supuesto que el número máximo de componentes a almacenar es N. Si la canti-
dad de componentes es superior, entonces para evitar el desborde de memoria, el arreglo debería redi-
mensionarse.
b) Punteros: A continuación se muestra gráficamente una forma de representar una lista de números
enteros por medio de punteros.
3 4 -8 9 15 23
3 -8 15
23 NULL
4 9
cabeza
Se puede observar que en cada nodo, el campo enlace apunta al siguiente nodo de la lista excepto el
último nodo que contiene NULL, para indicar el final de la lista. Como anteriormente se explicó, este
valor de apuntador NULL no hace referencia a un lugar de memoria y se puede asignar a cualquier va-
riable apuntador. Como puede inferirse, es ilegal una referencia al contenido de una variable puntero que
tiene asignado NULL.
Existe la necesidad de un puntero que contenga en todo momento la dirección del primer elemento de la
lista, denominado cabeza de la lista. Resguardar la cabeza de la lista garantiza el acceso a la misma.
Analizando la definición del registro nodo, se puede observar que es recursiva ya que uno de sus com-
ponentes (campo sig) es una referencia a si misma, es decir, una referencia al mismo registro.
De este modo, cada nodo se usa para construir la lista de datos, y cada uno mantendrá la relación con el
siguiente. Además, para acceder a un nodo de la estructura sólo se necesita el puntero a ese nodo.
Creación
La creación consiste en definir la lista vacía, utilizando la constante NULL.
Ejemplo 6
Supongamos creada una lista de números enteros (vacía), insertar una componente en la lista.
#include <alloc.h>
struct nodo
{
int nro;
struct nodo *sig;
};
typedef struct nodo * puntero;
Tanto en la función crear como insertar, el parámetro formal xp es una referencia al parámetro actual
cabeza, por lo tanto todo cambio producido en xp afecta a cabeza. De ahí, la variable cabeza guarda la
dirección de la primera componente de la lista.
Ejemplo 7
Supongamos creada una lista de números enteros (vacía), insertar varias componentes. El ingreso de
componentes finaliza con cero.
#include <alloc.h>
struct nodo
{ int nro;
struct nodo *sig;
};
typedef struct nodo *puntero;
void main(void)
{
puntero cabeza;
crear(cabeza);
insertar (cabeza);
}
La lista así creada tiene los elementos en un orden inverso a la forma en que se han introducido.
Por ejemplo, para el lote: 3, 4, -8, 9, 15, 23, 0, la lista queda almacenada de la siguiente forma.
23 9 4
15 -8 3 NULL
cabeza
ACTIVIDAD 5
Definir una función que permita crear la lista anterior, pero con sus componentes en el mismo orden en
el que ingresan.
Gestión de almacenamiento
Si en un programa se declaran variables de tipo puntero, cuyo espacio de almacenamiento se asigna
dinámicamente a través de la sentencia malloc, el sistema debe controlar no sólo el almacenamiento
estático y dinámico basado en pila, sino también el almacenamiento dinámico basado en el montículo.
Para mostrar la gestión del almacenamiento, considérese el siguiente programa que genera una lista de
números enteros, para el lote de prueba anterior.
Para interpretar el estado de la memoria durante la ejecución del programa, consideraremos dos situa-
ciones:
Segmentos de Códigos de
las funciones main, crear
e insertar
1001h
1002h
…….
…….. Montículo
……..
1049h
1050h
Como puede observarse el montículo está vacío, pues aún no se inició la generación de la lista. El valor
NULL de cabeza, en el registro de activación del main, indica que la lista aún está vacía.
2) Una vez que insertar(cabeza) es invocado para generar la lista, el estado de la memoria justo antes
de la sentencia return, es decir antes de transferir el control al programa principal, es el siguiente:
Almacenamiento estático:
Segmentos de Códigos de
las funciones main, crear,
insertar
cabeza xp
Registro de activación
1001h del función main
dato
23
Registro de activación de
nuevo la función insertar
1001h
Nuevamente se observa que la función insertar recibe por parámetro una referencia de la variable cabe-
za, es decir que xp no es otra cosa que un alias del lugar de memoria referenciado por cabeza.
1012h 9 1032h
…….
1006h 4 1050h
1007h Montículo
1032h -8 1006h
1040h 15 1012h
1050h 3 NULL
Recorrido de la lista
Para mostrar los elementos de una lista, se la debe recorrer a través de un puntero vaya apuntado sucesi-
vamente a cada uno de los elementos, comenzando desde la cabeza, y mostrando entonces el campo de
datos de cada elemento. Este proceso termina cuando se llega al final de la lista.
En general, si p apunta a un nodo, para avanzar al próximo se debe realizar la siguiente asignación:
p = p->sig;
Ejemplo 8
La siguiente función permite recorrer una lista como la generada en el ejemplo anterior.
void recorre (puntero xp)
{ printf("\n Listado de datos ");
while (xp != NULL)
{
printf("\n %d ",xp->nro);
xp = xp->sig;
}
}
Cuando la función “recorre” procese la última componente de la lista, la asignación p=p->sig; guardará
en p el valor NULL y la iteración terminará.
NOTA
Se puede observar que los elementos de la lista se muestran en el orden inverso al que ingresaron.
ACTIVIDAD 6
Analizar la función recorre y explicar qué resultados obtiene si el parámetro formal xp se pasa como
una referencia.
void main(void)
{
puntero cabeza;
int nn;
crear(cabeza);
insertar (cabeza);
printf (“\n Ingrese el número a buscar: “);
scanf (”%d”, &nn);
busca (cabeza, nn);
}
La función busca define el parámetro xp por valor, para preservar el valor original de la variable que
contiene la dirección del primer nodo de la lista.
Ejemplo 10
En este algoritmo se busca en la lista un número leído previamente y, si se encuentra, se cambia por
otro.
#include <alloc.h>
struct nodo
{ int nro;
struct nodo *sig;
};
typedef struct nodo *puntero;
void main(void)
{
puntero cabeza;
int nn;
crear(cabeza);
insertar (cabeza);
modifica (cabeza);
}
ACTIVIDAD 7
En el ejemplo anterior, analizar el pasaje de parámetros de las funciones modifica. Extraer conclusio-
nes.
NOTA: Las funciones de búsqueda y modificación responden al algoritmo señalado para recorrer
la lista:
Inicio Bucle
Se procesa el nodo apuntado (si es el nodo buscado se informa o se modifica y se sale
del bucle)
Se avanza al siguiente nodo (si no se encontró el buscado)
Fin del Bucle
void main(void)
{
puntero cabeza;
crear(cabeza); // ídem a anterior
insertar (cabeza); // ídem a anterior
insertarAlFinal (cabeza);
}
23 9 4
15 -8 3 NULL
cabeza
12
ACTIVIDAD 8
Realizar un programa que mediante funciones permita:
- Generar una lista de números enteros positivos, ordenada en forma ascendente. Validar la entrada.
- Escribir un mensaje diciendo si el número del último nodo de la lista es par.
NOTA: el algoritmo de inserción en lista visto anteriormente, también responde a estas dos for-
mas:
Inicio Bucle
Pedido de memoria
Carga de datos en el nuevo nodo
Enlace (se busca la posición en la lista o se va al final si es por cola)
Fin del Bucle
23 9 4
15 -8 3 NULL
anterior
Fig. 9 Eliminación de la componente con el valor 9
Puede observarse, que para eliminar la componente, es necesario ubicar la dirección de ese nodo (p en el
gráfico) y guardar la dirección del nodo anterior. Luego se enlaza el nodo anterior al que se desea elimi-
nar con el siguiente al mismo. Esto es, el nodo que contiene el valor 15 debe apuntar al nodo que contie-
ne el valor –8. Una vez realizado el cambio de punteros, debe liberarse el espacio de memoria apuntado
por p mediante la función free.
ACTIVIDAD 9
Investigar las siguientes situaciones:
¿Qué sucede si el número a suprimir es el primero de la lista?
¿Qué sucede si el número a suprimir aparece repetido más de una vez
El siguiente código corresponde a la función que suprime una componente de una lista enlazada.
ACTIVIDAD 10
A continuación se presenta el código de una función que ordena en forma ascendente una lista de núme-
ros enteros.
Realizar el seguimiento de la función ordena tomando como lote de prueba, una lista de cinco elementos
y una lista vacía.
void ordena (puntero cab)
{
puntero k, cota, p;
int aux;
cota = NULL;
k = NULL;
while (k != cab)
{
k = cab;
p = cab;
while (p->sig != cota)
{
if (p->nro > p->sig->nro)
{
aux = p->sig->nro;
p->sig->nro = p->nro;
p->nro = aux;
k = p;
};
p = p->sig;
}
cota = k->sig;
}
}
co- NUL
L
Este proceso consiste en ir recorriendo la lista desde el comienzo, e ir eliminando de a una componente
por vez, a medida que se avance a lo largo de ella.
Ejemplo 12
El siguiente ejemplo muestra la función que elimina todas las componentes de una lista.
Ejemplo 13
Generar una lista con los datos de los empleados de una empresa: legajo y sueldo. El ingreso de infor-
mación termina con legajo cero(0).
Mostrar el legajo de los empleados almacenados en la lista, cuyo sueldo es superior a 1000.
#include <stdio.h>
#include <alloc.h>
typedef struct
{ int legajo;
float sueldo;
} empleado ;
struct nodo
{ empleado dato;
struct nodo * sig;
};
typedef struct nodo *puntero;
ACTIVIDAD 11
En el algoritmo anterior, agregar una función recursiva que para un número de legajo conocido, devuel-
va el sueldo. Si el empleado no está en la lista, la función devuelve 0.
Referencias Bamboleantes
Sea p un puntero a una variable dinámica, una llamada a free(p) hace ilegal una referencia posterior a
p->, aunque el valor de p queda intacto (ya que con free solo se modifica la Tabla de direcciones de
memoria del Sistema Operativo)
No obstante algunos compiladores no detectan la ilegalidad, por lo tanto a través de p se puede acceder
a los datos en esa dirección, provocando errores impredecibles, tales como la modificación del almace-
namiento asignado previamente a otra variable dinámica. El puntero p recibe el nombre de referencia
desactivada o bamboleante.
Por todo lo expresado, es una buena “regla de pulgar”, después de liberar el espacio ocupado por una
variable dinámica colocar en NULL el puntero asociado a ella.
En el ejemplo anterior, la función elimina, libera el espacio ocupado por todas las componentes de la
lista y por lo tanto se la recorre hasta que la cabeza de la lista (cab) tome el valor NULL.
Basura
Otra característica peligrosa asociada a los punteros y el uso incorrecto de la sentencia free, es la genera-
ción de basura. Se llama basura, al espacio de memoria que no puede ser accedido porque se han perdido
todas las referencias a él.
Por ejemplo, supongamos tener generada una lista en la que cabeza es la dirección de la primer compo-
nente de ésta. Si por alguna circunstancia se hiciera free(cabeza); se liberaría la memoria de la primera
componente de la lista, perdiéndose la dirección del segundo nodo y, por lo tanto, todo el resto de la
lista. Las celdas que siguen a la primera quedarán como basura, es decir, queda inutilizado todo el espa-
cio de memoria ocupado por ellas. Esto puede provocar que el sistema no pueda seguir operando por
falta de espacio cuando en realidad si lo tiene. En estos casos, el sistema invoca a una rutina llamada
colector de basura para recuperar el espacio perdido.
NOTA
La recolección de basura (en inglés Garbage Collection - GC) es un mecanismo que proporciona recupe-
ración de memoria automática para bloques de memoria no utilizados. El motor de GC se encarga de
reconocer que ya no se usa un bloque particular de memoria asignada y lo vuelve a poner en el área de
memoria libre. GC fue presentado por John McCarthy en 1958, como el mecanismo de gestión de me-
moria del lenguaje LISP. Desde entonces, los algoritmos GC han evolucionado. Varios idiomas se basan
nativamente en GC.
Lenguaje C no incluye en forma nativa recolección de basura. Existe la biblioteca GC Boehm-Demers-
Weiser (BDW), un paquete que permite a los programadores C y C ++ incluir administración automática
de memoria en sus programas. La biblioteca BDW es una biblioteca de acceso libre que proporciona
programas C y C ++ con capacidades de recolección de basura.
ACTIVIDAD 12
Analizar la siguiente función, realizar el seguimiento con el Lote de prueba: 3, 4, -8, 9, 15, 23, 0
#include <alloc.h>
void crear(puntero &xp)
{
xp=NULL;
}
Arreglo de Listas
Ejemplo 14
La Facultad de Ciencias Exactas organizó el Congreso de Informática y necesita contar con un programa
que le permita administrar la información relativa a los 10 tutoriales que se proponen en dicho evento.
Para ello, el programa deberá permitir, a través de un menú de opciones, los siguientes ítems:
Ingresar los datos correspondientes a cada tutorial, a saber: número de tutorial (1-10), título.
Registrar las inscripciones, ingresando el DNI del inscripto y el número de tutorial al que desea
asistir.
Eliminar alguna inscripción, en cuyo caso se ingresarán los mismos datos que en el ítem anterior.
Dado el número o el título de un tutorial, mostrar los datos específicos del mismo, incluido la canti-
dad inscriptos.
Dado el DNI de una persona, informar el/los tutoriales (número y título) en los que se inscribió.
NOTA
Para resolver este ejercicio, se presentan las siguientes definiciones de las estructuras de los datos a uti-
lizar:
struct nodo
{
int dni;
struct nodo * sig;
};
struct datos /* corresponde a cada tutorial */
{
int numero;
cadena titulo;
struct nodo * ins; /* puntero a una lista de inscriptos*/
};
typedef struct nodo * inscriptos;
void main(void)
{
int i, n, op;
long d;
datos tutorial[fila]; /* arreglo de tutoriales */
printf("\n ********** Cargar de los tutoriales C\n *********");
for (i=0;i<fila;i++)
{
tutorial[i].numero=1;
printf("\n Ingrese nombre del tutorial %d :",i+1);
gets(tutorial[i].titulo);
crear(tutorial[i]);
}
printf("\n ******** MENU DE OPCIONES ***********\n");
do
{
clrscr();
printf("\n 1- Realizar una inscripción: ");
printf("\n 2- Eliminar una inscripción: ");
printf("\n 3- Mostrar Datos de un tutorial: ");
printf("\n 4- Mostrar tutoriales en los cuales se inscribió una persona : ");
printf("\n 5- Mostrar tutoriales con Datos de Inscriptos : ");
printf("\n 6- Salir del MenúRealizar una inscripción: ");
Programación Procedural 187
Listas
scanf("%d", &op);
switch (op)
{ case 1: { printf("\n Ingrese numero del tutorial :");
scanf("%d", &n);
printf("\n Ingrese DNI :");
scanf("%d", &d);
insertar(tutorial[n-1].ins,d);
break;
}
case 2: /*** Completar ***/; break;
case 3: /*** Completar ***/; break;
case 4: /*** Completar ***/; break;
case 5: listar(tutorial) ;
}
} while (op !=6);
getche();
}
Bibliografía
Apuntes de Cátedra Programación Procedural
Aho Alfred V., John E. Hopcroft, Jefrey D. Ullman (1988) ”Estructura de datos y algoritmos publi-
cación". Addison-Wesley Iberoamericana: Sistemas Técnicos de Edición. México, DF.
Braunstein y Gioia (1987) ”Introducción a la Programación y a la Estructura de Datos” - Eudeba
Criado Clavero, Mª Asunción.(2006) “Programación en Lenguajes Estructurados”. Alfaomega. Ra-
Ma. México.
Deitel, H M y Deitel, P J (2003) “Como programar en C/C++” Pearson Educación –
Hispanoamericana
Joyanes Aguilar A y Zahonero Martínez (2001) “Programación en C. Metodología, estructuras de
datos y objetos”. Mc Graw - Hill.
Joyanes Aguilar Luis (2004) “Algoritmos y Estructuras de Datos. Una Perspectiva en C”. Segunda
Edición Mc Graw Hill.
Práctica Unidad 5
Estructuras Dinámicas
En los ejercicios 1 y 2, para la solución, hacer como mínimo 1 función recursiva, para el resto como
mínimo hacer 2.
Ejercicio 1
Escribir un programa dados 2 vectores de N componentes enteras permita:
a) Calcular el producto escalar (Realizar un subprograma de carga que sea reusable y que permita in-
gresar por teclado el tamaño de los arreglos).
b) Generar una nueva estructura con los valores impares contenidos en uno de los arreglos (realizar un
subprograma que solicite memoria para la nueva estructura y la devuelva cargada).
c) Realizar el mapa de memoria con el siguiente lote de prueba, específicamente al momento de la
carga de un vector, creación y carga de la nueva estructura.
vector a={1, 2, 3}
vector b={4, 5, 6},
Nota
El producto escalar es una operación donde al multiplicar dos vectores se obtiene un escalar.
Ā = (ax,ay), Ē = (ex,ey) => Ā • Ē = ax • ex + ay • ey
Ejercicio 2
La Federación Argentina de boxeo (FAB) mantiene información de los boxeadores federados: DNI,
categoría y el peso (47..90) en kilogramos. Las categorías están codificadas por letras: „A‟: Peso cruce-
ro, …., „ H‟: Peso minimosca. La cantidad de participantes se ingresa por teclado.
Escribir un programa en C que permita:
a) Cargar los datos en una estructura adecuada. (Validar el ingreso, suponiendo que código de categor-
ía varía entre „A‟ y „H„)
b) Para una categoría determinada, mostrar los DNI de los boxeadores que tienen el peso máximo.
Generar una estructura auxiliar.
c) Realizar un listado que muestre, para cada Peso cuantos participantes existen. Generar una estructu-
ra auxiliar.
Ejercicio 3
El Instituto Provincial de la Vivienda ha implementado un sistema que consta de 5 planes de pago dis-
tintos, con el fin de que los adjudicatarios de sus viviendas puedan cancelar sus deudas. Por cada uno de
los 5 planes, se ingresa en forma ordenada la cantidad de adjudicatarios adheridos y por cada uno de
ellos el DNI y monto adeudado.
Se pide:
a) Cargar en una estructura de datos adecuada la información que se posee.
b) Para un adjudicatario cuyo DNI se ingresa por teclado, indicar el número de plan al cual se adhirió y
el monto adeudado.
c) Mostrar el mapa de memoria, después de ejecutar la función que carga los datos.
Ejercicio 4
Suponiendo una lista en una estructura enlazada, no vacía, realizar el seguimiento empleando el mapa de
memoria para la inserción de 4 nodos. Hacer el mismo proceso en forma recursiva.
Ejercicio 5
Realizar un programa que mediante funciones recursivas permita:
a) Generar una lista enlazada de números enteros positivos, ordenada en forma ascendente. Validar la
entrada.
b) Escribir un mensaje indicando si el número del último nodo de la lista es par.
Ejercicio 6
El Proyecto Internacional de Código de Barras de la Vida (iBOL, del inglés International Barcode of
Life Project) tiene como objetivo la obtención de las “huellas genéticas” de las especies en peligro de
extinción. Para ello se registra toda la fauna y flora con el fin de constituir una base de datos global que
pueda ser consultada por la comunidad científica de todo el mundo. En particular se registrará informa-
ción de los países de Argentina, Brasil y Estados Unidos, conociendo de los mismos: país, continente y
especies. De cada especie en peligro de extinción se registra: nombre, nombre científico, reino (ani-
mal/fauna o vegetal/flora) y cantidad de ejemplares.
Realizar un programa en C que a través de funciones óptimas permita:
a) Generar un arreglo de lista a través de punteros con los datos de las especies en extinción para los
países indicados. El ingreso de información se encuentra ordenada por país. Para cada país el ingre-
so de información finaliza con el nombre de la especie igual a FIN.
b) Para un nombre de país ingresado por teclado, realizar una función que devuelva al programa prin-
cipal la cantidad de especies de la flora y cantidad de especies de la fauna en peligro de extinción.
Realizar una función recursiva que devuelva un dato por parámetro y el otro que lo calcule la fun-
ción.
c) Incrementar en 200 ejemplares la cantidad de la especie con nombre Petiribí (árbol) en Brasil.
d) Indicar en el programa principal cantidad de ejemplares de Petiribí (árbol presente en Argentina y
Brasil) considerar solamente los ejemplares de los países indicados.
Nota: Para los distintos países se registra una sola vez las distintas especies.
Ejercicio 7
La Facultad de Ciencias Exactas organizó el Congreso de Informática, y necesita administrar la infor-
mación relativa a los 10 tutoriales que se proponen en dicho evento.
Realizar un programa, que a través de un menú de opciones permita:
a) Ingresar los datos correspondientes a cada tutorial: número de tutorial (1-10) y título.
b) Registrar las inscripciones, ingresando el DNI del inscripto y el número de tutorial al que desea asis-
tir.
c) Eliminar alguna inscripción, en cuyo caso se ingresarán los mismos datos que en el ítem anterior.
d) Dado el número de un tutorial, mostrar su título y la cantidad de inscriptos en él.
e) Dado el DNI de una persona, informar el/los tutoriales (número y título) en los que se inscribió.
Nota: Para cada ítem emplear al menos una función recursiva.
Ejercicio 8
La UNSJ todos los años otorga becas, para lo cual se ingresa el número de facultades participantes, de
las misma se ingresan los nombres y de cada una las inscripciones de los alumnos aspirantes a las becas
de ayuda económica. Se ingresa, en forma ordenada por facultad, los datos de cada alumno: Nombre,
promedio y año que cursa.
Se pide, un programa que permita:
a) Realizar un listado ordenado por promedio, de los alumnos inscriptos en una determinada facultad
cuyo nombre se ingresa por teclado. (Mostrar nombre del alumno, promedio y año que cursa).
b) Indicar el nombre de la facultad que tiene menos inscriptos y la cantidad de inscriptos suponer úni-
co).
c) Mostar por cada facultad la cantidad de alumnos con promedio mayor o igual a 7, que cursan de
segundo año en adelante. Usar una función recursiva.
Ejercicio 9
La biblioteca de la facultad cuenta con una cantidad variable de libros de Programación Procedural en
calidad de préstamo en la sala de lectura que puede modificarse. Una vez prestados los libros, de los
cuales se registra el código, se confecciona para cada uno una lista de alumnos que están en cola de es-
pera. Por cada alumno se guarda: nombre y carrera (LSI, LCC)
Se pide realizar un programa, que a través de un menú de opciones y mediante el uso de funciones, de
respuesta a las siguientes situaciones:
a) Crear una lista de listas inicializadas en NULL. para almacenar la información de los libros
b) Para un código de libro solicitado, insertar un alumno a la cola de espera correspondiente. Usar una
función recursiva.
c) Ingresar un nuevo libro para que esté en calidad de préstamo en la biblioteca.
d) Suponiendo devuelto un libro cuyo código se lee, realizar un préstamo al primer alumno de la lista
correspondiente y actualizar la misma (Esto es eliminarlo de la lista)
e) Ingresar un código de libro y una carrera, mostrar los nombres de los alumnos de dicha carrera que
están en cola de espera.
Unidad 6: Archivos
Introducción
Hasta ahora los tipos de datos simples y estructurados con que se ha trabajado son de almacenamiento
interno.A menudo se necesita guardar datos en forma permanente. La memoria RAM, o memoria prin-
cipal de la computadora es volátil, por ello, toda información contenida en ella se pierde si se apaga la
computadora ya que necesita energía para mantener los datos.
Si se necesita almacenar los datos para que perduren mas de una sesion de trabajo, hay que recurrir al
almacenamiento secundario, que aunque más lento que la memoria principal, permite almacenarlos en
forma permanente.
Los lenguajes de programación proporcionan estructuras que permiten el almacenamiento perma-
nente de la información, en el caso de C se denominada FILE.
Esta estructura permite gestionar a través del sistema operativo, los medios necesarios para almacenar
información en dispositivos de almacenamiento secundario en forma de archivo de datos. Entre los dis-
positivos de almacenamiento, secundarios o auxiliares se puede citar por ejemplo disco rígido, discos
externos, pendrive, SD, CD, etc.
Los archivos son utilizados por empresas, reparticiones del gobierno, usuarios particulares, etc. ya que
en todos los casos deben resguardar información que debe ser accedida cuando se necesite. Los datos a
su vez deben conservar su integridad, es decir, lo que se guarda en el archivo no debe sufrir alteración,
por la importancia que ellos revisten. Como ejemplos del uso de archivos se cita:
Archivos médicos con historial de pacientes.
Archivos de legajos del personal de una institución
Archivos sismológicos que permiten guardar la actividad sísmica de una región.
Archivos que almacenan stock de mercaderías.
Archivos con notas de alumnos de una carrera.
Archivos de legislaciones provinciales y/o nacionales.
Definición
Un archivo es una estructura compuesta por datos almacenados en forma organizada en un dispositivo
de almacenamiento secundario. Para su correcta manipulación, esta estructura es identificada por un
nombre.
La transferencia de información entre la memoria RAM y el almacenamiento secundario se realiza a
través de una unidad llamada componente.
Organización de Archivos
El término organización hace referencia a la disposición de los componentes del archivo en el dispositi-
vo físico. Esta organización es determinada en el momento de su creación o utilización.
Organización Secuencial: Un archivo tiene organización secuencial, cuando las componentes que lo
conforman se emplazan en posiciones físicas contiguas en el soporte de almacenamiento.
Organización Directa: Un archivo tiene organización directa, cuando las componentes que lo confor-
man ocupan posiciones físicas relacionadas mediante una clave. Esta clave puede ser un dato de la com-
ponente o bien el resultado de un cálculo matemático.
La organización adecuada para un archivo en particular está determinada por las características opera-
cionales físicas del dispositivo de almacenamiento y por los requerimientos de la aplicación.
Clasificación de archivos
A los archivos se los puede clasificar según diferentes criterios:
Esta estructura se crea al momento en que es creado o abierto un archivo, el contenido es manipulado
por las funciones de archivo que proporciona la biblioteca stdio.h. La estructura se destruye cuando se
cierra el archivo.
En realidad, una variable de tipo FILE * representa un flujo de datos que se asocia con un dispositivo
físico de entrada/salida (el archivo “real” estará almacenado en disco).
Pila VarArchi
*FILE
Flujo
de
datos
Montículo Estructura Almacenamiento
FILE Dispositivo Físico
Buffer o Canal
Archivo
FILE *archi;
La definición de la estructura FILE depende del compilador, pero en general mantiene un campo con la
posición actual de lectura/escritura, un buffer para mejorar las prestaciones de acceso al archivo y algu-
nos campos para uso interno, al programador le interesa su uso.
Formato
FILE * fopen (char *nombre, char *modo);
Esta función es utilizada para abrir y/o crear archivos en el dispositivo secundario y retorna un puntero a
FILE.
La función fopen tiene dos parámetros que son cadenas de caracteres, el primero es el nombre del archi-
vo físico que contiene los datos y el segundo corresponde al modo de acceso a éste.
nombre: es una cadena de caracteres que contiene un nombre de archivo válido.
modo: especifica la forma en que se va utilizar el archivo, esto es si es de lectura, escritura o para agre-
gar información, y el tipo de valores permitidos a cada byte, de texto o binarios.
Modo Apertura
“r” Sólo lectura: el archivo debe existir
“a+” Añadir, lectura y/o escritura: el puntero se sitúa al fi nal del archivo. Si el
archivo no existe, es creado, además se dispone para lectura y escritura.
NOTA
Los tipos de archivos son de texto (modo t) o binario (modo b), si no se especifica el tipo, la opción por
defecto depende del compilador utilizado, pero en general es modo texto.
El valor que retorna la función fopen es un puntero a FILE si la operación de la función es exitosa, caso
contrario retorna NULL.
Un ejemplo es:
FILE *archi ; /*declaración de la variable puntero a FILE*/
archi = fopen(“Datos.dat”, “w”); /*apertura del archivo Datos para escritura*/
Cierre de un archivo
Es importante cerrar los archivos antes de abandonar una aplicación. Esto implica almacenar datos que
aún están en el buffer de memoria y actualizar aquellos datos de la cabecera del archivo que mantienen
el sistema operativo. Además permite que otros programas puedan abrir el archivo para su uso. A me-
nudo, los archivos no pueden ser compartidos por varios programas.
Función fclose
Formato
int fclose(FILE *archivo);
El parámetro de esta función es un puntero a la estructura FILE del archivo que se quiere cerrar, o
NULL si se quiere cerrar todos los archivos abiertos.
El valor de retorno cero indica que el archivo ha sido correctamente cerrado, caso contrario indicar-
ía que hubo un error.
Archivos de caracteres
Los archivos de caracteres son una secuencia de caracteres almacenados en un dispositivo de almace-
namiento secundario e identificado por un nombre de archivo.
Grabar información
Para grabar una componente en un archivo de caracteres se utiliza la función fputc. En este tipo de ar-
chivo la componente es un carácter
Los parámetros de entrada de esta función son el carácter a escribir, convertido a int y un puntero al
FILE del archivo en donde se realizará la escritura.
El valor de retorno: es el carácter convertido a int escrito, si la operación fue completada con éxito, caso
contrario será EOF.
NOTA: EOF es una constante simbólica entera, definida en el archivo de cabecera <stdio.h> que indica
si el final de archivo ha sido o no alcanzado. Toma el valor cero si no se ha llegado al final, y distinto de
cero si se ha llegado.
Ejemplo
#include <stdio.h>
#include <conio.h>
void main(void)
{ FILE *archi ; /*declaración de la variable tipo archivo*/
archi=fopen("archivo.dat", "w"); /*apertura del archivo para escritura*/
char c; /*declaración de la variable caracter*/
c ='Z'; /*asignación del carácter 'Z' a la variable*/
fputc(c,archi); /*escritura en el archivo */
}
Recuperar información
Para recuperar una componente de un archivo de caracteres se utiliza la función fgetc.
Parámetro: un puntero a una estructura FILE del archivo del que se hará la lectura.
El valor de retorno es el carácter leído como un unsigned char convertido a int. Si no hay carácter dis-
ponible, el valor de retorno es EOF.
Ejemplo
#include <stdio.h>
#include <conio.h>
void main(void)
{FILE *archi ; /*declaración de la variable tipo archivo*/
archi=fopen("archivo.dat", "r"); /*apertura del archivo para lectura*/
char c ; /*declaración de la variable caracter*/
c=fgetc(archi); /*lectura del caracter del archivo */
printf(" %c",c);
}
Ejemplo
El siguiente programa permite mostrar en la pantalla el código fuente del archivo ejer_escribe progra-
ma.cpp.
En este caso, se lee el archivo fuente como archivo de caracteres utilizando la función fgetc, y a medida
que se lee se genera un archivo de caracteres de salida en la pantalla utilizando la función fputc.
#include <stdio.h>
#include <conio.h>
void main(void)
{char c;
FILE *archivo; // declara la variable archivo de tipo file
archivo = fopen("ejer_escribe programa.cpp", "r"); // abre el archivo ejer_escribe programa.cpp
printf(" listado de mi programa fuente \n"); // y se posiciona al comienzo
while (!feof(archivo)) //itera el archivo hasta que llegue al final y envía
{ c=fgetc(archivo); // los Caracteres leídos a la salida standard
fputc(c, stdout); // stdout, es el archivo de salida
} // por defecto es el monitor
fclose(archivo);
}
Archivos de textos
Un archivo de texto contiene un conjunto de “líneas” de caracteres de longitud variable y están separa-
das por un salto de línea.
El salto de línea está formado por dos caracteres especiales: avance de línea (line feed - carácter ASCII
10) y retorno de carro (CR carriage return - carácter ASCII 13)25.
El final del archivo se indica mediante el carácter ASCII 26, que también se expresa como ^Z o EOF.
Grabar información
La función que se utiliza para guardar una componente en un archivo de textos es fputs. Esta función
permite grabar una componente (línea) en un archivo de cadenas caracteres.
Función fputs: Esta función graba una cadena de caracteres especificada como parámetro, es decir,
fputs copia la cadena terminada en null en el archivo; (No se añade el carácter de retorno de línea ni el
carácter nulo final).
Formato
int fputs(const char *s, FILE *archivo);
Ejemplo
El siguiente ejemplo genera un archivo de textos Mi_texto.txt. Para esto, se ingresa un texto por teclado y
se supone que cada línea ingresada no supera los 200 caracteres.
#include <stdio.h>
#include <string.h>
void main ()
{ char texto[200];
FILE *archi;
archi=fopen ("Mi_Texto.txt","w");
printf("Ingrese el texto a almacenar en el archivo\n");
if (archi!=NULL)
{
gets(texto);
while(strcmp(texto,"FIN"))
{ fputs (texto,archi);
gets(texto);
}
fclose(archi);
}
}
NOTA: Un archivo de texto se puede visualizar utilizando un procesador de texto como Word, bloc de
notas. etc.
25
En una máquina de escribir convencional, para pasar a la siguiente línea se necesita dos movimientos: uno para
desplazar el carro hacia la izquierda, y otro para mover el papel una línea hacia arriba. En los ordenadores se siguió
esta analogía y se definieron los dos caracteres: CR para el retorno de carro, y LF para el salto de línea. Entre los
dos (CRLF), se conseguía que una impresora hiciera estos movimientos para poder escribir una nueva línea de
texto
Programación Procedural 201
Archivos organizados secuencialmente
Recuperar información:
La función que se utiliza para recuperar una componente en un archivo de textos es: fgets permite recu-
perar una componente (línea) de un archivo de cadenas de caracteres.
Función fgets
Esta función lee caracteres desde un archivo y los coloca en un arreglo de caracteres (variable tipo cade-
na). La lectura de caracteres termina cuando la cantidad de caracteres leídos alcanzó el número de carac-
teres especificado, o bien cuando se lea un carácter salto de línea o bien cuando no hayan más caracteres
para leer, en este caso retorna NULL
Formato
char *fgets(char *s, int n, FILE * archivo);
Ejemplo
#include <stdio.h>
#include <string.h>
void main ()
{ char texto[200];
FILE *archi;
archi=fopen ("Mi_Texto.txt","r");
if (archi!=NULL)
{
printf("\n TEXTO INGRESADO \n ");
while(!feof(archi))
{
fgets(texto,200, archi);
puts(texto);
}
fclose(archi);
}
}
Ejemplo
Abrir el documento "fichero.txt" en modo lectura y mostrar su contenido en pantalla.
#include <stdio.h>
void lectura (FILE *fp)
{ char buffer[50];
fp=fopen("fichero.txt","r");
if (fp = = NULL)
printf ("Error de apertura de archivo");
else{
while (!feof(fp) )
{
fscanf(fp, "%s" ,buffer);
puts(buffer);
}
fclose(fp);
}
}
En el ejemplo se lee una cadena 50 caracteres y se muestra en pantalla, se puede en el mismo momento
del fscaf hacer un cast, para esto, antes se deben declarar variables de los tipos de datos que se quiere
obtener: modificando el ejemplo anterior donde en vez de mostrar en pantalla se cargue un arreglo de 40
componentes se obtiene el siguiente código:
#include <stdio.h>
typedef struct
{
int día;
char nombre[30];
float sueldo;
} datos;
fprintf
La función fprintf funciona igual que printf en cuanto a parámetros, pero la salida se dirige a un archivo
de texto en lugar de la pantalla.
Formato
int fprintf(FILE *archivo, const char *formato, argumento, ...);
Ejemplo
Abrir el documento "fichero.txt" en modo lectura/escritura, leer de teclado escribir en él.
#include <stdio.h>
void escritura (FILE *fp)
{
char buffer[50];
fp=fopen("fichero.txt","r+");
if (fp = = NULL)
printf ("Error de apertura de archivo");
else{
gets(buffer);
while (strcmp(buffer,"fin"))
{
fprintf (fp, "%s" ,buffer);
gets(buffer);
}
fclose(fp);
}
}
En el ejemplo se graban cadenas 50 caracteres. Se pueden usar para guardar en el archivo datos de cual-
quier tipo pero al momento de la escritura fprintf hará un cast y convertirá su contenido en tipo char.
Modificando el ejemplo anterior donde se escribe en el archivo lo que se lee de teclado, se guardará el
contenido de un arreglo de 40 componentes:
#include <stdio.h>
typedef struct
{
int día;
char nombre[30];
float sueldo;
} datos;
La información grabada en el archivo será solo de tipo char es decir solo texto.
0 1 2 3 4
En este tipo de archivo el acceso puede ser secuencial o directo. El acceso directo a los datos de un ar-
chivo se hace mediante su posición, es decir el lugar que ocupa en el archivo.
Por medio de un ejemplo se estudiarán las funciones a utilizar para realizar las operaciones básicas con
archivos sin formato.
Ejemplo
A través de un programa en lenguaje C, generar una archivo de nombre “alumnos.dat”, que contenga la
siguiente información correspondiente a los alumnos que cursan la materia Programación Procedural:
Nombre y Número de registro y resultado de un parcial- A aprobado- R Reprobado.
Construir un nuevo programa que utilice el archivo “alumnos.dat” que permita:
1- Realizar un listado que contenga el número de registro y nombre de todos los alumnos aprobados.
2- Dado el nombre de un alumno, indicar si está aprobado.
A continuación se muestran los formatos de las funciones provistas por el lenguaje para realizar las ope-
raciones mencionadas:
Función fwrite: Esta función permite trabajar con bloques de bytes de longitud constante. Es capaz de
escribir en un archivo uno o varios bloques de la misma longitud almacenados a partir de una dirección
de memoria determinada.
Formato
size_t fwrite(void *puntero, size_t tamaño, size_t nregistros, FILE *archivo);
NOTA: size_t es un tipo de dato definido por el lenguaje que se utiliza con la función sizeof, el valor
que puede tomar la variable del tipo size_t pertenece al rango de entero sin signo.
Programación Procedural 205
Archivos organizados secuencialmente
typedef struct
{
char nombre[30];
int reg;
char nota;
} alumno;
void main(void)
{
FILE *archi;
char nom[30];
Función fread
Esta función está pensada para trabajar con bloques de longitud constante. Es capaz de leer desde un
archivo uno o varios bloques de la misma longitud, y guardarlos en una dirección de memoria deter-
minada.
Formato
size_t fread(void *puntero, size_t tamaño, size_t numero, FILE *archivo);
Función feof
Para recorrer de manera secuencial el archivo es necesario saber cuando se ha procesado la totalidad del
mismo, para ello se cuenta con la función feof.
Formato
int feof(FILE *archivo);
Función fflush
Para mejorar las prestaciones del manejo de archivos se utilizan buffers, almacenes temporales de datos
en memoria. Las operaciones de salida se hacen a través del buffer, y sólo cuando éste se llena, se reali-
za la escritura en el disco efectivamente.
En ocasiones hace falta vaciar el buffer manualmente, para eso se usa la función fflush.
Esta función permite descargar los datos acumulados en el buffer del archivo.
Formato
int fflush(FILE *archivo);
El parámetro es un puntero a FILE del archivo. Si es NULL se hará el vaciado de todos los buffers
asociados a los archivos abiertos.
El valor de retorno es cero si la función se ejecutó con éxito y distinto de cero si hubo error.
#include <stdio.h>
#include<conio.h>
#include <string.h>
typedef struct
{
char nombre[30];
int reg;
char nota;
} alumno;
void main(void)
{
FILE *archi;
char nom[30];
if ((archi=fopen("alumnos.dat","r"))==NULL) /* Se abre para lectura */
printf ("hay error1 \n");
else
{ mostrar(archi); //Apartado 1
printf("\n ingrese nombre del alumno a buscar: "); gets(nom);
buscar(archi, nom);
fclose(archi);
}
}
Nota 1: Es posible posicionarse al comienzo del archivo a través de la función rewind, cuyo prototipo
es: void rewind(FILE *fp);
Nota 2: Como la función fread retorna un valor distinto de cero cuando se ha leído un bloque, y cero
cuando no hubo más bloques para leer (o no pudo leer), se puede recorrer el archivo secuencialmente sin
usar la función feof, como se muestra a continuación:
#include <stdio.h>
#include<conio.h>
#include <string.h>
#include <malloc.h>
typedef char nombre[30];
typedef struct
{ nombre nom;
int reg;
char nota;
} alumno;
struct nodo
{ alumno datos;
struct nodo * sig ;
};
void main(void)
{ FILE *archi;
puntero cab;
nombre nom;
int b;
if ((archi=fopen("alumnos.dat","r"))==NULL) /* Se abre para lectura */
printf("hay error1 \n");
else
{ printf("\n LISTADO COMPLETO ALUMNOS, DESDE ARCHIVO \n");
printf("\n NOMBRE REGISTRO NOTA");
mostrar(archi);
cab=NULL;
generalista( archi,cab);
printf("\n LISTADO ALUMNOS APROBADOS, DESDE LISTA\n");
printf("\n NOMBRE REGISTRO NOTA");
mostrarApobados (cab);
printf("\n ingrese nombre del alumno a buscar: "); gets(nom);
b= buscar(cab,nom);
if(b==1)
printf("\n El Alumno ESTA APROBADO ");
else printf("\n El Alumno NO está Aprobado ");
fclose(archi);
}
}
Ejemplo
Utilizando el archivo “alumnos.dat, mostrar en forma ordenada alfabéticamente los 20 primeros alum-
nos.
#include <stdio.h>
#include <conio.h>
#include <string.h>
const int m=20;
typedef struct
{ char nombre[30];
int reg;
char nota;
} alumno;
void main(void)
{ FILE *archi;
if ((archi=fopen("alumnos.dat","r+"))==NULL) /* Se abre para lectura y escritura */
printf("hay error1 \n");
else
{
listar_primeros(archi);
fclose(archi);
}
}
Cuando se accede en forma directa a un archivo de acceso directo, hay que tener en cuenta algunas ca-
racterísticas:
La escritura de nuevos datos se realiza al final del archivo sin que se pierda la relación de la
clave con la posición física de la componente.
Para leer una componente concreta se utiliza la clave para ubicar dicha componente
Función fseek
Esta función sirve para ubicar el puntero en el archivo, para lectura o escritura en el lugar deseado.
Formato
int fseek(FILE *archivo, long int desplazamiento, int origen);
Por ejemplo, si se quiere posicionar el puntero en el registro anterior se puede usar la función FSEEK,
en la cual el desplazamiento se calcula así :
Función fgetpos
Esta función se utiliza para obtener la posición actual del puntero del archivo. Dicha posición está dada
en bytes.
Formato:
int fgetpos(FILE *archivo, fpos_t *posicion);
Los parámetros son, un puntero a FILE del archivo y la ubicación del registro dentro del archivo expre-
sada en bytes.
El valor de retorno es cero si la función se ejecutó con éxito, y distinto de cero si hubo error.
NOTA: fpos_t es un tipo de dato definido por el lenguaje que se utiliza para hacer referencia a las posi-
ciones de los punteros de archivos.
Ejemplo
El siguiente código genera un archivo de productos con los siguientes datos: código de producto, des-
cripción, precio unitario y stock. (El código de producto está ordenado en forma ascendente y consecuti-
va a partir de 100).
#include <stdio.h>
#include <conio.h>
#include <string.h>
typedef struct
{ int cod;
char descrip[30];
float pu;
int stock;
} prod;
void main(void)
{ FILE *archi;
int cod;
if ((archi=fopen("producto.dat","w"))==NULL) /* Se abre para escritura */
printf("hay error1 \n");
else
{ cargar(archi);
fclose(archi);
mostrar(archi);
fclose(archi);
}
}
Ejemplo
A partir del archivo de productos antes generado se pide, realizar un algoritmo en C que permita:
a) Agregar un nuevo producto (el código se debe generar en forma automática).
b) Dado el código de producto mostrar su stock.
c) Mostrar la información de todos los productos.
#include <stdio.h>
#include <conio.h>
#include <string.h>
typedef struct
{ int cod;
char descrip[30];
float pu;
int stock;
} prod;
void main(void)
{ FILE *archi;
int cod, opcion;
if ((archi=fopen("producto.dat","a+"))==NULL) // lectura con posibilidad de agregar
printf("hay error1 \n");
else
{ do {
printf("\n 1. agregar un producto");
printf("\n 2. mostrar stock de un producto");
printf("\n 3. mostrar todos los productos");
printf("\n 4. salir\n");
scanf("%d", &opcion);
switch(opcion)
{ case 1: agregar(archi); break;
case 2:
{ printf("\n Ingrese codigo de producto para mostrar su stock "); //Apartado b
scanf("%d",&cod);
mostrarstock(archi,cod-100);
break;
};
case 3: mostrar(archi);
case 4: break;
}// fin del switch
} while (opcion !=4);// fin del do
fclose(archi);
}
Ejemplo
A continuación se muestra la función que actualiza el stock de un producto del archivo “producto.dat”
cuyo código se ingresa por teclado.
Segundo caso: Cuando se ingresa un dato específico de una componente (no la ubicación)
El archivo está organizado secuencialmente y puede o no presentar un orden específico.
La modificación de una componente requiere un proceso que comprende
1. leer el archivo hasta encontrar la componente buscada
2. modificar la información en la variable
3. ubicar el puntero en la posición anterior
4. grabar la variable en el archivo.
Ejemplo
A continuación se muestra la función que actualiza el stock de un producto del archivo “producto.dat”
cuyo nombre se ingresa por teclado.
#include <stdio.h>
#include <string.h>
typedef struct
{ int cod;
char descrip[30];
float pu;
int stock;
} prod;
void main(void)
{ FILE *archi;
char n[30];
if ((archi=fopen("producto.dat","r+"))==NULL) /* Se abre para lectura y escritura */
printf("hay error \n");
else {
printf("\n Ingrese nombre del producto para Actualizar su stock "); gets(n);
modistock_pr_nombre(archi,n);
fclose(archi);
}
}
rename
Cambia el nombre a un archivo físico de un dispositivo de almacenamiento. Cambia el nombre actual
por un nuevo nombre
Formato
int rename(const char* viejo_nombre , const char* nuevo_nombre);
Los parámetros son dos cadenas, el nombre que el archivo tiene actualmente y otra cadena con el nuevo
nombre para dicho archivo físico.
El valor de retorno es cero si la función se ejecutó con éxito, y distinto de cero si hubo error.
remove
Borra o elimina un archivo físico de un dispositivo de almacenamiento.
Formato
int remove(const char *nombre_archivo);
El parámetro es, una cadena nombre_archivo que contiene el nombre del archivo físico que se quiere
eliminar.
El valor de retorno es cero si la función se ejecutó con éxito, y distinto de cero si hubo error.
Ejemplo
Se tiene un archivo clientes.dat con los siguientes datos: nombre del cliente y número de cuenta. Se
pide eliminar del archivo los clientes cuyo número de cuenta se ingresa por teclado, el ingreso finaliza
con número de cuenta igual a cero.
#include <stdio.h>
typedef struct
{ char nombre[30];
int cuen-
ta;
} cliente;
void main(void)
{
FILE *archi, *auxi;
if ((archi=fopen("clientes.dat","r+"))==NULL) // Se abre para lectura y escritura
printf("hay error \n");
else
{
marcar(archi); /* Marca los registros a borrar */
auxi=fopen("auxiliar.dat","wb"); /* crea un archivo auxiliar para grabar */
compactar(archi,auxi); /* pasa los registros válidos a un archivo auxiliar */
fclose(archi); /* cierra el archivo */
fclose(auxi); /* cierra el archivo */
remove("clientes.dat"); /* Borra el archivo físico clientes.dat */
rename("auxiliar.dat","clientes.dat"); /*cambia el nombre */
}
}
Nota: Estos pasos se pueden optimizar cuando son secuenciales, ya que en vez de marcar, se copian los
registros a conservar en el nuevo archivo y luego este se renombra.
void main(void)
{ long nRegistros;
fpos_t nBytes;
cliente cli;
FILE *archivo;
archivo=fopen(“clientes.dat”, rb) //se abre para lectura
fseek(archivo, 0, SEEK_END); // Coloca el puntero al final del archivo
fgetpos(archivo,&nBytes); // Tamaño en bytes
nRegistros = nBytes/sizeof(cli); // Tamaño en registros
printf(“La cantidad de registro en el archivo es : %d”, nRegistros);
}
Bibliografía
Apuntes de Cátedra Programación Procedural
Aho Alfred V., John E. Hopcroft, Jefrey D. Ullman (1988) ”Estructura de datos y algoritmos publi-
cación". Addison-Wesley Iberoamericana: Sistemas Técnicos de Edición. México, DF.
Braunstein y Gioia (1987) ”Introducción a la Programación y a la Estructura de Datos” - Eudeba
Cairó Osvaldo (2006) “Metodología de la Programación. Algoritmos, diagramas de flujo y progra-
mas". 3º Edición Alfaomega. México.
Criado Clavero, Mª Asunción.(2006) “Programación en Lenguajes Estructurados”. Alfaomega. Ra-
Ma. México.
Deitel, H M y Deitel, P J (2003) “Como programar en C/C++” Pearson Educación –
Hispanoamericana
Joyanes Aguilar Luis (2004) “Algoritmos y Estructuras de Datos. Una Perspectiva en C”. Segunda
Edición Mc Graw Hill.
Práctica Unidad 6
Archivos
Ejercicio 1
Codificar un programa en C que permita leer un archivo de caracteres cualesquiera e indique cuál de las
cinco vocales del abecedario tiene mayor frecuencia. Realice un menú de opciones que permita leer:
a) Archivo de caracteres con extensión . cpp
b) Archivo de caracteres con extensión . dat
c) Archivo de caracteres con extensión . txt
Ejercicio 2
Realizar en Lenguaje C los siguientes programas:
Programa 1:
Generar un archivo alumnosPP.dat que contiene la siguiente información correspondiente a los alumnos
que cursan la materia Programación Procedural: Nombre, Numero de Registro y Resultado de un parcial
(„A‟: Aprobado – „R‟: Reprobado). La información se ingresa ordenada por Numero de Registro.
Codificar una función que permita mostrar la información de cada uno de los alumnos
Programa 2:
Generar un archivo alumnosAL.dat que contiene la siguiente información correspondiente a los alumnos
que cursan la materia Algebra Lineal: Nombre, Numero de Registro y Resultado de un parcial („A‟:
Aprobado – „R‟: Reprobado). El archivo debe estar ordenado por Número de Registro.
Codificar una función que permita mostrar la información de cada uno de los alumnos
Programa 3:
Codificar una función que permita mostrar Numero de Registro y Nombre de alumnos que aprobaron
ambas materias.
Ejercicio 3
Un estudiante de la carrera de Geofísica, en desarrollo de su Trabajo Final sobre peligrosidad sísmica,
desea unificar y procesar diferentes catálogos de sismos de acuerdo con ciertos criterios. Para lo cual, se
proveen dos archivos correspondientes a catálogos de distintas fuentes, de una zona de estudio en parti-
cular:
Catalogo1.txt: sismos históricos registrados por el Centro Sismológico Nacional de la Universidad de
Chile. La información que contiene es la siguiente:
País Año Mes Dia Hora Lat. Long. Prof. Magnitud mb Magnitud ms
catalogo2.txt: sismos históricos registrados por el Servicio Geológico de EE. UU. La información que
contiene es la siguiente:
Año Mes Dia Hora Lat. Long. Prof. Magnitud Tipo de Magnitud
Observación:
El Tipo de Magnitud almacenado en el nuevo archivo debe ser mw, por lo tanto, deben ser convertidas
según lo indicado a continuación:
Dado un archivo ya unificado, cuyo nombre se ingresa por teclado (incluida la extensión), cambiar signo
de la profundidad de cada evento sísmico, y guardarlo en un nuevo archivo.
Ejercicio 4
Un video club procesa diariamente el archivo “TITULOS.DAT”. Este archivo almacena la información
para cada película: Código de la película (de 1 a 1500), Título, Director y Cantidad de ejemplares dispo-
nibles en alquiler. El archivo no tiene un orden particular.
Se pide realizar un programa óptimo que a través del uso de funciones genere un menú de opciones que
responda a las siguientes solicitudes:
a) Listar por cada película, el título y la cantidad de películas disponibles.
b) Dado el código de una película, mostrar el título y la cantidad de películas disponibles.
c) Para un director cuyo nombre se ingresa por teclado, listar el total de películas realizadas por el di-
rector y la cantidad de películas con menos de 2 ejemplares disponibles.
d) Para este ítem, generar una estructura que almacene el código de película y cantidad de ejemplares
disponibles en alquiler de cada una de sus películas.
e) Utilizando esta estructura, codificar una función recursiva que devuelva al programa principal el
total de películas realizadas por el director y la cantidad de películas con menos de 2 ejemplares dis-
ponibles
Nota: Antes de resolver, realice un programa que genere el archivo “TITULOS.DAT. Para eso ingrese
de manera desordenada los títulos de las películas que están propuestas en el sitio
Ejercicio 5
Un video club procesa diariamente el archivo, “TITULOS.DAT”. Este archivo almacena la información
para cada película: Código de la película (de 1 a 1500), Título, Director y Cantidad de ejemplares dispo-
nibles en alquiler. El archivo está ordenado por código de la película.
Se pide realizar un programa óptimo que a través del uso de funciones genere un menú de opciones que
responda a las siguientes solicitudes:
d) Dado el código de una película, mostrar el título y la cantidad de películas disponibles.
e) Dado el título de una película, eliminarla de la lista. Luego, mostrar la cantidad de películas distintas
que alquila el video club (usar longitud del archivo).
f) Generar una lista con los nombres de los distintos directores y cantidad de películas de cada uno.
Ejercicio 6
Programación Procedural 221
Práctica Unidad 6
Una consultora contable realiza la liquidación de haberes de los empleados de varias PYMES. Para ello
posee un archivo con información de empleados “EMPLEADOS.dat” de diferentes empresas: Nombre
del Empleado, Nombre de la Empresa, DNI, CUIT y sueldo neto. El archivo está ordenado por nombre
de empresa.
Se pide realizar un programa que:
a) Emita un listado ordenado por empresa con la liquidación de haberes de cada empleado. Además el
listado debe incluir el total a pagar en concepto de sueldo por cada empresa.
b) Generar el archivo “EMPRESAS.dat” que almacena para cada empresa la siguiente información:
Nombre, total de empleados, total pagado en concepto de liquidación.
Introducción
A menudo se necesita guardar datos en forma permanente. La memoria RAM, o memoria principal de
El diseño del software se encuentra en el núcleo técnico de la ingeniería del software. Una vez que se
analizan y especifican los requisitos del software, el diseño del software es la primera de las tres activi-
dades técnicas - diseño, generación de código y pruebas - que se requieren para construir y verificar el
software. Cada actividad transforma la información de manera que dé lugar por último a un software de
computadora validado.
Desde que el primer programa fue dividido en (partes) módulos, los sistemas de software han tenido
arquitecturas, y los programadores han sido responsables de sus interacciones a través de módulos y de
las propiedades globales de ensamblaje. Los buenos desarrolladores de software han adoptado, a menu-
do, uno o varios modelos de arquitectura como estrategias de organización del sistema, pero utilizaban
estos modelos de modo informal.
Se identifican tres razones clave por las cuales la arquitectura de software es importante:
Las representaciones de la arquitectura de software facilitan la comunicación entre todas las par-
tes (partícipes) interesadas en el desarrollo de un sistema basado en computadora.
La arquitectura destaca decisiones tempranas de diseño que tendrán un profundo impacto en todo
el trabajo de ingeniería del software que sigue, y es tan sumamente importante en el éxito final del
sistema.
La arquitectura constituye un modelo relativamente pequeño e intelectualmente comprensible de
cómo está estructurado el sistema y de cómo trabajan juntos sus componentes.
Diseño Modular
Como hemos visto, cuando construimos la solución (programa) a un determinado problema, indirecta-
mente hemos usado alguna técnica o metodología que nos permite una solución adecuada.
En general, para pensar en la construcción de un programa en lenguaje C hicimos uso de la técnica de
diseño Top-Down , a fin de descomponer el problema en partes menos complejas. Cada una de esas
partes respondía a procesos que eran codificados y agrupados en distintas funciones.
En la fase de diseño del ciclo de vida de un programa, la solución a un problema suele venir dada por
un programa representado por un módulo principal, el cual se descompone en subprogramas (sub-
módulos), los cuales, a su vez, también se pueden fraccionar, y así sucesivamente, es decir, el problema
se resuelve de arriba hacia abajo. A este método se le denomina diseño modular o descendente (top-
down)
Programa Principal
Descomposición Modular
Se refiere al software cuando se divide en componentes nombrados y abordados por separado.
Los siguientes criterios permiten evaluar un método de diseño en relación con la habilidad de defi-
nir un sistema modular efectivo:
Capacidad de descomposición modular. Si un método de diseño proporciona un mecanismo sis-
temático para descomponer el problema en subproblemas, reducirá la complejidad de todo el
problema, consiguiendo de esta manera una solución modular efectiva.
Capacidad de empleo de componentes modulares. Si un método de diseño permite ensamblar
los componentes de diseño (reusables) existentes en un sistema nuevo, producirá una solución
modular con componentes que ya se han construido.
Capacidad de comprensión modular. Si un módulo se puede comprender como una unidad autó-
noma (sin referencias a otros módulos) será más fácil de construir y de cambiar.
Continuidad modular. Si pequeños cambios en los requisitos del sistema provocan cambios en los
módulos individuales, en vez de cambios generalizados en el sistema, se minimizará el impacto de
los efectos secundarios de los cambios.
Protección modular. Si dentro de un módulo se produce una condición que provoque errores y
sus efectos se limitan a ese módulo, se minimizará el impacto de los efectos secundarios.
Finalmente, es importante destacar que un sistema se puede diseñar modularmente, incluso aunque
su implementación deba ser "monolítica" (es decir todas las funcionalidad quedan contenidas en el
mismo archivo).
El diseño modular propone dividir el sistema en partes diferenciadas (módulos) y definir sus interfaces
(formas de comunicación), sus ventajas, claridad, reducción de costos y reutilización. Los pasos a seguir
son:
Identificar los módulos
Describir cada módulo
Describir las relaciones entre módulos
Una descomposición modular debe poseer ciertas cualidades mínimas para que se pueda considerar sufi-
ciente adecuada (es decir validada).
Independencia funcional
Ocultamiento de la Información
Acoplamiento
Cohesión
Comprensibilidad
Adaptabilidad
Independencia Funcional
La independencia funcional es una propiedad deseable del diseño modular y consiste en generar módu-
los con una función específica que tengan baja interacción con otros módulos.
Dos módulos se dicen completamente independientes si cada uno de ellos puede completar su función
sin la participación del otro.
Los módulos independientes son más fáciles de mantener y probar porque:
Se limitan los efectos secundarios provocados por modificaciones en el código de alguno de
ellos.
Se reduce la propagación de errores.
Es posible reutilizarlos.
Para simplificar, la independencia funcional es la clave del buen diseño y el diseño es la clave de la
calidad del software.
La calidad de un software está influenciada por factores internos que solo afectan a los informáticos, y
factores externos que perciben los usuarios finales. Estos factores externos son los más importantes.
Los factores internos son condiciones para que se den factores externos favorables. Algunos factores
internos que podemos considerar son:
Confiabilidad: Es el factor de calidad por excelencia. Este factor hace referencia a que el
sistema cumpla con las especificaciones y a la capacidad de reaccionar ante situaciones ex-
cepcionales.
Lograr estas características en un sistema basado en un diseño modular, es posible en cuanto
el sistema es descompuesto en partes altamente independientes entre sí, lo que permite un
"testing" más íntegro ya que cada parte es menos compleja que el todo.
Además, los errores pueden aislarse del todo, limitándolos a unos pocos subconjuntos del
programa.
Variabilidad o Extensibilidad: Es la medida del costo de cambiar o extender partes de un
programa. Esto es, la facilidad para adaptarse a cambios de especificación. En el diseño mo-
dular se reduce el efecto "ondulante de errores" que pudieren ser provocados por cambios,
puesto que éstos generalmente afectan a pocos módulos.
Generalidad:(Reusabilidad) Caracterizada por el alcance que tiene la función para la cual
ha sido concebido el programa.
Usabilidad: Se caracteriza por la facilidad con que un software puede ser utilizado por dis-
tintos usuarios (personas de diferentes formaciones y capacidades).
Eficiencia: Es la medida de rendimiento de un software, como también, su conducta. El ren-
dimiento se mide en términos de velocidad de ejecución y espacio utilizado en memoria.
En principio, cada función será realizada en un módulo distinto. Si las funciones son independientes los
módulos tendrán independencia funcional.
Cada módulo debe realizar "una función concreta" o "un conjunto de funciones afines". Es recomenda-
ble reducir las relaciones entre módulos al mínimo.
Para medir la independencia funcional hay dos criterios: acoplamiento y cohesión.
Ocultamiento de la información
Cada módulo debe ser considerado como una caja negra. De ella sabemos cuál es su entrada, cuál es su
salida, que es lo que hace, pero no como lo hace. Esto es lo que se conoce como principio de oculta-
miento de la información.
¿Cómo lo hace?
(Ocultamiento)
Cohesión
Es necesario lograr que el contenido de cada módulo tenga la máxima coherencia. Para que el numero de
módulos no sea demasiado elevado y complique el diseño se tratan de agrupar elementos afines y rela-
cionados en un mismo módulo (este tema se amplía más adelante).
Acoplamiento
El grado de acoplamiento mide la interrelación entre dos módulos, según el tipo de conexión y la
complejidad de la interface (este tema se amplía más adelante).
Comprensibilidad
Para facilitar los cambios, el mantenimiento y la reutilización de módulos es necesario que cada uno sea
comprensible de forma aislada. Para ello es bueno que posea independencia funcional, pero además es
deseable:
Identificación: el nombre debe ser adecuado y descriptivo
Documentación: debe aclarar todos los detalles de diseño e implementación que no queden de
manifiesto en el propio código
Simplicidad: las soluciones sencillas son siempre las mejores
Adaptabilidad
La adaptación de un sistema al cambio se produce cuando sus componentes tienen o se acercan a la in-
dependencia funcional, es decir, con bajo acoplamiento y alta cohesión, y el diseño es bastante compren-
sible.
Algunos factores que facilitan la adaptabilidad son:
Previsión: es necesario prever que aspectos del sistema pueden ser suscepibles de cambios
en el futuro, y poner estos elementos en módulos independientes, de manera que su modifi-
cación afecte al menor número de módulos posible
Accesibilidad: debe resultar sencillo el acceso a los documentos de especificación, diseño, e
implementación para obtener un conocimiento suficiente del sistema antes de proceder a su
adaptación
Consistencia: después de cualquier adaptación se debe mantener la consistencia del sistema, in-
cluidos los documentos afectados.
Modularidad
La modularidad es la propiedad que permite subdividir una aplicación en partes más pequeñas (llamadas
módulos), cada una de las cuales debe ser tan independiente como sea posible de la aplicación en sí y de
las restantes partes.
Un módulo es un conjunto de sentencias contiguas que realizan una determinada tarea, con un nombre
que lo identifica, el cual se compila independientemente y puede ser invocado desde otros programas o
módulos. La siguiente grafica muestra este concepto:
(Módulo Principal)
Gestión de Video Club
El objetivo del diseño modular es lograr una descomposición del problema para obtener el mejor con-
junto de módulos.
Algunos aspectos a tener en cuenta son:
El problema debe ser particionado de modo que la función de cada módulo sea fácil de com-
prender.
Las conexiones entre los módulos deben ser las más simples y escasas posibles.
Una vez que se distinguen los módulos, éstos se desarrollan, codifican y compilan independientemente.
Luego se los organiza jerárquicamente para obtener la solución del problema general.
Se ha dicho que modularidad es el atributo individual del software que permite a un programa ser inte-
lectualmente manejable. El software monolítico (compuesto por sólo un módulo) no puede ser fácilmen-
te abarcado por un lector. El número de caminos de control, la expansión de referencias, el número de
variables y la complejidad global podrían hacer imposible su correcta comprensión.
La modularidad se deriva naturalmente de un principio elemental para manejar la complejidad: divide y
vencerás.
Una vez que el módulo ha sido escrito, es posible usarlo sin necesidad de interiorizarse acerca de las
particularidades de su algoritmo, sólo se necesita conocer la acción que realiza y una descripción de los
parámetros que maneja.
Cohesión: es una medida cualitativa de refleja cuan estrechamente relacionados están las activi-
dades internas de un módulo. Se refiere a cómo las actividades dentro de un módulo están rela-
cionadas entre sí.
Entendemos por actividades a los procesos que están incluidos en el módulo. En la práctica, esto signi-
fica que el diseñador debe asegurarse de no fragmentar los procesos esenciales en módulos, y también
debe asegurarse de no juntar procesos no relacionados en módulos sin sentido.
La cohesión modular puede verse como el cemento que amalgama a los elementos de procesamiento
dentro de un mismo módulo. Es el factor más crucial y el de mayor importancia en un diseño modular
efectivo.
La cohesión representa la técnica principal que posee un diseñador para mantener su diseño lo más
semánticamente próximo al problema real, o dominio de problema.
El principio de cohesión puede ponerse en práctica con la introducción de la idea de un principio asocia-
tivo.
En la decisión de poner ciertos elementos de procesamiento en un mismo módulo, el diseñador, utiliza el
principio de que ciertas propiedades o características relacionan a los elementos que las poseen. Debe
tenerse en mente que la cohesión se aplica sobre todo el módulo, es decir sobre todos los pares de ele-
mentos. Así, si Z está relacionado a X e Y, pero no a A, B, y C, los cuales pertenecen al mismo módulo,
la inclusión de Z en el módulo, redundará en baja cohesión del mismo.
Acoplamiento: es una medida cualitativa que refiere al grado de independencia entre módulos. En la
práctica indica el grado de conocimiento que un programador necesita acerca de otro módulo.
La independencia funcional óptima se logra cuando se maximiza la relación entre los elementos de un
mismo módulo (máxima cohesión) y se minimiza la relación entre distintos módulos (mínimo acopla-
miento).
Cohesión Modular
Diferentes principios asociativos fueron desarrollándose a través de los años por medio de la experimen-
tación, argumentos teóricos, y la experiencia práctica de muchos diseñadores.
Existen siete niveles de cohesión distinguibles por siete principios asociativos. Estos se listan a conti-
nuación en orden decreciente del grado de cohesión, de mayor a menor relación funcional:
Mejor
Cohesión Abstraccional
Cohesión Funcional
Cohesión Secuencial
Cohesión de Comunicación
Cohesión de Procedimiento
Cohesión Temporal
Cohesión Lógica
Cohesión por Coincidencia
Peor
Dentro del "espectro" mostrado, siempre se intenta conseguir una mejor cohesión, aunque un punto me-
dio del rango del espectro es frecuentemente aceptado. La escala de cohesión no es lineal, esto es, una
cohesión baja es mucho peor que la del rango medio, la cual es casi tan buena como la mejor.
Notación Grafica:
En los ejemplos siguientes se usará para la representación de los módulos los siguientes elementos:
Flecha con conector blanco: representa datos usados para realizar una tarea.
Flecha con conector negro: representa un control o dato pasado como una op-
ción.
Cohesión Lógica
Un módulo con cohesión lógica es aquel que realiza un conjunto de tareas relacionadas, eligiendo una
sola de ellas cada vez que es llamado. Las tareas están relacionadas por el hecho de pertenecer a la mis-
ma clase o categoría.
Los elementos de un módulo están lógicamente asociados si puede pensarse en ellos como pertenecien-
tes a la misma clase lógica de funciones, es decir aquellas que pueden pensarse como juntas lógicamen-
te.
La cohesión lógica es más fuerte que la de coincidencia, debido a que representa un mínimo de asocia-
ción entre el problema y los elementos del módulo. Sin embargo podemos ver que un módulo lógica-
mente cohesivo no realiza una función específica, sino que abarca una serie de funciones. Esto implica
tener un código compartido, que degrada los propósitos de un buen diseño.
Ejemplo
Un módulo que realice las operaciones de actualización de un archivo (altas, bajas, modificaciones), en
función de la opción elegida.
Cod Cod
Actualizar archivo
Actualizar archivo
1 - Realizar Alta
2 - Realizar Baja
3 - Realizar Modificación
En esta cohesión hay que conocer la lógica interna del módulo, por lo tanto se pierde el concepto de caja
negra y su reusabilidad es muy limitada.
Nota: Identifique si hay flujo de control y flujo de datos en este tipo de cohesión.
En síntesis:
La cohesión por coincidencia se da en aquellos módulos cuyos elementos contribuyen a actividades con
ninguna relación significativa entre ellas.
Un módulo de este tipo es similar a uno lógico: sus actividades no están relacionadas ni por el flujo de
datos ni por el flujo de control.
Sin embargo, las actividades de un módulo lógico están al menos en la misma categoría lo que no ocurre
en los módulos con cohesión por coincidencia.
Ambos son similares: ninguno posee función bien definida (no se puede decir con claridad que hace).
Ambos son cajas blancas.
Cohesión Temporal
La cohesión temporal hace referencia a que todos los elementos de procesamiento de un módulo ocurren
en el mismo período de tiempo durante la ejecución del sistema. Debido a que dicho procesamiento debe
o puede realizarse en el mismo período de tiempo, los elementos asociados temporalmente pueden com-
binarse en un único módulo que los ejecute en un momento determinado.
Un módulo tiene Cohesión Temporal si sus elementos están involucrados en actividades cuya relación
principal es el tiempo.
Un ejemplo común de cohesión temporal son las rutinas de inicialización (start-up) comúnmente encon-
tradas en la mayoría de los programas, donde se leen parámetros de control, se abren archivos, se inicia-
lizan variables contadores y acumuladores, etc.
Nota: Identifique si hay flujo de control y flujo de datos en este tipo de cohesión.
La cohesión temporal es más fuerte que la cohesión lógica, ya que implica un nivel de relación más,
pues todos los elementos del módulo son ejecutados en el mismo periodo de tiempo, mientras que en la
cohesión lógica sólo uno de ellos se ejecuta durante la ejecución del sistema. Sin embargo la cohesión
temporal aún es pobre en nivel de cohesión y acarrea inconvenientes en el mantenimiento y modifica-
ción del sistema.
Ejemplo
Módulo Iniciar Proceso
Iniciar Proceso
Los módulos con cohesión temporal tienden a mantener relaciones fuertes con otros módulos de un
mismo programa. Por ejemplo, el módulo INICIO del ejemplo anterior tiene una relación obvia con el
módulo AGREGAR UNA ENTRADA EN LA TABLA. En la organización de los módulos, primero
debe estar presente el módulo INICIO y luego el AGREGAR UNA ENTRADA EN LA TABLA.
Por otra parte, debido a que este módulo realiza actividades específicas, su reusabilidad sólo suele estar
presente en el contexto del sistema en el cual se define.
Cohesión de Procedimiento
Un módulo con cohesión de procedimiento es aquel cuyos elementos están implicados en actividades
diferentes y posiblemente no relacionadas, en el cual el control fluye de cada actividad a la siguiente. Es
decir, si la única relación existente entre las actividades del módulo es el orden específico de ejecución,
la cohesión es procedimental.
Los módulos temporales y procedimentales son similares en cuanto se ejecutan todas las fun-
ciones que constituyen el módulo, la diferencia es que el orden de ejecución de las actividades
solo es importante en los módulos con cohesión procedimental.
Ejemplo
Graficar función coseno.
Cohesión de Comunicación
Un módulo tiene cohesión de comunicación cuando las actividades del mismo utilizan un área de una
misma estructura de datos26. En este caso, no hay orden impuesto.
Ejemplo 4
Validar datos personales
26
Presman, Ingeniería de Software.
Programación Procedural 234
Cohesión Modular
Ejemplo
Obtener información de cliente.
Encontrar nombre
Encontrar saldo
Ejemplo
Calcular salario promedio y emitir informe de salarios por categoría
Tabla de salarios de
empleados
Salario promedio
Nota: Identifique si hay flujo de control y flujo de datos en este tipo de cohesión.
Algunos Inconvenientes
Los módulos con cohesión de comunicación son fáciles de mantener, aunque pueden traer problemas.
Supongamos en el ejemplo antes expuesto, que dentro del mismo sistema hay otro módulo que necesita
validar número de teléfono, pero no necesita validar el código postal. Ese módulo, o bien necesitará
descartar la validación del código postal (acoplamiento innecesario), o necesitará un módulo indepen-
diente para validar número de teléfono (duplicación de la función).
Otro inconveniente es que el programador se ve tentado de compartir el código de las distintas tareas
que realiza el módulo. En el módulo del ejemplo anterior, se podría realizar las dos tareas dentro de un
mismo proceso iterativo. Luego de un tiempo puede que se necesite un informe solo de las diez primeras
categorías lo cual implicaría modificaciones de código más complicadas que las que se hubiese nece-
sitado si el código no hubiere sido compartido.
Cohesión Secuencial
Si las actividades del módulo están implicadas en procesos tales que, los datos de salida de una activi-
dad sirven como entrada para la siguiente, el módulo presenta una cohesión secuencial.
Un módulo con cohesión secuencial usualmente posee poco acoplamiento con otros módulos. Una ligera
desventaja de la cohesión secuencial es que es de baja reusabilidad, ya que las actividades en general, no
son útiles juntas.
Ejemplo
Dado un vector, mostrarlo en forma ordenada.
vector vector
Ejemplo
Dado el registro de un alumno, grabarlo en caso de ser es válido
registro
V
Validar registro
G
Grabar registro
En los módulos secuenciales hay flujo de control, y lo más importante es que también tiene flujo de
datos, los datos fluyen de una actividad a otra, esto es que los datos de salida de una actividad son la
entrada de la siguiente.
En comparación con los módulos con cohesión procedimental, se puede decir que ambos tienen flujo de
control y la diferencia está en que, la cohesión secuencial tienen flujo de datos.
Cohesión Funcional
Un módulo tiene cohesión funcional si realiza una actividad específica. En un módulo funcional cada
elemento de procesamiento, es parte integral de, y esencial para, la realización de una función simple.
Los elementos del módulo trabajan juntos con un mismo fin.
Los ejemplos más claros y comprensibles provienen del campo de las matemáticas. Un módulo para
realizar el cálculo del factorial ciertamente será altamente cohesivo, y probablemente, completamente
funcional. Es improbable que haya elementos superfluos más allá de los absolutamente esenciales para
realizar la función matemática.
Cuando se agrupan unidades de software teniendo en cuenta que todas ellas contribuyen a realizar un
mismo fin. Es decir, cuando todas las unidades agrupadas, trabajan juntas para alcanzar un objetivo muy
especifico.
Un módulo funcional contiene elementos que contribuyen a la realización de una tarea simple. En ellos
se observa que son módulos más reutilizables ya que solo es necesario conocer la función que cumplen
(lo que hacen), el nombre de este tipo de módulos suele indicar claramente la función que realizan, por
ejemplo:
Calcular suma
Validar campo numérico
Calcular seno del ángulo
Este tipo de cohesión representa el mayor grado de independencia que puede tener un módulo. Por todo
esto es la cohesión más deseada.
Ejemplo
El siguiente módulo calcula el sueldo neto de un empleado de una fábrica.
Sueldo Bruto
Descuentos
Bonificaciones Sueldo Neto
Cohesión Abstraccional
Se logra cuando se diseña el módulo como tipo abstracto de datos. Hace referencia a un módulo de defi-
nición de tipos usados por el sistema.
typedef struct
{ int codigo;
char descripcion[30];
float precio;
int stock;
} prod;
struct nodo
{
prod producto;
struct nodo *sig;
};
typedef struct nodo * puntero;
SI NO
SI NO
SI NO SI NO
Ejemplo
Los siguientes módulos corresponden a un sistema de manejo de archivos. El módulo principal corres-
ponde a una interfaz a través de la cual se puede invocar al módulo MODIFICA o al módulo LISTA-
DO. El módulo MODIFICA puede actualizar un cliente o agregar uno nuevo según el código que se
envíe desde el programa principal. El módulo LISTADO muestra los datos de los clientes.
Módulo Principal
opcion
Listar Modificar
Coordina _modifica
1 - Actualiza
2 - Agrega
Archivo
Clientes
Tipos
Archivo
Clientes
Codificación en lenguaje C
En principio, como los módulos MODIFICA y LISTADO trabajan con un archivo de clientes se define
un archivo de cabecera que define el tipo de dato de las componentes.
Archivo Tipos.h
Módulo con Cohesion Abstraccional
int main(void)
{ int opcion,op;
do { system("CLS");
printf("1- Listar archivo \n");
printf("2 - Modificar o Agregar \n");
printf("3 - Salir \n");
printf("Opcion:"); scanf("%d", &opcion);
switch (opcion)
{
case 1:{ listar();
break;
}
case 2:{ printf("\n ingrese opcion 1- Modifica 2 - Agrega");
scanf("%d", &op);
coordina _modifica (op); // Se invoca a la función que coordina
// las actividades dentro del módulo
break;
}
}
} while (opcion!=3);
}
2) El módulo LISTAR, muestra el contenido del archivo cliente. Se codifica y se graba con el nombre
Listar_Archivo.cpp
void listar () ,
{ cliente c;
FILE * archi;
if ((archi=fopen("Clientes","r+"))==NULL)
printf("error de acceso \n");
else {
rewind(archi);
while(fread(&c,sizeof(c),1,aarchi))
printf("\n %20s %10d",c.nombre,c.cuenta);
fclose(archi);
}
}
3) El módulo MODIFICAR, codifica la función que coordina la actividad del módulo y desde la cual se
llama a actualizar o agregar un cliente, según la opción que se haya recibido del módulo principal. Se
codifica y se graba con el nombre modifica_archivo.cpp
void coordina _modifica (int op) // función coordinadora de las actividades dentro,
{ // del módulo a traves de la cual se invoca
cliente c;
FILE * archi;
if ((archi=fopen("Clientes","r+"))==NULL)
printf("error de acceso \n");
else {
printf ("\n ingrese el nombre del cliente "); gets(c.nombre);
printf ("\n ingrese nuevo número de cuenta"); scanf("%d",&c.cuenta);
if (op==1)
actualizar(archi, c);
else
agregar(archi, c);
fclose(archi);
}
}
Nota: la claùsula static se usar para que la función declarada solo pueda ser accedida dentro del módu-
lo.
Actividad
Investigue qué realiza la instrucción del preprocesador #ifndef ...... #endif
Analice qué ventajas y/o desventajas tienen las operaciones de apertura y cierre en el interior del
módulo.
¿Qué significado tiene el prefijo static en la función agrega y actualiza?
¿Es posible desde el módulo principal acceder a la función agrega?
La cuestión aquí es: ¿cuánto debe conocerse acerca de un módulo para poder comprender otro módulo?
Cuanto más debamos conocer acerca del módulo B para poder comprender el módulo A, menos inde-
pendientes serán A de B.
Módulos altamente "acoplados" estarán unidos por fuertes interconexiones, módulos débilmente acopla-
dos tendrán pocas y débiles interconexiones, en tanto que los módulos "desacoplados" no tendrán inter-
conexiones entre ellos y serán independientes.
El acoplamiento es un concepto abstracto que nos indica el grado de interdependencia entre módulos.
Contestando estas preguntas determinaremos cuales son probables para modificaciones requeri-
das por un gran número de módulos.
Claramente, el costo total del sistema se verá fuertemente influenciado por el grado de acoplamiento
entre los módulos.
El acoplamiento entre módulos se da de varias maneras: de control de flujo y de datos.
El acoplamiento de control implica la transferencia del control de un módulo a otro.
El acoplamiento de datos se refiere a compartir de datos entre los módulos.
Mejor
ACOPLAMIENTO NORMAL
ACOPLAMIENTO DE DATOS
ACOPLAMIENTO ESTAMPADO
ACOPLAMIENTO DE CONTROL
ACOPLAMIENTO EXTERNO
ACOPLAMIENTO COMÚN
ACOPLAMIENTO POR CONTENIDO
Peor
Ejemplo
Módulo A
Módulo B
Busq:---
- Jump to
Busq
Acoplamiento Común
Dos o más módulos tienen Acoplamiento Común si ellos hacen referencia a una estructura de datos
global compartida.
El acoplamiento común provoca los siguientes inconvenientes en los módulos así relacionados:
Una modificación en sólo uno de ellos puede provocar impactos no deseados en los demás. Por
ejemplo, si se quiere modificar la longitud de un campo de una estructura global compartida, habrá
que recompilar todos los otros módulos que hacen referencia a esa estructura.
Si un módulo hace referencia a un área común, se hace problemática su reusabilidad en otros contex-
tos.
Ejemplo
Validar Modificar
Datos Tabla Cod.
Empleados
Generar Emitir
Tabla
Tabla Cod. Códigos Cheques
Postales
Emitir
Emitir
Informe
Memorándum
Empleados
Cuando un algoritmo se implementa con un lenguaje en particular, muchas veces surgen problemas de
acoplamiento común que no pueden ser resueltos debido a las restricciones del lenguaje.
Acoplamiento Externo
Se produce cuando el módulo está ligado a algún dispositivo externo (como ser un disco, un sensor,
canal de comunicación, etc.). En este caso, el formato de los datos que maneja el dispositivo exige que
ante cualquier modificación se le haga también se modifiquen los módulos que lo usan.
El acoplamiento externo es inevitable, por esto es que en el diseño se debe buscar la forma de tener la
menor cantidad posible de módulos con este acoplamiento.
Los módulos están ligados a un entorno externo al software. Este acoplamiento es esencial pero deberá
estar limitado a unos pocos módulos.
Esto está básicamente relacionado con la comunicación a herramientas externas y dispositivos. Se puede
considerar como dispositivos vinculados a monitores e impresoras, pero por ser dispositivos de uso
permanente (sobre todo la pantalla) en el diseño se limita a aquellos que afectan de un modo directo al
comportamiento del módulo.
Ejemplo
Modificar
Archivo
Clientes
Acoplamiento de Control
Dos o más módulos están Acoplados por Control si uno pasa al otro una pieza de información destinada
al control de la lógica interna del otro. Los elementos típicos de control están representados por flags,
códigos, llaves, etc.
En otras palabras se puede decir que dos o más módulos están Acoplados por Control si uno envía al
otro además de los datos, información de cómo se desea que se procesen dichos datos.
Ejemplo
Actualizar
Maestro
En este tipo de Acoplamiento, el módulo llamante debe conocer cómo está organizada la lógica del
módulo subordinado, a fin de enviar la señal correcta.
Inversión de Autoridad
En el ejemplo anterior hemos detectado la presencia de un módulo padre (módulo llamante) que envía
señales de control a un módulo hijo (módulo llamado o subordinado) que termina la tarea indicada por el
módulo padre.
Si es el subordinado el que envía órdenes a su superior hemos realizado una falla en la partición que se
llama "inversión de autoridad".
Ejemplo
Validar
Cliente
1. teléfono correcto
Nro_Teléfono Códi- 2. teléfono no numérico
go: 3. característica errónea
Validar
Número
Telefónico
En este ejemplo es el módulo padre es el que termina la tarea, ya que es él quien debe escribir el mensaje
de error en función de la orden enviada por el módulo hijo. Luego, el módulo hijo deberá conocer la
lógica interna del módulo padre a fin de enviar la señal correcta.
Cómo solucionamos la inversión de autoridad?
a) El módulo subordinado escribe los mensajes de error
b) El módulo subordinado reporta el estado de la tarea y el superior decide qué hacer, pero sin que el
subordinado conozca cómo.
Ejemplo
Validar
Cliente
Nro_Teléfono Resultado
Validar
Número
Telefónico
Acoplamiento Estampado
Dos módulos tienen Acoplamiento Estampado cuando hacen referencia a la misma "estructura de datos",
la cual no está definida como global.
En el Acoplamiento Estampado, al no conocerse la ubicación o el nombre de la estructura de datos por
no estar definida como global, es necesario que sea pasada como parámetro. Este acoplamiento crea
dependencia entre módulos, que hasta este momento no estaban relacionados. Además tiende a exponer
al módulo a más datos de los que necesita con posibles consecuencias no deseadas.
El Acoplamiento Estampado reduce los efectos negativos del Acoplamiento Común, tales como adapta-
ción del módulo a diferentes contextos debido a que utiliza estructuras de datos de las cuales hay múlti-
ples versiones en el programa (una por cada módulo que la utiliza).
Ejemplo
Acoplamiento de Datos
Dos módulos tienen Acoplamiento de Datos, si todos los argumentos pasados desde y hacia los módulos
están representados por elementos de datos que no cumplen funciones de control, ni tampoco constitu-
yen estructuras de datos. El Acoplamiento de Datos significa relación por medio de elementos de datos
simples y no por medio de estructura de datos.
Una estructura de datos como puede ser un registro de un archivo o un arreglo, implica Acoplamiento
estampado.
Los módulos se comunican por parámetros y como los módulos deben comunicarse de
alguna manera, el acoplamiento de datos es inevitable y bastante inofensivo.
Ejemplo
Listar Círculo de
Área Máxima
Radio Área
Calcula Área
del Círculo
Acoplamiento Normal
Dos módulos están acoplados normalmente cuando no se pasan ningún parámetro entre ellos, sólo existe
la llamada de uno a otro.
Dos módulos A y B están normalmente acoplados sí: un módulo A llama a otro B, y B retorna el control
a A.
Ejemplo
Se observa entre el Módulo Principal y el Modulo Listar el acoplamiento Normal (en la grafica también
se ve un acoplamiento externo en Listar)
Módulo Principal
Listar
Archivo
Clientes
Bibliografía
Apuntes de Cátedra Programación Procedural.
Braunstein Silvia y A. Gioia (1987) “Introducción a la Programación y a la Estructura de Datos”.
Eudeba. Buenos Aires.
Brassard, G. & Bratley, P.( 1997)"Fundamentos de Algoritmia". Prentice-Hall. México.
Cairó Osvaldo (2006) “ Metodología de la Programación. Algoritmos, diagramas de flujo y progra-
mas". 3º Edición Alfaomega. México.
Criado Clavero, Mª Asunción (2006) “Programación en Lenguajes Estructurados” . Alfaomega. Ra-
Ma. Madrid
Calero, Moraga y Piattini (2010) "Calidad del Producto y Proceso Software". Editorial Ra-Ma. Ma-
drid.
Jesús Barranco de Areba (2001) "Metodología del análisis estructurado de sistemas". Universidad
Pontifica Comillas. 2da Edición. Madrid.
Kenneth y Kendall (2005) "Análisis y diseño de sistemas". Pearson Educación. 6ta Edición. Méxi-
co.
Morales, Roberto Cortés (1992) "Introducción al Análisis de Sistemas y la Ingeniería de Software".
EUNED. Costa Rica.
Fontela, Carlos (2003) "Programación Orientada a Objetos. Técnicas Avanzadas de Programación".
Editorial Nueva Librería. Buenos Aires.
Práctica Unidad 7
Diseño Modular
Estudio Dirigido
Propuesta de trabajo
La siguiente guía tiene como propósito orientar en la comprensión y aplicación de contenidos de co-
hesión y acoplamiento, a través de actividades prácticas que permitan integrar los temas vistos.
Mediante este estudio se propone fomentar la capacidad de análisis, síntesis y de trabajo colaborativo,
que se reflejará a través del desarrollo de un Proyecto en Lenguaje C, que tenga en cuenta característi-
cas del Diseño Modular como Metodología de Diseño de Software.
Objetivos
A través del siguiente trabajo se espera que cada alumno:
Conozca una forma de aprendizaje a través del análisis y estudio de material vinculado al
diseño estructurado como metodología de diseño de algoritmos y la discusión activa de
hallazgos en un grupo de estudio.
Potenciar el desarrollo del aprendizaje autónomo.
Conocer un Modelo de Trabajo Grupal, donde se asignen tareas y responsabilidades para
el logro de un objetivo determinado.
Contenidos Curriculares
1. Diseño Modular. Cohesión y acoplamiento como criterios cualitativos para medir la calidad de
software.
2. Administración de archivos.
3. Construcción de Proyectos en Dev-C++.
Modalidad de Trabajo
La técnica de estudio dirigido que hemos elegido como método de abordaje de este trabajo consiste en
que se analice grupalmente el tema.
El grupo debe estar constituido por 3 integrantes como máximo y la presentación de la tarea es indivi-
dual.
Evaluación
En el laboratorio de computación cada alumno deberá presentar su propuesta, justificando el diseño
elegido. El código debe ejecutar correctamente y responder al diseño realizado. El docente podrá
solicitar modificaciones u optimizaciones.
Actividades a presentar
Para llevar a cabo estas actividades se realizará el siguiente proceso:
a) Buscar entre todos los integrantes una solución a la problemática planteada
b) Elaborar el diseño modular que responde a la solución elegida.
c) Especificar la cohesión modular y el acoplamiento intermodular.
d) Codificar cada uno de los módulos usando funciones óptimas, al menos una recursiva
e) Utilizar proyectos en Dev-C++
ACTIVIDAD 1
Una consultora contable trabaja en la liquidación de haberes de los empleados de varias empresas pe-
queñas.
La empresa posee en un archivo “EMPRESAS.dat” con información general de cada una de las empre-
sas: código (número secuencial, generado automáticamente a partir de 1200), nombre, CUIT y direc-
ción.
Para el cálculo de sueldos de los empleados se realiza de la misma forma, se trabaja con un archivo de
empleados ordenado ascendentemente por Código de empresa.
El archivo de empleados “EMPLEADOS.dat” posee la siguiente información: Nombre del Empleado,
código de la empresa donde trabaja, DNI, sueldo básico, antigüedad.
Los empleados además tienen descuentos fijos sobre su remuneración (sueldo básico más antigüedad),
estos son: Jubilación 11%, Obra Social 3%, Seguro de Vida $ 3,80.
Se pide generar diferentes módulos que permitan:
Módulo 1: Genera un archivo con información de las distintas empresas que trabajan con el estudio
contable.
Módulo 2: Generar un archivo con información de los empleados.
Módulo 3: Emita un listado ordenado por empresa con la liquidación de haberes de cada empleado, que
incluya además el Total Pagado en concepto de haberes y el Total Pagado en concepto de Descuento:
Empresa: xxxxxx (nombre de la empresa)
Nombre de Empleado Remuneración
----------------------- --------
----------------------- --------
----------------------- --------
Total Pagado en concepto de haberes: --------
Total Pagado en concepto de Descuento: --------
___________________________________________________________________
Empresa: xxxxxx (nombre de la empresa)
Nombre de Empleado Remuneración
----------------------- --------
----------------------- --------
----------------------- --------
Total Pagado en concepto de haberes: --------
Total Pagado en concepto de Descuento: --------
ACTIVIDAD 2
Módulo 1: Generar un archivo “alumnosPP.dat” que contenga la información correspondiente a los
alumnos que cursan la materia Programación Procedural: Nombre y Número de registro y resultado de
un parcial (A: aprobado - R: Reprobado). La información se ingresa ordenada por matrícula.
Módulo 2: Generar un archivo “alumnosAL.dat” que contiene la siguiente información correspondiente
a los alumnos que cursan la materia Álgebra Lineal: Nombre y Número de registro y resultado de un
parcial (A: aprobado - R: Reprobado). La información se ingresa ordenada por matrícula.
Módulo 3: Recibir un código „A‟ o „R‟, Si es „A‟ se genera una lista enlazada con los alumnos que
aprobaron ambas asignaturas. Si es „R‟ genera una lista enlazada con los alumnos que reprobaron ambas
asignaturas. La lista debe crearse de manera ordenada por matrícula usando la técnica de mezcla de ar-
chivos.
Módulo 4: Este módulo debe diseñarse de modo que permita generar un archivo “UNA.dat” con infor-
mación de los alumnos que sólo aprobaron una asignatura.
Nota: La codificación de los módulos debe ser óptima.
ACTIVIDAD 3
La biblioteca de la Facultad de Ciencias Exactas, Físicas y Naturales posee un sistema que incluye
módulos, a través de los cuales desarrolla varias funcionalidades.
Para ello utiliza distinto archivos que a continuación se describen:
LIBROS, este archivo posee datos de los libros que posee la biblioteca.
Nombre: es el nombre del libro.
Código: es un campo clave que se inicia en 1 para el primer libro.
Cantidad: cantidad total de ejemplares de un libro.
Disponible: cantidad de ejemplares de un libro disponibles para préstamo.
Categoría: temática a la que pertenece el libro.
Nota: los libros se encuentran ordenados secuencialmente por código.
PRÉSTAMOS, este archivo posee datos de los distintos prestamos que la biblioteca realiza.
Número de socio: es el número de identificación de cada socio de la biblioteca.
Código de libro: es el código que identifica al libro que se entregó o recibió.
Operación: (P: préstamo; D: devolución) indica la operación que realiza el socio en la bi-
blioteca para cada uno de los libros.
NARRATIVA
La biblioteca de la FCEFyN posee diferentes tipos de libros, de los cuales se pueden realizar préstamos
y/o consulta de los mismos. La información de los libros se almacena en el archivo "Libros.dat".
Diariamente se crea un arreglo con la información de este archivo. Este arreglo se actualiza cada vez
que solicita el préstamo de un libro y cuando se devuelve algún libro por parte de un socio, por lo
tanto esto implica constante actualización. Es muy importante tener en cuenta que cuando sólo queda
un ejemplar de un libro, este no puede ser entregado en calidad de préstamo sino que el socio puede
consultarlo en el lugar.
El archivo "Libros.dat" se actualiza al salir del sistema.
Por otro lado la biblioteca posee un archivo con los datos de los socios “Socios.dat”, que pueden solici-
tar préstamos o realizar consultas. Los socios pueden retirar hasta 3 libros como máximo y no puede
llevar más de un ejemplar por libro.
Es necesario contar con una estructura auxiliar (lista) que permita almacenar las operaciones que reali-
zan los socios con la biblioteca (prestamos, consultas y devoluciones) por lo tanto se solicita registrar
número de socio, código de libro y tipo de operación. Esta estructura es necesaria para actualizar el ar-
chivo “Prestamos.dat” al salir del sistema.
Funcionamiento
Cuando una persona solicita un libro, el bibliotecario verifica si es socio, si no fuera así lo ingresa como
nuevo socio. Todo socio nuevo debe esperar 24 horas para poder hacer uso de la biblioteca.
Una vez verificada la calidad de socio, se consulta si está habilitado para solicitar libros (considerar que
los socios pueden tener en su haber solo 3 libros). Si el socio se encuentra en condiciones de solicitar
material, se le pide el nombre del libro, allí el bibliotecario verifica la existencia del mismo y la cantidad
de ejemplares disponibles como para cubrir el pedido. Si la cantidad de ejemplares de determinado libro
fuera superior a uno (1), puede realizarle la operación de préstamo, caso contrario se le comunica al
socio la negativa del pedido del ejemplar y se le notifica que se encuentra habilitado solo para consulta.
En caso de préstamo esto debe quedar registrado para la correcta actualización de los datos, lo mismo en
el caso de que un socio quiera realizar la devolución del o los ejemplares que posee.
Diseño modular
Represente gráficamente el diseño en general, y particularmente cada módulo especificando en
forma detallada las tareas que se realizan (Refinamiento), indicando datos de entrada y salidas.
Indique tipo de cohesión y acoplamiento.
Incluya en el diseño al menos 3 módulos: uno que contenga cohesión lógica, otro secuencial y
otro funcional.
Implementación
Para administrar las funcionalidades del sistema debe incluir módulos que permitan:
Crear los archivos indicados.
Registrar el ingreso de nuevos libros.
Registrar el ingreso de nuevos ejemplares para libros existentes. (solo pueden ser usados 24
horas después de su carga)
Registrar el ingreso de nuevos socios (están habilitados 24 horas después de su carga).
Realizar un listado de libros por temática.
Informar si un libro está disponible para préstamo o consulta conociendo el nombre del mismo.
Informar si un libro está disponible para préstamo o consulta conociendo su código.
Realizar un listado de los libros que se encuentren en consulta.
Registrar la devolución de un libro.
Actualizar el archivo de préstamos.
NOTA: Es importante optimizar el código. Realizar un menú de opciones y validar los datos de en-
trada.
Presentación
Cada Grupo presenta una carpeta con el desarrollo de cada actividad. Esta carpeta contiene:
Caratula: Apellido y Nombre, Registro y Carrera (de cada integrante del grupo).
Grafico del diseño Modular de cada actividad.
Cada Modulo debe estar graficado de modo tal que se observe su refinamiento, entradas y sali-
das. Respecto al Diseño modular deben indicarse cohesión y acoplamiento sin incluir definicio-
nes teóricas.