14 de diciembre de 2016

Ejercicios selectos (apuntadores).

  1. Ajuste el Ejemplo 7.1 para que:
    1. Imprima el contenido del apuntador bPtr, es decir, la dirección en memoria de a que almacena bPtr.
    2. Modifique el valor de la variable a a través de aPtr.
    3. Declare una nueva variable de tipo entero b, asígnele un valor, haga que bPtr apunte a ella, y cambie el valor de b a través de bPtr.
    4. Trate de asignar directamente un valor distinto de cero (no use el operador de dirección) a cualquiera de los apuntadores ¿Qué sucede y por qué?
  2. Reescriba desde cero el Ejemplo 7.2. El objetivo de este ejercicio es replicar dicho ejemplo desde su perspectiva y compresión de los conceptos explicados en el blog.
  3. Modifique el Ejemplo 7.3 para que lea una cadena de la entrada estándar, y la envíe a las funciones imprimeAlReves e imprimeSinEspacios.
  4. Determine lo que realiza la siguiente función sin ejecutarla. Posteriormente, corrobore su respuesta con un programa que pruebe la función.
  5. Escriba un programa que pruebe cada una de las funciones de la biblioteca ctype. Su programa deberá probar al menos las funciones descritas en la entrada Aritmética de apuntadores, pero recuerde que dicha biblioteca contiene más funciones.
  6. Con base en el programa del Ejemplo 7.4, escriba una función análoga a la función convierteAMayusculas que convierta la cadena referida por cPtr a minúsculas; utilice las funciones de la biblioteca ctype descritas en la entrada Aritmética de apuntadores.
  7. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void rellenaArreglo(int *a, int n, int rango). La función deberá inicializar los n elementos referidos por a con números aleatorios definidos entre 0 y rango - 1.
  8. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void inicializaArreglo(int *a, int n, int valor). La función deberá inicializar los n elementos referidos por a, con el valor valor.
  9. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void imprimeArreglo(const int *a, int n, int c). La función deberá imprimir en la salida estándar, los n elementos referidos por a, con c elementos por renglón.
  10. Siguiendo la propuesta planteada en el Ejemplo 7.5, escriba una función con el siguiente prototipo: void aMorse(const char *c). La función deberá imprimir en la salida estándar la representación en código Morse de la cadena referida por c. Dicha representación deberá estar separada por tabuladores '\t' por cada carácter de c. Cualquier otra cosa que NO sea una letra, imprimala tal cuál en la salida estándar.
  11. Con base en el Ejemplo 7.6 y a la notación de aritmética de apuntadores, substituya la línea 24 por el comentario correspondiente de la misma línea, y compruebe la equivalencia de la notación. Realice lo mismo intercambiando las líneas 26 y 27, y las líneas 28 y 29 respectivamente.
  12. Escriba un programa que defina y pruebe una función con el siguiente prototipo: int *copiaArreglo(const int *original, int n). La función deberá copiar los n elementos referidos por original en un arreglo que haya sido creado utilizando memoria dinámica dentro de la función, mismo que será utilizado como valor de retorno para la función.
  13. Escriba un programa que defina y pruebe una función con el siguiente prototipo: char *copiaCadena(const char *original). La función deberá copiar los elementos referidos por original, en un arreglo de caracteres que haya sido creado utilizando memoria dinámica dentro de la función, mismo que será utilizado como su valor de retorno.
  14. Escriba un programa que defina y pruebe una función con el siguiente prototipo: int compara(const char *c1, const char *c2 ). La función deberá determinar si los elementos referidos por c1 son iguales (regresa 1) o no (regresa 0) a los elementos referidos por c2.
  15. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void imprimeAlReves(const char *original). La función deberá imprimir invertidos los elementos referidos por original. La función deberá usar recursividad para imprimir al revés los elementos referidos por original.
  16. Escriba un programa que defina y pruebe una función con el siguiente prototipo: char *invierteCadena(const char *original). La función deberá copiar invertidos los elementos referidos por original, en un arreglo de caracteres que haya sido creado utilizando memoria dinámica dentro de la función, mismo que será utilizado como su valor de retorno.
  17. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void quitaEspacios(char *c). La función deberá eliminar (si los hubiera) cualquiera de los tipos de espacios de la cadena referida por c. Note que la función modifica la cadena original, por lo que deberá asegurarse de terminar adecuadamente la cadena con el fin de cadena '\0'.
  18. Escriba un programa que defina y pruebe una función con el siguiente prototipo: int esPalindromo(const char *c). La función deberá determinar si los elementos referidos por c constituyen (regresa 1) o no (regresa 0) un palíndromo. Tome en cuenta que la función deberá ignorar espacios, signos de puntuación, signos de admiración e interrogación, comillas, etcétera, y que no hará distinción entre mayúsculas y minúsculas; se puede auxiliar de las funciones desarrolladas hasta el momento en los ejercicios y en los ejemplos del blog.
  19. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void podar(char *c). La función deberá eliminar de la cadena c los espacios, si los hubiera, que se encuentren al principio y al final de c.
    1. Sugerencia: utilice la función isspace (ctype.h) para determinar cualquier tipo de espacio.
    2. Utilice notación y aritmética de apuntadores para desarrollar sus habilidades en el manejo de los apuntadores.


7 de diciembre de 2016

Encuesta Kahoot

   Esta encuesta es con fines estrictamente cualitativos, no afecta en ningún sentido los resultados de su evaluación o del curso en general.

   Puede ser contestada de manera anónima utilizando un seudónimo.

   Para iniciarla, da clic aquí y espera las instrucciones del profesor.

   Lo que se dice en el siguiente video en lo referente a Colombia y los colombianos, aplica también para México y los mexicanos:




23 de noviembre de 2016

Ejercicios selectos (arreglos).

  1. Modifique el Ejemplo 6.1 de tal forma que imprima los 15 elementos del arreglo arreglo antes del ciclo for de la línea 13, con la finalidad de verificar que los elementos no inicializados explícitamente, son implícitamente inicializados a 0, tal y como se describe en el blog.
  2. Para el Ejemplo 6.1 suponga que N es 5 (línea 7) y que los inicializadores (línea 10) son: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ¿Qué sucede si existen más inicializadores que número de elementos para el arreglo? Haga un programa que compruebe su respuesta.
  3. Considere el Ejemplo 6.2 ¿Qué sucede si en lugar de que N sea 5, se cambia para que sea 500 o 5000?
  4. Basándose en el Ejemplo 6.3, haga un programa que procese arreglos de tipo float, no olvide modificar también los especificadores de formato correspondientes.
  5. Basándose en el ejercicio anterior, haga un programa que almacene un grupo de n calificaciones (cambie el tipo de dato a float), y determine con funciones:
    1. Cuál es la menor (float menor(float a[ ], int n);).
    2. Cuál es la mayor (float mayor(float a[ ], int n);).
    3. Cuáles y cuántas son aprobatorias y reprobatorias (void imprimeAprobatoriasReprobatorias(float a[ ], int n, float criterio);).
    4. Su media aritmética o promedio (float promedio(float a[ ], int n);).
     6. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void rellenaArreglo(int a[ ], int n, int rango);. La función deberá inicializar los n elementos del arreglo a, con números aleatorios definidos entre 0 y rango - 1. Así por ejemplo, si rango es 100, el arreglo a deberá contener n números entre 0 y 99.
     7. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void inicializaArreglo(int a[ ], int n, int valor);. La función deberá inicializar los n elementos del arreglo a, con el valor valor.
     8. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void imprimeArreglo(const int a[ ], int n, int c);. La función deberá imprimir en la salida estándar los n elementos del arreglo a, con c elementos por renglón.
     9. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void copiaArreglo(const int original[ ], int copia[ ], int n);. La función deberá copiar los n elementos del arreglo original en el arreglo copia.
     10. Escriba un programa que mida el tiempo en segundos para cada una de las funciones del Ejemplo 6.4. La intención de este ejercicio es determinar si existe o no una mejora en la eficiencia con las modificaciones descritas en el blog, ya que el algoritmo de ordenamiento no cambia, sólo se ha hecho, en principio, más eficiente. Para ello, su programa deberá:
  • Declarar dos arreglos de al menos 50,000 elementos cada uno.
  • Utilizar una función para inicializar uno de ellos con números aleatorios (Ejercicio 6).
  • Utilizar una función para copiar el arreglo inicializado en el segundo arreglo declarado (Ejercicio 9).
  • Enviar uno de los arreglos a la función burbuja y medir el tiempo que le toma ordenar los elementos.
  • Enviar el otro arreglo (la copia) a la función burbujaMejorado y medir el tiempo que le toma ordenar los elementos.
     11. Escriba un programa que pruebe las funciones del Ejemplo 6.5. Pruebe con datos leídos desde el teclado, con datos aleatorios, y verifique que las funciones de búsqueda trabajan de acuerdo a lo esperado. Nota: No olvide que la búsqueda binaria tiene como pre requisito que el conjunto de datos esté ordenado.
     12. Realice una prueba de escritorio para la función busquedaBinaria del Ejemplo 6.5 para que busque las siguientes claves: -4, 22, 1 y 13. Utilice el siguiente conjunto de búsqueda: -20, -4, 0, -9, 7, 10, 15, 22.
     13. Una vez que haya realizado las pruebas de escritorio propuestas en el ejercicio anterior, realice una implementación recursiva de la búsqueda binaria con base en la descripción hecha en el blog respecto de ésta.

