Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
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
4
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á…
Como todo en la vida, hay que dar crédito y/o dedicar cada cosa que hacemos
para saber que nos motiva, que nos hacer ganar fuerzas para marchar en cada
tortuoso y espinoso camino que tomamos. Esto es lo que me motiva:
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
9
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.
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.
10
char b, c = ‘d’,d;
1<=2 1
2!=5 1
2= =2 1
0= = 1 0
3!=0 1
1!=1 0
0!=0 0
2.1 1
9.9 0
1<5 1
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.
12
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.
valor, que es cero, se realiza la suma 0+0+0+0, luego se le suma cuatro veces 1 a
la variable a, y esta termina con un valor de 4). Sin importar la variable, o el
orden, los “++” postfijos sólo aumentan el valor de la variable después de que
TODAS las variables de ese tipo hayan sido sustituidas por su valor anterior.
O sea, nunca, absolutamente NUNCA el efecto de un “++” que está después de
la variable en una operación se notará durante la misma operación, no importa
que tan larga sea. Solamente se notará la siguiente que vez que usemos la
variable en cuestión en otra operación. (Tal como hicimos hace un rato con “B+
+*5”, donde aumentamos B en 1, y luego, en la operación “--A*++B” lo
utilizamos con el valor alterado).
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:
15
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.
()
! ++(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
(retorna 1) ya que OR es verdadero si al menos una de sus partes es verdadera.
Luego de todo esto, la expresión queda así:
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
transforma el resultado en un int. Al transformar 10.2, se pierde el 0.2 y queda
10. En ese caso, el % no hubiera dado error y se hubiera asignado un valor a d.
Bueno, eso es todo por ahora.
1) 171(agrupe ++d y ++d, luego el resultado con el otro ++d, y luego súmele 5)
2) 3
20
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
se le asigna una cantidad a una variable, y con dos se “pregunta” si son iguales,
devolviendo 1 si son iguales, ó 0 si son distintos.
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.
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
26
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
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.
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”,
30
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 +
+numdiv, decimos Si num == numdiv, entonces imprimir “ “num” es
primo”, si no, entonces debemos seguir sacando el módulo de “num” y
“numdiv” para ver si hay algún valor que divida exactamente a num. Pero ya
hemos hecho esto antes. De hecho, en lo adelante lo que haremos es repetir
los pasos anteriores, y seguir incrementando numdiv en uno hasta que
verifiquemos que “num” se divide exactamente entre numdiv, en cuyo caso
diríamos que no es primo, o hasta que numdiv tenga el valor de “num” en
cuyo caso sería primo (como dijimos más arriba). Entonces los pasos 4 y 5
quedan de esta manera: 4) ++numdiv 5) Si num == numdiv, entonces
imprimir “ “num” es primo”, si no, ir al paso 3.
5. Fin
1. Inicio
2. Capturamos num
3. Si num<=0 entonces ir al paso 2.
34
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
aplicable a 1 y 2, pero no a los negativos o cero). Si asumimos que num es 1,
cuando digamos num % numdiv, 1 % 2 será igual a uno, por lo que el programa
sumará uno a numdiv y preguntará si ya num es igual a numdiv. La respuesta
será “no” y en consecuencia repetirá los pasos anteriores una y otra vez hasta que
numdiv sea igual a num. Pero esto es imposible. Por más que sumemos uno a
numdiv, como numdiv comenzó en 2, llegará al infinito y nunca será igual a 1.
Estamos ante otra iteración (ciclo) infinito.
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í:
35
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
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
36
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
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
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”
Fin
Inicio
no
sumapar += numsumado
no
numsumado = = N numsumado+=2
sí
La suma es “sumapar”
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
42
num
no no
numdiv >= num num % numdiv = = 0 ++numdiv
sí sí
. Fin
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”.
variable que almacene cada uno de los números y otra que almacene la cantidad
de datos. Eso lo sabemos a partir de un análisis básico. Ahora bien, pensamos:
“¿Y qué es lo que voy a hacer?” Bueno, lo primero en cualquier problema es
pedirle los datos al usuario para luego operar con ellos. Sin embargo, este es un
caso muy especial porque nos damos cuenta de que no hay forma de operar con
los datos a menos que ya tengamos todos los datos de un tirón. Pero por otra
parte, podemos tratar de resolver el problema paso a paso con interacciones con
el usuario. Es decir, en algoritmos anteriores el usuario introducía un dato y
luego la máquina hacía ciertas operaciones y arrojaba un resultado. Pero en este
caso conviene que pidamos un dato, hagamos algunas operaciones, luego
pidamos el otro dato, luego repita las mismas operaciones y así sucesivamente
pedirá datos hasta tenerlos todos. Ahora bien, un algoritmo de este tipo nos exige
que sepamos cuántos datos vamos a pedir. Como ya dijimos que tendremos una
variable que nos dirá la cantidad de datos, debemos tener otra que vaya contando
cuántos datos ya hemos pedido, para que cuando la cantidad de datos que hemos
pedido sea igual a la cantidad de datos que se querían suministrar detengamos el
ciclo de captura y arrojemos un resultado.
Haciendo el análisis del problema (ver Pág. 42) el diagrama de flujo queda
de esta manera:
Inicio
calcular el promedio?
cantdatos
A ++capturas no
dato
sumaprom += dato
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:
45
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?
46
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
después de las operaciones para ponerla antes de las operaciones.
Recuerde que por eso era que el algoritmo fallaba en el número 2. O sea
que en general, trate de que sus algoritmos, en el inicio, pregunten (“¿ya
terminamos?”) antes de seguir con otra operación, ya que a veces no es
necesario realizar ninguna operación para arrojar un resultado.
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
47
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
no
“divisor” es un divisor
de “num”
Inicio
¿Cuántos datos va a
50
comparar?
cantdatos
sí
cantdatos<0 Debe ser mayor que cero
no
A
no
capturas<cantdatos El mayor es “mayor”
++capturas Fin
num
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
51
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
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
52
Inicio
Inserte un número
num
A
++divisor
no
divisor<num sumfactors = = num El número “num”
es perfecto
sí
El número “num” no es perfecto
no Fin
num%divisor sumfactors += divisor
sí
los factores en la variable sumfactors. Pero fuera de ello, el criterio para determinar si
es un factor o no es el mismo. Si num%divisor da 0, divisor es un factor de num, y por
eso se agrega a numfactors (el no de la condicional). Por otro lado, si no es un factor,
sencillamente se irá por el sí de la condicional y aumentará el divisor para preguntar si
ya terminamos, y se repetirá el proceso.
Inicio
num1, num2
prom =(num1+num2)/2
sí
(num1+num2)%2
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!
54
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
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
59
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………………);
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.)
imprimirá en el lugar exacto donde estaba el %d. Si hay más cosas a imprimir
después del %d, las otras se imprimen normalmente a la distancia que estaban del
%d. Por ejemplo, corramos el mismo código, cambiando num a 50000 y
poniéndolo así:
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.
desde el código, así que debemos usar el ASCII de la letra para llamarla. Por
ejemplo, la letra ñ o cualquiera de las vocales acentuadas son irreconocibles en el
lenguaje C. Por ejemplo, intente imprimir este código.
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.
#include <stdio.h>
#include <stdlib.h>
{
char a = 83;
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.
una dirección de acuerdo al libro. Pero claro, el contenido del libro puede no
tener nada que ver con la posición en la que está. Por ejemplo, la dirección del
libro puede llamarse EDS-505-104-Y y el libro se trata sobre artes marciales.
#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>
variable = getch( ) ;
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
flechas, notará que sale un caracter sin sentido (en mi PC sale una ‘ó’). Eso no
debería sorprendernos porque de todas formas, las flechas no son caracteres, son
solamente teclas, pero de todas formas getch debería retornar el valor, y existe el
fallo increíble de que no importa que tecla usted presione de las flechas, siempre
se retorna el mismo valor. Bien, por ahora no enseñaremos esta técnica para
filtrar las flechas, primero necesitamos otros conocimientos fundamentales.
#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
70
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
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
72
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*/
ordenador, así que no invente). Luego solamente tenemos que colocar el nombre
de la constante en todos los lugares que debe aparecer. Haciendo esto podemos
eliminar algunas variables, algunas operaciones, e incluso algunos comentarios.
Además, cuando se captura el precio total, para que el programa se vea aún mas
elegante, le ponemos %.2f para que el resultado venga con dos cifras decimales.
El nuevo código queda así:
int cantcajas;
float preciototal;
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
75
1. Realice un programa que pida dos números reales cualesquiera al usuario para
encontrar el promedio de los dos números.
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
76
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>
77
//Este programa saca el cubo del valor que sea almacenado en num
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
78
system("PAUSE");
return 0;
}
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í:
#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”);
}
else
{
printf(“Usted es menor de edad\n”);
}
system(“PAUSE”);
return 0;
}
int edad = 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>
int peso = 0;
system(“PAUSE”);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
system(“PAUSE”);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
84
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).
85
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:
86
#include <stdio.h>
#include <stdlib.h>
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
87
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");
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,
88
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>
parte del incremento hemos puesto la expresión i++. Recuerde que la expresión
i++ incrementa en uno a i (después de las operaciones, pero en este caso i++ está
solo, o sea que da lo mismo poner i++ que ++i). Entonces, lo que el ciclo hace es
lo siguiente:
cerradura del proceso son opcionales. Pero si se trata de más de una, el uso
de las llaves es obligatorio. El efecto de no poner las llaves es el mismo que
para los if-else y los else-if, que el ciclo solamente considera la siguiente
instrucción como parte del ciclo, las demás se consideran independientes del
mismo. O sea que por ejemplo, en el caso anterior pudimos haberlo escrito
sin las llaves. Pruébelo y vea el resultado. Debe hacer exactamente lo
mismo.
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>
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
94
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);
que el usuario pudiera haber entrado (como letras, números con decimales, entre
otras…) y asegurar que aquellos “datos vagantes” no causaran algún error en la
computadora. Pues bien, quitemos el fflush y dejemos el resto del código intacto
para ver lo que pasa. Corra el programa e inserte una letra o un número con
punto decimal…o cualquier otra cosa ilógica que le llegue a la cabeza. Es más,
si usted tiene un hermanito más pequeño que usted (como yo que tengo uno) déle
el teclado y déjelo que invente lo que quiera. Su programa cometerá una locura
asombrosa. Pero no se preocupe, no se le va a dañar nada en el ordenador (no en
este ejemplo, por si acaso). Se le activará un ciclo infinito. Este es uno de los
efectos colaterales de dejar basura en el stdin. Por eso, recuérdelo, siempre que
usemos scanf, ponga fflush (stdin); en la línea inmediatamente después al scanf.
Realmente es algo enfurecedor cuando uno dura varios días haciendo un
programa para una tarea, y luego viene el profesor e inserta un valor alocado
como una letra o algo así y la frustración que uno experimenta sobrepasa los
límites de lo conocido. Por eso, nunca lo olvide, ponga fflush después de scanf.
Ahora antes de continuar, ponga el fflush donde estaba y déjele el teclado a su
hermanit@ nuevamente. Sin importar lo que haga, no encontrará la forma de
hacer que el programa funcione mal (salvo que usted lo deje editar el código por
supuesto, o si lo deja echarle un vaso de algo líquido a la PC). Pero fuera de
esto, el programa funcionará bien.
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
98
system("PAUSE");
return 0;
}
que es igual a ‘B’. Entonces se pregunta la condición del primer ciclo. Como
‘B’ es menor o igual que ‘E’, el ciclo entra una vez más. Pero como la
instrucción que sigue dentro del primer ciclo es el segundo ciclo, el ciclo se
“resetea” ya que estamos entrando otra vez después de haber salido de él. Por lo
tanto, j se inicializa nuevamente en ‘1’ y se repite el mismo proceso anterior. Se
imprimen “B1”, “B2”, “B3”, “B4” Y “B5”. El segundo ciclo se rompe una vez
más, y el primer ciclo cuenta como si se hubiera repetido otra vez. Esta
secuencia se repetirá mientras cada letra se imprime con los cinco caracteres
numéricos. Cuando i se incrementa y vale ‘F’, el primer ciclo se rompe, y por lo
tanto se salta todo lo está entre su llave de apertura y su llave de cerradura. Por
eso llega al system(“PAUSE”) y luego termina el programa. ¡Ufff! ¡Qué tediosa
ha sido esta explicación! Tal vez tiene algo que ver con la repetitividad. Pero
eso es todo lo que hay que decir (por suerte).
#include <stdio.h>
#include <stdlib.h>
if (x<1000)
if(x%2)
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;
}
usar la AND (&&). Pero de todas formas, quién sabe, tal vez ese es su gusto, así
que usted decide cuál será su estilo.
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:
+ +
+ ++ +
+++++ +
+
+
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
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.
(identificadas por las coordenadas (i, j)) tomando para comenzar una matriz
cualquiera, preferiblemente distinta del extremo inferior.
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
ejes va desde 0 hasta dimensión - 1, y como dimensión es 1, el recorrido va desde
0 hasta 0. Esto comprueba nuestra hipótesis, y por lo tanto, la fómula general del
recorrido en i va desde 0 hasta dim-1, donde dim es la dimensión, y en el eje de
las columnas es la misma fórmula.
0
0 +
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
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:
104
#include <stdio.h>
#include <stdlib.h>
printf(“\n”);
system("PAUSE");
return 0;
}
columna de la izquierda. Y así es, debido a que cada vez que se resetea el
segundo ciclo, significa que previamente imprimimos un retorno de carro y por
lo tanto el cursor está ubicado en la primera posición j de la línea.
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>
#include <stdlib.h>
printf(“\n”);
system("PAUSE");
return 0;
}
106
#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”);
}
El algoritmo anterior es casi infalible, por no decir propiamente que lo es.
El único problema es que como la ventana solamente puede contener una cierta
cantidad de caracteres de anchura (80 para ser exacto), si dim es un número
mayor que 79, ocurre una ligera desviación en la cruz. Mientras mayor sea el
número, mayor será la desviación. Claro, nosotros no queremos que eso suceda.
Así que entre las condiciones de restricción, agregaremos dim>79, para así poner
un mensaje de ayuda que diga que la dimensión no puede exceder el número 79.
Finalmente, y para terminar la parte la lógica gráfica, tendríamos esto:
#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);
} while (dim <= 0 || !(dim%2) || dim>79);
printf(“\n”);
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.
108
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
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.
111
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>
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
112
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 xx xx xx 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 x x
x x x x
x x x x
x x x x x x x x x
114
Para dim = 9
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
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);
115
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*/
system("PAUSE");
return 0;
}
Problema #2
que la relación podía ser de cualquier forma. Y en este caso, la relación de que la
suma de i+j era igual a una constante se ve de manera clara en la relación f).
En este caso, esa constante es la dimensión menos 1. Para este problema, el
recorrido va desde 0 hasta la dim-1 en ambas dimensiones, por eso los apartados
b y c toman dim-1 como la última fila y la última columna, respectivamente.
Haciendo OR entre todas las relaciones anteriores logramos el programa.
#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”);
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.
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--;
return 0;
}while(base==0 || exponente<0);
haya insertado al ejecutar srand. O sea, en verdad, srand significa seed rand.
Cuando ejecutamos srand, le pasamos un valor que se conoce como la semilla de
generación aleatoria. Luego de ejecutar srand, la función rand será capaz de
retornar un valor aleatorio. Pero claro, siempre que insertemos la misma semilla
se generará el mismo valor en el rand. Así que, al fin y al cabo, son
seudoaleatorios. La sintaxis de srand es srand (semilla);
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
srand(time(NULL));
num = rand();
printf("El n%cmero generado es %d\n", 163, num);
system("PAUSE");
return 0;
}
rand () % ( 42 - 25 + 1) + 25;
124
#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;
}
#include <stdio.h>
#include <stdlib.h>
#include <conio.c>
textcolor(GREEN);
textbackground(YELLOW);
textcolor(LIGHTGRAY);
textbackground(BLACK);
system("PAUSE");
return 0;
126
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);
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;
}
128
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
variable, o incluso una operación después del return. En el caso de una
operación, el resultado de la operación es lo que el return devuelve. Para el caso
de esta función, no es necesario poner ningún código adicional, basta con poner
return num1+num2+num3. Para que probemos el funcionamiento, tenemos:
#include <stdio.h>
#include <stdlib.h>
return num1+num2+num3;
}
Como se ve, pusimos la función suma directamente como un argumento
de la función printf, y el %lf capturará directamente el valor double que retorne
la función suma. También es posible asignar el retorno de una función a una
variable, poniendo variable = suma (a, b, c). Para llamar a una función ponemos
su nombre y entre paréntesis los argumentos que se le van a pasar. En caso de
que la función esté puesta como una instrucción, lleva punto y coma al final. Si
la instrucción está puesta como argumento de otra función, no se pone el punto y
coma. Nótese también que para hacer operaciones dentro de la función se
utilizan los nombres de los argumentos que fueron pasados a la función, de tal
manera que cada argumento tiene el valor del número que fue pasado
exactamente en esa posición dentro de los argumentos durante la llamada. O sea
que dentro de la función, como la llamamos diciendo suma(a,b,c), entonces num1
que está en la primera posición atrapa el valor de a, num2 que está en la segunda
atrapa el valor de b, y num3 que está en la tercera posición adquiere el valor de c.
De esta manera, cuando estamos dentro de la función ya no se trabaja con a,b y c,
sino más bien con sus respectivas copias almacenadas en num1, num2 y num3,
de tal manera que aunque modifiquemos los valores de num1, num2 y num3
dentro de la función, los valores de a, b y c no se alteran. Esto se conoce como
pase de argumentos por valor. Más tarde veremos otro tipo de pase de
argumentos, pero por ahora lo dejaremos así. La función que acabamos de ver es
más didáctica que otra cosa, pero ahora veremos algo un poco más real.
1) Inicio
2) Capturamos num1 y num2
130
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);
num2 -= num1;
return num1;
}
/*
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;
132
void holamundo(void);
plataforma del depurador. Por otra parte, podemos usar “step into” como un
“next step” para el resto de los pasos. O sea, la única diferencia entre ellos es
que cuando el paso a ejecutar es una función, next step se salta esa parte,
mientras que step into se sumerge dentro de la función, explorando paso a paso lo
que va pasando en ella.
Hay diversas categorías dentro de estas ramas, pero para nuestros fines
solamente consideraremos dos:
2. Realice la función
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
136
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>
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);
137
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.
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);
139
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
142
#include <stdio.h>
#include <stdlib.h>
#define HORAS 24
system (“cls”);
printf(“\nLista de temperaturas\n”);
system(“PAUSE”);
return 0;
}
143
que le estamos asignando es más grande que el espacio del arreglo (ya que la
máquina cuenta al nulo).
#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);
esto no causa problemas, pero a largo plazo, eso se convierte en uno de los tipos
de errores “No profe…pero si yo lo probé ahorita mismo y funcionaba…no
entiendo… ¿que pasó?” y así comienzan las historias…
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);
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:
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);
valor = strcmp(saludo1,saludo2);
printf(“\n%d\n”, valor );
tiene un ASCII de 32, así que si se compara con la ‘a’ cuyo ASCII es 97,
alfabéticamente se considera que el espacio va antes por tener un ASCII
menor. Esta función retornara -1 ya que la cadena del primer argumento
es menor que la segunda, tal como establecíamos más arriba.
Así es, “Buen día” alfabéticamente va antes que “Buenas noches”, ya que
alfabéticamente se considera que el espacio está antes que la ‘a’.
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();
150
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.
152
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.
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];
debajo de la fila anterior y procedemos de la misma manera para cada fila. Por
ejemplo, para declarar esta matriz tendríamos:
código como los dos índices) podemos usar una matriz de dos dimensiones como
un arreglo de strings. Vamos a ver con ejemplos las dos aplicaciones:
#include <stdio.h>
#include <stdlib.h>
#define SEMANAS 4
#define DIA 7
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
157
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:
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.
159
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];
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.
161
¿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;
}
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
165
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
166
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í.
167
Supongo que a esta altura del juego cuando menos te has preguntado mil
veces que es lo que significa el encabezado del main:
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.
168
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
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.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}
172
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.
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
173
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
es posible. Parece una broma, pero es la realidad. Bueno, ya lo dije…y como
dicen por ahí… “guerra avisada no mata soldados…y si los mata es por
pariguayos…”
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
i=0;
printf ("\nEscriba una frase...\n");
free (frase);
system("PAUSE");
return 0;
}
177
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.
que la función no retornará nada, y sin embargo, alteraremos desde adentro los
parámetros que fueron pasados por referencia a la función. Ejemplo:
#include <stdio.h>
#include <stdlib.h>
cadena = entrelazado(cad1,cad2);
printf("%s\n", cadena);
system("PAUSE");
return 0;
}
if (*(string2 + j) != '\0')
{
*(cadentrelaz+i+j) = *(string2+j);
j++;
}
}
*(cadentrelaz+i+j)='\0';
180
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));
manejo de cadenas. Por ejemplo, vamos a inicializar ese arreglo como si fuera
un puntero en vez de declararlo como una matriz.
strcpy(amigos+0*19,"Patricia Alexandra");
strcpy(amigos+1*19,"Edgar Gonzalez");
strcpy(amigos+2*19,"Eduardo Baret");
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:
Solución
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
system("PAUSE");
return 0;
}
srand(time(NULL));
//Desde 0 mientras sea menor que numelementos, llenamos el arreglo
185
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
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
187
Fib 0 1 1 2 3 5 8 13 21 34 55
typedef struct
{
campos de la estructura
}nombre de la estructura ;
typedef struct
188
{
char nombre[50];
int matricula;
float indice;
}ESTUDIANTE
ESTUDIANTE nombrevariable;
ESTUDIANTE estudiantesdePUCMM[30000];
typedef struct
{
int p1;
189
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
typedef struct
{
int p1;
int p2;
int exf;
}NOTAS;
typedef struct
190
{
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
dirección, lo que es algo importante sobre estructuras. Si por ejemplo
quisiéramos manejar la dirección de la cadena nombre, para un strcpy por
ejemplo, podríamos decir:
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…
***No me gusta dejar espacio en blanco, pero poner la solución aquí estaría
muy mal… ***
193
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.
194
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:
195
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).
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…
196
#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);
fclose(archivo);
}
system("PAUSE");
return 0;
}
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);
199
scanf("%d",&num);
fprintf(archivo,"%d",num);
fclose(archivo);
printf("Salvado con %cxito",130);
}
system("PAUSE");
return 0;
}
#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);
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…
200
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…)
201
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…