Fundamentos de Informática

Lenguaje de Programación C

FAQ (Frequently Asked Questions)

Preguntas Más Frecuentes

(y también dudas y fallos típicos, curiosidades, detalles importantes...)


PREGUNTAS


  1. Leer una cadena de carácter en carácter: Usando la función getchar(), sólo puedo leer un carácter y deseo leer una frase o cadena de caracteres.
  2. Leer y escribir números reales de gran precisión: Modificadores % para leer y escribir un dato de tipo double y long double con printf() y scanf().
  3. Leer y escribir grandes enteros: Modificadores % para leer y escribir un dato de tipo long int y long unsigned int con printf() y scanf().
  4. Valores que devuelven las funciones printf() y scanf().
  5. Uso de la llamada fflush(stdin) para borrar el buffer de teclado.
  6. Significado y utilidad de stdio.h.
  7. Los paréntesis y el tipo de main(): ¿Son necesarios?
  8. Operador de incremento: Si una variable x vale 5, por ejemplo, cuánto valdrá después de la siguiente instrucción: x = x++ + 5;
  9. En la definición de una función, los arrays NO deben definirse con su tamaño, puesto que siempre pasan por referencia: Pueden especificarse o bien con los corchetes vacíos (por ejemplo int a[]) o bien como un puntero (por ejemplo int *a). ¿Pasa lo mismo con las estructuras?
  10. Legibilidad de un programa en C: ¿Por qué es tan importante?
  11. Operador sizeof: ¿Cómo funciona?
  12. Valores matemáticos de PI y e.