Cadenas.

      1. Siguiendo las ideas expuestas en el blog respecto a los arreglos de caracteres (cadenas), escriba un programa que defina y pruebe una función con el siguiente prototipo: void copiaCadena(const char original[ ], char copia[ ]);. La función deberá copiar los elementos de la cadena original, en la cadena copia.
     2. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void invierteCadena(const char original[ ], char invertida[ ]);. La función deberá copiar invertidos o al revés los elementos de la cadena original, en la cadena invertida. Puede utilizar las funciones desarrolladas con anterioridad.
     3. Las cadenas (arreglos de caracteres) no pueden ser comparadas utilizando el operador "==". Para este ejercicio, construya una función con el siguiente prototipo: int esIgualCon(const char c1[ ], const char c2[ ]);. La función deberá comparar la cadena c1 con la cadena c2, y regresar 1 si c1 es exactamente igual a c2 y 0 en caso contrario. Nota: la función determina la coincidencia exacta, tome en cuenta que las cadenas "Hola" y "hola" son distintas aunque digan lo mismo.
     4. Un palíndromo es una expresión que representa o expresa lo mismo si se lee de izquierda a derecha que de derecha a izquierda, como por ejemplo:
  • Madam.
  • 2002.
  • Anita lava la tina.
  • Dabale arroz a la zorra el abad.
  • .  .  .
      Escriba un programa que defina y pruebe una función con el siguiente prototipo: int esPalindromo(const char c[ ]);. La función deberá determinar si los elementos de la cadena c constituyen (regresa 1) o no (regresa 0) un palíndromo. Puede apoyarse de las funciones desarrolladas hasta el momento y por ahora, asuma que los elementos de la cadena no contendrán espacios y que todos los caracteres serán introducidos en minúsculas.
      5.  Escriba un programa que defina y pruebe una función con el siguiente prototipo: int cuentaPalabras(const char c[ ]);. La función deberá contar y retornar el número de palabras de la cadena c. Considere y analice todas las formas posibles en las que puede aparecer una palabra en una frase: signos de puntuación, interrogación, admiración, etcétera.
     6. Escriba un programa que defina y pruebe una función con el siguiente prototipo: int cambiaOcurrencia(char c[ ], char viejo, char nuevo);. La función deberá cambiar el carácter viejo por el carácter nuevo en toda la cadena c todas las veces que aparezca. La función regresa el número de cambios que realizó o cero si no hubo ningún cambio.
     7. Escriba un programa que obtenga la CURP (Clave Única de Registro de Población). La CURP se obtiene de la siguiente forma (verifique estas reglas para su propia CURP):
  • Primera letra y primera vocal del primer apellido.
  • Primera letra del segundo apellido.
  • Primera letra del nombre de la persona, en casos como José o María, de ser posible, se tomará la primera letra del segundo nombre.
  • Últimas dos cifras del año de nacimiento.
  • Dos cifras del mes de nacimiento.
  • Dos cifras del día de nacimiento .
  • Sexo de la persona: H (Hombre) o M (Mujer).
  • Dos Letras, correspondientes a la entidad de nacimiento. La entidad de nacimiento se obtiene como la primera letra de la entidad y la última consonante. En caso de extranjeros es NE (Nacido en el Extranjero).
  • Consonante del primer apellido (la primera si el primer apellido empieza con vocal, la segunda si empieza en consonante).
  • Consonante del segundo apellido (la primera si el primer apellido empieza con vocal, la segunda si empieza en consonante).
  • Consonante del nombre de la persona (la primera si el primer apellido empieza con vocal, la segunda si empieza en consonante).
  • Dos dígitos para evitar duplicidad (genérelos aleatoriamente).
  • En casos en los que las personas no tienen segundo apellido, o que la letra que corresponde insertar sea la letra Ñ, se utilizará en su lugar la X. Esto lo puede hacer desde la captura de datos para facilitar las cosas.
     8. Escriba un programa que defina y pruebe una función con el siguiente prototipo: int terminaCon(const char c1[ ], const char c2[ ]);. La función deberá determinar si la cadena c1 termina con la cadena c2. Si c2 aparece al final de c1, la función regresa 1 y 0 en caso contrario. Ejemplos:
  • terminaCon("Esto es un ejemplo", "templo"); (regresa 0).
  • terminaCon("Esto es un ejemplo", "contemplo"); (regresa 0).
  • terminaCon("Esto es un ejemplo", "es un ejemplo"); (regresa 1).
  • terminaCon("Zona de desastre", "sastre"); (regresa 1).
  • terminaCon("Zona de desastre", "tu sastre"); (regresa 0).
  • terminaCon("Zona de desastre", "zona de desastre"); (regresa 0).
  • terminaCon("Zona de desastre", "La zona de desastre"); (regresa 0).
     9. Escriba un programa que defina y pruebe una función con el siguiente prototipo: int contiene(const char c1[ ], const char c2[ ]); . La función deberá determinar si la cadena c1 contiene a la cadena c2. Si c2 aparece en alguna parte de c1, la función regresa 1 y 0 en caso contrario.
     10. Escriba una función con el siguiente prototipo: void imprimeAlReves(const char c[ ]); . La función deberá imprimir al revés recursivamente la cadena referida por c. Sugerencia: utilice el modificador static.


16 de noviembre de 2016

Soluciones iterativas vs. recursivas.

   Existen dos funciones clásicas y claves que se resuelven con recursividad:
  1. La función que calcula el factorial de un número entero positivo.
  2. La función que calcula los términos de la serie de Fibonacci.
   Específicamente para estas dos funciones, el enfoque de la recursividad no es la manera más eficiente de resolverlas, pero ayudan significativamente a ilustrar el concepto de recursividad.

   Por otro lado, dichas funciones son tan clásicas que, para no incurrir en algún tipo de maldición del estilo del programa “Hola mundo!”, se revisarán necesaria y rápidamente, primero en su versión iterativa (ciclos) y posteriormente en su versión recursiva.

Factorial (Versión iterativa).
   El factorial de n denotado por n! (n >= 0) se obtiene en base a lo definido en la siguiente expresión:

n! = n * (n-1) * (n-2) * . . . * 1

de tal forma que, si n = 5 se tiene:

5! = 5 * 4 * 3 * 2 * 1 = 120

   De lo anterior puede observarse que la solución está dada por un ciclo. El Ejemplo 5.1 muestra la función que calcula de manera iterativa el factorial de un número. Note que la función asume que n >= 0.

   En base a todos los ejemplos anteriores todas las sentencias del Ejemplo 5.1 deberían ser completamente entendidas. Asegúrese de que así sea antes de continuar.

Factorial (Versión recursiva).
   Matemáticamente, la función factorial se define según lo expresado en la siguiente ecuación, para n >= 0.

   La función del Ejemplo 5.2 es esencialmente una traducción al lenguaje C de la ecuación anterior, donde a las líneas 25 y 26 se les conoce como criterio de paro o caso base de la recursividad, mientras que la línea 28 contiene el paso recursivo.

   Es importante hacer notar que en cada llamado recursivo se tiene una simplificación del problema original, lo que asegura que eventualmente se llegue al caso base de la recursividad, siempre que n >= 0.

   Las siguientes figuras ilustra el funcionamiento de la función factorial (líneas 24-29) del Ejemplo 5.2 para n = 4:
 
