Descarga la aplicación para disfrutar aún más
Vista previa del material en texto
INFORMÁTICA ALGORITMIA Y PROGRAMACIÓN EN C Pablo Carmona del Barco Dpto. Ingeniería de Sistemas Informáticos y Telemáticos Universidad de Extremadura ÍNDICE Tema 1: Introducción a la programación 1.1. Introducción.......................................................................................1 1.2. Metodología de la programación........................................................2 1.3. Los lenguajes de programación..........................................................6 Tema 2: Tipos de datos y expresiones 2.1 Tipos de datos, constantes y variables..............................................10 2.1 Expresiones...................................................................................... 15 2.2 Funciones internas............................................................................18 2.3 Punteros............................................................................................ 19 2.4 Reglas de prioridad...........................................................................20 Tema 3: Representación gráfica de los algoritmos y su traducción a C 3.1 Métodos de representación algorítmica............................................23 3.2 Operaciones primitivas.....................................................................24 3.3 Estructura de un programa en C.......................................................29 3.4 Estructuras de control.......................................................................30 Tema 4: Estructuras de datos (I): Arrays. Cadenas de caracteres 4.1 Introducción.....................................................................................43 4.2 Arrays............................................................................................... 44 4.3 Cadenas de caracteres.......................................................................49 Tema 5: Modularidad 5.1 Introducción a la modularidad..........................................................55 5.2 Definición de funciones....................................................................57 5.3 Invocación de funciones...................................................................59 5.4 Módulos que no devuelven ningún valor..........................................62 5.5 Módulos que devuelven más de un valor.........................................63 5.6 Arrays como parámetros..................................................................65 Tema 6: Estructuras de datos (II): Registros 6.1 Registros........................................................................................... 70 1 INTRODUCCIÓN A LA PROGRAMACIÓN 1.1 INTRODUCCIÓN Una breve definición de informática es la siguiente: «Ciencia del tratamiento racional, mediante máquinas automáticas, de la información». De ella se derivan dos hechos importantes. Por un lado, el tratamiento de la información es llevado a cabo mediante máquinas automáticas: los ordenadores. Por otro lado, la racionalidad en dicho tratamiento introduce el componente humano como elemento indispensable de cualquier proceso informático, ya que será el ser humano quien determine el modo de tratar la información. La informática es una disciplina académica relativamente joven. Baste con señalar que el término Computer Science no fue acuñado hasta los años 60 por el matemático George Forsythe y que el primer Departamento de Informática en una Universidad se formó en el año 1962. Sin embargo, la vertiginosa evolución de las tecnologías de la información ha llevado a la informática a estar presente en la actualidad en gran parte de los campos científico, económico y social. La principal herramienta que emplea la informática para llevar a cabo las tareas relacionadas anteriormente es el ordenador o computador. Su definición nos aporta nuevas claves: «Máquina automática para el tratamiento de la información que ejecuta programas formados por una sucesión de operaciones aritméticas y lógicas” Es decir, para realizar el tratamiento de la información, el ordenador acepta una entrada (los datos), que mediante un programa transforma para proporcionar una salida (el resultado). Es precisamente en la determinación del programa que el ordenador debe ejecutar donde el ser humano interviene de forma decisiva. La Ilustración 1 esquematiza este proceso para el tratamiento 1 Ilustración 1: Tratamiento automático de la información mediante el ordenador ENTRADA ORDENADOR SALIDA datos programa resultado 1. INTRODUCCIÓN A LA PROGRAMACIÓN automático de la información mediante el ordenador. En general, la secuencia de operaciones necesarias para resolver un problema se denomina algoritmo, que puede considerarse como una fórmula o receta para la resolución de un problema. Cuando se pretende que el problema sea resuelto por un ordenador, dicho algoritmo debe traducirse a una sintaxis adecuada: el programa. Un programa está formado por una serie de instrucciones que se corresponden con los distintos pasos del algoritmo que representa. Además, las instrucciones del programa deben ajustarse a las normas que dicte el lenguaje de programación utilizado. Finalmente, el conjunto de actividades que llevan al desarrollo de un programa informático se denomina programación. En la Ilustración 2 se representan todos estos conceptos. 1.2 METODOLOGÍA DE LA PROGRAMACIÓN Un programador es alguien que resuelve problemas. Para llegar a ser un programador eficaz es necesario primero aprender a resolver problemas de un modo riguroso y sistemático, lo que se denomina metodología de la programación. Aunque un problema puede resolverse normalmente de muchas formas distintas y se requiere cierta dosis de práctica e incluso de habilidad para obtener programas eficientes y de resolución clara, existen pautas que guían al programador de forma genérica para alcanzar una buena solución. En esta sección se estudiarán dichas pautas. La tarea de resolución de problemas se divide en tres fases: Análisis del problema: consiste en definir y comprender el problema en toda su extensión, analizando todo aquello que es relevante para su resolución. Diseño del algoritmo: determinar y representar genéricamente el método de resolución del problema (algoritmo). Resolución del problema mediante el ordenador: traducir el algoritmo a un lenguaje de programación y comprobar que se ha hecho correctamente. 2 Ilustración 2: Relación entre algoritmo y programa ALGORITMO PROGRAMA Lenguaje de programación escribir ('Es par') escribir ('Es impar') leer (n) n mod 2 = 0 if then elseescribir ('Un nº:') paridad BLOCK program paridad(input,output); var n:integer; begin write('Introduzca un número entero: '); readln(n); if n mod 2 = 0 then writeln('El número es PAR') else writeln('El número es IMPAR') end. 1. INTRODUCCIÓN A LA PROGRAMACIÓN 1.2.1 ANÁLISIS DEL PROBLEMA La fase de análisis del problema tiene como objetivo conseguir que el programador alcance a comprender el problema en toda su extensión. Esto requiere que el problema esté bien definido, es decir, que se disponga de una descripción del problema suficientemente detallada. A menudo, es tarea del propio programador desarrollar este “enunciado” y completarlo durante el análisis mediante la celebración de entrevistas con personal experto en el área de aplicación del problema (directivos de la empresa, personal especializado, etc.). La correcta definición del problema debe ir acompañada de la identificación y descripción de los datos de entrada y de salida del problema. Es decir, el programador debe determinar de qué datos relevantes para la resolución del problema se dispone (entrada) y qué información debe proporcionarse como resolución del problema (salida). Un dato de entrada puede definirse como un dato del que depende la salida del problema y que, a su vez, no depende de ningún otro (no puede calcularse a partir de otros datos de entrada). Para cada dato de entrada,deberá especificarse lo siguiente: Identificador del dato, que permita referenciarlo durante la fase de análisis. Descripción del dato: ¿en qué consiste el dato? Procedencia: Un dato de entrada puede solicitarse desde un dispositivo de entrada, generarse aleatoriamente o integrarse directamente en el propio programa. La primera alternativa deberá emplearse cuando se trate de información de entrada que varía habitualmente de una resolución a otra del problema y se indicará como procedencia el dispositivo de entrada receptor (teclado, etc.). La segunda alternativa se corresponde con programas donde alguno de los datos está sujeto al azar (por ejemplo, el valor de la tirada de un dado en un juego de azar). En tal caso, se indicará como procedencia aleatorio. La tercera alternativa es más apropiada cuando se trate de datos de entrada con valores que habitualmente no varían, evitando así que el usuario tenga que introducir dichos valores idénticos en cada resolución del problema. En este caso, se indicará como procedencia dato fijo. Valor (sólo para datos fijos): Cuando el dato se integre directamente en el programa (dato fijo), se indicará el valor asociado. Restricciones (sólo para datos procedentes de un dispositivo de entrada o aleatorios): deberán indicarse las restricciones impuestas a los valores que el dato pueda tomar para que se considere válido. El cumplimiento de estas restricciones deberá posteriormente ser exigido por el programa mediante el correspondiente mecanismo de control de entrada (en el caso de datos procedentes de teclado) o durante la generación de los números aleatorios (en el caso de datos aleatorios). Un dato de salida es aquél que proporciona toda o parte de la solución del problema y deberá poder obtenerse a partir de los datos de entrada especificados en el apartado anterior. Para cada dato de salida se proporcionará: Identificador del dato, que permita referenciarlo durante la fase de análisis. Descripción del dato: ¿en qué consiste el dato? Destino: deberá indicarse el dispositivo de salida hacia el que se dirigirá el dato (monitor, impresora, etc.). 3 1. INTRODUCCIÓN A LA PROGRAMACIÓN Una última sección de la fase de análisis estará reservada a la inclusión de aquellos comentarios que el programador considere oportunos para clarificar aún más el problema a resolver. EJEMPLO 1.1. Se desea realizar un programa que determine el precio de una llamada telefónica entre teléfonos fijos. El precio de la llamada lo constituye el coste por establecimiento de llamada (0.10 euros) y un coste por paso (0.03 euros/paso). La duración de un paso depende del tramo horario en el que se efectúe la llamada, según la siguiente tabla: Tramo 1 Tramo 2 Tramo 3 60 seg. 20 seg. 40 seg. El usuario indicará la duración de la llamada y el tramo horario en el que se efectuó. ANÁLISIS: a) Datos de entrada: EST_LLAM=0.10. Coste del establecimiento de llamada (en euros). Dato fijo. PASO=0.03. Coste de un paso (en euros). Dato fijo. DUR1=60. Duración de un paso en el primer tramo horario (en segundos). Dato fijo. DUR2=20. Duración de un paso en el segundo tramo horario (en segundos). Dato fijo. DUR3=40. Duración de un paso en el tercer tramo horario (en segundos). Dato fijo. duracion: Duración de la llamada (valor entero, en segundos). Teclado. (duracion > 0). tramo: Tramo en el que se efectuó la llamada (valor entero). Teclado. (tramo {1,2,3}). b) Datos de salida: precio: Coste de la llamada (en euros). Monitor. c) Comentarios: Las fracciones de pasos se computarán como pasos completos. Emplearemos una variable intermedia para calcular el número de pasos. 1.2.2 DISEÑO DEL ALGORITMO Como ya sabemos, el algoritmo es la representación de los pasos necesarios para resolver un problema. Estos pasos serán después trasladados a instrucciones que el ordenador podrá ejecutar. Aprender a diseñar algoritmos correctamente se considera esencial en la práctica de la programación, más que el conocimiento específico del lenguaje de programación más novedoso. El algoritmo determinará el método de resolución del problema, y si dicho método se ha desarrollado adecuadamente su traducción a un lenguaje de programación u otro es una cuestión menor. Igualmente, el conocimiento de las particularidades internas de un tipo de ordenador u otro tampoco será crucial para el diseño del algoritmo. Esto se debe a que el algoritmo es independiente tanto del lenguaje de programación que vaya a emplearse como del ordenador donde finalmente se ejecute el programa que lo represente: de ahí su gran importancia. Para la descripción de un algoritmo pueden utilizarse representaciones gráficas (diagramas) o textuales (pseudocódigo, lenguaje natural). Además, el diseño de algoritmos requiere conocer de qué herramientas se dispone para describir los pasos de resolución de un problema y cómo debemos utilizarlas. Esto dependerá de la metodología de programación que vayamos a emplear. En este curso nos centraremos en la 4 1. INTRODUCCIÓN A LA PROGRAMACIÓN metodología de la programación denominada programación estructurada y emplearemos diagramas estructurados para la representación de los algoritmos, los cuales serán estudiados en el siguiente capítulo. La fase de diseño se descompone en dos subfases: a) Parte declarativa: en ella se incluirá la definición de constantes y la declaración de variables que se estudiarán en el Capítulo 2, en este orden. b) Representación algorítmica: descripción del algoritmo empleando alguno de los métodos de representación mencionados. EJEMPLO 1.2. La fase de diseño del problema planteado en el Ejemplo 1.1, empleando pseudocódigo para la representación algorítmica, sería la siguiente: a) Parte declarativa: CONSTANTES EST_LLAM=0.10 PASO=0.03 DUR1=60 DUR2=20 DUR3=40 VARIABLES duracion,tramo,numPasos: entero precio: real b) Representación algorítmica: inicio leer desde teclado duracion mientras duracion ≤ 0 hacer escribir mensaje de error leer desde teclado duracion leer desde teclado tramo mientras tramo < 1 o tramo > 3 hacer escribir mensaje de error leer desde teclado tramo si tramo=1 entonces almacenar en numPasos el valor techo(duracion/DUR1) sino (es tramo=1) si tramo=2 entonces almacenar en numPasos el valor techo(duracion/DUR2) sino (es tramo=2) almacenar en numPasos el valor techo(duracion/DUR3) almacenar en precio el valor EST_LLAM+numPasos×PASO escribir precio fin 1.2.3 RESOLUCIÓN DEL PROBLEMA MEDIANTE EL ORDENADOR Tras el diseño del algoritmo, este debe traducirse a las instrucciones de un lenguaje de programación para que pueda ser resuelto por el ordenador. Esta fase se divide en tres subfases: a) Codificación o implementación del algoritmo: es la conversión de los pasos del algoritmo a las instrucciones equivalentes en un lenguaje de programación. El algoritmo escrito en un lenguaje de programación se denomina programa, código fuente o, simplemente, código. b) Verificación del programa: consiste en la comprobación de que la codificación del algoritmo se ha realizado correctamente, empleando adecuadamente de las reglas gramaticales y sintácticas del lenguaje utilizado. 5 1. INTRODUCCIÓN A LA PROGRAMACIÓN c) Validación del programa: consiste en la comprobación de que los resultados proporcionados por el programa se corresponden con los establecidos en el análisis del problema. Veremos un ejemplo de esta última fase en el siguiente capítulo, una vez que se conozcan las instrucciones en lenguaje C. 1.3 LOS LENGUAJES DE PROGRAMACIÓN Para el desarrollo de un programa es necesario conocer al menos un lenguaje de programación. Esta sección se centra en estos, estableciendo una clasificación atendiendo al grado de abstracción del lenguaje y describiendo ciertas herramientas deprogramación necesarias para que el ordenador pueda realizar la tarea descrita en el lenguaje de programación empleado. 1.3.1 NIVELES DE ABSTRACCIÓN El nivel de abstracción de un lenguaje se refiere a en qué medida la sintaxis y uso de ese lenguaje se encuentra cercano al modo de trabajar de la máquina o, por el contrario, al modo de pensar y hablar del ser humano. La clasificación de los lenguajes atendiendo al nivel de abstracción está relacionada con la evolución de los lenguajes de programación, ya que el grado de abstracción de los lenguajes ha ido aumentando con el paso de los años. A grandes rasgos, podemos distinguir tres grupos: lenguajes máquina, lenguajes de bajo nivel y lenguajes de alto nivel. LENGUAJES MÁQUINA El ordenador representa internamente la información que maneja utilizando código binario. En los primeros tiempos, los programadores tenían que realizar la tediosa tarea de traducir sus algoritmos directamente a las secuencias binarias que representaban las instrucciones y los datos contenidos en el programa. Estas instrucciones, conocidas como instrucciones de código máquina, constituyen el lenguaje máquina de un ordenador. La ventaja de programar en lenguaje máquina es que el código generado puede ser muy eficiente, es decir, emplear poca memoria y ser muy rápido, y, por tanto, utilizar la mínima cantidad de recursos. Sin embargo, la importancia de sus inconvenientes ha acabado con el uso de este tipo de lenguajes en la actualidad. Uno de estos inconvenientes es la dependencia respecto al hardware del ordenador, ya que, el lenguaje máquina de una familia de microprocesadores (por ejemplo, Intel Pentium) no tiene porqué ser compatible con el de otra. Otro inconveniente es la dificultad en la codificación. Por ejemplo, la operación aritmética 4 3+5✶ podría representarse esquemáticamente en un hipotético lenguaje máquina como multiplicación 4 3 dirección 0110 0100 0011 0001 suma 5 dirección 0101 0101 0001 donde puede observarse que tanto los códigos de operación como las direcciones de memoria y los valores se representan mediante código binario. Obsérvese que la equivalencia real en instrucciones máquina sería exclusivamente la cadena binaria 0110010000110001010101010001, difícilmente comprensible de forma directa por el programador. 6 1. INTRODUCCIÓN A LA PROGRAMACIÓN LENGUAJES DE BAJO NIVEL (ENSAMBLADORES) Si bien en los primeros tiempos el programador utilizaba lenguaje máquina para describir sus programas, pronto se extendió la costumbre de emplear, durante el diseño de los algoritmos, mnemotécnicos para representar sus pasos (es decir, abreviaturas en inglés de las instrucciones de código máquina a las que equivalen). Esto permitía a los programadores abstraerse de las particularidades de la representación interna de las instrucciones durante el desarrollo del método de resolución del problema y propició el siguiente paso en la evolución de los lenguajes de programación, los lenguajes ensambladores, donde las instrucciones se expresaban utilizando un lenguaje simbólico (no binario) que aumentó la legibilidad de los programas. Sin embargo, estos lenguajes mantienen su dependencia de la máquina, siendo por tanto cada lenguaje ensamblador específico de una familia de microprocesadores. Además, cada instrucción en lenguaje ensamblador equivale a una única instrucción en código máquina. Por ejemplo, la operación aritmética anterior tendría en un hipotético lenguaje ensamblador un aspecto parecido al siguiente: MUL 4,3,A SUM 5,A Debido a lo todavía complejo y poco intuitivo de su programación, actualmente los ensambladores se utilizan en áreas muy reducidas donde las exigencias en velocidad de ejecución o aprovechamiento de recursos son elevadas. LENGUAJES DE ALTO NIVEL Aunque la introducción de los lenguajes ensambladores supuso un importante paso hacia adelante en la evolución de los lenguajes de programación, aún existían inconvenientes importantes que salvar, tales como la dependencia respecto al hardware y la obligación de describir los métodos de resolución como una secuencia de los pequeños pasos que podían realizarse en lenguaje máquina. Se advirtió la necesidad de proporcionar instrucciones de más alto nivel que permitieran describir acciones de mayor envergadura, las cuales equivaldrían luego a un conjunto de instrucciones en código máquina. De esta idea surgieron los lenguajes de alto nivel, más cercanos al modo de expresarse del ser humano y, por tanto, más fáciles de aprender y de utilizar. Igualmente, debido a su fácil comprensión, los programas escritos en un lenguaje de alto nivel son más fáciles de actualizar y corregir. El lenguaje C, que se trata en este curso, es un lenguaje de alto nivel. El ejemplo anterior se expresaría en C con una única instrucción: A = 4 3+5✶ fácilmente entendible. Obsérvese que esta única instrucción en un lenguaje de alto nivel equivale a varias instrucciones en el código máquina y el ensamblador del ejemplo anterior. Además de su mayor legibilidad, otra de las características esenciales de los lenguajes de alto nivel es que son transportables. Esto significa que, a diferencia de los lenguajes de más bajo nivel, el mismo programa puede ser válido con pocos o ningún cambio en distintas plataformas, esto es, en ordenadores con distintos microprocesadores. Por tanto, los lenguajes de alto nivel son independientes del hardware. Sus inconvenientes se corresponden con las ventajas de los lenguajes máquina y ensambladores, ya que tanto en ocupación de memoria como en tiempo de ejecución los lenguajes de alto nivel suelen presentar un consumo mayor que aquéllos. 7 1. INTRODUCCIÓN A LA PROGRAMACIÓN 1.3.2 TRADUCTORES DE LENGUAJES El ordenador sólo entiende directamente programas escritos en lenguaje máquina. Cuando la codificación se realiza utilizando un lenguaje simbólico (ensambladores y lenguajes de alto nivel), será necesario traducir dicho programa al código correspondiente en lenguaje máquina. Como puede suponerse, el programador no tiene que hacer esto por sí mismo, sino que existen programas que realizan dicha traducción de forma automática. Al programa escrito en lenguaje simbólico se le llama programa o código fuente y al resultado de la traducción programa o código objeto: Distinguimos tres tipos de traductores: ensambladores, compiladores e intérpretes. Los ensambladores traducen programas escritos en ensamblador a código máquina, mientras que los compiladores e intérpretes traducen programas escritos en un lenguaje de alto nivel. Por otra parte, los intérpretes traducen el programa instrucción a instrucción, de forma que hasta que no terminan de ejecutar la última instrucción traducida no proceden a traducir la siguiente, por lo que no generan programa objeto en disco. Por contra, tanto compiladores como ensambladores traducen primero todo el programa fuente, generando un programa objeto en disco, y una vez terminada la traducción, el programa podrá ejecutarse. Las diferencias entre estos tres tipos de traductores están esquematizadas en la Tabla 1. Ahora bien, cuando se desarrolla un programa, este puede incluir referencias a tareas que no son instrucciones del lenguaje de programación, sino que están descritas en las llamadas bibliotecas de funciones que suelen acompañar al traductor del lenguaje para hacerlo más versátil. Dichas tareas son frecuentes y comunes a muchos programas, tales como operaciones gráficas (dibujar un círculo, cambiar el color del texto), matemáticas (logarítmicas, trigonométricas), etc. Por otro lado, un programa puede hacer referencia a otros programas desarrollados por el propio programador que resuelven tareas no incluidas en la biblioteca de funciones (por ejemplo, C no incorpora una función para calcular el factorial de un número). Estos programas se denominan programas fuentessecundarios y requerirán también un proceso de traducción. Por ello, después de traducir el programa principal mediante un ensamblador o un compilador (que solo conoce la equivalencia a lenguaje máquina de las instrucciones propias del lenguaje que traduce), será necesario unir el programa objeto generado con el resto de módulos donde existan tareas a las que se haga referencia desde dicho programa principal (programas fuentes secundarios y bibliotecas de funciones). Para conseguir el programa ejecutable resultado de esta operación de unión se debe utilizar una herramienta denominada montador, enlazador o linkador. 8 Programa fuente Programa objeto Simbólico Máquina TRADUCTOR Lenguaje fuente Traductor Método L. ensamblador Ensambladores L. de alto nivel Compiladores Todo el programa (programa objeto en disco) Intérpretes Instrucción a instrucción Tabla 1: Diferencias entre los tres tipos de traductores 1. INTRODUCCIÓN A LA PROGRAMACIÓN De este modo, cuando estamos empleando un compilador o un ensamblador, el esquema de funcionamiento es el que se muestra en la Ilustración 5. 9 bibliotecas programa fuente principal ENSAMBLADOR O COMPILADOR programa objeto ENLAZADOR programa ejecutable programas fuentes secundarios ENSAMBLADOR O COMPILADOR otros módulos Ilustración 5: Esquema de funcionamiento de compiladores y ensambladores 2 TIPOS DE DATOS Y EXPRESIONES En el capítulo anterior se definió la informática como la ciencia que estudia el tratamiento automático de la información. Ahora bien, un ordenador puede manejar dos categorías de información: instrucciones y datos. Mientras las instrucciones describen el proceso a realizar, los datos representan la información a procesar. En este capítulo estudiaremos las distintas alternativas de que dispone el programador para representar los datos (los tipos de datos), así como el modo de operar con ellos (las expresiones). 2.1 TIPOS DE DATOS, CONSTANTES SIMBÓLICAS Y VARIABLES Toda información, ya sea numérica, alfabética, gráfica o de cualquier otro tipo, se representa internamente en el ordenador mediante una secuencia de bits. En los primeros tiempos de la informática, esto suponía que el programador debía estar familiarizado con dicha representación interna, debiendo realizar por sí mismo la conversión de los datos al correspondiente código binario. Sin embargo, con la introducción del concepto de tipo de datos, los lenguajes actuales permiten que el programador pueda abstraerse de la representación interna de los datos y trabajar con ellos de una forma más natural. En la resolución de problemas mediante ordenadores, la estructura seleccionada para representar los datos con los que trabaja un programa es tan importante como el diseño del algoritmo que determina su comportamiento, ya que de dicha estructura va a depender en gran parte la claridad, eficiencia y requisitos de memoria del programa resultante. Existen dos categorías de datos: simples y estructurados. En este capítulo nos centraremos en los tipos de datos simples y los tipos de datos estructurados se estudiarán en los Capítulos 4 y 6. 2.1.1 TIPOS DE DATOS SIMPLES Un dato simple puede definirse como un elemento de información que se trata dentro de un programa como una unidad (por ejemplo, un número o una letra). El lenguaje C dispone fundamentalmente de tres tipos de datos simples: son los tipos entero, real y carácter. 10 2. TIPOS DE DATOS Y EXPRESIONES TIPO ENTERO El tipo entero se representa en C mediante la palabra clave int y consiste en el subconjunto de valores enteros contenido en el intervalo [-2147483648, 2147483647] (equivalente al intervalo [-231, 231-1] que permite representar 232=4.294.967.296 valores distintos y ocupa 4 bytes1). Un literal entero es una secuencia de uno o más dígitos entre el 0 y el 9, debiendo ser el primer dígito distinto de 0 cuando el literal esté formado por más de un dígito. Al literal puede añadírsele un operador unario positivo o negativo (+ o −), aunque se asumirá el signo positivo cuando no se especifique ningún signo. Sin embargo, no admite el uso de separadores de unidades de millar ni el empleo del separador decimal aunque la parte decimal no sea significativa, debiendo por tanto aparecer únicamente los dígitos que constituyen la cifra. Ejemplos de literales válidos de tipo int son: 5 -15 2003 Ejemplos de literales no válidos como tipo int son: -1050.0 3.456 3,200 034 TIPO REAL El tipo real consiste en un subconjunto de los números reales. Un literal real consta de una parte entera y una parte decimal, separadas por un punto decimal. En C, existen dos tipos básicos para representar valores reales: simple precisión (float) y doble precisión (double). El tipo float ocupa 4 bytes y permite representar valores en el rango [1.2×10-38, 3.4×1038] (positivo y negativo), además del cero. El tipo double ocupa 8 bytes y permite representar valores en el rango [2.2×10-308, 1.8×10308] (también positivo y negativo), además del cero. En aplicaciones científicas, a menudo se emplea una representación especial para manejar números muy grandes o muy pequeños. Por ello, en C existe una representación denominada notación exponencial donde cada literal consta de un número entero o real (mantisa) y de un número entero (exponente), separados por la letra "e" (en minúscula o mayúscula). Por ejemplo, el literal 1.345e2 es equivalente a 1.345×102 o 134.5, y el literal 2.058e-3 es equivalente a 2.058×10-3 o 0.002058. En el primer valor, el punto decimal se desplaza dos lugares hacia la derecha porque el exponente es +2. En el segundo, el punto decimal se desplaza tres lugares hacia la izquierda porque el exponente es -3. Ejemplos de literales reales válidos en C son: 0.45 .23 1234. 1e10 -2e-3 0.2E4 Ejemplos de literales reales no válidos en C son: 1e0.1 12.0a10 1,345.5 1.345,5 2.3-e5 34 TIPO CARÁCTER El tipo carácter se representa en C con la palabra clave char y consiste en el conjunto finito y ordenado de los caracteres representables por el ordenador (en el caso de los PC's, consiste en el conjunto de caracteres incluido en la tabla de códigos ASCII). Un literal de tipo carácter se representa en C mediante un solo carácter encerrado entre comillas simples o apóstrofos. 1 Los intervalos de valores válidos y el número de bytes ocupados por los datos de cada tipo pueden variar según el compilador y plataforma usadas. 11 2. TIPOS DE DATOS Y EXPRESIONES Ejemplos válidos de literales de tipo carácter de C serán: 'a' ',' '>' 'A' '3' Ejemplos de literales de tipo carácter no válidos en C serán: 'ab' 'A q "b" Existen ciertos caracteres especiales que se representan en C mediante las denominadas secuencias de escape. Algunas de las más frecuentes son las siguientes: Carácter Secuencia de escape Tabulador horizontal \t Nueva línea (inicio de la siguiente línea) \n Retorno de carro (inicio de la línea actual) \r Carácter nulo \0 Comillas \" Apóstrofo \' Barra inclinada \\ 2.1.2 CONSTANTES SIMBÓLICAS Y VARIABLES Tanto las constantes simbólicas como las variables permiten almacenar durante la ejecución del programa valores de los tipos de datos que acabamos de ver. Cada variable o constante simbólica lleva asociado un nombre o identificador que la designa. Los nombres elegidos deben ser significativos, es decir, deben hacer referencia al dato que representan. Por ejemplo: edad para almacenar la edad de una persona IVA para almacenar el porcentaje de IVA Es costumbre habitual por parte de los programadores emplear distinta notación para constantes simbólicas y variables, por ejemplo, emplear sólo letras mayúsculas para las constantes simbólicas y una combinación de mayúsculas y minúsculas para las variables. Aunque esta práctica no es obligatoria,facilita la comprensión del código al permitir determinar a simple vista y en cualquier lugar del programa si un identificador hace referencia a una constante simbólica o a una variable. Las reglas sintácticas que deben seguir los identificadores en C son: El primer carácter debe ser una letra o el carácter de subrayado. El resto pueden ser letras, dígitos o el carácter de subrayado. El diagrama sintáctico2 de un identificador será: 2 Un diagrama sintáctico representa las reglas de construcción sintácticas de un componente de un lenguaje de programación. Para determinar si una construcción determinada es sintácticamente válida basta con seguir su diagrama sintáctico en el sentido de las flechas e ir comprobando que la construcción cumple los requisitos indicados en el diagrama hasta salir del mismo. 12 letra subraya dígito letra subraya 2. TIPOS DE DATOS Y EXPRESIONES Además, un identificador en C no puede contener espacios, ni acentos, ni la letra ñ, ni puede coincidir con una palabra clave (por ejemplo, no pueden utilizarse int o float como identificadores). Por último, debe tenerse en cuenta que el lenguaje C es sensible a mayúsculas/minúsculas. Así, los identificadores Suma y suma hacen referencia a distintos datos dentro de un mismo programa. Una constante simbólica denomina un valor que no cambia nunca durante la ejecución del programa. La definición de una constante simbólica consiste en la asociación de un identificador con un valor y en C se realiza de la siguiente forma: Una constante simbólica no ocupa espacio en memoria, ya que el propio compilador sustituye cada aparición de la constante simbólica en el programa por su valor correspondiente durante el proceso de traducción. En algoritmia, la definición de constantes simbólicas utiliza el siguiente diagrama: A continuación se muestran definiciones válidas de constantes simbólicas construidas utilizando los diagramas sintácticos anteriores: En algoritmia: CONSTANTES LETRA='a' NUMERO=-3.141592 PI=-NUMERO En C: #define LETRA 'a' #define NUMERO -3.141592 #define PI -NUMERO Algunas de las ventajas que puede proporcionar la definición de constantes simbólicas frente al uso directo de literales son las siguientes: Facilita la comprensión del programa: la elección de un identificador alusivo al significado del valor que representa puede facilitar la comprensión del programa. Por ejemplo, si en un programa hacemos referencia al porcentaje del IVA, será más comprensible leer el identificador IVA que el literal 21. Facilita la modificación del código del programa: si es necesario modificar un dato considerado como constante en el programa, la definición de una constante simbólica permite hacerlo en un solo paso en lugar de tener que explorar todo el código en busca de literales que hagan referencia a ese dato. Por ejemplo, si el IVA variase, bastaría con modificar el valor asociado a la constante simbólica en la definición para que dicha modificación actuara sobre todas las referencias incluidas en el programa. No obstante, antes de emplear una constante simbólica en lugar de un literal en un programa, debe considerarse si alguna de las ventajas mencionadas va a ser aprovechada, pues en caso contrario sólo se conseguirá aumentar el tamaño del programa y hacerlo menos claro. Por ejemplo, si en un programa quisiéramos contabilizar el número de valores que el usuario introduce por teclado, no sería adecuado definir una constante simbólica para representar al literal 1 que debe tomar el contador como valor inicial, ya que, por un lado, referirse a dicho valor mediante un 13 identificadorCONSTANTES = expresión identificador#define expresión 2. TIPOS DE DATOS Y EXPRESIONES identificador como UNO o PRIMER_VALOR no facilitaría la lectura el programa y, por otro, el primer valor que toma un contador no es susceptible de cambiar en un futuro (siempre comenzaremos a contar desde el valor 1). Una variable denota a un dato cuyo valor puede cambiar durante la ejecución del programa. Cabe aclarar que el concepto de variable en el ámbito de la programación es distinto que en el ámbito de las matemáticas, ya que en programación las variables no representan incógnitas sino datos almacenados en memoria principal. Cada variable, además de un identificador, lleva asociado un tipo que determina su uso, de forma que dicha variable sólo podrá tomar valores de ese tipo. Por ejemplo, una variable de tipo entero sólo podrá almacenar valores enteros. Toda variable debe ser declarada antes de su utilización. Este proceso servirá para reservar espacio en memoria para el valor asociado a dicha variable. En la declaración deberán especificarse el identificador y el tipo de la variable. En algoritmia, se empleará el diagrama sintáctico siguiente utilizando la palabra entero para hacer referencia al tipo entero de C, la palabra real para los diferentes tipos de reales y la palabra carácter para el tipo char. En C, el diagrama sintáctico es el siguiente: Declaraciones válidas de variables serían: En algoritmia: VARIABLES radio,diametro:real grupo:carácter edad:entero En C: float radio,diametro; char grupo; int edad; 2.1.3 EL TIPO CADENA DE CARACTERES3 Un literal de tipo cadena de caracteres es una secuencia de cero o más caracteres incluidos en la tabla de códigos ASCII y encerrados entre comillas dobles. Todo valor de tipo cadena de caracteres finaliza con el carácter especial '\0' (carácter nulo). Este carácter es insertado automáticamente, por lo que el programador no tendrá que preocuparse de incluirlo en los literales de tipo cadena. Así, 3 Aunque el tipo cadena de caracteres es un tipo estructurado y sus características serán estudiadas en profundidad en el Capítulo 4, se introduce aquí para que se pueda trabajar con información de tipo cadena de caracteres desde un principio. 14 tipo identificador de variable ; , tipo identificador de variable : , VARIABLES 2. TIPOS DE DATOS Y EXPRESIONES el literal "Pulsa una tecla para continuar" es una cadena de caracteres con 31 caracteres (las 30 letras más el carácter nulo que indica el fin de cadena). Las variables de tipo cadena de caracteres permiten almacenar cadenas con una longitud máxima que se indica en la declaración entre corchetes junto al identificador. Por ejemplo, para declarar una variable frase de tipo cadena de caracteres con un máximo de 15 caracteres se utiliza la siguiente sintaxis: char frase[15]; que permitirá almacenar frases de hasta 14 caracteres significativos, pues el carácter nulo de fin de cadena ocupará la última posición. 2.2 EXPRESIONES Una expresión matemática tradicional está formada por una combinación de operandos, operadores y paréntesis. Por ejemplo: En los lenguajes de programación también es posible representar expresiones como una combinación de literales, identificadores (de variables o constantes simbólicas), símbolos de operación básicos, paréntesis y nombres de funciones especiales. Todos estos elementos podrán indistintamente estar o no separados por uno o más espacios. Los literales e identificadores actúan como operandos, mientras que los símbolos de operación y las funciones especiales lo hacen como operadores. Cada expresión toma el valor resultante de aplicar los operadores a los operandos. Dependiendo del tipo de este valor resultante, distinguimos dos categorías de expresiones fundamentales: aritméticas y lógicas. 2.2.1 EXPRESIONES ARITMÉTICAS Los operandos serán habitualmente de tipo numérico (real o entero). Los símbolos de operación básicos son: + suma y signo positivo - resta y signo negativo * multiplicación / división % módulo (resto) El operador % se debe aplicar siempre a operandos de tipo entero. Cuando la división se aplique a operandos de tipo entero, el resultado será la división entera (por ejemplo,5/3 dará como resultado 1, que es la parte entera de 1.666). 15 ∑ x−xn 2 n−1 2. TIPOS DE DATOS Y EXPRESIONES El resultado de una expresión aritmética será de tipo numérico. En concreto, Cuando los operandos implicados en la expresión sean del mismo tipo, el resultado será de ese tipo. Por el contrario, cuando en una expresión aritmética se incluyan operandos numéricos de distinto tipo, el resultado obtenido será del tipo correspondiente al operando de mayor precisión. Por ejemplo, si un operando es double y el otro float, el resultado será double, si un operando es int y el otro float, el resultado será float, etc. Para modificar el tipo de un operando o del resultado de una expresión es posible utilizar la conversión explicita de tipos (casting). Para ello, basta con preceder a la expresión que quiere convertirse a un nuevo tipo, de dicho tipo encerrado entre paréntesis. Así, aunque f sea una variable de tipo float, (int) f % 2 será una expresión válida, pues el operando f ha sido convertido previamente al tipo int antes de aplicar la operación de módulo (adviértase que en esa conversión se descartaría la parte decimal del valor de f). Reglas de prioridad: Es posible que en una expresión aritmética concurran varios operadores. En ese caso, el orden de aplicación de los mismos puede influir en el resultado final. Por ejemplo, en la expresión aritmética 6+4/2 el resultado será 5 u 8 dependiendo del orden en que se apliquen los operadores + y /. Por lo tanto, es necesario establecer unas reglas para determinar qué operadores se aplican primero en estos casos. Estas reglas se denominan reglas de prioridad y en C son las siguientes: 1. Operadores unarios signo positivo (+), signo negativo (-) y casting (tipo) 2. Operadores *, /, %. 3. Operadores binarios suma (+) y resta (-). Si coincidieran varios operadores de igual prioridad en una expresión, el orden de evaluación es de izquierda a derecha, excepto para los operadores unarios que será de derecha a izquierda. Por ejemplo, el resultado de 4+8/-2*6 es -20. Los paréntesis permiten alterar la prioridad por defecto de los operadores, ya que las expresiones encerradas entre paréntesis serán las primeras que se evalúen. Si existen paréntesis anidados (interiores unos a otros), las operaciones internas se evalúan primero. Por ejemplo, el resultado de (4+8)/(-2*6) es -1. 2.2.2 EXPRESIONES LÓGICAS Las expresiones lógicas permiten incluir en un programa instrucciones que se ejecutarán solamente bajo ciertas condiciones. Podrán tomar únicamente dos posibles valores: verdadero (1) o falso (0). Se forman combinando identificadores (de variables o constantes simbólicas), literales y subexpresiones con operadores relacionales (de relación o comparación) y operadores lógicos. 16 2. TIPOS DE DATOS Y EXPRESIONES Los operadores relacionales permiten realizar comparaciones. Son los siguientes: < menor que > mayor que == igual a <= menor o igual a >= mayor o igual a != distinto de El formato general de una expresión relacional es: expresión1 operador de relación expresión2 La aplicación a valores numéricos es evidente. Si X=4 e Y=3, entonces: X > Y es 1 (verdadero) (X - 2) < (Y – 4) es 0 (falso) Cuando se comparan valores de tipo carácter, el resultado de la comparación será el de la comparación de los valores ASCII asociados a los caracteres implicados. Por ejemplo, 'A'<='B' dará como resultado 1 (verdadero), ya que el código ASCII de la A (65) es menor que el de la B (66), mientras que 'X'=='Z' dará como resultado 0 (falso), ya que el código ASCII de la X (88) es distinto del de la Z (90). Los operadores relacionales no pueden aplicarse directamente a valores de tipo cadena de caracteres. Las reglas de prioridad entre operadores relacionales son: 1. <, <=, >, >= 2. ==, != Los operadores lógicos son: ! (no-lógico), && (y-lógico) y || (o-lógico). Se aplican casi siempre a operandos que son subexpresiones lógicas, aunque en general pueden aplicarse a cualquier valor entero. En este sentido, el 0 se interpretará como falso y cualquier entero distinto de 0 (no solo el 1) se interpretará como verdadero. El resultado será 1 o 0 (verdadero o falso, respectivamente). Los operadores && y || son operadores binarios, mientras que ! es unario. Los operadores &&, || y ! vienen definidos por sus tablas de verdad: a b a && b distinto de 0 distinto de 0 0 0 distinto de 0 0 distinto de 0 0 1 0 0 0 Devuelve un resultado verdadero si y sólo si los dos operandos son verdadero a b a || b distinto de 0 distinto de 0 0 0 distinto de 0 0 distinto de 0 0 1 1 1 0 Devuelve un resultado verdadero si uno cualquiera de los operandos es verdadero 17 2. TIPOS DE DATOS Y EXPRESIONES a !a distinto de 0 0 0 1 Devuelve un resultado opuesto al del operando Las reglas de prioridad entre operadores lógicos son: 1. no-lógico (!) 2. y-lógico (&&) 3. o-lógico (||) Si aparecen dos o más operadores lógicos iguales en una expresión, los operadores ! se aplicarán de derecha a izquierda y los operadores && y || de izquierda a derecha. Por ejemplo, si X=4 e Y=3, la expresión lógica (X > 3) || (Y > 5) && !(X=4) será verdadero (1). 2.3 FUNCIONES ESPECIALES Los operadores básicos que acaban de estudiarse no son suficientes para realizar ciertas operaciones de cálculo (trigonométricas, logarítmicas, etc.). No obstante, algunas de estas operaciones se encuentran disponibles en los lenguajes de programación a través de bibliotecas de funciones y se denominan funciones internas. Otras, como veremos en el Capítulo 5 pueden ser definidas por el programador y se denominan funciones externas. Cada función, cuando es invocada desde un programa, provoca la ejecución de un conjunto específico de instrucciones y puede devolver uno o más valores, que en cada caso serán de un tipo determinado. La mayoría de las funciones requieren datos de entrada que se denominan parámetros y que aparecen entre paréntesis detrás del nombre de la función. Estos parámetros también deberán ser datos de un tipo concreto, que vendrá determinado por la función en cuestión. Además, para poder utilizar una determinada función interna, es necesario conocer en qué biblioteca se encuentra e incluir al comienzo del programa una instrucción para hacer referencia al archivo de cabecera de la biblioteca correspondiente, que será un archivo con extensión “.h” (por ejemplo, stdio.h para las funciones de entrada/salida estándar o math.h para las funciones matemáticas). El diagrama sintáctico de esta instrucción es el siguiente: Los tipos de datos de los parámetros y del valor devuelto por algunas de las funciones internas más usuales se muestran en la siguiente tabla. El tipo de los parámetros se representa entre los paréntesis de las funciones (i=int, d=double, c=char), si bien los parámetros de tipo double también admiten valores de cualquier otro tipo numérico (que será convertido a double). 18 archivo de cabecera#include < > 2. TIPOS DE DATOS Y EXPRESIONES Función Tipo Devuelto Cabecera Propósito abs(i) int math.h Valor absoluto de i fabs(d) double math.h Valor absoluto de d ceil(d) double math.h Redondeo por exceso (el entero más pequeño mayor o igual a d) floor(d) double math.h Redondeo por defecto (el entero más grande menor o igual a d) cos(d) double math.h Coseno de d sin(d) double math.h Seno de d tan(d) double math.h Tangente de d exp(d) double math.h Exponencial de d (ed) log(d) double math.h Logarítmo neperiano de d pow(d1,d2) double math.h Potencia de d1 elevado a d2 sqrt(d) double math.h Raíz cuadrada de d tolower(c) char ctype.h Convertir c a minúsculas toupper(c) char ctype.h Convertir c a mayúsculas 2.4 PUNTEROS Un puntero es un dato que representa la dirección de memoria donde se almacena otro dato. Por ello, se dice que el valor de un puntero apuntaa otro valor. Los punteros son usados muy frecuentemente en C y están relacionados con otros conceptos como los de arrays y modularidad que se estudiarán en capítulos posteriores. Para trabajar con punteros es necesario conocer los operadores de dirección e indirección. El operador de dirección (&) permite obtener la dirección de memoria en la que se encuentra almacenado un dato (es decir, la dirección de memoria asociada a una variable). El resultado de la operación de dirección será, por tanto, de tipo puntero. Por ejemplo, si v es una variable declarada de tipo real, en el siguiente esquema v representa el contenido de la variable (el valor real 3.154) y &v representa la dirección de memoria donde se almacena dicho valor (la posición 65522). Se dice que &v es un puntero a v. El operador de indirección (*) permite obtener el valor al que apunta un puntero (es decir, el valor contenido en la dirección de memoria que representa el puntero). Por ejemplo, si pv es una variable puntero que apunta a la variable v (es decir, pv es igual a &v), entonces *pv representa el valor al que apunta la variable puntero pv (es decir, *pv es igual a v). En el siguiente esquema se representa la relación entre v y pv mediante los operadores de dirección e indirección: 19 3.154 65522pv=&v *pv=v65522 65520 pv 3.154 65522&v v 2. TIPOS DE DATOS Y EXPRESIONES La declaración de una variable de tipo puntero requiere indicar el tipo del valor al que apuntará el puntero y preceder al identificador del operador de indirección. En el ejemplo anterior, la declaración de pv tanto en algoritmia como en C sería: En algoritmia: VARIABLES *pv:real En C: float *pv; 2.5 REGLAS DE PRIORIDAD A continuación se muestran las reglas de prioridad y el orden de evaluación de todos los operadores vistos en este capítulo: 1. Funciones especiales 2. + (positivo), - (negativo), !, (tipo) (casting), &, * (indirección) 3. * (multiplicación), /, % 4. + (suma), - (resta) 5. <, >, <=, >= 6. ==, != 7. && 8. || Los operadores de la misma prioridad se aplicarán de izquierda a derecha, exceptos los operadores unarios (!, +, -, (tipo), & y *) que se aplicarán de derecha a izquierda. 20 2. TIPOS DE DATOS Y EXPRESIONES EJERCICIOS 1º) Indicar cuáles de los siguientes identificadores no son válidos: a) _Id b) 23 c) duración d) Id-1 e) D/2 f) 3D g) xXx h) D3 i) μm j) años 2º) Indicar cuáles de los siguientes literales son válidos y, de los válidos, de qué tipo son: a) 432 b) 40,000 c) 2.7×10 d) -9.237 e) 5/3 f) 2,933 g) -87E-5 h) 3.5e+1 i) '9' j) "A" k) '\"' l) 24e1.2 3º) Escribir las siguientes expresiones algebraicas como expresiones aritméticas en C, empleando el menor número posible de paréntesis: a) 8 x−3 x y b) 20 ' 2 x224 ' 5 x−40 c) 2 y 2 − y−10 y2 d) a 2 b 2 c × a c2 2b b2 c 2 a e) 3 2 a ba b f) 25×10−4 x 2 y g) q 3 q−2 h) b2−4a c i) ln x1x2y j) 3,2 2 y k) 2 lg l) 2cos 1−tan2 4º) Escribir las siguientes expresiones aritméticas en C como expresiones algebraicas: a) sqrt(x/y+x/z) b) x+3/y+2 c) exp(-pow(1-2*x/a,2)) 5º) Determinar el valor y tipo del resultado de las siguientes expresiones o decir si no son válidas: a) sqrt(25)/2 b) pow(sqrt(9),2) c) 44%(6/3.0) d) 44%6/3.0 e) 16/5%3 f) 16/5*5 g) 16/5*(double) 5 h) 16/(double)5*5 i) !(floor(6.6)==ceil(5.3)) || *&p==p && ceil(5./3)==5/3 j) 4/2*3/6+fabs(6/2-pow(3,2))*2 k) "A" <= "B" l) 6>2 && 5<4 || !(toupper(tolower('A'))=='A') 21 2. TIPOS DE DATOS Y EXPRESIONES 6º) Suponiendo la siguiente información en memoria, responder a las siguientes preguntas: a) ¿Cuál sería el valor de *v2, si v2 vale &v1? b) ¿Cuál sería el valor de *v2, si v2 vale v1? c) ¿Cuál sería el valor de &v2, si v2 vale &v1? d) ¿Cuál sería el valor de &v2, si v2 vale v1? 7º) Suponiendo la siguiente información en memoria, responder a las siguientes preguntas: a) ¿Cuál sería el valor de *v3, si v3 vale &v1? b) ¿Cuál sería el valor de *v3, si v3 vale v1? c) ¿Cuál sería el valor de *v3, si v3 vale &v2? d) ¿Cuál sería el valor de *v3, si v3 vale v2? e) ¿Cuál sería el valor de &v3, si v3 vale &v1? f) ¿Cuál sería el valor de &v3, si v3 vale &v2? 22 3 REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C En el Capítulo 1 estudiamos que un algoritmo nos permite describir paso a paso el proceso necesario para resolver un problema. La representación de un algoritmo se hace bien mediante texto (pseudocódigo), bien mediante fórmulas, o bien de forma gráfica utilizando símbolos geométricos en lo que se denominan diagramas. La representación de algoritmos mediante diagramas es una forma clara e intuitiva de describir los distintos pasos del método de resolución, su orden y su estructura. En este capítulo se tratará la representación de los distintos elementos de un algoritmo mediante diagramas. Paralelamente, se irán introduciendo las reglas sintácticas para traducir a lenguaje C cada uno de esos elementos. 3.1 MÉTODOS DE REPRESENTACIÓN ALGORÍTMICA Las técnicas de programación tienen un papel primordial en el desarrollo del software. Una de estas técnicas es la llamada programación estructurada, desarrollada por Edsger W. Dijkstra1 en 1972 y referida a un conjunto de directrices que aumentan considerablemente la productividad de los programadores, reduciendo el tiempo requerido para escribir, depurar y mantener los programas. La programación estructurada se basa en el Teorema de la Estructura, enunciado por Bohm y Jacopini2 en 1966 y según el cual todo programa puede ser escrito utilizando solamente tres tipos de estructuras básicas (también llamadas estructuras de control): secuenciales, selectivas y repetitivas. Este teorema permite minimizar la complejidad de los métodos de resolución al reducir el juego de estructuras empleado por los programas y facilitar así el seguimiento de la lógica de los mismos. 1 Edsger W. Dijkstra. Notes on structured programming, en Ole-Johan Dahl, Edsger W. Dijkstra y C. A. R. Hoare, editores, Structured Programming. Academic Press, 1972. 2 Bohm, C. y Jacopini, G, Flow Diagrams, Turing Machines and Languages with Only Two Formation Rules, Communications of the ACM, No. 5, Mayo 1966, págs. 366-371. 23 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C Los diagramas estructurados son herramientas que permiten representar adecuadamente algoritmos que respetan las reglas de la programación estructurada. Aunque existen diferentes métodos, en este libro nos centraremos en el denominado método de Tabourier (Ilustración 1), según el cual, todo diagrama estructurado comienza por un rectángulo dividido horizontalmente, en cuya parte superior aparece el nombre del programa y en la inferior la palabra BLOCK. De este rectángulo parten, unidos mediante líneas, los pasos que conforman el algoritmo encerrados en rectángulos y rombos. La estructura arborescente resultante se recorre en preorden, es decir, de izquierda a derecha y de arriba a abajo, de forma que la operación situada a la derecha de la actual se examina sólo después de haber examinado todas las que “cuelgan” de dicha operación actual. 3.2 OPERACIONES PRIMITIVAS Las operaciones primitivas son los elementos básicos de cualquier algoritmo o programa. Éstas, junto con las estructuras de control que se estudiarán a continuación, nos permitirán construir cualquier programa. En C, las instrucciones primitivas acaban todas en punto y coma (;), las instrucciones de control, no. 3.2.1 LA OPERACIÓN DE ASIGNACIÓN La operación de asignación permite almacenar valores en las variables. El operador de asignación se representa en un algoritmo mediante el símbolo ←. El formato general de una operación de asignación en un diagrama de Tabourier es: identificador de variable ← expresión Por ejemplo, la operación edad ← 25 asigna a la variable de identificador edadel valor 25. Hay que tener en cuenta que el valor que la variable pudiera contener antes de la asignación es sustituido por el nuevo. 24 Ilustración 1: Un ejemplo de diagrama de Tabourier 21 3.1.2 3 3.1 3.2 3.1.1 3.3.23.3.1 4 3.3 Nombre del programa BLOCK 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C El operador de asignación tiene menor prioridad que cualquier otro. Esto hace que en primer lugar se calcule el valor de la expresión al lado derecho del operador, y en segundo lugar este valor se almacene en la variable cuyo nombre aparece a la izquierda del operador de asignación. Es posible utilizar el mismo identificador en ambos lados del operador de asignación, dando lugar a operaciones de conteo o de acumulación. Por ejemplo, total ← total + 1 será una operación de conteo que incrementa en 1 el valor de la variable total, mientras que total ← total + incremento será una operación de acumulación que incrementa el valor de la variable total en una cantidad igual al valor almacenado en la variable incremento. Una instrucción de asignación en C utiliza como operador de asignación el carácter igual (=) y finaliza en punto y coma (;). Por ejemplo, edad=25; correspondería a la operación de asignación anterior. Adviértase la diferencia entre este operador y el operador relacional “igual que” (==) estudiado en el capítulo anterior. El primero asigna un valor a una variable, mientras que el segundo compara dos valores para determinar si son iguales. Es habitual entre los programadores noveles confundir ambos operadores. En C, el tipo del valor obtenido al evaluar el lado derecho de una instrucción de asignación se convertirá al tipo de la variable que se encuentra en el lado izquierdo, lo que puede provocar una alteración del valor realmente almacenado (por ejemplo, al asignar el valor real 5.45 a una variable de tipo entero, se almacenará el valor 5). Por ello, para evitar errores, es recomendable en general que el dato que se asigna pueda almacenarse sin pérdidas en la variable especificada (por ejemplo, el valor entero 5 podrá almacenarse sin pérdidas en una variable de tipo entero o de tipo real). 3.2.2 OPERACIONES DE ENTRADA Las operaciones de entrada permiten leer valores desde un dispositivo de entrada (por ejemplo, el teclado) y asignarlos a variables. Esta operación también se denomina operación de lectura, indicando que el programa (el ordenador) va a leer cierta información del exterior (usuario). En un diagrama de Tabourier, una operación de entrada desde teclado se representa: En C, las operaciones de entrada se realizan fundamentalmente a través de la función scanf que utilizaremos para asignar a variables valores de tipo simple (carácter, entero o real) introducidos por teclado. Su archivo de cabecera es stdio.h, que debe incluirse mediante la instrucción #include estudiada en capítulo anterior. La función scanf emplea el siguiente diagrama sintáctico: 25 leer (id1, id2, ..., idN) );,scanf( cadena de control & id. variable tipo simple 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C La cadena de control debe contener un número de especificaciones de conversión igual al número de identificadores de variables que le sucedan. Cuando hay más de una variable, las especificaciones de conversión deben aparecer separadas entre sí por espacios y quedan asociadas por orden a las correspondientes variables (la primera especificación de conversión con la primera variable, la segunda con la segunda, etc.). Cada especificación de conversión es un grupo de caracteres que indica el tipo de la variable correspondiente según la siguiente tabla: Especificación de conversión Tipo %d int %f float %lf double %c char Obsérvese que cada identificador de variable viene precedido por el operador de dirección (&), es decir, tras la cadena de control se indican las direcciones de las variables donde se almacenarán los valores introducidos desde teclado. Cuando se utiliza una misma instrucción para leer más de un valor numérico, éstos deben introducirse desde teclado separados por una pulsación de la tecla Intro o espacios. Para finalizar la entrada se pulsará la tecla Intro. EJEMPLO 3.1. Dadas las variables grupo, habitantes y masa, declaradas como char, int y double, respectivamente, podrán recibir valores desde el teclado utilizando la siguiente instrucción de entrada: scanf("%c %d %lf", &grupo, &habitantes, &masa); Los valores deberán introducirse desde teclado separados entre sí mediante la tecla Intro o espacios y la introducción deberá finalizar con la tecla Intro, por ejemplo: B 146832 1.9891e30 o bien B 146832 1.9891e30 Obsérvese que en la instrucción scanf las especificaciones de conversión van separadas entre sí por espacios y que cada identificador va precedido del carácter ampersand (&). Por otro lado, los valores reales podrá introducirlos el usuario indistintamente en notación clásica o en notación exponencial. Para la introducción desde teclado de cadenas de caracteres, emplearemos una función específica para este propósito: la función gets. Para utilizarla se incluirá entre paréntesis el identificador de la variable de tipo cadena a la que se asignará la cadena de caracteres introducida desde teclado. Para evitar ciertos problemas cuando se emplea en combinación con la función scanf, cada llamada a gets la precederemos de una llamada a la función fflush(stdin);, que borra el buffer de teclado. EJEMPLO 3.2. Dada la variable pelicula declarada como char[60], podrá recibir una cadena desde teclado utilizando la siguiente instrucción de entrada: fflush(stdin); gets(pelicula); 26 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C La cadena deberá introducirse seguida de la tecla Intro, por ejemplo: La Guerra de las Galaxias Obsérvese que en la función gets el identificador ahora no va precedido del carácter ampersand (&). 3.2.3 OPERACIONES DE SALIDA Las operaciones de salida (también llamadas de escritura) permiten visualizar el valor de variables o expresiones a través de un dispositivo de salida (por ejemplo, el monitor). En un diagrama de Tabourier, las operaciones de escritura sobre pantalla se representan: En C, utilizaremos la función printf para mostrar por pantalla valores de cualquier tipo (enteros, reales, caracteres y cadenas), cuyo archivo de cabecera es también stdio.h. Su diagrama sintáctico es el siguiente: En la instrucción printf, la cadena de control deberá contener una especificación de conversión por cada expresión y además podrá contener otros caracteres cualesquiera (incluidas las secuencias de escape vistas en el capítulo anterior) que se mostrarán por pantalla. El valor de las expresiones se insertará en la cadena de control allí donde aparezca su correspondiente especificación de conversión. Las especificaciones de conversión más habituales con la instrucción printf y sus formatos de salida asociados se muestran en la siguiente tabla: Especificación de conversión Tipo %d int %f float, double (notación clásica) %e float, double (notación exponencial) %g float, double (notación clasica/exponencial dependiendo de la precisión) %c char %s char [] 27 escribir (exp1, exp2, ..., expN) ); , printf( cadena de control expresión 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C EJEMPLO 3.3. Dadas las variables grupo, habitantes y masa declaradas como char, int y double, respectivamente, y con los valores del Ejemplo 3.1, la siguiente instrucción de salida: printf("Grupo=%c\nBadajoz=%d hab.\nMasa Sol=%e kg.\n",grupo,habitantes,masa); generará las siguientes tres líneas en pantalla: Grupo=B Badajoz=146832 hab. Masa Sol=1.989100e+30 kg. Obsérvese que el valor de las variables se ha insertado en el lugar de las correspondientes especificaciones de conversión dentro de lacadena de control. En estas instrucciones los datos se visualizan con un determinado formato por defecto que depende del tipo del dato. Por ejemplo, en el caso de los reales, las instrucciones a=15.2; printf("%f",a); producirían el resultado 15.200000 mostrando por tanto el valor de la variable a con 6 dígitos decimales, aunque 5 de ellos no sean significativos. Para que la información pueda mostrarse de forma más clara, es posible añadir ciertos modificadores a las especificaciones de conversión. Uno de estos modificadores permite indicar el número mínimo de caracteres que ocupará en pantalla el valor correspondiente. Cuando el valor a mostrar tenga menos caracteres que el número de caracteres reservado, el valor será precedido de espacios hasta completar dicho número. Cuando el valor tenga más caracteres que los reservados, el valor no aparecerá truncado, sino que se tomarán los caracteres necesarios por la derecha. Este modificador se indica mediante un número situado inmediatamente después del carácter % en la especificación de conversión correspondiente. EJEMPLO 3.4. Dadas las variables grupo, habitantes y masa del ejemplo anterior, la siguiente instrucción de salida: printf("Grupo=%4c\nBadajoz=%5d hab.\nMasa Sol=%13e kg.\n",grupo,habitantes,masa); generará las siguientes tres líneas en pantalla: Grupo= B Badajoz=146832 hab. Masa Sol= 1.989100e+30 kg. Obsérvese que el valor de grupo y de masa aparece precedido de 3 y 1 espacios para completar los 4 y 13 caracteres reservados, respectivamente. Sin embargo, aunque para habitantes se han reservado 5 caracteres y el valor ocupa 6, se visualiza el dato completo. En el caso de los valores reales, es posible indicar un modificador adicional que especifique el número de dígitos decimales que se mostrarán por pantalla. El valor a mostrar se redondeará, si es preciso, al número de decimales indicado por el modificador. Este modificador se incluirá en la especificación de conversión mediante un punto seguido de un número entero, tras el carácter % (es decir, %.entero) o, si se especifica el número mínimo de caracteres reservados, tras dicho número (es decir, %entero.entero). 28 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C EJEMPLO 3.5. Dada la variable masa del ejemplo anterior y la variable numeroPi de tipo float y valor 3.14159265, la siguiente instrucción de salida: printf("Masa Sol=%.2e kg.\nNúmero PI=%8.4f\n",masa,numeroPi); mostrará en pantalla las siguientes líneas: Masa Sol=1.99e+30 kg. Número PI= 3.1416 3.3 ESTRUCTURA DE UN PROGRAMA EN C El esqueleto de un programa en C simple tiene el siguiente aspecto: En este esqueleto encontramos los siguiente elementos: • En primer lugar se sitúan las instrucciones #include correspondientes a los archivos de cabecera de las funciones internas que se vayan a utilizar en el programa, seguido de la definición de las constantes simbólicas (si hubiera alguna), según se estudió en el Capítulo 2. • La siguiente línea int main() es la cabecera de la función principal, por donde todo programa comienza a ejecutarse, y el contenido de dicha función se encuentra a continuación entre llaves. El significado de su sintaxis se explicará en el Capítulo 5. • La declaración de variables deberá incluir todas las variables utilizadas en el programa, según la sintaxis estudiada en el Capítulo 2. • Finalmente, se incluirán las instrucciones ejecutables correspondientes a la traducción de los pasos del algoritmo representados en el diagrama de Tabourier, seguidas por la instrucción return 0 para finalizar la ejecución del programa. EJEMPLO 3.6. El siguiente programa en C permite calcular y mostrar por pantalla la suma de dos números enteros leídos desde teclado. #include <stdio.h> int main() { int s1,s2,result; printf("Introduzca dos números enteros: "); scanf("%d %d",&s1,&s2); result=s1+s2; printf("La suma de %d más %d es %d.\n",s1,s2,result); return 0; } 29 inclusión de archivos de cabecera (#include) definición de constantes (#define) int main() { declaración de variables instrucciones del programa return 0; } 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C 3.4 ESTRUCTURAS DE CONTROL A continuación, se estudiará el significado y la representación de las estructuras básicas de la programación estructurada (secuenciales, selectivas y repetitivas), así como las distintas variantes que existen. 3.4.1 ESTRUCTURA SECUENCIAL Es aquélla en la que todas las acciones que la componen se ejecutan exactamente una vez. La figura siguiente representa una estructura secuencial: EJEMPLO 3.7. Realizar un programa que calcule el perímetro y el área de un rectángulo a partir de la base y la altura dadas por el usuario. ANÁLISIS: a) Datos de entrada: bas: base del rectángulo. Teclado. (bas > 0)3 alt: altura del rectángulo. Teclado. (alt > 0)3 b) Datos de salida: per: perímetro del rectángulo. Monitor. area: área del rectángulo. Monitor. DISEÑO: a) Parte declarativa: VARIABLES bas,alt,per,area:real b) Representación algorítmica: 3 Dado que aún no se han estudiado las estructuras que permitirán realizar el control de entrada correspondiente a estas restricciones, dicho control se omitirá excepcionalmente en la representación algorítmica de este ejemplo. 30 3 ... n21 BLOCK rectangulo BLOCK escribir ("Base y altura:") leer (bas,alt) per←2bas+2alt area←basalt escribir (per,area) 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C CODIFICACIÓN: /************************************************/ /* Muestra el perímetro y área de un rectángulo */ /************************************************/ #include <stdio.h> int main() { float bas,alt,per,area; printf("Introduzca la base y la altura del rectángulo: "); scanf("%f %f",&bas,&alt); per=2*bas+2*alt; /* Perímetro del rectángulo */ area=bas*alt; /* Area del rectángulo */ printf("Su perímetro es %.2f y su área %.2f\n",per,area); return 0; } 3.4.2 ESTRUCTURAS SELECTIVAS La estructura secuencial es típica de los algoritmos que pueden llevarse a cabo con una calculadora básica, ya que todas las instrucciones introducidas se ejecutan exactamente una vez. El empleo de ordenadores para la ejecución de algoritmos cobra mayor sentido cuando en ellos se describe algo más que una mera secuencia de acciones. Este es el caso cuando el siguiente paso a ejecutar depende del valor de una expresión. En las estructuras selectivas se evalúa una expresión y en función de su resultado se determina cuál será el siguiente paso. La acción asociada a cada una de las alternativas consideradas en la estructura selectiva se denomina cuerpo de dicha alternativa. ESTRUCTURA SELECTIVA SIMPLE (IF-THEN) Esta estructura restringe la ejecución de una acción al cumplimiento de una condición. Su representación en un diagrama de Tabourier es la siguiente: En la ejecución de una estructura selectiva simple, en primer lugar se evalúa la condición. Si el resultado de la expresión lógica es verdadero se ejecuta la acción indicada; en caso contrario, no se ejecuta. EJEMPLO 3.8. La siguiente porción de algoritmo expresa que la operación de asignación se ejecutará solo si la variable x es mayor que 0. 31 if then positivo ← 1x > 0 if then acciónexpresión lógica 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C Es posible que la acción a realizar si se cumple la condición sea una acción compuesta, esto es, que conste de varios pasos más simples. Una acción compuesta en un diagrama de Tabourier se representará mediante una estructura secuencial: Por tanto, una estructura selectiva simple cuando contiene una acción compuesta se representará en un diagrama de Tabourier de la siguiente forma: En C, cuando el cuerpo de una estructura selectiva simple consiste en una acción sencilla (es decir, una sola instrucción), se utiliza el siguientediagrama sintáctico: Las acciones compuestas en C emplean el diagrama sintáctico: Por tanto, la estructura selectiva simple, cuando contiene una acción compuesta, utilizará el diagrama sintáctico siguiente: EJEMPLO 3.9. A partir de dos números reales introducidos por teclado, mostrar el resultado de la división si el divisor es distinto de 0. ANÁLISIS: a) Datos de entrada: ddo: Dividendo. Teclado. dsor: Divisor. Teclado. b) Datos de salida: coc (sólo si dsor ≠ 0): Cociente de la división. Monitor. DISEÑO: a) Parte declarativa: VARIABLES coc,ddo,dsor:real 32 expresión lógica if ( ) instrucción if then BLOCKexpresión lógica paso 1 paso 2 paso n... instrucción{ } expresión lógica if ( ) instrucción{ } BLOCK paso 1 paso 2 paso n... 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C CODIFICACIÓN: /***************************/ /* Cálculo de una división */ /***************************/ #include <stdio.h> int main() { float ddo,dsor,coc; printf("Introduzca el dividendo y el divisor: "); scanf("%f %f",&ddo,&dsor); if (dsor != 0) /* solo se calcula si el divisor no es cero */ { coc=ddo/dsor; printf("El resultado de la división es %.2f\n",coc); } return 0; } ESTRUCTURA SELECTIVA DOBLE (IF-THEN-ELSE) La estructura anterior es inadecuada para representar la selección de alternativas cuando se requiere que el algoritmo seleccione entre dos posibles acciones en función del resultado de una condición, de forma que si la condición se cumple se seleccione una alternativa y en caso contrario se seleccione la otra. La estructura selectiva doble nos permite representar más adecuadamente esta situación. Por lo tanto, una estructura selectiva doble deberá emplearse cuando se presenten dos alternativas de actuación mutuamente excluyentes que dependan del resultado de una misma condición. En un diagrama de Tabourier, esto se representará: 33 division BLOCK escribir ("Dividendo y divisor:") leer (ddo,dsor) escribir(coc) if then BLOCKdsor ≠ 0 coc ← ddo/dsor if then else acción 1expresión lógica acción 2 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C Si la expresión lógica toma valor verdadero, se ejecutará la acción 1; si toma valor falso, se ejecutará la acción 2. Al igual que en las estructuras selectivas simples, las acciones de una estructura selectiva doble pueden ser compuestas, en cuyo caso se representarán del modo descrito anteriormente para la estructura selectiva simple. En C, el diagrama sintáctico correspondiente a la estructura selectiva doble es el siguiente: donde bloque de sentencias se refiere de forma genérica a una única acción simple o a una acción compuesta, aplicándose en este último caso el diagrama sintáctico para acciones compuestas estudiado anteriormente. EJEMPLO 3.10. Indicar si, dado un número por teclado, este es par o impar. ANÁLISIS: a) Datos de entrada: n: Número entero. Teclado. b) Datos de salida: paridad: Mensaje indicando la paridad del número n. Monitor DISEÑO: a) Parte declarativa: VARIABLES n: entero CODIFICACIÓN: /***************************************/ /* Muestra si un número es par o impar */ /***************************************/ #include <stdio.h> int main() { int n; printf("Introduzca un número entero: "); scanf("%d",&n); if (n % 2 == 0) /* un número es par si al dividir entre 2 da resto 0 */ printf("El número es PAR\n"); else printf("El número es IMPAR\n"); return 0; } 34 expresión lógica if ( ) bloque de sentencias else bloque de sentencias paridad BLOCK escribir ("Un nº:") leer (n) if then else resto(n,2)=0 escribir ("Es par") escribir ("Es impar") 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C ESTRUCTURA SELECTIVA MÚLTIPLE (SWITCH) A menudo, en la resolución de un problema se presentan más de dos alternativas que dependen de una misma circunstancia, por ejemplo, del día de la semana en el que nos encontramos. Esto podría resolverse usando estructuras selectivas dobles encadenadas, pero generaría un código poco legible y de difícil escritura si el número de alternativas es grande. La estructura selectiva múltiple permite seleccionar una alternativa de entre varias posibles, en función del resultado de una expresión de tipo entero o de tipo carácter. En un diagrama de Tabourier, esta estructura se representa de la siguiente forma: Las listas lista 1, lista 2, ..., lista n contendrán el o los valores asociados a la alternativa correspondiente, separados por comas si son varios. La cláusula default es opcional y se usará cuando una alternativa englobe a los valores de la expresión que no se han indicado explícitamente. En C, el diagrama sintáctico de la estructura selectiva múltiple es: Cada valor de una misma lista se representa mediante un par case-expresión acabado en dos puntos (:). Tras la lista, se sitúa el bloque de sentencias correspondiente a la acción asociada. Obsérvese que se utiliza una instrucción break; para separar cada par lista-acción de la siguiente y, si está presente, del par default-acción. EJEMPLO 3.11. Se desea diseñar un algoritmo que escriba por pantalla la duración en días correspondiente al mes cuyo número de orden se indique por teclado o un mensaje de error si dicho valor no está en el intervalo [1,12]. ANÁLISIS: a) Datos de entrada: mes: Número de orden del mes. Teclado. b) Datos de salida: dias: Mensaje indicando el número de días que tiene mes o un mensaje de error. Monitor DISEÑO: a) Parte declarativa: VARIABLES mes:entero 35 switch acción 1expresión acción 2 acción n acción d... lista 1 lista 2 lista n default expresiónswitch ( ) bloque de sentencias default { case expresión : break; bloque de sentencias } 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C CODIFICACIÓN: /****************************************************************************/ /* Muestra la duración en días correspondiente al número de orden de un mes */ /****************************************************************************/ #include <stdio.h> int main() { int mes; printf("Introduzca el número de orden del mes: "); scanf("%d",&mes); switch (mes) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: printf("31 días\n"); break; case 4: case 6: case 9: case 11: printf("30 días\n"); break; case 2: /* depende de si el año es o no bisiesto */ printf("28 o 29 días\n"); break; default: printf("Error: el valor no corresponde a ningún mes.\n"); } return 0; } 3.4.3 ESTRUCTURAS REPETITIVAS Otro tipo de estructura de control es aquélla que describe la repetición de una acción un número de veces a priori determinado o indeterminado. Estas estructuras se denominan estructuras repetitivas o bucles y se llama iteración a cada una de las ejecuciones de la acción en el bucle. El conjunto de pasos que constituye la acción que se repite se denomina cuerpo del bucle. Aparte del cuerpo del bucle, será necesario también establecer una condición de parada que determine el número de veces que el cuerpo del bucle se ejecutará (el número de iteraciones). Esta condición se localizará al comienzo o al final de bucle y podrá indicarse de distintos modos, dando lugar a tres tipos distintos de estructuras repetitivas: while-do, do-while y for-do. 36 duracionMes BLOCK escribir ("Mes:") leer (mes) switch mes escribir ("31 días") escribir ("30 días") escribir ("28 o 29 días") escribir ("Error") default 1,3,5,7,8,10,12 4,6,9,11 2 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C ESTRUCTURA REPETITIVA WHILE-DO En ella, el cuerpo del bucle se repite mientras se cumpla una determinada condición. Su representación en un diagrama de
Compartir