12 de diciembre de 2018

Errores frecuentes.

   La siguiente es una lista de errores frecuentes observados en mis estudiantes o lectores del blog. Es una lista necesariamente incompleta y en frecuente actualización; sin embargo la considero de utilidad para el lector interesado en aprender de las experiencias de otros. Sobre todo si se considera que muchas veces se aprende más de los errores que de los aciertos.

   He tratado de clasificar los errores para facilitar al lector su ubicación, espero que ésta sea también de utilidad.

Sintaxis:
  • Omitir el punto y coma (;)  al final de las sentencias.
  • Olvidar las llaves de bloque asociadas a una estructura de control, cuando el número de sentencias es mayor a uno.
 Lectura de datos:
  • Olvidar el ampersand (&) antes de la variable: 
    • Incorrecto: scanf("%d", variable_int);
    • Correcto:   scanf("%d", &variable_int);
  • Utilizar secuencias de escape:
    • Incorrecto: scanf("%d\n", &variable_int);
    • Correcto:   scanf("%d", &variable_int); 
  • Utilizar el especificador de formato incorrecto:
    • Incorrecto: scanf("%d",  &variable_float);
    • Correcto:   scanf("%f",  &variable_float);
  • Utilizar el especificador de precisión para flotantes:
    • Incorrecto: scanf("%.2f", &variable_float); 
    • Correcto:   scanf("%f", &variable_float);
 Impresión de datos:
  • Utilizar el especificador de formato incorrecto:
    • Incorrecto: printf("El valor es %d\n", variable_float);
    • Correcto:   printf("El valor es %.2f\n", variable_float);
Estructuras de control:
  • Omitir las llaves del bloque cuando el cuerpo de la estructura de control tiene más de una sentencia.
  • Utilizar el operador de asignación "=" en lugar del operador de comparación "==".
Variables:
  • Pensar que las variables son automáticamente inicializadas con algún valor cuando no es así, utilizando en consecuencia valores "basura".
  • No inicializar (normalmente con cero) variables de tipo acumulador. 
 Funciones:
  • Olvidar colocar el tipo de dato a cada variable en la lista de parámetros.
  • Invocar a una función con más o menos parámetros de los que necesita, o con tipos de datos distintos a los especificados en la lista de parámetros.
  • Colocar corchetes a los arreglos cuando se pasan como argumento a una función (invocación o llamado de función): los arreglos se pasan como argumentos solamente con su identificador. Consulte más abajo la sección referente a Arreglos.
  • Hacer lectura de datos dentro de una función: si la función NO es de lectura de datos, la función no debe leer datos en su definición. Esta es una consideración de diseño más que un error en sí mismo.
  • Hacer impresión de datos dentro de una función: si la función NO es de impresión de datos, la función no debe imprimir los datos que gestiona en su definición. Esta es una consideración de diseño más que un error en sí mismo.
  • En una "biblioteca" personalizada de funciones, si una función A utiliza a una función B, B debe estar definida antes que A.
  • Invocar funciones que regresan un apuntador con el operador de des referencia. Consulte más abajo la sección referente a Apuntadores.
Arreglos:
  • No definir el tamaño de un arreglo.
  • Definir el tamaño de un arreglo estático con el valor de una variable. La mayoría de los compiladores modernos soportan esta característica sin problema; sin embargo, en el estándar ANSI C hacer esto es incorrecto, por lo que su programa estaría sacrificando su portabilidad.
  • No inicializar los elementos del arreglo cuando su valor inicial será utilizado.
  • Colocar más inicializadores que elementos en el arreglo.
  • Omitir el ampersand (&) en la lectura de elementos individuales en el arreglo (para un arreglo de enteros):
    • Incorrecto: scanf("%d", arreglo[i]); 
    • Correcto:   scanf("%d", &arreglo[i]);
  • Colocar corchetes al pasarle un arreglo a una función:
    • Incorrecto: funcion(arreglo[ ], n);
    • Correcto:   funcion(arreglo, n);