(a) Llamados recursivos para el factorial del número cuatro.
(b) Retornos recursivos para el factorial del número cuatro.
 
   En la figura (a) se muestran los llamados recursivos, note cómo en cada nivel de n existen valores pendientes de ser calculados debido precisamente al funcionamiento de la recursividad. Cuando finalmente se alcanza el caso base (n = 0), es posible determinar los valores que fueron quedando pendientes (las flechas de la figura (b) muestran dicho procedimiento), y calcular los resultados de n para cada nivel, los cuales son mostrados encerrados en círculos en la figura (b). Analice y estudie lo anterior hasta comprenderlo, ya que en buena medida, de ello depende la comprensión del Ejemplo 5.2.

   Es preciso mencionar que una vez que se tiene la relación de recurrencia (una relación de recurrencia es una ecuación que relaciona a los términos de una sucesión con alguno de sus predecesores) que establece tanto el criterio de paro como el (los) paso(s) recursivo(s), el proceso de traducción al lenguaje C es muy sencillo, por lo que la deducción de dicha relación de recurrencia es en realidad la parte difícil en el diseño de funciones recursivas. Para el caso de la función factorial, la relación de recurrencia está dada por la definición matemática de la expresión que la define.


   Por último, note que se ha utilizado el tipo de dato long debido a que el factorial de un número crece de manera exponencial. La salida de los Ejemplos 5.1 y 5.2 para n = 4 se muestra en la siguiente figura:

Salida de los Ejemplos 5.1 y 5.2 para n = 4.
 
Fibonacci (Versión iterativa).
   Otra función matemática clásica y clave es la función que genera la serie o sucesión de Fibonacci. La serie de Fibonacci, es la siguiente sucesión infinita de números:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, . . .

   Observe que, con excepción de los dos primeros términos, cada término de la serie se obtiene como la suma de los dos términos anteriores:

  • fib(0) = 0
  • fib(1) = 1
  • fib(2) = 1
  • fib(3) = 2
  • fib(4) = 3
  • fib(5) = 5
  • fib(6) = 8
  • fib(7) = 13
  • fib(8) = 21
  • fib(9) = 34
  • fib(10) = 55
  • fib(11) = 89
  • . . .

   De los elementos de la serie mostrados, es posible deducir que la solución puede obtenerse también mediante un ciclo.

   El Ejemplo 5.3 muestra la función que calcula de manera iterativa el número de Fibonacci para un término de la serie. Observe que la función asume que n >= 0.

   Asegúrese de entender el programa del Ejemplo 5.3 antes de continuar, particularmente la función fibonacci. Note que, al igual que antes, se ha utilizado el tipo de dato long.

Fibonacci (Versión recursiva).
   Ahora bien, matemáticamente, la función de Fibonacci que genera los términos de la sucesión de Fibonacci se define en base a lo expresado en la siguiente ecuación para n >= 0:


   Note que la función fibonacci del Ejemplo 5.4 es una traducción al lenguaje C de la ecuación anterior. Observe también que las líneas 25 y 26 implementan el criterio de paro o caso base de la recursividad, mientras que la línea 28 contiene los dos pasos recursivos involucrados en el cálculo de los términos de la serie.

   Para n >= 2 cada término de la serie se calcula como la suma de dos llamados recursivos, por lo que la función fibonacci del Ejemplo 5.4 genera un árbol de recursividad como el que se muestra en la siguiente figura para n = 4:
 
Árbol de recursividad de la serie de Fibonacci para n =4.
 
    Observe que el término fibonacci(2) se repite en la rama derecha y en la rama izquierda del árbol. Dicha repetición de cálculos aunada al overhead implicado en cada llamado de función, hace que las implementaciones recursivas sean en general ineficientes respecto del tiempo y del espacio en memoria.

   Lo anterior debe reafirmar en la mente del lector que las implementaciones recursivas de las funciones factorial y fibonacci son ineficientes, y por consiguiente, un mal enfoque de solución; sin embargo, ambas funciones ilustran de manera extraordinaria el concepto de recursividad. Por otro lado, basta con ver las definiciones matemáticas y compararlas con sus correspondientes versiones escritas en lenguaje C para darse cuenta de la elegancia, la correspondencia directa, y la simplicidad inherente a las implementaciones recursivas.

   La salida de los Ejemplos 5.3 y 5.4 para n = 4 se muestra en la siguiente figura:

Salida de los Ejemplos 5.3 y 5.4 para n = 4.
 
Investigación empírica.
   El Ejemplo 5.5 muestra un esqueleto de programa para medir el tiempo en segundos de un proceso largo.

   La línea 6 incluye la biblioteca de funciones time.h necesaria para usar las funciones time (líneas 11 y 13) y difftime (línea 16).

   La función time y el tipo time_t se han descrito brevemente con aterioridad  en el Ejemplo 3.16, basta ahora con recordar que sirven para leer la fecha y hora del sistema en donde se ejecuta el programa. Por otro lado, la función difftime calcula la diferencia en segundos que existe entre las variables comienzo y final (líneas 9, 11 y 13).

   La línea 12 representa el proceso al que se desea tomar el tiempo de ejecución en segundos. El Ejemplo 5.5 también sugiere que se incluya ahí el llamado a la función recursiva de la serie de Fibonacci con un argumento de 46 por proponer un determinado valor. Resultaría sumamente interesante probar éste y otros valores.

   El Ejemplo 5.5 como tal y por sí mismo no ejecuta ni determina nada. De hecho si lo ejecuta tal cual visualizará siempre una salida de cero segundos. Sin embargo, dicho ejemplo resultará de mucha utilidad para poder medir, de manera empírica el tiempo en segundos que le toma a una función recursiva por ejemplo, terminar sus cálculos. Aunque para los seres humanos un segundo es poco, para una computadora un segundo es muchísimo tiempo, ya que en un segundo se procesan millones de micro instrucciones en el procesador, sin embargo esta forma de medir el tiempo en segundos para algunos de procesos (como las funciones recursivas), nos proporcionarán una idea más asequible del esfuerzo requerido por parte de algunas funciones.

8 de noviembre de 2016

Bibliotecas personalizadas de funciones.

   El lenguaje de programación C incorpora muchas y muy útiles, además de variadas, funciones incluidas en sus diferentes bibliotecas pero además, a través de un mecanismo relativamente sencillo, es posible crear "bibliotecas" personalizadas de funciones, esto es, archivos que contengan funciones definidas por el programador y que puedan ser utilizadas en cualquier programa que las incluya, de manera similar a como se hace con las bibliotecas estándares del lenguaje.

   Estrictamente hablando, con el procedimiento que se describirá a continuación, no se estará creando ninguna biblioteca en el sentido más estricto del lenguaje de programación, sino que, haciendo uso del mecanismo de abstracción proporcionado por la directiva de inclusión de archivos #include, se proporciona al programador la sensación de incluir sus propias bibliotecas de funciones.

   En esta entrada se mostrará brevemente el procedimiento para crear bibliotecas personalizadas de funciones. La descripción se basará en el Ejemplo 4.2, por lo que se recomienda su revisión.

   El primer paso para crear una biblioteca personalizada de funciones, es tener las funciones definidas por el programador en un archivo nuevo e independiente. Dicho archivo debe contener al menos una función, mientras que el límite dependerá del espacio designado para el archivo en el disco que lo almacene, es decir: potencialmente ilimitado.

   Resulta sumamente conveniente crear bibliotecas personalizadas de funciones con funciones que tengan algún tipo de relación entre ellas, por lo que en realidad, la limitante en el número de funciones que pueda contener una biblioteca debería estar supeditada a la abstracción de los servicios que se espera proporcione dicha biblioteca.

   Las funciones mayor y menor del Ejemplo 4.2, se han colocado en un nuevo archivo tal y como se muestra en el Ejemplo 4.5. El nombre de archivo que contendrá la biblioteca personalizada de funciones no deberá contener espacios preferentemente, símbolos acentuados o compuestos como la letra ñ.

   Note que en esencia el Ejemplo 4.2 y el Ejemplo 4.5 son esencialmente iguales, la diferencia radica en la localización de las funciones que, para el caso de bibliotecas personalizadas, se encuentran en un archivo independiente con extensión “.h”.

   La línea 7 del Ejemplo 4.6 muestra la inclusión de la biblioteca personalizada de funciones del Ejemplo 4.5. Observe que en la directiva #include se han cambiado los corchetes angulares (< >) por comillas (“ ”) lo cual, además de diferenciar una biblioteca personalizada de funciones de una biblioteca del lenguaje, le indica al compilador que busque el archivo indicado entre comillas en la misma localidad (carpeta o directorio) que el archivo que incluye a dicha biblioteca.

   En resumen, tanto el archivo fuente que usará a la biblioteca personalizada de funciones como el archivo que contiene las funciones de biblioteca personalizada, deberán estar en el mismo directorio o carpeta. Cabe mencionar que es posible especificar una ruta para la localización de un archivo de biblioteca personalizada en particular, pero en general esto no es recomendable debido a que se sacrifica la portabilidad, ya que la especificación de rutas de archivos y directorios cambia de un sistema operativo a otro.

   Así como no hay un límite para el número de funciones que puede contener un archivo de biblioteca personalizada, se debe aclarar que tampoco hay un límite para el número de archivos de bibliotecas personalizadas que se pueden incluir dentro de un programa. Para el caso del Ejemplo 4.6, se han incluido una biblioteca del lenguaje (línea 6) y una biblioteca personalizada (línea 7), pero podrían ser un número arbitrario de cualquiera de ellas; la única recomendación en este sentido sería, incluir primero las bibliotecas del lenguaje, y después las bibliotecas personalizadas con las funciones definidas por el programador.

   Finalmente, note que si se proporcionan los mismos datos para el Ejemplo 4.2 y para el Ejemplo 4.6 se obtendrá la misma salida, como se muestra en la siguiente figura:

