Programacion C
Programacion C
Programacion C
Lenguajes de Programación
Introducción
En este documento se hace una breve introducción a los distintos tipos de
lenguajes de programación existentes, como también así a los procesos de traducción
hacia un código entendible por un sistema de computación.
1. Arquitectura de Computadoras
Arquitectura de Von Neumann (fines de ’40): computadora compuesta por las
siguientes unidades funcionales:
v Memoria: almacena datos e instrucciones.
v Unidad Aritmético-Lógica: opera con datos.
v Unidad de Control: interpreta y ejecuta instrucciones.
v Subsistema de Entrada-Salida.
C.P.U.
MEMORIA SUBSISTEMA
A. L. U. DE
PRINCIPAL
ENTRADA /
SALIDA
U. C.
v La ejecución de control del programa está dada por flujo de instrucciones, el cual es
secuencial (de hecho las instrucciones de salto condicional o incondicional son un
salto hacia otra secuencia)
I.T.B.A. - 2000
Programación I - Clase 1 2
2. Paradigmas de Programación
Se llama así a conceptualizaciones de técnicas y lenguajes empleados para
construir programas. Existen distintos paradigmas de programación, y pasamos a
enumerarlos y caracterizarlos:
Sintaxis
Semántica
Los programas realizados por programadores deben poseer una semántica clara
para que sean fáciles de entender, modificar y adaptar.
I.T.B.A. - 2000
Programación I - Clase 1 3
Desventajas
v Si se desea llevar el mismo programa para que funcione a una computadora con
distinta arquitectura, se debe re-escribir el código (no es reusable)
I.T.B.A. - 2000
Programación I - Clase 1 4
Ventaja:
Desventajas
v El código ensamblador tampoco es portable (sigue siendo relación uno a uno con el
lenguaje de máquina)
Ventajas:
v Mayor portabilidad para los programas de usuarios, ya que sólo basta con
recompilar el código en alto nivel al lenguaje de máquina correspondiente
I.T.B.A. - 2000
Programación I - Clase 1 5
Desventaja:
4. Traductores de Programas
Todo programa fuente no escrito en lenguaje de máquina debe pasar por un
traductor, para poder ser entendido por la máquina.
4.1 Compiladores
El código de un programa se almacena en un archivo denominado programa
fuente. Este es el único archivo de todo el proceso de traducción que contiene líneas que
el humano pueda leer y entender.
Una vez que se tiene el programa fuente, el próximo paso a ejecutar es usar el
compilador para traducir el programa fuente en un formato que la computadora pueda
entender directamente. Este proceso varía de una máquina a otra. El compilador traduce
el archivo fuente en un segundo archivo llamado archivo objeto, que contiene
instrucciones apropiadas para el sistema de computación.
I.T.B.A. - 2000
Programación I - Clase 1 6
Dicho archivo objeto puede combinarse con otros archivos objetos o bibliotecas,
generando un archivo ejecutable que pueda correr en el sistema. El proceso de combinar
todos los archivos objetos individuales en un ejecutable final se llama linkedición.
Linkeditor
4.2 Intérpretes
Los intérpretes no generan un programa ejecutable, sino un programa que puede
ser interpretado en una “maquina virtual”. O sea, el código que generan no es lenguaje
de máquina para la arquitectura en la que están ejecutando, sino un código intermedio
que cuando se intenta ejecutar va siendo interpretado paso a paso. Suelen ser más lentos
pero más portables. Ejemplos: Smalltalk, Java.
I.T.B.A. - 2000
Programación I - Clase 2 1
Introducción
La Ingeniería de Software es la disciplina de la Ciencia de la Computación que
se ocupa de las técnicas necesarias para el desarrollo y mantenimiento de Sistemas de
Software (inclusive a gran escala).
En el presente documento se desarrolla una serie de temas que introducen pasos
importantes para el desarrollo y testeo de software.
Importante:
Todos los programas que se entreguen serán ejecutados por la cátedra en todas
estas plataformas, por lo tanto no presuponer nada respecto de la plataforma en la que
va a correr. Este es el motivo por el cual elegimos ANSI C. Por lo tanto no utilizar
funciones que sólo puedan ser ejecutados en Windows, o DOS, etc.
I.T.B.A. - 2000
Programación I - Clase 2 2
Algoritmo genérico:
Se le agregan condiciones para que pueda ser ejecutado por una computadora, a
saber:
1. Debe ser simple y no ambiguo.
2. Debe ser efectivo.
3. Debe satisfacer la propiedad de finitud.
Aclarando la definición:
1. Simple y no ambiguo significa que debe ser presentado en forma clara y de tal modo
que sea posible entender los pasos que involucra. Debe ser determinístico.
2. Efectivo quiere decir que es posible llevarlo a la práctica. Ejemplo: calcular el
perímetro de una circunferencia como 2*R*π no es efectivo, ya que en computación
el valor de π no es exacto (en matemática puede dejarse expresado).
3. No puede ejecutar indefinidamente, o sea debe garantizarse que el mismo finaliza
luego de un número finito de pasos.
I.T.B.A. - 2000
Programación I - Clase 2 3
Importante:
Muy Importante:
I.T.B.A. - 2000
Programación I - Clase 2 4
Testeo de Software
Importante
Aclaración:
I.T.B.A. - 2000
Programación I - Clase 2 5
software
resultados
del test
Testeo
Evaluación
errores
test
promedio Debuggeo
resultados de errores
esperados
corrección
Modelo de
Confiabilidad
Figura 1. predicción de la
confiabilidad
Aclaración:
I.T.B.A. - 2000
Programación I - Clase 2 6
Importante
Ejemplo: Si hay que testear un programa que lee tres valores de datos e informa si los
mismos corresponden a los lados de un triángulo habría que probar con los valores: (2,
0, -3), (4, ‘¿’, ‘5’), (‘u’, ‘r’, 3), (“hola”), etc., etc., etc.
Muy Importante
Armar un test con todas las posibles combinaciones de datos ingresables resulta
impracticable.
I.T.B.A. - 2000
Programación I - Clase 2 7
son tratados en forma idéntica por el programa se los divide en más clases de
equivalencia. Después se completa la siguiente tabla:
... ...
• Se elige un valor representante de cada clase válida y todos ellos deben ser
cubiertos por lo menos por un test. Hay que elegir tantos tests como sean
necesarios hasta cubrir todos los valores elegidos como representantes válidos.
Pueden ser menos test que la cantidad de valores elegidos si se busca involucrar
simultáneamente a varios valores en un mismo test. Finalmente se busca un
valor representante de cada clase inválida y se construye un test para cada
uno de ellos por separado (la idea de testear los inválidos por separado es tratar
de asegurarse de que cada uno de ellos esté testeado). Ejemplo: Si se pidiera
ingresar dos números positivos y el test consistiera en ingresar el par (-5, 0)
según como esta construido el programa, y obviamente esto no se sabe en caja
negra, puede haberse validado el primero pero no el segundo y no nos daríamos
cuenta.
Ø Análisis de los valores límite: Los valores límites son aquellos que están justo por
debajo o arriba de lo márgenes de las clases de equivalencia para las entradas y
las salidas (en el paso anterior solo se toma las clases de equivalencia de la entrada).
Notar que para los valores límites también se analizan los espacios de resultados
que se esperan obtener para saber si lo que el módulo dice calcular realmente
coincide con lo anunciado. Si el rango de entrada o salida fuera [n,m] probar la
funcionalidad de la unidad con n, n-1, n+1, m, m-1 y m+1. Notar que para el rango
de salida es más difícil porque hay que pensar en la entrada que habría que ingresar
para que la salida fuera la buscada.
Ø Conjetura de errores: La experiencia muestra que hay ciertas personas que poseen
una mejor predisposición a construir muy buenos test de software, quizás en base a
su experiencia. A modo de ejemplo citaremos algunos trucos a tener en cuenta que
pueden ayudar a completar los tests de caja negra: si el módulo realiza una división
(aunque no se vea el código, por su funcionalidad se puede deducir) tratar de
provocar una entrada que haga que el divisor sea cero, si solicita una lista de entrada
hacer que sea vacía, que todos los valores sean iguales, etc.
I.T.B.A. - 2000
Programación I - Clase 2 8
Ø Cobertura de sentencias: armar test que aseguren que por lo menos se ejecuta una
vez cada instrucción.
Ø Cobertura de valores limites: armar casos de prueba para que los criterios
involucrados en una condición que especifiquen un rango [n, m] se prueben con
valores justo alrededor de sus límites: n-1, n, n+1, m-1, m, m+1. Notar la similitud
con caja negra, pero ahora a nivel interno y no externo.
Importante:
Nunca aplicar Caja de Pandora que significa no testear nada y rogar para que el
usuario del software no tenga problemas con el mismo.
I.T.B.A. - 2000
Programación I - Clase 2 9
Con respecto a la técnica de caja blanca y caja negra, hemos visto que cada una
de ellas tiene sus puntos fuertes y débiles. En esta materia usaremos la combinación de
ambas.
Hay un viejo dicho que dice “si Ud. cree que diseñar y codificar ese
programa ha sido difícil, entonces aún no ha visto nada” y se refiere a la gran tarea
que implica diseñar y aplicar test de software para detectar la calidad del mismo, por tal
motivo hemos estudiado técnicas que servirán como herramientas de ayuda en el
desarrollo de software confiable.
Muy Importante
Notar que todo lo que se expuso hasta ahora corresponde al buen diseño de tests
de software. No perder de vista que una vez construidas dichas pruebas lo que debe
hacer es ejecutar el módulo con cada una de ellas, prediciendo de antemano qué debería
hacer el módulo frente a los valores de entrada propuestos y convalidando si la salida
concuerda con lo esperado (ver figura 1), caso contrario el test ha resultado un éxito, y
se procede a arreglar el código fuente, para luego testearlo nuevamente. Tener en cuenta
que en la mayoría de los casos, al arreglar un error suelen surgir errores nuevos.
1.3.4 Debuggeo
Los errores que suelen surgir en un programa podrían clasificarse globalmente en:
v Errores en tiempo de ejecución (lógicos o bugs). Estos errores son los que
producen el funcionamiento incorrecto de un programa. Debido a la confianza que
tiene todo programador sobre su propio código, son muy difíciles de detectar. Por
este motivo es muy importante aplicar las distintas técnicas de testeo de software ya
vistas, para lograr encontrarlos y corregirlos. El éxito de un programador se debe en
parte a aplicar adecuadamente buenas técnicas de testeo de software. Una vez
detectados los errores lógicos, se suelen corregir mediante la técnica de debuggeo,
que consiste en poder ejecutar paso a paso el programa, inspeccionando el estado de
las variables, verificando si el flujo de control es el esperado, etc.
I.T.B.A. - 2000
Programación I - Clase 2 10
2 Ejercicios de Aplicación
Ejercicio 1
Se tiene un módulo para calcular la media aritmética de un conjunto no nulo de
números. Para el ingreso de datos se solicita primero la cantidad de números a ingresar
y luego los mismos.
Leer(cantidad)
blanquear(Acumulador)
escribir( Acumulador/cantidad)
Posible Solución
v Caja Negra
Ø Particiones de equivalencia
Armamos las clases de equivalencia para la entrada. Podría ser que los caracteres
sean tratados diferentes a los números desde el punto de vista de programación, y por lo
tanto partimos la clase de equivalencia de lo invalido:
I.T.B.A. - 2000
Programación I - Clase 2 11
T1= { cantNro= 3, nro= -3, nro= 0.4, nro= 5 } con esto abarcamos los
representantes de cada clase válida en la entrada.
El valor acotado es la cantidad de números a ingresar que debe ser mayor o igual
que cero. No hay cota explícita indicada para la cantidad ni para los valores. En este
caso el límite es inferior: cantNro >=1.
Así debemos probar con un test que oscile en uno: cantNro= 0, otro con
cantNro= 1 y otro con cantNro= 2.
T7= { cantNro= 0 }
T8= { cantNro= 1, nro= 5 }
T9= { cantNro = 2, nro = 2.3, nro= 7}
Ø Conjetura de errores
Como el módulo realiza una división para hacer el promedio, podríamos pensar
un test que haga que la división incorrecta. Para eso cantNro debería ser cero, y eso ya
lo contemplados en T7.
I.T.B.A. - 2000
Programación I - Clase 2 12
v Caja Blanca
Ø Cobertura de sentencias
Marcamos flujos como para que las sentencias se an ejecutadas por lo menos una
vez.
leer(cantidad)
blanquear(acumulador)
escribir( Acumulador/cantidad)
o bien
Nótese además que si el diseñador de este test fuese el mismo que ideó las
pruebas para caja negra, podría haber considerado explícitamente: T10 = T1 (la calidad
del test no está dado por la cantidad de las pruebas, sino por tal potencia de descubrir
errores).
I.T.B.A. - 2000
Programación I - Clase 2 13
Ø Cobertura de decision/condicion
Para la condición cantidad > 0 debemos considerar un test para que la misma se
evalúe como verdadera y otra como falsa. O sea qué datos habría que entrar para que
esto fuera posible. Si cantidad fuera 2, se evaluaría hasta llegar a valer 0 y no debería
entrar más, o sea se evaluaría como falsa.
Como el límite inferior de la condición del ciclo es mayor que cero, eso implica
que el valor entero límite con el cual se entra al cuerpo del ciclo es el entero 1. Por lo
tanto habría que hacer pruebas con los valores cero, 1 y 2 para cantidad y eso ya lo
hicimos en caja negra.
Ahora habría que ejecutar (en un papel o con una computadora) cada uno de los
tests para ver si producen los valores esperados.
Los test ideados para caja negra sólo pueden ejecutarse en computadora. Los
otros pueden correrse en papel o con computadora.
Una vez ideado los tests, se procede a la etapa final, que consiste en ejecutar el
módulo o programa con esos valores y ver si se obtienen los valores esperados.
Podríamos completar la siguiente tabla:
Obviamente hay que revisar el código por qué siempre se anula el divisor!
I.T.B.A. - 2000
Programación I - Clase 2 14
Ejercicio 2
Se tiene un módulo que solicita un cartel de 5 letras como máximo y lo escribe
transformado a minúscula. Cualquier símbolo que no sea letra mayúscula queda intacto.
El cartel podría ser nulo.
leer(cartel)
longitud = largo del cartel leído
decrementar(longitud, 1)
fin
Posible Solución
v Caja Negra
Ø Particiones de equivalencia
I.T.B.A. - 2000
Programación I - Clase 2 15
T3= { “” }
T4= { “a”}
T5= { “mono” }
T1
T6= { “123456” }
Habría que pensar, sin conocer el código, qué casos se pueden haber escapado al
programador que hace que falle la transformación: si el carácter a pasar es el primero, es
del medio o es el ultimo, o bien si vienen todos iguales. Con un poco de intuición
construimos:
T7= { “T”}
T8= { “aSi” }
T9= { “asI” }
T10= { “VEZ” }
T11= { “No es” }
Ø Conjetura de errores
I.T.B.A. - 2000
Programación I - Clase 2 16
v Caja Blanca
Ø Cobertura de sentencias
Marcamos flujos como para que las sentencias sean ejecutadas por lo menos una
vez:
leer(cartel)
longitud = largo del cartel leído
decrementar(longitud, 1)
fin
Así es como de este análisis surgen test que usen minúsculas y mayúsculas. Para
esto nos sirve T8.
Ø Cobertura de decision/condicion
Para el mientras tenemos la condición (longitud >0 and longitud < 6) como toda
ella debe evaluarse como verdadera y como falsa, y a cada subcondición le debe pasar
lo mismo podríamos pensar en tests que combinen: (verdadero, verdadero), (falso,
verdadero) y (verdadero, falso).
T11
T3
T6
Para la condición del “si entonces“ tenemos letra > =‘A’ and letra <= ‘Z’. Los
test podrían ser los que permitan obtener (verdadero, verdadero), (falso, verdadero),
(verdadero, falso)
I.T.B.A. - 2000
Programación I - Clase 2 17
• Para cantidad tenemos el rango [1, 5], por lo tanto buscamos entradas que
oscile con las cantidades de letras 0, 1, 2, 4, 5, 6:
• Para letra tenemos el rango [‘A’, ‘Z’], por lo tanto buscamos entradas que
oscile con las cantidades de letras ‘@’, ‘A’, ‘B’, ‘Y’, ‘Z’, ‘[‘ o por separado:
T14 = { “@AB“ }
T15 = { “YZ[“ }
Una vez ideados los tests, se procede a la etapa final, que consisten en ejecutar el
módulo o programa con esos valores y ver si se obtienen los valores esperados.
Obviamente hay algo en el código que hace que el cartel se escriba al reves,
y eso se nota en carteles con más de un carácter.
I.T.B.A. - 2000
Programación I - Clase 2 18
3.1. Modularización
v Dividir el problema original en pequeños subproblemas. Dichos subproblemas no
debe tener más que unas pocas líneas de código (jamás debe exceder el tamaño de
una pantalla, aproximadamente 20 líneas)
v Cada módulo debe realizar una única función, el nombre del mismo debe resumir lo
que hace (modularización)
3.2. Claridad
v Utilizar nombres claros para los identificadores de constantes, variables, y funciones
(no letras o nombres no significativos)
v Cada módulo debe ser documentado, incluyendo todos los comentarios suficientes
para comprender lo que hace. No olvidarse que en grandes proyectos el
programador que comienza un módulo no siempre es el que lo mantiene o modifica
posteriormente, por lo tanto la documentación deber ser más que clara.
3.3. Estructuración
v Utilizar las siguientes estructuras de control: secuencia, decisión e iteración. No usar
estructuras de salto incondicional (goto y variantes).
I.T.B.A. - 2000
Programación I - Clase 2 19
3.4. Eficiencia
v No basta con crear algoritmos inéditos. Es importante también ver algoritmos
realizados por otros programadores y discutir si son eficientes, claros y correctos. En
la materia se sugerirá la consulta de algoritmos para alentar el desarrollo del espíritu
crítico y tomar ideas sobre buen estilo de programación.
v No producir códigos que para generar una cierta eficiencia sean imposibles de
entender en su semántica. La lógica de los programas debe ser lo más importante
(sin por eso degradar la performance).
3.5. Correctitud
v Programar defensivamente, es decir no presuponer que ciertos valores son
imposibles o que ciertas acciones nunca van a ocurrir.
v El módulo desarrollado debe ser testeado utilizando técnicas desarrolladas para tal
fin. La calidad del test es más importante que la cantidad del mismo.
v Contemplar todos los posibles errores que puede tener un programa en tiempo de
ejecución, e informar para dichos casos un mensaje de error muy claro que permita
entender a qué se debió el error producido (Ej: valor fuera del rango, disquetera
abierta, etc.)
Importante:
I.T.B.A. - 2000
Programación I - Clase 3 1
Introducción
En este documento se detallan las técnicas Top-Down y Bottom_Up, para la
resolución de problemas y se presenta una breve introducción al Lenguaje C, que
comprende sus características básicas, su proceso de traducción y la forma de
estructurar un programa.
1. Estrategias de Programación
I.T.B.A. - 2000
Programación I - Clase 3 2
Ejemplo:
Se tiene el problema de dibujar un calendario completo para un año solicitado,
que debe ser posterior a 1900
Representación Gráfica:
calendario
pedirAnio imprimirCalendario
imprimirMes
calcularDiasDelMes indentarPrimeraLinea
Documentación de la Interfaz
I.T.B.A. - 2000
Programación I - Clase 3 3
I.T.B.A. - 2000
Programación I - Clase 3 4
2. Lenguaje de Programación C
El lenguaje C fue desarrollado por Dennis Ritchie en los laboratorios Bell, a
partir de ciertas ideas del lenguaje B, desarrollado previamente por Ken Thompson, el
cual a su vez estaba basado en el lenguaje BCPL, creado por Martin Richards en 1967.
Existían ciertas características del lenguaje que no estaban bien detalladas por
los autores, y como los desarrolladores de compiladores sólo utilizaban como referencia
el libro “The C Programming Language” escrito por Brian Kernighan y Dennis
Ritchie, surgían algunas ambigüedades entre los distintos compiladores. Así es como
los programadores encontraban que sus programas no podían ser realmente
multiplataforma porque existían variaciones entre los distintos compiladores del
lenguaje C. Para eliminar la libre interpretación de los implementadores de
compiladores C, se decidió estandarizar el lenguaje.
Importante:
En esta materia vamos a trabajar dentro del paradigma imperativo, con el
lenguaje ANSI C.
I.T.B.A. - 2000
Programación I - Clase 3 5
Aclaración:
Las funciones de la Biblioteca Estándar no forman parte del lenguaje, aunque
se garantizan que vienen con el paquete del mismo, por lo tanto para lograr un
ejecutable hay que linkeditarla
Programa Programa
fuente Preprocesador Compilador Linkeditor ejecutable
escrito
en C
§ Archivos de § Bibiliotecas
Encabezado § Programas
previamente
compilados
Aclaración:
Si todas las fases citadas anteriormente han resultado exitosas, se está en
condiciones de pedirle al sistema operativo que ejecute el programa obtenido. Así es
como el programa será cargado en Memoria Principal por un módulo del Sistema
Operativo (el cargador). En algún momento el Sistema Operativo le dará el control al
programa (la UCP empieza a ejecutar sus instrucciones)
I.T.B.A. - 2000
Programación I - Clase 3 6
2.2.1 El Preprocesador
Es un módulo separado del compilador de C, y tiene sus propias reglas y
sintaxis.
Los programas utilizan al preprocesador para incluir archivos de
encabezamientos (header files), expandir macros, definir símbolos, etc. (Recordar el
proceso de macro-expansión que ocurría en Z80 antes del ensamblado)
Toda directiva al preprocesador comienza con el símbolo #.
El preprocesador acepta directivas en cualquier lugar del programa, y permite
anidamientos entre directivas.
El mismo deja un archivo de salida transitorio (salvo que hayan habido errores)
para que el compilador pueda procesar.
2.2.2 El Compilador
Es un módulo que toma el archivo de salida transitoria que generó el
preprocesador y se encarga de chequear la sintaxis (reglas gramaticales) del lenguaje C
y hacer la traducción al lenguaje de máquina, generando un modulo objeto, no
ejecutable todavía. (Recordar el módulo zas de Z80)
• ¿Por qué es que no puede generar un programa ejecutable?
• ¿Qué cosas puede no poder resolver el compilador?
2.2.3 El Linkeditor
Es un modulo que toma el programa objeto generado por el compilador, y lo
vincula con librerías u otros módulos objeto, resolviendo las referencias externas. La
salida, de ser exitosa, es un programa listo para ejecutarse. (Recordar el link de Z80).
I.T.B.A. - 2000
Programación I - Clase 3 7
Sintaxis
#include <nombreDeArchivo>
ó bien
#include “nombreDeArchivo”
I.T.B.A. - 2000
Programación I - Clase 3 8
/*
* Programa hello.c Comentarios
*/
int
main (void)
{ Funciones
printf(“Hello World \n”);
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 3 9
Se considera que el nombre de un archivo puede pensarse formado por una parte
fija, llamada prefijo o base, y una parte opcional, compuesta de un punto y un sufijo.
Los nombres de los archivos deben comenzar con un caracter letra, continuando
con letras o números, sin sobrepasar el límite de 8 caracteres para la parte prefija.
Dichos nombres deberían contener solo letras minúsculas.
Por otra parte, se sugiere que los archivos que contienen código fuentes en
lenguaje C tengan el sufijo c , y que los archivos de encabezado cuenten con el sufijo h.
1ro: Prólogo que indique una descripción del propósito del contenido en dicho
archivo. Debe además contener autor, versión, referencias, etc.
4to: Funciones que conforman el programa. Las mismas deben aparecer en el orden
correspondiente al recorrido por niveles, de izquierda a derecha (BreadthFirst),
del árbol obtenido en el refinamiento de la metodología Top-Down aplicada.
I.T.B.A. - 2000
Programación I - Clase 3 10
Se deben evitar los comentarios triviales que surgen del código y los que no se
corresponden con el mismo (confunden más que si no estuvieran).
Ejemplos:
/*
* Esto es un hermoso comentario
* Sobre el bloque que va a comenzar a continuación
*/
/*
** Esto es un hermoso comentario
** Sobre el bloque que va a comenzar a continuación
*/
Ejemplos:
if (argc > 1)
{
/* Se toma el archivo desde la línea de comandos */
if (freopen (argv[1] , “r”, stdin) == NULL)
{
..........................
if (a == excepcion)
b = 1; /* Se devuelve el valor VERDADERO */
else
b = esPrimo(a) /* solamente se trabaja con números pares */
I.T.B.A. - 2000
Programación I - Clase 4 1
Introducción
1. Tipos de Datos
Tipo de un Dato
El tipo de un dato determina el conjunto de valores que puede tomar dicho dato
(dominio) y las operaciones que se pueden realizar sobre él.
Las instrucciones de una computadora para operar con números enteros no son
las mismas que las utilizadas para reales (recordar Assembler).
Aclaración
Los límites del dominio para un determinado tipo de dato están dados por la
arquitectura de la máquina.
Un compilador para un lenguaje tipado aprovechará los tipos básicos que ofrece
la arquitectura de computadora, pudiendo ofrecer tipos estructurados que se construyen
a partir de tipos simples, y pudiendo permitir que los usuarios definan sus propios tipos.
Exigir que el programador especifique el tipo de datos con el que va a trabajar permite
generar código en forma eficiente y facilita la etapa de compilación.
I.T.B.A. - 2000
Programación I - Clase 4 2
Los lenguajes tipados pueden ser fuertemente tipados (strongly typed) o no,
según la efectividad con que evite errores, o sea según cuan estricto es el compilador en
el manejo de tipos:
v Fuertemente tipado:
Sólo acepta expresiones seguras, no se permite asignar un tipo de datos a otro sin
una función de conversión previa.
Ejemplo: Pascal
v No fuertemente tipado:
Es menos estricto que el anterior, se pueden ver los datos de distintas formas.
Ejemplo: C
Aclaración
Ventaja:
A programadores expertos les da mayor flexibilidad.
Desventaja:
Puede ocasionar efectos colaterales debido a la evaluación de una expresión con
un valor inesperado.
I.T.B.A. - 2000
Programación I - Clase 4 3
v char
Utiliza un byte para el almacenamiento. Típicamente sirve para almacenar
caracteres (el código correspondiente a los mismos que maneja la máquina, por ejemplo
ASCII).
Importante
v int
Su tamaño refleja el tamaño de la palabra de la máquina. Típicamente en
arquitecturas de 16 bits es de 16 bits y en las de 32 de bits es de 32 bits.
I.T.B.A. - 2000
Programación I - Clase 4 4
Ejemplo:
El par signed/unsigned, como en el caso del char sólo sirve para desviar el rango
de valores posibles. A diferencia de éstos el default para los enteros es signed.
Ejercicio:
1. int
2. short
3. short int
4. long
5. long int
6. signed int
7. signed short
8. signed short int
9. unsigned
10. unsigned short
11. unsigned int
I.T.B.A. - 2000
Programación I - Clase 4 5
Respuesta:
1, 6
2, 3, 7, 8
4, 5
9, 11
10
Ejercicio:
1. char
2. unsigned char
3. signed char
Respuesta:
v float
Sirve para almacenar punto flotante de precisión normal
v double
Sirve para almacenar punto flotante de doble precisión.
Se le puede agregar el cualificador long, para aumentarle la precisión.
I.T.B.A. - 2000
Programación I - Clase 4 6
Ejemplo:
Nota
Los compiladores tienen en los archivos de encabezado limits.h y float.h los
rangos para los tipos aritméticos concretos para dicha implementación de arquitectura
de máquina.
Importante
La precisión indica cuantos dígitos significativos están disponibles en la
representación. Debido a la precisión, en una máquina dos números reales distintos
pueden ser considerados iguales (Ejemplo: con 5 dígitos significativos los números 1.0
y 1.000001 son iguales)
I.T.B.A. - 2000
Programación I - Clase 4 7
2. Expresiones
Expresión
Término
2.1 Constantes
Constantes o Literales
Tienen solamente un atributo: valor, que no puede cambiar durante la ejecución
y que debe pertenecer al dominio de algún tipo de dato soportado por el lenguaje.
2.1.1 Constantes en C
Recordando los tipos disponibles para C, veremos las constantes correspondientes
a cada tipo.
v Constantes Enteras
Las constantes enteras pueden ser escritas en los siguiente sistemas de
numeración: decimal, octal y hexadecimal.
I.T.B.A. - 2000
Programación I - Clase 4 8
Para poder especificar el tamaño de los tipos de las constantes enteras se utilizan
sufijos (no sensible): U ó u para indicar unsigned, y L ó l para long. Se pueden
combinar, pero siempre la U debe preceder a la L.
Para los enteros del sistema decimal, si no se especifica sufijo, el tipo asumido
será el primero de la siguiente lista que sea capaz de representar dicho valor:
int
long
unsigned long
int
unsigned int
long
unsigned long
32 ⇒ int
32L ⇒ long
100000 ⇒ long
0x6 ⇒ int (hexadecimal)
0171060 ⇒ unsigned int (octal, 62000 en decimal)
I.T.B.A. - 2000
Programación I - Clase 4 9
6.32 ⇒ double
6.32F ⇒ float
6.32L ⇒ long double
632 E-2 ⇒ double
v Constantes Caracter
La constante de caracter es de tipo int y se escribe como un caracter entre
comillas simples (su valor corresponde a la representación para la máquina, por ejemplo
ASCII).
Secuencia Significado
\a beep
\b backspace
\f comienzo de nueva página
\n comienzo de nueva línea
\r return (retorno al comienzo de la línea actual, sin avanzar)
\t tabulador (se mueve horizontalmente al próximo tab)
\0 caracter nulo (caracter con código ASCII cero)
\\ el propio caracter barra invertida
\’ el caracter comilla simple (sólo se usa en caracteres constantes)
\” el caracter comilla doble (sólo se usa en strings constantes)
\ddd el caracter cuyo código ASCII es el número octal ddd
\xhh el caracter cuyo código ASCII es el número hexadecimal hh
v Constantes Cadena
La constante cadena es una secuencia de cero o más caracteres, encerrados entre
comillas dobles. Las comillas no son parte de la cadena pero son necesarias para
delimitarlas.
Internamente se la representa con un caracter por cada uno de los caracteres que
la componen, más el caracter extra nulo ‘\0’. Su tamaño, en cantidad de bytes, es igual
a la cantidad de caracteres que contiene más 1.
I.T.B.A. - 2000
Programación I - Clase 4 10
Atención
Las cadenas pueden contener símbolos que deben ser escapados (como ocurre
con los caracteres) y en ese caso el símbolo de escape no es un caracter más de la
misma.
Ejemplo:
v Constantes de Enumeración
La constante de enumeración es una lista de valores enteros constantes. Por
default el primer nombre tiene el valor 0, y cada elemento tiene el valor anterior
aumentado en 1. Si se especifica algún valor, la enumeración aumentada en uno
comienza a partir de él.
Sintaxis:
enum nombreIdentificatorio { ListaDeConstantes }
I.T.B.A. - 2000
Programación I - Clase 4 11
Ejemplos
Ayudan a darle claridad al código. Resulta muy conveniente para tener valores
de constantes relacionadas entre sí, pudiendo luego utilizarlas en ciclos.
§ Las constantes enumerativas deben empezar con mayúscula o bien estar todas en
mayúsculas.
Ejemplo: Para una float usar 540.0 en vez de 540, así evitará que luego se
realice la conversión (casteo) implícita de int a float.
I.T.B.A. - 2000
Programación I - Clase 4 12
a) 0.5
b) 0515
c) ‘\052’
d) 27,822
e) ‘\xb’
f) “x”
g) 0x1f
h) 0xful
i) “8”
j) ‘8’
k) 8
Respuestas:
I.T.B.A. - 2000
Programación I - Clase 4 13
Sintaxis
#define nombre texto
Aclaración
Ejemplo:
El uso del número 0.21 para el cálculo del I.V.A. en varias partes del programa
dificulta la modificación futura para poder agregarle mayor precisión. Es mejor hacer:
Así, si en otro momento varía el porcentaje del impuesto, sólo se debe cambiar
esta línea en vez de todos los lugares donde se la referencia (obviamente hay que
recompilar):
#define IVA 0.25
Los identificadores para las constantes simbólicas deben estar formados por
mayúsculas.
I.T.B.A. - 2000
Programación I - Clase 4 14
2.2 Variables en C
Variables
Identificador
Nombre que se usa para representar variables, constantes, funciones y rótulos del
programa.
Para los nombre resueltos por el Linkeditor sólo se garantiza los primeros 6
caracteres.
I.T.B.A. - 2000
Programación I - Clase 4 15
§ Evitar tener identificadores que sólo difieran en una letra, por ser mayúscula en uno
y minúscula en otro (Ejemplo: cantidad y Cantidad)
Muy Importante
I.T.B.A. - 2000
Programación I - Clase 4 16
Ejemplo:
int cantidad, acumulador = 0, edad;
float sueldo;
char letra;
float delta = 0.001F;
Nota:
Observar qué chico es el lenguaje que, de las 32 palabras reservadas ya
conocemos 9 con sólo hablar de tipos de datos (más del 25%).
I.T.B.A. - 2000
Programación I - Clase 5 1
Operadores y Expresiones
Introducción
En el presente apunte se detallan los tipos de operadores existentes en lenguaje C
y se ejemplifica la forma de armar expresiones con los mismos.
1. Operadores en C
Operadores
Todos los operadores tienen una cierta precedencia respecto del resto: cuando
aparecen varios en una expresión, los que tienen mayor precedencia son evaluados antes
que los de menor precedencia.
Todos los operadores tienen un orden de evaluación: cuando en una
expresión aparecen varios operadores de la misma precedencia, dicho orden de
evaluación determina dónde empezar a evaluar.
I.T.B.A. - 2000
Programación I - Clase 5 2
Ejemplos
9.0 / 2
9 / 2.0
9.0 / 2.0
9/2
9%2
I.T.B.A. - 2000
Programación I - Clase 5 3
Ejemplo
§ Si se tiene una variable int x con valor 3, y otra variable double z con valor 1.5, al
evaluar las siguientes expresiones:
Para lenguaje C, todo valor igual a cero es interpretado como FALSE y todo
valor distinto de cero es interpretado como TRUE.
I.T.B.A. - 2000
Programación I - Clase 5 4
Tablas de Verdad
P not P P Q P and Q P Q P or Q
V F V V V V V V
F T V F F V F V
F V F F V V
F F F F F F
Leyes de De Morgan
Muy Importante
I.T.B.A. - 2000
Programación I - Clase 5 5
Ejemplo
§ Si se tiene una variable int x con valor 3, y otra variable double z con valor 1.5, al
evaluar las siguientes expresiones:
Ejemplo
§ Si se tiene una variable unsigned x con valor 9 y otra variable unsigned z con valor
1, al evaluar las siguientes expresiones:
x | z se obtiene el int 9
I.T.B.A. - 2000
Programación I - Clase 5 6
Muy Importante
Ejemplo
§ Si se tiene una variable int x con valor 9 y otra double z con el valor 12.5, al evaluar
las siguientes expresiones:
I.T.B.A. - 2000
Programación I - Clase 5 7
Nota
Muy Importante
I.T.B.A. - 2000
Programación I - Clase 5 8
Ejemplo
§ Si se tiene una variable double precio con valor 120.0 al evaluar la expresión:
Aclaración Importante
Ejemplo
I.T.B.A. - 2000
Programación I - Clase 5 9
Muy Importante
El operador de casteo NO cambia el tipo del operando sobre el cual está aplicado
Ejemplo 1:
Ejemplo 2:
sueldo= 1000;
Ejemplo 3:
parteEntera= 25.3;
I.T.B.A. - 2000
Programación I - Clase 5 10
Ejemplo:
Se tienen las variables int dividendo, int divisor inicializadas con los valores 9 y
2 respectivamente. Se tiene también otra variable double cociente.
En la expresión dividendo / divisor, como el operador “/ “ se aplica a dos
operandos del mismo tipo entero, se obtiene el resultado 4 de tipo también entero.
Luego se hace la asignación a una variable de tipo double, con lo cual el valor de
cociente queda en 4.0, ya que en la asignación 4 es promovido. Observar que se
obtuvo parte fraccionaria cero.
Para poder obtener el resultado 4.5 hay que castear alguno de los dos operandos
antes de que se evalúe la expresión dividendo / divisor.
ó bien
ya que se estaría casteando el resultado que ya es de tipo int 4 a tipo double 4.0.
I.T.B.A. - 2000
Programación I - Clase 5 11
Importante
Aclaración
Ejemplo
§ Si se tiene una variable int x con el valor 17 y otra variable double z con el valor
12.0 al evaluar la expresión:
§ Si se tiene una variable int suma que contiene la acumulación de los n elementos de
un conjunto, y el int n indica la cantidad de los mismos. Con la siguiente expresión
se puede obtener una forma “segura” de calcular el promedio:
I.T.B.A. - 2000
Programación I - Clase 5 12
Importante
I.T.B.A. - 2000
Programación I - Clase 5 13
Operadores Asociatividad
() [] Izq. a der.
! ~ ++ -- sizeof + (unario) - (unario) Der. a izq.
(casteo) *(indirección) &(dirección)
* / % Izq. a der.
+ - (ambos binarios) Izq. a der.
<< >> Izq. a der.
< <= > >= Izq. a der.
== != Izq. a der.
& Izq. a der.
^ Izq. a der.
| Izq. a der.
&& Izq. a der.
|| Izq. a der.
?: Der. a izq.
Todos los operadores de asignación Der. a izq.
, Izq. a der.
Muy Importante
§ &&
Ya que se asegura que son LAZY
§ ||
§ ?: (operador condicional)
§ , (operador coma)
I.T.B.A. - 2000
Programación I - Clase 5 14
§ Todo operador binario (excepto “.” y “->”) debe estar separado por un blanco de los
operandos sobre los cuales está aplicado.
§ Colocar la primera expresión del operador condicional entre paréntesis, para mejorar
la claridad del código.
§ Si se considera que una expresión es muy difícil de leer, separarla en varias líneas,
haciendo el corte en el operador de más baja precedencia.
I.T.B.A. - 2000
Programación I - Clase 5 15
Ejercicio 1
Suponiendo arquitectura de 16 bits, indicar valor y tipo para cada una de las
siguientes expresiones:
§ 23 / 8 Rta: int 2
§ 23 % 8 Rta: int 7
§ 8 % 23 Rta: int 8
§ 3 * 5.0 Rta: double 15
§ 3 * 5f Rta: no compila
§ 3 * 5.0f Rta: float 15
§ 9–5/4–2 Rta: int 6
§ 2 + 2 * (2 * 2 – 2 ) % 2 / 2 Rta: int 2
§ 10 + 9 * ( ( 8 + 7 ) % 6 ) Rta: int 37
§ sizeof( char ) Rta: int 1
§ sizeof( ‘a’) Rta: int 2
§ 4 * ( ( 5 * 6 % 7 * 8 ) – 9 ) – 10 Rta: int 18
Ejercicio 2
Si la variables i, j, k son declaradas de tipo int, indicar para cada una de las
siguientes expresiones su valor y tipo
I.T.B.A. - 2000
Programación I - Clase 5 16
Ejercicio 3
Rta:
Este código no funciona porque la multiplicación se realiza con aritmética de
enteros ( el tipo de la constante 1000 es int), y el resultado sobrepasa el rango, ciclando
antes de ser promovido al tipo long debido a la asignación. Una solución hubiera sido
usar la expresión:
Nota:
Tampoco hubiera servido long c = (long) ( 1000 * 1000) porque es lo mismo
que esperar a la conversión implícita.
Ejercicio 4
C = * ( F − 32º )
5
9
Se la codificó por medio de la siguiente expresión, pero al evaluarla siempre se
obtiene el valor 0. Explicar cuál es el error y proponer una solución.
Rta:
Esta expresión siempre se evalúa como cero porque la división entre 5 y 9 es
entera, y el valor que se obtiene de la misma es el int 0. La forma correcta hubiera sido
usar la expresión:
gradosCelcious = (double) 5 / 9 * (gradosFarenheit -32)
o bien
gradosCelcious = 5.0 / 9 * (gradosFarenheit -32)
I.T.B.A. - 2000
Programación I - Clase 5 17
Ejercicio 5
Rta:
Se convierten todos los tipos char a int y se los opera con aritmética de enteros.
Finalmente el resultado que es de tipo int se lo castea a char (demoted) para asignarlo a
la variable upper. Siempre que en una expresión tiene lugar un tipo char o short se pasa
a int para poder aplicarle operadores.
Ejercicio 6
(x <<= 1) + ( z >> x)
Rta:
El operador + no garantiza cuál de los dos operandos será evaluado primero.
Consejo:
Usar expresiones seguras. No asumir nada que no esté garantizado, porque
deja de ser portable.
I.T.B.A. - 2000
Programación I - Clase 5 18
Ejercicio 7
(x != 0 ) && ( z % x == 0)
Rta:
Si z es múltiplo de x en forma segura, o sea sin correr los riesgos de una división
por cero.
Ejercicio 8
Rta:
Si el valor que contiene la variable year representa un año bisiesto.
Ejercicio 9
Se ha declarado la variable de tipo int valor, inicializada con el valor 15. Indicar
si la siguiente expresión sirve para evaluar si dicho valor está comprendido entre 1 y 10
inclusive:
0 < valor < 11
Rta:
No sirve para testear si valor está entre 1 y 10, ya que al evaluar esta expresión
formada por dos operadores de igual precedencia, se aplicaría la asociatividad de
izquierda a derecha: ( 0 < valor ) < 11. La sub-expresión izquierda se evalúa como 1, y
la expresión 1 < 11 se evalúa como 1. Pero obviamente 15 NO está en el rango pedido y
debió haberse obtenido como resultado de dicha expresión el valor 0 (interpretado
como FALSE).
La forma correcta hubiera sido:
I.T.B.A. - 2000
Programación I - Clase 5 19
Ejercicio 10
§ !(x == 2 || x ==5 )
§ x != 2 || x != 5
Rta:
NO. Las leyes de De Morgan aplicadas a la primera expresión nos hacen obtener
la expresión:
Ejercicio 11
Indicar para qué valores la variable int x hacen que la expresión sea
interpretadas como verdadera:
( x != 4 ) || ( x != 17 )
Rta:
Esta expresión siempre se evalúa verdadera ya que hay dos casos a considerar
Atención:
Si lo que se quería era evaluar como verdadera cuando x no fuera ni 4 ni 17,
debió usarse la expresión ( x != 4 ) && ( x != 17 )
No MENOSPRECIAR este tipo de errores: Por medio de caja blanca estos errores
pueden ser corregidos, tenemos pues todas las herramientas necesarias para no
dejar que se produzcan.
I.T.B.A. - 2000
Programación I - Clase 5 20
Ejercicio 12
Indicar en palabras qué intentan hacer cada una de las siguientes expresiones,
considerando la variable unsigned x inicializada con algún valor y la variable unsigned
nroBit inicializada con un valor entre 0 y 7 . (Nota: consideraremos el bit 0 como el de
más a la derecha).
Ejercicio 13
Rta:
Queda en el byte el valor 1 (fijarse en la tabla ASCII). El compilador sólo indica
un WARNING sobre el truncamiento de la constante debido a que el lenguaje C es
“typed” pero NO “strongly typed”
I.T.B.A. - 2000
Programación I - Clase 5 21
Ejercicio 14
Dado el siguiente programa indicar paso a paso, los valores que va tomando la
variable x:
int
main(void)
{
int x = OPERANDO1 && OPERANDO2;
x = OPERANDO1 || OPERANDO2;
x = ! OPERANDO1;
x = ! OPERANDO2;
return 0;
}
Rta: 0, 1, 0, 1
Ejercicio 15
#define PI 3.14159
int
main(void )
{
double radio;
double volumen;
radio = 5;
volumen= 4/3 * PI * radio * radio * radio;
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 5 22
Rta:
#1 “nombreArchivo.c”
int
main(void )
{
double radio;
double volumen;
radio = 5;
volumen= 4/3 * 3.14159 * radio * radio * radio;
return 0;
}
Ejercicio 16
#define IVA_A 19
#define IVA_B 1.5
#define IVA_TOTAL IVA_A + IVA_B
int
main(void)
{
float precio= 100.75;
float incremento= precio * IVA_TOTAL / 100;
precio+= incremento;
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 5 23
Rta:
#1 “nombreArchivo.c”
int
main(void)
{
float precio= 100.75;
float incremento= precio * 19 + 1.5 / 100;
precio+= incremento;
return 0;
}
Ejercicio 17
Rta:
#define IVA_A 19
#define IVA_B 1.5
#define IVA_TOTAL (IVA_A + IVA_B)
int
main(void)
{
float precio= 100.75;
float incremento= precio * IVA_TOTAL / 100;
precio+= incremento;
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 6 1
Introducción
En este apunte se muestran formas básicas para el manejo de los datos de
entrada y salida de un programa en Lenguaje C.
Todos los lenguajes deben ofrecer instrucciones o funciones para realizar dichas
operaciones.
I.T.B.A. – 2000
Programación I - Clase 6 2
solicitud del
servicio
provisión
del servicio
Supongamos ahora un programa llamado proof que lee desde teclado y escribe
en pantalla. Si quisiéramos que los datos entraran desde un cierto archivo llamado
datos.txt, podríamos usar el mismo programa siempre y cuando le indiquemos al
sistema operativo que reemplace la entrada estándar (teclado) considerada como un
archivo, por otro archivo llamado datos.txt.
Se podría hacer algo parecido si se quisiera que la salida de datos no fueran a la
salida estándar (pantalla) sino a un cierto archivo llamado out.dat.
I.T.B.A. – 2000
Programación I - Clase 6 3
Ejemplo
siendo programa1 el que posee la salida estándar que debe ser conectada con la entrada
estándar del programa2 El sistema operativo manejará todos los detalles para lograr el
efecto deseado.
I.T.B.A. – 2000
Programación I - Clase 6 4
Ejemplo
Si quisiéramos que la salida estándar del programa inicial pase a ser la entrada
estándar del proceso final, deberíamos escribir:
Con esto se logra que todos lo datos que el programa inicial envíe a su salida
pasen como entrada al programa final.
Más Ejemplos
I.T.B.A. – 2000
Programación I - Clase 6 5
Ejercicio 1
Rta:
Se obtiene por pantalla, el contenido del archivo telefono ordenado
ascendentemente y mostrado de a páginas.
Ejercicio 2
Rta:
El listado de directorio ordenado ascendentemente y mostrado de a páginas.
1.2 Bufferización
Típicamente en un bajo nivel, existen rutinas dependientes del sistema operativo
(no multiplataforma) que manejan los dispositivos particulares de teclado, discos, etc.
I.T.B.A. – 2000
Programación I - Clase 6 6
Esto parecería ser una dificultad, pero en realidad no lo es porque nadie haría un
front-end con pantallas deslizables y manejo de mouse en ANSI C. Por lo tanto la
necesidad de responder inmediatamente cuando se presiona una tecla (sin esperar a que
se presione Return) no es nuestro problema ya que no se trata de una implementación
multiplataforma. Cuando hablamos de multiplataforma (alcance de esta materia) nos
centramos en el back-end.
I.T.B.A. – 2000
Programación I - Clase 6 7
Ejemplo
#include <stdio.h>
int
main(void)
{
int dividendo= 12;
int divisor= 0;
if (divisor == 0)
fprintf(stderr, "división por cero\n"); /* Error estándar */
else
printf("%d", dividendo / divisor); /* Salida estándar */
printf("Gracias por usar este software\n"); /* Salida estándar */
return 0;
}
I.T.B.A. – 2000
Programación I - Clase 6 8
#include <stdio.h>
I.T.B.A. – 2000
Programación I - Clase 6 9
Aclaración
getchar puede estar implementado como una macro.
Para saber cuando finaliza la entrada de datos, se trabaja con un valor distintivo,
que obviamente no debe coincidir con el código de máquina de ningún caracter (no debe
estar comprendido entre 0 y 255). Para normalizar dicha marca se utiliza la constante
simbólica EOF (end of file) que generalmente vale –1, pero que cada sistema puede
definirla con un valor diferente (el valor de dicha constante se encuentra en el archivo
stdio.h, pero las aplicaciones, para que sean portables, usarán la constante EOF en vez
de su valor).
Debido a que getchar devuelve un número entero (por el EOF), la variable que
reciba el valor devuelto por getchar debe ser de un tipo suficientemente grande para
poder almacenarlo: int en lugar de char.
Muy Importante
Para ANSI, la constante EOF representa cualquier valor negativo, no
necesariamente el valor –1.
Ejemplo
Rta: Como != tiene mayor precedencia que la asignación, primero se evalúa getchar()
!= EOF y el valor 0 o 1 resultante se asigna a c, que no se lleva el caracter leído.
I.T.B.A. – 2000
Programación I - Clase 6 10
Ejemplo
y en el buffer quedará D↵
y el buffer quedará ↵
I.T.B.A. – 2000
Programación I - Clase 6 11
Aclaración
Ejemplo:
El siguiente fragmento coloca en la salida el flujo PA↵
............
putchar (‘P’);
putchar (65);
putchar (‘\n’);
............
I.T.B.A. – 2000
Programación I - Clase 6 12
Sintaxis de la invocación:
I.T.B.A. – 2000
Programación I - Clase 6 13
Símbolo Formato
- Alineamiento a izquierda
0 Para rellenar con ceros
Un número (ancho) Se imprime en un campo de al menos este ancho y se completa
con blancos a izquierda o a derecha según corresponda.
Si el dato supera este ancho, se ignora la especificación.
. Separa el ancho de la parte entera de la precisión
Un número (precisión) Nro. de dígitos decimales (para punto flotante), nro. máximo
de caracteres a imprimir (para cadenas) o nro. mínimo de
dígitos a imprimir (para enteros)
Ejemplo
I.T.B.A. – 2000
Programación I - Clase 6 14
Recordar que la barra invertida es una carácter de escape para que el compilador
interprete caracteres en tiempo de compilación. En este caso dicho carácter no tiene
por qué servir ya que lo tiene que interpretar la función printf en tiempo de ejecución.
Para implementar un ancho de campo indicado por una variable (no una
constante como “%8d” ), y poder especificarlo en tiempo de ejecución, hay que usar
printf(“%*d”, ancho, valor), donde el asterisco indica que un valor int de la lista de
argumentos variable debe ser usado para el ancho de campo.
Recomendamos leer el uso de printf en las secciones 7.2 y B1.2 del texto de
Kernighan & Ritchie.
Ejemplo
imprimiría:
num vale 65*
num vale 65*
num vale 65 *
num vale A*
num vale 00065*
IMPREDECIBLE !!!
I.T.B.A. – 2000
Programación I - Clase 6 15
Ejemplo
imprimiría:
Ejemplo
imprimiría:
I.T.B.A. – 2000
Programación I - Clase 7 1
Instrucciones de Decisión
Introducción
En todo lenguaje del paradigma imperativo existen instrucciones de control para
cambiar el flujo de control secuencial hacia otra secuencia. En el presente documento se
detallará la forma de cambiar el control en base a la evaluación de una condición.
1. Proposiciones y Bloques
Proposición
Cabe aclarar que, aunque una proposición sea legal en C, puede resultar inútil si
se pierde el valor de su respuesta.
Ejemplo
Se usa un bloque cuando existe una acción que consiste en varios pasos.
Como ya hemos visto anteriormente, la propia función main está constituida por
un bloque.
I.T.B.A. - 2000
Programación I - Clase 7 2
Aclaración Importante
Ejemplos
a += 4; /* proposición simple */
2. Control de Flujo
En un lenguaje imperativo para una arquitectura de Von Neumann, el flujo de
control para la ejecución de un proceso es secuencial: las instrucciones se ejecutan una a
una en el orden en que aparecen (recordar el ciclo de CPU, en el cual el fetch ya
preparaba el registro PC con la dirección de la próxima instrucción a levantar).
I.T.B.A. - 2000
Programación I - Clase 7 3
2.1.1 Proposición if
Sintaxis
línea de control
if (expresión)
proposición;
cuerpo
Ejemplo
if ( a > 0 )
printf(“Numero positivo\n”);
I.T.B.A. - 2000
Programación I - Clase 7 4
Sintaxis
línea de control
if (expresión)
proposicion1;
else
proposición2; cuerpo
Ejemplo
if (a%2 == 0)
printf(“Numero par\n”);
else
printf(“Numero impar\n”);
I.T.B.A. - 2000
Programación I - Clase 7 5
Ejemplo
if ( a > 0 )
if (b > 1)
x= 5;
else x= 4;
Pregunta:
if ( a > 0 )
{
if (b > 1)
x= 5;
}
else x= 4;
Muy Importante
if ( a > 0 )
if (b > 1)
x= 5;
else; HORRIBLE!!!
else x= 4;
I.T.B.A. - 2000
Programación I - Clase 7 6
Ejemplo de Anidamiento
Sintaxis
línea de control
switch (expresión)
{
case exp_constante1: proposiciones;
case exp_constante2: proposiciones; cuerpo
case exp_constante3: proposiciones;
........................
case exp_constanteK: proposiciones;
default: proposiciones;
}
I.T.B.A. - 2000
Programación I - Clase 7 7
Importante
Las expresiones constantes utilizadas para las cláusulas case pueden contener
operaciones, pero éstas deben tener un valor entero constante (resuelto en tiempo de
compilación). No pueden ser variables, ni tipos distintos de enteros.
Consejo
Aunque la cláusula default es optativa, para evitar la posibilidad de ignorar
algún caso inesperado, resulta de buena programación incluirla en toda proposición
switch, aunque se crea haber contemplado todas las posibilidades,
Ejemplo
El siguiente código imprime el nombre de un color identificado entre 0 y 7, de
acuerdo a su descomposición en potencias de 2, donde los colores primarios son 1,2 y 4.
I.T.B.A. - 2000
Programación I - Clase 7 8
Rta:
Aparecería en pantalla
violeta
negro
blanco
En un switch el break resulta muy útil para salir una vez que se ha llegado al
case correcto, si es que no se quiere seguir ejecutando las proposiciones restantes. Con
el break, el programa continúa con la proposición siguiente al switch.
I.T.B.A. - 2000
Programación I - Clase 7 9
............................
switch (expresión)
{
case exp_constante1:
proposiciones;
break;
case exp_constante2:
proposiciones;
break;
..................................
default: proposiciones;
}
............................
Ejemplo
Mejoramos nuestro ejemplo para que sólo imprima el color correspondiente:
I.T.B.A. - 2000
Programación I - Clase 7 10
Ejemplo
switch (mes)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
printf(“31 dias\n”);
break;
case 4:
case 6:
case 9:
case 11:
printf(“30 dias\n”);
break;
case 2:
printf(“28 o 29 dias\n”);
break
default:
printf(“El numero de mes es incorrecto\n”);
}
Ejemplo
int naipe;
switch (naipe)
{
case 1: printf(“as\n”); break;
case 11: printf(“jack\n”); break;
case 12: printf(“reina\n”); break;
case 13: printf(“rey\n”); break;
default: printf(“%d\n”, naipe); break;
}
I.T.B.A. - 2000
Programación I - Clase 7 11
Consejo 1
Para preguntar por variables booleanas no hace falta indicar la igualdad, lo cual
sería una redundancia.
Ejemplo:
En lugar de preguntar
if (flag != 0 )
printf("es verdadero");
else
printf("es falso");
if (flag )
printf("es verdadero");
else
printf("es falso");
Consejo 2
No siempre es necesario usar una instrucción if else para asignarle a una variable
un resultado booleano, es mucho más elegante hacerlo directamente usando los
operadores correspondientes
Ejemplo:
En vez de
I.T.B.A. - 2000
Programación I - Clase 7 12
Consejo 3
Para condiciones excluyentes no usar if paralelos porque es ineficiente (se pierde
tiempo evaluando condiciones que ya se saben falsas).
Ejemplo:
No hacer
sino
Consejo 4
Cuando se trabaja con números reales hay que introducir un rango de
tolerancia y en vez de utilizar una igualdad o no igualdad, usar un delta (en el sentido
matemático propiamente dicho)
Ejemplo:
Si tenemos una variable punto flotante sobre la que hemos estando realizando
operaciones, posiblemente no posea el valor que nosotros creemos que tiene. Tal vez
pensamos que almacena un 5 y en realidad se guardó un 4.9999999999999999 o un
5.000000000001. Si más tarde queremos tenemos que tomar una decisión en base a si la
misma contiene el valor 5 o no, la comparación
if (a == 5) será impredecible
En estos casos hay que usar una tolerancia en el sentido matemático: | a - 5| < 10-12
I.T.B.A. - 2000
Programación I - Clase 7 13
Problema
Al ejecutar el siguiente fragmento de código, aparece en la salida el cartel “a es
positivo”, ¿dónde se encuentra el error ?
int a = -3;
if (a > 0 );
printf( “a es positivo”);
Rta:
El problema es que la proposición if no tiene cuerpo, por lo tanto el printf está
fuera de su alcance.
Ejercicio
Indicar cuál es el error de compilación (sintaxis) de las siguientes instrucciones:
§ if a > b { c = 0;}
§ if (a > b) c = 0 else b = 0;
Rta:
En la primera se debe usar (a >b) y en la segunda el “;” en la proposición c = 0;
Ejercicio
Explicar por qué el siguiente fragmento de código siempre imprime “mujer”,
aunque los valores para representar los sexos sean 1 para la mujer y 2 para el hombre:
int sexo;
.............................
sexo = getint(“\nIngrese el sexo (1: femenino, 2: masculino)”);
if ( sexo = 1)
printf(“\nEs mujer\n”);
else
printf(“\nEs hombre\n”);
Rta:
Porque sexo siempre vale 1 (hay una asignación en lugar de una comparación)
I.T.B.A. - 2000
Programación I - Clase 7 14
Ejercicio
int x = 7, y = 8, z = 2;
Rta:
Se imprime: La segunda expresion es VERDADERA.
Cabe destacar que sólo realiza las comparaciones (z>x) y luego (y>z),
resolviendo ambos casos con dicho resultado (es evaluación LAZY)
Tenemos 1 libro
Tenemos 5 libros
I.T.B.A. - 2000
Programación I - Clase 7 15
Ejercicio
Rta:
#include <stdio.h>
#include “getnum.h”
int
main(void)
{
int horasExtra, totalHoras;
if (horasExtra > 0)
printf(“Sueldo extra:\t %.2f\n”, horasExtra *
PRECIO_HORA_EXTRA);
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 7 16
Se calcula con un
seguimiento en
Se calcula a través papel o ejecutándolo
del enunciado en una computadora
Conva-
Rango de Valores Ti Valor Esperado Valor Obtenido
lidación
Cobertura
S Base= 412 S Base= 412
de horasExtra > 0 T1={45} ü
S Extra= 86 S Extra= 86
Sentencias
horasExtra>0
T1 ü ü ü
Cobertura verdadero
de
Decisiones horasExtra>0
T2={40} S Base= 412 S Base= 412 ü
falso
T2 ü ü ü
Cobertura
S Base= 412 S Base= 412
de horasExtra= 0, 1, 2 T3={41} ü
S Extra= 17.20 S Extra= 17.20
Límites
S Base= 412 S Base= 412
T4={42} ü
S Extra= 34.40 S Extra= 34.40
Ejercicio
Deporte Temperatura
Ajedrez Menor o igual a -20° C
Ski Mayor a -20°C y menor o igual a 10°C
Tenis Mayor a 10°C y menor o igual a 25°C
Golf Mayor a 25°C y menor o igual que 30°C
Natación Mayor a 30°C
I.T.B.A. - 2000
Programación I - Clase 7 17
Rta:
#include <stdio.h>
#include “getnum.h”
int
main(void)
{
int temperatura;
return 0;
}
¿No sería conveniente usar switch para que no haya tanto anidamiento?
I.T.B.A. - 2000
Programación I - Clase 7 18
Se calcula con un
seguimiento en
Se calcula a través papel o
del enunciado ejecutándolo en una
computadora
I.T.B.A. - 2000
Programación I - Clase 7 19
Ejemplo:
En vez de
§ Las palabras clave que son seguidas de expresiones entre paréntesis deben separase
del parámetro izquierdo por medio de un espacio, excepto en el caso del operador
sizeof.
I.T.B.A. - 2000
Programación I - Clase 7 20
§ Debe haber sólo una proposición por línea, al menos que las proposiciones estén
altamente relacionadas (como el caso del break en las cláusulas case del switch).
Ejemplo:
Usar if (esNroValido(n))
en vez de if (esNroValido(n) != 0)
ó en vez de if (esNroValido(n) == 1)
Justamente para poder hacer este uso de variables booleanas, es muy importante
que los nombre de las mismas sean significativos y representen sin ambigüedad cuándo
resultan verdadera y cuándo falsas.
Ejemplo:
Si una función hace la validación de un número, no usar como nombre
para la misma numero, o darNumero, sino esNumeroValido.
§ Aunque el lenguaje permite resumir varias acciones en una sola expresión, lo que se
debe priorizar es la claridad y la mantenibilidad del código
Ejemplo:
Usar a = b + c;
d = a + r;
d = (a = b + c) + r;
§ Las llaves de un bloque deben estar siempre en una línea separada, y cada
proposición del mismo debe estar en línea aparte, e indentada respecto de la llave.
I.T.B.A. - 2000
Programación I - Clase 8 1
Instrucciones de Repetición
Introducción
En todo lenguaje del paradigma imperativo existen instrucciones de control para
cambiar el flujo de control secuencial hacia otra secuencia. En el presente documento se
detallará la manera de ejecutar repetidamente una misma secuencia en base a la
evaluación de cierta condición que la controla, hasta salir de ella iniciando la ejecución
de una nueva secuencia.
1. Estructuras de Repetición
Permiten cambiar el flujo de control de un programa para repetir una cierta
cantidad de veces una proposición determinada. Una vez terminado el ciclo repetitivo se
continúa nuevamente con otra secuencia. El lenguaje C ofrece tres estructuras de
repetición:
v while
v for
v do while
Sintaxis
línea de control
while (expresión)
proposición;
cuerpo
Notas:
§ Puede ocurrir que no se ejecute ni una sola vez
§ Es responsabilidad del programador lograr que el ciclo se ejecute una cierta cantidad
finita de veces, ya que de lo contrario no sería un algoritmo. Es importante
asegurarse de que, una vez que se haya entrado al ciclo, alguna ejecución del cuerpo
cambie la expresión de control al valor cero, para salir del mismo.
I.T.B.A. - 2000
Programación I - Clase 8 2
Ejemplo
#include <stdio.h>
#define DELTA 'a' - 'A'
int
main(void)
{
int caracter;
return 0;
}
Ejemplo
int
main(void)
{
int caracter, cantidadBlancos=0;
I.T.B.A. - 2000
Programación I - Clase 8 3
Sintaxis
línea de
for ( expresion1; expresion2; expresion3 )
control
proposición;
cuerpo
La expresión1 se evalúa una sola vez al comienzo del ciclo (no hay restricción
en cuanto a su tipo), la expresión2 se evalúa en cada iteración y, análogamente al
while, cuando se evalúa como cero termina el ciclo. La expresión3 se evalúa justo antes
de volver a comenzar el ciclo.
Cualquiera de las tres expresiones que figuran en el for pueden estar ausentes,
pero los puntos y comas son obligatorios.
expresion1;
while (expresion2)
{
proposición;
expresion3;
}
Pregunta:
Rta:
expresion1;
while (1)
{
proposición;
expresion3;
}
I.T.B.A. - 2000
Programación I - Clase 8 4
Pregunta:
De no existir ninguna de las tres expresiones del for, ¿a qué sería equivalente?
Rta:
for ( ; ; )
{
.................
}
es equivalente a
while (1)
{
.................
}
El operador coma resulta muy útil cuando se desean asignar valores a diferentes
variables en la primera parte del ciclo for.
Recordar que los operandos del operador coma se evalúan de izquierda a derecha
y el resultado es del tipo y valor del operando de la derecha.
expresion1
I.T.B.A. - 2000
Programación I - Clase 8 5
Ejemplo
Este fragmento de código coloca en la salida la letra ‘A’6 veces:
int
main(void)
{
int cant = 20;
return 0;
}
Ejemplo
Esto es un ciclo infinito. Inadmisible, pues cant nunca cambia su valor:
int
main(void)
{
int cant = 20;
return 0;
}
Ejemplo
Esto es un ciclo infinito. Inadmisible, ya que la expresión2 ausente se toma
como verdadera:
int
main(void)
{
int cant = 20;
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 8 6
Ejemplo:
En ambos casos el ciclo se ejecuta 10 veces, pero eso se ve mucho más claro en
el ciclo for.
i=0;
while(i<10)
{
...
i++;
}
Sintaxis
do cuerpo
proposición;
while (expresión);
línea de control
I.T.B.A. - 2000
Programación I - Clase 8 7
Ejemplo
do
{
printf (“pulse S para abandonar este ciclo \n”);
}
while ( (c=getchar()) != EOF && c != ’S’ && c != ‘s’ );
Termina la ejecución del ciclo más anidado que encierre directamente dicha
proposición.
Ejemplo 1
while ( expresion1)
{
if ( expresion2 )
break; De verificarse expresion2, el break haría
.... continuar el flujo de control a lo que sigue
} al while (corta la ejecución del while)
....
I.T.B.A. - 2000
Programación I - Clase 8 8
Ejemplo 2
while (expresion1)
{
for( ; ; )
{
if ( expresion2 )
break; De verificarse expresion2, el break
.... continuaría con el flujo de control que
} le sigue al for. Sólo corta la ejecución
..... más anidada (sigue dentro del while)
}
Ejemplo 3
while ( expresion1)
{
switch (expresion2)
{
case cte1: ....; break;
case cte2: ...; break; De ejecutarse algún break, sólo
default: ......; se saldría fuera del switch, pero
} continuaría dentro del while, ya
....... que estos breaks no están
} contenidos directamente en el
while (sólo sale del más anidado)
I.T.B.A. - 2000
Programación I - Clase 8 9
Esquemáticamente
while (expresión)
{
....
continue; Si se alcanzara este continue, se saltaría a la
.... línea de control y las proposiciones que le
.... siguen al continue serían salteadas
}
do
{
....
....
continue; Si se alcanzara este continue, se saltaría a la
.... línea de control del ciclo que lo contiene
directamente, salteando las proposiciones
} while (expresión) que le siguen
Como se pudo observar, el uso del continue hace que los ciclos for y while
no funcionen en forma equivalente, ya que en el while no se evaluaría la expresión3 (si
forma parte del cuerpo del mismo y está más abajo que el continue) y se pasaría
directamente a la evaluación de la expresión, para saber si se continúa dentro del ciclo
o no.
I.T.B.A. - 2000
Programación I - Clase 8 10
Aclaración
Existe un esquema básico que sirve para realizar un ciclo, que consiste en:
1) Pedir al usuario el ingreso de un dato
2) Si el dato coincide con un cierto “centinela” prefijado, termina el ingreso de datos,
caso contrario se procesa el dato y se sigue en el ciclo.
while (1)
{
printf( “Ingrese el dato [se termina con ... ]” );
valor = lecturaDato();
if (valor == centinela)
break;
procesarDato();
}
claramente, el uso del salto incondicional break enturbia la semántica del código, y
sería mejor reemplazarla por una salida manejada por la línea de control del ciclo:
I.T.B.A. - 2000
Programación I - Clase 8 11
Sintaxis
goto rotulo;
rotulo: proposición;
Todo lenguaje imperativo ofrece una forma de cambiar el flujo de control hacia
otra parte del programa en forma incondicional
Aunque el lenguaje C ofrece su uso por medio del goto, no alienta su utilización
porque los programas que los utilizan dejan de tener una metodología estructurada y
oscurecen notablemente su semántica
Muy Importante
Es un error muy grave asumir que las variables así declaradas empiezan
teniendo automáticamente un valor por default: No se les asigna directamente ni cero, ni
ningún otro valor especial por el solo hecho de haber sido declaradas. Son creadas en el
stack y contienen cualquier información. Por lo tanto si se las va usar como
acumuladores, contadores, etc., habrá que asignarles el valor correspondiente antes de
usarlas para tal fin
I.T.B.A. - 2000
Programación I - Clase 8 12
int
main(void)
{
int cantidadDeLetras;
int letra;
int
main(void)
{
int cantidadDeLetras= 0;
int letra;
ó bien
int
main(void)
{
int cantidadDeLetras;
int letra;
cantidadDeLetras=0;
while((letra= getchar() ) != EOF)
cantidadDeLetras++;
printf(“Ud. ha ingresado %d simbolos\n”, cantidadDeLetras);
return 0;
}
¿Por qué no hizo falta inicializar o asignarle valor alguno a la variable letra?
Porque letra toma un valor desde la entrada estándar ANTES de ser usada.
I.T.B.A. - 2000
Programación I - Clase 8 13
Ejercicio
a) int a=0;
while ( a <= 4)
resultado += a;
a++;
Rta:
b) El cuerpo del while es vacío: cuando se pulsa “Y” aparece el cartel, porque el printf
está fuera del ciclo
I.T.B.A. - 2000
Programación I - Clase 8 14
Ejercicio
int
main(void)
{
int tope, recorrido;
return 0;
}
#include <stdio.h>
#include “getnum.h”
#define MULTIPLO 3
int
main(void)
{
int tope, recorrido;
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 8 15
int
main(void)
{
int tope, recorrido = MULTIPLO;
return 0;
}
§ Si la preposición while tiene cuerpo vacío, éste debe estar en una línea separada y
comentado para que quede claro que no se trata de una omisión.
I.T.B.A. - 2000
Programación I - Clase 9 1
Introducción
En este documento se presentan distintos ejercicios, aplicando las proposiciones
de control de flujo vistas hasta el momento. Creemos de suma importancia observar
códigos ya realizados por programadores con experiencia, antes de comenzar a
programar los propios. En algunos casos se efectúa el testeo correspondiente, quedando
a cargo del alumno realizarlo en el resto de los ejercicios.
Ejercicio 1
Escribir un programa que sume los dígitos de un entero positivo, ingresado
desde la entrada estándar.
#include <stdio.h>
#include “getnum.h”
int
main(void)
{
int nro, total= 0;
do
{
nro = getint("\nIngrese un numero entero positivo:");
}
while (nro <= 0);
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 9 2
Valor Valor
Rango de Valores Ti Convalidación
Esperado Obtenido
Con un númro
Cobertura
mayor a 0 se pasa
de T1={15 } 6 6 ü
por todas las
sentencias
sentecias
sigue esperando
( nro<= 0 ) T2={ -1 } entrada
un entero ü
verdadero inválida
positivo
( nro<= 0 )
Cobertura T3={ 235} 10 10 ü
falso
de
decisiones/
condiciones
( nro > 0 )
en el ciclo while
T3
siempre empieza ü ü ü
falso y termina
verdadero
T2 - - -
Cobertura sigue esperando
( nro<= 0 ) ⇒ entrada
de T4={0} un entero ü
tomar –1, 0, 1 inválida
límites positivo
T5={1} 1 1 ü
I.T.B.A. - 2000
Programación I - Clase 9 3
Ejercicio 2
Escribir un programa que imprima en la salida estándar la tabla de
multiplicación de los números hexadecimales entre 0 y F, de acuerdo al siguiente
formato:
| 1 2 3 4 5 6 ...... E F
--- | ------------------------------------------------------------------------------------------------
1 | 1 2 ...
2 | 2 4 6 ...
3 | 3 6 9 C ...
........ ..... ........ .
F | F 1E 2D ...
#include <stdio.h>
int
main(void)
{
int fila, columna;
printf(" |");
for(columna= 1; columna <= 0xF; columna++)
printf("%3X ", columna);
printf("\n");
printf("---|");
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 9 4
Ejercicio 3
Escribir un programa que lea frases desde la entrada estándar y las procese de
acuerdo al siguiente criterio:
En las frases que se encuentren en las líneas pares se deberá reemplazar
cada uno de sus blancos por tabuladores, permaneciendo sin cambios aquellas
frases que se encuentren en las líneas impares.
Ejemplo:
Si la entrada fuera
Esta es una
prueba de lo que
se puede
hacer con el procesador pedido
debería salir
Esta es una
prueba de lo que
se puede
hacer con el procesador pedido
#include <stdio.h>
int
main(void)
{
int letra, linea;
I.T.B.A. - 2000
Programación I - Clase 9 5
Ejercicio 4
Escribir un programa que determine si un numero positivo entero, ingresado
desde la entrada estándar, es o no primo.
#include <stdio.h>
#include “getnum.h”
int
main(void)
{
int nro, rec;
do
{
nro = getint("\nIngrese un numero entero positivo:");
}
while (nro <= 0);
switch( nro )
{
case 2:
case 3:
printf("El numero %d es primo\n", nro);
break;
default:
for (rec= 2; rec< nro; rec++)
if ( !(nro % rec) )
break;
return 0;
I.T.B.A. - 2000
Programación I - Clase 9 6
Ejercicio 5
Rehacer el ejercicio anterior utilizando la propiedad de la raíz cuadrada (si un
número no tiene divisores menores o iguales a su raíz cuadrada y distintos de 1,
entonces es primo.
#include <stdio.h>
#include “getnum.h”
int
main(void)
{
int nro, rec;
do
nro = getint("\nIngrese un numero entero positivo:");
while (nro <= 0);
switch( nro )
{
case 2:
case 3:
printf("El numero %d es primo\n", nro);
break;
default:
for (rec= 2; rec * rec <= nro; rec++)
if ( !(nro % rec) )
break;
return 0;
I.T.B.A. - 2000
Programación I - Clase 9 7
Ejercicio 6
Escribir un programa que imprima en la salida la secuencia de números de
Fibonacci, hasta un cierto orden ingresado desde la entrada estándar.
La secuencia de Fibonacci es un ejemplo de sucesión recurrente, en la cual se
fijan dos valores iniciales (0 para el orden 0 y 1 para el orden 1) y el resto se obtiene de
como la suma de los valores correspondientes a los dos órdenes anteriores:
F(0) = 0
F(1) = 1
F(2) = 1
F(3) = 2
F(4) = 3
F(5) = 5
F(6) = 8
F(7) = 13
................
#include <stdio.h>
#include “getnum.h”
int
main(void)
{
int orden, rec;
int fiboAnt= 0, fibo= 1, aux;
do
{
orden= getint("Ingrese el orden de Fibonacci deseado:");
}
while (orden < 0);
I.T.B.A. - 2000
Programación I - Clase 9 8
Valor Valor
Rango de Valores Ti Convalidación
Esperado Obtenido
F(0) = 0 F(0) = 0
Cobertura rec = 0
F(1) = 1 F(1) = 1
de rec = 1 orden=3 T1={ 3 } ü
F(2) = 1 F(2) = 1
sentencias rec = 3 F(3) = 2 F(3) = 2
sigue
( orden < 0 ) orden esperando un
T2={-5} ü
verdadero inválido orden
positivo
( orden < 0 )
Cobertura T1 ü ü ü
falso
de
decisiones/
condiciones
verdadero
y falso
para cada case T1 ü ü ü
del switch y
para el default
sigue
orden esperando un
T4={-2} ü
inválido orden
positivo
Cobertura ( orden < 0 ) ⇒ sigue
de ( orden <= -1) ⇒ orden esperando un
límites tomar -2, -1, 0 T5={-1} ü
inválido orden
positivo
T6={0} F(0) = 0 F(0) = 0 ü
I.T.B.A. - 2000
Programación I - Clase 9 9
Ejercicio 7
Escribir un programa que lea caracteres desde la entrada estándar (hasta llegar al
EOF) y escriba en la salida estándar el mismo texto, pero habiendo removido los
excesos de espacios en blanco y dejando todo el texto en una sola línea, sin líneas en
blanco.
Consideramos espacio en blanco a los espacios, tabuladores y caracteres de
nueva línea. Exceso es cuando hay más de uno de ellos seguidos, en cuyo caso se deja
sólo el primero, excepto en el caso de fin de línea, que se reemplaza por un blanco y se
sigue en la misma línea. Definimos como palabra a toda secuencia de caracteres sin
blancos en el medio. A su vez, las palabras se separan unas de otras por medio de
secuencias de uno ó más espacios en blanco.
Ejemplo:
Si entra el texto
Esta es una prueba para mostrar.
Un mensaje
que no se
merece. Ahora.
debe salir
Esta es una prueba para mostrar. Un mensaje que no se merece. Ahora.
Para determinar los pasos a seguir en el procesamiento de textos, suele ser muy
útil plantear un autómata de estados finitos, en el cual se estipulen los estados posibles
y las transiciones entre ellos:
inicio
≠ ‘\t’
caracter ≠‘’
≠ ‘\n’
≠ ‘\t’ = ‘\t’
≠‘’ =‘’
≠ ‘\n’ = ‘\n’
= ‘\t’
=‘’
espaciador
= ‘\n’
I.T.B.A. - 2000
Programación I - Clase 9 10
#include <stdio.h>
int
main(void)
{
int estado= 1, letra;
switch (letra)
{
case ' ':
case '\t':
estado= 2;
break;
case '\n':
letra= ' ';
estado= 2;
break;
}
putchar(letra);
break;
return 0;
I.T.B.A. - 2000
Programación I - Clase 10 1
Preprocesador - Parte I
Introducción
Muchas de las características útiles del lenguaje C no son implementadas por el
compilador sino por el preprocesador. En esta primera parte sobre el preprocesador de C
se desarrollan los usos básicos del mismo.
1. Preprocesador
El preprocesador es un módulo que lee un archivo fuente, realiza ciertas
acciones y genera una salida que es usada por el compilador de C. Las acciones que el
preprocesador realiza son aquellas que están indicadas por directivas comenzando con
el símbolo #.
Sintaxis
Muy Importante
I.T.B.A. - 2000
Programación I - Clase 10 2
Separación en líneas
Aclaración
Ejemplo:
int
main(void)
{
printf(“PI vale %g\n”, 3.1416);
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 10 3
Ejemplo:
#define PI = 3.1416
int
main(void)
{
printf(“El valor de PI es %g\n”, PI);
return 0;
}
int
main(void)
{
printf(“El valor de PI es %g\n”, = 3.1416);
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 10 4
Sintaxis
Ejemplo
#define PI 3.1416
#define AREA_CIRCULO(RADIO) PI * RADIO * RADIO
area= AREA_CIRCULO( 6 );
parámetro suministrado
area= AREA_CIRCULO( 7 ); en la invocación
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 10 5
int
main(void)
{
double area;
area= 3.1416 * 6 * 6;
area= 3.1416 * 7 * 7;
return 0;
}
Ejemplo
#include <stdio.h>
#define Leo_Numero_No_EOF(c) (( (c = getchar()) != EOF) && \
(c >= '0') && (c<= '9') )
...........
while (Leo_Numero_No_EOF (num))
putchar (num);
............
I.T.B.A. - 2000
Programación I - Clase 10 6
Ejercicio 1
a) Dado el siguiente código fuente, indicar cuál sería la salida del preprocesador
b) Indicar si la compilación resulta exitosa
c) Indicar si el programa ejecuta correctamente
#define PI 3.1416
#define AREA_CIRCULO(RADIO) PI * RADIO * RADIO
int
main(void)
{
int radioInicial= 3, incremento= 1;
double area;
return 0;
}
Rta:
a)
int
main(void)
{
int radioInicial= 3, incremento= 1;
double area;
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 10 7
Ejercicio 2
Escribir la macro AREA_RECTANGULO( BASE, ALTURA) para calcular el
área de un rectángulo
Rta:
Ejercicio 3
Si la macro anterior es invocada en un programa con los parámetros del
siguiente código, escribir la salida que deja el preprocesador, e indicar qué se mostraría
en la salida estándar en tiempo de ejecución.
int
main(void)
{
int lado1= 5, lado2= BASE;
return 0;
}
int
main(void)
{
int lado1= 5, lado2= 10;
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 10 8
Ejercicio 4
int
main(void)
{
int acumulador= 0;
SUMATORIA (1, 5, acumulador);
printf (“Sumatoria de los primeros 5 enteros: %d \n”, acumulador);
return 0;
}
Rta:
int
main(void)
{
int acumulador= 0;
{
int i;
acumulador = 0;
for(i= 1; i<=5; i++)
acumulador+=i;
}
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 10 9
v Operador ##: es binario y concatena los dos componentes léxicos a los cuales se
aplica.
Ejemplo
#define SALUDO(nombre) printf ("\tHola " #nombre ", como estas? \n")
SALUDO (Ana);
I.T.B.A. - 2000
Programación I - Clase 10 10
Ejemplo
DUMP_INT( i)
printf( “i=%d\n”, i );
Ejemplo
Rta:
Para unir dos tokens léxicos
Ejemplo
Rta:
I.T.B.A. - 2000
Programación I - Clase 10 11
2. Directiva #include
El preprocesador reemplaza la línea con la directiva #include con el contenido
del archivo especificado
Existen dos formas para esta directiva, que sólo se diferencian por el lugar en
donde el preprocesador busca el archivo a incluir:
v #include <nombre_de_archivo>
Busca el archivo en directorios reservados por el sistema. Generalmente se
utiliza para incluir archivos de la biblioteca estándar.
v #include “nombre_de_archivo”
Busca el archivo en el directorio actual de trabajo y si no lo encuentra lo sigue
buscando en los directorios reservados por el sistema. Generalmente se utiliza para
incluir archivos escritos por el mismo programador.
§ Suele ser útil definir las siguientes macros para clarificar los valores booleanos:
#define FALSE 0
#define TRUE 1
I.T.B.A. - 2000
Programación I - Clase 11 1
Funciones
Introducción
Las funciones son una herramienta que sirve para simplificar la estructura de los
programas. Se las puede analizar desde dos puntos de vista:
v Holístico: Sólo se presta atención a “qué hace” una función. Esta perspectiva sirve
para utilizar las funciones en la construcción de bloques de complejidad mayor.
v Reduccionista: Sólo se estudia “cómo hace” una función su tarea. Esta perspectiva
sirve para analizar la implementación de una función.
1. Funciones
Una función es un conjunto de sentencias que reciben un nombre.
Sintaxis de Definición
I.T.B.A. - 2000
Programación I - Clase 11 2
Importante
El tipo de los parámetros o el valor que devuelve una función puede ser
cualquiera de los ya vistos.
v Si se omite la especificación del tipo, tanto para algún parámetro como para el valor
que retorna la función, el compilador asume para el mismo el tipo int.
v Para que una función reciba una lista vacía de parámetros se debe especificar
explícitamente la palabra void como especificación de argumento.
Ejemplo 1:
while( numero )
{
suma += numero % 10;
numero /= 10;
}
return suma;
}
I.T.B.A. - 2000
Programación I - Clase 11 3
Ejemplo 2:
A continuación se define la función dibujaTriangulo que muestra en la salida
estándar un triángulo invertido generado por asteriscos, donde cada línea contiene dos
asteriscos menos que la línea anterior (uno menos a cada lado). La cantidad de líneas a
dibujar es el parámetro de tipo unsigned que recibirá la función.
putchar('\n');
} ¿Es necesario el último ciclo for interno?
return;
}
Ejemplo 3:
La función main es un ejemplo de función que no recibe parámetros (En
Estructura de Datos y Algoritmos veremos otra variante).
I.T.B.A. - 2000
Programación I - Clase 11 4
Sintaxis de Invocación
Invocar una función consiste en ejecutar las proposiciones que la definen. Para
ello se escribe el nombre de la misma, seguida por una lista de expresiones (que puede
ser vacía) encerrada entre paréntesis. Dichas expresiones de invocación reciben el
nombre de parámetros actuales o argumentos , y permiten el intercambio aceptable de
información entre el módulo llamador y la función invocada.
Existe una forma de definir funciones con una cantidad de parámetros variables
pero la veremos en Estructura de Datos y Algoritmos por ser de mayor complejidad
(pensar el caso de la función printf).
Importante
La acción que ocurre cuando una función devuelve el flujo de control al módulo
invocador se denomina retornar. Si la función además de retornar el flujo de control,
devuelve un valor al módulo invocador, se dice que retorna un valor.
I.T.B.A. - 2000
Programación I - Clase 11 5
Aclaración
Importante
I.T.B.A. - 2000
Programación I - Clase 11 6
Aclaración
Las funciones también suelen recibir el nombre de subprogramas porque son los
bloques de construcción de los programas. Notar la analogía de una función con la de un
programa:
Los programadores sólo tienen que conocer que la ÚNICA forma de pasaje de
parámetros en C es por valor (o sea la información es de entrada a la función). Si se
quiere que la función pase información de vuelta al módulo invocador se lo debe hacer
permitiendo que la función retorne un valor. Obviamente NO se usarán variables
globales como intercambio de información entre una función y el módulo invocador
para evitar acoplamiento y efectos colaterales indeseables (Recordar que en Z80 las
subrutinas no debían usar rótulos del programa principal)
Muy Importante
I.T.B.A. - 2000
Programación I - Clase 11 7
I.T.B.A. - 2000
Programación I - Clase 11 8
Ejemplo:
int
main(void)
{
float sueldo;
int cantEmpleados;
....
sueldo= getfloat(“\nSueldo: “);
cantEmpleados= getint(“\nEmpleados: “);
.....
return 0;
}
Una forma mucho más prolija de trabajar con funciones consiste en:
v escribir los prototipos de las funciones afines en un archivo llamado de
encabezamiento (header) y que tiene la extensión .h
v escribir las implementaciones de las funciones afines en un archivo con extensión .c
(pudiéndose inclusive más adelante armar bibliotecas)
v distribuir por un lado los archivos de encabezamiento y por otro lado los
archivos .c o bien los .obj o bien las bibliotecas, dependiendo de si se quiere o no que
los que utilicen las funciones conozcan o no las implementaciones.
I.T.B.A. - 2000
Programación I - Clase 11 9
Ejemplo
....
sueldo= getfloat(“\nSueldo: “);
cantEmpleados= getint(“\nEmpleados: “);
.....
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 11 10
Para evitar estos errores es que ANSI C exige que todas las funciones sean
prototipadas, y agrega la palabra reservada void para poder especificar cuando la lista
de parámetros es vacía, y cuando no se desea retornar valor alguno al módulo
invocador.
Importante
ANSI C exige prototipar todas las funciones que se van a usar
Ejemplo
Supongamos que estamos en una arquitectura de 32 bits, y decidimos escribir
una función que calcule el cuadrado de un número dado. Sea el archivo matem.c que
contiene su código:
/* Archivo matem.c */
I.T.B.A. - 2000
Programación I - Clase 11 11
Sea ahora el programa que usa dicha función, en el cual hemos omitido la
prototipación, y hemos olvidado la declaración de la función cuadrado antes de su
invocación:
/* Archivo program.c */
int
main(void)
{
int valor = 5;
printf(“El cuadrado de %d es %g\n”,
valor, cuadrado( valor ));
return 0;
}
$ El cuadrado de 5 es 6.64587e-316
I.T.B.A. - 2000
Programación I - Clase 11 12
v Cuando se le transfiere el flujo de control a la función, ésta levanta del stack los
valores que se le pasaron en el mismo, almacenándolos en los correspondientes
parámetros formales. O sea con la invocación de una función se crea un conjunto de
variables (parámetros formales) nuevo cuyo contenido es una copia de los valores de
los parámetros actuales, por eso es que en C el pasaje de parámetros es sólo por
valor (por ser una copia cualquier modificación del valor de un parámetro formal
NO afecta el valor que posee el parámetro actual correspondiente.
I.T.B.A. - 2000
Programación I - Clase 11 13
Ejemplo:
main
valor
Los parámetros actuales y los parámetros formales
5 están en correspondencia en tipo y cantidad.
I.T.B.A. - 2000
Programación I - Clase 11 14
§ El valor de retorno de la función debe estar separado en una línea, antes del nombre
de la función, que debe encontrarse en la línea siguiente.
§ No omitir la definición del tipo de la función o de sus argumentos, por más que sea
int. Cuando no devuelve dato o no tiene parámetros usar void explícitamente.
§ El cuerpo de la función debe estar tabulado respecto de sus llaves, las cuales deben
colocarse en la primera columna.
§ Debe haber una separación de por lo menos una línea en blanco entre las
declaraciones de variables locales y sentencias del bloque.
I.T.B.A. - 2000
Programación I - Clase 11 15
Ejemplo:
No usar #include”c:\jobs\borlandc\include\getnum.h”
sino solamente #include “getnum.h”
§ No quedarse con el primer algoritmo que se nos ocurra. Pensar varias alternativas,
evaluarlas y quedarse con la mejor, en términos de claridad y eficiencia (en ese
orden).
§ Aplicar siempre a cada función los tests de software explicados: caja blanca y
caja negra (obviamente si la función tuviera muchas líneas de código o salto
incondicionales innecesarios el test de caja blanca sería prácticamente imposible)
I.T.B.A. - 2000
Programación I - Clase 12 1
Aplicación de Funciones
Introducción
En este documento se ponen en práctica las reglas básicas para la prototipación y
definición de funciones, mostrando sus ventajas.
m!
Combinatorio (m, n) = -------------- con n ≥ 0 y m ≥ n
n! (m-n)!
programa
ingresoNumNoNeg combinatorio
factorial
I.T.B.A. - 2000
Programación I - Clase 12 2
Documentación de la Interfaz:
/* Archivo combin.h
** Autores: G & G
** -----------------
*/
I.T.B.A. - 2000
Programación I - Clase 12 3
/* Archivo combin.c
** El siguiente programa calcula el numero combinatorio,
** una vez solicitados el numerador y el denominador.
*/
#include <stdio.h>
#include "getnum.h"
#include "combin.h"
int
main(void)
{
int numerador, denominador;
do
{
nro = getint(“”);
}
while (nro < 0);
return nro;
}
unsigned long
combinatorio(unsigned m, unsigned n)
{
return (factorial(m) / ( factorial(n) * factorial(m - n)) );
}
I.T.B.A. - 2000
Programación I - Clase 12 4
unsigned long
factorial(unsigned nro)
{
unsigned long producto;
return (producto);
}
Cobertura de - - - - -
límites
O.K.
Cobertura de - - - - -
límites
O.K.
I.T.B.A. - 2000
Programación I - Clase 12 5
Cobertura de - - - - -
límites
O.K.
main ingresoNumNoNeg
numerador nro
denominador
combinatorio factorial
m nro
n producto
I.T.B.A. - 2000
Programación I - Clase 12 6
main ingresoNumNoNeg
numerador nro
5 5
denominador
main ingresoNumNoNeg
numerador nro
5 2
denominador
2
main combinatorio
numerador m
5 5
denominador n
2 2
I.T.B.A. - 2000
Programación I - Clase 12 7
combinatorio factorial
m nro
5 2 1
n producto
2 1 2
devuelve 2
combinatorio factorial
m nro
5 3 2 1
n 5-2 producto
2 1 3 6
devuelve 6
combinatorio factorial
m nro
5 5 4 3 2 1
n producto
2 1 5 20 60 120
devuelve 120
I.T.B.A. - 2000
Programación I - Clase 12 8
120 / ( 6 * 2 ) = 120 / 12 = 10
C ( 5, 2) = 10
Para aplicar dicho método se forma un rectángulo R cuya base está formada por
el segmento de abscisa entre los extremos del intervalo, y cuya altura es igual al
máximo absoluto de la función en dicho intervalo.
a b x
I.T.B.A. - 2000
Programación I - Clase 12 9
Representación Gráfica:
principal
Documentación de la Interfaz:
I.T.B.A. - 2000
Programación I - Clase 12 10
I.T.B.A. - 2000
Programación I - Clase 12 11
/*
** Archivo monte.h
** Autores: G & G
*/
/*
** La siguiente funcion recibe los extremos de un intervalo real y
** determina si es valido (1) o no (0)
*/
int esInterValido(float izq, float der);
/*
** La siguiente funcion calcula la integral de la funcion matematica
** positiva monótona en un cierto intervalo [izq, der] mediante el
** Metodo de Montecarlo, solicitando cantidad de tiros desde la
** entrada estándar
*/
float aproximoIntegral(float izq, float der);
/*
** La siguiente funcion calcula la integral de la funcion en forma
** teorica, aplicando Regla de Barrow
*/
float integralTeorica(float a, float b);
/*
** La siguiente funcion calcula el error relativo porcentual cometido
** en un calculo practico, respecto del valor teorico esperado.
** Respeta el signo de la diferencia para indicar si el error
** cometido fue por exceso o por defecto
*/
float calculoError(float integCalculada, float integTeorica);
/*
** La siguiente funcion devuelve el maximo de la funcion matematica
** positiva monótona en un cierto intervalo [a,b]
*/
float maxFuncion(float a, float b);
/*
** La siguiente funcion permite ingresar desde la entrada estandar un
** numero entero no negativo
*/
int ingresoEntNoNeg(void);
I.T.B.A. - 2000
Programación I - Clase 12 12
/*
** La siguiente funcion devuelve un numero aleatorio real,
** perteneciente al intervalo [izq, der]
*/
float generoAzar(float izq, float der);
/*
** La siguiente funcion representa una funcion matematica real de una
** sola variable real
*/
float funcion(float x);
/*
** La siguiente funcion calcula el area de un rectangulo cartesiano,
** definido por las abscisas de sus lados verticales y las ordenadas
** de sus lados horizontales
*/
float areaRectang(float xIzq, float xDer,
float yArriba, float yAbajo);
/*
** Archivo: monte.c
** Autores: G & G
**
** Este programa calcula la integral de una funcion positiva
** monótona en un intervalo real ingresado desde la entrada
** estandar, mediante el Metodo de Montecarlo.
** Tambien indica el porcentaje de error cometido con dicho
** calculo, respecto del valor teorico.
** En este código la función a integrar es F(x)= x*x
*/
#include <stdio.h>
#include <stdlib.h>
#include “getnum.h”
#include “monte.h”
int
main(void)
{
float izq, der;
float simulacion, integral;
I.T.B.A. - 2000
Programación I - Clase 12 13
if ( esInterValido(izq,der) )
{
simulacion = aproximoIntegral(izq, der);
integral = integralTeorica(izq, der);
return 0;
}
int
esInterValido(float izq, float der)
{
return (der >= izq);
}
float
aproximoIntegral(float izq, float der)
{
float x, y, tope;
int i, acum, tiros;
float
integralTeorica(float a, float b)
{
return ( (b * b * b / 3) - (a * a * a / 3) );
}
I.T.B.A. - 2000
Programación I - Clase 12 14
float
calculoError(float integCalcu, float integTeo)
{
return ( VALOR_ABSOLUTO(integCalcu-integTeo) / integTeo * 100 );
}
float
maxFuncion(float a, float b)
{
return (funcion(a) > funcion(b))?funcion(a):funcion(b);
}
int
ingresoEntNoNeg(void)
{
int n;
do
n= getint(“”);
while (n <0);
return n;
}
float
generoAzar(float izq, float der)
{
float n;
n= izq + (der - izq) * rand() / RAND_MAX;
return n;
}
float
funcion(float x)
{
return (x * x);
}
float
areaRectang(float xIzq, float xDer, float yArriba, float yAbajo)
{
return ( (xDer - xIzq) * (yArriba - yAbajo));
}
I.T.B.A. - 2000
Programación I - Clase 13 1
Gestión de la Memoria
Introducción
En este documento se describe la relación entre el formato de un objeto binario y
la zona de memoria que se le asignará cuando se solicite la ejecución del mismo.
Asimismo se verá la manera que tiene el programador para gestionar la memoria en
lenguaje C.
$ size nombreDelArchivoBinario
Ejemplo 1:
Para obtener la información de los segmentos en un archivo objeto llamado
pepe.o se debe realizar
$ size pepe.o
obteniendo
text data bss dec hex filename
31 0 0 31 1f pepe.o
I.T.B.A. - 2000
Programación I - Clase 13 2
Ejemplo 2:
Para obtener la información de los segmentos en un archivo ejecutable llamado
pepe.out se debe realizar
$ size pepe.out
obteniendo
text data bss dec hex filename
1037 196 4 1237 4d5 pepe.out
Ejemplo 3:
Si se quiere obtener información de los segmentos en un archivo fuente llamado
pepe.c se obtendrá un error porque no es binario
$ size pepe.c
I.T.B.A. - 2000
Programación I - Clase 13 3
El propósito para el cual sirve cada zona mostrada en la próxima figura y los
pasos que el cargador realizará típicamente cuando tenga que cargar el programa en
memoria son:
v el Stack Segment: es el área donde se almacenan los stack frames, que recordemos,
es el mecanismo que usan los compiladores para manejar la invocación de funciones
(guardando dirección de retorno, parámetros y variables automáticas), y las variables
temporarias (que surgen temporalmente al evaluar una sub-expresión). Su tamaño
crece y decrece dinámicamente durante la ejecución (cada vez que se invoca una
función o se evalúa una expresión). El cargador alocará cierto lugar en memoria para
este segmento.
I.T.B.A. - 2000
Programación I - Clase 13 4
programa ya en memoria
Heap Segment
.
.
.
.
Stack Segment
BSS segment
archivo ejecutable acá van las variables
static y globales que
no fueron
... cierta información ... inicializadas por el
tamaño para el programador. Acá ya
BSS segment el sistema las puso en
cero.
Data Segment:
Data Segment:
acá van las variables
acá van las variables
static y globales que
static y globales que
han sido inicializadas
han sido inicializadas
por el programador
Text Segment:
Text Segment:
acá van las
acá van las
instrucciones
instrucciones
Aclaración:
Las zonas en memoria no necesariamente van en este orden ya que esto depende
de la arquitectura de la computadora.
I.T.B.A. - 2000
Programación I - Clase 13 5
Definición
Ø Duración Automática
I.T.B.A. - 2000
Programación I - Clase 13 6
Ø Duración Estática
Muy Importante:
Aclaración
Obviamente tiene que ver con dónde se almacenan las variables dentro de las
zonas de memoria antes vista.
Aquellas variables que están en el Data Segment o BSS Segment son creadas
antes de comenzar la ejecución del proceso (cuando el cargador coloca el proceso en
memoria a partir del archivo ejecutable) y van a existir durante toda la ejecución del
proceso. Como estas variables mantienen su lugar en memoria durante toda la ejecución
cada vez que se las utiliza mantienen el último valor que se les fue asignado.
I.T.B.A. - 2000
Programación I - Clase 13 7
Importante:
Ejemplo:
Dado el siguiente programa, si desde la entrada estándar se ingresara No↵
#include <stdio.h>
int
main(void) dentro de este bloque
{
float letra= 43;
que comienza con el
while (getchar() != EOF ) while, esta nueva
{ declaración tiene
int letra= 1; prioridad sobre la
printf(“bloque mas anidado %d\n”, letra); otra (aunque la
}
printf(“bloque mas externo %f\n”, letra); anterior tiene alcance
} sobre ésta)
se obtendría
I.T.B.A. - 2000
Programación I - Clase 13 8
Ejercicio:
Indicar para cada una de las siguientes variables cuales tienen persistencia
automática y cuales estática.
persistencia estática
int valor;
int
prueba(void)
{ persistencia automática
int letra;
void
cartel( void )
{
extern int acumulador; persistencia estática
2.2 Alcance
Definición
I.T.B.A. - 2000
Programación I - Clase 13 9
Ejemplo:
Para el ejemplo anterior indicar cual es el alcance de las variables.
alcance global
int valor;
int
prueba(void)
{ alcance local
int letra;
void
cartel( void )
{
extern int acumulador; alcance local
I.T.B.A. - 2000
Programación I - Clase 13 10
Definición
Ø Visibilidad Externa
La palabra reservada usada para lograr esto es extern, pero en el caso de que la
variable declarada sea de alcance global puede omitirse. Cuando se tiene una variable de
visibilidad externa implica que la misma puede ser declarada por otros módulos, pero en
realidad todos se están refiriendo a la misma variable (como se verá en la próxima
sección uno de dichos módulo debería en realidad definirla).
I.T.B.A. - 2000
Programación I - Clase 13 11
Ø Visibilidad Interna
La única forma de lograr esto es calificando con la palabra reservada static a una
variable de alcance global.
Ø Sin Enlace
Es la visibilidad que poseen todas las variables locales que no son precedidas
por la palabra reservada extern. No hace falta utilizar ninguna palabra reservada para
lograr esto. Cuando una variable no tiene enlace cada declaración que aparece se refiere
a un nuevo objeto. En este caso, como veremos en la próxima sección, cada declaración
es en realidad una definición.
Ejemplo:
Se tienen los siguientes fuentes que formarán un único archivo ejecutable
Tiene enlace interno. El módulo1
ambas tiene enlace externo ya que se están refiriendo la no puede referenciar la misma
misma variable. Se aloca un solo lugar en memoria para localidad de memoria. La variable
ambas referencias sólo es visible en este módulo. Es
global y precedida por la palabra
static
modulo1.c modulo2.c
double porcentaje; double porcentaje;
int float
main(void) + suma(void) ⇒
{ { ejecutable
static int sueldo; extern float sueldo;
int contador; ....
..... } Tiene enlace externo. Es
} la referencia de la misma
zona de memoria que la
Sin enlace. Ningún otro módulo variable sueldo en el
Tiene enlace externo. Si módulo2 podrían intentar referenciar estas módulo1. Nos damos
quiere la puede declarar, usar y en variables (si otro módulo usa el cuenta porque es local
ese caso ambas estarían mismo nombre de variable, en pero está precedida por
referenciando a la misma variable, realidad referencia otro objeto, o la palabra reservada
o sea tendrían un único lugar extern
sea otra localidad de memoria)
asignado en memoria
I.T.B.A. - 2000
Programación I - Clase 13 12
Importante
La definición de una variable sirve para reservar espacio en memoria para la
misma.
Según dónde se la defina y que calificador la modifique se reservará espacio en
los distintos segmentos antes vistos.
Aclaración
Todas las declaraciones que habíamos realizado en los códigos de las clases
anteriores, a través de variables locales eran en realidad definiciones.
I.T.B.A. - 2000
Programación I - Clase 13 13
Viendo un código existen ciertas heurísticas que ayudan a darse cuenta si una
variable está siendo solo declarada o definida:
§ si la variable está inicializada, sin lugar a dudas está siendo definida. No está
permitido inicializar una variable varias veces porque justamente no está permitido
definir una variable más de una vez.
§ en cualquier otro caso para saber si la variable está declarada o definida hay que
inspeccionar que pasa en el resto del dicho módulo u otros módulos.
Ejemplo 1:
I.T.B.A. - 2000
Programación I - Clase 13 14
Ejemplo 2:
Para resaltar la ambigüedad del ultimo caso, a continuación se muestran los dos
archivos fuentes que formarían luego de la linkedicion un único código ejecutable. En la
primera opción se evidencia que en el módulo1.c la variable double precio sólo declara,
en cambio en la segunda opción se muestra que se la define.
Opción A
modulo1.c modulo2.c
con sólo ver este fragmento no con sólo ver este fragmento sabemos que es
podríamos darnos cuenta que es sólo una definición por la inicialización de la
una declaración. Viendo que en el otro variable
módulo se la define nos damos cuenta
que esto es declaración
Opción B
modulo1.c modulo2.c
con sólo ver este fragmento no con solo ver este fragmento ya nos
podríamos darnos cuenta que es una damos cuenta que es sólo una
definición. Viendo que en el otro declaración
módulo solo se la declara nos damos
cuenta que esto es la definición.
I.T.B.A. - 2000
Programación I - Clase 13 15
Ejemplo 3:
A continuación se muestran los errores de compilación y linkedición que se
obtendría al generar un único programa ejecutable a partir de los siguientes dos módulos
modulo1.c modulo2.c
static int dia; static int dia;
No es necesario
declararla nuevamente,
ya que se trata de la
misma variable valor
externa y global
A su vez la variable dia, en ambos módulos, refiere a objetos distintos: cada una
de dichas definiciones sólo afecta el módulo en el que se encuentran debido a que son
globales y están afectadas por el calificador de static, restringiendo su visibilidad.
I.T.B.A. - 2000
Programación I - Clase 13 16
Ejemplo 4:
A continuación se muestran los errores de compilación y linkedición que se
obtendría al generar un único programa ejecutable a partir de los siguientes dos módulos
modulo1.c modulo2.c
float valor= 15; extern float valor= 3;
int float
main(void) suma(void)
{ {
static int dato; ....
..... }
}
I.T.B.A. - 2000
Programación I - Clase 13 17
4. Ejercicios
Ejercicio1
Dado el siguiente código fuente, que es el único módulo para formar un
programa ejecutable:
a) Para cada una de las variables indicar persistencia, alcance y enlace. Decir además si
se las está sólo declarando o definiendo.
b) Indicar los errores que se obtendrían en tiempo de compilación.
c) Indicar los errores que se obtendrían en tiempo de linkedición debido a no poder
resolver alguna referencia externa o redefinción de variables.
d) Mostrar qué se obtendría en la salida estándar al finalizar la ejecución del mismo, si
se obtiene de la entrada estándar: No↵
1. #include <stdio.h>
2.
3. int contador;
4. int letra= ‘W’;
5.
6. int
7. main(void)
8. {
9. int letra;
10. printf(“Valor de letra al comenzar el bloque: %c\n”,letra);
11. while ( (letra= getchar() ) != EOF )
12. {
13. if ( ‘a’ <= letra && letra <= ‘z’)
14. putchar( letra – ‘a’ + ‘A’);
15. else
16. putchar( letra );
17. contador++;
18. }
19. printf(“Cantidad de letras ingresadas: %d\n”, contador);
20. return 0;
21. }
Respuesta:
I.T.B.A. - 2000
Programación I - Clase 13 18
Notar que las variables de alcance global no hace falta inicializarlas aunque
funcionen como acumulador. Si la variable contador hubiera tenido alcance local (se
hubiera definido dentro del bloque) tendría que haberse inicializado con cero.
Como se observa, en la línea 9 aparece otra vez una declaración de variable,
pero como no se hace referencia explícita sobre su correspondencia a la misma variable
ya definida en la línea 4, corresponde a una nueva definición de variable (que inclusive
podría ser de otro tipo). La variable letra de la línea 9 tiene alcance local, y tiene
prioridad sobre la letra global de lalínea 4.
Ejercicio 2
Idem al ejercicio anterior, pero donde se tiene dos programas fuentes que juntos
formaran un único archivo ejecutable
módulo 1.c
1. #include <stdio.h>
2.
3. void
4. imprime(void)
5. {
6. printf(“el valor de letra es %c\n”, letra);
7. }
8.
9. int
10. main(void)
11. {
12. extern int letra;
13. int contador= 0;
14.
15. imprime();
16. printf(“Valor de letra al comenzar el bloque: %c\n”,letra);
17. while ( (letra= getchar() ) != EOF )
18. {
19. if ( ‘a’ <= letra && letra <= ‘z’)
20. putchar( letra – ‘a’ + ‘A’);
21. else
22. putchar( letra );
23. contador++;
24. }
25. printf(“Cantidad de letras ingresadas %d\n”, contador);
26. return 0;
27. }
I.T.B.A. - 2000
Programación I - Clase 13 19
módulo2.c
28. int letra= ‘W’;
Respuesta:
d) Se obtendría:
El valor de la letra es W
Valor de la letra al comenzar el bloque: W
NO
Cantidad de letras ingresadas 3
I.T.B.A. - 2000
Programación I - Clase 13 20
Ejercicio 3
Indicar en cada uno de los siguientes casos si existen o no errores de
compilación y/o linkedición. Para cada una de las variables decir en que zona de
memoria (que segmento) la almacenaría.
a)
int
cantidadDeCifras( int valor)
{
static int acumulador= valor;
return acumulador;
}
int
main(void)
{
cantidadDeCifras(10);
return 0;
}
b)
int
cantidadDeCifras( int valor)
{
static int acumulador;
acumulador += valor;
return acumulador;
}
I.T.B.A. - 2000
Programación I - Clase 13 21
c)
int
main(void)
{
int letra;
return 0;
}
d)
int
main(void)
{
int letra;
acumulador++;
return 0;
}
Respuestas:
I.T.B.A. - 2000
Programación I - Clase 13 22
I.T.B.A. - 2000
Programación I - Clase 13 23
§ Para ANSI los identificadores externos deben diferir en los primeros 6 caracteres.
§ Los archivos de encabezado que declaran funciones o variables externas deben ser
incluidos en el archivo que define dichas funciones y variables. De esta forma el
compilador puede hacer chequeo de tipos y la declaración externa siempre
coincidirá con la definición. Los linkeditores no suelen arrojar errores al linkeditar
módulos donde aparecen variables extern con el mismo nombre pero distintos tipos,
ocasionando obviamente serios problemas durante la ejecución
§ Cualquier variable que se pretende que comience con un valor debe ser
explícitamante inicializada, o al menos debe comentarse la aceptación de su
inicializacion por omisión, si ésta existe.
§ Todas las declaraciones de datos externos deben ser precedidas por la palabra
extern.
§ Si una función usa alguna variable externa que no haya sido declarada
globalmente en el archivo, debería tener su propia declaración en el cuerpo de la
función utilizando la palabra clave extern.
§ Todas las declaraciones que no están relacionadas deben estar en líneas separadas,
aunque sean del mismo tipo.
§ Las variables que deban ser accedidas desde otros archivos (visibilidad externa)
deben sólo ser usadas cuando es demostrable que no existe otra opción para
resolver el problema. A lo sumo se usará el calificador static cuando se precise una
variable global en un módulo que integra un gran sistema, para reducir su visibilidad
en el resto.
I.T.B.A. - 2000
Programación I - Clase 13 24
Por otra parte, el uso de variables de alcance global denotan un estilo muy
pobre de programación, ya que, al igual que las externas, compartir información entre
distintas funciones sin usar pasaje de parámetros, enturbia notablemente la semántica de
los programas y acarrea efectos colaterales indeseables.
Toda variable debe ser declarada justo antes de ser usada (declaración tardía) y
debe haber una sola función responsable por el contenido que posee la misma. Así es
como sólo se usarán variables con alcance local y sin enlace. Los programas que se
desarrollen en las materia antes citadas no precisan bajo ningún punto de vista el uso de
otra clase de almacenamiento.
La Cátedra
I.T.B.A. - 2000
Programación I - Clase 14 A 1
Introducción
El lenguaje C es bastante reducido, sin embargo ofrece un conjunto de funciones
en la biblioteca estándar que lo potencian y que si bien no forman parte del lenguaje se
encuentran disponibles en cualquier paquete del lenguaje C.
En este documento se presentan algunas de sus funciones y se las utiliza en
algunos ejemplos.
Prototipo Descripción
int getchar( void ); Lee un carácter de la entrada estándar, y retorna
un entero para permitir detectar el fin de archivo
(EOF). Ya la vimos
ungetc( char ch, FILE * infile); Coloca el caracter ch (no puede ser EOF) otra vez
en el stream indicado por infile, haciendo que el
mismo esté disponible otra vez en el próxima
lectura. Si se desea colocar el dato otra vez en la
entrada estándar, el segundo parámetro debe
invocarse con stdin. Devuelve el carácter ch si
todo fue exitoso o EOF en caso contrario. Se
garantiza que por lo menos un carácter es devuelto
a la entrada estándar (al realizar sucesivos ungetc
sin su correspondiente getchar posterior).
I.T.B.A. - 2000
Programación I - Clase 14 A 2
Ejemplo:
Si se obtiene de la entrada estándar abc↵ con el siguiente programa
#include <stdio.h>
int
main(void)
{
int letra;
ungetc(letra, stdin);
letra= getchar();
I.T.B.A. - 2000
Programación I - Clase 14 A 3
Prototipo Descripción
int abs(int n); Devuelve el valor absoluto de un número de tipo
int
long labs(long n); Devuelve el valor absoluto de un número de tipo
long
int rand(void); Devuelve un número pseudo-aleatorio
perteneciente al rango entre 0 y RAND_MAX
inclusive
void srand(unsigned int seed); Setea la semilla generadora de la próxima
secuencia de números pseudo-aleatorios con el
valor especificado. Si se invoca por primera vez la
función rand() sin haber invocado esta función
antes, el sistema utiliza la semilla 1.
void abort(void); Produce una finalización anormal del proceso en
ejecución.
int exit(int status); Produce una terminación normal del proceso en
ejecución (cierra los flujos abiertos, devuelve el
control al entorno que invocó la ejecución del
proceso, etc). Puede utilizarse los parámetros
EXIT_SUCCESS y EXIT_FAILURE para
indicarle al entorno que la terminación fue o no
exitosa.
Muy Importante
I.T.B.A. - 2000
Programación I - Clase 14 A 4
Ejemplo:
Si se ejecuta el siguiente programa:
#include <stdio.h>
#include <stdlib.h>
int
main(void)
{
printf(“|%d|= %d\n”, -5, abs(-5) );
printf(“|%ld|= %ld\n”, -20L, labs(-20L) );
return 0;
}
|-5|= 5
|-20|= 20
Valor pseudo aleatorio en el rango [0, 2147483647]: 1804289383
Aclaración
I.T.B.A. - 2000
Programación I - Clase 14 A 5
Citaremos todas sus funciones. Las armamos en dos grupos porque el valor que
devuelven difieren.
a)
Las funciones de la siguiente tabla regresan un valor diferente de cero si el
argumento se satisface o cero en caso contrario
Prototipo Descripción
int isupper(int ch); Testea si el caracter ch es una letra mayúscula (del
alfabeto inglés)
int islower( int ch ); Testea si el caracter ch es una letra minúscula (del
alfabeto inglés)
int isalpha(int ch); Testea si el caracter ch es una letra mayúscula o
minúscula (del alfabeto ingles)
int isdigit(int ch); Testea si el caracter ch es un dígito decimal
I.T.B.A. - 2000
Programación I - Clase 14 A 6
b)
Las funciones de la siguiente tabla intentan hacer una conversión. Si se realiza
devuelve dicha conversión pedida, caso contrario retorna el valor recibido intacto.
Prototipo Descripción
int toupper(int ch); Testea si el caracter ch es una letra minúscula
(del alfabeto inglés)y la convierte a mayúscula.
int tolower(int ch); Testea si el caracter ch es una letra mayúscula
(del alfabeto inglés)y la convierte a minúscula.
Ejemplo:
El siguiente programa lee desde la entrada estándar y coloca en la salida estándar
todos los caracteres pasados a mayúsculas (en el alfabeto ingles) eliminando los
caracteres blancos.
#include <stdio.h>
#include <ctype.h>
int
main(void)
{
int letra;
return 0;
}
NUEVAPRUEBAQUE ELIMINAESPACIOSYPASAAMAYUSCULAS.123.
I.T.B.A. - 2000
Programación I - Clase 14 A 7
Prototipo Descripción
double fabs(double x); Devuelve el valor absoluto de un número de tipo
double
double floor(double x); Retorna en un tipo double la representación del
entero más grande menor o igual al parámetro x.
double ceil(double x); Retorna en un tipo double la representación del
entero más chico mayor o igual al parámetro x.
double fmod(double x, double y); Retorna en un tipo double el resto de división
entre x e y, con el mismo signo que x. Si el
segundo parámetro fuera cero el resultado es
dependiente de la implementación
double sqrt(double x); Retorna la raíz cuadrada del argumento x.
Obviamente x debe ser mayor o igual a cero.
double pow(double x, double y); Retorna xy, o sea x elevado al argumento y. Se
obtendrá un error de dominio si x=0 y y≤0. o bien
si x<0 y el argumento y no representa un entero
double exp(double x); Retorna ex, o sea el numero e elevado al
argumento x
double log(double x); Retorna ln(x), o sea el logaritmo natural de x. El
argumento debe ser un número mayor que cero
double log10(double x); Retorna log10(x), o sea el logaritmo en base 10 de
x. El argumento debe ser un número mayor que
cero
double sin(double angulo); Retorna el seno del argumento angulo, donde el
mismo debe ser expresado en radianes
double cos(double angulo); Retorna el coseno del argumento angulo, donde el
mismo debe ser expresado en radianes
double tan(double angulo); Retorna la tangente del argumento angulo, donde
el mismo debe ser expresado en radianes
double asin(double x); Retorna seno-1(x), o sea el arcoseno del argumento
x. El argumento debe pertenece al intervalo [-1,
1], y el resultado obtenido representa un angulo
expresado en radianes que se encuentra en el
intervalo [-π/2, π/2]
I.T.B.A. - 2000
Programación I - Clase 14 A 8
Prototipo Descripción
double acos(double x); Retorna cos-1(x), o sea el arcocoseno del
argumento x. El argumento debe pertenece al
intervalo [-1, 1], y el resultado obtenido representa
un angulo expresado en radianes que se encuentra
en el intervalo [0, π]
double atan(double x); Retorna tan-1(x), o sea el arcotangente del
argumento x. El resultado es un ángulo expresado
en radianes que se encuentra en el intervalo [-π, π]
double atan2(double y, double x); Retorna el ángulo formado entre el eje x y la línea
que se extiende desde el origen al punto (x, y). x.
El resultado es un ángulo expresado en radianes
double sinh(double x); Retorna el seno hiperbólico del argumento x
Muy Importante
I.T.B.A. - 2000
Programación I - Clase 14 A 9
Muy Importante
Dicha variable global y externa errno sólo se setea en el caso de que hayan
habido errores, por lo tanto si se invoca una función que produce algún error queda con
alguno de los valores antes citados. Pero si después se invoca otra función matemática
que no produce errores el contenido de errno sigue con el valor anterior (el sistema no
se encarga de blanquearla).
Esto evidencia una vez más que el uso de variable globales enturbia la
semántica de un programa y puede produce efectos colaterales indeseables.
Hoy en día, mucho años después de la creación del lenguaje C, los nuevos
lenguajes (orientados a objetos) se diseñan sin variables globales para el pasaje de
información.
Aclaración
En Unix para que el programa cc linkedite con la librería matemática hay que
utilizar la opción –l de la línea de comandos (con la letra m para indicarle el uso de la
librería estándar matemática).
I.T.B.A. - 2000
Programación I - Clase 14 A 10
Ejemplo:
Si se ejecutara el siguiente programa
#include <stdio.h>
#include <math.h>
int
main(void)
{
printf(“Floor( %g )= %g\n”, 23.5, floor( 23.5 ) );
printf(“Ceil( %g )= %g\n”, 23.5, ceil( 23.5 ) );
return 0;
}
Floor( 23.5 )= 23
Ceil( 23.5 )= 24
Floor( -23.5 )= -24
Ceil( -23.5 )= -23
I.T.B.A. - 2000
Programación I - Clase 14 A 11
Ejercicio:
El siguiente programa intenta realizar dos lecturas de números punto flotante desde
la entrada estándar. Para cada una de dichos valores calcula el valor de la raíz cuadrada
y lo imprime si no hay errores obtenidos.
a) Decir qué se obtendría si desde la entrada estándar se ingresara –144 y 25
b) Arreglarlo para que funcione correctamente.
#include <stdio.h>
#include <math.h>
#include <errno.h>
#include “getnum.h”
int
main(void)
{
double rta;
float leido;
rta= sqrt(leido);
if (errno == EDOM)
printf("Error en el dominio de la funcion\n");
else
printf("sqrt( %f ) = %g\n", leido, rta );
rta= sqrt(leido);
if (errno == EDOM)
printf("Error en el dominio de la funcion\n");
else
printf("sqrt( %f ) = %g\n", leido, rta );
return 0;
Respuestas:
I.T.B.A. - 2000
Programación I - Clase 14 A 12
explícitamente blanquear
errno= 0; esta variable justo antes de
invocar la función que
rta= sqrt(leido); puede setear algún código
if (errno == EDOM) de error en ella
printf("Error en el dominio de la funcion\n");
else
printf("sqrt( %f ) = %g\n", leido, rta );
§ Como dichas funciones son usadas por millones de usuarios, están altamente
testeadas. Es más probable que introduzcamos errores si las re-programamos.
I.T.B.A. - 2000
Programación I - Clase 14 B 1
Preprocesador - Parte II
Introducción
En este documento se presentan opciones avanzadas del preprocesador de C. Las
mismas permiten evitar la redefinición de identificadores y sirven a los efectos de
aplicar técnicas de debuggeo.
1. Inclusión Condicional
Muchas veces queremos que cierto código no llegue a ser visto por el
compilador. Para esto nos sirve el uso de la directiva de inclusión condicional.
En este caso es de gran utilidad hacer que “cierto código” sea compilado si se
está en cierta arquitectura, pero que no sea tenido en cuenta si se está en otra
arquitectura.
Sintaxis
#ifdef IDENTIFICADOR
....
#endif
Muy Importante
Las directivas al preprocesador son resueltas por éste antes de que el compilador
genere código objeto.
I.T.B.A. - 2000
Programación I - Clase 14 B 2
Sintaxis
#define IDENTIFICADOR
Lo cual tiene una semejanza con la definición de una constante simbólica, pero
sin molestarse en asociarle valor alguno.
El alcance de una definición de identificador es hasta el final del lote fuente, por
lo tanto si se quisiera más adelante des-definir dicho identificador se procede con otra
directiva al preprocesador
Sintaxis
#undef IDENTIFICADOR
Ejemplo:
El archivo de encabezamiento time.h de Borland C tiene un fragmento donde
define a una constante simbólica con cierto valor según hay encontrado o no definida la
constante __OS2_. Claramente para el compilador de Borland C los sistemas operativos
DOS y OS2 tienen puntos en común. El diseñador de dicha biblioteca decidió definir
con distintos valores a las constantes simbólicas CLOCKS_PER_SEC y CLK_TCK y
no quiso escribir un archivos de encabezamiento distinto para cada uno de ellos. Prefirió
tener todo en un mismo archivo y colocar inclusiones condicionales donde
correspondan.
......
#ifdef __OS2__
#define CLOCKS_PER_SEC 1000
#define CLK_TCK 1000
#else
#define CLOCKS_PER_SEC 18.2
#define CLK_TCK 18.2
#endif
........
I.T.B.A. - 2000
Programación I - Clase 14 B 3
Importante
Ejemplo:
Las directivas antes vistas pueden ser usadas como técnica de debuggeo: se
puede generar determinado código según se esté corriendo la versión debugger, y
eliminarlo cuando se esté generando la versión release.
Pero como no queremos que esos “printf” aparezcan en la versión release, y tampoco
queremos tomarnos el trabajo de eliminarlos del archivo fuente, los escribimos dentro
de una inclusión condicional:
#ifdef DEBUG
printf(“El valor de la variable x=%dºn”, x);
#endif
Con esto estaría casi listo, salvo que no debemos olvidarnos de que el preprocesador
encuentre definido el identificados DEBUG antes de la directiva #ifdef en la versión
debugger, debiendo no encontrarlo en la versión release. Entonces en el archivo fuente
que incluya esas directivas tendremos:
#define DEBUG
#include <stdio.h>
......
#ifdef DEBUG
printf(“El valor de la variable x=%dºn”, x);
#endif
...
I.T.B.A. - 2000
Programación I - Clase 14 B 4
#include <stdio.h>
......
#ifdef DEBUG
printf(“El valor de la variable x=%dºn”, x);
#endif
Nota
/* archivoHeader.h */
#ifndef NOMBRE_DEL_HEADER
#define NOMBRE_DEL_HEADER
#endif
I.T.B.A. - 2000
Programación I - Clase 14 B 5
2. Macro Assert
Existe una macro que puede ser usada para testear si determinada situación
ocurre en nuestros códigos, en tiempo de debuggeo.
Sintaxis
Es muy útil el uso de assert en tiempo de debuggeo, pues no hace perder tiempo
al desarrollador con la ejecución total del programa cuando ya se dio cuenta de que se
produjo algún error. En cambio esto sería inadmisible en la versión release que recibe el
usuario, para el cual el programa debe informar sobre los posibles errores que ocurran
sin abortar bruscamente.
Se colocará assert en aquellos lugares del código donde se supone que “debería
una expresión valer distinta de cero”, y por lo tanto el hecho de que valga cero es
inadmisible y merece detener la ejecución, arreglar el código y re-testear.
I.T.B.A. - 2000
Programación I - Clase 14 B 6
#ifndef EXAMPLE_H
#define EXAMPLE_H
#endif
§ Usar la macro assert para asegura que cada función recibe valores bien definidos y
que los cálculos intermedios también están bien formados.
I.T.B.A. - 2000
Programación I - Clase 15 1
Introducción
En este documento se discuten heurísticas para diseñar funcionalidades
relacionadas en bibliotecas que permitan potenciar el desarrollo de aplicaciones.
v Perspectiva del usuario de la biblioteca: que sólo conoce cómo invocar ciertas
funcionalidades que posee una librería pero desconoce detalles de implementación
de la misma. La ventaja de separar el código que invoca una biblioteca del código
que la implementa es muy importante porque si la biblioteca cambia su
implementación (no su interface) en versiones posteriores, el cliente no se ve
afectado en su desarrollo más que por el hecho de tener que re-linkeditar su
aplicación.
1.1 Su Diseño
El diseño de la misma corresponde a decidir qué funcionalidades podrá ofrecer.
Este proceso ocurre en momento previo a la codificación y supone poder imaginarse
distintos usos para la misma.
Ya que lo único que conocerá el usuario de la biblioteca será su interface, es
muy importante reparar en ella y evaluarla según los siguientes heurísticas
I.T.B.A. - 2000
Programación I - Clase 15 2
Ø Debe ser simple. La idea de usar bibliotecas surge como objetivo para reducir la
complejidad de los programas (es una forma de modularización). La interface
siempre debe ocultar los detalles complejos que tengan que ver con la
implementación de la biblioteca (ni siquiera deben aparecer reflejados en los
comentarios).
Ø Debe ser suficiente. Las funcionalidades serán las necesarias para poder utilizar la
biblioteca en distintas aplicaciones. Si los usuarios encuentran que hay funciones
que deberían ofrecerse y no están soportadas, buscarán otra biblioteca.
Ø Debe ser general. Una interface bien diseñada sirve a varios propósitos y no sólo
para un caso. Recordar que un fuerte objetivo de Ingeniería de Software es la
reusabilidad de código. Si una función hace demasiadas cosas es muy difícil reusarla
y si hace demasiado poco no le sirve nadie. Si el desarrollo de una biblioteca
surgiera en el momento de solución de un problema en particular, debe tomarse
distancia de dicho problema específico y pensar a qué otras aplicaciones podría
servir su desarrollo.
Ø Debe ser estable. Una vez publicada, una interface no debe cambiar. Justamente lo
importante de la separación entre interface e implementación reside en poder
cambiar detalles de esta última en el transcurso del tiempo, sin que por eso se vea
afectado el código de los programas que usen la biblioteca.
1.2 Su Uso
El usuario de una biblioteca es aquel que, leyendo la documentación de la
interface de la misma, está en condiciones de aprovechar las funcionalidades que ésta
ofrece para reducir la complejidad de sus aplicaciones. Obviamente, remitirse a la
especificación de una simple interface, buscando simplificar la estructura de un
programa, implica usar un nivel de abstracción ya que consiste en usar módulos de los
cuales se desconoce su codificación.
Una vez que se ha optado por alguna biblioteca, el programa del usuario se verá
afectado por las funcionalidades publicadas en la interface de dicha biblioteca. Por lo
tanto, el usuario no deseará que su proveedor le cambie dicha interface en las próximas
versiones, porque ello implicaría tener que recambiar también su código. Hay que
diferenciar entre el esfuerzo que significa re-codificar un programa y la acción de sólo
re-linkeditarlo.
Nota:
En la mayoría de los casos el implementador de una biblioteca no es el que la
usa. Sólo en tiempo de desarrollo el que implementa una biblioteca también escribe
algún pequeño código para testearla. No caer en el error de que el código que la testea
se vea afectado por el conocimiento interno de su implementación.
I.T.B.A. - 2000
Programación I - Clase 15 3
Recordemos que las únicas funciones que ANSI C ofrece para este fin son:
§ srand(int)
§ int rand(void)
Las funcionalidades que nuestra biblioteca ofrece pueden ser resumidas en:
I.T.B.A. - 2000
Programación I - Clase 15 4
2.1 Su Interface
/*
** Interface para la biblioteca random
** Version 1.0
** Autores G & G
** Fecha de ultima modificacion 01/01/1998
*/
double randNormalize(void);
void randomize(void);
I.T.B.A. - 2000
Programación I - Clase 15 5
v ¿Tiene criterios unificados? Sí, la forma de uso de las funciones es similar (por lo
menos en las que se parecen entre sí que son randInt y randReal).
v ¿Es simple? La forma de implementación está oculta. La única función que pone en
evidencia un problema de implementación es la necesidad de que el usuario invoque
explícitamente la función randomize para cambiar la secuencia (estamos de alguna
forma dándole a conocer la necesidad de que cambie la semilla generadora en el
algoritmo), pero no es complicada de usar. Hay ciertas operaciones que no se les puede
ocultar al usuario de la biblioteca: por ejemplo las funciones que se ofrecen en una
biblioteca que escriba o lea datos de un archivo en disco, exigirá que el usuario invoque
una función para “abrir” el mismo, y luego de usarlo lo “cierre”. De alguna forma se
está poniendo al descubierto que antes de leer o escribir a un archivo hay que abrirlo y
después cerrarlo, pero se está manteniendo oculto un montón de operaciones que tienen
que ver con la gestión que implica dicha apertura o cierre de archivo (alocar o desalocar
buffers en memoria, guardar la fecha de ultima modificacion del archivo, etc.).
Análogamente, nuestra función no le dice al usuario ni siquiera que se moleste en
invocarla con un nuevo valor de semilla como lo hace srand (notar que randomize no
tiene parámetros).
v ¿Es general? Si la interface provista sirviera sólo para “juegos de dados”, esto sería
un indicador de poca generalidad. Sin embargo vemos que se adapta a diferentes juegos
e inclusive a simulaciones, lo que hace pensar que posee generalidad suficiente.
2.2 Su Implementación
Como nos vamos a valer de las funciones de la biblioteca estándar de C para
armar la biblioteca random, y ésta garantiza que los números pseudoaleatorios que nos
ofrece la función rand() están bien distribuidos en el intervalo [0, RAND_MAX], vamos
a tener que analizar precisamente cómo hacer para que los números que nosotros
devolvamos con las nuevas funcionalidades sigan estando bien distribuidos en los
nuevos rangos propuestos.
I.T.B.A. - 2000
Programación I - Clase 15 6
0 1 2 RAND_MAX
el extremo 1 debe
excluirse
0 1
Por lo tanto el número double buscado podría ser obtenido como un cociente
entre dos números, tal que el numerador sea menor que el denominador:
Aclaraciones
I.T.B.A. - 2000
Programación I - Clase 15 7
/*
** Implementación para la biblioteca random
*/
#include <stdlib.h>
#include “random.h” siempre incluir el prototipo de las mismas funciones
que se están definiendo
double
randNormalize(void)
{
return rand() / ( (double) RAND_MAX + 1);
}
Esta no parece ser una buena distribución para los números pseudoaleatorios por
varios motivos:
a) Si el intervalo fuera [0, 1], estaríamos haciendo rand() % 2, o sea los pares
mapearían al 0 y los impares al 1. Dentro del rango [0, RAND_MAX] la función rand()
nos devuelve números bien distribuidos, por ejemplo: 23, 37, 1371, 1999, 9253, 19, 2,
etc. lo cual con nuestra función estaría mapeando a los números 1, 1, 1, 1, 1, 1, 0 que no
parecen estar bien distribuidos.
b) Si el intervalo fuera [0, 30000] y el tamaño del int en esta arquitectura fuera
2 bytes (o sea el máximo entero fuera 32767), habría números que se generarían con
mayor frecuencia, pues con la fórmula rand() % 30001 tendríamos que los números en
rango 30001 al 32767 volverían a ser mapeados en los primeros.
I.T.B.A. - 2000
Programación I - Clase 15 8
Una segunda aproximación, nos permitiría elegir mejor la fórmula para obtener
números en el rango pedido, aprovechando el hecho de que la función que ya
implementamos para randNormalize() aseguraba una buena distribución.
0 1 el extremo 1 debe
excluirse
........
int
randInt( int izq, int der)
{
return (int) (randNormalize() * (der – izq + 1) ) + izq;
}
I.T.B.A. - 2000
Programación I - Clase 15 9
En todos los casos generamos N números aleatorios dentro del rango entero
[a, b], donde lo esperado teóricamente es una distribución uniforme de N/(b-a+1)
apariciones para cada número entero del intervalo. Obviamente la distribución que se
obtiene experimentalmente no es pareja ya que algunos números logran más apariciones
a expensas del resto.
Caso 1)
Caso 2)
Intervalo = [0, 1]
N= 100
Caso 3)
Intervalo = [0, 1]
N= 20
σp(R%) = 0.0012
σp(R*) = 0.0008
En todos los casos se evidencia que la fórmula usada en nuestra biblioteca produce
menos desviación respecto de la distribución uniforme que la típica distribución a través del
resto de la división entera, aunque la diferencia se hace significativa para pocos tiros.
I.T.B.A. - 2000
Programación I - Clase 15 10
Notar que el usuario de esta biblioteca no debería, por estar operando con
números reales, preguntar por la aparición de “double der”, como una igualdad, sino por
la aproximación del mismo.
........
/*
** Esta funcion devuelve setea la semilla con la hora del sistema
*/
void
randomize(void)
{
srand ( (int) time(NULL) );
}
I.T.B.A. - 2000
Programación I - Clase 15 11
En este caso probaremos la biblioteca con una pequeña aplicación que simula el
juego de ruleta de casino, indicando para cada tiro de la bola el número, su columna,
paridad, y docena ganadora.
/*
** Juego del Casino. Se usa para probar la biblioteca random
*/
#include <stdio.h>
#include “random.h”
#define BORRA_BUFFER while (getchar() ¡= ‘\n’)
void
presentacion(void)
{
printf("\nBienvenido al casino\n");
printf("1- Sacar numero\n");
printf("2- Salir\n");
}
int
columna( int numero )
{
return (--numero % 3 + 1);
}
int
docena( int numero )
{
return (--numero / 12 + 1);
}
int
main( void )
{
int numero;
int opcion;
randomize();
presentacion();
I.T.B.A. - 2000
Programación I - Clase 15 12
do
{
printf("Ingrese Opcion:");
opcion= getchar();
BORRA_BUFFER
if ( !isspace(opcion) && opcion != '1' && opcion != '2')
printf("Opcion Invalida\n");
else
if (opcion=='1')
{
numero= randInt(0, 36);
printf("El numero extraido es %d\n", numero);
if (numero != 0 )
{
printf("Columna: %d\n", columna(numero));
printf("Docena: %d\n", docena(numero));
printf("Paridad: %s\n", (numero%2)? "Impar":"Par");
}
else
printf("CERO: Oooohhhhh\n");
}
}while (opcion != '2');
return 0;
}
Aclaración:
I.T.B.A. - 2000
Programación I - Clase 15 13
La palabra static también puede utilizarse para hacer que la definición de una
función no sea vista por otros módulos. Esto es muy importante especialmente en el
armado de bibliotecas, porque cuando la misma es de cierta complejidad, su
implementación requerirá de muchas más funciones que las que se publican en la
interface. Esa subdivisión de las funcionalidades exportadas en funciones más pequeñas
responde obviamente al objetivo de simplificar el código del implementador de la
biblioteca. Sin embargo todas las funciones que estén definidas en un módulo son por
omisión visibles en el resto, lo que significará que cualquier usuario de nuestras
bibliotecas podría usarlas (pero como no están publicadas, se las usaría sin el correcto
prototipo).
Para evitar conflictos, usaremos la palabra static para todas las declaraciones y
definiciones de una función que no figuren en la interface de la biblioteca.
Importante:
Siempre definir como static todas las funciones de una biblioteca que no estén
publicadas en la interface de la misma.
I.T.B.A. - 2000
Programación I - Clase 15 14
Sintaxis
Hasta ahora la única forma clara de evidenciar que la función no iba a devolver
cualquier entero era definiendo una contante de enumeración:
a partir de esto las funciones, parámetros y variables podían devolver el tipo enum
estado. En nuestro ejemplo anterior tendríamos:
I.T.B.A. - 2000
Programación I - Clase 15 15
Importante
error imprime();
Importante
I.T.B.A. - 2000
Programación I - Clase 16 1
Arreglos - Parte I
Introducción
Hasta el momento nos centramos en las técnicas de desarrollo de algoritmos por
medio de estructuras de control. Discutimos la forma de aumentar la claridad de los
algoritmos por medio de funciones y macros. Sin embargo los datos que se manipularon
eran sencillos. Existe la posibilidad de definir estructuras de datos que permitan
representar a un conjunto de datos como una única entidad. Las estructuras de datos
también ayudan a darle claridad a los programas.
En este documento se describe la estructura de datos llamada array.
1. Arreglo (Array)
Un arreglo es una colección de datos con dos características fundamentales:
v Homogeneidad. Todas sus componentes son del mismo tipo. Así es como se puede
tener un arreglo de enteros, o de chars pero no de ambos mezclados.
v Orden. Cada una de sus componentes tienen un lugar dentro del mismo y pueden
accederse directamente con sólo referirse a dicho lugar. Esto no está diciendo que las
componentes del arreglo estén ordenadas de acuerdo a sus contenido, sino que el orden
del que se habla se refiere al lugar que ocupa una componente dentro de la estructura.
1.1 Declaración
I.T.B.A. - 2000
Programación I - Clase 16 2
Aclaración
Importante
El lenguaje C exige que las variables de tipo arreglo sean declaradas antes de ser
usadas, igual que cualquier otra variable. Pero, a diferencia de los tipos de datos
simples, solicita que se le informe la cantidad máxima de elementos que almacenará.
Una variable de tipo arreglo se guardará el Data Segment, BSS Segment o Stack
(se aplican los mismo conceptos discutidos en la clase de Gestión de Memoria), según
su clase de almacenamiento.
Si se tiene
I.T.B.A. - 2000
Programación I - Clase 16 3
Ejemplo 1:
Si se tiene una arquitectura de 16 bits, con
Ejemplo 2:
Si se tiene una arquitectura de 32 bits, con
Ejemplo 3:
I.T.B.A. - 2000
Programación I - Clase 16 4
Ejemplo 4:
se garantiza que los que faltan inicializar explícitamente se colocarán en cero, pero éstos
deben ser los últimos de la lista (no se pueden saltear elementos sin colocar elementos
entre las comas)
1.3 Acceso
La variable de tipo arreglo designa con un único nombre a todo un conjunto de
componentes homogéneas individuales.
Dicho nombre (el de la variable de tipo arreglo) puede ser usado para referenciar
genéricamente a todas sus componente o para referenciar a una de sus componentes
en particular.
nombreArreglo[ i ]
I.T.B.A. - 2000
Programación I - Clase 16 5
Indice
Ejemplo
Se mostrará el uso de la variable temperatura para almacenar la temperatura
promedio de los 5 continentes.
float temperatura[5];
máxima cantidad
temperatura[0]= 12.3;
temperatura[1]= 25;
a usar 5
temperatura[2]= 22.4;
temperatura[3]= 16;
la última posición válida a referenciar es 1
temperatura[4]= 28;
menos que la cantidad declarada
printf(“Temperatura promedio en Africa=%g\n”, temperatura[4]);
....
Ejemplo
Otra forma hubiera sido:
temperatura[AMERICA]= 12.3;
temperatura[EUROPA]= 25;
a usar 5
temperatura[ASIA]= 22.4;
temperatura[OCEANIA]= 16;
temperatura[AFRICA]= 28;
I.T.B.A. - 2000
Programación I - Clase 16 6
Aclaración
Ejercicio:
Representar cómo iría cambiando la memoria asignada a la variable número, en
tiempo de ejecución, suponiendo que comienza almacenando su primera componente en
la dirección $1500 y el tamaño de entero en esta arquitectura es de 2 bytes.
#include <stdio.h>
int
main( void )
{
int numeros[3];
numeros[2]= -10;
numeros[0]= numeros[1]= numeros[2] * 3;
return 0;
}
Rta:
I.T.B.A. - 2000
Programación I - Clase 16 7
Ese cálculo es muy fácil de hacer, ya que no es otra cosa que multiplicar el
índice indicado por el sizeof( tipo ) del mismo:
desplazamiento
Ejemplo:
De acuerdo al ejemplo anterior
Muy importante
¿Qué ocurre cuando un usuario, quizás por error, intenta referenciar una
componente que está fuera de los límites del arreglo?
I.T.B.A. - 2000
Programación I - Clase 16 8
Ejercicio 1
a)
float reales[ ] = { 2.5, -3.7, -1, 0.34 };
Rta: Se imprimen en la salida estándar las componentes del arreglo de tipo float.
b)
#define DIM 10;
int pares[DIM], i;
Rta: Se llena al arreglo pares con los pares 0, 2, 4, 6, 8, 10, 12, 14 ,16 y 18
c)
int
main(void)
{
int cantidad= 10;
float valores[ cantidad ];
while (cantidad-- )
valores[cantidad]= cantidad;
return 0;
}
Rta: Intenta crear un arreglo de tamaño dado por una variable. No compilará porque el
tamaño máximo debe estar dado por una constante. Atención: estamos hablando de
forma genérica para los compiladores, podríamos encontrar algún compilador en
particular que lo admita, pero nuestro código no será multiplataforma!!!. No
generalizar casos particulares.
I.T.B.A. - 2000
Programación I - Clase 16 9
d)
int
main( void )
{
int rec;
long valores[10];
Rta: Se está intentando blanquear cada componente del arreglo, pero como se está
“pisando memoria” debido a la asignación cuando rec es 10 (recordar que el último
índice válido es uno menos que el que aparece en la declaración), el resultado es
impredecible.
e)
int
main( void )
{
int c, rec letra[26];
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 16 10
Sin embargo, normalmente dicho tamaño es un tope, lo que significa que se usan
muchos menos. En estos casos no tiene ningún sentido recorrer todos los elementos
para inicializarlos, o lo que es más absurdo mostrar su contenido. Por eso cuando no
necesariamente se precisan usar todos los elementos de un arreglo, siempre se trabaja
con una variable entera asociada en la lógica de un programa que guarda el valor
exacto o verdadera dimensión de los elementos del mismo (ver próximo ejemplo).
Ejercicio 2
Escribir un programa que lea valores de la entrada estándar que representan las
notas de los alumnos e indique la desviación estándar. Los valores ya están validados, y
vienen entre 0 y 10, pero un valor negativo indicará el fin del ingreso de datos.
Rta:
#include <stdio.h>
#include <math.h>
#include “getnum.h”
int
main( void )
{
float notas[120];
int dimension= 0;
float promedio= 0;
float total= 0;
if ( dimension > 0 )
{
int cant= dimension;
promedio /= dimension;
while ( dimension-- )
total += pow ( promedio – notas[ dimension ], 2 );
printf(“La desviacion se calcula en %g\n”,
sqrt(total)/ cant);
}
else
printf(“No se han ingresado notas todavia\n”);
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 16 11
Notas:
no pueden aparecer en cualquier orden, ya que hay que asegurarse de que dimension
es realmente menor estricto que la dimensión tope, antes de asignarla como
componente del arreglo, caso contrario se estaría “pisando memoria”, aunque
después no se ejecutara el cuerpo del while. En este caso hay que aprovechar el
hecho de que el operador && es lazy y asegura evaluarse de izquierda a
derecha.
2. Arreglos Multidimensionales
Aunque los bidimensionales son los más usados, ANSI C asegura un mínimo de
12 subíndices.
Ejemplo:
Para una arreglo de 3 filas por 2 columnas podríamos hacer
#define FILAS 3
#define COLUMNAS 2
I.T.B.A. - 2000
Programación I - Clase 16 12
2.1.2 Declaración
/* sólo declaración */
/* declaración e inicialización */
o bien
I.T.B.A. - 2000
Programación I - Clase 16 13
Importante
Ejemplo:
Si se tiene una arquitectura de 32 bits, con
I.T.B.A. - 2000
Programación I - Clase 16 14
2.1.3 Acceso
La variable de tipo arreglo designa con un único nombre a todo un conjunto de
componentes homogéneas individuales.
Pero como, a su vez, los arreglos que lo componen tienen componentes, también
se puede querer acceder a cada una de ellas, esto es a un elemento indicado por su fila y
su columna:
nombreArreglo[ i ] [ j ]
Ejercicio 3:
#define FILAS 10
#define COLUMNAS 5
int
main( void )
{
....
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 16 15
Rta:
#define FILAS 10
#define COLUMNAS 5
int
main( void )
{
int numeros[ FILAS] [COLUMNAS];
int fila, col;
Ejercicio 4:
int
main( void )
{
int dimFilas= 0, dimCol= 0;
float numeros[ FILAS] [COLUMNAS];
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 16 16
Rta:
int
main( void )
{
int dimFilas= 0, dimCol= 0;
float numeros[ FILAS] [COLUMNAS];
float total= 0;
Ejercicio 5:
Indicar cuál sería la salida del siguiente programa, en una arquitectura de 32 bits.
#include <stdio.h>
#define FILAS 5
#define COLUMNAS 10
int
main( void )
{
int numeros[ FILAS] [COLUMNAS];
...
printf(“%d\n”, sizeof( numeros ) );
printf(“%d\n”, sizeof( numeros[0] ) );
printf(“%d\n”, sizeof( numeros[0][0] ) );
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 16 17
Rta:
La salida será
200
40
4
Esto muestra que el compilador trata a cada uno de ellos de forma diferente. Por
ejemplo, numeros[0] es un arreglo de diferente tamaño que numeros a secas.
Hay que tener cuidado cuando se los utiliza en expresiones porque cada uno
de ellos tiene un significado diferente.
I.T.B.A. - 2000
Programación I - Clase 17 1
Arreglos - Parte II
Introducción
Las variables de tipo arreglo también pueden participar del proceso de pasaje de
parámetros a funciones. Esto nos da la potencia de poder pasar todo un conjunto de
componentes al subprograma que las quiera manejar.
1.1 Pasaje de una componente de tipo simple (char, int, float, double)
Una componente de un arreglo que es de tipo simple, podría ser pasada como
parámetro a un arreglo como ya lo conocemos, ya que es un l-value y jugaría el rol de
una variable.
Ejemplo
El siguiente programa lee un mensaje desde la entrada estándar y lo almacena en
un vector de caracteres. Luego de realizar ciertos procesos, utiliza la función encripta
para codificar cada letra del mensaje con la siguiente regla: cada letra del alfabeto inglés
se cambia por otra (de acuerdo a cierta convención), el resto de los caracteres quedan
igual. La función escribe cada letra en la salida estándar, pero la original queda intacta.
#include <stdio.h>
#include <ctype.h>
#define TOPE 5
I.T.B.A. - 2000
Programación I - Clase 17 2
char
encripta( char letra )
{
if ( isalpha(letra) )
letra= transforma[letra - 'A' ];
return letra;
}
int
main( void )
{
char mensaje[ TOPE ];
int dimension = 0;
int leido;
int rec;
..........................................
..........................................
return 0;
I.T.B.A. - 2000
Programación I - Clase 17 3
1ª invocación: main
mensaje[0] encripta
‘P’
letra
‘P’ ‘w’
main
2ª invocación:
mensaje[1]
encripta
‘G’
letra
‘G’ ‘{’
3ª invocación: main
mensaje[2] encripta
‘M’
letra
‘M’ ‘)’
4ª invocación: main
encripta
mensaje[3]
‘1’ letra
‘1’
I.T.B.A. - 2000
Programación I - Clase 17 4
Como se puede observar en cada una de las invocaciones, el valor del parámetro
actual NUNCA cambia, ya que el pasaje de parámetros es realizado por valor, o sea
cuando se crea el stack frame, se crea “una nueva variable” llamada parámetro formal,
y su contenido se inicializa con el contenido del parámetro actual correspondiente. De
esta forma, como dicha variable tiene otra dirección de memoria, y su contenido es
una copia del original, cualquier cambio realizado sobre el parámetro formal NO
cambia el contenido del parámetro actual correspondiente.
Cuando una variable de tipo arreglo se pasa como argumento a una función, se
crea un stack frame y se copia como siempre el contenido del parámetro actual en el
parámetro formal correspondiente.
I.T.B.A. - 2000
Programación I - Clase 17 5
Ejemplo
El siguiente programa utiliza la función leeArreglo para leer desde la entrada
estándar números positivos de tipo float y guardarlos como componentes de un arreglo,
devolviendo además la cantidad de componentes leídas.
#include <stdio.h>
#include “getnum.h”
#define TOPE 5
Es indistinto indicar o no la cantidad de
int componentes de un vector unidimensional.
leeArregloPositivo( float arreglo[] ) El compilador no chequea acceso fuera
{ de los límites
int dim= 0;
float leido;
int
main( void )
{
int cantidad;
float nrosPositivos[TOPE];
I.T.B.A. - 2000
Programación I - Clase 17 6
leido $0xBFFFFD94
dim $0xBFFFFD98 stack frame para
$0xBFFFFD9C usado para leeArregloPositivos
$0xBFFFFDA0 retornar
arreglo $0xBFFFFDA4 $0xBFFFFDA8
nrosPositivos[0] $0xBFFFFDA8 ????
nrosPositivos[1] $0xBFFFFDAC ????
nrosPositivos[2] $0xBFFFFDB0 ???? stack frame para
nrosPositivos[3] $0xBFFFFDB4 ???? main
nrosPositivos[4] $0xBFFFFDB8 ????
cantidad $0xBFFFFDBC ????
main
cantidad nrosPositivos
???? $0xBFFFFDA8
leeArregloPositivo
s
arreglo dim leido
$0xBFFFFDA8 0 ????
I.T.B.A. - 2000
Programación I - Clase 17 7
main
cantidad nrosPositivos
???? $0xBFFFFDA8
leeArregloPositivo
s
arreglo dim leido
$0xBFFFFDA8 0 ????
1 3.4
2 10
2 -2
I.T.B.A. - 2000
Programación I - Clase 17 8
Ejemplo
Supongamos ahora que queremos agregar al programa anterior una función que
imprima cada una de las componentes del vector por la salida estándar. Dicha función
sería:
void
imprimeArreglo( float arreglo[], int dim )
{
int rec;
return;
}
Nótese que esta función debe recibir la dirección de la primera componente del
arreglo, y también la dimensión real del mismo, para recorrerlo sólo hasta donde hay
elementos.
Reflexión
El hecho de que al pasar en los parámetros arreglos a las funciones, sólo se copie
la dirección de su primera componente ofrece ciertas ventajas y desventajas:
v Desventaja: ofrece toda la potencia del cambio de una componente como efecto
colateral. En el caso de funciones donde sólo se quiera leer el contenido de una
componente (no cambiarlo), puede resultar riesgoso. Sintácticamente estaríamos
ofreciendo el mismo prototipo cuando queremos modificar una componente del arreglo
que cuando no queremos hacerlo. Para mejorar la semántica respecto de este punto y
evitar algunos dolores de cabeza, ANSI C ofrece la posibilidad de agregar el calificador
const a un arreglo para indicar que las componentes no serán modificadas. Si un código
intenta modificar las componentes de un arreglo pasado como parámetro y calificado
como const, entonces el compilador indicaría algún error.
I.T.B.A. - 2000
Programación I - Clase 17 9
Ejemplo
Rehacemos la función imprimeArreglo con el calificador const:
void
imprimeArreglo( const float arreglo[], int dim )
{
int rec;
return;
}
Ejemplo
Si lo hubiéramos intentado usar con la función leeArregloPositivos hubiéramos
obtenido un error de compilación del estilo:
int
leeArregloPositivo( const float arreglo[]) No se puede modificar el
{ contenido de una componente
int dim= 0; porque se usó el calificador
float leido; const
return dim;
I.T.B.A. - 2000
Programación I - Clase 17 10
Ejemplo
El siguiente programa muestra la función sumaElementos, que recibe una
matriz, y devuelve la suma de sus componentes:
#include <stdio.h>
#define TOPEFILAS 10
#define TOPECOLUMNAS 5
no hace falta colocar la cantidad de filas (la primera dimensión), sí es obligatorio indicar
la cantidad reservada para las columnas, pues este dato lo necesita el compilador para
calcular la dirección del comienzo de cada fila
float
sumaElementos(const float arreglo[][TOPECOLUMNAS], int dimFil, int
dimCol)
{
float total= 0;
int c;
for( ; dimFil--; )
for( c= 0; c < dimCol; c++)
total+= arreglo[dimFil][c];
return total;
}
I.T.B.A. - 2000
Programación I - Clase 17 11
int
main(void)
{
float matriz[TOPEFILAS][TOPECOLUMNAS];
int cantFilas;
int cantCol;
return 0;
} sólo se invoca con el nombre de la matriz (se va a copiar
la dirección de la primera componente)
Para hacer un seguimiento con los stack frame , vamos a ejecutar en Linux,
suponiendo que el arreglo bidimensional comienza almacenándose en la dirección
$0xBFFFFAA0. De los 50 lugares reservados, sólo se ingresaron dos filas y tres
columnas, y se asignaron sólo los valores:
main
matriz cantFilas cantCol
0xBFFFFCF8 2 3
sumaElementos
arreglo dimFil dimCol total c
0xBFFFFCF8 2 3 0 0
1 2 1
3 2
0 -3 3
7 0
12.3 1
10.3 2
3
I.T.B.A. - 2000
Programación I - Clase 17 12
¿Cómo hace para buscar la componente arreglo[2[[0], o sea la segunda fila del
arreglo? Para eso precisa conocer cuántas columnas reservadas (no las indicadas por
dimCol) saltear para llegar a la segunda fila. Esto lo hace por medio del calculo:
0xBFFFFCF0 10 5.3
0xBFFFFD00 -2 ? ? 2
0xBFFFFD10 5 0 ? ?
0xBFFFFD20 ? ? ? ?
0xBFFFFD30 ? ? ? ?
0xBFFFFD40 ? ? ? ?
0xBFFFFD50 ? ? ? ?
0xBFFFFD60 ? ? ? ?
0xBFFFFD70 ? ? ? ?
0xBFFFFD80 ? ? ? ?
0xBFFFFD90 ? ? ? ?
0xBFFFFDA0 ? ? ? ?
0xBFFFFDB0 ? ? ? ?
I.T.B.A. - 2000
Programación I - Clase 17 13
Ejemplo
El siguiente programa reemplaza todos los elementos de las filas pares de una
matriz bidimensional por sus opuestos. Consideramos que para el usuario la primera
fila está numerada como 1.
#include <stdio.h>
int
main( void )
{
float matriz[TOPEFILAS][TOPECOLUMNAS];
int dimFilas, dimCol;
int rec;
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 17 14
Ejemplo
Vamos a intentar asignarle a una variable de tipo arreglo otra variable del mismo
tipo. También vamos a declarar un parámetro formal de tipo arreglo y vamos a intentar
asignarle otra variable del mismo tipo
#define TOPE 3
void
prueba( float arreglo[])
{
float auxi[TOPE]; No hay problema debido a esta asignación.
El parámetro formal sí es un l-value
arreglo= auxi;
}
int
main(void)
{
float vector[TOPE];
float otro[TOPE];
vector[0]= 10;
vector[1]= 3.5;
vector[2]= 7; No compila debido a este intento
de asignación.
vector= otro; Vector NO es un l-value
prueba( vector);
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 17 15
Conclusiones
Algunos Ejercicios
a) Indicar qué hace el siguiente programa:
void
funcion(int matriz[][TOPECOLUMNAS], int dim)
{
int i, j;
Rta: Setea en 1 a todos los elementos de la diagonal principal y en cero al resto, de una
matriz cuadrada de dimensión real dada por dim.
Rta:
void
funcion(int matriz[][TOPECOLUMNAS], int dim)
{
int i, j;
I.T.B.A. - 2000
Programación I - Clase 17 16
Rta:
int
cantidadHoras( materias horario[ ][VIERNES-LUNES+1],
int dimHorasDiarias, materias miMateria)
{
int hora, dia;
int total= 0;
return total;
int
main(void)
{
materias horario[][VIERNES-LUNES+1]=
{
{ METOD, LIBRE, PGM1, LIBRE, MATE2},
{ METOD, PGM1, PGM1, LIBRE, MATE2},
{ FIS1, PGM1, FIS1, LIBRE, MATE2},
{ FIS1, PGM1, FIS1, LIBRE, MATE2},
{ FIS1, PGM1, LIBRE, LIBRE, LIBRE},
{ FIS1, PGM1, LIBRE, LIBRE, LIBRE},
{ LIBRE, PGM1, LIBRE, LIBRE, LIBRE},
{ LIBRE, LIBRE, LIBRE, LIBRE, LIBRE},
{ LIBRE, LIBRE, LIBRE, LIBRE, DISCRE},
{ LIBRE, LIBRE, LIBRE, LIBRE, DISCRE}
};
printf("PGM1=%d\n", cantidadHoras(horario,
sizeof(horario)/sizeof(horario[0]), PGM1 ));
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 18 1
Introducción a Punteros
Introducción
Dado que en Lenguaje C el pasaje de parámetros es por valor, cuando una
función debe modificar el valor de un parámetro para que lo reciba cambiado quien la
invocó, se le debe enviar la dirección de la memoria en la cual se encuentra dicho
parámetro. Para poder trabajar con direcciones de memoria hay que utilizar punteros.
1. Parámetros de Entrada-Salida
Primera Versión:
#include <stdio.h>
void
intercambio( int num1, int num2)
{
int aux;
aux = num1;
num1 = num2;
num2 = aux;
}
int
main( void )
{
int dato1 = 15;
int dato2 = 32;
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 18 2
Después de la invocación:
aux $0xBFFFFD98 ???? 15
$0xBFFFFD9C usado para
$0xBFFFFDA0 retornar stack frame de
intercambio
num2 $0xBFFFFDA4 32 15
num1 $0xBFFFFDA8 15 32
dato2 $0xBFFFFDAC 32 stack frame de
dato1 $0xBFFFFDB0 15 main
Segunda Versión:
#include <stdio.h>
#define TOPE 2
void
intercambio( int vector[ ])
{
int aux;
aux = vector[0];
vector[0] =vector[1];
vector[1] = aux;
}
int
main( void )
{
int datos[TOPE];
datos[0] = 15;
datos[1] = 32;
printf( “dato1: %d \t dato2: %d\n”, datos[0], datos[1]);
intercambio( datos);
printf( “dato1: %d \t dato2: %d\n”, datos[0], datos[1]);
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 18 3
Además, otro problema sería el tratar de definir funciones que deban devolver
más de un dato, como por ejemplo una función que deba regresar un número complejo:
podría devolver la componente real en su nombre, pero ¿cómo regresar la componente
imaginaria? Sería de pésima programación devolver un arreglo de un único elemento
para solucionar el problema de los parámetros de salida.
2. Punteros
Un puntero es una variable que contiene la dirección de otra variable. Esto
permite considerarlos como un mecanismo de acceso indirecto.
Al declarar una variable tipo puntero, el compilador le asigna una cantidad fija
para que guarde una dirección en la cual va a estar el dato en cuestión
Sintaxis
TipoApuntado * nombreDelPuntero
Ejemplo:
I.T.B.A. - 2000
Programación I - Clase 18 4
Mucho cuidado
Si bien todos los punteros tienen el mismo tamaño (el necesario para indicar una
dirección de memoria), son distintos en cuanto a lo que apuntan. Se verá más tarde que
este punto es muy importante para el momento en el cual se quiera trabajar con el dato
apuntado por cada uno de ellos (desreferencia de los punteros)
Hay que prestar mucha atención, ya que el asterisco que se usa en la declaración
de una variable tipo puntero, está asociado al nombre de la variable y no al tipo
apuntado.
Por lo tanto, si se quieren declarar varias variables tipo puntero en una misma
línea, habrá que colocar el asterisco antes de cada nombre de variable.
Para referenciar una dirección de memoria hay que aplicar el operador & a un l-
value: una variable o una componente de vector. De esta forma se obtiene la dirección
de memoria en la cual se encuentra almacenado dicho objeto.
Ejemplo:
int vector[10];
int legajo= 5000;
double sueldo= 890.50;
I.T.B.A. - 2000
Programación I - Clase 18 5
2.2. Operador *
Operador * (unario)
Ejemplo Global
Sea la siguiente declaración de variables
int x= -5,;
int y = 20;
int *px, *py;
py $0xBFFFFDA8 ???
px $0xBFFFFDAC ???
y $0xBFFFFDB0 20
x $0xBFFFFDB4 -5
I.T.B.A. - 2000
Programación I - Clase 18 6
px = &x;
py = &y;
py $0xBFFFFDA8 0xBFFFFDB0
px $0xBFFFFDAC 0xBFFFFDB4
y $0xBFFFFDB0 20
x $0xBFFFFDB4 -5
(*px)++;
*py = 50;
py $0xBFFFFDA8 0xBFFFFDB0
px $0xBFFFFDAC 0xBFFFFDB4
y $0xBFFFFDB0 50
x $0xBFFFFDB4 -4
• px = py;
py $0xBFFFFDA8 0xBFFFFDB0
px $0xBFFFFDAC 0xBFFFFDB0
y $0xBFFFFDB0 50
x $0xBFFFFDB4 -4
• *px = *py;
py $0xBFFFFDA8 0xBFFFFDB0
px $0xBFFFFDAC 0xBFFFFDB4
y $0xBFFFFDB0 50
x $0xBFFFFDB4 50
I.T.B.A. - 2000
Programación I - Clase 18 7
int *punt;
*punt = 5;
$BFFFFA20
$BFFFFA30 ????
$BFFFFA40 5
$BFFFFA40
NULL
Constante simbólica (está en el header stdio.h) para indicar que un puntero
apunta a “nada” (dirección nula). Por supuesto, es un error grave intentar
desreferenciar un puntero con valor NULL.
Ejemplo
Cuando se declara una variable de tipo arreglo se reserva en memoria el lugar
necesario para almacenar todas sus componentes, en el lugar correspondiente a su
característica (Stack, Data, BSS). En el caso particular de encontrarse en el Stack, sus
componentes no quedan inicializadas, a menos que se haya realizado esta acción en la
declaración.
int
main(void)
{
int rec, numeros[4];
I.T.B.A. - 2000
Programación I - Clase 18 8
En la invocación de la función:
#include <stdio.h>
void
intercambio( int *num1, int *num2)
{
int aux;
I.T.B.A. - 2000
Programación I - Clase 18 9
int
main( void )
{ enviamos las direcciones de las
int dato1 = 15; variables a intercambiar
int dato2 = 32;
return 0;
}
Importante
I.T.B.A. - 2000
Programación I - Clase 18 10
Ejemplo
El siguiente programa recibe un intervalo de tiempo expresado en minutos (en
un parámetro de entrada) y lo devuelve convertido en horas y minutos, a través de dos
parámetros de salida.
/* Archivo: convTime.c
** El siguiente programa lee un tiempo expresado en minutos desde la
** entrada estándar y lo convierte a horas y minutos
*/
#include <stdio.h>
#include “getnum.h”
#define MINUTOS_POR_HORA 60
int
main(void)
{
int tiempo, horas, minutos;
return 0;
}
static void
minToHorasMin(int tiempo, int *pHoras, int *pMinutos)
{
*pHoras = tiempo / MINUTOS_POR_HORA;
*pMinutos = tiempo % MINUTOS_POR_HORA;
}
Notar que desde main, se invoca la función que realizará la transformación con
la variable que contiene el tiempo (copiamos su contenido, ya que es un parámetro de
entrada) y con las direcciones de las variables en las cuales se quieren las horas y los
minutos (parámetros de salida).
I.T.B.A. - 2000
Programación I - Clase 18 11
usado para
$0xBFFFFD9C retornar stack frame de
pMinutos $0xBFFFFDA0 $0xBFFFFDA8 minToHorasMin
pHoras $0xBFFFFDA4 $0xBFFFFDAC
minutos $0xBFFFFDA8 ??? 38
stack frame de
horas $0xBFFFFDAC ??? 20 main
tiempo $0xBFFFFDB0 1238
#include <stdio.h>
int
main(void)
{
double valores[ ] = {1.5, 2.89, 5.0};
imprimir(valores);
return 0;
}
void
imprimir(double vector[ ])
{
int rec, cant;
I.T.B.A. - 2000
Programación I - Clase 18 12
sizeof(vector) / sizeof(vector[0])
sizeof(puntero) / sizeof(double)
cant <= 1
#include <stdio.h>
int
main(void)
{
double valores[ ] = {1.5, 2.89, 5.0};
imprimir(valores, sizeof(valores)/sizeof(valores[0]));
return 0;
}
void
imprimir(double vector[], int cant)
{
int rec;
for (rec= 0; rec < cant ; rec++)
printf(“En la dirección %p hay un %g\n”, &vector[rec],
vector[rec]);
}
I.T.B.A. - 2000
Programación I - Clase 18 13
sizeof(valores) / sizeof(valores[0])
12 / 4
Por otra parte, cuando uno declara el vector, reserva espacio que generalmente
no se ocupa realmente. Por esta razón siempre que se envíe un vector a una función,
hay que enviarle también su dimensión real, excepto que el vector sea de caracteres y
termine en ‘\0’ (ver sección 2).
4. Arreglos y Punteros
Recordando el uso de los operadores & y *, y sabiendo que el nombre de un
arreglo contiene la dirección de la primera componente, podemos decir que:
I.T.B.A. - 2000
Programación I - Clase 18 14
Ejemplo
int
main(void)
{
int datoEntero[3] = { 1000, 2000, 3000 };
int * puntEntero;
long double datoLDouble[5] = { 0, 2.5, 45E+37 };
long double *puntLDouble;
puntEntero = datoEntero;
puntLDouble = datoLDouble;
............................
return 0;
}
STACK
BFFFFFDD0 + 2 * 4 = BFFFFFDD8
BFFFFFDA4 + 2 * 8 = BFFFFFDB4
I.T.B.A. - 2000
Programación I - Clase 18 15
Si se tienen
nombrePuntero = nombreArreglo
entonces
nombrePuntero + n es equivalente a &nombreArreglo[n]
y análogamente
Por este motivo, aunque el Stack crezca hacia las direcciones bajas de
memoria, las componentes de un arreglo dentro del stack se almacenan de manera
que la primera componente quede en la zona más baja reservada para el mismo.
de esta forma al aumentar la dirección de la “cabeza”, sigue estando dentro del
stack.
Dado que cuando una función espera recibir un arreglo en su parámetro formal
lo que realmente recibe es una copia de la dirección de comienzo del arreglo, los
siguientes prototipos son equivalentes:
I.T.B.A. - 2000
Programación I - Clase 19 A 1
Cadena de Caracteres
Introducción
En este documento se muestra el tratamiento especial que hace el lenguaje sobre
los arreglos de caracteres.
Para muchas funciones de la Biblioteca Estándar los strings que se envían como
argumentos deben se NULL TERMINATED, es decir, deben terminar con el caracter
‘\0’ (ASCII = 0). De esta forma se evita tener que enviar la cantidad de caracteres del
string (longitud) , ya que detecta la finalización del mismo al encontrar dicha marca.
Esto resultaba muy difícil de implementar con arreglos de int, long, float,
double, ya que los datos numéricos permitirían cualquier número como válido,
entonces ¿cuál sería la marca elegida? Simplemente no la hay. Por eso al enviar un
arreglo como argumento de una función, se lo debe acompañar con su tamaño.
I.T.B.A. - 2000
Programación I - Clase 19 A 2
printf (“hola”);
printf (“%s\n”, “hola” + 2); /* imprime sólo las dos últimas letras */
Atención
Ejemplo
En este ejemplo se muestra la diferencia entre el uso de string para la
inicialización de arreglos (donde no se los considera strings constantes) y la
inicialización de punteros a char con strings constantes (que ya fueron previamente
almacenados por el compilador en Data o Text).
int
main(void)
{
char saludo[] = “hola”;
char *despedida= “adios”
............................................
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 19 A 3
STACK
DATA ó TEXT
STACK
DATA ó TEXT
I.T.B.A. - 2000
Programación I - Clase 19 A 4
STACK
char *cartel;
cartel[0] = ‘M’;
cartel[1] = ‘A’;
cartel[2] = ‘L’;
cartel[3] = ‘\0’;
char *cartel;
cartel = “BIEN”;
I.T.B.A. - 2000
Programación I - Clase 19 A 5
Mucha Atención
BIEN !!!
NO se debe confundir
MAL !!!
Ejemplo 1
Veremos cómo armar un string "null terminated" en tiempo de ejecución, para
poder usarlo en una función que requiere como parámetro ese tipo de string:
.............................
char cadena[5];
char *pCadena;
.............................
STACK
I.T.B.A. - 2000
Programación I - Clase 19 A 6
.............................
for (i = 0; i < 4; i++)
cadena[i] = ‘a’ + i;
cadena[4] = ‘\0’; /* o bien cadena[4] = 0 */
printf (“cadena: %s\n”, cadena);
....................................
Ejemplo 2
El siguiente código NO COMPILA porque se trata de hacer la asignación de
una dirección (la del string constante "abcd") en el nombre de un arreglo, que no es un l-
value
char cadena[5];
char *pCadena;
cadena = “abcd”;
Ejemplo 3
Si bien el siguiente código compila, tiene un error grave pues estamos
“pisamos memoria”, ya que pCadena no fue inicializado con una dirección válida. Si
en la zona en donde colocamos 'a', 'b' y 0 había información útil, la perdimos.
char cadena[5];
char *pCadena;
STACK
???
‘a’ ‘b’ 0
I.T.B.A. - 2000
Programación I - Clase 19 A 7
Ejemplo 4
El siguiente es un ejemplo de fragmento de código correcto.
char cadena[5];
char *pCadena;
pCadena= cadena;
*pCadena = 'a'; /* idem a pCadena[0] = 'a' */
*(pCadena + 1) = 'b' /* idem a pCadena[1] = 'b' */
*(pCadena + 2) = 0 /* idem a pCadena[2] = 0 */
STACK
#include <stdio.h>
#include <ctype.h>
int
main(void)
{
char cartel[ ] = “Hola”;
imprimirUpper(cartel);
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 19 A 8
void
imprimirUpper(char vector[])
{
int rec, cant;
cant = sizeof(vector);
Una posible solución es usar una función que calcule la cantidad de caracteres
del string. Para esto la Biblioteca Estándar nos brinda la función strlen, que devuelve la
cantidad de caracteres existentes entre el primer caracter del string y la primera
ocurrencia del caracter de ASCII cero (‘\0’), excluyendo a éste último.
void
imprimirUpper(char vector[])
{
int rec, cant;
putchar(‘\n’);
}
Sin embargo hay que tener cuidado al usar funciones de la biblioteca estándar
preparadas para trabajar con cadenas “null terminated”, ya que como lo indica su
nombre, se considera que terminan en ‘\0’
I.T.B.A. - 2000
Programación I - Clase 19 A 9
Ejemplo
En este ejemplo se muestra lo que ocurre si se invoca una función para string
“null terminated” con una cadena que no termina en el caracter nulo esperado.
#include <stdio.h>
#include <ctype.h>
int
main(void)
{
char cartel[5];
cartel[0] = ‘H’;
cartel[1] = ‘o’;
cartel[2] = ‘l’;
cartel[3] = ‘a’;
imprimirUpper(cartel);
return 0;
}
void
imprimirUpper(char vector[])
{
int rec, cant;
I.T.B.A. - 2000
Programación I - Clase 19 B 1
Introducción
En este documento se plantean una serie de ejercicios que ayudan a
conceptualizar los distintos temas vistos durante el dictado de este curso. Las respuestas
se encuentran al final.
Ejercicio 1
Dadas los siguientes fragmentos de código indicar cuáles producen resultados
equivalentes al siguiente patrón
a)
int numeros[4], *pn;
int i;
b)
int numeros[4], *pn;
int i;
pn = numeros;
for ( i= 0; i < 4; i++ )
pn[ i ] = 0;
c)
int numeros[4], *pn;
int i;
pn = numeros;
for ( i= 0; i < 4; i++ )
*( pn + i ) = 0;
I.T.B.A. - 2000
Programación I - Clase 19 B 2
Ejercicio 2
Tomar el ejercicio anterior, suponiendo que se ejecuta en una arquitectura de 32
bits, que a la primera componente del arreglo se le asigna la dirección BFFFFDA0, que
a la variable pn se le asigna la dirección BFFFFD9C y que a la variable i se le asigna la
dirección BFFFFD98.
Ejercicio 3
Dadas las siguientes funciones indicar cuáles de los siguientes prototipos
resultan equivalentes entre sí:
Ejercicio 4
Para los prototipos del ejercicio 3, indicar cuáles de las siguientes invocaciones
compilarían:
a) int nro;
funcion( &nro );
b) int * nro;
funcion ( nro );
c) int numeros[10];
funcion( numeros );
d) int numeros[10];
funcion( &numeros[0] );
Ejercicio 5
Explicar por qué lenguaje C decidió que cuando se pasa un arreglo como
argumento en la invocación de una función, sólo se pasa la dirección de su primera
componente.
I.T.B.A. - 2000
Programación I - Clase 19 B 3
Ejercicio 6
Suponiendo que el stack comienza en la dirección 0xBFFFFDB0, que el Data
empieza en 0xCCCCAAAA y que la zona BSS comienza en 0xBBBB0000, indicar la
salida en cada uno de los siguientes programas, en el caso de que compilen:
a)
#include <stdio.h>
int
main(void)
{
int numero= 5, *puntero;
puntero= №
printf(“%p\n %p \n %p \n %d\n”, &numero, &puntero, puntero,
*puntero);
return 0;
}
b)
#include <stdio.h>
int
main(void)
{
int numero= 5;
static int *puntero;
puntero= №
printf(“%p\n %p \n %p \n %d\n”, &numero, &puntero, puntero,
*puntero);
return 0;
}
c)
#include <stdio.h>
int
main(void)
{
int numero= 5;
static int *puntero = №
I.T.B.A. - 2000
Programación I - Clase 19 B 4
Ejercicio 7
Indicar cuál de los siguientes programas contienen errores:
a)
#include <stdio.h>
void
leer( char *s)
{
/* esta función lee en s un string desde la entrada estandar */
}
int
main(void)
{
char *buffer;
leer (cartel);
printf (“%s”, buffer);
return 0;
}
b)
#include <stdio.h>
void
leer( char *s)
{
/* esta función lee en s un string desde la entrada estandar */
}
int
main(void)
{
char cartel[100];
leer (cartel);
printf (“%s”, cartel);
return 0;
}
Ejercicio 8
Hacer un programa que lea dos matrices y calcule, si se puede, su
producto.
n
A × B = C donde c ij = Σ aik * bkj
↓ ↓ ↓ 1
m.n n.p m.p
I.T.B.A. - 2000
Programación I - Clase 19 B 5
Respuestas
Ejercicio 1
a) Es equivalente ya que las expresiones i++ e ++i no son usadas sino para aprovechar
sus efectos colaterales: “incrementar la variable i”.
valor temporario
Ejercicio 2
a)
BFFFFD98 ???????? i
BFFFFD9C ???????? pn
BFFFFDA0 ???????? numeros
BFFFFDA4 ????????
BFFFFDA8 ????????
BFFFFDAC ????????
I.T.B.A. - 2000
Programación I - Clase 19 B 6
b)
• Cuando se ejecuta pn= numeros, se obtiene:
BFFFFD98 ???????? i
BFFFFD9C BFFFFDA0 pn
BFFFFDA0 ???????? numeros
BFFFFDA4 ????????
BFFFFDA8 ????????
BFFFFDAC ????????
• i= 0;
• *( pn + i ) = 0; ⇒ * ( BFFFFDA0 + 0) = 0 ⇒ * (BFFFFDA0 ) = 0
esta suma se realiza sumándole a la dirección dada 0 componente, cada una de tamaño sizeof(int)
BFFFFD98 0 i
BFFFFD9C BFFFFDA0 pn
BFFFFDA0 0 numeros
BFFFFDA4 ????????
BFFFFDA8 ????????
BFFFFDAC ????????
• i= 1;
• *( pn + i ) = 0; ⇒ * ( BFFFFDA0 + 1 ) = 0 ⇒ * (BFFFFDA4 ) = 0
BFFFFD98 1 i
BFFFFD9C BFFFFDA0 pn
BFFFFDA0 0 numeros
BFFFFDA4 0
BFFFFDA8 ????????
BFFFFDAC ????????
• i= 2;
• *( pn + i ) = 0; ⇒ * ( BFFFFDA0 + 2) = 0 ⇒ * (BFFFFDA8 ) = 0
BFFFFD98 2 i
BFFFFD9C BFFFFDA0 pn
BFFFFDA0 0 numeros
BFFFFDA4 0
BFFFFDA8 0
BFFFFDAC ????????
I.T.B.A. - 2000
Programación I - Clase 19 B 7
• i= 3;
• *( pn + i ) = 0; ⇒ * ( BFFFFDA0 + 3) = 0 ⇒ * (BFFFFDAC ) = 0
BFFFFD98 3 i
BFFFFD9C BFFFFDA0 pn
BFFFFDA0 0 numeros
BFFFFDA4 0
BFFFFDA8 0
BFFFFDAC 0
•i = 4;
BFFFFD98 4 i
BFFFFD9C BFFFFDA0 pn
BFFFFDA0 0 numeros
BFFFFDA4 0
BFFFFDA8 0
BFFFFDAC 0
Ejercicio 3
Todos son equivalentes entre sí, ya que cuando el
compilador encuentra un arreglo como parámetro formal es
tratado como puntero.
Obviamente el código dentro de la implementación de
las funciones deberá tenerlo en cuenta, ya que debería saber
hasta cuántas componentes debe acceder. Esto podría ser una
convención de algún caracter especial como en el caso de los
strings que “incrustan” el ASCII 0 para indicar terminación, o
habría que pasar un parámetro extra.
Ejercicio 4
Todas compilan, ya que todas pasan como parámetro
actual la dirección de un número entero.
Cabe señalar, que en el segundo caso, dependiendo cuál sea
el objetivo de la función, puede haber problemas en tiempo de
ejecución ya que nro es un puntero a un entero pero no se ha
inicializado para que apunte a un entero válido antes de la
invocación.
Ejercicio 5
Por eficiencia. De no ser así se estarían copiando todas las
componente del mismo en el stack, lo cual consumiría tiempo y espacio
en cada invocación.
I.T.B.A. - 2000
Programación I - Clase 19 B 8
Ejercicio 6
a) Salida
b) Salida
BFFFFDAC
BFFFFDB0 BFFFFDB0 5 numero
STACK
BBBB0000
BFFFFDB0
5 BBBB0000 BFFFFDB0 puntero
BBBB0004 BSS
c)
Ni siquiera compila (No puede completar la zona DATA)
“inicializer element is not constant”
El problema es que puntero es una variable static inicializa por el
programa se debe almacenar en tiempo de compilación (se aloca en DATA), pero la
dirección de la variable numero recién se va a conocer en tiempo de ejecución (cuando
se aloque en el STACK)
Ejercicio 7
a)
HORRIBLE!
No se reservó lugar para leer la cadena que comienza en buffer.
La variable buffer (tipo puntero) se aloca en el stack, pero su contenido es
desconocido (apunta acualquier zona de memoria). Luego, a través del uso
de scanf, el string que se ingrese desde la entrada estándar se guardará a
partir de esa zona desconocida de memoria, destruyendo lo que hubiere en su
lugar.
b)
CORRECTO!
La función main reservó lugar en la variable cartel para la lectura del
string y le pasó el nombre del arreglo (r-value) a la función que hace la
lectura. Cabe aclarar que el mismo efecto se hubiese logrado con la
invocación de scanf(“%s”, cartel) desde main.
I.T.B.A. - 2000
Programación I - Clase 19 B 9
Ejercicio 8
/* Archivo iomatriz.h */
/* Archivo iomatriz.c */
#include <stdio.h>
#include “getnum.h”
#include “iomatriz.h”
void
leeMatriz( float matriz[][DIM], int *cantFil, int *cantCol)
{
int i,j;
sugerimos, como
*cantFil= getint("\nIngrese la cantidad de filas:"); extensión, validar que
cantFil y cantCol no
superen la dimensión
*cantCol= getint("\nIngrese la cantidad de columnas:"); tope dad por DIM
void
imprimeMatriz ( float matriz[ ][DIM], int cantFil, int cantCol)
{
int i, j;
I.T.B.A. - 2000
Programación I - Clase 19 B 10
/* Archivo prodMatriz.c */
#include <stdio.h>
#include “iomatriz.h”
int
main(void)
{
float matA[DIM][DIM], matB[DIM][DIM], matC[DIM][DIM] ;
int cantFilA, cantColA, cantFilB, cantColB;
int i, j, k;
if (cantColA != cantFilB)
printf ( "No se puede hacer el producto\n");
else
{
for (i=0; i<cantFilA; i++)
for (j=0; j<cantColB; j++)
{
matC[i][j]= 0;
for (k=0; k<cantColA; k++) /* o bien k<cantFilB */
matC[i][j] += matA[i][k] * matB[k][j];
}
printf("Matriz A*B:\n");
imprimeMatriz (matC, cantFilA, cantColB);
}
return 0;
}
Muy Importante:
Notar que en la función leeMatriz, que recibe desde quien la invoca el puntero a
entero cantFil, hubiera sido incorrecto hacer la lectura como cantFil=getint(“”) ya que
en ese caso el dato leído hubiera quedado en el stack frame de leeMatriz, perdiéndose al
regresar a quien la invocó (en este caso main).
I.T.B.A. - 2000
Programación I - Clase 20 1
Aritmética de Punteros
Introducción
En el lenguaje C los punteros pueden participar de un número restringido de
operaciones aritméticas, de asignación y de comparación. A continuación se describen los
operadores que pueden aplicarse a los operandos punteros y para qué sirve su uso
Sintaxis Significado
aPointer ++ La variable aPointer quedó apuntando a un elemento a
++ aPointer la derecha más que el original
Importante
Nótese que en todos lo casos cuando se suma o resta un número a una variable de
tipo puntero, el numero indicado NO SE INTERPRETA como cantidad de bytes, sino
como cantidad de elementos (donde el tamaño de elemento está dado por el tipo que se
especificó al indicar el puntero).
I.T.B.A. - 2000
Programación I - Clase 20 2
Muy Importante
Ejercicio 1
Indicar qué se obtiene si se ejecuta el siguiente fragmento de código en una
arquitectura de 32 bits donde el tamaño del double es 8 bytes y la dirección de la primera
componente del arreglo se encuentra en $BFFFFDA8:
puntero= arreglo;
puntero + 1;
puntero++;
printf("El contenido de puntero es=%p\n", puntero);
printf("El elemento referenciado por puntero es=%g\n", *puntero);
(*puntero)++;
printf("El contenido de puntero es=%p\n", puntero);
printf("El elemento referenciado por puntero es=%g\n", *puntero);
Respuesta
I.T.B.A. - 2000
Programación I - Clase 20 3
Pregunta
Hubiera sido lo mismo evaluar la expresión *puntero++ y evitar el uso de
paréntesis? Explicar.
Respuesta
No, por la precedencia de operadores y asociatividad. En este caso al no tener
paréntesis hay que observar cual de los dos operadores se aplica primero: tienen la misma
precedencia. Entonces hay que resolverlo con la asociatividad que es de derecha a
izquierda. Así es como al evaluar la expresión tenemos que se realiza un post-incremento
del puntero y la desreferencia del mismo, pero como es una post-referencia su efecto
ocurrirá al finalizar la evaluación de dicha expresión, obtenemos el valor apuntado por la
variable puntero y como efecto colateral el puntero avanza luego a la componente
siguiente.
O sea que de no haber usado paréntesis, los elementos del arreglo hubieran quedado
intactos y como al resultado de la desreferencia no se lo usó en ninguna otra expresión, se
hubiera obtenido el mismo efecto que hacer puntero++
Ejercicio 2
Suponiendo que la memoria se encuentra al comenzar la ejecución del código
anterior con los siguiente valores, indicar paso a paso como va modificándose el código del
ejercicio 1 al correr la aplicación.
0 1 2 3 4 5 6 7 8 9 A B C D E F
BFFFFDA ???????? 5.5
BFFFFDB 6.6 7.7
BFFFFDC
Respuesta
0 1 2 3 4 5 6 7 8 9 A B C D E F
BFFFFDA BFFFFDA8 5.5
BFFFFDB 6.6 7.7
BFFFFDC
I.T.B.A. - 2000
Programación I - Clase 20 4
c) Después de puntero++:
0 1 2 3 4 5 6 7 8 9 A B C D E F
BFFFFDA BFFFFDB0 5.5
BFFFFDB 6.6 7.7
BFFFFDC
d) Despues de (*puntero)++:
0 1 2 3 4 5 6 7 8 9 A B C D E F
BFFFFDA BFFFFDB0 5.5
BFFFFDB 7.6 7.7
BFFFFDC
Ejercicio 3
Indicar qué se obtiene si se ejecuta el siguiente fragmento de código en una
arquitectura de 32 bits donde el tamaño del double es 8 bytes y la dirección de la primera
componente del arreglo se encuentra en $BFFFFDA8:
arreglo[2]= *++puntero;
Respuesta
I.T.B.A. - 2000
Programación I - Clase 20 5
Ejercicio 4
¿Qué se obtiene al ejecutar el siguiente fragmento de código?
puntero1= arreglo;
puntero2= &arreglo[2];
Respuesta
puntero2 – puntero1= 2
puntero1 – puntero2= -2
Pregunta
¿Se obtendría el mismo resultado si en el ejemplo anterior se realiza puntero1=
&arreglo[0] en vez de puntero1= arreglo?
Respuesta
Sí, porque el nombre del arreglo es la dirección de la primera componente
Muy Importante
I.T.B.A. - 2000
Programación I - Clase 20 6
Sintaxis Significado
La variable aPointer quedó apuntando numero elementos
aPointer += numero
más a la derecha que originalmente
La variable aPointer quedó apuntando numero elementos
aPointer –= numero
más a la izquierda que originalmente
Asignar el contenido de la variable anotherPointer al
aPointer = anotherPointer
contenido de la variable aPointer
Muy Importante
En ANSI C los tipos puntero a entero, puntero a double, puntero a char, etc. son
distintos, por lo cual un puntero puede ser asignado a otro si son del mismo tipo, o alguno
de ellos es de tipo puntero a void.
Recordar
Para cualquiera de las dos declaraciones se puede usar aritmética de punteros porque el
parámetro formal vector es un puntero.
I.T.B.A. - 2000
Programación I - Clase 20 7
Aparte de esta restricción, las variables de tipo puntero y de tipo arreglo se pueden usar
en expresiones de manera que parezcan intercambiables.
Por lo tanto es frecuente inicializar una variable puntero con la dirección de alguna
componente de un arreglo y luego subindicarlo para acceder a otras componentes del
arreglo (de ahí que se diga que los punteros y los arreglos son prácticamente
intercambiables, ya que participan de formas sintácticas similares).
Ejemplo:
Indicar qué imprime el siguiente programa, suponiendo que la dirección de la
primera componente del arreglo es $BFFFFDA8 y la arquitectura es de 32 bits con tamaño
de double de 8 bytes
#include <stdio.h>
int
main(void)
{
double arreglo[] = { 5.5, 6.6, 7.7 };
sorpresa( arreglo );
return 0;
}
Respuesta
I.T.B.A. - 2000
Programación I - Clase 20 8
Sintaxis Significado
aPointer == anotherPointer Se compara los contenidos de las variable apuntadoras
aPointer != anotherPointer (para saber si apuntan o no al mismo lugar)
aPointer < anotherPointer
Se comparan los contenidos de las variables
aPointer <= anotherPointer
apuntadoras para saber cual de ellos apuntan a
aPointer > anotherPointer
direcciones menores o mayores de memoria.
aPointer >= anotherPointer
Importante
Las comparaciones entre punteros no tienen ningún sentido, a menos que los
punteros señalen a miembros del mismo arreglo.
Una comparación de dos punteros que señalen al mismo arreglo podría servir para
no irse fuera de los límites del arreglo.
Ejercicio 5
Re-escribir la función strlen de la librería estándar, utilizando aritmética de
punteros para recorrer el arreglo de caracteres.
Respuesta
Para saber si llegué al fin del recorrido no es necesario que reciba como parámetro
la cantidad de componentes, siempre que el string sea NULL terminated, pues ya tiene un
caracter incrustado al final (‘\0’o ASCII 0 ) para saber que allí finaliza.
return cantidad;
}
I.T.B.A. - 2000
Programación I - Clase 20 9
Pregunta
¿Se puede invocar con un string constante, como se indica a continuación?
int
main(void)
{
printf("%d\n", strlen ( "hola") );
return 0;
}
Respuesta
Sí, porque lo que se copia en el parámetro formal es la dirección de la primera
componente del arreglo. Cuando se aplica aritmética de punteros al parámetro formal no se
está cambiando la dirección de la primera componente del arreglo original. Las
componentes del arreglo tiene direcciones de memoria, pero no pueden moverse por la
memoria. El puntero que sirve para recorrer el arreglo sí puede ir cambiando su contenido
para referenciar en cada instante distintas componentes del arreglo.
Ejemplo
A continuación vamos a graficar la memoria, paso a paso, durante la ejecución de
la función strlen suponiendo que el string constante “hola” se encuentra cargado a partir
de la dirección $0040604C, y que las variables cantidad y string se encuentran en las
direcciones $0063FDE8 y $0063FDF4 respectivamente
0 1 2 3 4 5 6 7 8 9 A B C D E F
0040604 ‘h’ ‘o’ ‘l’ ‘a’
0040605 0
.....
0063FDE 0
0063FDF 0040604C
§ Al evaluar (*string++) se obtiene ‘h’ y como efecto colateral avanza el puntero, o sea
que por solo evaluar eso se modifica la memoria:
0 1 2 3 4 5 6 7 8 9 A B C D E F
0040604 ‘h’ ‘o’ ‘l’ ‘a’
0040605 0
.......
0063FDE 0
0063FDF 0040604D
I.T.B.A. - 2000
Programación I - Clase 20 10
0 1 2 3 4 5 6 7 8 9 A B C D E F
0040604 ‘h’ ‘o’ ‘l’ ‘a’
0040605 0
.......
0063FDE 1
0063FDF 0040604D
§ Al evaluar (*string++) se obtiene ‘o’ y como efecto colateral avanza el puntero, o sea
que por solo evaluar eso se modifica la memoria:
0 1 2 3 4 5 6 7 8 9 A B C D E F
0040604 ‘h’ ‘o’ ‘l’ ‘a’
0040605 0
......
0063FDE 1
0063FDF 0040604E
0 1 2 3 4 5 6 7 8 9 A B C D E F
0040604 ‘h’ ‘o’ ‘l’ ‘a’
0040605 0
......
0063FDE 2
0063FDF 0040604E
§ Al evaluar (*string++) se obtiene ‘l’ y como efecto colateral avanza el puntero, o sea
que por solo evaluar eso se modifica la memoria:
0 1 2 3 4 5 6 7 8 9 A B C D E F
0040604 ’h’ ‘o’ ‘l’ ‘a’
0040605 0
......
0063FDE 2
0063FDF 0040604F
I.T.B.A. - 2000
Programación I - Clase 20 11
0 1 2 3 4 5 6 7 8 9 A B C D E F
0040604 ‘h’ ‘o’ ‘l’ ‘a’
0040605 0
.....
0063FDE 3
0063FDF 0040604F
§ Al evaluar (*string++) se obtiene ‘a’ y como efecto colateral avanza el puntero, o sea
que por solo evaluar eso se modifica la memoria:
0 1 2 3 4 5 6 7 8 9 A B C D E F
0040604 ‘h’ ‘o’ ‘l’ ‘a’
0040605 0
.....
0063FDE 3
0063FDF 00406050
0 1 2 3 4 5 6 7 8 9 A B C D E F
0040604 ‘h’ ‘o’ ‘l’ ‘a’
0040605 0
......
0063FDE 4
0063FDF 00406050
§ Al evaluar (*string++) se obtiene ‘\0’ o sea que no entra en el cuerpo del while. Como
efecto colateral igualmente avanza el puntero:
0 1 2 3 4 5 6 7 8 9 A B C D E F
0040604 ‘h’ ‘o’ ‘l’ ‘a’
0040605 0
.....
0063FDE 4
0063FDF 00406051
I.T.B.A. - 2000
Programación I - Clase 20 12
4. Arreglo de Punteros
Si quisiéramos armar una colección de strings tendríamos dos posibilidades:
• armar una matriz bidimensional de caracteres
• armar un arreglo de punteros a caracteres
Ejemplo
Veamos la declaración del arreglo de cadenas de caracteres palo, que puede servir
para representaron un mazo de naipes.
Cada una de las cadenas tiene distinta longitud, pero lo único que guarda cada una
de las componentes del arreglo palo son punteros a donde se encuentran dichos strings. No
guardan sus caracteres (no es una matriz bidimensional)
Ejercicio 6
Escribir un programa que primero imprima qué direcciones contiene cada una de
esas componentes (para saber dónde están dichos strings) y luego imprima los strings
Respuesta
int
main(void)
{
char *palo[ ] = {"Copa", "Basto", "Oro", "Espada"};
int i;
I.T.B.A. - 2000
Programación I - Clase 20 13
‘\0’ ó ASCII 0
0 1 2 3 4 5 6 7 8 9 A B C D E F
0040603 ‘C’ ‘O’ ‘P’ ‘A’ 0
0040604 ‘B’ ‘A’ ‘S’ ‘T’ ‘O’ 0
0040605 ‘O’ ‘R’ ‘O’ 0
0040606 ‘E’ ‘S’ ‘P’ ‘A’ ‘D’ ‘A’ 0
......
0063FDE 00406038 00406048 00406054
0063FDF 00406060
I.T.B.A. - 2000
Programación I - Clase 21 1
Introducción
En este documento presentaremos una serie de funciones se ofrecen en la
Biblioteca Estándar de C y que no tratamos anteriormente porque involucraban
punteros.
prototipo descripción
Convierte la cadena s a double.
double atof( const char *s); Trunca la conversión en el primer caracter no
convertible a número.
Convierte la cadena s a int.
int atoi( const char *s); Trunca la conversión en el primer caracter no
convertible a número.
Convierte la cadena s a long.
long atol( const char *s); Trunca la conversión en el primer caracter no
convertible a número.
Ejemplos de invocación:
#include <stdlib.h>
#include <stdio.h>
int
main(void)
{
char num[10]="23092.654";
char numBad[10]="230G.654";
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 21 2
prototipo descripción
Regresa en su nombre la fracción normalizada
double frexp( double x, int *exp); [1/2, 1] de x, y en *exp la potencia de 2.
Si x es cero, ambas partes se devuelven en cero.
Ejemplos de invocación:
#include <math.h>
#include <stdio.h>
int
main(void)
{
double p_frac , p_ent , num= 1.005;
int expon;
1.0 0.005
p_frac= frexp(num, &expon);
printf("numero: %f \t 2^ %d\ n", p_frac , expon);
0.5025 1
1.005 = 0.5025 * 21
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 21 3
prototipo descripción
char *strcpy (char *sd, const char * sf); Copia sf en sd y devuelve sd.
Copia n caracteres de sf en sd y
char *strncpy (char *sd, const char * sf, int n);
devuelve sd.
Devuelve sd con sf concatenado al
char *strcat (char *sd, const char * sf)
final
Devuelve sd con n caracters de sf
char *strncat (char *sd, const char * sf, int n);
concatenados al final
Compara sf con sd.
Si sd < sf, devuelve un número < 0
int strcmp (const char *sd, const char * sf);
Si sd > sf, devuelve un número > 0
Si sd == sf, devuelve 0
Compara n caracteres de sf con sd.
int strncmp (const char *sd, const char * sf , int n);
Devolución: ídem a strcmp
Devuelve un puntero a la primera
char *strchr (const char *sd, char c); aparición del caracter c en sd.
Si no existe, devuelve NULL.
Devuelve un puntero a la última
char *strrchr (const char *sd, char c); aparición del caracter c en sd.
Si no existe, devuelve NULL.
Devuelve un puntero a la primera
aparición en sd de cualquier caracter
char *strpbrk (const char *sd, const char * sf);
de sf .
Si no lo hay devuelve NULL.
Devuelve un puntero a la primera
char *strstr (const char *sd, const char * sf); aparición del substring sf en sd.
Si no la hay devuelve NULL.
I.T.B.A. - 2000
Programación I - Clase 21 4
Ejemplos de invocación:
#include <string.h>
#include <stdio.h>
int
main(void)
{
strcpy(cad2, "proof");
xx= strcpy(cad1,cad2);
printf("&cad1=%p \n &cad2=%p \n xx=%p \n" , cad1, cad2, xx);
printf("cad1: %s \n cad2: %s \n", cad1, cad2);
strcpy(cad2,"proof");
xx= strncpy(cad1, cad2, 3);
cad1[3]=0;
printf("&cad1=%p \n &cad2=%p \n xx=%p \n", cad1, cad2, xx);
printf("cad1:%s \n cad2: %s\n", cad1, cad2);
strcpy(cad1,"casa");
strcpy(cad2,"blanca");
strcat(cad1,cad2);
printf("&cad1=%p \n &cad2=%p \n", cad1, cad2);
printf("cad1:%s \ncad2:%s \n", cad1, cad2);
casablanca blanca
strcpy(cad1,"casa");
strcpy(cad2,"blanca");
strncat(cad1,cad2,4);
printf("&cad1=%p \n &cad2=%p \n", cad1, cad2);
printf("cad1:%s \ncad2:%s \n", cad1, cad2);
casablan blanca
I.T.B.A. - 2000
Programación I - Clase 21 5
strcpy(cad1,"casa");
strcpy(cad2,"Casa");
if ( (compare= strcmp(cad1,cad2)) == 0)
printf("%s y %s Son iguales\n", cad1, cad2);
else
if (compare>0)
printf("%s > %s \n", cad1, cad2);
else
printf("%s < %s \n", cad1, cad2);
strcpy(cad1,"casa");
strcpy(cad2,"casas");
if ( (compare= strncmp(cad1,cad2,2)) == 0)
printf("%s y %s tienen igual el prefijo %d\n", cad1, cad2,
2);
else
if (compare>0)
printf("%s > %s en su %d prefijo\n", cad1, cad2, 2);
else
printf("%s < %s en su %d prefijo\n", cad1, cad2, 2);
strcpy(cad1,"abracadabra");
xx= strchr(cad1,'r');
printf("&cad1=%p \n cad1=%s letra=%c\n xx=%p \n",
cad1, cad1, 'r', xx);
return 0;
I.T.B.A. - 2000
Programación I - Clase 21 6
prototipo descripción
Obtiene datos desde el flujo stdin (entrada
estándar), con el formato pedido.
int scanf( const char * formato, ...); Devuelve EOF si ocurre un error en la
entrada antes de cualquier conversión o el
número de ítems que se llegaron a asignar.
Obtiene datos desde el string “null
int sscanf( const char * s,
terminated” s, con el formato pedido.
const char *formato, ... );
Se detiene si encuentra el caracter nulo.
Estas funciones son similares a printf, pero proveen funcionalidad para lectura
de valores desde una entrada (estándar o un string). Son útiles para la lectura de valores
de tipos simples.
I.T.B.A. - 2000
Programación I - Clase 21 7
I.T.B.A. - 2000
Programación I - Clase 21 8
CONSEJO
I.T.B.A. - 2000
Programación I - Clase 21 9
Nota Importante
Notar que, aunque la función scanf devuelve el número de ítems que pudieron
ser asignados, regresando cero si no se llegó a leer ninguno. Sin embargo, en caso de
encontrar un error (como por ejemoplo que se temina el archivo antes de leer) devuelve
EOF, con lo cual ya no se sabe cuántos fueron exitosamente almacenados. Si se quiere
tener un control estricto, hay que dividir el ingreso en múltiples llamadas a scanf.
Por ejemplo, supongamos que scanf está esperando un valor float y llega
123EASY. Aunque el subcampo 123E resulta válido, la conversión requiere al menos
un dígito de exponente. Entonces 123E es consumido, pero la conversión falla. NO se
almacena valor alguno (123E se pierde) y la función scanf retorna. El próximo caracter
a leer desde la entrada es A. Este problema es típico de un float o double, en otros
tipos esto generalmente no ocurre.
I.T.B.A. - 2000
Programación I - Clase 21 10
Ejemplos de invocación:
#include <stdio.h>
int
main(void)
{
printf("ingrese fecha[dd/mm/aa]:");
cant= scanf("%d / %d / %d", &d, &m, &a);
printf("cant=%d -> dia=%d\t mes=%d\t anio=%d\n", cant, d, m, a);
printf("ingrese edad:");
cant= scanf("%d", &edad);
printf("cant=% d-> edad=%d \n", cant, edad);
printf("ingrese un número:");
scanf("%[0123456789] %[0-9]", cad1, cad2);
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 21 11
5. Ejercicios de Aplicación
Ejercicio 5.1
Escribir una función invertString que invierta los caracteres de un string null
terminated (parámetro de entrada) y los deje en un arreglo de caracteres (parámetro de
salida) que debe contar con cantidad suficiente resevada para alocar los caracteres del
primer parámetro, además del cero final.
Ejemplo:
Si se tiene
char *dato = “HOLA - CHAU!”;
char cambio[20];
invertString( dato, cambio);
Respuesta
void
invertString( const char *fuente, char *destino)
{
int cant;
cant = strlen(fuente);
*destino = 0;
}
Ejercicio 5.2
Escribir una función invertNombre que invierta el orden entre nombre y apellido
de un string null terminated (parámetro de entrada) donde cada nombre está separado
por un espacio en blanco y el último de todos ellos es el apellido. La inversión debe
quedar en un segundo arreglo de caracteres (parámetro de salida) de manera tal que
después del apellido aparezca una coma y luego los nombres en el orden correcto. En un
tercer parámetro se recibe la máxima cantidad reservada para el parámetro de salida, sin
incluir el lugar para el ‘\0’.
Si la inversión se realiza con éxito, la función debe retornar en su nombre un 0,
caso contrario debe devolver un 1.
Se cuenta con una función trim que recibe un string null terminated (parámetro
de entrada-salida) y lo retorna habiéndole sacado todos los blancos de adelante y de
atrás (su código se muestra en el ejercicio 5.3)
I.T.B.A. - 2000
Programación I - Clase 21 12
Ejemplo:
Si se tiene
char *nombre = “Maria Laura Santillan”;
char newNombre[30];
int error;
int
invertNombre( char *fuente, char *destino, int maximo )
{
int cant, error = 0;
char *st;
trim(fuente);
cant = strlen(fuente);
return error;
}
I.T.B.A. - 2000
Programación I - Clase 21 13
int
invertNombre2( char *fuente, char *destino, int maximo )
{
int cant;
char *st;
trim(fuente);
cant = strlen(fuente);
strcpy(destino, fuente);
}
else
{
cant++; /* agrego lugar para la coma */
if (cant > maximo)
return 1;
strcpy(destino, st + 1);
strcat(result, “, ”);
strncat(destino, fuente, st – fuente);
}
return 0;
}
Ejercicio 5.3
Escribir una función IntValido que devuelva en su nombre un número entero
(long) leído desde la entrada estándar, correctamente validado como entero, y en un
parámetro de salida el valor 0 si el ingreso fue correcto o 1 en caso de error. Cuando el
parámetro de salida indique error, la función devuelve en su nombre el valor 0.
Ejemplo:
Si se tiene
int error;
long num;
I.T.B.A. - 2000
Programación I - Clase 21 14
Respuesta:
long
intValido( int *error )
{
char stNum[100];
char *pstNum;
int n, c;
*error = 1;
if (pstNum)
{
pstNum[strlen(pstNum)-1 ]= 0; /* tapamos el ‘\n’ con ‘\o’ */
if (strlen(pstNum) != 0)
{
if (*error)
return 0;
I.T.B.A. - 2000
Programación I - Clase 21 15
void
trim(char* pInit)
{
char *pEnd= pInit + strlen(pInit) -1;
char *pRec;
void
trim(char* pInit)
{
char *pEnd= pInit + strlen(pInit) -1;
I.T.B.A. - 2000
Programación I - Clase 22 1
Introducción
1. Propósito de la Biblioteca
Vamos a escribir una biblioteca INTERVAL de uso matemático que permita
manejar operaciones con intervalos reales cerrados, determinados a través de dos
números reales que representan sus extremos.
I.T.B.A. - 2000
Programación I - Clase 22 2
Por otra parte hemos decidido (puede haber otros diseños alternativos) que, si
algún parámetro de entrada es inválido, además de devolver el código de error 0, los
parámetros de salida quedan con información incierta. Obviamente, en caso de
entradas inválidas, los parámteros de entrada-salida no se modifican.
/* Archivo interval.h
** Autores: G&G
** Encabezamiento de la biblioteca para manejo de intervalos
*/
/* MUY IMPORTANTE
** Si una función recibe un intervalo invalido, retorna en su
** nombre código 0, no altera los parámetros de entrda-salida,
** pero los parámetros de salida quedan seteados con valores
** inciertos:
**
** SI EL CÓDIGO DEVUELTO ES CERO, NO USAR LA INFORMACIÓN DEL
** PARÁMETRO DE SALIDA !!!
*/
I.T.B.A. - 2000
Programación I - Clase 22 3
I.T.B.A. - 2000
Programación I - Clase 22 4
I.T.B.A. - 2000
Programación I - Clase 22 5
/* Archivo interval.c
** Autores: G&G
** Biblioteca para manejo de intervalos
*/
#include <stdlib.h>
#include “interval.h”
#define max(a,b) (a>b)?a:b
#define min(a,b) (a<b)?a:b
int
esInterval(double izq, double der)
{
return (izq <= der);
}
int
norma(double izq, double der, double *rta)
{
*rta = der - izq;
return esInterval(izq, der);
}
I.T.B.A. - 2000
Programación I - Clase 22 6
int
cantParticion(double izq, double der, double longitud, int *rta)
{
double n;
if ( longitud != 0 )
if ( norma(izq, der, &n) )
*rta = n / longitud;
int
pertenece(double izq, double der, double pto)
{
return (izq <= pto && pto <= der);
}
int
intersec(double izq1, double der1, double izq2, double der2,
double *izq3, double *der3)
{
*izq3= max(izq1, izq2);
*der3= min(der1, der2);
int
traslada(double delta, double *izq, double *der)
{
I.T.B.A. - 2000
Programación I - Clase 22 7
#include <stdio.h>
#include “interval.h”
int
main(void)
{
int i;
double izq = -2, der = -1, pto= -1.7;
/* Se busca la intersección */
printf ("Interseccion entre [%g,%g] y [%g,%g]) = ”,
izq + 5, 3.0, izq, der);
/* Trasladamos un intervalo */
printf ("Trasladamos [%g,%g] en %f = ", izq, der, long);
traslada(longitud, &izq, &der);
printf([%f, %f]\n”, izq, der);
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 23 1
Recursividad - Parte I
Introducción
En este documento se explica la técnica de programación llamada recursividad,
su alcance y cuando resulta conveniente usarla.
Técnica Recursiva
Ejemplo:
I.T.B.A. - 2000
Programación I - Clase 23 2
RecolectarDonación de $N
si ( $N <= $100)
tomar el dinero de un donante
sino
encontrar 10 voluntarios
pedirle a cada voluntario RecolectarDonanción de $N/10
recolectar el dinero de los 10 voluntarios
2. Recursividad Directa
Recursividad Directa
tipo
funcionRecursiva (listaParámetros)
{
if ( se cumple caso base)
retornar la solución simple
else
{
dividir el problema en subproblemas de igual formato
resolver cada llamada recursiva de funcionRecursiva (....)
retornar la combinación de las soluciones parciales
}
}
I.T.B.A. - 2000
Programación I - Clase 23 3
Cabe aclarar que cualquier problema que se pueda resolver en forma recursiva
también puede encararse en forma iterativa, aunque en algunos casos pueda resultar
muy complicado.
I.T.B.A. - 2000
Programación I - Clase 23 4
Ejemplo 1
El cálculo de un factorial se puede encarar recursivamente.
¿Cuál es el caso base? Es 0! = 1.
long
fact (long n)
{
if ( n== 0)
return (1);
else
return ( n * fact(n - 1) );
}
Posible invocación :
int dato = 3;
printf ( “%d”, factorial (dato) );
main
dato
3 la función devuelve 6
fact
n
3 return n * fact(n-1) = 6
fact
n
2 return n * fact(n-1) = 2
fact
n
return n * fact(n-1) = 1
1
fact
n
0
return 1
I.T.B.A. - 2000
Programación I - Clase 23 5
Ejemplo 2
La potencia entera (exponente entero) de un número real también puede ser
tratada aplicando recursividad, ya que matemáticamente se tiene que:
N4 = N * N3
N * N2
N * N1
N * N0
int
potencia( int base, int exponente)
{
if (base == 0)
/* para evitar recursión cuando la base es nula */
return ( 0 );
else
if (exponente== 0)
return ( 1 );
else
return ( base * potencia(base, exponen te - 1) );
}
Posible Invocación:
.................
int base, exponente;
I.T.B.A. - 2000
Programación I - Clase 23 6
Ejemplo 3
Un caso típico de recursividad lo conforma la serie de números de Fibonacci.
Recordemos que la sucesión de Fibonacci (Liber Abbaci, 1202) se obtienen con la
siguiente regla:
N si N = 0 ó N = 1
TN =
TN-1 + TN-2 si N >= 2
T0 T1 T2 T3 T4 T5 T6 ...
0 1 1 2 3 5 8 ...
long
fibo( long n)
{
if ( n < 2 )
return ( n );
else
return fibo(n-1) + fibo(n-2);
}
Posible Invocación:
................
printf("\nIngrese un nro:");
scanf("%d", &nro);
printf("\nfibo(%d)= %ld\n", nro, fibo(nro));
..................
Muy Importante
I.T.B.A. - 2000
Programación I - Clase 23 7
main
nro
3 la función devuelve 2
fibo
n
3 return fibo(n-1) + fibo(n-2) = 2
fibo
n
2
return fibo(n-1) + fibo(n-2) = 1
fibo
n
1 return n = 1
fibo
n
1 return n = 1
fibo
n
0 return n = 0
= fibo(1) + fibo(0) + 1 + 1 + 0
= 1 + 0 + 1 + 1 + 0
= 3
I.T.B.A. - 2000
Programación I - Clase 23 8
Ejemplo 4
Indicar qué se logra con cada función recursiva:
#include <stdio.h>
void
cualquiera(void)
{
int letra;
int
main(void)
{
int n1, n2;
return 0;
}
I.T.B.A. - 2000
Programación I - Clase 23 9
1ra. Invocación
letra= S
dir retorno → (*) 1
2da. Invocación
letra= A
dir retorno → (*) 2
3ra. Invocación
letra= L
dir retorno → (*) 3
4ta. Invocación
letra= EOF
Este proceso se repite hasta encontrarse con EOF. En ese punto, la cuarta
invocación devuelve en control a quien la invocó, con lo cual el punto de retorno está en
la instrucción printf("%c", letra); de (*) 3 , impirmiéndose en la salida
estándar el valor de la variable letra del stack frame correspondiente a la tercera
invocación, o sea, 'L'.
I.T.B.A. - 2000
Programación I - Clase 23 10
IMPORTANTE
queEs( 3, 2 ) = queEs( 2, 1 ) * 3 / 2
= queEs( 1, 0 ) * 2 / 1 * 3 / 2
= 1 * 2 / 1 * 3 / 2
queEs( 5, 3 ) = queEs( 4, 2 ) * 5 / 3
= queEs( 3, 1 ) * 4 / 2 * 5 / 3
= queEs( 2, 0 ) * 3 / 1 * 4 / 2 * 5 / 3
= 1 * 3 / 1 * 4 / 2 * 5 / 3
I.T.B.A. - 2000
Programación I - Clase 23 11
NOTAS IMPORTANTES
• En general las funciones recursivas NO validan sus parámetros, ya que esto suele
complicar el código e introduce ineficiencia, porque se termina validando en cada
invocación. De querer hacer algún tipo de validación, hay que escribir una función
validadora, que luego invoque a la función recursiva.
I.T.B.A. - 2000
Programación I - Clase 24 1
Recursividad - Parte II
Introducción
En este documento se presentan varios ejercicios de recursividad con sus
respuestas, entre ellos uno de los más famosos casos de recursión: las Torres de Hanoi.
1. Torres de Hanoi
El juego de Torres de Hanoi es milenario, pero aquí presentamos su resolución
computacional, obviamente recursiva.
Se tienen N discos de distinto tamaño y tres varillas en las cuales se insertan los
mismos. Los discos sólo se pueden cambiar de varilla de a uno por vez y nunca puede
colocarse un disco sobre otro si tiene mayor tamaño que el inferior.
¿Cómo hacer para trasladar N discos desde A hacia C? Sería sencillo si pudiera
pasar N-1 discos a B, usando C como auxiliar, luego el último disco de A a C, y
finalmente los N-1 volverlos a pasarlos de B a C, usando A como auxiliar.
¿Cómo hacer para trasladar N-1 discos desde un origen hacia un destino? Paso
N-2 discos usando la tercera varilla como auxiliar, paso el último disco y luego
reordeno los N-2 disco hacia su destino.
void
Hanoi( int cantidad, char inicial, char auxiliar, char destino)
{
if (cantidad > 0)
{
Hanoi(cantidad-1, inicial, destino, auxiliar);
printf("Mover %c -> %c\n", inicial, destino);
Hanoi(cantidad-1, auxiliar, inicial, destino);
}
}
I.T.B.A. - 2000
Programación I - Clase 24 2
(1)
cantidad inicial auxiliar destino
Hanoi(cantidad-1, inicial, destino, auxiliar); (2)
2 ‘A’ ‘C’ ‘B’
printf("Mover %c -> %c\n", inicial, destino); (6)
Hanoi(cantidad-1, auxiliar, inicial, destino); (7)
(2)
cantidad inicial auxiliar destino
Hanoi(cantidad-1, inicial, destino, auxiliar); (3)
1 ‘A’ ‘B’ ‘C’
printf("Mover %c -> %c\n", inicial, destino); (4)
Hanoi(cantidad-1, auxiliar, inicial, destino); (5)
(3)
cantidad inicial auxiliar destino
0 ‘A’ ‘C’ ‘B’
(5)
cantidad inicial auxiliar destino
0 ‘B’ ‘A’ ‘C’
(7)
cantidad inicial auxiliar destino
Hanoi(cantidad-1, inicial, destino, auxiliar); (8)
1 ‘C’ ‘A’ ‘B’
printf("Mover %c -> %c\n", inicial, destino); (9)
Hanoi(cantidad-1, auxiliar, inicial, destino); (10)
(8)
cantidad inicial auxiliar destino
0 ‘C’ ‘B’ ‘A’
I.T.B.A. - 2000
Programación I - Clase 24 3
(10)
cantidad inicial auxiliar destino
0 ‘A’ ‘C’ ‘B’
(12)
cantidad inicial auxiliar destino
Hanoi(cantidad-1, inicial, destino, auxiliar); (13)
2 ‘B’ ‘A’ ‘C’
printf("Mover %c-> %c\n", inicial, destino); (17)
Hanoi(cantidad-1, auxiliar, inicial, destino); (18)
(13)
cantidad inicial auxiliar destino
Hanoi(cantidad-1, inicial, destino, auxiliar); (14)
1 ‘B’ ‘C’ ‘A’
printf("Mover %c-> %c\n", inicial, destino); (15)
Hanoi(cantidad-1, auxiliar, inicial, destino); (16)
(14)
cantidad inicial auxiliar destino
0 ‘B’ ‘A’ ‘C’
(16)
cantidad inicial auxiliar destino
0 ‘C’ ‘B’ ‘A’
(18)
cantidad inicial auxiliar destino
Hanoi(cantidad-1, inicial, destino, auxiliar); (19)
1 ‘A’ ‘B’ ‘C’
printf("Mover %c-> %c\n", inicial, destino); (20)
Hanoi(cantidad-1, auxiliar, inicial, destino); (21)
(19)
cantidad inicial auxiliar destino
0 ‘A’ ‘C’ ‘B’
I.T.B.A. - 2000
Programación I - Clase 24 4
(21)
cantidad inicial auxiliar destino
0 ‘B’ ‘A’ ‘C’
(6) A→ B (9) C→ B
(11) A→ C (15) B→ A
(17) B→ C (20) A→ C
Qué Genio!!!
I.T.B.A. - 2000
Programación I - Clase 24 5
2. Más Ejercicios
Ejercicio 1
Escribir una función recursiva que reciba como parámetros dos números enteros
y que devuelva el máximo común divisor (usar algoritmo de Euclides)
Algoritmo de Euclides
Ejemplo:
Buscamos el MCD entre 10 y 25
10 25
10 0
25 10
5 2
10 5 MCD(10, 25) = 5
0 2
La idea es que si entramos una vez más, 5 dividido 0 no se puede realizar, pero
en ese caso estamos en presencia del caso base, y el dividendo 5 sería el MCD buscado.
Rta:
int
mcd( int a, int b)
{
if ( b==0 )
return a;
else
return mcd(b, a % b);
}
no omitir el return
I.T.B.A. - 2000
Programación I - Clase 24 6
Ejercicio 2
Indicar qué hace la siguiente función recursiva:
void
whatIs(int num)
{
if (num>=2)
{
whatIs( num / 2);
putchar ( num % 2 + '0' );
}
else
putchar( num + '0');
}
Rta:
Ejercicio 3
Escribir una función recursiva que calcule el cociente entero entre dos enteros
positivos, devolviendo el resto en un tercer parámetro.
Rta:
int
cocienteEntero( int dividendo, int divisor, int* resto)
{
if (dividendo < divisor)
{
*resto= dividendo;
return 0;
}
else
return 1+cocienteEntero(dividendo-divisor, divisor, resto);
}
I.T.B.A. - 2000
Programación I - Clase 24 7
main
cocEnt
dividendo divisor resto return 1 + cocEnt(dividendo-divisor, divisor, resto) = 3
10 3 F010
cocEnt
dividendo divisor resto
return 1 + cocEnt(dividendo-divisor,divisor,resto) = 2
7 3 F010
cocEnt
dividendo divisor resto
return 1+cocEnt(dividendo-divisor,divisor,resto) =1
4 3 F010
cocEnt
dividendo divisor resto *resto= dividendo
1 3 F010
0 return 0
I.T.B.A. - 2000
Programación I - Clase 25 1
Ejercicios de Recursividad
Introducción
En este documento se presentan más ejercicios de recursividad con sus
respectivas respuestas, para practicar algunos conceptos importantes de la técnica
recursiva.
Ejercicio 1
Escribir una función recursiva que reciba un arreglo de enteros con su dimensión
y devuelva 1 si el mismo es nulo y 0 en caso contrario.
Posible invocación:
..........
int array[10] = { 0 , 0, 4 , 0, 7 };
printf("array %s nulo \n",
esNulo(array, sizeof(array)/sizeof(array[0]) )?"es":"no es");
..........
Respuesta
Una posibilidad es correr en cada nueva invocación el puntero recibido como
parámetro a la siguiente componente, bajando la dimensión del arreglo apuntado. De
esta forma la condición base es tener dimensión nula:
int
esNulo(int vector[], int dim)
{
if (dim == 0)
return (1);
else
return ((vector[0]==0) && esNulo(vector+1, dim-1) );
}
I.T.B.A. - 2000
Programación I - Clase 25 2
Ejercicio 2
Escribir una función recursiva que reciba un string (cadena null terminated) y
devuelva la cantidad de vocales que contiene.
Posible invocación:
....................
printf("Ingrese una palabra\n");
scanf ("%s", pal);
printf("vocales= %d \n", cantVocales(pal));
........................
Respuesta
La idea es recorrer el string, corriendo el puntero a char en una componente para
cada nueva invocación recursiva, hasta alcanzar el caso base, que es detectar la
finalización del mismo (encontrando el caracter ‘\0’). Dentro de cada ejecución, si la
cabeza del string es una consonante se vuelve a invocar la función, si es una vocal se
vuelve a invocar pero incrementando en 1 el resultado:
int
cantVocales(char palabra[])
{
if (palabra[0] == '\0')
return ( 0 );
else
switch( toupper(palabra[0]))
{
case 'A':
case 'E':
case 'I':
case 'O':
case 'U':
return ( 1 + cantVocales (palabra +1) );
default:
return ( cantVocales (palabra +1) );
}
}
Notar que no hace falta colocar break, ya que la proposición return es un corte.
I.T.B.A. - 2000
Programación I - Clase 25 3
Ejercicio 3
Escribir una función que detecte si una palabra es palíndroma, recibiendo la
palabra y su cantidad de letras:
“ S A L A S ” “ N A R R A N ”
“ A L A ” “ A R R A ”
“ L ” “ R R ”
“” “”
Respuesta:
int
esPalindromo( char palabra[ ], int longitud)
{
if (longitud <= 1)
return 1;
else
return ( palabra[0] == palabra[longitud-1]
&& esPalindromo(palabra+1, longitud-2);
}
I.T.B.A. - 2000
Programación I - Clase 25 4
Ejercicio 4
Escribir una función recursiva que reciba un entero representando la altura
central de una regla y muestre por pantalla los tamaños de las subdivisiones a cada lado,
sabiendo que cada subdivisión es la mitad de la anterior.
1 2 1 5 1 2 1
Respuesta
La idea es repetir el mismo patrón antes y después de la impresión del valor
deseado, sin imprimirlo hasta no haber llegado al valor 1 de cada lado, recursivamente.
void
regleta( int altura)
{
if (altura > 0 )
{
regleta( altura / 2 );
printf("%d ", altura);
regleta( altura / 2);
}
}
I.T.B.A. - 2000
Programación I - Clase 25 5
Ejercicio 5
Escribir una función recursiva que reciba un entero representando la altura de la
rama inicial de un árbol fractal y la altura mínima tolerada para graficar, y devuelva la
cantidad de ramas que se pueden dibujar del mismo, sabiendo que en cada paso se
generan dos nuevas ramas desde la mitad de las ramas del paso anterior, a 45º de
inclinación y de mitad tamaño.
Ejemplo:
La invocación ramasArbol( 7, 0.5); debería devolver 15
Respuesta
Debemos pensar que en cada rama siempre ocurre la misma secuencia: se
dibujan tres líneas, la inicial y dos de mitad tamaño. Este esquema se repite hasta que el
tamaño de la rama que se quiere representar sea menor que el mínimo aceptado, en cuyo
caso no se dibuja (caso base).
int
ramasArbol(float longitud, float minimo)
{
if (longitud < minimo )
return 0;
else
return 1 + 2 * ramasArbol(longitud / 2, minimo);
}
I.T.B.A. - 2000
Programación I - Clase 25 6
Ejercicio 6
Escribir la función recursiva del ejercicio anterior, pero que en vez de devolver
la cantidad de ramas en su nombre, la devuelva en un tercer parámetro.
Posible invocación:
.........................
int cant;
ramasArbol2( 7 , 0.5 , &cant );
printf( "cantidad de ramas =%d\n", cant );
...........................
Respuesta
Como la respuesta se debe hacer en un parámetro, éste debe ser un puntero a la
zona de memoria donde se desea almacenar el resultado. Dicha dirección apunta a un
lugar en el cual no necesariamente está el contador inicializado. Por lo tanto, la
inicialización del mismo se debe hacer en el caso base:
void
ramasArbol2(float longitud, float minimo, int* cantRamas)
{
if (longitud < minimo )
*cantRamas= 0;
else
{
ramasArbol2( longitud / 2, minimo, cantRamas);
*cantRamas= 1 + 2 * *cantRamas;
}
}
void
ramasArbol2(float longitud, float minimo, int* cantRamas)
{
if (longitud < minimo )
*cantRamas= 0;
else
*cantRamas= 1+2*ramasArbol2( longitud / 2, minimo, cantRamas);
}
I.T.B.A. - 2000
Programación I - Clases 26 y 27 1
Introducción
En este documento se presentan las implementaciones de algunas funciones de la
Biblioteca Estándar de C.
§ Función strcpy
char*
strcpy(char* dest, const char* fuente)
{
int i;
char*
strcpy(char* dest, const char* fuente)
{ ¿Por qué entre paréntesis?
int i;
char*
strcpy(char* dest, const char* fuente) Se puede obviar,
{ pero resulta más claro
char* s;
I.T.B.A. - 2000
Programación I - Clases 26 y 27 2
§ Función strncpy
char*
strncpy(char* dest, const char* fuente, int n)
{
char* s;
return dest;
}
§ Función strcat
char*
strcat(char* dest, const char* fuente)
{
char* s;
return dest;
}
§ Función strncat
char*
strncat(char* dest, const char* fuente, int n)
{
char* s;
return dest;
}
I.T.B.A. - 2000
Programación I - Clases 26 y 27 3
§ Función strcmp
int
strcmp(const char* dest, const char* fuente)
{
for( ; *dest == *fuente ; ++dest, ++fuente )
if (*dest == '\0')
return 0;
§ Función strchr
char*
strchr(const char* s, int c)
{
char ch= c;
return (char*) s;
}
§ Función rand
Hemos elegido esta función para mostrar una posible versión para generar números
pseudo-aleatorios.
Una posible implementación de la función puede ser la siguiente, que garantiza una
buena distribución:
int
rand(void)
{
_Randseed = _Randseed * 1103515245 + 12345;
I.T.B.A. - 2000
Programación I - Clases 26 y 27 4
Este proceso hace que las funciones para manejo de caracteres sean muy utilizadas.
Sin embargo la implementación de dichas funciones puede resultar inservible si no se
amolda al tipo de alfabeto utilizado en la zona.
int
isalpha( int c)
{
return ( c >=’A’ && c <= ‘Z’ || c >=’a’ && c <= ‘z’ )
}
para pasar el caracter ch a mayúscula en alfabeto inglés bastaría con invocar toupper(ch).
Una solución es re-escribir cada función de manejo de caracteres para cada idioma
en particular, y la otra es tener una única implementación que se base en la tabla de los
códigos correspondientes a cada idioma. Obviamente, se eligió la segunda.
Luego se define una tabla (por alfabeto) con tantos lugares como posibles códigos
ASCII existan y se coloca en cada lugar de la tabla el código de la categoría a la que
pertenece el carácter cuyo ASCII coincide con esa posición.
I.T.B.A. - 2000
Programación I - Clases 26 y 27 5
/* _Ctype.h */
#ifndef _CTYPE
#define _CTYPE
int isalpha(int);
int isdigit(int);
int isxdigit(int);
int isalnum(int);
int ispunct(int);
int isspace(int);
int iscntrl(int)
int isprint(int);
int islower(int);
int isupper(int);
int tolower(int);
int toupper(int);
#endif
I.T.B.A. - 2000
Programación I - Clases 26 y 27 6
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
/* macros */
#define XDI ( _DI | _XD )
#define XLO ( _LO | _XD )
#define XUP ( _UP | _XD )
/* static data */
static const short ctyp_tab[257] = {0,
XDI, XDI, XDI, XDI, XDI, XDI, XDI, XDI, Dígitos del ‘0’al ‘9’
};
I.T.B.A. - 2000
Programación I - Clases 26 y 27 7
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
/* static data */
static const short tolow_tab[257] = {EOF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0xl0, 0xll, 0x12, 0xl3, 0xl4, 0x15, 0xl6, 0xl7,
0x18, 0xl9, 0xla, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, Al caer en el lugar
0x40, 'a', 'b', 'c', 'd', 'e', 'f', 'g', del ASCII de la ‘G’
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', se lleva la ‘g’
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 'ö' , 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
0xaO, 0xal, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xse, 0xaf,
0xbO, 0xbl, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
0xcO, 0xcl, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
0xc8, 0xc9, 0xca, 0xcb, 0xac, 0xed, 0xce, 0xcf,
0xdO, 0xdl, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
0xeO, 0xel, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0xea, 0xab, 0x c, 0xed, 0xee, 0xef,
0xfO, 0xfl, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xfb, 0xEc, 0xEd, 0xfe, 0xff
};
Si la tabla fuera para alfabeto alemán, al caer en
el lugar del ASCII de la ‘Ö’ se lleva la ‘ö’
I.T.B.A. - 2000
Programación I - Clases 26 y 27 8
I.T.B.A. - 2000
Programación I - Clases 26 y 27 9
§ Función isdigit
int
isdigit (int c)
{
return (_Ctype[c] & _DI);
}
§ Función isxdigit
int
isxdigit(int c)
{
return (_Ctype[c] & _XD );
}
§ Función isalpha
int
isalpha(int c)
{
return (_Ctype[c] & ( _LO | _UP | _XA ) );
}
§ Función isalnum
int
isalnum(int c)
{
return (_Ctype[c] & ( _DI | _LO | _UP | _XA ) );
}
§ Función isspace
int
isspace(int c)
{
return (_Ctype[c] & ( _CN | _SP | _XS ) );
}
I.T.B.A. - 2000
Programación I - Clases 26 y 27 10
§ Función islower
int
islower (int c)
{
return (_Ctype[c] & _LO);
}
§ Función tolower
int
tolower(int c)
{
return (_Tolower[c]);
}
Ejercicio
Para que un programa escrito en C tenga en cuenta el lenguaje correcto (español,
inglés, etc.) hay que setear una variable de entorno y luego debe tenerse en cuenta dicho
seteo. Por ejemplo, si se busca imprimir la mayúscula de la letra ‘n’ y el alfabeto seteado
es el inglés, se obtiene la misma letra. En cambio si estuviera seteado el alfabeto español se
obtendría la ‘Ñ’. Escribir el siguiente programa:
#include <stdio.h>
#include <ctype.h>
#include <locale.h>
int
main(void)
{
setlocale(LC_CTYPE, “”);
printf(“%c\t%c\n”, ‘ñ’, toupper(‘ñ’));
return 0;
}
I.T.B.A. - 2000
Programación I - Clases 26 y 27 11
e m
(11 bits) (52 bits)
s
. . . . . . . . .
offset 0 . . . . . . . . . . . . offset 3
( 16 bits )
Recordemos que la formación del double en IEEE 754 está dado por:
#define NAN 2
#define INF 1
#define FINITE -1
I.T.B.A. - 2000
Programación I - Clases 26 y 27 12
Para esto, definimos todas las constantes en función del tamaño del double, medido
en short, unidad que se usa para recorrerlo:
Por otra parte, previendo que se pueda estar usando una arquitectura en la cual cada
acceso a memoria invierta o no los bytes de la palabra (recordar el word de Z-80), habrá
que considerar que el short que contiene signo y exponente puede ser el primero o el
último. Para que el código sea portable, conviene usar constantes:
I.T.B.A. - 2000
Programación I - Clases 26 y 27 13
#if _D0 == 3
#define _D1= 2 /* little-endian */
#define _D2= 1
#define _D3= 0
#else
#define _D1= 1 /* big-endian */
#define _D2= 2
#define _D3= 3
#endif
if (xchar == _DMAX)
return ( ps[_DO] & DFRAC || ps[_D1] || ps[_D2] || ps[_D3])?
NAN: INF;
return 0;
}
§ Función fabs
Retorna el valor absoluto del double recibido como parámetro. En caso de un error
de dominio, la variable errno toma el valor EDOM. Si el resultado produce un overflow,
la función devuelve un valor máximo y errno toma el valor ERANGE.
double
fabs(double x)
{
switch( _DTEST( &x)
{
case NAN:
errno= EDOM;
return x;
case INF:
errno= ERANGE;
return MAX_INF;
case 0:
return 0;
default:
return ( x<0.0? -x: x );
}
}
I.T.B.A. - 2000