Apuntadores:
  • No utilizar el operador de des referencia cuando se quiere acceder al valor de a lo que apunta el apuntador (el valor al que hace referencia el apuntador): 
    • Incorrecto: printf("El valor es: %d\n", intPtr);
    • Correcto:   printf("El valor es: %d\n", *intPtr);
  • Utilizar un apuntador y asumir que se tiene espacio de almacenamiento como en un arreglo. Error común con cadenas:
    • char *s1; es distinto de char s2[100]
      • Incorrecto: fgets(s1, 100, stdin);
      • Correcto:   fgets(s2, 100, stdin); 
  • Utilizar el operador de des referencia en la invocación a funciones que regresan un apuntador. Ejemplo, considere (vea el Ejercicio 12 de la entrada Ejercicios selectos (apuntadores)):
    • int *arreglo, original[100]; (apuntador a entero y arreglo) y el siguiente prototipo int *copia(const int *original, int n); 
      • Incorrecto: *arreglo = *copia(original, n);
      • Incorrecto: *arreglo = copia(original, n);
      • Incorrecto:  arreglo = *copia(original, n);  
      • Correcto:    arreglo = copia(original, n);
Estructuras (struct):
  • Acceder a los elementos miembro de una estructura con el operador incorrecto:
    • Si la variable es de tipo estructura, se utiliza el operador punto (.).
    • Si la variable es de tipo apuntador a estructura, se utiliza el operador flecha (->).
  • Para una estructura definir elementos miembros de otra estructura que no está definida:
    • Sean e1 y e2 dos variables de tipo struct distintos: e2 puede tener elementos miembro del tipo de e1 si y sólo si el tipo struct de e1 está definido antes que e2.
Archivos:
  • Utilizar las funciones fwrite y fread para archivos de texto. Los archivos de texto se usan con fprintf, fscanf, fputs, fgets, etc.
  • Utilizar las funciones fprintf, fscanf, fputs, fgets, etc. para archivos binarios. Los archivos de binarios se usan con las funciones fwrite y fread.
Errores más frecuentes del curso
Errores más frecuentes que se cometen al tomar el curso de forma presencial.



3 de diciembre de 2018

Arreglos de dos dimensiones (matrices).

   Los arreglos de dos dimensiones también reciben el nombre de arreglos bidimensionales o simplemente matrices.

Conceptos, representación y estructura.
   Los arreglos bidimensionales son una estructura de datos de dos dimensiones de elementos contigüos, relacionados por un mismo tipo de datos y un mismo nombre, donde los elementos son distinguidos por dos índices.

(a) Representación (abstracción) de una matriz.
(b) Representación en memoria de una matriz.
 
    Las figuras anteriores muestran en (a), la representación de una matriz m de cuatro renglones y cuatro columnas (cuadrada). Los renglones y columnas de una matriz en C siempre empiezan en el índice 0, y terminan en el índice tamaño - 1 (3), dónde tamaño se refiere al tamaño, dimensión o longitud del renglón o columna de la matriz respectivamente. Por otro lado, la figura (b) muestra la representación física de la matriz m en la memoria principal de la computadora. Observe que la memoria de la computadora es una estructura lineal y no una estructura cuadrada (bidimensional), por lo que los elementos de la matriz son almacenados linealmente, de tal forma que el elemento m[1][0] es el inmediato posterior del elemento m[0][3], y que el elemento m[2][0] es el inmediato posterior del elemento m[1][3], y así sucesivamente.

   En general, la declaración de un arreglo bidimensional en el lenguaje de programación C tiene la siguiente estructura:

tipo_de_dato nombre_de_la_matriz[TAMAÑO1][TAMAÑO2];

donde:

  • tipo_de_dato es cualquier tipo de dato válido en C.
  • nombre_de_la_matriz es un identificador válido en C.
  • TAMAÑO1 es el número de renglones que tendrá la matriz.
  • TAMAÑO2 es el número de columnas que tendrá la matriz.

Declaración, inicialización, recorrido y uso con funciones.
   El Ejemplo 6.7 muestra la declaración, la inicialización, el recorrido, y el uso de matrices con funciones. Las líneas 6 y 7 definen dos constantes simbólicas M y N para el número de renglones y columnas de la matriz respectivamente. Estas constantes determinarán las dimensiones reales o físicas de matriz1, matriz2 y matriz3, declaradas en las líneas 14 y 15. Note que estas tres matrices tiene la forma de las figuras anteriores.

   Observe también que matriz2 y matriz3 han sido inicializadas en su declaración, esto quiere decir que en tiempo de compilación le son asignados los valores especificados. Los valores asignados están representados en las siguientes figuras:

(a) Inicialización de la matriz2.
(b) Inicialización de la matriz3.
 
    Ahora bien, la línea 14 muestra un tipo de inicialización en secuencia, la cual es posible debido a la forma en que se representa la matriz en la memoria de la computadora.

   En base a lo anterior, los primeros cinco elementos físicos de matriz2 tendrán los valores que se muestran en la figura (a). Los elementos que no son inicializados explícitamente, son puestos en 0 por omisión.

   Por otro lado, la línea 15 muestra una inicialización por renglones para matriz3; este tipo de inicialización se explica mejor con la figura (b) en donde se observa que los tres primeros elementos del renglón 0 están inicializados con los valores especificados (línea 15), mientras que el último de ellos ha sido inicializado por omisión con 0. Antes de continuar, asegúrese de entender lo que sucede con el segundo renglón de inicialización de la matriz3 y su correspondiente representación en la figura (b).

   El ciclo do-while de las líneas 17-20 valida (la validación sólo considera matrices cuyas dimensiones sean de al menos 2x2 y de a lo más MxN. Si bien es cierto que existen matrices de 3x1 o de 1x4 por ejemplo, también es cierto que técnicamente se les puede denominar vectores, por lo que por simplicidad y para motivos de ilustración se ha hecho este tipo  de validación) las dimensiones para la matriz1 específicamente, debido a que el Ejemplo 6.7 define unas dimensiones físicas (líneas 14 y 15), pero controla unas dimensiones lógicas (líneas 21 y 24), y dichas dimensiones lógicas estarán determinadas por las variables m y n (línea 19) para el número de renglones y columnas respectivamente.

   Las figuras de inicialización anteriores  indican también las dimensiones lógicas (indicadas con una elipse) para la matriz2 y la matriz3 respectivamente, mientras que las dimensiones físicas están representadas por la malla (matriz) de 4x4.

   La línea 21 hace el llamado a la función leeMatriz definida en la líneas 33-44. El llamado envía como argumentos la matriz1, y las dimensiones m y n obtenidas en la línea 19.

   Ahora bien, la función leeMatriz define tres parámetros: matriz[ ][N], m y n. Observe que matriz será el nombre con el que la función hará referencia al arreglo bidimensional que reciba, y que la notación [ ][N] le indica al compilador que lo que recibirá será precisamente un arreglo bidimensional. Note también que los primeros corchetes están vacíos mientras que los segundos tienen la dimensión física N declarada para el arreglo (líneas 14 y 15).

   En C, los arreglos de más de una dimensión deben llevar siempre, con posibilidad de omitir únicamente el tamaño de la primera dimensión, el tamaño físico de cada una de las dimensiones restantes, y esto está estrechamente relacionado con la representación física en la memoria de la computadora de los arreglos de n dimensiones.

   La especificación del tamaño de la segunda dimensión para las matrices le indica al compilador cuántos elementos hay por renglón, para así poder determinar en dónde empiezan cada unos de los renglones de la matriz.

   Continuando con el Ejemplo 6.7, la función leeMatriz se encarga de leer de la entrada estándar (línea 40) los elementos que conformarán la matriz lógica determinada por los parámetros m y n. Note cómo los ciclos for de las líneas 36 y 38 están en función de dichos parámetros.

   Por otro lado, las líneas 24, 26 y 28 hacen el llamado a la función imprimeMatriz definida en la líneas 33-44. El primer llamado envía como argumentos a la matriz1 y a las dimensiones obtenidas en la línea 19; mientras que el segundo y tercer llamado envían a la matriz2 y a la matriz3 respectivamente ambas con dimensiones lógicas 2 y 4.

   Finalmente, la función imprimeMatriz definida en las líneas 46-54 define una lista de parámetros análoga a la de la función leeMatriz. La función imprimeMatriz realiza un recorrido por renglones de la matriz matriz para su impresión en la salida estándar. Una posible salida del Ejemplo 6.7 se  muestra en la siguiente figura:

Una posible salida del Ejemplo 6.7.