Una posible salida producida por los Ejemplos 4.2 y 4.6.

3 de noviembre de 2016

Ejercicios selectos (funciones).

  1. Modifique el Ejemplo 4.1 para que considere el caso cuando la ecuación cuadrática sólo tiene una raíz, i.e. cuando el discriminante es igual a cero; de tal forma que sólo se calcule y presente un resultado en la salida estándar.
  2. Modifique el ejercicio anterior para que tantos los coeficientes de la ecuación como las raíces, sean ahora tipos de dato double. No olvide hacer los ajustes correspondientes para los especificadores de formato.
  3. Modifique el Ejemplo 4.2 para que las variables de la función main sean x ,y y z ¿Compila?, ¿por qué? Suponiendo que compila, ¿cambia en algo el funcionamiento del programa?, ¿por qué? ¿Y si ahora se cambia el nombre de los parámetros de las funciones por a, b y c?
  4. Complete el Ejemplo 4.2 con la función medio, la cual devuelve el número de en medio en base a su valor ordinal. Utilice lo resuelto en el Ejercicio 5 (Primera Parte) de la entrada Ejercicios selectos para las estructuras de control.
  5. Escriba un programa que, a través de una función, calcule el cuadrado de un número double.
  6. Utilice la función desarrollada en el ejercicio anterior para resolver el problema del Ejemplo 4.1. Observe que en la línea 22 el discriminante lleva el cálculo de b al cuadrado (); utilice su función para calcular dicho valor.
  7. Escriba una función que permita convertir de millas a kilómetros. Una milla es aproximadamente igual a 1.61 kilómetros. Escriba también un programa que pruebe su función.
  8. Escriba una función que permita convertir de kilómetros a millas. Escriba también un programa que pruebe su función.
  9. El factorial de un número n (n >= 0) denotado por n! se obtiene según lo definido en la siguiente expresión: n! = n * (n - 1) * (n - 2) * . . . * 1; es decir, se multiplica el número, por su antecesor y éste por su respectivo antecesor y así sucesivamente hasta llegar al neutro multiplicativo. Escriba una función que calcule el factorial de un número.
  10.  Escriba un programa que a través de una función, calcule el producto de dos números enteros a y b por medio de sumas sucesivas, esto es: a * b = a + a + . . . + a, donde a se suma tantas veces como lo indique b. Para ello:
    1. Resuelvalo primero con dos números enteros positivos.
    2. ¿Qué pasa si alguno de los números es negativo? ¿Cómo se comporta su algoritmo, representado en la función, ante dicha situación? Tome en cuenta que el resultado es un número negativo.
    3. ¿Qué pasa si ambos números son negativos? ¿Cómo se comporta su algoritmo, representado en la función, ante dicha situación? Tome en cuenta que para este caso el resultado es un número positivo.
   11.  Escriba un programa que a través de una función, calcule el cociente entero de dos números enteros positivos a y b por medio de restas sucesivas, esto es a / b ==>

  a - b = c1, si c1 >= b, entonces
c1 - b = c2, si c2 >= b, entonces
c2 - b = c3, si c3 >= b, entonces
        .     .     .                                
donde el cociente estará dado por el número de veces que se haya podido repetir el procedimiento descrito.
   12.  Escriba un programa que a través de una función, calcule la potencia de dos números a y b por medio de productos sucesivos, esto es:
                                        a^b = a * a * .  .  . * a 
donde a se multiplica tantas veces como lo indique b.
 Para ello:
  • Resuélvalo primero con dos números enteros positivos.
  • ¿Qué pasa si a es negativo?
  • ¿Qué aspectos debe tomar en consideración si b es negativo?
  • ¿Qué pasa si a o b son cero? ¿Qué pasa si ambos lo son?
   13.  Repita el ejercicio anterior, pero en lugar de usar el operador de multiplicación (*), utilice la función que desarrolló en el Ejercicio 10.
   14.  Si para el programa del Ejemplo 4.4 se realizara una nueva ronda de llamados consecutivos a cada una de las tres funciones involucradas, ¿cuáles serían los valores que se visualizarían en la salida estándar?
   15.  Para el Ejemplo 4.6 elimine la línea 7, guarde el archivo, y compile. ¿Qué sucede?, ¿a qué se debe y por qué?
   16.  Incorpore a la biblioteca personalizada de funciones del Ejemplo 4.5 la función medio realizada en el Ejercicio 4 y pruebe su funcionamiento.
   17.  Escriba una función que determine si un número entero positivo es o no un número primo. Un número primo es aquél que es divisible únicamente por la unidad y sí mismo. Su función deberá regresar un 1 si el número enviado como argumento es primo y 0 si no lo es.
   18.  Escriba una función que determine si un número es perfecto o no. Un número perfecto es un entero positivo que es igual a la suma de sus divisores positivos sin considerarse él mismo como divisor.
   19.  Escriba un programa que, dado un número de hasta cinco cifras, exprese el valor de dicho número en palabras. Así por ejemplo, para el número 99 999, el programa dará como salida: “99 999: noventa y nueve mil novecientos noventa y nueve”. El castellano (español) es un idioma complejo, así que si la salida de su programa es “99 999: noventa y nueve mil nueve cientos noventa y nueve”, o genera expresiones por el estilo, es aceptable. El número debe ser leído como tal (no por dígitos separados), y su análisis inicia por "desmenuzarlo" en unidades, centenas, millares, etcétera e identificar los patrones convenientes para expresarlo en palabras. 
    La idea de este ejercicio es que haya funciones que se encarguen de procesar cada uno de los dígitos, y que se identifiquen patrones de repetición al expresar cantidades dependiendo del dígito y su posición.
   20.  Pruebe creando otras bibliotecas personalizadas de funciones con los ejemplos y ejercicios realizados hasta ahora. No importa por ahora que las funciones de dichas bibliotecas no tengan mucha relación, lo que importa por ahora es practicar la creación de bibliotecas personalizadas de funciones.


26 de octubre de 2016