RESPUESTAS


  1. Leer una cadena de carácter en carácter: Usando la función getchar(), sólo puedo leer un carácter y deseo leer una frase o cadena de caracteres.

    La función getchar() lee SÓLO un carácter. Cuando el usuario teclea una secuencia de caracteres, todos y cada uno de ellos se posicionan, según se van tecleando, en una cola interna del ordenador que llamamos BUFFER de teclado. Entonces cuando se realiza un getchar() se coge el primer carácter de esa cola y se elimina de la misma. Luego en el siguiente getchar() se coge el siguiente carácter y así hasta que no hubiese caracteres en la cola o hasta que decidimos parar el proceso (por ejemplo, porque al realizar una lectura de un carácter vemos que ese carácter es el punto). Muchas veces se necesita un bucle que vaya leyendo caracteres (o sea ejecutando getchar()), hasta que se lea cierto carácter (un punto...). Observa que el retorno de carro -tecla Intro- es también un carácter así que el proceso solo parara cuando el programa lo decida dependiendo del carácter tecleado.


  2. Leer y escribir números reales de gran precisión: Modificadores % para leer y escribir un dato de tipo double y long double con printf() y scanf().

    El modificador para leer un double con scanf() es %lf que viene de long float. Para leer un long double debemos usar %Lf. Para printf() puede usarse el mismo que para imprimir un dato de tipo float: %f, %e, %E, %g o %G. El número de decimales que se imprimen no sólo depende del tipo de dato (de su precisión), sino también de la forma elegida para imprimirlo. Por defecto, sólo se escriben 6 dígitos decimales. Por ejemplo, para escribir 9 decimales debemos usar %.9f. Para escribir un dato de tipo long double se pondrá la letra L mayúscula delante de esos modificadores (%Lf, %Le, %LE, %Lg o %LG).


  3. Leer y escribir grandes enteros: Modificadores % para leer y escribir un dato de tipo long int y long unsigned int con printf() y scanf().

    Como para un dato de tipo int se usa %i (o bien %d, %x, %X y %o), para un long int se usa %li (o bien %ld, %lx, %lX y %lo). Como para un dato de tipo unsigned int se usa %u (exclusivamente), para un unsigned long int se usa %lu (en scanf() también sirve %U). Para enteros pequeños (short) se usa el modificador h (en vez de l).


  4. Valores que devuelven las funciones printf() y scanf().

    La función printf() devuelve el número de caracteres escrito. Si se produce un error devuelve la constante simbólica EOF (definida en stdio.h normalmente como -1). Es raro que printf() produzca un error ya que lo normal es que esta función sea utilizada para escribir en la pantalla. Sin embargo, printf()escribe en la Salida Estándard, la cual puede ser redirigida, desde el Sistema Operativo, a otro dispositivo (impresora, disco...) y estos dispositivos pueden producir errores de escritura (impresora no conectada o sin papel, disco lleno, defectuoso o inexistente...).

    La función scanf() lee un conjunto de caracteres y lo convierte a binario para ser almacenado en cierta variable cuya dirección es facilitada (utilizando el operador de dirección &). Esta función devuelve el número de valores correctamente convertidos. Por tanto, devuelve 0 si no ha conseguido leer correctamente ningún valor. La función devuelve EOF si se lee la marca de fin de fichero (End Of File) antes de leer corectamente algún valor. Igualmente, scanf() lee por defecto del buffer de teclado porque este es el valor por defecto de la Entrada Estándard, la cual puede ser redirigida, desde el Sistema Operativo, para que el programa lea sus datos desde un fichero.


  5. Uso de la llamada fflush(stdin) para borrar el buffer de teclado.

    El buffer de teclado es una memoria temporal donde se introducen los caracteres tecleados antes de pulsar la tecla RETURN (Intro o Enter), la cual es otro carácter (el carácter '\n'). Usualmente, cuando se usan funciones de lectura como scanf() o getchar(), por ejemplo, la ejecución del programa se detiene hasta que se introducen estos caracteres en el buffer de teclado, o sea, hasta que se pulsa la tecla RETURN. Sin embargo, si en el buffer ya existen valores entonces la función de lectura lee esos valores sin detenerse.

    Cuando estamos leyendo números la función intenta leer números, saltándose algunos caracteres de control (si se los encuentra), como el carácter '\n'. Sin embargo, cuando estamos leyendo un carácter (o varios) la función de lectura leerá el siguiente carácter que haya en el buffer de teclado, sea cual sea. Si no hay ninguno, entonces, la función se detiene a la espera de poder leer. Pero si en el buffer existe algún carácter éste será leído, aunque sea un carácter de control. Para evitar la lectura de caracteres de control se usa la instrucción fflush(stdin); que limpia el buffer de teclado.

    Ejemplo: Imagine que un programa lee un número. El usuario teclea 13 y RETURN (que son 3 caracteres: '1', '3' y '\n'). El programa lee el número 13, pero NO lee el carácter '\n' ya que éste no forma parte del número. Si a continuación el programa intenta leer un carácter, leerá, sin pararse, el carácter '\n' (ya que ese es el siguiente carácter que hay en el buffer). Si el programador desea que NO lea ese carácter debe limpiar el buffer de teclado con fflush(stdin); antes de leer el carácter.

    En general, es recomendable usar la instrucción fflush(stdin); antes de cualquier operación de lectura de un carácter, para evitar posibles errores.


  6. Significado y utilidad de stdio.h.

    El nombre de stdio.h viene del inglés standard input/output (entrada/salida estándar). Así, stdio.h es la biblioteca de funciones que tiene C para efectuar las operaciones de lectura y escritura. Estas funciones son muy utilizadas: printf(), scanf(), putchar(), getchar(), puts(), gets()... Todas ellas escriben en la entrada/salida estándar que suele ser el teclado y la pantalla, por defecto. Sin embargo estos periféricos pueden cambiarse haciendo que el programa lea o escriba en otros dispositivos (ficheros, impresora...). Esto se hace utilizando los redireccionamientos y las tuberías del Sistema Operativo (caracteres >, < y |) y podrás encontrar más información en un buen manual del Sistema Operativo. La extensión .h viene del inglés headers, ya que ese fichero almacena las cabeceras o prototipos de esas funciones. Al incluir (con #include) el fichero stdio.h lo que se está incluyendo es el fichero con las cabeceras o prototipos de todas sus funciones. Esto habilita al programa para utilizar cualquier función de ellas.


  7. Los paréntesis y el tipo de main(): ¿Son necesarios?

    Los paréntesis son necesarios porque main() es una función (es la función principal) y en lenguaje C todas las funciones deben llevar paréntesis. Dentro de los paréntesis se ponen los argumentos, y si la función no tiene argumentos, los paréntesis se dejan vacíos, o bien, se pone el tipo void en su interior: main(void). Por otra parte, la función main() puede tener dos argumentos (llamados argc y argv) que sirven para leer desde el programa los argumentos que se hallan usado al ejecutar el programa desde el Sistema Operativo. Cualquier libro sobre C debe explicar detenidamente esos argumentos.

    Por otra parte, el tipo de main() es, por defecto, int. Es decir, si no se especifica nada, el compilador supone que la función main() va a devolver un valor entero, usando la sentencia return. Ese valor puede ser usado posteriormente por el sistema operativo desde el que se ejecutó el programa, aunque no suele ser muy útil. Sin embargo, si la función main() no devuelve ningún valor al sistema operativo (no usa return), entonces puede ocasionar un error o aviso (warning) en el compilador. Para evitarlo podemos definir la función principal de tipo void indicando explícitamente que dicha función no va a devolver ningún valor: void main().


  8. Operador de incremento: Si una variable x vale 4, por ejemplo, cuánto valdrá después de la siguiente instrucción: x = x++ + 5;

    Ese tipo de instrucciones lo mejor es no usarlas nunca, para evitar ambigüedades. No obstante, el proceso a seguir es simple x++ vale 4 (el incremento se efectuará al final), por lo que se efectúa la suma 4+5 dando un resultado de 9 que se asigna a la variable x de la izquierda de la asignación. Finalmente se ejecuta el incremento de la variable x, que asignará a esta variable el valor final de 10.


  9. En la definición de una función, los arrays NO deben definirse con su tamaño, puesto que siempre pasan por referencia: Pueden especificarse o bien con los corchetes vacíos (por ejemplo int a[]) o bien como un puntero (por ejemplo int *a). ¿Pasa lo mismo con las estructuras?

    La razón es muy simple: Cuando se pasa un array a una función no se copian todos los elementos del array sino que a la función sólo se le pasa la dirección de memoria del primer elemento del array (que es el identificador del array, es decir un puntero). Por eso, no tiene sentido especificar en una función el tamaño del array del argumento. El tamaño de ese array será, siempre, el tamaño del array que se utilice en la llamada (el argumento actual). O sea, una misma función puede utilizarse con distintos arrays de distintos tamaños. En cada llamada, el array del argumento formal de la función tendrá distinto tamaño. Por eso, ambas declaraciones son posibles, aunque si realmente el argumento se trata de un array es aconsejable usar la de los corchetes vacíos, ya que esta forma indica explícitamente que se trata de un array.

    Para poder recorrer un array completamente suele ser útil pasar también a la función el tamaño de ese array, de forma que la función sepa cuántos elementos existen en el array. Esto tiene dos ventajas: Por una parte permite que una función en C sea genérica y pueda utilizarse con arrays de cualquier tamaño. Por otra parte, aumenta la velocidad en la ejecución de los programas ya que al pasar un array como argumento en una función no tiene que copiar todos los elementos del array sino que sólo copia el puntero al primer elemento. El inconveniente es que, de hecho, los arrays siempre pasan por referencia, por lo que si un array se modifica dentro de una función también se modificará el array usado en la llamada (ya que son, de hecho, el mismo array). Si eso no es lo que se desea, siempre podemos sacar una copia del array para usarlo en la función, manteniendo el original intacto.

    Recuerde que eso no pasa con las estructuras. Es decir, cuando se pasa una estructura a una función se saca una copia íntegra de la estructura y la función trabajará sobre esa copia. Si la estructura es muy grande esa copia es muy ineficiente. Por este motivo, en ocasiones, los programadores prefieren pasar el puntero de una estructura (usando un paso de argumentos por referencia) haciendo que el programa sea más eficiente aunque ese tipo de paso de argumentos no sea necesario. Para ello se usa el operador de dirección & en la llamada a la función y dentro de ella se usará el operador flecha -> para acceder a cada campo de la estructura a la que apunta el argumento formal.


  10. Legibilidad de un programa en C: ¿Por qué es tan importante?

    La legibilidad de un programa se refiere a en qué medida ese programa es fácil de leer, entender, corregir o modificar. Es muy importante escribir programas fácilmente legibles ya que esto ahorra tiempo y reduce errores. Hay una serie de Normas de Estilo para Programación en LENGUAJE C, que todos los programadores deberían seguir. Léelas e intenta cumplirlas.


  11. Operador sizeof: ¿Cómo funciona?

    Este operador determina el tamaño en bytes de su operando. Si su operando es un tipo de dato debe ponerse entre paréntesis. Por ejemplo sizeof (int) es el tamaño de cualquier variable de tipo int. Si el operando no es un tipo, sino una expresión, los paréntesis pueden quitarse, aunque también pueden ponerse (ya que los paréntesis pueden formar parte de cualquier expresión). Por ejemplo sizeof x devuelve el tamaño en bytes de la variable x. Es curioso destacar que al calcular el tamaño de una expresión esa expresión no es evaluada. O sea, sizeof (a=b+1) no asigna ningún valor a la variable a. Si el operando es un array se devuelve el tamaño, en bytes, de todo el array. Así, es fácil descubrir el tamaño del array dividiendo ese valor por el tamaño de cada uno de sus elementos. Es una pena, pero esa técnica no funciona si se usa en el argumento de una función, ya que dicho argumento es sólo un puntero y sizeof devuelve el tamaño del puntero.


  12. Valores matemáticos de PI y e.

    La biblioteca de funciones matemáticas math.h, además de incluir multitud de funciones matemáticas (seno, coseno, raíz cuadrada, potencia, logaritmos...) incluye la definición de algunas constantes simbólicas de valores matemáticos que suelen ser bastante utilizados. Quízás los más utilizados sean las definiciones del número PI y del número e:

    #define M_PI        3.14159265358979323846
    #define M_E         2.71828182845904523536