| Ambos lados, revisión anteriorRevisión previaPróxima revisión | Revisión previa |
| el_lenguaje_de_programacion_c_-_capitulo_4 [2024/10/01 17:20] – [4.3 Variables Externas] peron | el_lenguaje_de_programacion_c_-_capitulo_4 [2026/04/21 16:06] (actual) – editor externo 127.0.0.1 |
|---|
| Las variables externas son también útiles debido a su mayor alcance y tiempo de vida. Las variables automáticas son internas a una función y su existencia se inicia cuando se entra a la función y desaparecen cuando ésta se abandona. Por otro lado, las variables externas son permanentes, de modo que retienen sus valores de la invocación de una función a la siguiente. Así, si dos funciones deben compartir algunos datos, aun si ninguna llama a la otra, con frecuencia es más conveniente que los datos compartidos se mantengan en variables externas, en lugar de que sean pasados como argumentos de entrada y salida. | Las variables externas son también útiles debido a su mayor alcance y tiempo de vida. Las variables automáticas son internas a una función y su existencia se inicia cuando se entra a la función y desaparecen cuando ésta se abandona. Por otro lado, las variables externas son permanentes, de modo que retienen sus valores de la invocación de una función a la siguiente. Así, si dos funciones deben compartir algunos datos, aun si ninguna llama a la otra, con frecuencia es más conveniente que los datos compartidos se mantengan en variables externas, en lugar de que sean pasados como argumentos de entrada y salida. |
| |
| Examinemos más a fondo este tema con un ejemplo más amplio. El problema es escribir el programa de una calculadora que provea los operadores ''+'' '','' ''—'' ''/'' ''*'' ''^'' ''/''. Por ser más fácil su implantación, la calculadora utilizará notación polaca inversa en lugar de infija. (La polaca inversa es utilizada por algunas calculadoras de bolsillo, y en lenguajes como Forth y PostScript.) | Examinemos más a fondo este tema con un ejemplo más amplio. El problema es escribir el programa de una calculadora que provea los operadores ''+'' ''-'' ''*'' y ''/''. Por ser más fácil su implantación, la calculadora utilizará notación polaca inversa en lugar de infija. (La polaca inversa es utilizada por algunas calculadoras de bolsillo, y en lenguajes como Forth y PostScript.) |
| |
| | En notación polaca inversa, cada operador sigue a sus operandos; por lo tanto, lo que en una expresión infija se exoresa como |
| En notación polaca inversa, cada operador sigue a sus operandos; una expresión infija como | |
| |
| <code>(1 - 2) * (4 + 5)</code> | <code>(1 - 2) * (4 + 5)</code> |
| |
| se introduce como | en notación polaca inversa se introduce como |
| |
| <code c>1 2 - 4 5 + *</code> | <code c>1 2 - 4 5 + *</code> |
| |
| Las paréntesis son innecesarias; la notación no es ambigua mientras sepamos cuántos operandos espera cada operador. | Las paréntesis son innecesarias; toda vez que sepamos cuántos operandos espera cada operador, la notación no resulta ambigua. |
| |
| La implantación es simple. Cada operando se introduce en una pila o "stack", cuando un operador llega, el número correcto de operandos (dos para operadores binarios) son extraídos, se aplica el operador y el resultado se regresa a la pila. En el ejemplo anterior, se introducen ''1'' y ''2'', después se reemplazan por su diferencia, ''-1''. En seguida se introducen ''4'' y ''5'' y luego se reemplazan por su suma, ''9''. El producto de ''-1'' y ''9'', que es ''-9'', los reemplaza en la pila. El valor que se encuentra en el tope de la pila se extrae e imprime cuando se encuentra el fin de la línea de entrada. | La implantación es simple. Cada operando se introduce en una pila o "stack", cuando se arriba a un operador, se extrae el número correcto de operandos (dos en el caso de los operadores binarios), se aplica el operador y el resultado se regresa a la pila. En el ejemplo anterior, se introducen ''1'' y ''2'', luego se reemplazan por su diferencia, ''-1''. A continuación se introducen ''4'' y ''5'' y luego se reemplazan por su adición, ''9''. El producto de ''-1'' y ''9'', que es ''-9'', los reemplaza en la pila. El valor que se encuentra en el tope de la pila se extrae e imprime cuando se encuentra el fin de la línea de entrada. |
| |
| La estructura del programa es así un ciclo que realiza las operaciones adecuadas sobre cada operador y operando que aparece; | De esta forma, La estructura del programa que opere de esta manera será un ciclo que realice las operaciones adecuadas sobre cada operador y operando en la medida que aparecen; |
| |
| <code c> | <code c> |
| while (siguiente operador u operando no es fin de archivó) | while (siguiente operador u operando que no es fin de archivó) |
| if (número) | if (número) |
| introducirlo | introducirlo |
| </code> | </code> |
| |
| Las operaciones de introducir {''push'') y extraer de una pila (''pop'') son sencillas, pero cuando se les agrega detección y recuperación de errores, son suficientemenfe largas como para que sea mejor ponerlas en funciones separadas en lugar del codigo a lo largo de todo el programa. Además, debe existir una función separada para buscar el siguiente operador u operando. | Las operaciones de introducir en pila {//push//, o "apilar") y extraer de una pila (//pop// o "desapilar") resultan triviales, pero cuando al intentar agregar la detección y recuperación de errores, resultan suficientemente largas como para que sea conveniente colocarlas en funciones separadas en lugar del código repartido a lo largo de todo el programa. Además, debería existir una función separada para buscar el siguiente operador u operando. |
| |
| La principal decisión de diseño que aún no se ha explicado es dónde está la Mía, esto es, cuáles rutinas tienen acceso a ella directamente. Una posibilidad es Mantenerla en ''main'', y pasar la pila y la posición actual a las rutinas que introducen y extraen elementos. Pero ''main'' no necesita saber acerca de las variables que controlan a la pila. sólo efectúa las operaciones de introducir y extraer. Así, hemos decidido almacenar la pila y su información asociada en variables externas accesibles a las funciones push y pop, pero no a main. | La principal decisión de diseño que aún no se ha explicado es dónde está la pila, esto es, cuáles rutinas tienen acceso a ella directamente. Una posibilidad es mantenerla en ''main'', y pasar la pila y la posición actual a las rutinas que introducen y extraen elementos. Pero ''main'' no necesita saber acerca de las variables que controlan a la pila; sólo efectúa las operaciones de introducir y extraer. Así, hemos decidido almacenar la pila y su información asociada en variables externas accesibles a las funciones ''push'' y ''pop'', pero no a main. |
| |
| Traducir este bosquejo a código es sumamente fácil. Si por ahora pensamos que el programa existe en un archivo fuente, se verá así | Traducir este bosquejo a código es sumamente fácil. Si por ahora pensamos que el programa existe en un archivo fuente, se verá así |
| </code> | </code> |
| |
| Más adelante se verá cómo esto se puede dividir entre dos o más archivos fuente. | Más adelante se verá cómo se puede dividir todo esto entre dos o más archivos de código fuente. |
| |
| La función ''main'' es un ciclo que contiene un gran ''switch'' sobre el tipo de operador y operando; éste es un uso del ''switch'' más típico que el mostrado en la [[sección 3.4]]. | La función ''main'' es un ciclo que contiene un ''switch'' gigante sobre el tipo de operador y operando; éste es un uso del ''switch'' más típico que el mostrado en la [[sección 3.4]]. |
| |
| <code c> | <code c> |
| double pop(void); | double pop(void); |
| |
| /* calculadora polaca inversa */ | /* Calculadora polaca inversa */ |
| main() | main() |
| { | { |
| push(pop() / op2); | push(pop() / op2); |
| else | else |
| printf("error: zero divisor\n"); | printf("error: divisor cero\n"); |
| break; | break; |
| case '\n': | case '\n': |
| break; | break; |
| default: | default: |
| printf("error: unknown command %s\n", s); | printf("error: comando desconocido %s\n", s); |
| break; | break; |
| } | } |
| </code> | </code> |
| |
| Puesto que ''+'' y ''*'' son operadores conmutativos, el orden en el que se combinan los operandos extraídos es irrelevante, pero para ''-'' y ''/'' deben distinguirse los operandos izquierdo y derecho. En | Puesto que ''+'' y ''*'' son operadores conmutativos, el orden en el que se combinan los operandos extraídos es irrelevante ("el orden de los factores no altera el producto"), pero en el caso de ''-'' y ''/'', se hace necesario distinguir su órden, en este caso específico, cuales son los operandos izquierdo y derecho. En |
| |
| <code c> | <code c> |
| </code> | </code> |
| |
| no se define el orden en el que se evalúan las dos llamadas de pop. Para garantizar el orden correcto, es necesario extraer el primer valor en una variable temporal, como se hizo en ''main''. | no se ha definido el orden en el que se evalúan las dos llamadas de ''pop''. Para garantizar que el orden sea correcto, se hace necesario extraer el primer valor en una variable temporal, como hicimos en ''main''. |
| |
| <code c> | <code c> |
| |
| |
| Una variable es externa si se encuentra definida fuera de cualquier función. Así, la pila y el índice de la pila que deben ser compartidos por ''push'' y por ''pop'' se definen fuera de estas funciones. Pero ''main'' en sí misma no hace referencia a la pila o a la posición de la pila — la representación puede estar oculta. | Una variable es externa si se encuentra definida fuera de cualquier función. Por tanto, la pila y el índice de la pila que deben ser compartidos por ''push'' y por ''pop'' va definida por fuera de estas funciones. Pero ''main'' en sí misma no hace referencia a la pila o a la posición de la pila — la representación puede estar oculta. |
| |
| Pasemos ahora a la implantación de ''getop'', la función que toma el siguiente operador u operando. La tarea es fácil. Ignorar blancos y tabuladores. Si el siguiente carácter no es un dígito o punto decimal, regresarlo. De otra manera, reunir una cadena de dígitos (que pueda incluir un punto decimal), y regresar ''NUMBER'', la señal de que ha sido reunido un número. | Pasemos ahora a la implantación de ''getop'', la función que toma el siguiente operador u operando. La tarea es fácil: "Ignorar blancos y tabuladores. Si el siguiente carácter no es un dígito o punto decimal, regresarlo; De otra manera, reunir una cadena de dígitos (que pueda incluir un punto decimal), y regresar ''NUMBER'' (la señal de que ha sido reunido un número)". |
| |
| <code c> | <code c> |
| s[1] = '\0'; | s[1] = '\0'; |
| if (!isdigit(c) && c != '.') | if (!isdigit(c) && c != '.') |
| return c; /* no-número */ | return c; /* no-número */ |
| i = 0; | i = 0; |
| if (isdigit(c)) /* recoge parte de entero */ | if (isdigit(c)) /* recoge parte de entero */ |
| while (isdigit(s[++i] = c = getch())) | while (isdigit(s[++i] = c = getch())) |
| ; | ; |
| if (c == '.') /* recoge parte fraccional */ | if (c == '.') /* recoge parte fraccional */ |
| while (isdigit(s[++i] = c = getch())) | while (isdigit(s[++i] = c = getch())) |
| ; | ; |
| </code> | </code> |
| |
| ¿Qué son ''getch'' y ''ungetch''? Por lo común se da el caso de que un programa no puede determinar si ha leído suficiente de la entrada hasta que ha leído demasiado. Un ejemplo es reunir los caracteres que forman un número: hasta que se vea el primer no-dígito, el número no está completo. Pero para entonces el programa ya ha leído un carácter de más, para el cual no está preparado. | ¿Qué son ''getch'' y ''ungetch''? A menudo se da el caso de que un programa no puede determinar si ha leído lo suficiente de la entrada hasta que ya ha leído demasiado de ella. Reunir los caracteres que forman un número es un ejemplo de ello: el número no está completo hasta que se ve el primer caracter no-dígito... pero para cuando ello sucede, el programa ya ha leído un carácter de más, para lo cual no está preparado. |
| |
| El problema podría ser resuelto si fuera posible “desleer” el carácter no deseado. Entonces, cada vez que el programa lea un carácter de más, podría regresarlo a la entrada, así que el resto del código se podrá comportar como si nunca se hubiese leído. Afortunadamente, es fácil simular el regreso de un carácter, escribiendo un par de funciones cooperativas, ''getch'' entrega el siguiente carácter de la entrada que va a ser considerado; ''ungetch'' reintegra el carácter devuelto a la entrada, de modo que llamadas posteriores a ''getch'' lo regresarán antes de leer algo nuevo de la entrada. | El problema podría ser resuelto si fuera posible “desleer” el carácter indeseado. De esta manera, toda vez que el programa lea un carácter de más, podría devolverlo a la entrada, provisión con la cual el resto del código se comportará como la lectura de más jamás hubiese sucedido. Afortunadamente, es fácil simular el regreso de un carácter, escribiendo un par de funciones cooperativas, ''getch'' entrega el siguiente carácter de la entrada que va a ser considerado; ''ungetch'' reintegra el carácter devuelto a la entrada, de modo que llamadas posteriores a ''getch'' lo regresarán antes de leer algo nuevo de la entrada. |
| |
| Cómo trabajan juntas es sencillo, ''ungetch'' coloca el carácter regresado en un buffer compartido: un arreglo de caracteres. ''getch'' lee del buffer si hay algo allí y si el buffer está vacío llama a ''getchar''. También debe existir una variable índice que registre la posición del carácter actual en el buffer temporal. Puesto que el buffer y el índice son compartidos por ''getch'' y ''ungetch'' y deben retener sus valores entre llamadas, deben ser externos a ambas rutinas. Asi, podemos escribir ''getch'', ''ungetch'' y sus variables compartidas como: | Cómo trabajan juntas es sencillo, ''ungetch'' coloca el carácter devuelto en un buffer compartido: un arreglo de caracteres. ''getch'' lee del buffer si hay algo allí, y si el buffer está vacío llama a ''getchar''. También debe existir una variable índice que registra la posición del carácter actual en el buffer temporal. Puesto que el buffer y el índice son compartidos por ''getch'' y ''ungetch'' - y deben retener sus valores entre llamadas - deben ser externos a ambas rutinas. Asi, podemos escribir ''getch'', ''ungetch'' y sus variables compartidas como: |
| |
| <code c> | <code c> |
| |
| char buf[BUFSIZE]; /* buffer para ungetch */ | char buf[BUFSIZE]; /* buffer para ungetch */ |
| |
| int bufp = 0; /* siguiente posición libre en el buffer */ | int bufp = 0; /* siguiente posición libre en el buffer */ |
| |
| int getch(void) /* obtiene un (posiblemente regresado) caracter */ | int getch(void) /* obtiene un (posiblemente regresado) caracter */ |
| { | { |
| return (bufp > 0) ? buf[--bufp] : getchar(); | return (bufp > 0) ? buf[--bufp] : getchar(); |
| * **Ejercicio 4-9**, Nuestros ''getch'' y ''ungetch'' no manejan correctamente un EOF que se regresa. Decida cuáles deben ser sus propiedades si se regresa un EOF, y después realice su diseño. □ | * **Ejercicio 4-9**, Nuestros ''getch'' y ''ungetch'' no manejan correctamente un EOF que se regresa. Decida cuáles deben ser sus propiedades si se regresa un EOF, y después realice su diseño. □ |
| * **Ejercicio 4-10**. Una organización alternativa emplea ''getline'' para leer una línea completa de entrada; esto hace innecesarios a ''getch'' y a ''ungetch''. Corrija la calculadora para que use este planteamiento. □ | * **Ejercicio 4-10**. Una organización alternativa emplea ''getline'' para leer una línea completa de entrada; esto hace innecesarios a ''getch'' y a ''ungetch''. Corrija la calculadora para que use este planteamiento. □ |
| ==== 4.4 Reglas y Alcance ==== | |
| |
| | ==== 4.4 Reglas y Alcance ==== |
| |
| ==== 4.5 Archivo de encabezamiento header ==== | ==== 4.5 Archivo de encabezamiento header ==== |