Repeticiones híbridas.

   El lenguaje de programación C incorpora dos sentencias bastante prácticas y útiles en diferentes contextos: break y continue. Dichas sentencias permiten implementar un tipo de repetición híbrida, en cuanto a que es posible construir ciclos que incorporen los mecanismos de repetición controlada por contador y centinela de manera combinada.

   El programa del Ejemplo 3.20 muestra el uso de la sentencia continue dentro de un ciclo for, pero tanto break como continue pueden utilizarse también en los ciclos while y do-while.

   En principio, la estructura de repetición for está gobernada por un tipo de repetición controlada por contador, y también en principio, dicho ciclo se repetirá NUM veces. Sin embargo, la sentencia continue de la línea 13 indica un cambio en el flujo de ejecución secuencial, de tal forma que cuando el flujo de ejecución procesa la sentencia continue, el flujo de ejecución brinca a la verificación de la expresión3, para después seguir con la secuencia de ejecución usual de la estructura for, es decir, hacia la validación de la expresión condicional (expresión2).

   En resumen, el ciclo for se repite NUM veces, pero no así todas las sentencias del ciclo, ya que cuando el flujo de ejecución alcanza la sentencia continue, las sentencias posteriores a ella serán ignoradas, por lo que la salida del Ejemplo 3.20, es la impresión de los números del uno al diez, uno por renglón, excepto el CENTINELA, tal y como se muestra en la siguiente figura:

Salida del Ejemplo 3.20.
 
    Ahora bien, el Ejemplo 3.21 muestra una situación análoga. Para empezar, note que dicho ejemplo es exactamente el mismo que el Ejemplo 3.20 excepto por la línea 13, donde se ha cambiado la sentencia continue por la sentencia break.

   La sentencia break ha sido descrita en la entrada referente a la estructura de selección switch, en donde servía para romper el flujo de ejecución y brincar al final de dicha estructura de selección. Para el caso del ciclo for funciona de manera análoga, ya que cuando el flujo de ejecución la procesa, es como si se diera un salto a la expresión condicional (expresión2) del ciclo y ésta fuera evaluada como cero (falsa), lo que implicaría la terminación del ciclo.

   En términos prácticos es válido decir que la sentencia break rompe el ciclo en el que se encuentre la sentencia sin importar la expresión condicional que lo gobierna. Vale pena mencionar que, si se encuentran varios ciclos anidados, la sentencia break rompe únicamente el ciclo más interno que la contenga, los demás ciclos permanecen inalterados (compruebe lo anterior probando el Ejemplo 3.21_1).

   Observe que en principio el ciclo for del Ejemplo 3.21 está también gobernado, al menos en su expresión condicional, por un tipo de repetición controlada por contador, y que en principio se repetirá NUM veces. Sin embargo, cuando el valor del contador coincida con el del CENTINELA, la sentencia break será procesada y el ciclo terminará, por lo que el efecto del programa es ver los números del uno al CENTINELA - 1, uno por renglón, tal y como se muestra en la siguiente figura:

Salida del Ejemplo 3.21.
 
    Finalmente se presenta el Ejemplo 3.25 el cual muestra una variación del Ejemplo 3.21. Tome en cuenta que el Ejemplo 3.25 genera una salida distinta y se deja como ejercicio al lector determinar su salida antes de ejecutarlo y corroborar su respuesta con su correspondiente ejecución.

   El Ejemplo 3.25 declara una variable entera llamada bandera. Una bandera es muy común en la programación y se utiliza como indicador para señalar una determinada situación. Para el caso de nuestro ejemplo, la bandera se ha "encendido" o inicializado en 1 (línea 12) para garantizar la continuidad del ciclo en la expresión condicional (expresión2) del ciclo for, por lo que su continuidad depende en principio, estrictamente de la variable de control (i). Ahora bien, cuando la expresión condicional de la línea 13 se cumple, es decir, la variable de control tiene el mismo valor que el CENTINELA, la variable bandera se "apaga" (se le asigna 0 en la línea 14) y la sentencia de la línea 15 se ejecuta independientemente de la condición de la línea 13.

   El efecto derivado de apagar la bandera es el siguiente: al llegar al fin del bloque del ciclo for, el flujo de control brinca a la modificación de la variable de control (expresión3 del for), para posteriormente pasar el control a la expresión condicional del ciclo for (expresión2) y, al determinarse que la bandera ha sido apagada (0), toda la expresión condicional es evaluada a cero (como si fuera falso) terminando así el ciclo antes de que la variable de control terminara con sus NUM iteraciones.

   En resumen, el Ejemplo 3.25 implementa una repetición controlada por contador (i), pero si se verifica la aparición del CENTINELA, la continuidad de ciclo se interrumpe por medio de una bandera.

20 de octubre de 2016

Tipos de repetición.

   Independientemente del tipo de estructura de control que se utilice para la repetición de un grupo de sentencias, existen básicamente dos tipos de repetición:
  1. Repetición controlada por contador.
  2. Repetición controlada por centinela.
   Ambos tipos de repetición pueden ser implementados en C utilizando cualquiera de las estructuras de control while, do-while y for, por lo que no hay una relación de correspondencia única entre el tipo de repetición y la estructura de control de repetición que se utilice. Sin embargo, debido a la naturaleza y al funcionamiento de las estructuras de control de repetición, el enfoque de repetición controlada por contador se implementa de manera más natural utilizando los ciclos while y for; mientras que la repetición controlada por centinela, se implementa de forma ligeramente más natural utilizando la estructura de control de repetición do-while, pero esto es más una conveniencia que una asociación.


18 de octubre de 2016

La estructura de control for.

   La estructura de control o ciclo for tiene la siguiente estructura general:

          for ( expresión1; expresión2; expresión3 ) {
              sentencia(s);
          }

   El ciclo for procesa o ejecuta el grupo de sentencia(s) delimitadas por su bloque de manera repetida, mientras la expresión2, al ser evaluada, sea distinta de cero, es decir: expresión2 != 0.

   La llave izquierda “{” delimita el inicio de su bloque, mientras que la llave derecha “}” delimita su final.

   Si se procesan el conjunto de sentencia(s) delimitadas por el bloque del ciclo for, cuando la secuencia de ejecución alcanza el indicador de fin del bloque, la estructura de repetición for modifica el flujo de ejecución secuencial y brinca automáticamente a la evaluación de la expresión3, la cual habitualmente modifica la(s) variable(s) de control; después de esto, el flujo de control se pasa a expresión2, y si expresión2 != 0, se repite el procesamiento de las sentencia(s) delimitadas por el bloque. En caso contrario, se procesa la siguiente sentencia que se encuentre después del indicador de fin de bloque del ciclo for (es un error común de programación terminar por "inercia" una estructura de repetición for con “;”, lo cual no es ningún error respecto a la gramática del lenguaje, por lo que el compilador no identificará nada extraño).

   Cabe mencionar aquí que en un ciclo for cualquiera de las expresiones: expresión1, expresión2, o expresión3, pueden ser vacías. Si expresión2 es vacía, se genera un ciclo infinito.

   La estructura de repetición for es equivalente a la estructura de repetición while en el siguiente sentido:

                  expresión1;
                  while (expresión2) {
                      sentencia(s);
                      expresión3;
                  }

por lo que puede decirse, que cualquier ciclo escrito en una estructura de repetición while puede ser reescrito en la estructura de repetición for y viceversa, de tal forma que es posible mantener la equivalencia lógica y funcional entre una y otra estructura.

   El Ejemplo 3.13 muestra el uso de la estructura de repetición for, el cual genera la misma salida que para el Ejemplo 3.9. Asegúrese de comprender su funcionamiento.

   Considere ahora el Ejemplo 3.14 el cual utiliza una estructura for un poco más elaborada que la anterior para implementar la sumatoria de los números entre 1 y n, dada por la siguiente expresión:
   Observe que es posible inicializar más de una variable en la expresión1 del ciclo for (línea 16): la variable de control i, que sirve para ir generando la serie de números, mientras que la variable sum se utiliza como acumulador. De hecho, es posible tanto inicializar como modificar más de una variable de control en expresión1 y expresión3 respectivamente, y la forma de hacerlo es escribir dichas expresiones como una lista de variables separadas por comas.

   La expresión de la línea 17 del Ejemplo 3.14 puede interpretarse en nuestro lenguaje como: “A la variable sum increméntala en i”, debido a que en C, la expresión:

variable = variable operador expresión;
es equivalente a:

