Ángel Blanco
“El Mago”
Primera Edición
Índice
Título Página
Introducción………………………………………………………………………22
Ejemplo #1………………………………………………………………22
Ejemplo #2………………………………………………………………25
Ejemplo #3………………………………………………………………31
Ejemplo #4………………………………………………………………32
Diagramas de flujo………………………………………………………………38
Notas y reglas sobre diagramas de flujo……………………………40
Ejemplo #5………………………………………………………………44
Ejemplo #6………………………………………………………………46
Resumen del capítulo 2…………………………………………………………50
Ejercicios del Capítulo #2: Lógica de algoritmos……………………………50
Funciones básicas………………………………………………………………55
¡Hola mundo! ………………………………………………… ………55
La función printf y la declaración de variables……………………60
Función scanf: La entrada de datos…………………………………64
Funciones getch y getche………………………………………………67
La constantes, cómo escribir código y los comentarios……………70
Resumen del capítulo III: Introducción a la programación en C…………75
Ejercicios del capítulo #3: Introducción a la programación en C…………80
Dedicatoria especial:
A:
José Cruz
Special quotation:
Espero que te haya ido muy bien en el semestre. Tú eres el lugar donde se
podrán apoyar los que tengan problemas en algoritmos (a mi parecer tú eres el
que más va entender y mejor le va a ir) así que ya sabes…échale una mano a
nuestros amigos y mantén unido el pequeño grupo que ha ido estudiando con
nosotros desde hace algún tiempo. No es para que te dejes comer d nadie
tampoco, pero yo creo que tú serás el único que les podrá ayudar.
Si, si, si…en primer lugar hay que aclarar eso. Este manual es
completamente GRATIS. ¿Se entendió bien? ¡No! Hey, no es broma, el manual
no tiene derechos de copia, puedes venderlo, fotocopiarlo parcial o totalmente, o
reproducirlo en cualquier forma digital o fotoeléctrica. ¿Todavía no me crees?
Para ponerlo simple, donde sea que consigas este manual, no te pueden cobrar
más que el precio de las copias (o sea, por página, la cantidad de fotocopias). He
dicho esto cuando menos mil veces y nadie me cree.
Si así es…puedes hacer lo que quieras con el…espero que no seas tan
ambicioso de cogerlo y plagiarlo y luego decir que es tuyo, aunque si eres tan
mediocre de llegar a ese límite de coger el trabajo de alguien más y decir que es
tuyo, tu propia ambición te llevará a tu hundimiento. O sea que ya ves…hasta te
estoy dando la idea (si eres de esa clase de personas…). Al fin y al cabo, los
derechos de autor no sirven para gran cosa (por eso puse unas risitas “jajaja” en
el índice donde señala esta sección), así que no creo que te sirva para eso. Y
además, lo que importa es lo que uno tiene en la mente…si dices que sabes todo
lo que dice este manual y luego demuestras lo contrario…la misma verdad te
tumbará…
todas formas ayudan. En una revisión futura, editaré los capítulos que crea que
necesiten mejoras, o según las sugerencias que me hagan mis lectores.
resultados dependerán grandemente de que tanto esfuerzo usted haga y que tanto
se ejercite, así que recomiendo que siga con tiempo el curso, y vaya resolviendo
los ejercicios y observando los temas un tiempo prudente. El caso ideal es que
usted siempre vaya adelante con respecto al tema que el profesor va a impartir.
A mi apreciadísima amiga Patricia del Carmen Ureña Santana, por ser la mejor
amiga y consejera que alguien pueda haber tenido.
A mis amigos Patricia Núñez y Eduardo Baret, que ya han vivido desde el
colegio más de una historia conmigo.
Tipos de variables
char: (1 byte) Es un dato que puede almacenar un carácter cualquiera (‘a’, ‘b’,
‘c’, ‘5’, ‘3’) o un número entero entre -127 y 127 (2 ,5 ,7 ). Note que cuando
ponemos el caracter ‘5’ usamos comillas simples, mientras que para el número 5
no se usan comillas. Esto es porque la computadora hace una diferencia entre el
5 como número y el ‘5’ como letra. Ya hablaremos de esto luego.
short: (2 bytes) Almacena un número entero entre -16,383 y 16,383 o una letra.
int : (4 bytes) Almacena un número entero entre -32,767 y 32,767 o una letra.
que un float es igual a 5, se debe poner 5.0 para decir explícitamente que el
número es float. Los double también tienen esa característica.
Para declarar una variable (esto se aplica tanto a diagramas de flujo como a la
programación en C propiamente dicha) se declara diciendo el tipo, luego el
nombre de la variable, y si se desea se le asigna un valor de inmediato usando el
signo “=”, seguido de un punto y coma (“;”). Por ejemplo:
Podemos declarar varias variables de golpe. Ponemos el tipo que tendrán todas,
luego las variables, separadas por comas, y luego un punto y coma. En este
ejemplo, se declaran b, c y d como caracteres, y a la variable c le asignamos la
letra ‘d’. RECUERDE…esa letra ‘d’ simboliza la letra ‘d’ en el teclado, no la
variable d. La variable d es otra variable.
char b, c = ‘d’,d;
AND (“&&”): Retornará verdadero si las dos partes de análisis que se están
evaluando son verdaderas, en otro caso será falsa. Ejemplo:
OR (“| |”): retorna verdadero (1) si al menos una de las partes de la expresión es
verdadera, retornará falso (0) si ambas partes son falsas. Ejemplo:
5 | | 0 al evaluarse retorna 1.
3 | | 2 al evaluarse retorna 1.
0 | | 1 al evaluarse retorna 1.
0 | | 0 al evaluarse retorna 0.
! ( 5 ) da un valor de 0.
! ( 0 ) da un valor de 1.
Asigno el valor de B B =3
Escribo la expresión B++ *5
Sustituyo a B por su valor 3*5
Se realiza la evaluación de la expresión, 3*5 da como resultado 15
El efecto de ++ hace que B aumente en uno por lo que ahora B = 4
Expresión --A*++B
El efecto de -- hace que A, que es igual a 6, baje a 5, y el ++ hace que B, que vale
4 después del proceso de hace un rato (“B++*5”), valdrá 1 más, así que valdrá 5.
Se sustituye A y B por su valor 5*5
Se evalúa la expresión, y el resultado es 25.
En el caso de que los signos estén antes (o ligados antes y después), las
operaciones se van realizando aritméticamente (es decir, primero se realiza lo que
esté en paréntesis, o se realiza de izquierda a derecha si no hay paréntesis). Los
signos de ++ actuarán en parejas de 2 de izquierda a derecha de acuerdo al grupo
de operaciones que se va realizando. Por ejemplo, si tuviésemos a = 0, y luego
evaluar ++a + ++a + ++a + ++a, el resultado de la expresión sería 11, y el valor
final de a sería 4. En este orden:
Luego se sustituye cada a por dos (su valor). La expresión queda así:
Expresión: 5 + 14 + ++c
Otros símbolos importantes, pero bastante entendibles, son “- =”, “+=”, “*=”,
“/=” y “%=”. Estas hacen que a la expresión de la izquierda se le asigne su
propio valor operado con la expresión de la derecha. Es decir, es lo mismo decir:
En una última nota sobre unarios (otra vez), cabe destacar que la
asignación es mucho más “lenta” todavía que los “++” o “--” postfijos. O sea,
en el último ejemplo que dimos de unarios, imagínese que hubiésemos tenido que
c += 5 + c++ + ++c + ++c + c++ + ++c, sabiendo que c comienza en 2. La
expresión 5 + c++ + ++c + ++c + c++ + ++c está intacta desde el ejemplo
anterior. Sólo que ahora a c le estamos asignando su propio valor más el
resultado de la expresión. Como recordamos, c terminaba con valor de 5, pero
por el efecto de los ++ postfijos, aumentaba a 7. Ahora bien, habíamos dicho que
nunca los postfijos hacen su efecto mientras halla una variable de ese tipo que
aún no haya sido sustituida por su valor. El asunto en este caso es que esa “c”
que está antes del “+=” realmente no se considera parte de la expresión evaluada
(dése cuenta de que esa c no se sustituirá por nada, solamente está ahí para que se
le asigne un valor). Por lo que en ese caso, los ++ postfijos actúan antes que la
asignación, y al final tendremos c += 24, y como c vale 7 (después de los efectos
de los ++ postfijos) tenemos c = c +24, c =7+24, c valdrá 31. Eso es todo lo que
hay que decir sobre unarios.
17
()
! ++(prefijo) --(prefijo) sizeof (luego lo veremos)
* / %
+ -
< <= > >=
== ¡=
&&
||
¿
++(postfijo) --(postfijo)
= += -= *= /= %=
int a = 6, b = 3 , c = 5, d = 2;
float x = 2.5;
Primer paso, se evalúa lo que está dentro de los paréntesis, sustituyendo a por 6,
tendríamos que 6 - 5 da 1:
Ahora evaluamos los signos de suma y resta. Tendríamos que evaluar (b + 10),
(0 - 2) y (3 - d). b es 3 y d es 2, así que los resultados son 13, -2 y 1.
El resto de sumas y restas están después del ternario por lo que las
ignoramos por el momento.
Las operaciones que quedan son operadores lógicos. Recordando la tabla más
arriba, vemos que la AND (&&) se evalúa antes que la OR (| |). Entre las mismas
AND, debemos evaluar de izquierda a derecha. Decimos ( -2 && 1 && b), el
resultado de -2 && 1 es 1(verdadero) ya que AND es verdadero si sus dos partes
son verdaderas, y en este caso, como tanto -2 y 1 son distintos de cero, se
consideran verdaderos y la expresión se concluye como verdadera. Luego,
tomando ese uno que retornó la AND, hacemos la otra AND ( 1 && b).
Sustituyendo b por 3 (1 && 3) da verdadero(retorna 1). Con ese uno, como ya
no quedan mas AND, resolvemos la OR( 13 | | 1), y el resultado es verdadero
19
d %= 1 ¿ 3 + ++c * b / x : 3 * b++
d %= 3 + ++c * b / x
Como ahora estamos evaluando una nueva expresión, tenemos que volver a ir
cayendo desde el tope de los órdenes de operación. No hay paréntesis, así que
evaluamos el unario. Como el ++ está antes del c, se modifica el valor de c
sumándole 1 (c valía 5, ahora c valdrá 6). Luego se sustituye.
d %= 3 + 6 * b / x
d %= 3 + 7.2
Como una nota interesante sobre esta evaluación, existe un operador para
transformar el tipo de datos. En los capítulos siguientes no se usa mucho que
digamos, pero si por ejemplo queremos convertir un retorno float en un dato
de tipo int, ponemos dentro de un paréntesis el tipo de dato de la conversión de
dato y luego el dato. En el ejemplo anterior, si hubiese quedado d% = (int) 10.2,
luego de hacer la suma y que quedaba 10.2, el int dentro del paréntesis
20
1) 171(agrupe ++d y ++d, luego el resultado con el otro ++d, y luego súmele 5)
2) 3
3) ¡ERROR! (25/2.5 es 10, pero ese 10 es 10.0 (float) por el 2.5, recuerde que el
resultado “hereda” el tipo de dato más complejo y el % da error con float)
4)1
5) 205 (primero se agrupa c++ y c++, da 204, luego al 204 se le resta --c, c baja
uno, tenemos 204-101, da 103, a eso le sumamos ++c, c vuelve a subir a 102, y
103 + 102 da 205. Luego de esto, por el efecto de los dos c++ que usamos al
principio, c sube hasta 104, pero esto no interesa en este ejercicio, no altera)
Introducción
4. Algoritmo: para saber si es par tenemos que ver si se divide entre 2 o no.
Bueno, entonces pensamos “Bien, pero… ¿y eso cómo lo hago?”.
Recordemos el concepto de módulo.
int Num;
Indicamos el inicio del algoritmo (todo algoritmo debe indicar mediante pasos
su inicio y su fin)
1) Inicio
2) Capturamos Num
25
Ahora bien, existe otra forma de hacer este algoritmo, sin usar el signo de
“= =”. ¿Desea probar suerte? Haga un esfuerzo mental y piense alguna forma de
rodear esta situación sin usar el signo de “= =”. Realmente existen muchas
formas. Pista: Nótese que la condición no necesariamente debe ser una igualdad,
puede ser cualquier tipo de expresión que retorne un valor.
26
Ejemplo #2: Sumar los números enteros desde 1 hasta N (incluyendo N).
Ejemplo #2: Sumar los números enteros desde 1 hasta N (incluyendo N).
A ver, necesito una para ir guardando la suma…y necesito otra para saber
que número es el que debo agregar a la suma. Necesito a N para saber hasta
donde debo sumar, pero a N ya lo declaramos. Con fines de ahorrar espacio,
donde hubiésemos declarado a N sola, mejor declararemos: int N, suma,
numsumado; Es recomendable que cuando escojamos los nombres de las
variables, escojamos nombres con alguna relación a la función de la variable,
de esta manera el algoritmo es más claro, y tú mismo podrás entender el
algoritmo después de un largo tiempo sin verlo con mayor facilidad. Es decir,
yo pude haber declarado int N, A, B; pero esa A y esa B no le dicen nada a
una gente que vea el algoritmo sin haberlo pensado, mientras que “suma” y
“numsumado” guardan una clara relación con lo que esas variables van a
hacer en el algoritmo (en suma guardaremos la suma, en numsumado, el
número que vamos a sumar). Preferiblemente, las variables tienen nombres
cortos (para comodidad al codificar). Imagínese que yo hubiera declarado
“numsumado” como “numerosumado”, si yo hubiera necesitado escribir 32
veces esa palabra en el teclado el mero hecho de tener que escribir una
variable con un nombre tan largo podría volverse un poco tedioso. Además,
se reducen las posibilidades de cometer un error de tecleo al escribirlo, ya que
si escribimos “numerosumao”, y sin querer no tecleamos la ‘d’, el lenguaje C
no reconocería la variable, ya que no está declarada, y el computador te tiraría
un error “Variable no declarada”, que en un examen se traduce como 10
puntos menos (-10). Así que…cuidado con eso.
1. Inicio
2. Capturamos N.
3. suma += numsumado
4. Si numsumado = = N entonces imprimir “La suma es “suma” ”, si no, +
+numsumado y volver al paso 3.
5. Fin
Este algoritmo tiene varias notitas y a la vez varios fallos que debemos
comentar. Por ejemplo:
1. Inicio
2. Capturamos N.
3. suma += numsumado
4. Si numsumado = = N entonces imprimir “La suma es “suma” ”, si no, +
+numsumado y volver al paso 3.
5. Fin
1. Inicio
30
2. Capturamos N.
3. Si N <= 0 entonces volver al paso 2.
4. suma += numsumado
5. Si numsumado = = N entonces imprimir “La suma es “suma” ”, si no, +
+numsumado y volver al paso 4.
6. Fin
1) Inicio
2) Capturamos N, por lo que N = 1.
3) Si N <= 0 entonces volver al paso 2. Como N es 1, el resultado de la
expresión N <= 0 es 0(falso). Como tenemos Si 0, entonces…, debemos
elegir la opción del si no. Como el “si no” no existe en la condicional, la
condicional sencillamente sigue hacia el siguiente paso.
4) suma +=numsumado, al valor de suma le asignamos su propio valor que
es cero, mas numsumado que es 1, así que suma será 1 en lo adelante.
5) Si numsumado = N entonces imprimir “La suma es “suma” ”, si no, +
+numsumado y volver al paso 4. Como 1 es igual a 1, el algoritmo
imprime “la suma es “suma””. Cuando algo de lo que se imprime es una
variable, se pone entre comillas dobles. En este caso, se imprimiría “La
suma es 1” ya que suma es 1. Se sigue con el siguiente paso.
6) Fin. Ya hemos cumplido nuestra tarea.
31
1) Inicio
2) Capturamos N, por lo que N = 5.
3) Si N <= 0 entonces volver al paso 2. Como N es 5, el resultado de la
expresión N <= 0 es 0(falso). Como tenemos Si 0, entonces…, y no hay
un “si no” seguimos con el paso siguiente.
4) suma +=numsumado, suma = suma (0)+numsumado (1), suma valdrá 1.
5) Si numsumado = = N entonces imprimir “La suma es “suma” ”, si no, +
+numsumado y volver al paso 4. Como 5 no es igual a 1, se toma el si no
y dice “++numsumado y volver al paso 4”. Ahora numsumado vale 2.
4) A suma se le agrega numsumado, 1+2 es 3.
5) Como 2 no es igual a 5, subimos numsumado en 1(valdrá 3) y volvemos al
paso 4.
4) A suma que es 3 se le agrega numsumado que es 3, entonces suma será 6
5) Como 3 no es igual a 5, se numsumado sube a 4 y vamos al paso 4.
4) A suma que es 6, se le agrega numsumado (4), suma valdrá 10.
5) Como 4 no es igual a 5, se sube numsumado a 5 y vamos al paso 4.
4) A suma que es 10 se le agrega numsumado (5), y suma valdrá 15.
5) Como 5 es igual a 5, se retorna un 1 y la expresión nos queda Si 1
entonces…si no…y como 1 se considera verdadero, tomamos el “entonces”,
e imprimimos “La suma es “suma” ”. Como suma es 15, quedará “La suma
es 15”. Como no hay ninguna otra instrucción seguimos al paso siguiente.
6) Fin.
Así sería la corrida del programa. Claro, que el usuario no vería nada de
eso, solamente pondría el número 5 y al siguiente milisegundo después de darle a
ENTER le saldría “La suma es 15”. Con algo de entrenamiento te acostumbrarás
a correr los algoritmos en tu mente con cierta rapidez (no tan rápido como la PC
por supuesto, no te hagas ilusiones). Si ya haz entendido todo hasta aquí, cuando
menos es posible decir que haz captado la naturaleza de la construcción de un
algoritmo estándar.
Ejemplo #3: Diga la suma de los números pares desde 0 hasta N (incluyendo N).
1. Inicio
2. Capturamos N.
3. Si (N < 0 | | N %2) entonces volver al paso 2.
4. suma += numsumado
5. Si numsumado = = N entonces imprimir “La suma es “suma” ”, si no,
numsumado += 2 y volver al paso 4.
6. Fin
4. Algoritmo: para saber si es primo tenemos que ver si se divide entre algún
número además de sí mismo y la unidad. Bueno, para eso tendríamos que
dividir “num” entre todos los números entre “num” y la unidad. En
consecuencia, por análisis de variables, nos damos cuenta de que
necesitaremos otra variable que vaya asumiendo los valores de todos los
números entre num y 1, para poder sacar el módulo entre num y esa
variable. Digamos que, además de num, declararemos esa otra variable
como numdiv (“número divisor”) diciendo int num, numdiv = 2;
Inicializamos numdiv en 2 porque lo “lógico” es que el primer número
entre 1 y Num es el 2. Num no lo inicializamos porque dentro de poco se
capturará su valor. Ahora bien, sabemos que es necesario dividir Num
entre todos los valores entre 1 y Num, o sea que de antemano sabemos que
iremos incrementando numdiv de uno en uno hasta que numdiv sea igual
num. Si en algún momento el módulo es cero, significa que el número no
es primo. Si por el contrario, los módulos no dan cero, numdiv irá
subiendo, y si llega a ser igual a num, entonces el algoritmo se desvía y
nos imprime “num es primo”.
1. Inicio
Si esto pasa, significa que ya dividimos entre todos los números entre num
y 1, y en consecuencia, el número es primo. O sea que, luego de decir +
35
5. Fin
1. Inicio
2. Capturamos num
3. Si num<=0 entonces ir al paso 2.
4. Si num % numdiv = = 0, entonces imprimimos “ “num” no es primo”. Ir
al paso 7.
5. ++numdiv
6. Si numdiv = = num, entonces imprimir “ “num” es primo”, si no, volver
al paso 4.
7. Fin
Ya eliminamos la posibilidad de que num sea negativo. Pero aún hay dos
casos no contemplados en el algoritmo: 1 y 2 son números primos. Son mayores
que cero, así que sobrevivirán el “filtrado del dominio” en el paso 2 (Y así debe
ser, ya que 2 y 1 están entre los positivos; no podemos andar filtrando todo lo que
nos moleste por ahí. Sólo debemos filtrar valores que verdaderamente estén
fuera del dominio del algoritmo que ejecutamos. El concepto de primo es
36
Hay diversas formas de resolver esta falla. La razón por la que el 2 se nos
escapa es porque la pregunta que hacemos en el paso 6 la hacemos demasiado
tarde. Si movemos esa pregunta a un lugar anterior al paso 4 podríamos resolver
el problema del 2. En ese caso, el paso 6 quedaría donde está el 4 y el paso 4 y 5
rodarían para abajo un espacio. Pero aún así, nos sigue interesando que, después
del módulo, verifiquemos si ya numdiv llegó a ser igual que num, para saber si
terminamos o no. Eso lo resolvemos poniendo, donde estaba el viejo paso 6
(ahora quedaría como el paso 7), una iteración que mande el algoritmo a la
pregunta “numdiv = = num”. Quedaría así:
1. Inicio
2. Capturamos num
3. Si num<=0 entonces ir al paso 2.
4. Si numdiv = = num, entonces imprimir “ “num” es primo, ir al paso 8.
5. Si num % numdiv = = 0, entonces imprimimos “ “num” no es primo”. Ir
al paso 8.
6. ++numdiv
7. Ir al paso 4.
8. Fin
A ver, el problema del uno es que como uno es menor que el primer
número con el que vamos a dividir, numdiv se incrementa pero nunca llega a
ser igual a num. Ahora bien, podemos observar un detalle curioso en esto…
el detalle de que estamos dividiendo entre un número mayor que num.
¿Cómo paso esto? Se supone que para determinar si es primo sólo teníamos
37
que dividir entre los números que estuvieran entre num y 1. Si sucede que
dividimos entre un número mayor que num ¿Qué significa esto? Recuerde
que si “numdiv” ya es igual a num, significa que ya hemos pasado por
todos los números entre 1 y num, pero aún mejor, si numdiv es mayor que
num, significa que estamos fuera del rango de numdiv, que ya nos
pasamos de num, y que fuera de toda duda no habrá ningún número más que
divida a num exactamente. (Ningún número al dividirse entre un número
mayor que él puede dar residuo cero). Por lo tanto, el mero hecho de que
numdiv sea mayor que num nos afirma al instante que num es primo. Fíjese
que casualmente esto sólo pasará si num es 1. Si el número es 2 o mayor (no
puede ser cero ni negativo) detectaremos en el cuarto paso si son iguales (al
instante en el caso del 2), pero en caso de que no lo sean al instante, en unas
cuantas iteraciones numdiv irá subiendo hasta alcanzar a num, pero nunca
llegará a ser mayor que num, ya que desde el instante en el que sean iguales,
el programa dice “ “num” es primo” y termina el algoritmo. Sin embargo,
está característica especial del uno nos ayuda a filtrarlo como un número
primo. Si en la condicional que decía “Si numdiv = = num, entonces
imprimir “ “num” es primo”, si no, ir al paso 8.” en vez de poner “= =”,
pondremos “>=”, ya que si numdiv llega a ser mayor que num, esto significa
que el número num es 1, y por lo tanto debemos imprimir que es primo. Por
otro lado, el viejo asunto de que si numdiv llega a ser igual a num sigue
estando contemplado en el signo “>=”, y si numdiv va subiendo hasta llegar a
num, el algoritmo dirá que es primo. Visto de esa manera, el algoritmo se
considera funcional.
1. Inicio
2. Capturamos num
3. Si num<=0 entonces ir al paso 2.
4. Si numdiv >= num, entonces imprimimos “ “num” es primo”. Ir al paso 8
5. Si num % numdiv = = 0, entonces imprimimos ““num” no es primo”. Ir
al paso 8.
6. ++numdiv
7. Ir al paso 4.
8. Fin
Si este algoritmo llega al paso 7, significa que numdiv aún es menor que
“num”, y que num % numdiv no fue igual a cero (esto lo deducimos porque si
llegó al paso 7, significa que sobrevivió las dos condiciones anteriores). En
consecuencia, aun no hemos determinado si el número era primo o no. Por eso
subimos ++numdiv y volvemos al paso 4, para preguntar nuevamente si numdiv
ya llegó a ser igual a num. El proceso se repite e inmediatamente numdiv llegue
a ser “num” o el módulo de num % numdiv sea 0, el programa dice si el número
es primo o no y termina su ejecución. O en el caso de que sea 1 o 2, el algoritmo
38
se da cuenta al instante de que numdiv es mayor que num(en el caso que num sea
1) o de que numdiv es igual que num al instante (en el caso del 2) y por lo tanto
imprime de inmediato que el número es primo. Esa es la idea del algoritmo.
Posiblemente, como este algoritmo es algo fuerte, usted puede pensar que
fue muy difícil, o que mis razonamientos fueron muy avanzados para lo que se
supone debe ser este manual (o sea, usted se pregunta como le va a llegar todo
eso a la cabeza en un primer parcial, por ejemplo). La verdad es que sólo con
muchas horas de práctica y lectura puede uno adquirir un nivel respetable en el
área de algoritmos. Más adelante tenemos unos problemas propuestos y sus
respectivas soluciones, pero antes de ello es necesario explicar el tema de
Diagramas de Flujo.
Diagramas de Flujo
puede contener una opción que se llame “en cualquier otro caso”.
A B C D otra
int num;
Inicio
num
El número “num” es
par
Fin
no
suma += numsumado
no
numsumado = = N ++numsumado
sí
La suma es “suma”
42
Fin
Inicio
no
sumapar += numsumado
no
numsumado = = N numsumado+=2
sí
La suma es “sumapar”
43
Fin
Aquí podemos ver una vez más la aplicación de las salidas de datos como
mensajes de ayuda y de solicitud de información. También la condicional posee
una condición compuesta por dos expresiones unidas por una OR ( | | ) como
habíamos mencionado cuando hicimos el algoritmo escrito. Las iteraciones
siguen siendo representadas mediante flechas que se devuelven al paso
correspondiente en la iteración.
Inicio
Inserte un número
num
no no
numdiv >= num num % numdiv = = 0 ++numdiv
sí sí
. Fin
un conector al que llamamos “A” por donde entramos, y luego, por el otro
conector “A” salimos y llegamos a la condicional. Es aconsejable que los
conectores nunca tengan nombres de variables.
Algo que es muy notable en este diagrama es que para las condicionales
que no tenían el “si no” en el diagrama escrito, como sencillamente lo que se
hace es seguir con el siguiente paso, entonces en el “no” del diagrama de flujo lo
que hacemos es poner la siguiente instrucción. De esta manera, en caso de que la
condición sea falsa, el diagrama sencillamente seguirá con la siguiente
instrucción, en ese caso, la instrucción del “no”.
Haciendo el análisis del problema (ver Pág. 42) el diagrama de flujo queda
de esta manera:
Inicio
cantdatos
A ++capturas no
dato
sumaprom += dato
46
no
cantdatos = = capturas A
sí
sumaprom /= cantdatos
Fin
En primer lugar, declaramos a cantdatos y a capturas como enteros, ya
que, por la lógica del problema, no pueden tener decimales (es decir, cómo se
imagina usted que el usuario nos dé 3 datos y medio, por ejemplo). Luego, como
la variable del dato a sumar y la suma o promedio en sí pueden tener decimales,
las declaramos como float. Ahora, la idea que se plasma en el diagrama de flujo
anterior es la siguiente:
Muchas veces uno ve ejemplos de sobra pero nunca sabe con certeza
contra que clase de muro se va a estrellar a la hora de la verdad. Así que para
finalizar este capítulo de algoritmos nos iremos contra el análisis de un ejercicio
que salió en mi quiz de diagramas de flujo. El quiz tenía exclusivamente este
ejercicio y por lo tanto la calificación del quiz era totalmente evaluada en ese
punto.
Ejemplo #6: ¿Cuáles son los divisores de “num” y cuántos de ellos son impares?
1. Entrada que nos dan: “num” (En este caso le pusimos ese nombre por
gusto y nada más)
3. Finalidad: encontrar todos los divisores exactos de num, o sea, todos los
números en los que el módulo de num y ese número es cero. Además
de eso nos interesa saber cuántos de ellos son impares (pero no tenemos
que imprimirlos de nuevo, solamente contar cuántos son). Para eso,
sabemos que el resultado del módulo del divisor con 2 debe ser 1(si el
divisor es impar). Por lo tanto, necesitamos dos variables más: una que irá
asumiendo los valores de todos los divisores desde 1 hasta num, y otra que
vaya contando cuántos de ellos son impares. Supongamos que llamamos a
esas variables “divisor” e “impares” respectivamente. Deberíamos
inicializar impares en cero (para comenzar a contarlos) y divisor en 0, para
poner al principio del ciclo de operaciones “++divisor”, y de esa manera el
primero número entre el que dividiremos da 1. Podríamos inicializar
divisor en 1, pero esto implicaría tener que poner el “++divisor” después
de las operaciones (para que divisor vaya asumiendo los valores desde 1
hasta num) y esto, en general, causa defectos en los algoritmos, o sea,
causa casos no contemplados, como en el ejemplo del algoritmo del
número primo, donde tuvimos que mover pregunta condicional que estaba
48
4. Algoritmo: como dijimos antes, luego del filtrado del dominio es bueno
que preguntemos de inmediato si ya hemos llegado a la finalidad del
algoritmo, para eso subimos divisor en uno poniendo divisor++ (como
está en cero, al subir valdrá 1) y luego preguntamos si ya divisor es mayor
que num (si divisor es mayor que num significa que ya pasamos de num y
en consecuencia ya sabemos todos sus divisores). Si esto es verdad,
imprimimos: “estos son todos los divisores de “num”. Tiene “impares”
divisores impares”. Si no, debemos hacer las operaciones de lugar para
determinar los divisores de num e irlos imprimiendo. Para saber si el
divisor actual es un divisor de num ponemos una condicional donde
preguntemos si num % divisor = = 0. O mejor aún, en vez de poner si
num %divisor = = 0, solamente ponemos num % divisor como la
condición y luego, en el si no de la condicional, ponemos lo que queremos
que haga cuando num %divisor sea igual a cero (Recuerde que la
condicional sólo tomará la parte del si no cuando al evaluar la expresión
de la condición el resultado sea cero). Como lo que queremos hacer es
imprimir los divisores, imprimimos que “ “divisor” es divisor de “num” ”.
list@ y que has entendido este tema, ya te encuentras en el buen camino para
superar este curso de algoritmos.
Inicio
num
sí
num <= 0 Debe ser mayor que cero
A no
++divisor
sí
divisor > num Estos son todos los divisores de “num”.
Tiene “impares” divisores impares.
no
no
sí
divisor %2 ++impares
50
no
“divisor” es un divisor
de “num”
Inicio
¿Cuántos datos va a
comparar?
cantdatos
sí
cantdatos<0 Debe ser mayor que cero
no
A
no
capturas<cantdatos El mayor es “mayor”
++capturas Fin
num
52
sí
capturas = = 1
no
sí
num>mayor mayor = num
no
A
Lógica del número mayor: pedimos la cantidad de datos que usaremos (cantdatos) y
filtramos que no sea menor que cero. Luego preguntamos si “capturas” es menor que
“cantdatos”, o sea, si aun nos faltan datos por capturar. “capturas” está inicializado en
0, así que será menor que cualquier número positivo la primera vez que pase por aquí.
Luego, se aumenta capturas en uno y capturamos el dato “capturas”, o sea, la primera
vez, como capturas es 1, dirá “Inserte el dato 1”. Luego preguntamos “¿capturas es
igual a 1?”, o sea, si este es el primer número. Si este es el primer número, el algoritmo
asume de inmediato que es el mayor, y prosigue pidiendo los otros datos. Por otra parte,
si este no es el primer dato (capturas no es igual a 1), el algoritmo compara el actual
mayor con el num. Si num es mayor que el mayor, entonces es porque num es el
mayor actual, y por lo tanto se le asigna a mayor el valor de num. (Note que ponemos
el valor de num dentro de mayor y no viceversa). Si no, no se hace nada más y se va al
paso en el que preguntamos si ya acabamos. Si capturas<num da falso (o sea, que ya no
nos faltan datos por capturar) se imprime quien es el mayor. En otro caso, capturas sube
en 1 nuevamente y se captura el siguiente dato.
A credstotal += creditos
letra , creditos
letra
A B C D F otra
53
no
totpuntos+=4*creditos credstotal
totpuntos+=3*creditos sí
Fin Su índice es
A “indice”
Lógica del índice calculado: el algoritmo captura la letra y los créditos, cada uno se
almacena en su correspondiente variable. El selector múltiple hace que se calculen los
puntos de acuerdo al valor de la letra y los créditos, y se van sumando en la variable
totpuntos. También se van acumulando los créditos en la variable credstotal. Si el
usuario inserta otra letra distinta a las calificaciones, el algoritmo revisa si los créditos
totales son cero. Esto lo vemos en la condicional que tiene credstotal adentro. Si
credstotal es 0, la condicional se va por el no, e imprime el valor por defecto de indice
que es 4.0. Si credstotal es diferente de cero, a indice se le asigna la división de
totpuntos/credstotal. Y luego se imprime el indice. Ese es el fin del programa.
Inicio
Inserte un número
num
A
++divisor
no
54
sí
El número “num” no es perfecto
no Fin
num%divisor sumfactors += divisor
sí
Inicio
num1, num2
prom =(num1+num2)/2
sí
(num1+num2)%2
55
Fin
Lógica de la mediana: los números que están entre el medio de dos números se
determinan fácil si sabemos el promedio. Si la suma es par, la mediana será
exactamente el promedio. Si la suma de los números es impar, las medianas son el
promedio y el promedio+1. Ejemplo: (2+6)/2 da 4; (2+7)/2 es 4 y 4+1 es 5.
Capítulo III: Introducción a la programación en C:
Funciones básicas
¡Hola mundo!
Hemos llegado al punto al que posiblemente muchos querían llegar… o al
que muchos tenían miedo de llegar. Así es…por fin comenzaremos a programar.
En realidad, la programación no es tan terrorífica como la pintan, ni tan difícil o
increíble de comprender o practicar. De hecho, lo más difícil es generar el
algoritmo en la mente, pero programarlo a nivel de código es más cuestión de
memoria que de práctica. O sea, aprender un lenguaje de programación no
requiere ningún esfuerzo de lógica (si la explicación es buena claro) pero
requiere una cierta facultad en la memoria (botella, ja ja ja) para recordar algunos
conceptos. Además, parte de lo que vamos a ver ya lo hemos visto, porque los
operadores fundamentales que vimos en el Cáp. I se usan de igual manera ahora.
dropped files”, “Show hidden line characters”, “Enhanced Home Key”, “Cursor
past EOF”, “Cursor past EOL”, “Double click line” y “Half Page Scroll”.
Además de eso, donde dice “Tab Size” en la esquina inferior derecha, ponga el
número 5. Lo demás se deja como está.
#include <stdio.h>
#include <stdlib.h>
Eso es una noción intuitiva del uso de una función, pero aún no hemos
explicado el código. A ver, comencemos desde arriba:
#include <stdio.h>
#include <stdlib.h>
Eso es lo que significan esas dos líneas: adjuntamos dos archivos llamados
stdio.h y stdlib.h que contienen las funciones printf y system, respectivamente.
Si no las ponemos, el programa no funciona porque no puede encontrar las
funciones en las librerías y no compila. Intente quitando eso del código. Primero
quite la primera línea dejando la segunda, luego hágalo al revés. Abajo, en el
59
reporte de errores al compilar, dirá “printf undeclared (first use this function).”
Este mensaje de error nos aparece cuando queremos usar una función y no hemos
incluido su librería. Para el segundo caso pasa lo mismo pero con system.
También sale el mismo error si usamos una variable no declarada, eso lo veremos
más adelante.
¿Tiene usted alguna idea de lo que pasa si quitamos esa parte del código?
Bórrelo manteniendo lo otro igual y compile el programa con F9. Dependiendo
de la velocidad de su PC, es posible que usted ni siquiera se de cuenta de que el
programa abrió. Esto se debe a que luego del printf, la máquina no se detiene, y
cuando se encuentra con return 0, el programa sencillamente se cierra. Esta ha
sido la causa fundamental de mucha frustración en los novatos de la
programación. Algunos manuales ignoran este comando, y cuando el pobre
novato está en su casa y ve que la ventana se abre y se cierra sola, solamente
piensa “¡Ay no…esto es muy difícil para mí!¡¡ Esto es para extraterrestres!!”. Y
así comienzan las historias. Por eso, para prevenir eso, ponemos la función
system con el argumento PAUSE entre comillas dobles.
printf(“texto a imprimir”,variable1,variable2,variable3………………);
reservadas del lenguaje ni tampoco ningún caracter especial del lenguaje (como
“#” ó “\”, por ejemplo). En general, deben ser alfa-numéricas.
Bien, por ejemplo, si usted coloca el siguiente código podrá ver el efecto
de printf. (Recuerde que arriba de todo esto deben estar incluidas las librerías
#include <stdio.h> y #include <stdlib.h>, o si no el programa fracasará. En la
mayoría de los ejemplos se asume que usted ya puso esto en la parte de arriba del
código.)
printf (“Jose tiene $%d, pero Vielka tiene $%d\n”, num, num+num2);
system(“PAUSE”);
return 0;
}
En este caso, se imprimirán los números 20 y 50, que son los capturados
para impresión entre los argumentos de printf.
printf (“José tiene %d años, pero Vielka tiene %d años\n”, num, num+num2);
system(“PAUSE”);
return 0;
}
Sin duda alguna, el resultado será un caracter sin sentido. Para corregir
esto, en los lugares donde van las letras irreconocibles debemos poner un %c y
luego, entre los argumentos de printf, colocar en su lugar correspondiente el
código ASCII de la letra deseada. Por ejemplo, el anterior, hecho de manera
correcta, queda así (El printf queda en una sola línea, el espacio me engañó):
El printf de más arriba debe quedar en una sola línea, aunque también es
válido en dos líneas si el último caracter escrito fue una coma que iba a separar
dos argumentos (como está arriba). En cualquier caso, la frase debe estar
correctamente impresa esta vez, ya que el código ASCII de la é es 130 y el de la
ñ es 164. Fíjese que los argumentos están exactamente en el orden en que son
capturados por la frase. Si ocurriese que la frase intentara atrapar más valores
que los que se pasan como argumentos variables, la frase atraparía un valor
alocado del computador. En el ejemplo anterior, repita lo mismo pero después de
donde dice “Vielka tiene %d a%cos” entre eso y el “\n”, escriba %d. Notará que
el printf no halla dónde está el valor que le piden atrapar, y simplemente pondrá
un valor alocado allí. Ese valor está en algún lado de la PC, quién sabe donde.
De todas formas, eso no nos sirve para nada, más que para no cometer ese error.
que nos dice que se esperaba un punto y coma antes de una palabra en específico,
aunque a veces también dice “after….”. En general, el 70% de los errores que
uno suele cometer son del tipo “expected “tal cosa” before…”. O sea, lo que el
error nos dice es que se esperaba tal o cual cosa en un lugar en particular, o sea
que lo más seguro es que se nos haya olvidado poner eso. Después de mucho
estrellarse contra la pared y de inventar mucho con el lenguaje uno aprende a
controlar esos errores. Pero es imprescindible saber leer el reporte de errores de
la compilación. Para probar esto y verlo visiblemente, pruebe en el ejemplo
anterior quitando el punto y coma que está después del printf. En mi ordenador
sale “expected ‘;’ before “system” ”. El error nos dice que se esperaba un punto
y coma antes de system, o sea, después del paréntesis del printf.
#include <stdio.h>
#include <stdlib.h>
Otro truco del printf es limitar las cifras decimales. Para el caso anterior,
en vez de poner %f, ponga % .2f, o sea, el porciento, un punto, la cantidad de
cifras decimales deseadas y luego la f. La diferencia es realmente notable.
O sea, donde dice variable tipo pondremos el símbolo % y la letra del tipo.
Por ejemplo, si vamos a capturar un entero, pondríamos %d. En cuanto a la
dirección de memoria de la variable, primero debemos aclarar algo.
#include <stdio.h>
#include <stdlib.h>
Por otra parte, en las otras combinaciones, como se trata de tipos de datos
que no se confunden entre sí, podemos capturarlos sin dejar ningún espacio. Por
ejemplo(recuerde incluir las librerías <stdio.h> y <stdlib.h>):
#include <stdio.h>
#include <stdlib.h>
68
variable = getch( ) ;
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
69
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
Un negociante tiene una fábrica de jeans y éstos se venden por cajas. La caja trae
13 unidades. Cada unidad cuesta $36.67. Cada cliente decide cuantas cajas debe
pedir y el programa debe decirle cuánto le costará su pedido. El precio tiene que
contemplar el 16% de ITBIS que se le cobrará al cliente por el pedido (que es lo
mismo que multiplicar el precio por 1.16). Además, luego de haber agregado el
16%, se le agrega a esto $5.00 por concepto de envío.
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
fflush(stdin);
b = (36.67*13*a);
c = b*1.16+5;
printf (“El precio total es $%f\n”,c);
system(“PAUSE”);
return 0;
}
Así está mejor, pero puede mejorarse cambiando los nombres de las
variables, y además de esto, no le caería mal uno que otro comentario sobre qué
es lo que está haciendo el programa. Los comentarios se ponen mediante el uso
de los caracteres /* y */ (ese slash es el normal, no el slash invertido). Si usamos
/*, todo lo que esté en el código a partir del signo se convertirá a un color azul
claro. Si cerramos el comentario con un */, solamente se pondrá azul lo que está
entre el “/*” y su correspondiente cerradura “*/”. También, se puede poner un
comentario poniendo “//” en cuyo caso solamente se pondrá azul el texto que esté
en la misma línea donde está el “//”. Se puede cerrar con “//”, pero incluso si lo
dejamos abierto solamente se pondrá la línea actual como un comentario.
Cuando ponemos algo como un comentario, lo que está como comentario es
ignorado por el compilador y no tiene ningún efecto sobre el programa. En
consecuencia, los comentarios nos sirven para poner “notitas” sobre el
funcionamiento del programa. Hay que saber usar los comentarios, ya que no
podemos andar comentando cualquier disparate del programa. Debemos
comentar cosas oscuras e intrigantes sobre el funcionamiento del programa, cosas
que no son obvias a simple vista. Por ejemplo, el ejercicio anterior queda mejor
si cambiamos las variables de nombre y lo comentamos de esta manera:
#include <stdio.h>
#include <stdlib.h>
int cantcajas;
float precioparcial,preciototal;
/*Variables para almacenar los precios parcial y total, según las cajas
pedidas*/
int cantcajas;
float preciototal;
75
Para los siguientes ejercicios, trate de desarrollar los programas en el código más
claro y explícito posible. Incluya comentarios, macros (constantes) y variables
según considere necesario. Trate de formular el código de la forma más efectiva
y clara. **Ver respuestas propuestas en las páginas siguientes. **
1. Realice un programa que pida dos números reales cualesquiera al usuario para
encontrar el promedio de los dos números.
$3.75, los chivos son a $12.50 los cerdos son a $15.25, las vacas a $22.00, y los
huevos de las gallinas son a $1.00. El granjero calcula el beneficio de la semana
calculando el precio de todas las gallinas que hay en la granja, y luego restándole
el precio de todas las que había al principio de la semana. Claro, que como el
único animal no es la gallina, debemos calcular la ganancia de todos los animales
durante la semana y sumarlas para obtener la ganancia neta de los animales.
Además de esto, a la ganancia total debemos restarle el precio de los alimentos
que se comieron los animales y debemos sumarle la ganancia por los animales
vendidos que ya no están en la granja. La explicación lógica que nos dice como
aparecen nuevos animales en la granja es que los animales que están en la granja
se reproducen, en consecuencia, puede haber más animales de un tipo a la
siguiente semana. Claro, que el granjero es quien debe insertar todos esos datos.
Haga un programa que calcule y al final imprima la ganancia que se obtuvo en
cada tipo de animal (y los huevos de las gallinas), e imprima también la ganancia
total, sabiendo que el granjero suministra los datos de los animales de la semana
pasada y la actual (incluyendo los números de huevos de gallina), el costo de la
comida durante esa semana, y las ganancias que la granja obtuvo por concepto de
vender animales (el granjero nos suministra este número).
Soluciones propuestas a los problemas de programación
Estas son mis soluciones, pero usted lo pudo haber hecho diferente y haberlo
hecho bien. Haga una comparativa y evalúe su trabajo. Para los printf que
quedan en dos líneas, considere que todo está escrito en una línea (lo que pasa es
que word no me permite frases tan largas en una sola línea).
Problema #1
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
//Este programa saca el cubo del valor que sea almacenado en num
#include <stdio.h>
#include <stdlib.h>
{
int parcial1=0,parcial2=0, quizes=0, final=0;
#include <stdio.h>
#include <stdlib.h>
gangallinas=(cantactual-cantantes)*VALGALLINA;
printf("\nInserte la cantidad de chivos de la semana pasada y la
actual\n”);
scanf("%d %d",&cantantes,&cantactual);
fflush(stdin);
ganchivos=(cantactual-cantantes)*VALCHIVO;
printf("\nInserte la cantidad de cerdos de la semana pasada y la
actual\n”);
scanf("%d %d",&cantantes,&cantactual);
fflush(stdin);
gancerdos=(cantactual-cantantes)*VALCERDO;
printf("\nInserte la cantidad de vacas de la semana pasada y la
actual\n”);
scanf("%d %d",&cantantes,&cantactual);
fflush(stdin);
ganvacas=(cantactual-cantantes)*VALVACA;
printf("\nInserte la cantidad de huevos de gallina de la semana pasada y la
actual\n”);
scanf("%d %d",&cantantes,&cantactual);
fflush(stdin);
ganhuevos=(cantactual-cantantes)*VALHUEVOS;
system("PAUSE");
return 0;
}
81
if( condición)
{
Lo que se hace si la condición es verdad
}
else
{
Lo que se hace si la condición es falsa
}
Nótese que la sintaxis de la sentencia if-else es exactamente igual a la de
un proceso en general (o sea, se abre con una llave y se cierra con otra). La única
diferencia es que el proceso está dividido en dos procesos, la parte del if, y la
parte del else. Además, por el hecho de tener la sintaxis de un proceso necesita
una tabulación adicional a la tabulación del main. Es decir, las llaves deben
quedar una sobre la otra, pero las instrucciones deben quedar en la columna que
está un TAB a la derecha. O sea, visto en el main quedaría así:
{
if ( condición)
{
Lo que se hace si la condición es verdad
}
else
{
Lo que se hace si la condición es falsa
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
printf(“Introduzca su edad\n”);
scanf(“%d”, &edad);
fflush(stdin);
if ( edad >= 18)
{
printf(“Usted es mayor de edad\n”);
}
83
else
{
printf(“Usted es menor de edad\n”);
}
system(“PAUSE”);
return 0;
}
printf(“Introduzca su edad\n”);
scanf(“%d”, &edad);
fflush(stdin);
if ( edad >= 18)
{
printf(“Usted es mayor de edad\n”);
}
system(“PAUSE”);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
system(“PAUSE”);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
scanf(“%d”, &peso);
fflush(stdin);
if ( peso >= 190)
printf(“Los gordos son bonachones\n”);
printf(“Usted es gordo\n”);
system(“PAUSE”);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
system(“PAUSE”);
return 0;
}
Primero tenemos el if que nos dice si el número es mayor o igual que 90.
Si eso es cierto, se imprime que saco una A, y luego se “salta” las demás
condicones y se termina el programa. Pero si no es así, se entra al primer else-if.
Este pregunta si “cuando menos” es mayor o igual que 80 (porque si llegamos
hasta ahí se deduce que el número ya no es mayor ni igual a 90). Si así es, el
número esta entre 80 y 89, por lo que imprimimos que saco una B. Para los
else-if que tienen más de una condición, también se cumple que se deben poner
las llaves si es más de una, es opcional ponerlas si es solamente una. El resto de
los else-if hacen lo mismo con 70 y 60, para luego poner un else generalizado (o
sea, si todo lo anterior fue falso, entonces se ejecuta esto). En caso de que no
hubiésemos puesto el else, la condicional sencillamente entiende que como no se
cumplió ninguna condición, no hay nada que hacer (igual que una condicional
normal). En este caso, si llegamos hasta el else, signifca que la nota es menor
que 60, ya que ninguna de las anteriores se cumplió, y en consecuencia le
imprimimos una F (con sus respectivas condolencias por supuesto).
En una última nota con respecto a if-else y else-if, note claramente que la
sintaxis del proceso NO lleva punto y coma al final. Al igual que el proceso
main y todos los procesos, solamente se colocan llaves al inicio y llaves al final.
Solamente las instrucciones (que todavía no podemos definir de manera exacta
más que por pura observación) llevan punto y coma al final.
Con respecto al ternario no creo que haya mucho que decir sobre como
funciona, ya que habíamos visto su sintaxis durante el capítulo I. Más bien,
ahora aplicaremos su sintaxis para algunos usos. Un ternario es casi igual a un if,
con la excepción de que el ternario no se puede omitir la parte que va después de
los dos puntos (como en el if, donde podemos obviar el else):
El switch lo que hace es que obtiene una variable que nosotros le pasamos como
argumento, y luego lo compara en orden descendente con diversos casos que
nosotros hemos puesto en el código. Si la variable no es igual a ninguno de los
casos anteriores, al final (opcionalmente) se coloca un default: que es un caso al
estilo “Ninguna de las anteriores”, donde se ejecuta un código si ninguna de las
otras opciones fueron válidas con respecto al valor de la variable.
switch(variable)
{
case opción1:
Instrucciones a ejecutar si variable es igual a opción 1
break;
case opción2:
Instrucciones en caso de que variable sea igual a opción2
break;
…………………………………………………………………………
default:
Instrucciones si variable no es ninguna de las anteriores
break;
}
En la sintaxis de switch podemos notar una nueva instrucción llamada
break; Esta instrucción se encarga de que, luego de que hayan sido ejecutadas
las instrucciones de un caso en específicio, el break “rompe” la continuidad del
switch y se sale de las llaves para que se siga ejecutando el código que está
después de la llave de cerradura del switch. Si no ponemos la palabra break;
después de las instrucciones de los casos, aunque el switch elija la opción1 como
la opción correcta, como no existe el break; se ejecutarán las instrucciones del
primer caso y además también se ejecutarán las de los casos siguientes, hasta que
el código encuentre un break; o encuentre la llave de cerradura del switch. Para
verlo más práctico, utilizemos este ejemplo.
#include <stdio.h>
#include <stdlib.h>
88
switch(X)
{
case 12:
printf(“En este caso X es igual a 12\n”);
break;
case 24:
printf(“En este caso X es igual a 24\n”);
break;
default:
printf(“En este caso X es distinta de 12 y 24\n”);
break;
}
system(“PAUSE”);
return 0;
}
En el ejemplo anterior inicializamos int X = 12; para que el código elija el
primer caso. Varíe el valor de X en el código cambiándolo a 24, y después con
otro número que no sea 24 ni 12, y compile nuevamente. Verá que se eligen las
diferentes opciones de acuerdo al valor de X.
printf("Inserte su edad para saber quienes tienen la misma edad o menos que
usted:\n");
89
scanf("%d", &usuario);
fflush(stdin);
switch(usuario)
{
default:
if(usuario>19)
printf("Usted es m%cs viejo que:\n",160);
else
printf("Usted es m%cs joven que:\n",160);
case 19:
printf("Tarafa\n");
case 18:
printf("Yaritza\n");
case 17:
printf("Rossi\n");
case 16:
printf("Yamilka\n");
}
system("PAUSE");
return 0;
Analicemos el ejemplo anterior. En primer lugar tenemos el default que
es la opción que se activa cuando todos los otros casos son falsos,
independientemente de su posición. Luego en cada caso ponemos el nombre de
los estudiantes, según su edad. Como no hay break; si la edad del usuario es 19,
18, 17 ó 16, la máquina se ubica en su caso correspondiente y comienza a
imprimir todo lo que hay de ahí en adelante (debido a que no hay break;). Como
están ordenados descendentemente, la máquina imprimirá el que es igual en edad
y todos los que son menores que él. Por otra parte, si el número no es ninguno de
los anteriores, el switch recurre a su última alternativa que es el default, que por
conveniencia está colocado arriba. Dentro del default hay una condicional que
pregunta si el usuario es mayor de 19 años (o sea, mayor que el mayor). En ese
caso, se imprime “Usted es más viejo que:” debido a que como no hay un break
después del default, el código sigue e imprime los nombres de todos los
estudiantes. Sí no es así (o sea, que el número es menor que 19), sabemos que,
como ya todos los casos fueron falsos (por eso fue que caímos en el default) el
número no es ni 19, ni 18, ni 17, ni 16, ni mayor que 19. En consecuencia, el
número debe ser menor que 16. Por ello, en el else de esa condicional ponemos
“Usted es más joven que”. Como no hay break en lo adelante, se imprimen los
nombres de todos los estudiantes. Y así es como funciona el programa.
En una nota final sobre el uso de switch, los valores de las opciones deben
ser de tipo char o de tipo int, así como la variable que se está evaluando en el
switch. No funciona con float ni con double. En verdad, la función switch no es
muy usada, pero de todas formas hay que explicarla. Además, la instrucción
break; si se usa, y de hecho es muy importante, o sea que al fin y al cabo la
explicación ha valido la pena.
Estas son todas las sentencias condicionales que veremos por el momento.
#include <stdio.h>
#include <stdlib.h>
Otras variantes del for pueden ocurrir usando más de una condición, más
de un incremento, o incluso más de un contador. Por ejemplo:
Una vez más, antes de acabar con el for, recuerde que su sintaxis es la de
un proceso (se abre con llaves, no lleva punto y coma, bla bla bla…).
Además, tenga especial cuidado de no crear ciclos infinitos tal como:
En ese caso i subirá uno mientras i sea mayor que 5, como i comenzó en
10 el ciclo nunca termina. Ocurre un error en tiempo de corrida y el programa se
cierra. En caso de que algún día usted desee hacer un ciclo que vaya
disminuyendo el valor de i, recuerde poner i--. En este caso, con i-- el ciclo ya
no es infinito.
Además del for, existen dos sentencias más para la creación de ciclos.
Estas sentencias son while… y do…while…. La sintaxis de while es la
siguiente:
while (condición)
{
Instrucciones a ejecutar en el ciclo
}
En este sentido, la sintaxis del while es bastante sencilla. Pero hay que ser
cuidadoso con su uso. La condición es cualquier expresión que retorne un valor,
y entre llaves se ponen las instrucciones del ciclo. Al igual que en las otras
sentencias, si solamente hay una instrucción, el uso de llaves es opcional. El
ciclo se repetirá mientras (“while” traducido desde el idioma del imperio) la
condición sea verdadera (distinta de cero). Al igual que en el for, primero se
pregunta cuál es la condición, si la condición es verdadera, el ciclo entra y se
ejecuta, si la condición es falsa, el ordenador se salta el ciclo y continúa con las
otras instrucciones. Como ejemplo, usaremos lo mismo que hicimos con for,
pero lo haremos con while:
#include <stdio.h>
#include <stdlib.h>
{
printf (“Ahora mismo el ciclo va por el n%cmero %d\n",163, i);
i++;
}
system(“PAUSE”);
return 0;
}
Note que como hay más de una instrucción, utilizamos las llaves.
Además, observe que una de las instrucciones del while es i++; y que en la
declaración de variables a i le asignamos el valor de 0. Esto es porque a
diferencia del for, el while no tiene una sección en la que se incremente el valor
de i, ni tampoco tiene una asignación. Si no ponemos el i++, como i nunca
cambia de valor, el ciclo se volvería infinito. Por otra parte, si no inicializamos i
en cero, como no sabríamos el valor de i, el programa podría arrojar resultados
inesperados.
do
{
Instrucciones a ejecutar durante el ciclo
} while (condición);
#include <stdio.h>
#include <stdlib.h>
do
{
printf (“Ahora mismo el ciclo va por el n%cmero %d\n",163, i);
i++;
}while( i<12);
system(“PAUSE”);
return 0;
}
Solamente se imprimirá “Ahora mismo el ciclo va por el número 15” y el
ciclo terminará, porque la condición i<12 es falsa y en consecuencia el ciclo no
se repite otra vez. Ahora bien, note que, a diferencia de todas las demás
sentencias, do…while… SI lleva punto y coma después del paréntesis de la
condición del while. Y ese punto y coma debe ir necesariamente ahí. Si no se
pone el punto y coma, cuando compilamos el programa nos sale un error que nos
dice que se esperaba un punto y coma en ese lugar. Vaya, vaya… para
molestarnos la existencia, el do…while…necesita ese punto y coma. Esto se
debe a que el do…while… es una combinación de un proceso con una
instrucción. Nótese que las llaves del proceso cierran antes del while, no
después. Eso es curioso, considerando que while es un proceso…pero lo que
pasa es que la combinación do…while…hace que ese while se comporte como
una instrucción, en vez de cómo un proceso. Bueno, esas son algunas de las
peculiaridades del C.
do
{
printf ("\nInserte un n%cmero par\n", 163);
scanf ("%d", &n);
fflush (stdin);
if( n<0 || n%2)
printf("\nDebe ser par y no negativo\n");
}while( n<0 || n%2);
era que se repitiera hasta que numsumado fuera igual a n, en código colocamos
como condición que queremos que se repita mientras (“while”) numsumado sea
menor o igual al número, ya que esta es la idea del algoritmo (ir sumando los
numeros pares menores que el número n) y luego de eso se incrementa
numsumado en 2. En este caso (y en la mayoría) las instrucciones quedaron en el
mismo orden que en el diagrama de flujo, salvo porque la condición ocupo su
lugar al lado de while.
#include <stdio.h>
#include <stdlib.h>
programa está bien tabulado, solamente las instrucciones que están un TAB a la
derecha de la columna del inicio de un proceso pertenecen a dicho proceso. O
sea, como el printf está 2 TABS a la derecha, está en otro nivel. La única
instrucción que realmente pertenece al primer for es el segundo for. Para
demostrar esto, vamos a reeditar el código poniendo todo igual con excepción de
las llaves. De esa manera el código queda así:
#include <stdio.h>
#include <stdlib.h>
system("PAUSE");
return 0;
}
printf que dice printf (“%c%c\n”, i, j); Como le estamos haciendo %c lo que se
va a capturar es el caracter en sí de cada variable (no el ASCII). Como i
actualemente vale ‘A’ y j vale ‘1’, se imprime “A1”, y ponemos un retorno de
carro ‘\n’ para avanzar a la siguiente línea. Luego, como aún estamos dentro del
segundo ciclo, aumentamos el incremento que dice j++, y volvemos a preguntar
la condición del segundo ciclo. Ahora j valdrá ‘1’ + 1, y como el caracter uno
tiene el número ASCII 49, 49 + 1 da 50, y el 50 es el número ASCII del caracter
‘2’. Recuerde, que ‘1’ + 1 no es ‘2’ porque se hayan sumado algebraicamente,
sino porque al sumar uno al ASCII de cualquier caracter obtenemos el siguiente
caracter, y como los caracteres de los números están consecutivos, ocurre esta
“coincidencia”. Pero para sacarle de dudas, ‘9’ + 1 no da ‘10’, sino que da ‘:’, el
caracter dos puntos, que se encuentra después del nueve.
#include <stdio.h>
#include <stdlib.h>
if (x<1000)
if(x%2)
101
if(x>50)
printf (“Si este letrero sale, x es menor que 1000, es
impar, y adem%cs es mayor que 50\n”,160);
system("PAUSE");
return 0;
}
Encabezado: Haga un programa que imprima una cruz en pantalla sabiendo que
la dimensión de la cruz debe ser impar, y que el mínimo tamaño de la cruz
posible es 1.
Ejemplos de la corrida:
+ +
+ ++ +
+++++ +
102
+
+
dimensión = 1
dimensión=3
dimensión=5
dimensión=7
0
0 +
0 1 2
0 +
1 + + + 0 1 2 3 4
2 + 0 +
1 +
2 + + + + + 0 1 2 3 4 5 6
Para la lógica del 3 + 0 +
algoritmo gráfico 4 + 1 +
debemos, una vez 2 +
construida la matriz, encontrar cuál es la relación 3 + + + + + + +
de la posición del elemento con respecto a su 4 +
contenido. O sea, saber en que lugares de la 5 +
pantalla debemos imprimir una cruz para formar 6 +
la cruz completa. El análisis por casos es en
cierta manera un método de tanteo, pero más estrictamente hablando, es un
103
método de prueba y error. Más que adivinar por tanteo la fórmula, lo que
haremos es analizar las gráficas para saber cuál es la relación algorítmica entre
ellas y la variable de la dimensión, ya que al fin y al cabo lo que buscamos es una
relación entre la dimensión y la forma que se imprimen las variables. Podemos
identificar las posiciones en cada matriz como una coordenada (i , j), donde i es
la fila horizontal en la que se encuentra un elemento y j es la columna vertical
donde está. Por ejemplo, en la matriz de dimensión = 5, la segunda
letra ‘i’ de la palabra “posición” en este mismo párrafo está justamente debajo de
la posición (4, 2) de esa tabla, la cual contiene una cruz. Bien, sabiendo
identificar las posiciones, debemos determinar cuál es la relación entre las
posiciones que tienen una cruz en las diferentes dimensiones. Para ello,
asumimos que las casillas que no tienen nada lo que contienen es un “espacio” y
en consecuencia no vemos nada en esos lugares. Pero aún así, las posiciones en
las que están los espacios “existen” y por lo tanto, son parte de la gráfica. Claro,
que solamente tenemos que averiguar cuál es la relación entre las posiciones de
las cruces, y las posiciones de los espacios sencillamente serán el resto de los
lugares donde no hay cruces.
0
0 +
0 1 2
0 +
1 + + + 0 1 2 3 4
2 + 0 +
1 +
Por ejemplo, vamos a 2 + + + + + 0 1 2 3 4 5 6
tomar la matriz de la 3 + 0 +
dimensión = 5. Lo 4 + 1 +
primero que notamos 2 +
es que las posiciones (i, j) van desde 0 hasta 4 en 3 + + + + + + +
ambos ejes. Este es el recorrido de la gráfica, o 4 +
sea, la forma del cuadrado (o rectángulo) que 5 +
contiene la figura. Lo primero que debemos 6 +
expresar en términos de la dimensión es el
recorrido. Por ejemplo, podemos decir que generalizadamente, el recorrido va
desde 0 hasta la dimensión - 1 en ambos ejes. Verificamos con otra gráfica
además de la dimensión = 5, y nos damos cuenta que con 7 el recorrido llega
hasta 6, y en el de 3 llega hasta 2. Finalmente, para asegurar la prueba,
verificamos el extremo inferior. Cuando la dimensión es 1, el recorrido en ambos
104
0 1 2
0 +
1 + + + 0 1 2 3 4
2 + 0 +
1 +
2 + + + + + 0 1 2 3 4 5 6
Continuando con la 3 + 0 +
prueba y el error, 4 + 1 +
revisamos otro tipo de 2 +
fórmulas que relacionen las posiciones de las 3 + + + + + + +
cruces. Otra idea, por ejemplo, es que la suma de 4 +
las coordenadas sea una constante. A ver, en la 5 +
matriz cinco tenemos una cruz en (2, 2) y la suma 6 +
de 2+2 es 4. Tomamos otra posición, pero
rápidamente notamos que en la posición (2,4) hay una cruz, y la suma de 2+4 es
6. Esa relación queda descartada. Proseguimos con la siguiente relación. Una
relación puede ser que la posición de la cruz está ubicada donde al menos la fila o
la columna es la mitad de la dimensión. Esto también se ve claramente en la
matriz de dim = 5. 5/2 da 2(división entera). Y es claro que hay cruces en los
lugares donde al menos i ó j es igual a 2 (o ambos, como sucede en (2,2) ).
Entonces pasamos a revisar las otras matrices. En la matriz dim = 7, también es
notable que hay cruces solamente en los lugares donde al menos la fila ó la
105
columna es igual a 7/2, o sea 3. Pasamos a ver la matriz de dim = 3. Como 3/2
da 1, también se cumple la relación. Finalmente, evaluamos nuestra hipótesis en
el extremo inferior. Como 1/2 da 0, inmediatamente afirmamos nuestra hipótesis
como válida, y asumimos que se cumplirá para todo dim entero que sea mayor
que cero. Habiendo sacado la fórmula de la ubicación de las cruces, ya todo lo
que queda es la creación del programa, que, para fines de los algoritmos gráficos,
tienen una forma estándar si ya sabemos cuál es el recorrido y cual es la relación
de las cosas en la gráfica.
Esta relación, al igual que casi todas las relaciones gráficas, pueden ser
representadas mediante un bucle anidado de dos dimensiones (i ,j), donde la
parte más externa del ciclo controla los valores de i, y la parte más anidada
(interna) del ciclo controla la variable j. Los ciclos deben ser preferiblemente
representados mediante la instrucción for del lenguaje C, para mayor claridad y
comodidad al codificar. También las variables i y j suelen llamarse fila y
columna, respectivamente. Pero también es convencional llamarlas i y j, como
usted posiblemente habrá notado desde que ha estado estudiando los bucles en
este capítulo. El código que suele usar para los algoritmos gráficos es el
siguiente:
#include <stdio.h>
#include <stdlib.h>
printf(“\n”);
system("PAUSE");
return 0;
}
106
Por otra parte, dentro del mismo segundo ciclo tenemos una condicional
que imprime dos tipos de caracteres: “caracter deseado en la gráfica” que en
nuestro caso es la cruz (“+”) y el otro es un espacio. La condición de relación de
la gráfica es la que determina cuál de los dos se imprime. Si es verdadera, se
imprime el caracter deseado. Si no, se imprime un espacio. En ambos casos, el
cursor se mueve un espacio a la derecha, por lo que “mentalmente” debemos
aumentar j para tomar en cuenta que estamos una columna una unidad más a la
derecha que antes. ¡Pero…! ¡Ufff! Como inmediatamente después de que se
imprime el caracter el segundo ciclo se termina (recuerde que ese printf(“\n”)
pertenece al primer ciclo) el ordenador aplica el incremento en j, con lo cual
mantenemos la concordancia entre el valor de j y la posición del cursor en la
pantalla.
#include <stdio.h>
107
#include <stdlib.h>
printf(“\n”);
system("PAUSE");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
do
{
printf ("\nInserte la dimensi%cn de la cruz\n", 162);
scanf ("%d", &dim);
fflush (stdin);
if( dim <= 0 || !(dim%2))
printf("\nDebe ser un n%cmero impar y postivo\n",163);
} while (dim <= 0 || !(dim%2));
printf(“\n”);
#include <stdio.h>
#include <stdlib.h>
do
{
printf ("\nInserte la dimensi%cn de la cruz\n", 162);
scanf ("%d", &dim);
fflush (stdin);
if( dim <= 0 || !(dim%2) || dim>79)
printf("\nDebe ser un n%cmero impar y positivo menor que
79\n",163);
109
Recuerde que el printf debe quedar en una sola línea. Nota: ¡Ah! Y
excúsame si fui demasiado irritante diciendo “mentalmente” como cien veces
seguidas.
#include <stdio.h>
#include <stdlib.h>
i++;
}
system(“PAUSE”);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
{
printf (“Ahora mismo el ciclo va por el n%cmero %d\n",163, i);
}
system(“PAUSE”);
return 0;
}
Separar con comas los argumentos del for: este error me sucede la
mayoría del tiempo, pero creo que ya aprendo a controlarlo. Sucede que los
argumentos del for van separados por punto y coma, y uno regularmente está
acostumbrado a separar los argumentos del printf y el scanf mediante comas.
Pero este error no es tan dañino porque el reporte de errores lo detecta, así que no
hay mucho más que decir con respecto a eso.
No usar llaves cuando hay más de una instrucción: muchas veces las
personas se olvidan que tanto para los ciclos como para las condicionales si hay
más de una instrucción las llaves son obligatorias. Si no se ponen, el ciclo
asume que solamente la siguiente instrucción al ciclo forma parte de él. Observe
bien cuántas cosas desea que pertenezcan al ciclo o la condicional, y luego
determine si las llaves son necesarias o no.
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
113
while (i == 'A')
{
printf ("Pulse la A may%cscula para repetir esto otra vez\n",163);
i = getch ( );
}
system ("PAUSE");
return 0;
}
Para el ciclo anterior, mientras se pulse la tecla ‘A’ con el CAPS LOCK
activado el ciclo se repetirá (recuerde que el ASCII de las mayúsculas y las
minúsculas son distintos). Si por el contrario, en vez de decir i ==‘A’
hubiésemos puesto i = ‘A’, en cada ciclo a i le asignaríamos ‘A’, y como ‘A’ es
distinto de cero, el ciclo sería siempre verdadero sin importar la tecla pulsada que
haya sido capturada en el getch (Nótese que incluimos la librería conio.h para
poder usar el getch). Por ahora, eso es todo lo que hay que decir sobre bucles y
condicionales.
x x x x x
x x x x
x x x
x x x x
x x x x x
x x x x x x x x
x x x x
x x x x
x x x x
x x x x
x x x x
Para dim = 5 x x x x
x x x x x x x x
Para dim = 8
x x x x x x x x x
x x x x
x x x x
Para dim = 9
x x x x
x x x
x x x x
x x x x
x x x x
**Ver soluciones páginas siguientes** x x x x x x x x x
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
dias = deposito = 0;
if(deposito<=0)
printf("\nDebe ser mayor que cero\n");
}while(deposito<=0);
do
{
printf("\nPor favor inserte la cantidad de d%cas que desea dejar
el dep%csito\n", 161, 162);
scanf("%d",&dias);
fflush(stdin);
if(dias <= 0)
printf("\nDebe ser mayor que cero\n");
}while(dias <= 0);
}while(continuar=='S' || continuar=='s');
/*Si continuar es ‘s’ se repite todo desde aquel do que vimos al principio, si no,
es porque continuar es ‘n’, y por lo tanto se sale del ciclo*/
117
system("PAUSE");
return 0;
}
Problema #2
#include <stdio.h>
#include <stdlib.h>
do
{
printf ("\nInserte la dimensi%cn\n", 162);
scanf ("%d", &dim);
fflush (stdin);
if( dim <= 0 || dim>80)
printf("\nDebe ser un n%cmero positivo menor que
79\n",163);
} while (dim <= 0 || dim>80);
printf(“\n”);
118
Para motivarle, déjeme decirle que este tema es bastante simple y breve,
por lo que no tomaremos mucho en explicarlo. Sin embargo, su importancia es
increíble.
119
Estas son técnicas lógicas que podemos usar para la detección de errores.
Pero existe una herramienta dentro del mismo C que nos puede facilitar la
detección de errores: el depurador.
#include <stdio.h>
#include <stdlib.h>
do
{
printf(“Inserte la base y el exponente a elevar\n”);
scanf(“%d %d”, base, exponente);
fflush(stdin);
while( exponente>0)
resultado*=base;
exponente--;
}while(base==0 || exponente<0);
Revisamos los watches y vemos que las variables tienen el mismo valor que
tenían en la inicialización, así que por ahí no hay problemas. Le seguimos dando
a “next step”, pero como el scanf espera unos datos, el programa se queda en
espera de los datos y no pasa de esa línea. Entonces tomamos el programa y le
insertamos los datos. Inmediatamente presionemos enter, en el Dev-C++ sale el
mismo mensaje de error, e incluso es posible que la plataforma se cierre
automáticamente. Pero finalmente ya sabemos donde está el error. Sin duda,
debe estar en la línea del scanf, ya que es la última línea que siempre ejecutamos
y nos da problemas. Revisamos el código y tenemos que…
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
srand(time(NULL));
125
num = rand();
printf("El n%cmero generado es %d\n", 163, num);
system("PAUSE");
return 0;
}
rand () % ( 42 - 25 + 1) + 25;
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
srand(time(NULL));
num = rand( ) % ( 42 - 25 + 1) + 25;
printf("El n%cmero generado es %d\n", 163, num);
system("PAUSE");
return 0;
}
126
#include <stdio.h>
#include <stdlib.h>
#include <conio.c>
127
textcolor(GREEN);
textbackground(YELLOW);
textcolor(LIGHTGRAY);
textbackground(BLACK);
system("PAUSE");
return 0;
}
Declaraciones de funciones
Proceso main
Fíjese que para declarar la función se usa punto y coma al final. Además,
hay otro tipo de valor de retorno que no conocíamos hasta ahora. Hasta ahora
habíamos visto char, short, int, long, float y double. Pero existe otro tipo de
datos llamado void. La palabra void (ya usted sabe en cual idioma) significa
vacío en español. El tipo void se usa cuando no queremos que la función retorne
nada, o si no queremos pasarle ningún argumento. Por ejemplo, si hacemos una
función llamada holamundo que imprimer “Hola mundo\n” en la pantalla, no
retorna nada, y tampoco es necesario pasarle ningún argumento, la declaración de
la función sería:
void holamundo(void);
129
El tipo de dato void refleja un dato sin tipo. Por lo tanto, no es posible
asignar un retorno de tipo void a una variable. Las funciones void simplemente
se ponen en el código para que hagan algo, pero sin asignar su valor a nadie.
return 0;
}
O sea, la sintaxis del main es la de una función, con la salvedad de que los
argumentos del main siempre son int argc y char *argv []. Por eso podemos
definir nuevas funciones de la forma que deseemos. Bien, como seguro habrás
notado, también usamos tabulación en el cuerpo de la función, y dejamos una
línea por el medio entre las declaraciones y el código. En el código hacemos las
operaciones necesarias, y luego de que obtengamos un resultado, ponemos ese
valor con return valorderetorno. Lo que sea que pongamos después del return
es lo que la función retornará cuando termine. Podemos poner un número, una
130
#include <stdio.h>
#include <stdlib.h>
1) Inicio
2) Capturamos num1 y num2
3) Si num1<= 0 || num2 <= 0 entonces volver al paso
4) Si num1 = = num2 entonces num1 (ó num2) es el m.c.d. Ir al paso 7.
5) Si num1 > num2 entonces num1-= num2 y volver al paso 4.
6) Si num1 < num2 entonces num2 -= num1 y volver al paso 4.
7) Fin
O sea, si tenemos dos números cualquiera, primero vemos si son iguales, en cuyo
caso el m.c.d es cualquiera de los dos. Si no son iguales, entonces preguntamos
quién es el mayor, y luego, al mayor se le asigna su propio valor menos el valor
del menor. Si num1 es el mayor, entonces a num1 le asignamos su propio valor
menos el de num2. Si num2 es el mayor, a num2 le asignamos su valor menos
num1. Luego de esto, en ambos casos volvemos a preguntar si ahora son iguales.
El ciclo se repite hasta que sean iguales. El algoritmo tiene un filtro para
asegurar que los números no sean negativos ni cero, pero eso lo pondremos en el
main. Luego de haber elaborado el código, tendríamos:
#include <stdio.h>
#include <stdlib.h>
do
{
printf(“\nPor favor inserte los dos n%cmeros para hallar su
m.c.m\n”,163);
scanf(“%d %d”, &dato1, &dato2);
fflush(stdin);
if (dato1 <= 0 || dato2 <= 0)
printf(“\nAmbos deben ser mayores que cero\n”);
}while(dato1<=0 || dato2<=0);
/*
Función: mcd
Objetivo: Determinar el máximo común divisor de dos números
Argumentos: los dos números (num1 y num2) de tipo int
Retorno: su máximo común divisor (tipo int)
*/
int mcd ( int num1, int num2)
{
while (num1!=num2)
if ( num1 > num2)
num1 -= num2;
else
num2 -= num1;
return num1;
}
void holamundo(void);
Hay diversas categorías dentro de estas ramas, pero para nuestros fines
solamente consideraremos dos:
2. Realice la función
137
void calificacion (int calif1, int calif2, int calif3, int valcal1, int valcal2, int
valcal3, int valfinal) donde el usuario proporciona tres calificaciones que, junto
con el examen final, valen 100 puntos entre todas. El usuario proporciona el
valor de cada calificación con respecto a los 100 puntos totales. El objetivo de la
función es decirle al usuario cuánto necesita sacar en el final para obtener cada
letra. Un ejemplo de la corrida de esta función sería:
Luego el usuario inserta 53, 71, 100, que son las calificaciones de sus tres
parciales (por ejemplo), y luego inserta 20 (lo que vale el primer parcial con
respecto a los 100), luego otro 20 (el valor del segundo), luego otro 20 (el
tercero), y finalmente inserta 40, que es el valor del examen final. Luego diría:
O sea, el programa le dice lo que debe sacar para obtener esa letra. En
este caso, es imposible que el individuo tenga una A ya que basado en los
cálculos matemáticos, tendría que sacar 115 en el final, pero eso es imposible. El
programa debe alertar al usuario sobre está situación. Realmente esta función
requiere más aplicación de la matemática que hay en su cabeza que de la
programación, pero considere que eso debe hacerlo más fácil para usted
(considerando que usted está aprendiendo a programar desde ahora, mientras que
las matemáticas las viene viendo desde Kinder). Como nota final, filtre que los
argumentos no sean menores que cero ni mayores que 100, ni la suma de los
valores puede ser distinta de 100. Para los cálculos, el programa no hace
redondeo. Si desea haga un main donde llame a la función.
Problema #1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
system("PAUSE");
intentos = adivinar( );
printf("\nAdivinaste en %d intentos\n", intentos);
system("PAUSE");
return 0;
}
int adivinar(void)
{
int numero, intento = 0, jugadas = 0;
srand(time(NULL));
numero = rand( )%200+1;
do
{
jugadas++;
system(“cls”);
printf(“\nIntente un n%cmero\n”, 163);
scanf(“%d”, &intento);
fflush(stdin);
if (numero < intento)
printf(“\nEl n%cmero es menor que %d\n”, 163, intento);
if (numero > intento)
printf(“\nEl n%cmero es mayor que %d\n”, 163, intento);
if (numero == intento)
{
printf(“\Nadivinaste!!!\n”);
return jugadas;
}
system(“PAUSE”);
}while(numero != intento);
}
Este programa genera el número y lo pregunta hasta que el usuario
adivine. Nótese que el return esta vez no estuvo al final. Pero eso no importa,
sin importar donde esté el return, cuando la función encuentre el return se cierra
y continúa con el main. Para hacer el programa un poco más limpio, le puse
system(“cls”) al inicio del do. Además, le puse que imprimiera el número de
jugadas en el que se ganó. No comenté la función…pero en caso de una tarea
real recuerde que debe comentarla tal como indiqué durante este capítulo. Otra
cosa a decir es que en los parciales de algoritmos usted hará funciones, pero no
tiene que hacer el main ni nada de eso. Lo hacemos aquí para ver la función
corriendo, pero nada más. Para el siguiente problema, solamente haré la función,
observando que la función tenga un filtro integrado para que si algún argumento
es menor que cero, la función se salga.
139
Problema #2
void calificacion (int calif1, int calif2, int calif3, int valcal1, int valcal2, int
valcal3, int valfinal)
{
int acumulado, paraA, paraB, paraC, paraD;
acumulado=(calif1*valcal1+calif2*valcal2+calif3*valcal3)/100;
paraA=( 90-acumulado)*100/valfinal;
paraB=( 80-acumulado)*100/valfinal;
paraC=( 70-acumulado)*100/valfinal;
paraD=( 60-acumulado)*100/valfinal;
if (paraA>100)
printf("\nEs imposible que usted saque una A, a menos que ocurra
un milagro\n");
else
printf("\nUsted saca una A si saca %d en el final\n",paraA);
if (paraB>100)
printf("\nEs imposible que usted saque una B, a menos que ocurra
un milagro\n");
else
printf("\nUsted saca una B si saca %d en el final\n",paraB);
if (paraC>100)
printf("\nEs imposible que usted saque una C, a menos que ocurra
un milagro\n");
else
printf("\nUsted saca una C si saca %d en el final\n",paraC);
if (paraD>100)
printf("\nEs imposible que pase esta materia, a menos que ocurra un
milagro\n");
else
printf("\nUsted saca una D si saca %d en el final\n",paraD);
return;
}
Capítulo VI: Arreglos
Arreglos (arrays)
nombrearreglo [5] = 2;
Dirección 1000 1004 1008 1012 1016 1020 1024 1028 1032
índice 0 1 2 3 4 5 6 7 8
contenido 15 24 36 75 12 0 3 6 71
#define ELEMENTOS 9
system("PAUSE");
return 0;
}
Este código nos va pidiendo datos y los almacena en la posición i del
arreglo. Fíjese que i es el contador del for y va aumentado de uno en uno cada
vez. De esta manera, se nos piden los datos y los almacenamos en cada posición
del arreglo. El ciclo se repite mientras i sea menor que la cantidad de elementos,
en este caso, mientras i sea menor que 9. Cuando i llega a 9 significa que ya ese
elemento que sigue no está en el dominio, y el ciclo no entra. Analice el código y
los bucles utilizados, ya que en general, siempre que usemos arreglos usaremos
bucles for para llenarlos de datos siguiendo un proceso similar al anterior.
Esto de usar bucles para llenar y extraer información del arreglo es lo que
se conoce como recorrer el arreglo. Una vez que aprendemos a manejar el
arreglo con bucles, podemos hacer básicamente lo que queramos con las
posiciones de memoria del arreglo.
Algo importante es que los indices de los arreglos deben ser números
enteros (nada de decimales), deben ser mayores o iguales que cero, y deben estar
dentro del rango en el que se declaró el arreglo. En caso de que quisiéramos
saber cual es el tamaño de un arreglo, introduciremos un nuevo operador que no
habíamos usado hasta el momento, el operador sizeof (objeto). Donde dice
objeto ponemos la variable a la que deseamos medir el espacio que ocupa en la
memoria. Por ejemplo, para el código anterior, justo antes de system(“PAUSE”);
agregue un printf que diga printf(“El espacio que ocupa en la memoria el arreglo
numeros es %d”, sizeof(numeros)); En ese caso, se imprime la cantidad de bytes
que ocupa el arreglo. Para saber el size de un elemento solamente, ponemos
sizeof (numeros [4] ) por ejemplo. Esto nos da el size de un elemento. Ahora
bien, si quisiéramos saber la cantidad de elementos del array (en caso de no
haberlos definido de forma directa) podemos hacerlo dividiendo el size del
arreglo completo entre el size de un elemento, lo que nos da la cantidad de
elementos. Antes de finalizar con esta breve introducción a los arreglos, demos
un ejemplo:
#include <stdio.h>
#include <stdlib.h>
#define HORAS 24
float sumatemps = 0;
system (“cls”);
printf(“\nLista de temperaturas\n”);
system(“PAUSE”);
return 0;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAXTAM1 30
#define MAXTAM2 40
system(“PAUSE”);
return 0;
}
Lo hacemos para ahorrar espacio. Donde dice código del ejemplo usted
pegará el ejemplo correspondiente a cada función. Okay, aquí vamos:
printf: Para imprimir una cadena de caracteres con printf, se utiliza %s. La ‘s’
viene de string, como tal vez notaste. En su lugar correspondiente colocamos la
dirección de memoria de la cadena o una cadena constante de caracteres. Por ej:
scanf: Para capturar una cadena usamos %s. El único fallo de scanf es que
solamente captura caracteres consecutivos hasta que encuentra un espacio. No
puede capturar más de una palabra. Pruebe este código insertando distintas
cadenas y vea lo que se imprime. Procure no escribir más caracteres del tamaño
de la cadena, o podría ocurrir un error.
scanf(“%s”, apodo);
fflush(stdin);
printf(“\nSoy Angel Blanco, y mi apodo es %s\n”, apodo);
Lo que pasa es que como C escribe fuera del arreglo, no sabemos que hay
en esas posiciones de memoria, ya que no las hemos reservado. Puede ser que
allí no haya nada importante, al menos no en su PC, o al menos no en ese
momento. Pero tal es la mala suerte que cuando uno lleva la tarea para que se la
corrijan, allí es donde “resplandece” el error. Viene C y modifica esas posiciones
desconocidas, y desgraciadamente ocurre el milagro que allí estaban los archivos
de la configuración de la PC y… ¡¡¡puff!!! Los puntos desaparecen como por
arte de magia. Así que si planea usar gets, reserve la suficiente memoria para ni
siquiera imaginarse que se va a sobrepasar. No querrá vivir esa mala experiencia,
se lo aseguro. La función gets pertenece a la librería stdio.h. Este es el ejemplo,
pero recuerde no insertar una cadena demasiado larga:
gets(nombre);
printf(“\nMi nombre es %s\n”,nombre);
que representa una cadena. Para el ejemplo, inserte algo y puts lo imprimirá(no
lo haga demasiado largo):
gets(nombre);
puts(cadena);
strcat: esta función pertenece a string.h y lo que hace es concatenar dos cadenas
de caracteres. Por concatenar se entiende unir dos cadenas. La sintaxis es la
siguiente:
149
strcat (nombre,apellido);
printf(“\nMi nombre es %s\n”, nombre);
indice 0 1 2 3 4 5 6 7 8 9 10 11
nombre A n g e l B l a n c o \0
apellido B l a n c o \0
Creo que está claro como quedan las cadenas al usar strcat. Además de
esto, existe la variante…
valor = strlen(apellido);
printf(“\nLa cadena %s tiene %d caracteres\n”, apellido, valor );
variable = strcmp(cadena1,cadena2);
150
valor = strcmp(saludo1,saludo2);
printf(“\n%d\n”, valor );
Al igual que las otras, strcmp tiene una variante en la que podemos
especificar hasta donde queremos comparar. La sintaxis de strncmp es:
Antes de concluir con strcmp, existe otra variante de las funciones strcmp
y strncmp. La variante se logra agregando una i antes de la c en el nombre de la
función, o sea, las variantes se llaman stricmp y strnicmp. La sintaxis de estas
dos es la misma que la de las originales, y de hecho, funcionan casi igual. La
diferencia es que la i (que significa Ignore case) hace que al comparar las
palabras se considere a las mayúsculas y minúsculas como caracteres iguales en
ASCII, por lo tanto, lo que decide todo es la posición en el alfabeto. Por
ejemplo, strcmp(“S”, “a”); retornaría -1 ya que la S es mayúscula y tiene menor
ASCII. Pero si ponemos stricmp(“S”, “a”); retornaría 1, ya que, olvidándose del
ASCII, la ‘a’ está antes que la ‘S’ en el alfabeto y se considera menor, por lo que
S es mayor y se retorna1. Eso es todo sobre strcmp.
variable = getchar();
valor = getchar();
printf(“\n%c\n”, valor );
En este caso, getchar atrapará la primera letra de la línea que usted escriba.
valor = getchar();
while (valor != EOF)
{
printf(“%c”,valor);
valor = getchar();
}
putchar: pertenece al string.h. Es una función void que no retorna nada y que
sirve para imprimir un caracter en pantalla, en el lugar donde esté el cursor. El
argumento que le pasamos es el char a imprimir.
putchar(letra a imprimir);
valor = ‘N’;
putchar(valor);
valor = ‘\n’;
putchar(valor);
Por ejemplo, para los ejemplos de getchar, en vez de haber usado printf
para imprimir una sola letra, hubiéramos usado putchar. Generalmente putchar y
getchar se usan juntos, getchar atrapa una letra y de inmediato la imprimimos con
putchar. Mediante la utilización de bucles, podemos guardar en variables o
imprimir en pantalla caracter a caracter mediante el uso combinado de ambos, y
sin salirnos del dominio de la cadena (lo cual dije es la desventaja de casi todas
otras funciones del string.h, que si uno guarda algo más grande que el espacio
reservado podemos tener problemas). Más tarde enseñaré la técnica para esto.
154
valor = ‘c’;
printf(“\nValores de retorno al aplicar tolower y toupper a la letra c\n”);
putchar(tolower(valor));
putchar(toupper(valor)); //Cuarta línea
valor = ‘D’;
printf(“\nValores de retorno al aplicar tolower y toupper a la letra D\n”);
putchar(tolower(valor));
putchar(toupper(valor));
Eso es todo lo que hay que decir sobre funciones de manejo de cadenas
(¡¡¡vaya, como si no fuera suficiente!!!). Por favor tome nota ya que estas
funciones solamente se aprenden con algo de práctica. O si usted es una botella
andante…bueno, usted sabrá como resolver.
Arreglos multidimensionales
scanf(“%d”, &valor);
fflush(stdin);
char arreglo[valor];
mantiene igual. Cuando ya llegamos al último valor para ese índice de fila, el
siguiente espacio es la posición cero la fila siguiente. Esto lo notamos donde se
ve que en la memoria después de numeros [0] [3] viene numeros [1] [0]. Nótese
que en la memoria cada bloque que reservamos ocupa tantos bytes como el
tamaño del dato que declaramos. Como el sizeof(int) es 4 bytes, vemos que cada
dato tiene una dirección anterior aumentada en 4. Por ejemplo, numeros[0][0]
tiene una dirección de 2000, pero como sabemos de antemano que ocupa 4 bytes,
sabemos que realmente ocupa los bytes 2000, 2001, 2002 y 2003. Luego
notamos que el siguiente dato tiene la dirección 2004, o sea, que están
adyacentes. El 2004 ocupa ese byte y los tres siguientes, y así sucesivamente
para los demás datos.
#include <stdio.h>
#include <stdlib.h>
#define SEMANAS 4
#define DIA 7
float sumatemps = 0;
system (“cls”);
printf(“\nLista de temperaturas\n”);
system(“PAUSE”);
return 0;
Mediante una matriz es posible decir que cada fila es una frase, y la
cantidad de columnas (que se supone debe ser un valor constante que no puede
variar durante la ejecución) indica cuantas letras caben en cada fila. Podemos
visualizarlo así:
char nombres[4][13];
fil\col 0 1 2 3 4 5 6 7 8 9 10 11 12
0 E d g a r \0
1 B r e n d a \0
2 I v á n S a n t a n a \0
3 G e l a n y H a w a \0
En este caso, la fila 0 tiene el string “Edgar”. El nulo está incluido para
todos los strings, incluso la cadena “Iván Santana” quedo exactamente de tal
manera que el nulo estuviera en la última posición. Note que la máxima cantidad
de caracteres que puede tener una frase incluyendo el nulo es igual al número de
las columnas (si contamos desde 0 hasta 12, incluyendo el 0, tenemos 13
caracteres).
Yo había mencionado esto antes, en el capítulo III cuando hablé del getch.
La técnica es simple. Hacemos getch(), y si el número atrapado es 224, hacemos
un override del getch y entonces nos devuelve un número que identifica la flecha
pulsada. Este número NO ES UN ASCII, es un override de la tecla con getch.
Los números que se capturan en el override son:
160
char letra;
letra=getch();
if (letra=224)
{
letra=getch();
código a ejecutar en donde necesitamos los overrides de las flechas
}
Note que lo que sea que queremos hacer cuando se presionen las flechas debe
estar dentro del mismo if que causa el override. Si ponemos el código afuera,
como 72, 80, 75 y 77 son ASCII de las letras H, P, K y D respectivamente, las
instrucciones no se darían cuenta si lo que se pulsó fue la letra, o si lo que pasó
fue un override del getch. Por eso el código debe estar dentro del if del override.
El siguiente truco es más bien un código que podemos usar siempre igual. Como
dije en el capítulo del ternario, la aplicación se puede ver ahora que sabemos
manejar matrices. A veces algunas de las respuestas que imprimimos dependen
de si lo que vamos a decir está en plural o en singular, por ejemplo:
i=0;
dimarreglo = sizeof (tucadena) / sizeof(tucadena[0]);
tucadena [i]=getchar();
while (i < dimarreglo-2 && tucadena [i]!= '\n')
{
i++;
tucadena [i]=getchar();
}
tucadena [i+1]= ‘\0’;
tipodatos nombrearreglo[dimensión];
162
gets (cadena);
puts (cadena);
strcpy (cadena de destino, cadena de origen);
strncpy (cadena de destino, cadena de origen, cantidad de caracteres a copiar);
strcat (cadena inicial, cadena a concatenar);
strncat (cad inicial, cadena a concatenar, cantidad de caracteres a concatenar);
strlen (cadena);
strcmp (cadena1,cadena2)
strncmp (cadena1, cadena2, cantidad de caracteres a comparar);
variable = getchar();
putchar (variable a imprimir);
toupper (variablechar);
tolower (variablechar);
Las matrices (arreglos de dos dimensiones) son útiles para crear grupos de
datos distinguidos por dos características (identificadas en la fila y la
columna) y para crear arreglos de cadenas.
¿Crees que estás listo para el primer parcial? Pruébate contra el mío…
2. (25%) Realice una función int overavg(int valores[ ], int n) que recibe
como parámetro un arreglo de n elementos enteros y retorne la cantidad de
elementos que están por encima del promedio.
4. (25%) Realice una función int cuadruple(char s[]) que recibe como
parámetro una cadena de caracteres s y debe devolver cuantas palabras de
cuatros letras tiene la cadena.
Problema #1
int espalindromico(int n)
{
int numreves = 0, noria = n;
while ( noria != 0 )
{
numreves = numreves * 10 + noria % 10;
noria /= 10;
}
164
if ( numreves == n )
return 1;
else
return 0;
}
El código es evidentemente más corto que la lógica astral que hay que
tener para determinar el algoritmo de voltear el número. Pero no se asuste si
usted no lo pudo hacer, de hecho ese fue el punto más difícil y casi nadie lo hizo
así, sino que halló otra forma o sencillamente no lo hizo. Y además, en una de
las clases, Alejandro había puesto un ejemplo de cómo obtener los dígitos de un
número con el %10. Así que no te sorprendas de nada. Pero Alejandro no nos
dijo como voltear el número (claro que no, eso era para el examen), así que si
haces este algoritmo puedes considerarte afortunado…
Problema #2
float prom;
prom /= n;
return cont;
}
Problema #3
return 1;
Problema #4
consecutivos = 0;
}
else
consecutivos++;
i++;
}
if (consecutivos==4)
palsde4++;
return palsde4;
}
Ahora bien, para entender eso, tenemos que leer la otra parte del gran if.
Imagínese…si la condicional se va por el else, significa que no estamos en un
espacio, por lo que lugar debe estar ocupado por un caracter, y por eso
incrementamos consecutivos en 1. Cada vez que el ciclo se encuentre con algo
distinto a un espacio, consecutivos sube 1(porque habríamos encontrado otra
letra consecutiva).
Realmente traté de hacerlo lo más simple posible, pero imagínese, eso era
un examen, y a veces vienen cosas difíciles de explicar en un manual que se
supone es de finalidad didáctica “ridículamente simple”. Pido mil excusas si he
vuelto a fallar en mi objetivo de explicar todo esto tan simple como sea posible,
pero francamente ya no le pude hallar la vuelta a todo esto de una manera más
simple que la que muestro aquí.
Supongo que a esta altura del juego cuando menos te has preguntado mil
veces que es lo que significa el encabezado del main:
169
tipodato *nombrepuntero;
O sea, ponemos el tipo de dato que contendrá esa dirección, o sea, char,
int, float, double…se pone un asterisco y luego, sin dejar ningún espacio, el
nombre que le daremos al puntero. Para asignar una dirección al puntero,
tomamos el nombre del puntero sin el asterisco y le asignamos una dirección.
Por ejemplo, si tenemos una variable “calif” y queremos asignar su dirección al
puntero dircal, tendríamos:
dircal = &calif;
#include <stdio.h>
#include <stdlib.h>
*dirnum = 18;
printf(“El n%cmero es %d\n”, 163, numero);
system(“PAUSE”);
return 0;
}
Los punteros solamente pueden apuntar a un dato del mismo tipo que el
puntero, y viceversa, un dato de un cierto tipo solamente puede ser apuntado por
un puntero de ese tipo. En el caso anterior, como numero es un int, declaramos a
dirnum como un int. Luego hicimos que dirnum apuntara a numero diciendo
dirnum = &numero. Luego, mediante un asterisco, modificamos a numero desde
dirnum diciendo *dirnum=18; Bueno, creo que ya lo repetí muchas veces y no
171
deseo redundar, pero por favor recuerden eso, es una de las cosas que más se
olvidan.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
system(“PAUSE”);
return 0;
}
En ambos casos se imprime lo mismo. El asunto es que, como habíamos
dicho antes (pero no con las mismas palabras) cuando se usa el nombre de una
cadena sin usar índices es lo mismo que la dirección del primer elemento, o sea,
un puntero al primer elemento. Por eso asignamos directamente la dirección de
la cadena diciendo dirnom = nombre, ya que nombre representa por sí solo la
dirección del primer elemento de la cadena, y por eso no se pone apersand (&).
Recuerde que en las funciones de manejo de cadenas lo que siempre se pasaba
como argumento era la dirección de memoria del primer elemento de la cadena.
Por lo tanto, manejar una cadena mediante su puntero es lo mismo que cuando
usamos el nombre de la cadena sin ningún índice. Por eso en el ejemplo anterior
usamos dirnom normalmente sin el asterisco (*) ya que las funciones de manejo
de cadenas lo que nos piden es la dirección, no el contenido.
172
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
173
i=0;
dimarreglo = sizeof (frase) / sizeof(frase[0]);
frase[i]=getchar();
while (i < dimarreglo-2 && frase[i]!= '\n')
{
i++;
frase[i]=getchar();
}
printf("\n\n%s\n\n", frase);
system("PAUSE");
return 0;
}
La función que hice está diseñada para que aunque insertemos una frase
más larga que el espacio de la matriz, la matriz solamente se queda con los que
puede, sin salirse del dominio. En este caso, la cadena frase tiene 38 caracteres,
37 sin contar el nulo. Si corremos el programa y escribimos cualquier cosa, se
copiará sin problemas mediante el uso de getchar al arreglo. Claro, no nos
saldremos del dominio, aunque escribamos una frase muy larga solamente se
atraparán como máximo 37 caracteres, guardando la última posición para el nulo.
Ahora bien, ¿Qué tal si quisiéramos que el usuario nos diga cuántos caracteres
desea insertar, y luego mediante un malloc reservamos la memoria necesaria para
guardar la cadena? Declararemos un puntero de tipo char y luego le asignaremos
el espacio en bytes que ocupará. Para saber la cantidad de bytes, no lo vamos a
poner directo (ya que en algunos sistemas operativos los datos no siempre ocupan
lo mismo, por ejemplo, en algunos sistemas un int no ocupa 4 bytes, sino 6
bytes). Por ello, para estar seguros de que estamos pidiendo la cantidad de bytes
necesarios, pondremos como argumento un producto donde decimos
sizeof(dato)*cantdatos. Por ejemplo, si queremos reservar espacio para 37 letras,
diríamos sizeof(char) * 37, y así la máquina averigua cuanto ocupa un char, para
que al multiplicarlo por 37 reservemos 37 veces el espacio de ese tipo de dato.
cadena, con la certeza de que aunque el usuario se haga el loco e inserte más
caracteres que los que pidió, la función “truco” que estamos usando impide que
nos salgamos del dominio. Una última cosa a decir, es que luego de que
hallamos hecho lo que debíamos hacer con la memoria reservada dinámicamente,
debemos liberar el puntero y la memoria. Esto lo hacemos mediante la
instrucción free (puntero); que libera al puntero. Si no liberamos al puntero, esa
memoria se queda reservada y poco a poco vamos llenando la memoria sin
darnos cuenta. Luego en el momento menos esperado la pantalla se nos pone
azul… así que recuerde hacer free (puntero); al final de todos los códigos en los
que use asignación dinámica de memoria.
En último lugar hay que decir que para recorrer el arreglo mediante el
puntero debemos usar aritmética de punteros. Cuando tenemos un puntero
apuntando al primer elemento de lo que será una cadena, para acceder una
posición cualquiera y modificar su contenido, solamente debemos poner un
asterisco y dentro de un paréntesis poner la dirección del puntero más la cantidad
de bloques (el índice) a la que se encuentra el elemento de nuestro interés. O sea,
si tenemos la matriz char numeros [6] = {1, 6, 8, 3, 2, 4} y deseamos modificar
la posición de índice 2 (o sea, que queremos modificar el valor donde está el 8),
con un puntero llamado char *nums que está apuntado hacia el, lo hacemos
poniendo *(nums + 2) = 9. O sea, si queremos modificar una posición X en la
memoria utilizando un puntero, lo hacemos diciendo *(nums + X) = “tal cosa”.
Lo que el ordenador hace es contar esa cantidad de posiciones en la memoria a la
derecha del puntero para ubicarla (tal como se veía la memoria en el Cáp. VI) y
luego que sabe donde está, lo modifica. Recuerde que el puntero se mueve en
bloques, no por bytes. O sea que si el arreglo es int, iría saltando de 4 bytes en 4
bytes, saltando de un dato al siguiente. En pocas palabras, el número que
sumamos es como quien dice el índice que hubiéramos usado en el arreglo. El
puntero no se mueve, ya que no le estamos asignando nada, solamente lo estamos
usando como una referencia para encontrar las otras posiciones. Ahora bien,
tenga cuidado de no usar ++ ó -- con los punteros, o de usar += ó -= con los
punteros, ya que moveríamos el puntero de lugar al asignarle otro valor, y
perderíamos de vista la dirección original de la memoria que reservamos.
Incluso podríamos mover el puntero a un lugar donde al apuntarle a un tipo de
dato distinto al del puntero causemos un error. Así que ya sabe…prohibido
usarle ++, --, +=, ó -= a los punteros. Y ni se diga, los otros operadores no se
aplican a los punteros.
Ahora bien, vamos con el para no tener que seguir explicando en el aire:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
i=0;
frase = (char *) malloc(sizeof(char)*(dimarreglo+1));
* ( frase + i) = getchar();
while (i < dimarreglo-2 && *(frase+i) != '\n')
{
i++;
*(frase + i)=getchar();
}
printf("\n\n%s\n\n", frase);
free(frase);
system("PAUSE");
return 0;
}
En primer lugar, hacemos un filtrado de dominio para asegurarnos que el
usuario insertará una dimensión mayor que cero para la matriz. Luego
reservamos esa cantidad de memoria diciendo sizeof(char)*(dimarreglo+1).
Ponemos dimarreglo +1 para reservar un espacio extra para el nulo en la
memoria, además de los espacios que reservaremos por el usuario. El resto del
ciclo permanece igual, con la salvedad de que como estamos manejando arreglos
en la memoria mediante punteros, en vez de frase[i], ponemos *(frase + i) para
modificar la posición i de la matriz que existe en la memoria reservada. Este
programa es más efectivo que el anterior, ya que el usuario reserva tanta memoria
como desee y luego digita la cadena. Observe el free (frase) al final del código
para liberar el espacio que reservamos y que ya no usaremos más. Grábese esto
en la mente: casi todos los puntos que muchos pierden en los quizes o en las
tareas de asignación dinámica de memoria es porque se les olvida poner el free.
Recuérdelo, escríbaselo en una mano de ser necesario, hasta con lapicero si
176
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
i=0;
178
free (frase);
system("PAUSE");
return 0;
}
Para finalizar con la asignación dinámica de memoria, hay una última función
llamada calloc, pero que no es la gran cosa. Hace lo mismo que malloc, la única
diferencia es la sintaxis:
Tal vez parezca alguna especie de chiste, pero eso es todo lo que hay que
decir sobre punteros y asignación dinámica de memoria. El asunto es saber
usarlo. En lo que nos resta del capítulo daremos algunos ejemplos de las
aplicaciones en programas de punteros, para calentar más el entendimiento de los
mismos.
#include <stdio.h>
#include <stdlib.h>
cadena = entrelazado(cad1,cad2);
printf("%s\n", cadena);
system("PAUSE");
181
return 0;
}
if (*(string2 + j) != '\0')
{
*(cadentrelaz+i+j) = *(string2+j);
j++;
}
}
*(cadentrelaz+i+j)='\0';
return cadentrelaz;
}
Note que esta función tiene como tipo de retorno un char *, o sea, un
puntero a caracter. Además, se pasan dos argumentos por referencia, aunque no
se alteren durante el proceso. La función toma un caracter de una cadena y uno
de la otra durante cada vuelta del ciclo, mientras al menos uno de los dos
todavía no ha llegado al nulo. Cuando se toma cada caracter, se coloca en la
posición que le toca, de acuerdo al total de letras que han sido absorbidas. Esa
posición es i+j, o sea, la suma de las letras que ya han sido asignadas a la cadena.
Cuando ya todos los caracteres han sido entrelazados, se devuelve la cadena
como retorno de la función y la imprimimos en el main. Nótese también que el
free está en el main, después de usar la cadena.
Antes de terminar, hay que aclarar como se manejan dos dimensiones con
un puntero. Como habrás visto, en la memoria real, incluso en las matrices, los
elementos están consecutivos, y al final de cada fila está el inicio de la siguiente.
Por ejemplo, si tenemos char palabras [5][6], estamos hablando de 5 cadenas de 6
caracteres incluyendo el nulo, y por ejemplo, después de la posición [0][5] que es
la última de la primera fila, sigue la posición[1][0], que es la primera posición de
la siguiente fila. Ahora bien, a nivel de punteros no se distingue si la declaración
se va a manejar como un arreglo o como una matriz. Para manejar cualquier
posición en específico de una matriz, usamos la formula…
*(puntero+i*columnas+ j) = valor
char *amigos;
amigos = (char *) malloc( 3*19*sizeof(char));
strcpy(amigos+0*19,"Patricia Alexandra");
strcpy(amigos+1*19,"Edgar Gonzalez");
strcpy(amigos+2*19,"Eduardo Baret");
para asignar “Patricia Alexandra” que está en la primera fila (la fila cuyo índice
es cero en la matriz) decimos strcpy (amigos+ 0*19, “Patricia Alexandra”); de tal
manera que es igual que decir strcpy (amigos, “Patricia Alexandra”). Tenga en
cuenta que usted debe haber reservado espacio suficiente de manera que incluso
la cadena más larga tenga espacio para caber en las filas.
NUNCA use ++, --, +=, -=, *=, /= ó %= con un puntero, eso es un
error.
Bueno, producto del hecho que estoy un poco atrasado en escribir el resto
del manual, voy a poner solamente un ejercicio, de hecho más simple que
algunos reales o de quices, para dar una explicación breve y luego continuar con
el siguiente ejemplo. Es una pena tener presión del tiempo, pero aún así he
tratado de seguir manteniendo la misma calidad en las explicaciones de los
capítulos. Es más… ¿Qué tal si lo hacemos más competitivo? Trata de hacer
este problema en menos de media hora. Demuéstrame que la magia no es lo que
aparenta, sino lo que uno mismo lleva adentro. Bueno, ahí te va:
185
Solución
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int *punt, i;
system("PAUSE");
return 0;
}
srand(time(NULL));
//Desde 0 mientras sea menor que numelementos, llenamos el arreglo
return;
}
Los comentarios dicen todo, espero que lo hayas logrado lo más rápido
posible. Puedes variar a, b, o numelementos si deseas, esto es un ejemplo y ya.
Capítulo VIII: Recursividad y Estructuras
están antes que él, hasta llegar a 1. O sea, el factorial de 5 es igual a 5*4*3*2*1.
Por definición matemática se establece que tanto el factorial de uno como el
factorial de cero es uno. Ahora bien, con lo que hemos aprendido hasta ahora la
podríamos diseñar así:
En esta, desde i igual al número, mientras i sea mayor que cero, resultado
se multiplica el resultado por i. Como i va bajando de uno en uno debido al --,
multiplicaremos por todos los números desde el número hasta 1. Y ese sería el
factorial.
Ahora bien, una forma recursiva de ver la función factorial es verla como
el número multiplicado por el factorial del número anterior. Por ejemplo si
tenemos factorial de 5 (5!) es lo mismo que decir cinco multiplicado por el
factorial de cuatro (5*4!). A su vez 4 es lo mismo que 4*3! y así sucesivamente.
Cuando llegamos al 1!, entonces, por definición, su factorial es 1.
Mes 0 1 2 3 4 5 6 7 8 9 10
Fib 0 1 1 2 3 5 8 13 21 34 55
typedef struct
{
campos de la estructura
}nombre de la estructura ;
typedef struct
{
char nombre[50];
int matricula;
float indice;
}ESTUDIANTE
ESTUDIANTE nombrevariable;
ESTUDIANTE estudiantesdePUCMM[30000];
typedef struct
{
int p1;
int p2;
int exf;
}NOTAS;
typedef struct
{
char nombre[50];
int matricula;
float indice;
NOTAS calificaciones;
}ESTUDIANTE;
En este caso, para acceder al campo del primer parcial del estudiante 4526
sería:
estudiantesdePUCMM [4526].calificaciones.p1
191
typedef struct
{
int p1;
int p2;
int exf;
}NOTAS;
typedef struct
{
char nombre[50];
int matricula;
float indice;
NOTAS calificaciones;
}ESTUDIANTE;
ESTUDIANTE estudiante;
ESTUDIANTE *direstud;
direstud = &estudiante;
direstud->matricula = 20090216
O sea, que el puntero que apuntará a la estructura debe ser del mismo tipo
que la estructura en sí, tal como se ve más arriba. Para acceder a un campo, en
vez de usar un punto, usamos el símbolo ->, como dije más arriba. O sea, que en
el caso anterior, se asignó al estudiante el número 20090216 como matrícula.
Nótese que aunque no pusimos * lo que se maneja es el contenido de esa
192
Fíjese que como nombre es una cadena, y como no pusimos el índice del
arreglo, entonces lo que se maneja es la dirección del campo nombre de la
estructura estudiante. Por supuesto, al igual que en los arreglos normales, si
ponemos el índice entre corchetes, en vez de manejar la dirección del elemento
cero, lo que haremos es manejar el contenido del índice que especifiquemos.
Ahora bien, para scanf y para gets por ejemplo, como lo que hay que pasar es la
dirección, tendríamos:
scanf(“%d”, &(direstud->matricula);
fflush(stdin);
gets (direstud->nombre);
typedef struct
{
campos de la estructura
}NOMBREESTRUCTURA;
typedef struct
{
nombre[40];
jp;
jg;
promedio;
}EQUIPOS;
Ahora, diseñar la función int filtrar(EQUIPOS equipo[], int n, int flags) a
la que se le pasa un arreglo de n estructuras de tipo EQUIPO. Según el valor de
flags se retorna diferente valor. Si flags es 1, se retorna cuántos tienen un
promedio por debajo de 500. Si flags es 2, se retorna cuántos tienen 500
justamente. Si flags es 3, se retorna cuántos tienen por encima de 500.
Ahora, la gracia es… ¡que usted ni siquiera tiene que sacar el promedio!
El promedio viene teóricamente dado en el campo promedio de la estructura. O
sea…lo único que usted debe hacer es ver cuántos hay en la categoría de “flags”
que le haya tocado y luego contar los equipos con esos “flags” de promedio. Yes,
that’s it, hope you like it…
194
***No me gusta dejar espacio en blanco, pero poner la solución aquí estaría
muy mal… ***
if(flags==1)
return bajo500;
else if(flags==2)
return en500;
else if(flags==3)
return sobre500;
}
Se compara en el ciclo desde i=0 mientras i < n, cada promedio con 500.
Si es menor, aumentamos bajo500, si es igual, aumentamos en500, si es mayor
(else, si no es menor ni igual, entonces es mayor) aumentamos sobre500. Al
final, dependiendo de si el flan es 1, 2, ó 3, retornamos uno de esos tres valores.
Y termina la función.
Para abrir un archivo o trabajar con el, debemos declarar una variable de
tipo archivo puntero, la cual contendrá la dirección en el disco donde se
almacenará la información. Para declarar un puntero a archivo, se dice:
FILE *direccion;
Para asignar una dirección al puntero de tipo FILE, debemos usar las
funciones fopen y fclose. Para abrir el archivo, o sea, el canal de comunicación
para pasar los datos, la sintaxis de fopen es:
fopen nos devuelve un NULL si ocurre algún error al intentar abrir el archivo. Es
importante revisar si el archivo se pudo abrir con éxito, ya que si algo falló y
luego intentamos usar el puntero FILE para escribir en el archivo podríamos
cometer una barbaridad de consecuencias impensables, desde lo ridículamente
inofensivo hasta lo fatalmente destructivo ( como cualquier cosa en la que haya
un puntero involucrado).
existen tres características que están aparte al abrir un archivo. En las siguientes
combinaciones para el fopen, la letra x representa una b(binario) o una t(texto)
que son las combinaciones que se pueden ligar con las siguientes características.
Esa x se sustituye por t y nos quedaría rt, wt, at, r+t, w+t, a+t, en cuyos
casos se abre un archivo de texto con esas características. Si es binario, pasa lo
mismo pero el archivo se escribe en binario. Un ejemplo simple de cómo abrir
un archivo…
#include <stdio.h>
#include <stdlib.h>
archivo = fopen("c:\\ejemplo.txt","r+t");
if (archivo == NULL)
{
printf("No se pudo abrir el archivo\n");
}
else
{
198
archivo
2: Cuenta desde el final del archivo. SEEK_END
ftell(puntero_archivo);
feof(puntero archivo);
rewind(puntero archivo);
Escribe el caracter que se le pase como primer argumento en el puntero FILE que
se pasa como segundo argumento. El caracter se escribirá en la posición que esté
el cursor, y después de la escritura el cursor se moverá uno a la izquierda.
Similar al anterior, pero en vez de escribir lo que hace es leer un caracter desde el
archivo, lo almacena en la variable.
Escribe una cadena que se le pase como primer argumento en el puntero FILE
que se pasa como segundo argumento.
A partir desde donde está el cursor, atrapa todos los caracteres hasta haber
atrapado n-1 caracteres, haber encontrado un nulo o el final del archivo, y los
coloca en la dirección de la cadena donde se almacenará.
#include <stdio.h>
#include <stdlib.h>
archivo = fopen("c:\\ejemplo.txt","r+t");
if (archivo == NULL)
{
printf("No se pudo abrir el archivo\n");
}
else
{
printf("El archivo abri%c con exito\n",162);
printf("Inserte un n%cmero\n",163);
scanf("%d",&num);
fprintf(archivo,"%d",num);
fclose(archivo);
printf("Salvado con %cxito",130);
}
system("PAUSE");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
{
FILE *archivo;
int num=0;
archivo = fopen("c:\\ejemplo.txt","r+t");
if (archivo == NULL)
{
printf("No se pudo abrir el archivo\n");
}
else
{
printf("El archivo abri%c con exito\n",162);
fscanf(archivo,"%d",&num);
fclose(archivo);
printf("El n%cmero atrapado fue %d\n",163,num);
}
system("PAUSE");
return 0;
}
¡Ah! Creo que eso es todo. Excúsenme la falta de ganas de explicar más
detallado, sencillamente estoy un poco cansado, además, no hay cosa que más me
desanime que estar escribiendo algo que seguramente no le saldrá en ningún
examen ni nada así…pero nada…ahí está…realmente te deseo la mejor de las
suertes en el manejo de archivos…no voy a hacer el resumen ni los ejercicios
esta vez…eso sería ridículo…si tal vez algún día me llego a enterar que el
manejo de archivos está saliendo en los exámenes, le daré un update a todo esto
para explicarlo de una manera más “ridículamente simple”, pero mientras tanto…
rezaré para que no haya que hacerlo :P Jejeje, acaba de salir humo de mi teclado
(ilusión óptica tal vez) o será que lo he usado tanto durante los últimos dos meses
que ya estoy a punto de fundirlo jejejeje….
Antes de finalizar hay que dar crédito y agradecer a algunas personas para
la construcción de este manual. A pesar de que fui yo quien lo escribió, no se
puede restar la importante contribución de los siguientes personajes:
José Cruz: quien fue que leyó el manual mientras estaba en modo de prueba y
me fue diciendo que cosas no estaban lo suficientemente claras, para luego darles
la vuelta y explicarlas de otra forma. Es mi beta-tester oficial….
Ubán Hernández: por aconsejarme con respecto a que estaba diciendo cosas
alocadas que podían haberme metido en líos con los profesores. Eso fue
importante, y además, por ser un maldito loco y hacernos reír a todos los del
grupo que estudiamos juntos para botar el estrés incluso en los momentos de
mayor presión…
Edgar González: por haber cubierto muchos de los turnos en los que a mí me
tocaba hacer la tarea de algoritmos (era en grupos de 2, el y yo estábamos juntos)
y el la hacía por mí muchas de las veces para que yo siguiera con el manual.
Edgar, no solamente el dinero de la tinta o los papeles…más que eso…aprecio el
tiempo que dedicaste y del cual se que no dispones, porque tienes una agenda
muy ocupada. En serio, gracias…
Gelany Hawa: por darnos una sonrisa cada vez que necesitamos verla, y motivar
la construcción de este manual (Dime tu...si me preguntaba a cada segundo que
cuándo le iba a dar la primera edición jejeje…)
Martha Rodríguez: porque, de la misma forma que Edgar, hizo una práctica de
lab. de circuitos II de la cual yo me había hecho responsable y al final se la cedí
para poder seguir escribiendo el manual ya que el tiempo se me estaba acabando.
Gracias Martha, realmente te admiro y hay más de ti de lo que se ve a simple
vista.
Maika Rodríguez: porque debido a algo que ella me contó, este manual existe.
Maika pagó mucho dinero (ni voy a decir a quién ni cuánto, mientras más me doy
cuenta de que lo conozco al tipo más me quillo) para que le hiciera el programa
que detecta si un número es primo o no. Ese programa lo hice yo sentado en mi
casa comiendo galleticas e incluso se lo regalé a algunas personas. Diablos…ese
abuso fue una de las principales cosas, además de que a la mayoría le iba mal en
algoritmos, para que este manual exista hoy. A veces el dinero hace que personas
que crees que conoces se transformen en unos monstruos hambrientos. Por eso
este manual es gratis…soy una persona sin demasiada ambición en la vida y solo
me interesa lo necesario. Por eso este manual está aquí: para que no se repita esa
historia con nadie más…
203
backspace 8 BS 40 ( 72 H 104 h
Tabulador horizontal 9 HT 41 ) 73 I 105 i
Salto de línea 10 LF 42 * 74 J 106 j
Tabulador vertical 11 VT 43 + 75 K 107 k
Salto de página 12 FF 44 , 76 L 108 l
Retorno de carro 13 CR 45 - 77 M 109 m
Shift fuera 14 SO 46 . 78 N 110 n
Shift dentro 15 SI 47 / 79 O 111 o
Escape línea de datos 16 DLE 48 0 80 P 112 p
Control dispositivo 1 17 DC1 49 1 81 Q 113 q
Control dispositivo 2 18 DC2 50 2 82 R 114 r
Control dispositivo 3 19 DC3 51 3 83 S 115 s
Control dispositivo 4 20 DC4 52 4 84 T 116 t
neg acknowledge 21 NAK 53 5 85 U 117 u
Sincronismo 22 SYN 54 6 86 V 118 v
Fin bloque
transmitido
23 ETB 55 7 87 W 119 w
Cancelar 24 CAN 56 8 88 X 120 x
Fin medio 25 EM 57 9 89 Y 121 y
Sustituto 26 SUB 58 : 90 Z 122 z
Escape 27 ESC 59 ; 91 [ 123 {
Separador archivos 28 FS 60 < 92 \ 124 |
Separador grupos 29 GS 61 = 93 ] 125 }
Separador registros 30 RS 62 > 94 ^ 126 ~
Separador unidades 31 US 63 ? 95 _ 127 DEL