Herramientas de usuario

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anteriorRevisión previa
Próxima revisión
Revisión previa
el_lenguaje_de_programacion_c_-_capitulo_4 [2024/09/27 22:59] – [4.11 El preprocesador de C] peronel_lenguaje_de_programacion_c_-_capitulo_4 [2026/04/21 16:06] (actual) – editor externo 127.0.0.1
Línea 253: Línea 253:
 ==== 4.3 Variables Externas ==== ==== 4.3 Variables Externas ====
  
-==== 4.4 Reglas Alcance ====+Un programa en C consta de un conjunto de objetos externos, que son va­riables o funcionesEl adjetivo “externo” se emplea en contraste con “interno”, el cual describe los argumentos las variables definidas dentro de las funciones. Las variables externas se definen fuera de cualquier función, y por lo tanto , están po­tencialmente disponibles para muchas funciones. Las funciones en sí mismas son siempre externas, puesto que C no permite definir funciones dentro de otras fun­ciones. Por omisión, las variables y funciones externas tienen la propiedad de que todas las referencias a ellas por el mismo nombre, incluso desde funciones compi­ladas separadamente, son referencias a la misma cosa. (El estándar llama a esta propiedad //ligado externo//). En este sentido, las variables externas son análogas a los bloques ''COMMON'' de Fortran o a las variables del bloque más externo de Pascal. Más adelante veremos cómo definir variables y funciones externas que sean visibles sólo dentro de un archivo fuente.
  
 +Debido a que las variables externas son accesibles globalmente, proporcionan una alternativa a los argumentos en funciones y a los valores de retorno para co­municar datos entre funciones. Cualquier función puede tener acceso a variables externas haciendo referencia a ellas solamente por su nombre, si éste ha sido declarado de alguna manera.
 +
 +Si un gran número de variables se debe compartir entre funciones, las va­riables externas son más convenientes y eficientes que las largas listas de argumen­tos. Sin embargo, como se señaló en el capítulo 1, este razonamiento se deberá aplicar con precaución, pues puede tener un efecto negativo sobre la estructura del programa y dar lugar a programas con demasiadas conexiones de datos entre funciones.
 +
 +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 valo­res 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 lu­gar 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 ''+'' ''-'' ''*'' y ''/''. Por ser más fácil su implantación, la calculadora utilizará notación polaca in­versa 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 expre­sión infija se exoresa como
 +
 +<code>(1 - 2) * (4 + 5)</code>
 +
 +en notación polaca inversa se introduce como
 +
 +<code c>1 2 - 4 5 + *</code>
 +
 +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 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.
 +
 +De esta forma, La estructura del programa que opere de esta manera será un ciclo que realice las operaciones adecua­das sobre cada operador y operando en la medida que aparecen;
 +
 +<code c>
 +while (siguiente operador u operando que no es fin de archivó)
 +    if (número)
 +        introducirlo
 +    else if (operador)
 +        extraer operandos
 +        hacer operaciones
 +        introducir el resultado
 +    else if (nueva línea)
 +        extrae e imprime el tope de la pila
 +    else
 +        error
 +</code>
 +
 +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 separa­da 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 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í
 +
 +<code c>
 +#includeS
 +#defineS
 +
 +declaración de funciones para main
 +
 +main() { ... }
 +
 +variables externas para push y pop
 +
 +  void push(double f) { ... }
 +  double pop(void) { ... }
 +
 +  int getop(char s[ ]) { ... }
 +rutinas llamadas por getop
 +</code>
 +
 +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 ''switch'' gigante sobre el tipo de ope­rador y operando; éste es un uso del ''switch'' más típico que el mostrado en la [[sec­ción 3.4]].
 +
 +<code c>
 +#include <stdio.h>
 +#include <math.h>    /*para atof() */
 +
 +#define MAXOP 100    /* máx tamaño de operando u operador */
 +#define NUMBER '0'   /* señal de que un número se encontró */
 +
 +int getop(char []);
 +void push(double);
 +double pop(void);
 +
 +/* Calculadora polaca inversa */
 +main()
 +{
 +    int type;
 +    double op2;
 +    char s[MAXOP];
 +    
 +    while ((type = getop(s)) != EOF) {
 +        switch (type) {
 +        case NUMBER:
 +            push(atof(s));
 +            break;
 +        case '+':
 +            push(pop() + pop());
 +            break;
 +        case '*':
 +            push(pop() * pop());
 +            break;
 +        case '-':
 +            op2 = pop();
 +            push(pop() - op2);
 +            break;
 +        case '/':
 +            op2 = pop();
 +            if (op2 != 0.0)
 +            push(pop() / op2);
 +        else
 +            printf("error: divisor cero\n");
 +            break;
 +        case '\n':
 +            printf("\t%.8g\n", pop());
 +            break;
 +        default:
 +            printf("error: comando desconocido %s\n", s);
 +            break;
 +        }
 +    }
 +    return 0;
 +}
 +</code>
 +
 +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 ope­randos izquierdo y derecho. En
 +
 +<code c>
 +push(pop() - pop());   /* INCORRECTO */
 +</code>
 +
 +no se ha definido el orden en el que se evalúan las dos llamadas de ''pop''. Para garanti­zar que el orden sea correcto, se hace necesario extraer el primer valor en una variable tempo­ral, como hicimos en ''main''.
 +
 +<code c>
 +#define MAXVAL 100    /* máxima profundidad de la pila val •/
 +int sp = 0 ;          /* siguiente posición libre en la pila */
 +double val[MAXVAL];   /* valores de la pila */
 +
 +/* push: introduce f a la pila */
 +void push(double f)
 +{
 +    if (sp < MAXVAL)
 +        val[sp++] = f;
 +    else
 +        printf("error: pula llena, no puede hacer push de %g\n", f);
 +}
 +
 +/* pop: extrae y regresa el valor superior de la pila */
 +double pop(void)
 +{
 +    if (sp > 0)
 +        return val[--sp];
 +    else {
 +        printf("error: pila vacia\n");
 +        return 0.0;
 +    }
 +}
 +</code>
 +
 +
 +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 si­guiente carácter no es un dígito o punto decimal, regresarlo; De otra manera, reu­nir 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>
 +#include <ctype.h>
 +
 +int getch(void);
 +void ungetch(int);
 +
 +/* getop: obtiene siguiente caracter u operando numérico */
 +int getop(char s[])
 +{
 +    int i, c;
 +
 +    while ((s[0] = c = getch()) == ' ' || c == '\t')
 +        ;
 +    s[1] = '\0';
 +    if (!isdigit(c) && c != '.')
 +        return c;     /* no-número */
 +    i = 0;
 +    if (isdigit(c))   /* recoge parte de entero */
 +        while (isdigit(s[++i] = c = getch()))
 +           ;
 +    if (c == '.'    /* recoge parte fraccional */
 +        while (isdigit(s[++i] = c = getch()))
 +           ;
 +    s[i] = '\0';
 +    if (c != EOF)
 +        ungetch(c);
 +    return NUMBER;
 +}
 +</code>
 +
 +¿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 de­masiado 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 progra­ma 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 indesea­do. 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, escri­biendo 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 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 va­riable í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, pode­mos escribir ''getch'', ''ungetch'' y sus variables compartidas como:
 +
 +<code c>
 +#define BUFSIZE 100
 +
 +char buf[BUFSIZE];  /* buffer para ungetch */
 +int bufp = 0;       /* siguiente posición libre en el buffer */
 +
 +int getch(void)     /* obtiene un (posiblemente regresado) caracter */
 +{
 +    return (bufp > 0) ? buf[--bufp] : getchar();
 +}
 +
 +void ungetch(int c)    /* devuelve caracter a la entrada */
 +{
 +    if (bufp >= BUFSIZE)
 +        printf("ungetch: demasiados caracteres\n");
 +    else
 +        buf[bufp++] = c;
 +}
 +</code>
 +
 +La biblioteca estándar incluye una función ''ungetc'' que proporciona el regreso de un carácter; esto se verá en el [[capitulo 7]]. Se ha utilizado un arreglo para lo que se regresa a la entrada, en lugar de un carácter sencillo, para dar una idea más general.
 +
 +  * **Ejercicio 4-3**. Dada la estructura básica, es fácil extender la calculadora. Agre­gue el operador módulo (''%'') y consideraciones para números negativos. □
 +  * **Ejercicio 4-4**. Agregue órdenes para imprimir el elemento al tope de la pila sin sa­carlo de ella, para duplicarlo y para intercambiar los dos elementos del tope. Agregue una orden para limpiar la pila. □
 +  * **Ejercicio 4-5**. Agregue acceso a funciones de biblioteca como ''sin'', ''exp'' y ''pow''.
 +Consulte ''<math.h>'' en el [[apéndice B, sección 4]]. □
 +  * **Ejercicio 4-6**. Agregue órdenes para manipular variables. (Es fácil proporcio­nar veintiséis variables con nombres de una letra.) Añada una variable para el va­lor impreso más reciente. □
 +  * **Ejercicio 4-7**. Escriba un a rutina ''ungets(s)'' que regresa a la entrada una cadena completa. ¿Debe ''ungets'' conocer acerca de ''buf'' y ''bufp'', o sólo debe usar ''ungetch''?
 +  * **Ejercicio 4-8**. Suponga que nunca existirá más de un carácter de regreso. Modifique ''getch'' y ''ungetch'' de acuerdo con eso. □
 +  * **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 des­pué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. □
 +
 +==== 4.4 Reglas y Alcance ====
  
 ==== 4.5 Archivo de encabezamiento header ==== ==== 4.5 Archivo de encabezamiento header ====

Este sitio web utiliza cookies para guardar datos esenciales de su actividad, como su autenticación. Al entrar acepta el uso de cookies.

Más información