variable operador= expresión;
donde operador es cualquier operador aritmético válido en C, y se le denomina notación compacta. Es muy común escribir este tipo de expresiones usando dicha notación.

   El Ejemplo 3.15 muestra el uso de la estructura de repetición for con dos variables de control i y j, el ciclo puede ser gobernado por ambas variables si el problema a resolver así lo requiere, pero para este ejemplo, sólo se imprimen las variables de control en orden ascendente y descendente respectivamente, como se muestra en la siguiente figura:

Salida del Ejemplo 3.15.
 
    Observe que la expresión1 del ciclo for (línea 11) contiene la inicialización de las dos variables de control separadas por una coma (,). De manera análoga, la expresión3 contiene la modificación, es decir el incremento (i++) y decremento (j--) respectivamente de las variables de control separadas también por una coma.

   Por otro lado, el Ejemplo 3.16 genera una secuencia de quince caracteres (véanse las línea 8, 14 y 15) aleatorios. El programa incluye en la línea 5 la biblioteca estándar stdlib.h, la cual es necesaria para poder utilizar las funciones srand y rand de las líneas 13 y 15 respectivamente.

   La biblioteca time.h de la línea 6 se ha incluido para poder hacer uso de la función time de la línea 13, la cual permite leer la fecha y hora del sistema en donde se ejecuta el programa, y regresa un número de tipo size_t el cual es capaz de almacenar los segundos que han transcurrido desde las cero horas del primero de Enero de 1970, y que es utilizado por la función srand para inicializar la semilla de números aleatorios con un valor distinto en cada ejecución.

   Los números aleatorios son generados por alguna función matemática, y para generarse, se basan en un valor inicial (semilla), si éste valor no cambia, la secuencia de números generados tampoco. Por lo tanto, la función srand es la responsable de cambiar la secuencia de generación de números aleatorios a partir del valor semilla, que para el caso del Ejemplo 3.16, utiliza la hora del sistema cada vez que se ejecuta el programa.

   Ahora bien, la función encargada de generar el número aleatorio a partir de la inicialización que hizo la función srand es la función rand, (línea 15). La función rand genera un número aleatorio entre 0 y RAND_MAX, el cual es un valor dependiente de la implementación de la biblioteca (debido a las diferentes arquitecturas de hardware y software), pero se garantiza que es de al menos 32,767 en cualquier implementación de la biblioteca que se base en el estándar ANSI C.

   Observe que el valor de la función rand es divido por 26, y que el residuo de dicha división es sumado al carácter 'a' (línea 15). Por extraño que parezca (sumarle un número a una letra), tiene sentido al menos en C, y se explica de la siguiente manera:

   Sea n el número que representa el código del carácter 'a' (recuerde que lo que la computadora entiende, procesa y “ve” son sólo números, de hecho el valor del carácter 'a' es traducido por el compilador a su representación numérica, pero para nosotros como seres humanos, es más fácil y claro de entender el símbolo 'a', que el código numérico que lo representa; y aunque se podría poner directamente el código del carácter 'a', el precio a pagar sería la portabilidad a otros sistemas, así como la problemática de los números mágicos discutida con anterioridad), y sea d definido como:

d = rand( ) % 26
y c como:
c = n + d

entonces c estará en el conjunto de las letras:

{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}

   El valor de c representado como carácter, es lo que se imprime en el especificador de formato “%c”, el cual es el especificador de formato en C para los caracteres (char).

   En resumen, el Ejemplo 3.16 genera una secuencia de quince caracteres aleatorios. Antes de avanzar, asegúrese de comprender lo que se ha descrito hasta aquí y por qué el Ejemplo 3.16 genera una salida como la de la siguiente figura. Pruebe con varias ejecuciones, quizá alguna de ellas contenga una palabra medianamente comprensible.

Una posible salida del Ejemplo 3.16.

14 de octubre de 2016

Ejercicios selectos (estructuras de control).

Primera Parte.
  1. Escriba el Ejemplo 3.1 eliminando los delimitadores de bloque para cada una de las estructuras de selección if, y compruebe que que sigue funcionando igual. Tome en cuenta que ésto es cierto cuando la estructura de control se compone únicamente de una única sentencia.
  2. Escriba un programa que determine si un número entero positivo es par o impar. Un número par es aquel que es divisible por dos.
  3. Escriba un programa que utilice una estructura de selección if-else y los operadores relacionales para determinar el menor de tres números enteros (asuma que los números son distintos). Para este ejercicio, no debe utilizar operadores lógicos, si no sabe qué son, mucho mejor, en su momento se presentarán en el blog.
  4. Escriba un programa que utilice una estructura de selección if-else y los operadores relacionales para determinar el mayor de tres números enteros (asuma que los números son distintos). Para este ejercicio, no debe utilizar operadores lógicos, si no sabe qué son, mucho mejor, en su momento se presentarán en el blog.
  5. Escriba un programa que utilice una estructura de selección if-else y los operadores relacionales para determinar el número central (en función de su ordinalidad) de tres números enteros (asuma que los números son distintos). Para este ejercicio, no debe utilizar operadores lógicos, si no sabe qué son, mucho mejor, en su momento se presentarán en el blog.
  6. Reescriba el Ejemplo 3.6 usando la simplificación de líneas descrita para el operador ternario (vea el penúltimo párrafo de la entrada El operador ternario ? :) y compruebe su equivalencia, ¿cuál es mejor y por qué?, ¿en qué casos convendría utilizar una u otra?
  7. Reescriba el Ejemplo 3.6 utilizando una estructura de selección if-else en lugar del operador ternario.
  8. Escriba un programa que utilice el operador ternario para determinar el menor de dos números enteros. Este programa es similar al del Ejemplo 3.6.
  9. Extienda el Ejemplo 3.7 para que el rango de conversión de números a palabras sea más amplio: agregue las sentencias necesarias de tal forma que el programa considere al menos números del uno al diez, ¿qué sucede si al ejecutar el programa en lugar de un número escribe una letra?
  10. Escriba un programa que convierta lo siguientes números a su correspondiente número romano: 1, 5, 9, 14, 20, 26, 40, 50, 90, 99, 100, 500 y 1000. Para cualquier otro número se deberá reportar: "No existe traducción disponible".
  11. Convierta el Ejemplo 3.7 y el ejercicio anterior a su correspondiente representación con una estructura de selección if-else anidada. Para este ejercicio, asuma que no existe la estructura de control switch; la idea es obtener la misma funcionalidad cambiando la estructura switch por una estructura if-else.
  12. Reescriba el Ejemplos 3.8 y el Ejemplo 3.9 utilizando el operador de incremento como prefijo.
  13. Reescriba los Ejemplos 3.8 y 3.9 utilizando el operador de incremento como postfijo.
  14. Para el Ejemplo 3.10 sustituya el operador de incremento (++) por el operador de decremento (--), analice lo que sucederá, determine su respuesta y compruébela con la ejecución correspondiente.
  15. Escriba un programa que imprima en la salida estándar los números del uno al diez, uno por renglón pero en orden inverso; es decir: 10, 9, 8, 7, . . . , 1. No utilice el operador de decremento.
  16. Escriba un programa que imprima los números del uno al diez uno por renglón pero en orden inverso; es decir: 10, 9, 8, 7, . . . , 1. Utilice el operador de decremento.
  17. Reescriba el Ejemplo 3.9 utilizando la estructura de repetición do-while.
  18. Reescriba el Ejemplo 3.12 utilizando la estructura de repetición while.
  19. Repita el Ejercicio 16 utilizando ahora la estructura de repetición do-while.
  20. Escriba un programa que imprima en la salida estándar los números del 1 a n, donde n es un valor proporcionado por el usuario de su programa. Así por ejemplo, si n es 6, su programa imprimirá los números del 1 al 6.
  21. Considere el Ejemplo 3.13 y coloque al final de la línea 12 un “;”, compile el programa y ejecútelo. Lo único que verá en la salida estándar es: “i = 11” ¿Por qué?
  22. Reescriba todos los ejemplos y ejercicios que utilizan la estructura de repetición while utilizando ahora la estructura de repetición for.
  23. Reescriba el Ejemplo 3.14 cambiando la estructura de repetición for por la estructura de repetición while.
  24. Reescriba el Ejemplo 3.16 con una estructura de repetición while.
  25. Modifique el Ejemplo 3.16 para que imprima en la salida estándar varias palabras (n) de longitud (línea 8). Pruebe con varias combinaciones de n y m, e imprima en varias columnas utilizando la secuencia de escape '\t'; la idea es reconocer una palabra de las que se generan; tome en cuenta que mientras más larga sea la palabra, la probabilidad de reconocer una será menor. Sugerencias:
    1. Utilice un ciclo externo que contenga al ciclo for del Ejemplo 3.16 para que controle el número de palabras (n) a generar.
    2. Defina a n y m como variables para un mayor dinamismo e interacción y no olvide validarlas.
    3. Coloque el inicializador de semilla de números aleatorios (función srand) afuera y antes de los ciclos.


 

Segunda Parte.

  1. Escriba un programa que, siguiendo la idea del Ejemplo 3.17 y considerando un esquema de calificaciones basado en letras (A, B, C, D y F), procese un conjunto de dichas calificaciones desde la entrada estándar y determine cuántas calificaciones hay de cada una. Si se procesa una calificación no considerada, se deberá indicar dicha irregularidad (no existe la calificación E por ejemplo).
  2. Escriba un programa que lea dos números enteros:
    1. Si los números son iguales, su programa reporta dicha situación y termina.
    2. Si el primer número (num1) es menor que el segundo (num2), su programa deberá imprimir en la salida estándar la sucesión ascendente de números de num1 a num2; es decir: num1, num1+1, num1+2, . . . num2.
    3. Si el primer número (num1) es mayor que el segundo (num2), su programa deberá imprimir en la salida estándar la sucesión descendente de números de num1 a num2; es decir: num1, num1-1, num1-2, . . . num2.
  3. Escriba un programa que determine si un número entero positivo n es o no un número primo. Un número primo es aquel que sólo es divisible por sí mismo y la unidad. Sugerencia: intente con un ciclo todos los posibles divisores de n, si tiene más de dos, no es primo. Otro enfoque es descartar los divisores obvios (1 y n) y buscar algún divisor entre 2 y n/2 (para n > 2), si existe alguno, no es primo.
  4. Modifique el Ejemplo 3.21_1. Cambie la sentencia break por continue y determine su salida sin ejecutarlo en la computadora; después corrobore su respuesta con la salida real.
  5. Considere el programa del Ejemplo 3.22 y determine su salida sin ejecutarlo en la computadora; después corrobore su respuesta con la salida real.
  6. Considere el programa del Ejemplo 3.23 y determine su salida sin ejecutarlo en la computadora; después corrobore su respuesta con la salida real.
  7. Para el programa del Ejemplo 3.23 agregue una llave izquierda ({) al final de la línea 11 y una llave derecha (}) en la línea 15, de tal forma que se delimite el bloque del ciclo for más externo para que las sentencias de las líneas 12-14 pertenezcan a él ¿Cambia en algo la salida del programa? Determine su respuesta sin ejecutarlo en la computadora, después ejecútelo, y analice lo que sucede.
  8. Para el programa modificado con base en lo expuesto en el ejercicio anterior, cambie la i por j en la línea 13 y determine la salida sin ejecutarlo en la computadora; después corrobore su respuesta con la salida real.
  9. Considere el programa del Ejemplo 3.24 y determine su salida sin ejecutarlo en la computadora; después corrobore su respuesta con la salida real.
  10. Modifique el Ejemplo 3.25 par que funcione exactamente igual que el Ejemplo 3.21, es decir, ambos programas deben generar la misma salida. Para este ejercicio, asuma que no existe la sentencia break y no modifique el CENTINELA ni el valor de las variables. Sugerencia: intente dos versiones, una utilizando un continue en el if original, y otra utilizando una estructura if-else sin tener que hacer uso del continue.
  11. Pruebe con más datos el Ejemplo 3.17, proporcionados tanto del teclado como de un archivo siguiendo los mecanismos de re direccionamiento.
    12. Reescriba el Ejemplo 3.18 con una estructura de repetición do-while y asegúrese de que sea lógica y funcionalmente equivalente.
    13. Reescriba el Ejemplo 3.18 con una estructura de repetición for y asegúrese de que sea lógica y funcionalmente equivalente.
    14. Modifique el Ejemplo 3.18 para que lea el número de calificaciones a procesar. Tanto el número de calificaciones (mayor que cero), como las calificaciones (entre 0 y 10) deberán ser validados. Utilice la estructura de repetición while para la repetición controlada por contador.
    15. Modifique el Ejemplo 3.18 para que lea el número de calificaciones a procesar. Tanto el número de calificaciones (mayor que cero), como las calificaciones (entre 0 y 10) deberán ser validados. Utilice la estructura de repetición do-while para la repetición controlada por contador.
    16. Modifique el Ejemplo 3.18 para que lea el número de calificaciones a procesar. Tanto el número de calificaciones (mayor que cero), como las calificaciones (entre 0 y 10) deberán ser validados. Utilice la estructura de repetición for para la repetición controlada por contador.
    17. Reescriba el Ejemplo 3.19 con una estructura de repetición do-while y asegúrese de que sea lógica y funcionalmente equivalente.
Reescriba el Ejemplo 3.19 con una estructura de repetición for y asegúrese de que sea lógica y funcionalmente equivalente.
    18. Modifique el Ejemplo 3.19 para que las calificaciones sean validadas (que estén entre cero y diez). Utilice la estructura de repetición while para la repetición controlada por centinela.
    19. Repita el ejercicio anterior pero utilice la estructura de repetición do-while para la repetición controlada por centinela.
    20. Repita el ejercicio anterior pero utilice la estructura de repetición for para la repetición controlada por centinela.
    21. Escriba un programa que realice la suma de los números del uno al diez:
                (a) Utilice la estructura de repetición while.
                (b) Utilice la estructura de repetición do-while.
    22. Escriba una programa que realice la suma de números enteros procesados desde la entrada estándar. La suma se detiene cuando el número leído sea igual a cero.
                (a) Utilice la estructura de repetición while.
                (a) Utilice la estructura de repetición do-while.
    23. Escriba un programa que permita determinar el mayor de n números proporcionados desde el teclado, donde n >1.
    24. Escriba un programa que permita determinar la suma de los primeros n números enteros positivos proporcionados desde el teclado, donde n >1. Para este programa, si el usuario introduce números negativos se deberán ignorar, y el programa deberá considerar únicamente la suma de números positivos.
    25. Escriba un programa que permita determinar por selección la suma de los primeros n números pares (n > 1). Su programa no debe generar los números pares, debe seleccionarlos.
    26. Escriba un programa que permita determinar por selección la suma de números pares contenidos entre 1 y n, donde n > 1.  Su programa no debe generar los números pares, debe seleccionarlos.
    27. Escriba un programa que permita determinar por selección la suma de números pares contenidos entre m y n, donde m > 1 y n > m.  Su programa no debe generar los números pares, debe seleccionarlos.
    28. Escriba un programa que permita identificar los números primos entre 1 y n, donde n > 1.
    29. Escriba un programa que permita determinar los números primos contenidos entre m y n, donde m > 1 y n > m.
    30. Dada una lista de números enteros positivos proporcionados desde la entrada estándar, determine el mayor y el menor de ellos. La lista termina al proporcionar cualquier número negativo o cero.
    31. ¿Qué conclusiones puede determinar de la realización de los seis ejercicios anteriores?, ¿qué tipo de estructura de repetición es más natural, desde su perspectiva, para una repetición controlada por contador y por qué? En este mismo sentido, ¿qué tipo de estructura de repetición es más natural para una repetición controlada por centinela?
    32. Reescriba el programa del Ejemplo 3.20 utilizando una estructura de repetición while y determine lo que pasa. ¿Funciona de la misma manera?, ¿se genera algún tipo de problema?, ¿se mantiene la equivalencia lógica y funcional?
    33. Reescriba el programa del Ejemplo 3.20 utilizando una estructura de repetición do-while y determine lo que pasa. ¿Funciona de la misma manera?, ¿se genera algún tipo de problema?, ¿se mantiene la equivalencia lógica y funcional?
    34. Reescriba el programa del Ejemplo 3.21 utilizando una estructura de repetición while y determine lo que sucede. ¿Funciona de la misma manera?, ¿se genera algún tipo de problema?, ¿se mantiene la equivalencia lógica y funcional?
    35. Reescriba el programa del Ejemplo 3.21 utilizando una estructura de repetición do-while y determine lo que sucede. ¿Funciona de la misma manera?, ¿se genera algún tipo de problema?, ¿se mantiene la equivalencia lógica y funcional?
    36. Escriba un programa que lea un número que representará una cantidad de dinero. El número debe estar entre 1 y 4000. Su programa deberá determinar la menor cantidad de billetes de $500, $200, $100, $50 y $20 así como de monedas de $10, $5 y $1 que debe entregar un empleado a una persona que solicita dicha cantidad.

6 de octubre de 2016

Lectura de datos.

Lectura de datos.
   El Ejemplo 2.3 muestra un sencillo programa que lee desde la entrada estándar dos números enteros decimales (los operandos), y los suma. Este ejemplo también se describirá línea a línea.

   Las líneas 1-7 deberían resultar ahora  familiares, en base a la explicación de un primer programa en C. Por otro lado, la línea 8 presenta un nuevo elemento: la declaración de variables.

   La declaración de variables en C puede hacerse al iniciar un bloque ({ ... }) y tiene la siguiente estructura general:

          {
                tipo_de_dato lista de variables;
                             .
                             .
                             .
          }

donde tipo_de_dato es alguno de los tipos de datos de C, y la lista de variables es una lista de identificadores separada por comas. En base a lo anterior, la línea 8 está declarando tres variables de tipo entero a través de la palabra reservada int.

   Una palabra reservada es una palabra (token) que no puede ser utilizada como identificador y que es propia del lenguaje, lo cual quiere decir que ninguna variable o función (ningún identificador) por ejemplo, puede llamarse “int”.

   Por otro lado, un identificador es una secuencia de caracteres sin espacios que inicia con una letra. Los identificadores sirven para dar nombre a las variables que se declaran y que serán utilizadas dentro de un programa.

   En C existen diferentes tipos de datos: int, float y char son tres de ellos. La idea de tipo de dato se refiere a una categoría de datos pertenecientes a un dominio pero, desde el punto de vista del lenguaje de programación C ¿qué es un tipo de dato?

   Lo que una computadora almacena en su memoria principal no es otra cosa que una secuencia de bits; pues bien, esa misma secuencia de bits tiene diferentes interpretaciones, y la interpretación específica está en función del tipo de dato al que se refiera.

   Considere la siguiente secuencia de bits: 1 1 0 1 0 1 0 1. Suponga que dicho byte representa, como número, al entero decimal 213. Si esta misma secuencia de bits, se interpreta ahora como carácter (char) por ejemplo, podría representar a la hermosa letra 'R' (no es que así sea, es sólo una suposición), mientras que interpretada como un número con punto decimal (float) podría representar al 69.5.

   En resumen, un tipo de dato es la manera en que una computadora interpreta un patrón de bits.

   Continuando con el ejemplo que iniciamos, la línea 10 debería resultar familiar. A este tipo de sentencias se les conoce en el argot computacional como prompt, es decir, un mensaje de solicitud de datos.

   La línea 11 es nueva y muestra uno de los usos de la función scanf. La función scanf se compone básicamente de dos partes:

  1. El (los) especificador(es) de formato indicados entre comillas.
  2. Una lista de variables separadas por comas en donde se almacenarán los valores procesados (leídos) de la entrada estándar.

   El especificador de formato%d”, le indica a la función scanf que leerá un entero decimal, mientras que la expresión “&operando1”, especifica la variable en la que se almacenará el valor leído de la entrada estándar: operando1.

   Debe observarse que el nombre de la variable ha sido antecedido por el símbolo “&”, la justificación de ello se comprenderá mejor cuando se trate el tema de Apuntadores; por ahora, basta con que no olvide anteceder con dicho símbolo el nombre de la variable en la que se desee almacenar el valor procesado, ya que su omisión no es detectada por el compilador como un error, debido a que no es una violación a la gramática del lenguaje C.

   Las líneas 12 y 13 son análogas a las líneas 10 y 11 respectivamente. Asegúrese de comprender la similitud. Por otro lado, la línea 14 presenta la expresión:

suma = operando1 + operando2;

   Dicha expresión le indica a C que realice la operación aritmética de adición sobre los valores almacenados en operando1 y operando2, y que el resultado se asigne (almacene) en la variable suma.

   La tabla siguiente muestra los operadores aritméticos en C:

Operador      Descripción
                   /             División (cociente)
               %            Módulo (residuo)
                             *             Multiplicación (producto)
             +             Adición (suma)
                  -              Sustracción (resta)

   Los operadores se evalúan siguiendo una precedencia. La precedencia de operadores le indica al compilador cuáles de los operadores en una expresión deben ser evaluados primero. Considere la siguiente expresión:

3 + 5 * 4

   La expresión anterior es evaluada como 23 y no como 32, debido precisamente a que la multiplicación tiene una precedencia mayor sobre la adición, sin importar que la adición sea la operación que aparece primero (más a la izquierda).

   Si lo que se desea es primero realizar la suma y luego el producto, la expresión debe escribirse como:

(3 + 5) * 4

   Los paréntesis modifican la forma en que son evaluadas las expresiones, es decir, modifican la precedencia de cualquier operador y éste es su uso más común, aunque en muchas ocasiones se utilizan sólo por claridad en la forma de representar una expresión sin que modifiquen la precedencia de los operadores involucrados.

   Los operadores aritméticos “/”, “%” y “*” tienen la misma precedencia y es más alta que la de los operadores “+” y “-”. Si en una expresión existen operadores con la misma precedencia, la expresión es evaluada de izquierda a derecha.

   Finalmente, la línea 15 (la línea 17 se explicó a detalle en un primer programa en C ) introduce una novedad respecto al primer ejemplo, ya que muestra el uso de especificadores de formato dentro de la función printf.

   Al igual que en la función scanf, el especificador de formato “%d” le indica a la función printf que en la posición del especificador de formato va a imprimir, en la salida estándar, un entero decimal cuyo valor está almacenado en la variable suma, la cual se encuentra después de la coma. En este sentido, por cada especificador de formato debe existir su correspondiente variable asociada, de tal forma que tanto el número de especificadores de formato como el de variables, debe corresponder.

   Una posible salida de nuestro ejemplo se muestra en la siguiente figura. Asegúrese de comprender la descripción realizada hasta el momento, así como de entender lo que sucede con la ejecución y la salida correspondiente antes de continuar.

Una posible salida del Ejemplo 2.3.

   Considere ahora el Ejemplo 2.4 y compárese con el anterior. Note que todas las líneas son iguales excepto por la línea 15. La línea 15 contiene tres especificadores de formato “%d”. Cada especificador de formato, le indica a la función printf que imprima en orden, el entero decimal correspondiente almacenado en cada una de las variables correspondientes: operando1, operando2 y suma.

   La lógica del programa de este nuevo ejemplo no cambia en nada con respecto de la lógica del ejemplo anterior, sólo se ha cambiado el estilo en la forma de presentar los resultados, la cual es más ad hoc con la intención del programa.

   El segundo ejemplo  muestra la forma de incluir más de un especificador de formato en la función printf, de tal forma que puede observarse que por cada especificador de formato existe su correspondiente variable asociada. Es importante mencionar también que es responsabilidad del programador el asegurar que exista una correspondencia entre el especificador de formato y el tipo de dato de la variable, C no realiza esta verificación.

   Los especificadores de formato para printf tienen la siguiente forma general:

%[-]m.nx

donde % delimita el inicio del especificador de formato y x representa el especificador de formato a utilizar. El guión o signo de menos alinea el campo a la izquierda; si se omite, el campo se alinea a la derecha. Ahora bien, dependiendo del valor de x, los número enteros representados por m y n se interpretan de manera diferente:
  • Usualmente, m es la longitud mínima y n es la longitud máxima del campo (ancho de campo o espacio) que se utilizará para imprimir x.
  • Si x representa el especificador de formato para un número con punto decimal, n es interpretado como la precisión que deberá ser utilizada (número de decimales después del punto).
   Una posible salida para el Ejemplo 2.4 se muestra en la siguiente figura. Pruebe ambos ejemplos con distintos valores y observe sus resultados; repita el procedimiento hasta que se sienta cómodo y entienda por completo el mecanismo de funcionamiento de los programas.

Una posible salida del Ejemplo 2.4.