0% encontró este documento útil (0 votos)
56 vistas52 páginas

Arrays

Este documento explica los pasos involucrados en compilar código fuente en C, incluyendo preprocesamiento, compilación, ensamblado y enlace. Explica cómo el preprocesador reemplaza las líneas #include con el contenido de los archivos de cabecera especificados. También describe cómo gcc compila el código a código objeto y luego lo enlaza con bibliotecas como cs50.c para crear un ejecutable, automatizando este proceso tedioso a través de make.

Cargado por

Dany Valverde
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
56 vistas52 páginas

Arrays

Este documento explica los pasos involucrados en compilar código fuente en C, incluyendo preprocesamiento, compilación, ensamblado y enlace. Explica cómo el preprocesador reemplaza las líneas #include con el contenido de los archivos de cabecera especificados. También describe cómo gcc compila el código a código objeto y luego lo enlaza con bibliotecas como cs50.c para crear un ejecutable, automatizando este proceso tedioso a través de make.

Cargado por

Dany Valverde
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd

Arrays

Lección 31 – Conceptos de C

Vamos a echar un vistazo a algunos de los conceptos que vimos


antes para que puedas comprender mejor algunas de las
características de C.

Anteriormente comenzamos con el más canónico de los programas


que se pueden escribir en cualquier lenguaje, que es el que
dice, "Hola, mundo". Pero antes de ejecutar este programa o
código fuente, tuvimos que convertirlo al lenguaje que
entienden las computadoras, que definimos como binario, 0 y
1, o lenguaje de máquina.

También introdujimos un comando para esto. Y ese comando se


llamaba make. Y a través de este comando, "hacer hola",
¿podríamos hacer un programa llamado hola? Y make fue un poco
elegante. Asumió que, si quieres hacer un programa llamado
hola, buscará un archivo llamado hola.c. Eso sucede
automáticamente para ti. Y el resultado final, fue un archivo
adicional llamado hola que terminaría en su directorio
actual. Entonces podría hacer ./hola y seguir su camino.

Pero make está automatizando un conjunto de pasos más


específicos para nosotros que veremos más de cerca ahora. Así
que en la pantalla aquí está el mismo código que escribimos
antes para decir, "hola, mundo". Y recuerde que cada vez que
ejecuta "make hola" o "make mario" o "make efectivo" o "make
crédito", verá una salida críptica en la pantalla. Con
suerte, no hay mensajes de error rojos o amarillos, pero
incluso cuando todo está bien, verá este texto blanco que
indica que todo ha estado bien.

Y antes ignoramos esto e inmediatamente hicimos algo como


./hola. Pero hoy, entendamos mejor qué es lo que hemos estado
haciendo de la vista gorda para que cada semana, a medida que
pasa, haya menos y menos que no entiendas en su totalidad con
respecto a lo que está pasando en tu pantalla.

Si hago ls aquí, veremos no solo hola.c, sino también el


programa ejecutable llamado [Link].

que creé a través de make. Pero mira esta salida. Hay alguna
mención de algo llamado clang aquí. Y luego hay muchas otras
palabras o frases crípticas, algo en lenguaje informático
aquí que tiene todos estos guiones delante de ellos.

Lo que hace make es automatizar la ejecución de un comando


llamado clang.

gcc es el compilador que convierte el código fuente en código


de máquina. Hemos estado usando gcc todo este tiempo. Pero
tenga en cuenta que gcc requiere un poco más de
sofisticación. Tienes que entender un poco más sobre lo que
está pasando para usarlo.

Elimino el programa [Link]. Voy a usar el comando rm que


vimos la última vez. Confirmo presionando y. Y si vuelvo a
escribir ls ahora, hola.c es el único archivo que queda.

Temporalmente, elimino la capacidad de usar make.

Y ahora digo gcc y luego el nombre del archivo que quiero


compilar, hola.c. Presiono Enter. Y no pasa nada, lo que
implícitamente significa que se ha compilado con éxito.

Si escribo ls, no veo el programa, [Link]. Veo este archivo


[Link]. Hace años, cuando los humanos usaban un compilador, el
nombre de archivo predeterminado que se le daba a cada
programa era [Link] para la salida del ensamblado. Pero este
es un nombre no descriptivo de lo que el programa hace.

Los programas como gcc se pueden configurar en la línea de


comandos, la terminal donde puede escribir comandos.
Elimino este archivo: rm espacio [Link], y confirmo con y. Y
ahora solo figura hola.c.

Voy a hacer "gcc -o hola" y luego la palabra "hola.c". Y lo


que estoy haciendo aquí es proporcionar un argumento de línea
de comandos. Comandos, como rm, a veces pueden ejecutarse
solos. Escribo una sola palabra y presione Entrar.

Pero a menudo, toman entradas. Escribes "make hola". Escribes


"rm hola". La segunda palabra, "hola", en esos casos, es una
entrada para el comando, también conocido como argumento de
línea de comando.

Aquí tenemos la palabra "gcc", que es el compilador que


estamos a punto de ejecutar, "-o", que es una notación
abreviada para "salida". La siguiente palabra es "hola". Y
luego la última palabra es "hola.c".
Este comando dice, ejecute gcc, genere un archivo llamado
hola y tome como archivo de entrada hola.c.
Cuando ejecuto este comando después de presionar Enter,
parece que nada vuelve a suceder. Pero si escribo ls, no veo
ese nombre de archivo [Link]. Ahora veo el nombre del archivo,
[Link].

Así es como gcc me está ayudando a compilar mi código. Es


como automatizar todos esos procesos.

Entro en mi archivo hola2.c Esta versión del programa


implicaba agregar cs50.h en la parte superior del archivo.
Luego solicitar al usuario una entrada usando la función
get_string de CS50, diciendo: "Ingresa tu nombre",
almacenando la salida en una variable llamada nombre y con un
salto de línea.

Y luego, en lugar de imprimir "hola, mundo", imprima "Hola,


%s", que es un marcador de posición, y mostrar el nombre de
la persona.

Elimino [Link] porque esa es ahora la versión anterior.

Así antes, la forma en que compilamos este programa fue "make


hola", no diferente de ahora. Pero supongamos que me deshago
de make, porque es una especie de pasos automatizados que
ahora quiero entender con más detalle.

Compilo este programa con gcc -o hola hola.c, que es una


aplicación de la idea de pasar tres argumentos, -o, hola y
hola.c.

Pero ahora voy a ver un mensaje de error. "referencia


indefinida a get_string".

Al usar una biblioteca, como la de CS50, a veces no es


suficiente incluir el archivo de cabecera en la parte
superior del código. También tiene que decirle a la
computadora dónde encontrar los 0s y los 1s para implementar
una función como get_string.

El archivo de cabecera, como cs50.h, solo le dice al


compilador que la función existe. Pero hay un segundo
mecanismo que le dice a la computadora dónde encontrar los 0
y 1 que implementan las funciones en ese archivo de cabecera.
Entonces, necesitaré agregar otro argumento de línea de
comando a gcc. Y al final de este comando: gcc -o hello
hello.c, voy a agregar cs50.c que se refiere al enlace en la
biblioteca CS50.

Este argumento "enlace" le dice a gcc que existe una función


como get_string.

cs50.h le indica que al compilar hola.c, se asegure de


incorporar el código de máquina de la biblioteca de CS50 en
tu programa.

Cuando presiono Enter, todo parece estar bien. Si escribo ls,


veo [Link]. Y puedo hacer ./hola, escribir mi nombre, Dany.
Y obtengo, "Hola, Dany".

Compilar su código escribiendo todos estos argumentos de la


línea de comandos se vuelve tedioso. Y así, los
programadores, tienden a automatizar pasos monótonos.

Entonces, lo que está sucediendo en última instancia con make


es que todo esto se está automatizando para nosotros.
Entonces, cuando escribió "make hola" la semana pasada, y de
ahora en adelante, puede continuar usando make también,
observe que genera este comando extra largo, del cual ni
siquiera hemos hablado. Pero reconozco el sonido metálico al
principio. Reconozco hola.c ver aquí. Reconozco -lcs50 aquí.

También hay otras cosas, no solo -o hola, sino también -lm,


que se refiere a una biblioteca matemática, -lcrypt, que se
refiere a una biblioteca criptográfica o de cifrado.

En resumen, nosotros, el personal, hemos preconfigurado make


para asegurarnos de que cuando compile su código, todas las
dependencias, bibliotecas, etc. necesarias, estén disponibles
para usted sin tener que preocuparse por todos estos
argumentos de la línea de comandos.

De ahora en adelante, ciertamente puede compilar su código de


esta manera usando gcc directamente. O puede regresar al
punto donde estuvimos la semana pasada y ejecutar "make
hola". Pero hay una razón por la que ejecutamos make hola,
porque ejecutar todos esos pasos manualmente tiende a
volverse tedioso.

Lección 32
Lo que hemos hecho aquí es compilar nuestro código. Y
compilar significa pasar del código fuente al código máquina.

Pero hoy, revelamos que hay un poco más, de hecho, debajo del
capó, esta "conexión" a la que me referí y un par de otros
pasos también.
Entonces cuando compila su código desde el código fuente
hasta el código de máquina, hay algunos pasos más que están
involucrados.

Cuando decimos "compilar", nos referimos a estos cuatro


pasos.

tal vez sea esclarecedor ver un breve recorrido de lo que


sucede cuando comienzas con tu código fuente y terminas
tratando de producir un código de máquina.

Este es el paso 1 que la computadora está haciendo por usted


cuando compila su código.

Preprocesamiento
El paso 1 toma su propio código fuente que se parece un poco
a esto. Y preprocesa su código, de arriba a abajo, de
izquierda a derecha. Y preprocesar su código significa que
busca cualquier línea que comience con un símbolo hash, así
que #include cs50.h, #include stdio.h.

Lo que hace el paso de preprocesamiento es como buscar y


reemplazar. Aquí hay una línea #include. Copio el contenido
de ese archivo, cs50.h, en tu propio código. De manera
similar, cuando encuentro #include stdio.h, el llamado
preprocesador, abre ese archivo, stdio.h, y copia/pega el
contenido de ese archivo para que lo que hay en el archivo
ahora se vea más como esto. Esto está sucediendo
automáticamente.

Antes vimos sobre estas líneas de código que tienden a ir en


la parte superior de su archivo, lo que hace es, está
definiendo las funciones en mi código para que la computadora
sepa qué hacer.

Porque nos encontramos con ese tipo de error antes, por el


cual estaba tratando de implementar una función llamada,
get_positive_int. Y cuando implementé esa función en la parte
inferior de mi archivo, el compilador no se dio cuenta de que
esa función existía porque estaba implementada en la parte
inferior de mi archivo.

Al mencionar esta función, una pista, por así decirlo, en la


parte superior, es como entrenar al compilador para saber de
antemano que aún no sé cómo se implementa, pero sé que
get_string va a existir al igual que printf.

Por lo tanto, estos archivos de cabecera que hemos estado


incluyendo contienen los prototipos, las sugerencias para las
funciones que existen en la biblioteca, de modo que su
código, cuando se compile, sepa de arriba hacia abajo que
esas funciones existirán.

El preprocesador nos ahorra la molestia de tener que copiar y


pegar todos estos prototipos, todas estas sugerencias,
nosotros mismos.

Es muy posible que haya otros archivos de cabecera y otros


contenidos en esos archivos. Supongamos que solo está el
prototipo. Así que ahora compilar tiene un significado más
preciso.

Compilación
Compilar su código significa tomar este código fuente en C y
convertirlo a otro tipo de código llamado código ensamblador.

Hay muchas computadoras diferentes en el mundo y hay muchos


tipos diferentes de CPU (Unidad Central de Procesamiento), el
cerebro de una computadora. Y una CPU entiende ciertos
comandos. Y esos comandos tienden a expresarse en este código
ensamblador.

Destaco algunos caracteres operativos aquí, observe que se


menciona main, get_string, printf. Esto es como una
implementación de nivel inferior de main, de get_string y
printf, en lenguaje ensamblador.

Así que escribes el código C. La computadora, lo convierte a


código ensamblador.

Y hace décadas, los humanos escribieron código ensamblador.


Pero hoy en día, tenemos lenguajes como C, Python, que son
más fáciles de usar. El código ensamblador está un poco más
cerca de lo que entiende la computadora.
Ensamblaje.

todo esto sucede cuando ejecuta make y, a su vez, este


comando, gcc.

Ensamblar su código significa tomar este código ensamblador y


convertirlo a código máquina, 0's y 1's. Así que escribes el
código fuente. El compilador lo compila en código
ensamblador. Luego lo ensambla en código de máquina hasta que
tenemos los 0s y 1s.

Vinculación
El paso final es que el código que escribió, debe vincularse
con los 0 y 1 que escribió CS50 y que los diseñadores del
lenguaje C escribieron cuando implementaron la biblioteca
CS50, y la función printf.

Esto quiere decir que cuando tiene un código como este que no
solo incluye los prototipos para funciones como get_string e
printf en la parte superior, estas líneas aquí son las que se
convierten en 0 y 1.

Ahora tenemos que combinar esos 0 y 1 con los 0 y 1 de


cs50.c, e incluir un archivo llamado stdio.c.

Y técnicamente, podría llamarse algo diferente debajo del


capó.

Hay tres archivos que se combinan cuando escribes tu


programa. El primero, una vez preprocesado, compilado y
ensamblado, tiene esta forma de 0s y 1s. Hay 0s y 1s que
representan a cs50.c. Hay otro archivo que representa los 0s
y 1s para stdio.c.

La vinculación, toma mis 0s y 1s, los 0s y 1s de CS50, los 0s


y 1s de printf, y los une a todos representando
colectivamente su programa, hola.

Lo que hemos estado haciendo antes es tomar estos cuatro


conceptos de bajo nivel y, si, abstrayéndolos para que nos
refiramos a todo este proceso como compilación.

Entonces, aunque, compilar es uno de los cuatro pasos, lo que


un programador suele hacer cuando dice compilar es referirse
a todos esos detalles de nivel inferior.
Y esto es lo que gcc están haciendo por usted, automatizando
este proceso de pasar del código fuente al código ensamblador
y luego al código de máquina y luego vinculándolo todo junto
con cualquier biblioteca que haya usado.

En el primer paso, cuando reemplazamos toda la información en


la parte superior, ¿esa información está contenida en el IDE?

Cuando está usando CS50 IDE, o, si está usando su propia Mac


o su propia PC, y ha preinstalado un compilador en su Mac o
PC al igual que tenemos CS50 IDE, lo que obtiene es un montón
de archivos .h en algún lugar del sistema informático.

También puede tener un montón de archivos .c, o versiones


compiladas de los mismos, en algún lugar del sistema.
Entonces, cuando descarga e instala un compilador, obtiene
todas estas bibliotecas agregadas para usted. Y preinstalamos
una biblioteca adicional llamada biblioteca de CS50 que viene
con su propio archivo .h y su propio código de máquina.

Todos esos archivos están en algún lugar del CS50 IDE, o de


manera equivalente, en su propia Mac o PC si está trabajando
localmente.

El compilador, gcc, sabe cómo encontrar eso porque uno de los


pasos involucrados en la instalación de su propio compilador
es asegurarse de que esté configurado para saber dónde están
todos esos archivos.

Entonces, siempre que compilamos hola, por ejemplo, ¿el


compilador también compila, por ejemplo, cs50? ¿O cs50 ya
existe en código de máquina en algún lugar debajo?

Probablemente cs50.c no esté instalado en el sistema. Y


técnicamente, stdio.c probablemente no esté instalado en el
sistema. no necesita serlo. Sería algo ineficiente, o lento,
si cada vez que compilara su propio programa, tuviera que
compilar el programa de cs50 y el programa de stdio, etc.

Lo que suelen hacer las computadoras es compilar previamente


esos archivos de biblioteca para que puedan vincularse de
manera más eficiente. Y no tiene que seguir preprocesando,
compilando y ensamblando código de terceros. Solo realiza
esos pasos en su propio código y luego vincula todo. Y ese es
el caso.
Hay muchas más líneas de código en esos archivos. Pero su
compilador solo usará las líneas que le interesan.

Lección 33

Antes fue un poco frustrante en algunos aspectos porque


probablemente tuvieron problemas. Te encontraste con errores,
errores en tu propio código. Probablemente vio uno o más
mensajes de error amarillos o rojos. Y es posible que haya
luchado un poco solo para compilar su código. Y de nuevo, eso
es normal. Eso desaparecerá con el tiempo.

Pero, cada vez que escribo C, digamos el 20% del tiempo,


todavía tengo un error de compilación, por no hablar de
errores lógicos, en mi propio código. Así que esto es solo
parte de la experiencia de escribir código.

Los seres humanos cometemos errores de todas las formas en


vida. Y eso también es cierto en el contexto del código,
donde la precisión es tan importante como la corrección. Y a
veces es difícil lograr ambos objetivos.

Consideremos cómo podrías estar más facultado para depurar tu


código, es decir, encontrar problemas en tu código.

Quizás el error más famoso es el que se muestra aquí en el


cuaderno de investigación de Grace Hopper, una científica
informática, que descubrió que había algunos problemas con la
computadora Harvard Mark II. La computadora estaba teniendo
problemas. Y, cuando los ingenieros observaron su interior,
había un bug, que se muestra aquí y se grabó en el cuaderno
de Grace Hopper. Este no fue necesariamente el primer uso del
término "bug", pero es un ejemplo muy conocido de un error en
una computadora. Hoy en día, hablamos un poco más
metafóricamente de que un bug es solo un error en un
programa.

Y le dimos algunas herramientas antes para solucionar


errores. Help50 le permite comprender mejor algunos de los
mensajes de error crípticos. Y eso es solo porque el personal
escribió este programa que analizó el problema que está
teniendo, y tratamos de traducirlo a un lenguaje más amigable
para los humanos.

Vimos una herramienta llamada style50, que no lo ayuda con su


corrección, sino solo con la estética de su código,
ayudándolo a sangrar mejor las cosas y agregar espacios en
blanco, es decir, líneas en blanco o caracteres de espacio,
por lo que es un poco más fácil de usar para el ser humano
para leer. Y luego check50, que, por supuesto, el personal
escribe para que podamos brindarle comentarios inmediatos
sobre si su código es correcto o no según los conjuntos de
problemas o la especificación del laboratorio.

Pero hay algunas otras herramientas que debe tener en su caja


de herramientas.

En el contexto de C, printf es una herramienta de depuración


universal. printf, es solo esta función que imprime cosas en
la pantalla. Pero es una herramienta poderosa a través de la
cual puede seguir problemas en su código.

E incluso después de que dejemos C e introduzcamos Python y


otros lenguajes,

Casi todos los lenguajes de programación tienen alguna forma


de printf, Tal vez se llama imprimir. Tal vez se llame como
lo era en Scratch, pero es decir cierta capacidad para
mostrar información o presentar información a un humano.

Así que tratemos de usar esta noción de printf, para


perseguir un error en el código de uno. Así que

Tenemos deliberadamente un programa con errores en el archivo


buggy0.c. Y en la parte superior de este archivo, tenemos
#include stdio.h. No se necesita la biblioteca CS50 para
este.
Y tenemos int main(void), que vimos la semana pasada, y lo
explicaremos con más detalle hoy.

Luego tenemos un bucle para imprimir en la pantalla una


columna vertical de 10 numerales, como una de esas capturas
de pantalla de Super Mario Bros.

Tenemos, int i = 0, para empezar a contar desde 0. Tenemos la


condición en este bucle for. Y hacemos esto 10 veces. Lo hace
menor o igual a 10. Luego tenemos el incremento, expresado
como i++. Y dentro de este bucle, imprimo un solo numeral
seguido de una nueva línea.

Voy a guardar el programa.


Compilamos el programa con gcc -o buggy0 buggy0
No tienes que usar gcc manualmente de esta manera. Es mucho
más simple abstraer eso, eso no es un comando, abstraer eso y
ejecutar make buggy0. Y make se encargará del proceso de
invocación de Clang por ti.

Parece estar compilando con éxito, por lo que no necesita


ayuda50.
el programa.

De hecho, si ejecuto style50 en este buggy0, todavía no tengo


ningún comentario.

Pero al menos se ve muy bien sangrado.


Pero déjame agregar ese comentario y hacer "Imprimir 10
hashes" solo para recordar mi objetivo.

Ejecutamos ./buggy0, Enter.

Y obtengo 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11.

Tenemos un bug.

Y tal vez salte a la vista obviamente para algunos de


ustedes. Pero tal vez sea un poco más sutil para otros de
ustedes. ¿Pero donde empiezas? Supongamos que fuera a
ejecutar check50. Y check50 fuera a decir, no,

imprimiste 11 hashes en lugar de 10. Pero mi código me parece


correcto, al menos a primera vista.

Bueno, ¿cómo puedo hacer para depurar esto o resolverlo?


Bueno, de nuevo, printf es tu amigo.

Para comprender más sobre tu programa, printf puede imprimir


temporalmente más información en la pantalla, no que queramos
en la versión final, pero que tú, el programador, puedas ver
temporalmente.

Antes de imprimir este numeral, imprimo:


"i ahora es %i \n".

Deseo saber, para mi propio cálculo, el valor de i en este


punto antes de imprimir ese numeral. Voy a pegar el valor de
i. Estoy usando %i como marcador de posición. Estoy
conectando el valor de la variable i.

Guardo mi código ahora.


Recompilo buggy0.
Lo ejecuto ./buggy0, Enter.

Ahora veo mi salida, mezclada con esa salida de diagnóstico,


o de depuración, diciéndome, "ahora soy 0", "ahora soy 1",
"ahora soy 2", punto, punto, punto, "ahora soy 9", "ahora soy
10". Está bien, no odio el hecho de que i sea 10. Comencé en
0 e imprimí un numeral, y llegué a 10 e imprimí otro numeral,
obviamente, ahí está mi problema.

Así que podría no haber sido mucho más obvio que mirar el
código en sí. Pero

Usar printf, puede ser mucho más aclaratorio de lo que está


sucediendo. Ahora veo, si empiezo en 0, tengo que subir hasta
10 y esto está de acuerdo con la convención de los
programadores.

Cambio mi código para que sea menor que 10 y recompilo


buggy0.

Déjame continuar y aumentar el tamaño de la ventana


nuevamente para poder ver temporalmente esto y

Ejecuto ./buggy0.

OK, empiezo ahora en 0, 1, 2, punto, punto, punto. Ahora me


detengo en 9. Y eso, por supuesto, me da 10 numerales.

Ya no necesito estos comentarios en el resultado final por lo


que elimino esta salida temporal.

Teniendo esos instintos, si no entiende por qué su código se


compila pero no se ejecuta correctamente, y quiere ver mejor
lo que la computadora está viendo claramente, su ojo mental,
use printf para decirse a sí mismo cuál es el el valor de
alguna variable o variables están en cualquier parte de su
código que desea ver un poco más de detalle.

Muy bien, permítanme hacer una pausa por un momento para ver
si hay alguna pregunta sobre esta técnica de usar printf para
comenzar a depurar su código y ver los valores de las
variables de una manera un poco más explícita. ¿No? Está
bien.
Voy a proponer una herramienta más poderosa con la que se
ahorrará horas, porque puede ayudarlo a ver lo que sucede
dentro de su código.

Esta herramienta se llama debug50 de CS50, que se basa en


GDB, GNU DeBugger, que es una herramienta estándar de la
industria que utilizan muchos sistemas informáticos para
brindarle la capacidad de depurar su código de manera más
sofisticada que solo usando printf.

Vuelvo a la versión con errores de este programa que me hizo


pasar del 0 al 10. Propuse que usáramos printf para ver el
valor de i. Pero, cuanto más grandes se vuelven nuestros
programas, más complicados se vuelven, más salida necesitan
tener en la pantalla. Se va a desordenar rápidamente si está
imprimiendo cosas que no deberían estar allí.

Piensa en la pirámide, este tipo de salida gráfica.


Rápidamente se volvería difícil de entender si está mezclando
esa pirámide con la salida textual de printf.

debug50, y a su vez un depurador en cualquier idioma, le


permite ejecutar su código paso a paso y mirar dentro de las
variables y otras piezas de memoria dentro de la computadora
mientras su programa se está ejecutando.

Casi todos los programas que ejecutamos tardan una fracción


de segundo en ejecutarse. Eso es muy rápido para el ser
humano, para pensar en lo que está pasando paso a paso. Un
depurador le permite ejecutar su programa, paso a paso, para
que pueda ver lo que está pasando.

Ejecutaré:
debug50 ./buggy0.
Voy a presionar Enter.
Y noten que cambié mi código. Lo volví a la versión con
errores. Arreglar esto: hago buggy0. Y ahora ya no tengo
errores.

Ejecuto debug50 nuevamente.

Voy a presionar Enter. Y observe este mensaje de error: no he


establecido ningún punto de interrupción. "¡Establezca al
menos un punto de interrupción haciendo clic a la izquierda
de un número de línea y luego vuelva a ejecutar debug50!"
debug50 necesita que le diga a la computadora con
anticipación en qué línea quiero entrar y paso a paso.

Voy a ir al lado del archivo aquí. Hago clic en el llamado


canalón, el lado izquierdo de la pantalla, en la línea 6. Y
eso automáticamente puso un punto rojo allí, como una señal
de alto.

Ejecuto debug50 ./buggy0 y presiono Enter.

Este elegante panel se abre en el lado derecho. Consideremos


lo que ha cambiado en la pantalla. La línea 6 está resaltada
en este tipo de color amarillo apagado. Eso se debe a que lo
que está haciendo debug50 es ejecutar mi programa, pero ha
detenido la ejecución en la línea 6. Ha hecho todo, desde la
línea 1 a la 5, pero ahora me está esperando en la línea 6.

Amplio esta ventana aquí. Centrémonos por un momento, no en


Expresiones de vigilancia, ni en Pila de llamadas, sino solo
en Variables locales. Tengo una variable llamada i cuyo valor
inicial es 0, y es de tipo int.

A través de estos íconos aquí arriba, puedo hacer clic en


esta línea Step Over y comenzar a recorrer mi código línea
por línea. Me alejo. hago clic en Pasar por alto. Y el
resaltado amarillo se mueve hacia abajo a la siguiente línea.
Si vuelvo a hacer zoom aquí arriba, el valor de i no ha
cambiado. Voy a pasar de nuevo. Y observe que el resaltado
amarillo se duplica. Eso tiene sentido porque estoy en un
bucle. Debería ir de un lado a otro.

Cada vez que regrese al comienzo del bucle, ocurre su


incremento, como el i++. En la esquina superior derecha,
cuando paso por encima ahora, el valor de i en mi depurador
acaba de cambiarse a 1. No tuve que usar printf. No tuve que
alterar la salida de mi pantalla. Puedo ver en esta interfaz
gráfica de usuario GUI en el lado derecho, cuál es el valor
de i.

Si hago clic un poco más rápido, a medida que se ejecuta el


bucle, una y otra vez, el valor de i sigue actualizándose.
Comenzamos en 0, si hago esto las veces suficientes, veré que
el valor es 10 ahora, lo que me da otra impresión en la parte
inferior, lo que explica los 11 numerales totales que vi.

No he recibido ninguna información nueva aquí. Pero he


obtenido información sin imprimir declaraciones printf en la
pantalla. Estoy observando más metódicamente lo que sucede
con el estado de mi variable en la parte superior derecha.

Compilo el código. Ejecuto debug50 en el código, pero solo


después de establecer un punto de interrupción, donde decide
de antemano dónde desea pausar la ejecución de su código.
Aquí lo hice al comienzo de mi programa, para programas más
grandes, será muy conveniente hacer una pausa a la mitad del
código y no tener que pasar por todo el proceso.

Voy a salir del depurador.

Step Into y Step Over

Escribamos otro programa que tiene una segunda función. Voy a


crear un nuevo archivo llamado buggy1.c deliberadamente
defectuoso.

Voy a #incluir cs50.h y #include stdio.h

Voy a colocar int main void.


Voy a usar una variable llamada i. Y voy a intentar obtener
un entero negativo llamando a una función get_negative_int.
Voy a imprimir este valor, "%i barra invertida n", i;

get_negative_int, es una función personalizada que debe


devolver un número entero. Puedes especificar su salida,
poniendo su valor de retorno primero en esta línea. Y luego
puedes poner el nombre de la función, como get_negative_int,
y luego entre paréntesis, puedes poner la entrada a la
función. Si no requiere una entrada, puede escribir la
palabra "void", que significa que no hay un valor aquí.

Voy a implementar get_negative_int. Voy a declarar una


variable llamada n. Luego a establecer n igual a get_int.
Voy a pedir al usuario un "Entero negativo" seguido de un
espacio. Seguiré haciendo esto mientras n sea menor que 0. Y
luego, en la última línea, devolveré n.

Afirmo que esta función me dará un entero negativo del


usuario. Y seguirá haciéndolo una y otra vez hasta que el
usuario coopere. En este momento, voy a cometer un error
deliberado: compilo buggy1, y hago Enter. Y veo un montón de
errores aquí.
"Error: la declaración implícita de la función
'get_negative_int' no es válida en C99".

Así que no sé todo eso, pero la declaración implícita de


función es algo que comenzarás a ver más a menudo si cometes
este error.

Dado que lo declaraste después de que ya lo usaste en tu


código, no sabe qué leer cuando lo está procesando. Debe
mover la primera línea arriba cuando inicia el código.

Voy a copiar la primera línea de esa función. Y voy a pegarlo


en la parte superior del archivo, dándome así una pista o
prototipo. Lo etiquetaré como tal para recordarme que es el
prototipo de esa función. Y voy a "Obtener un entero negativo
del usuario". Luego esta función queda tal como está escrita.

Ahora tengo este prototipo en la parte superior de mi


archivo. Compilo buggy1. Ejecuto ./buggy1
ingreso un número entero negativo, -1. -2, -3.
Hay un error.

Presiono Control-C para salir de mi programa porque, de lo


contrario, se ejecutaría potencialmente para siempre.

Ahora voy a usar debug50. No estoy escribiendo todo mi código


en main. Hay otra función llamada get_negative_int.

Establezco un punto de interrupción en la línea 10. Es


interesante en el sentido de que todo lo demás es repetitivo
en este punto. Solo tiene que hacerlo para que su programa
comience. Hago debug50 ./buggy1. Se abre esa barra lateral. i
es igual a 0 aquí de manera predeterminada. Voy a revelar
esta opción aquí, Pila de llamadas.

Por tanto, Call Stack es una forma elegante de referirse a


todas las funciones que su programa ha ejecutado en este
momento y de las que aún no ha regresado. En este momento,
hay una cosa en la pila de llamadas porque la única función
que se está ejecutando actualmente es, main, porque establecí
un punto de interrupción en la línea 10, que está dentro de
main.

Creo que las líneas 10 y 11..., se ven bastante correctas, Es


difícil en este punto haber estropeado las líneas 10 y 11,
excepto sintácticamente, porque obtengo un int negativo. Lo
almaceno en i, y luego imprimo el valor de i en esas dos
líneas.

Pero, ¿y si, en cambio, tengo curiosidad acerca de


get_negative_int? Me siento como el error; lógicamente, tiene
que estar ahí porque ese es el código más difícil que
escribí.

En lugar de hacer clic en Pasar por encima, hago clic en


Pasar por dentro. Cuando hago clic en Entrar, debug50 salta a
la función get_negative_int, y se enfoca en la primera línea
de código interesante. Int n no es tan interesante porque aún
no le está asignando un valor. La primera línea interesante
de código parece ser la línea 19. Es por eso que el depurador
saltó a esa línea.

n = get_int se siente bastante correcto. Es difícil hacer mal


uso de get_int. Pero ahora en el lado derecho, en Call Stack,
ahora se ve no solo main, sino también get_negative_int en
una pila. Es como una pila de bandejas en una cafetería. La
primera bandeja en la parte inferior es como la principal. La
segunda bandeja en la pila de la cafetería ahora es
get_negative_int.

Puedo ver mis variables locales, n, la variable que usé. Ya


no veo i. Veo n porque estoy en la función get_negative_int.
Si sigo haciendo clic en Pasar por encima una y otra vez
después de escribir un número. Escribo -1 aquí.

En la parte superior derecha de la pantalla, en el depurador


n es igual a -1. Hago clic en Pasar por alto. Y terminaré en
la línea 22. Si el humano ha escrito un número como -1,
obviamente, eso es un entero negativo. Procedamos a la línea
22. Cuando hago clic en Pasar por alto parece estar volviendo
al bucle do while una y otra vez, y sigo proporcionando
enteros negativos.

Mi lógica debería ser, Si n es -1 y, por definición, es un


número entero negativo, pero mi bucle aún se está ejecutando,
la conclusión de diagnóstico si el depurador le está
revelando esta pista, es que o la condición es incorrecta o
hay algo mal con mi lógica booleana.

Y la lógica booleana solo se refiere a verdadero o falso. En


algún lugar, digo verdadero en lugar de falso, o digo falso
en lugar de verdadero. Y, el único lugar donde tengo un
código que hará que este bucle se repita una y otra vez debe
ser en la línea 21. - 1 es lo que hay en la variable, pero el
bucle sigue en marcha.

La línea 21 es la fuente del error. De 23 líneas, hemos


encontrado una línea donde sé que tiene que estar la
solución. ¿Cuál es la solución?

En lugar de n menor que 0, quiero decir n mayor que 0. 0 no


es negativo. Y si quiero un entero negativo, lo que
probablemente querré decir es que mientras n sea mayor o
igual a 0, siga haciendo el ciclo. Comprensiblemente invertí
la lógica. Estoy pensando en cosas negativas, y lo hice menor
que. Pero la solución es fácil. El punto es que el depurador
te llevó a este punto.

De las 23 líneas, tiene que ser uno de esos. Pero a medida


que nuestros programas se vuelven más sofisticados y
comenzamos a escribir más líneas de código, debug50 y los
depuradores en general serán sus amigos.

Al principio, al usar un depurador, sentirás, pero solo voy a


usar printf. Debido a que hay una pequeña curva de
aprendizaje, recuperará ese tiempo y usará un depurador
cuando persiga problemas como este. Muy bien, eso es todo
para debug50, una nueva herramienta en su kit de herramientas
además de printf. Pero debug50 es sin duda el más poderoso de
los dos.

Algunos de ustedes se han preguntado en las últimas dos


semanas por qué hay este patito de goma aquí. Y en realidad
hay una razón para esto también. Y hay una última técnica de
depuración que, con toda seriedad, le presentaremos hoy
conocida como depuración de pato de goma. Y puedes googlear
esto. Hay todo un artículo de Wikipedia al respecto. Y esto
es una especie de cosa en los círculos de informática para
que los informáticos o programadores tengan patitos de goma
en su escritorio.

Y el punto aquí es que a veces, cuando tratas de entender qué


es lo que está mal en tu código, ayuda hablarlo. Y en un
mundo ideal, hablaríamos con nuestro colega o nuestro socio
en algún proyecto. Y con solo escucharte a ti mismo vocalizar
lo que se supone que debe hacer tu código, muy a menudo, esa
bombilla proverbial se apaga. Y dices, oh, espera un minuto,
no importa, lo entiendo, solo porque te escuchaste hablando
ilógicamente cuando pretendías algo realmente lógico.
Ahora bien, a menudo no todos tenemos colegas, socios o
amigos con los que estamos trabajando en un proyecto. Y no
solemos tener familiares o amigos que quieran escuchar acerca
de nuestro código de todas las cosas. Y así, un maravilloso
representante de ese compañero conversador sería, un patito
de goma. Y entonces aquí, en tiempos más saludables, les
estaríamos dando a todos ustedes patitos de goma. Aquí en el
escenario, trajimos uno más grande para que todos lo
compartamos. Si te has dado cuenta en algunos de los planos
generales de la cámara, hay un pato que ha estado observando
todo este tiempo. Así que cada vez que meto la pata,
literalmente tengo a alguien con quien puedo hablar de forma
no verbal, en este caso.

Además de printf, y de debug50, hablar sobre sus problemas


con el código es algo valioso. Y si sus amigos o familiares
están dispuestos a escuchar sobre algún código de bajo nivel
que está escribiendo y algún error que está tratando de
resolver, genial. En ausencia de eso, habla con un animal de
peluche en tu habitación. Habla con un pato de goma real si
tienes uno. Hable incluso en voz alta o piense en voz alta.
Es un maravilloso y convincente hábito para adquirirlo porque
con solo escucharte a ti mismo vocalizar lo que crees que es
lógico, muy a menudo lo ilógico salta a la vista.

Lección 34

Anteriormente vimos que C admite diferentes tipos de datos:


char, string, int, long, float, double.

Cada uno de estos } se define en un sistema informático como


si ocupara una cantidad fija de espacio.

Y depende de la computadora, cuánto espacio suele utilizar


estos tipos de datos.

Los tamaños de estos tipos son los siguientes:

Bool: Usa 1 byte (8 bits). Aunque para verdadero o falso,


solo deberías necesitar 1 bit, no se puede trabajar
fácilmente con un solo bit en C.

Char: Usa 1 byte. Vimos que, para ASCII, el número de


caracteres posibles que puede representar con un char era 256
o 2 a la octava potencia.
float, Utiliza 4 bytes. Es un número real con un punto
decimal.

double, Usa 8 bytes. Es un número real con un punto decimal,


pero con aún más precisión. Por ejemplo, puede tener dígitos
más significativos después del punto decimal.

int, Usa 4 bytes para representar in número entero.

long usa 8 bytes, y eso te permite representar un número


entero aún mayor.

Y algunos de ustedes podrían haberlo hecho exactamente a


crédito al almacenar un número de tarjeta de crédito
completo.

string, usa un número variable de bytes. Puede ser una cadena


corta o larga de texto o un párrafo completo.

Aquí hay una imagen de una pieza de memoria o RAM (memoria de


acceso aleatorio) que hay dentro de tu computadora.

Su tamaño dependerá de si se trata de una computadora


portátil, una computadora de escritorio, un teléfono o
similar.

Es en la memoria RAM, donde los programas se almacenan


mientras se ejecutan y donde se almacenan los archivos cuando
son abiertos.

Si guarda archivos o instala programas, estos se guardan en


su disco duro, disco de estado sólido, DVD, USB o algún otro
medio físico. Estos no requieren estar conectados a la
electricidad para mantener almacenados sus datos a largo
plazo.

La RAM en cambio, es volátil, es decir, requiere electricidad


para seguir alimentándola, pero es mucho más rápida porque no
utiliza partes móviles y es puramente electrónica y permite
que almacenar temporalmente cuando abre archivos y ejecuta
programas.

Si la batería de su computadora portátil alguna vez se agotó,


o su computadora se desenchufó, o su teléfono se descompuso,
se tiende a perder datos porque la RAM es volátil.
Cuando está ejecutando un programa en C, se está ejecutando
en la memoria RAM de su computadora.

En esta imagen cada uno de estos rectángulos es una especie


de chip. En estos chips están almacenados los 0s y 1s, los
interruptores a los que aludimos antes. Si nos acercamos más
a uno de estos chips, no sabemos qué tan grande es esta barra
de RAM, tal vez sea 1 gigabyte, mil millones de bytes. Tal
vez son 4 gigabytes más o menos. Hay una cierta cantidad de
bytes representados físicamente por este hardware. Podemos
enumerar estos bytes.

Podríamos pensar en esta memoria, como una cuadrícula, de


arriba a abajo, de izquierda a derecha. Cada uno de los
cuadrados que acabo de superponer en este dispositivo físico
podría representar un byte. Sin importar cuántos haya,
podemos pensar que cada uno de ellos tiene una ubicación.
Este es el primer byte, segundo byte, tercer byte, y así
sucesivamente.

Si la memoria de su computadora está ejecutando un programa


que usa una variable char, que ocupa 1 byte, el carácter que
está almacenando en esa variable puede estar almacenado
físicamente en la esquina superior izquierda o en otra parte
de esta pieza de RAM. Pero es sólo un cuadrado físico.

Si está almacenando un int, que ocupa 4 bytes, eso ocuparía


los cuatro cuadrados en la parte superior o en otro lugar.

Si está usando long, ocupará 8 bytes. Por tanto, representar


un número aún mayor en la memoria de su computadora requerirá
que use todos los 0s y 1s que componen estos 8 bytes.

Pensemos en la memoria de nuestra computadora como esta


cuadrícula de bytes. Podría dibujar estos bytes de izquierda
a derecha. Cada byte es de 8 bits. Esos bits son 0 y 1.

Cuando haces doble clic o ejecutas un programa en tu


computadora, son estos bytes en la memoria los que se llenan
con todos los valores de tus variables.

***************
Lección 35

Tenemos un programa llamado puntajes.c para promediar las


calificaciones de un estudiante en tres pruebas.

Tenemos #include stdio.h. Luego int main (void).

Tenemos una variable de tipo entero llamado puntaje1 igual al


valor 72. puntaje2 igual a 73, y puntaje3 igual a 33.

Vamos a imprimir el promedio de esos tres valores conectando


un marcador de posición para el valor de coma flotante, dado
que si sumas tres números enteros y los divides por 3, es
posible obtener número real con un punto decimal.

Uso %f en lugar de %i para no truncar una calificación. De


este modo, si obtengo un 99,9, se redondeará a 100 en vez de
que se trunque en 99.

El promedio será:
puntaje1 + puntaje2 + puntaje3) / 3;

Guardo ese archivo.

A veces parece que escribo muy rápido. En realidad, estoy


revisando mi historial en la terminal. Con las teclas de
flecha, arriba y abajo, puede retroceder en el tiempo para
ver todos los comandos escritos durante los últimos minutos,
horas o días. Esto le ahorra pulsaciones de teclas y tiempo.

Cada vez que pierde el control de un programa o tal vez,


escribiste un programa que tiene un bucle infinito, con
Control-C saldrás de ese programa.

Compilamos puntajes.c en la terminal. Obtenemos un error por


el %f en printf.: "El formato especifica el tipo 'doble',
pero el argumento tiene el tipo 'int'".

puntaje1, puntaje2, puntaje3 y el número 3 son enteros. El


compilador detecta que estoy tratando de convertir un
resultado entero en un valor de coma flotante, pero no he
hecho ninguna aritmética de coma flotante.

Antes propusimos convertir uno o más de esos valores en un


flotante. Podría hacer esto, pero la solución más sencilla es
dividir, entre 3,0 asegurando así de que haya al menos un
valor de punto flotante involucrado en esta aritmética.
Recompilo puntajes. Compila bien. Ejecuto ./puntajes, y
ahora, mi promedio es 59.333333

Consideremos la cuadrícula que representa la memoria de la


computadora dónde se almacenan estas tres variables puntaje1,
puntaje2, puntaje3.

Comienzo en la parte superior izquierda pero los valores


pueden estar en lugares diferentes de la memoria de tu
computadora.

puntaje1, el número 72, estará arriba a la izquierda, por


simplicidad. Cada casilla, representa 1 byte. Un número
entero es de 4 bytes. Así, he usado 4 bytes de espacio para
almacenar 72. El 73 en puntaje2 también ocupa cuatro
casillas, al igual que 33 en puntaje3.

Dado que cada casilla representa 1 byte, y cada byte tiene 8


bits, y 1 bit es un 0 o un 1, esta memoria electrónica está
almacenando electricidad para representar estos patrones de
0s y 1s, llamados 72, 73 y 33 en decimal.

Estamos asignando espacio en la memoria a través de nuestro


código de lenguaje de programación.

Lección 36

Aparte de la corrección y el estilo, el diseño es una métrica


de la calidad del código. El "olor del código" es un término
de arte. El código huele mal, cuando no tiene un buen diseño.

En este caso si estoy calculando el promedio, no necesito


almacenar las tres variables juntas en el código. Puedo
almacenar su suma en una variable y luego dividir esta suma
total por el número total.

Este programa calcula el promedio para un estudiante que


obtuvo esos tres puntajes en las pruebas, pero si quiero
tener una cuarta prueba, ahora tengo que agregar puntaje4 y
si tengo una quinta prueba, puntaje5. No hay dinamismo aquí.

Podemos limpiar esto a través de otra característica de C y


de otros lenguajes, llamada Arrays, Matrices o Vectores, muy
similar las listas de Scratch.
Un Array es una secuencia de valores almacenados en la
memoria de manera contigua, espalda con espalda. Es como una
lista de valores de izquierda a derecha de acuerdo a la
cuadrícula de memoria.

Si quieres almacenar valores, pero todos están


interrelacionados, como en este caso todos son puntajes, no
tienes que almacenar puntaje1, puntaje2, puntaje3, puntaje4,
puntaje5, hasta cuántas puntuaciones haya. Mejor sería llamar
puntajes a todos esos números, y usar una sintaxis diferente
que te de acceso a los Arrays.

La sintaxis aquí es un ejemplo de declarar espacio para tres


enteros a la vez y referirse colectivamente a todos ellos
como la palabra "puntajes".

Los tres puntajes que teníamos están ahora en una variable


llamada "puntajes". Dentro de estos corchetes, hay un número
que indica cuántos enteros desea almacenar bajo el nombre de
"puntuajes".

Esto me permite definir tres enteros en ese Array. Así que


este Array va a ser una sección de memoria contigua en la que
puedo poner valores. La forma en que pongo esos valores se ve
sintácticamente así. Estoy usando una nueva notación, pero
ahora es más generalizada y dinámica.

Para actualizar el primer puntaje en ese Array, escribo el


nombre de la variable puntajes[0] y luego le asigno el valor.
Para el segundo puntaje, hago puntajes[1]. Para el tercer
puntaje, puntajes[2]. Estamos "indexando a cero" nuestros
arrays.

En los ejemplos anteriores de bucles for y while, vimos que


es una convención en programación comenzar a contar desde 0.
También para los arrays, de lo contrario, estarás
desperdiciando espacio al pasar por alto un valor.

En lugar de llamar a estos tres rectángulos puntaje1,


puntaje2, puntaje3, todos se llamarán puntajes. Para
referirse al primero, segundo o tercero usas esta notación de
corchetes.

Al declarar el Array, diciendo, dame tres enteros, usas [3]


donde [3] es el número total de valores. Cuando indexas en el
Array, es decir, cuando vas a una ubicación específica en esa
porción de memoria, usas números de manera similar. Pero
ahora se refieren a sus posiciones relativas, posición 0,
posición 1, posición 2. Este es el número total de espacios.
Este es el espacio específico primero, segundo y tercero.

A continuación vamos a mejorar el diseño de este programa y


deshacernos de su mal olor.

Lección 37

Para mejorar el diseño de este programa, primero en lugar de


las tres variables separadas, declaramos una variable llamada
puntajes en la forma de un Array de tamaño 3.

Para inicializar ese Array con estos tres valores, tenemos


puntajes[0], puntajes[1], puntajes[2].

Si tuviera un cuarto puntaje, lo asigno escribiendo 4 dentro


de los corchetes del array y luego pongo su valor en una
nueva línea de código. De la misma forma si tuviera 5, 6, 7 o
más puntajes. Esto es más dinámico que copiar y pegar
diferentes nombres de variables.

Al hacer los cálculos, quiero puntajes[0] más puntajes[1] más


puntajes[2]. La lógica sigue igual, pero ahora estoy tomando
dinámicamente tres enteros.

Para solicitar un puntaje usamos get_int declarando primero


la biblioteca cs50. Hacer esto para cada puntaje, en este
caso, tres veces sugiere que hay una especie de patrón y que
podríamos hacerlo mejor usando un bucle.

y, como entrada, pedir cuántos puntajes quiere al principio.

Ahora, en lugar de solicitar puntajes[0], puntajes[1],


puntajes[2] usamos un bucle for con una variable i que va de
0 hasta una variable TOTAL para indexar o ir a una ubicación
específica en un array y solicitar el puntaje
correspondiente.

for (int i = 0, i < TOTAL, i++)

He reducido mis líneas de código de las tres casi idénticas,


a una sola dentro de un bucle que va a hacer lo mismo una y
otra vez.
No tengo que codificar estos 3 por todos lados. Podría
preguntarle al humano desde el principio cuántos puntajes en
total hay:

int total = get_int("Número total de puntajes: ");

Luego, puedo usar esta variable, total, en varios lugares, de


modo que estoy haciendo mis cálculos más dinámicamente.

Antes voy a definir una característica de C y de muchos


lenguajes programación, llamada constante. Una constante es
una variable, la cual una vez que estableces su valor, no
puedes cambiarla en el contexto de este programa. Esto es
conveniente para declarar un número que deseo usar una y otra
vez sin tener que copiarlo y pegarlo. Para declarar una
constante escribimos:

const int TOTAL = 3;

Esto también te brinda una protección en caso accidental o


cuando no sea tu intención cambiar su valor.

Otra convención al declarar una constante es ponerla en


mayúsculas para dejar claro que es una variable especial.

Utilizamos ese valor TOTAL en el array, en el bucle y en el


cálculo del promedio.

En main en int puntajes[TOTAL];

es necesario declarar el array en porque la computadora


necesita saber qué tan grande es el array cuando lo está
creando.

En el cálculo ya no escribimos puntajes[0], [1] y [2] porque


si el número total de las puntajes son más o menos que 3,
necesitaré sumar un número cambiante de valores.

Para resolver esto, creamos una función llamada promedio que


calcule el promedio. Y las entradas para esta función son la
longitud del array y el array mismo.

float promedio(int longitud, int array[]);


Cuando pasas un array de una función como entrada para otra
función personalizada, usas estos corchetes sin especificar
el tamaño o qué tan grande es el array porque se trata de una
función genérica y más dinámica cuyo propósito es admitir o
tomar como entrada un array de enteros con uno, dos, tres, o
más elementos y responder con un promedio que coincida con la
longitud de esa entrada.

A diferencia de Java y otros lenguajes, en C la longitud de


un array no está integrada en el array. Por ello, en C debes
pasar tanto el array como su longitud por separado.

Para calcular el promedio dentro de esta función, hacemos una


suma de los números y lo dividimos por el total:

int suma = 0;

Teniendo como entradas de esta función, la longitud del array


y el array mismo, utilizo un bucle for desde i = 0 hasta el
valor de la longitud, y luego incremento en 1 el valor de i
en cada iteración.

for (int i = 0, i < longitud, i++)

En cada iteración, el valor de la suma será igual el valor


que tenga en ese momento la variable suma más el valor que
esté en la ubicación i del array.

{
suma += array[i];
}

Esta es la notación abreviada de suma = suma + array[i]; que


vimos anteriormente.

Luego la función devuelve la suma total dividida por la


longitud del array. Este valor debe ser de punto flotante,
para no truncar ningún cálculo, pero como estoy dividiendo un
entero por un entero, debo asegurarme de hacer de tipo float
al menos uno de ellos:

return suma / (float) longitud;

En main, se abstrae el cálculo del promedio haciendo:


printf(“Promedio: %f\n”, promedio(TOTAL, puntajes);

Aquí imprimo el promedio de un marcador de posición de coma


flotante, pero ahora estoy pasando como entrada esta función
promedio, cuyas entradas son TOTAL, que es esta constante en
la parte superior, y puntajes, que, es este array de esos
puntajes.

Aunque en main los nombres de las variables que se pasan a la


función promedio se llaman TOTAL y puntajes, en el contexto
de esta función, puedo llamarlos x e y, a y b, o longitud y
array. Aquí no sé cuál es el array, pero es un array de
enteros, y no sé cuál es su tamaño, pero esa respuesta va a
estar en longitud.

Aplicamos algunos de los principios vistos anteriormente.


Tengo una variable, tengo un bucle y estoy haciendo
aritmética de coma flotante. Ahora estoy creando una función
que toma dos entradas. Uno es la longitud y el otro es el
array en sí y el tipo que devolvemos es un float, por lo que
mi salida también está bien definida.

Estas funciones son abstracciones. No me preocupo por cómo


calculo un promedio porque ahora tengo esta función
personalizada para ello. La salida de esta función promedio
se convertirá en una entrada en printf.

La otra característica que he agregado aquí no son solo


arrays, que nos permiten crear múltiples variables, sino
también esta noción de constante. Si me encuentro usando el
mismo número una y otra vez, esta constante puede ayudarme a
mantener mi código limpio.

Si el próximo semestre o año, hay cuatro puntajes o cuatro


exámenes, lo cambio en un solo lugar, vuelvo a compilar y
listo. Un programa bien diseñado no requiere que lo leas en
su totalidad. Cambiarlo en un lugar puede permitirme
mejorarlo, hacer que admita cuatro pruebas el próximo año en
lugar de solo tres. Pero mejor aún sería usar get_int y
preguntarle al humano cuántas pruebas tiene en realidad.
Lección 38

En este ejemplo, en la función main, cuando llamo a la


función obtener_entero_negativo, vemos que ésta carece de
entradas dentro de esos paréntesis.

La función obtener_entero_negativo toma void como su entrada,


lo que significa que no toma ninguna entrada.

void, es una palabra clave explícita en C que indica que


sería incorrecto pasar un número o un aviso, dentro de esos
paréntesis.

Para la función obtener_entero_negativo, no hay necesidad de


parametrizar o personalizar un comportamiento, solo se quiere
obtener un entero negativo.

Por el contrario, la función promedio, requiere tomar


entradas, porque no puede obtener el promedio, sin antes
recibir la información respectiva. Sus entradas para que
pueda calcular el promedio son: el array de números y la
longitud de ese array.

Cuando no quieres tomar entradas usas void pero cuando deseas


recibir información, especificas argumentos separados por
comas.

Los arrays y la memoria se emplean para crear algunas


características de la mayoría de los programas de
computadora, a saber, texto o cadenas.

Escribamos un programa que solo cree un solo ladrillo de ese


juego de Mario Brothers.

Creo un programa aquí llamado ladrillo.c donde incluyo:

#include <stdio.h>

int main(void)
{
    char c = '#';

    printf("%c\n", c);
}
Este es un simple programa cuyo único propósito es imprimir
un solo numeral como lo podrías tener en una pirámide de
Mario Brothers de altura 1.

Compilo y luego ejecuto el programa. Y obtenemos un solo


ladrillo. Pero consideremos lo que realmente estaba
sucediendo.

Veamos qué sucede si no imprimo c, que es este carácter


numeral, como %c, que es un marcador de posición para un
carácter, sino cómo %i. Podría forzar este carácter
convirtiéndolo en un entero para poder ver su equivalente
decimal, su código ASCII real.

Compilamos y Ejecutamos el programa. Y obtenemos 35, que es


el código ASCII para el numeral #.

Pueden verificarlo si van a un sitio web como [Link].


En este gráfico vemos que, para el símbolo numeral, el código
ASCII es 35.

En C, no tienes que convertir explícitamente un carácter en


un entero. En su lugar, puede convertir implícitamente un
tipo de datos a otro solo desde el contexto. printf y C saben
que estás dando un carácter en forma de una variable c y que
deseas mostrarlo como un %i, un número entero.
Es una simple conversión.

Si esto representa mi memoria RAM, c almacena este numeral,


en uno de estos bytes. Solo requiere un cuadrado porque, un
carácter es un solo byte. Pero de manera equivalente, 35 es
el número que en realidad se almacena allí.

Anteriormente almacenamos no solo caracteres individuales,


sino palabras como "hola" y otras expresiones.

En este código tenemos cinco variables: c1, c2, c3, c4 y c5


que tienen almacenadas deliberadamente H, O, L, A, en
mayúsculas, seguido de un signo de exclamación.
En C, cuando se trata de caracteres individuales, debes usar
comillas simples, pero cuando trabajes con varios caracteres
o cadenas, usa comillas dobles.

Se imprimirá %c, %c, %c, %c, %c y se generará c1, c2, c3, c4


y c5. Es decir, se imprimirá la palabra "¡HOLA!" almacenando
cada carácter en su propia variable. Estoy usando %c como mi
marcador de posición.
Compilamos y ejecutamos el programa, y obtenemos HOLA!

Si quiero cambiar el nombre de mi archivo de ladrillo.c a


saludo.c, uso el comando mover, mv. Si abro este archivo,
tenemos saludo.c. Si compilo y ejecuto veo el "HOLA!"

Ahora voy a imprimir %i, %i, %i, %i y %i que incluya espacios


esta vez para poder ver la separación entre los números.

Compilamos y ejecutamos.

Y obtenemos 72 79 76 65 y 33.

Estos cinco caracteres, los estoy almacenando en cinco cajas


diferentes, c1, c2, c3, c4, c5 en la memoria de la
computadora. Cuando lo miras en conjunto, se ve una palabra
completa, pero son caracteres individuales. Lo que hay en
realidad es 72 79 76 65 y 33 o su equivalente en binario.

Estamos hablando de caracteres en vez de números enteros.

Lección 39

En este programa, tenemos una cadena o string en mayúsculas y


entre comillas dobles:

string s = "HOLA!";

%s es un marcador de posición para string.

Cada lenguaje de programación tiene "strings". C no tiene un


tipo de datos llamado string, pero se ha agregado este tipo a
C a través de la biblioteca de CS50.

Si compilo este código y luego lo ejecuto, obtengo:


"HOLA!", que es lo que habíamos obtenido anteriormente.

Dado que "HOLA!" tiene cinco letras, se almacena en cinco


cajas en la memoria de la computadora y le llamo s a esta
cadena.

Podemos representar secuencias de números enteros tomando


otro tipo de datos como carácter o char. Si queremos
deletrear palabras con esos caracteres, entonces una cadena
se define como un array de caracteres.
“HOLA!", técnicamente hablando es un array llamado s.
Esto es s[0] Esto es s[1]. Esto es s[2]. Esto es s[3]. Esto
es s[4].

No usamos el array de palabras antes porque no es tan


familiar como la noción de una "cadena de texto".

Que sea un array significa que podemos acceder a sus


caracteres individuales mediante la notación de corchetes.

Pero resulta que hay algo un poco especial en las cadenas a


medida que se implementan.

En nuestro ejemplo con puntajes, sabíamos la longitud de ese


array porque, no solo asignamos el array mismo como primera
variable de entrada, sino que también hicimos un seguimiento
de cuántos elementos constaba ese array utilizando una
segunda variable de entrada llamada longitud que almacenaba
el número total de enteros en ese array.

Hasta ahora a la función printf, solo le hemos brindado una


cadena s o el array de caracteres mismo.

Pero printf calcula cuál es la longitud del array e imprime


el valor de s, es decir H, O, L, A, signo de exclamación.

Si su cadena tiene una longitud de 5, como en H, O, L, A,


signo de exclamación, técnicamente en realidad esta cadena
utiliza 6 bytes en total. El sexto byte lo utiliza para
inicializar un carácter o valor especial conocido como
carácter nulo, denotado como barra invertida \0 y que
representa el final de una cadena.

Esto es equivalente a una señal de alto para que la


computadora sepa dónde termina la cadena "HOLA!" y donde tal
vez comience la siguiente cadena. Dado que habrá otros
elementos en la memoria de su computadora si tiene otras
variables u otros programas ejecutándose.

No basta comenzar a imprimir caracteres dentro de printf uno


a la vez, de izquierda a derecha.

Convirtamos los valores a decimal:


72 79 76 65 33.
Esa barra invertida \0 es solo una forma de decir, en forma
de carácter, es 0. Más específicamente, son ocho 0 bits
dentro de ese cuadrado.
Para almacenar una cadena, la computadora ha estado usando un
byte adicional, todo de 0 bits, escrito como barra invertida
\0, que es un carácter especial llamado valor 0 o NUL.

En este gráfico, el número 0 es llamado NUL.

Volvemos al código para mejorar este programa.

Si quiero imprimir estos caracteres de s, puedo imprimir %c,


%c, %c, %c, %c.
Si s es un array, puedo hacer s[0], s[1], s[2], s[3], s[4].

Guardo, recompilo y ejecuto el código y obtengo "HOLA!"

Si uso %i para poder ver esos códigos ASCII. Recompilo y


ejecuto, obtengo los 72 79 76 65 33.

Ahora imprimo un sexto valor como s[5]. Compilo y ejecuto, y


obtengo 0.

Esta es una característica muy peligrosa de C. Si imprimo


s[6], según mi imagen, no debería haber nada ahí. Pero ahora
compilo y ejecuto y obtengo el número 37. En a la tabla ASCII
vemos que el número 37 es un signo de porcentaje. Pero
explícitamente no imprimí un porcentaje. Estamos buscando en
la memoria de la computadora en lugares que no deberíamos.

Si miramos la ubicación 40, compilamos y ejecutamos,


obtenemos 101. Puedo mirar la ubicación 400, recompilo y
ejecuto y obtengo 114.

Esto es lo poderoso y peligroso de C. Puedes mirar, cambiar


cualquier valor de memoria. Solo tenemos el código de honor
para no tocar la memoria que no te pertenece, pero esto puede
ocurrir accidentalmente y hacer que los programas de la
computadora se bloqueen, otra fuente más de errores comunes.
Lección 40

Consideremos cómo podríamos almacenar varias cadenas en un


programa.

Si necesitamos dos cadenas, es una convención en programación


llamar a la primera s y a la segunda t.

Podría almacenar: "HOLA!" y “CHAO!"

"HOLA!", se almacenará aquí. Todo esto se refiere a s, y está


tomando 6 bytes porque el último es ese carácter nulo que es
la señal de alto que marca el final de la cadena.

"CHAO!", ocupará otros seis bytes incluyendo otro sexto byte


para representar el carácter nulo. Esto representa a t.

Aunque en la realidad no necesariamente hay una cuadrícula,


éste es un modelo para ilustrar lo que sucede dentro de una
computadora.

Si comienzo a hurgar en la memoria de la computadora usando


la notación de corchetes, mirando un poco más allá de la
cadena s, podría acceder al valor de C o de H o de A o de O.

A pesar de lo complicado que se vuelven nuestros programas,


lo que sucede en realidad es que se almacenan valores en la
memoria en ubicaciones como estas.

Consideremos algunas de las funciones que puedes usar en los


programas que escribes.

Tenemos aquí un programa llamado cadena.c que imprime la


longitud total de una cadena.

int main(void)
{
    string s = get_string("Entrada: ");
    printf("Salida: ");
    for (int i = 0; s[i] != '\0'; i++)
    {
        printf("%c", s[i]);
    }
    printf("\n");
}
Tenemos las librerías cs50 y stdio
En main, obtengo una cadena pidiéndole al humano una Entrada:
Luego imprimo la palabra "Salida" que acompañará al
resultado.

Para imprimir esa cadena utilizo un bucle for. Hasta ahora,


cuando he usado bucles for, he contado desde 0 hasta algún
número. Pero esta condición puede ser cualquier expresión
booleana. Solo necesito tener una respuesta sí/no o
verdadero/falso.

Considerando esto, itero desde el carácter en la posición i =


0, Pero ahora no estoy comprobando una longitud
predeterminada porque no sé a priori dónde termina esta
cadena. Solo sé que terminan una vez que veo la barra
invertida 0. Por tanto, seguiré iterando incrementando i en
uno. mientras se cumpla la condición de que el carácter en la
ubicación i o el i-ésimo carácter en s, a saber: s[i], no sea
igual a barra invertida \0.

El signo de exclamación igual significa: es diferente a.

Dentro del bucle, imprimo un carácter a la vez usando la


notación de array.

Al final del programa, imprimo una nueva línea para asegurar


de que el cursor esté en su propia línea.

Este es un programa que, va a tratar una cadena como un


array, por tanto, la sintaxis usa la notación de corchetes.

Bang es igual a bang es como un programador pronuncia el


signo de exclamación porque es un poco más rápido bang es
igual a no es igual. Así es como harías un signo igual con
una barra oblicua en matemáticas. Es, en código, signo de
exclamación, signo igual.

Barra invertida 0 es la forma de expresar el "carácter nulo",


pero está entre comillas simples porque es por definición un
carácter. Al igual que barra invertida n es un carácter de
escape a una nueva línea, barra invertida 0 es un carácter
que es todo 0.

Ahora compilo y ejecuto, escribo "HOLA" en mayúsculas y la


salida es "HOLA". Lo hacemos nuevamente con “CHAO”, y el
resultado es "CHAO". Este es un programa que está imprimiendo
lo mismo que escribí. Pero estoy usando condicionalmente esta
expresión booleana para decidir si seguir imprimiendo
caracteres o no.

Pero C ya viene con una función llamada strlen que existe en


una biblioteca a través del archivo de cabecera string.h, y
con la que puedo averiguar cuál es la longitud de la cadena.

Compilo, ejecuto y escribo "HOLA" y el programa funciona.

¿cómo lo escribieron? Lo más probable es que escribieron la


versión que hice al verificar esa barra invertida 0.

Este programa es correcto. Recorre toda la longitud de la


cadena e imprime todos los caracteres que contiene.

No he hecho algo de manera óptima desde el punto de vista del


diseño en la línea 9.

Sin embargo, este diseño requiere de una optimización.

El bucle for, está constantemente comprobando la condición,


que en este caso es i menor que la longitud de la cadena s.

Esta expresión booleana se evalúa una y otra y otra vez. Pero


strlen es una función, lo que significa que hay un fragmento
de código, que constantemente va a preguntar cuál es la
longitud de la cadena.

Y en nuestra imagen, cuando calculas la longitud de una


cadena comienzas al principio de ésta y compruebas si estás
en barra invertida 0 y sigues.

De modo que para calcular la longitud de "HOLA!" me tomará 6


pasos, porque tengo iterar desde la ubicación 0 hasta el
final. De igual modo para el caso de "CHAO!".

Dado que, una vez que el humano escribe la palabra, ésta no


va a cambiar en este contexto, no tiene sentido pedir la
longitud de la cadena s una y otra vez.

Para corregir esto, hago que int n sea igual a la longitud de


cadena de s. Pero ahora estoy haciendo la misma pregunta al
humano una sola vez.
Y ahora en lugar de esta función strlen(s), pongo n en esta
condición.

Así que, pero no de manera ineficiente como cuando requería


encontrar la barra invertida 0 una y otra vez.

Existe otra característica de los bucles for. Si deseas


inicializar otra variable a un valor, puedes hacerlo todo a
la vez antes del punto y coma.

Puedes hacer n igual a strlen de s. Luego puedes usar n,


aquí. Esto es más limpio ya que tomé dos líneas de código y
las colapsé en una sola. Pero ambas variables tienen que ser
del mismo tipo de datos, como en este caso lo son i y n.

La ineficiencia aquí es que antes seguía haciendo la misma


pregunta una y otra vez. Ahora estoy haciendo la pregunta una
vez, recordándola en una variable llamada n, y solo
comparando i con ese número entero que no cambia.

Lección 41

Comencemos a ver aplicaciones de estos componentes básicos en


el campo la legibilidad del lenguaje y la criptografía.

Si deseas cifrar información de mensajes escritos, podrías


necesitar convertir caracteres ASCII de modo que, si tu
mensaje fuese interceptado por un tercero, no puedan
descifrar qué es lo que has enviado.

En el código, podemos convertir una palabra en otra o


codificar nuestro texto.

Aquí tenemos dos palabras en la memoria de la computadora:


"HOLA!" y "CHAO!", con signos de exclamación y barras
invertidas 0 que no ponemos explícitamente pero que estarán
presentes cuando utilizamos string o la función get_string.

Si llamo a estas cadenas s y t respectivamente, dado que una


cadena es un array, puedo hacer referencia a estos caracteres
individuales usando la notación de corchetes, por ejemplo
s[0], s[2], o t[3], t[5].

Si tenemos un array de palabras. La sintaxis es idéntica a la


que usamos para una serie de puntajes o de números enteros.
Aquí defino una variable como un array que llamaremos
palabras y cuyo tamaño será 2,

string palabras[2];

Cada elemento del array será una cadena y las llamo


"palabras" en dos lugares diferentes, 0 y 1 respectivamente:

palabras[0] = "HOLA!";
palabras[1] = "CHAO!";

Podemos redibujar la imagen de esta forma:


Esta palabra es llamada palabras[0].
Y ésta otra es llamada palabras[1].

Entonces tenemos un array de palabras, pero una palabra es


una cadena, y una cadena es un array de caracteres. Por lo
tanto, tenemos un array de arrays o array bidimensional.

Esta sintaxis, puede tener corchetes consecutivos.


Para obtener la primera palabra de este array, escribes
palabras[0] y obtienes "HOLA!" y para obtener el primer
carácter de esa palabra, escribes [0] a la derecha y obtienes
H. El primer corchete se refiere a la palabra elegida en el
array y el segundo se refiere al carácter elegido en esa
palabra. De este modo, la H sería palabras[0][0], la O es
palabras[0][1] y así sucesivamente. Asimismo, la C sería
palabras[1][0], la H es palabras[1][1], etc.

Aquí tenemos un programa llamado versal cuyo propósito es


convertir una palabra de entrada a mayúsculas.

#include cs50.h
#include stdio.h
#include string.h que nos dará funciones como strlen.

int main (void)

En main, Obtengo una cadena del usuario.

Luego voy a imprimir "Posterior", para ver qué sucede luego


de poner en mayúscula todo en la cadena.

for (int i = 0, n = strlen(s); i < n; i++)

Usando el bucle for, itero sobre cada carácter de la cadena


obtenida.
Inicializo i a 0 y n que obtiene el valor de la longitud de
cadena de s.
Sigo incrementando i hasta n.

Compruebo si el carácter actual en la ubicación i en s está


en minúsculas, es decir si el carácter s[i] >= a minúscula y
<= a z minúscula.

De ser así, imprimo s[i] – 32 para convertirlo a mayúsculas.


De lo contrario, imprimo s[i] sin cambios.

Este símbolo && es el operador lógico AND, que verifica una


condición y otra y es su resultado es verdadero si ambas
condiciones son verdaderas.

En el código podemos usar implícitamente el equivalente en


números de a minúscula y z minúscula que en el gráfico ASCII,
tienen asociados los números 97 y 122 respectivamente, pero
esto haría que el código sea menos obvio y menos amigable.

En la tabla ASCII la a minúscula es 97 mientras que la A


mayúscula es 65. Asimismo, la b minúscula es 98 y la B
mayúscula es 66. Observando estos pares de números: 65 a 97,
66 a 98, y en general para las 26 letras en inglés,
minúsculas y mayúsculas, siempre están separadas por 32.

Por esta razón, para obtener la letra A mayúscula, tomo la


letra a minúscula que es 97, resto 32 por lo que se
convertirá en 65, es decir la A mayúscula. 98 se convertirá
en 66, y así sucesivamente. Pero no los imprimo como números
ya que estoy usando %c para forzarlo a que sea un char.

Compilo y ejecuto. Escribo mi nombre en minúsculas. Y lo


obtengo en mayúsculas.

Observe que está capitalizando todo carácter por carácter.


Lección 42

Este código que hemos visto, es correcto, está bien diseñado


y es legible. No obstante, podemos mejorarlo usando otra
biblioteca a través de su archivo de cabecera llamado ctype.h
que contiene un par de funciones: islower y toupper.

Aquí es donde C, y la programación se vuelve poderosa.

Aquí llamo a una función islower pasándole como entrada el


carácter s[i]. islower devolverá un valor booleano, verdadero
o falso. Si ese carácter está en minúsculas entonces islower
será verdadero e imprimo un marcador de posición seguido de
la función toupper pasándole como entrada el carácter s[i]
para así obtener las mayúsculas de ese carácter. De lo
contario imprimo ese carácter s[i] sin cambios.

Ahora el programa tiene menos código y es más legible.


Entendemos lo que significa islower más fácilmente que la
sintaxis donde usamos el && (AND lógico) y teníamos que saber
que había que restarle 32 al carácter s[i] para convertirlo a
mayúsculas.

Ahora compilo, ejecuto y escribo mi nombre en minúsculas. Y


el programa funciona.

Pero en el código no tengo que ser tan explícito, puedo pasar


cualquier carácter a toupper, y esta función será capaz de
ponerlo en mayúsculas si son minúsculas o de lo contrario, lo
pasará sin cambios.

En este programa, imprimo un marcador de posición %c y luego


las mayúsculas del carácter s[i], toupper manejará el caso en
que esté o no en minúsculas.

Recompilo, ejecuto, pongo mi nombre y funciona.

Ahora el código es más estricto, limpio, simple y legible.

toupper espera como entrada un carácter. No puedes pasarle


una palabra completa, por ello se usa este bucle para hacerlo
carácter por carácter.

En el Manual para el lenguaje C, puedes ver una lista de


todas las funciones disponibles en C. Y si desmarcamos una
casilla en la parte superior, podremos ver aún más funciones.
Hay docenas, tal vez cientos de funciones.
Referiremos este tipo de recursos para que también tengas a
su disposición otras funciones y conjuntos de herramientas.

Antes de ver la criptografía y la codificación de la


información como para el conjunto de problemas 2.

Un argumento de línea de comandos es una palabra que escribes


después del nombre de un programa para proporcionarle una
entrada a la línea de comandos.

Anteriormente hemos visto argumentos de línea de comandos en


gcc hola donde hola es un argumento de línea de comandos para
el programa hola. También lo vimos en rm [Link] donde [Link] es
un argumento de línea de comandos para el programa rm que se
usa para eliminar archivos.

Hasta ahora, las entradas que hemos recibido en nuestros


programas provienen de get_string, get_int, etc. Sin embargo,
ahora escribiremos un programa que permita al usuario aceptar
palabras u otras entradas desde la línea de comandos.

Aquí tenemos este programa llamado argv.c.

stdio.h.

Al igual que nuestras funciones personalizadas pueden tomar


entradas, como cuando vimos la función
obtener_entero_negativo o la función promedio, también C te
permite que la función main puede tomar entradas, aunque
hasta ahora, hemos estado usando void.

int main(int argc, string argv[]) entre corchetes.

No tienes que escribirlo de esta manera, pero lo hacemos por


convención humana.

Tu función main, toma un número entero argc como una entrada


y no una cadena sino un array de cadenas argv[] como entrada.

argc es una notación abreviada de cantidad de argumentos.


Este es un número entero que representará la cantidad de
palabras que los humanos escriben en la línea de comandos.

argv es la abreviatura de vector de argumentos. Vector es una


forma de decir Lista. Es una variable que va a almacenar en
un array todas las cadenas que un humano escribe en la línea
de comandos después del nombre de su programa.

Supongamos que para ejecutar este programa quiero que el


usuario escriba su nombre en la línea de comandos, como
cuando ejecutamos gcc, rm, y otros programas. No deseo usar
get_string para pedirle al humano su nombre más tarde.

Entonces, si el humano escribe dos palabras en la línea de


comandos, como "hola, Dany", "hola, Sergio", esto significa
que si el número de argumentos de mi programa argc es 2
imprimo, "hola, %s", y a continuación el valor argv[1]. De lo
contrario, si argc no es igual a 2, imprimo el valor
predeterminado, "hola, mundo".

Compilo y ejecuto argv.

gcc -o argv argv.c cs50.h

./argv

Y obtengo:

"Hola, mundo!"

Ejecuto de nuevo, pero esta vez escribo:


./argv Dany

Y obtengo:
"Hola, Dany".

Si escribo:
./argv Sergio

"Hola, Sergio!".

Si escribo:
./argv Dany Valverde

Obtengo:
"Hola, mundo!"

La forma en que escribes programas en C que aceptan 0 o más


argumentos de la línea de comandos, es decir, palabras en el
indicador después del nombre de tu programa, es cambiar de
void a argc string argv con corchetes.
La computadora automáticamente almacenar en argc el número
total de palabras que el humano escribió, incluido el nombre
de su propio programa. Luego va a llenar este array de
cadenas, argv, con todas esas palabras que el humano escribió
en la línea de comandos, no solo los argumentos como Dany o
Sergio, sino también el nombre de su programa.

Si el humano escribió dos palabras en total, por ejemplo,


./argv Dany, entonces imprimo "Hola" seguido de un marcador
de posición y luego el valor que esté en argv[1], que en este
caso es Dany . Si hiciera argv[0], según la definición,
obtendría el nombre del programa que siempre se almacena
automáticamente en la primera ubicación de ese array:

Entonces, recompilo y ejecuto y obtengo:

Hola, C:\Users\dany_\cproyectos\[Link]

Pero en este caso, la primera información útil está en la


ubicación argv[1] conteniendo el nombre del humano.

Y de esta manera con argv podemos acceder a palabras


individuales.

Lección 43

Supongamos que quiero imprimir todos los caracteres


individuales de una cadena que alguien ha ingresado en la
línea de comandos a continuación del nombre del programa.

for (int i = 0, n = strlen(argv[1]); i < n; i++)

Entonces itero sobre todos los caracteres en argv[1], la


palabra en argv cuyos caracteres imprimiremos.

Inicializo la variable i a 0 y asigno a la variable n el


valor de la longitud de la cadena argv[1]. Y mientras i sea
menor que n, incremento i en 1 e imprimo el carácter que está
en argv[1] pero en la ubicación i:

printf(“%c”, argv[1][i]);

Como vimos antes, un array de cadenas es un array de arrays y


puedo ir a argv[1] para obtener una palabra como "Dany" o
"Sergio" y luego indexarlo con la sintaxis de corchetes
consecutivos para obtener la D, la A , la N, la Y, etc.
Y usamos un carácter de nueva línea para ver explícitamente
el resultado.

Elimino este "hola, mundo" porque no quiero ver ningún


saludo. Solo quiero ver la palabra que escribió el humano.

Hago argv... Usé strlen cuando no debería porque no incluí


string.h en la parte superior.

Compilo y ejecuto este código

gcc -o argv argv.c cs50.c


./argv2 Dany

Y vemos un carácter por línea.

Y si hago lo mismo con el nombre Sergio o el nombre de


cualquier persona, estoy imprimiendo un carácter a la vez.
./argv2 Sergio

Mis objetivos era iterar sobre los caracteres en esa primera


palabra e imprimirlos.

Tenemos un programa que tiene acceso a los caracteres


individuales en cada una de estas cadenas.

A pesar de que no es una función promedio ni es una función


get_positive_int ni get_negative_int,

main sigue devolviendo un número entero.

Por lo general, las funciones al final han devuelto 0. Y eso


significa que la función se detiene. Y el 0 es el número
entero que sale de la función principal.

Cada vez que tu computadora se congela, lo más probable es


que veas un mensaje de error, pero muy a menudo ves un código
numérico.

Por ejemplo, si tiene problemas con Zoom, a menudo verá el


número 5 en la ventana de error del programa que significa
que tienes problemas de red.
A menudo se asocia los números enteros con cosas que pueden
salir mal en un programa. Se usa 0 para connotar que todo
está bien.

Este programa llamado salida.c nos presentará un o Estado de


Salida.

#include cs50.h.
#include stdio.h.

int (argc, argv[])


{
if (argc != 2)
{
printf(“Falta el Argumento de Línea de Comandos\n”);
return 1;
}
printf("Hola, %s\n", argv[1]);
return 0;
}

Si el humano no ingresa su nombre, voy a decir que faltan


argumentos en la línea de comandos.

Cualquier tipo de error que quiero que el humano vea en la


pantalla, se lo voy a decir con ese mensaje.

Pero voy a devolver un código de error, en este caso el


número 1 y el humano no necesariamente va a ver este código.
Pero si tuviera una interfaz gráfica de usuario o alguna otra
característica para este programa, ese sería el número que
vería en la ventana de error que aparece, al igual que Zoom
podría mostrarle el número 5 si algo salió mal.

Si visitó una página web que no existe, verá el número entero


404. Esto es representativo del uso de números para
representar errores.

De forma predeterminada, imprimiré pasando lo que esté en


argv[1]:

printf("Hola, %s\n", argv[1]);

Así que en vez de imprimir "Hola, mundo" si el humano no


escribe su nombre, voy a verificar si el humano me dio dos
palabras en la línea de comandos. Si no, imprimo, "falta el
argumento de la línea de comandos", y luego devolveré este
código de salida 1.

De lo contrario, si todo está bien, devolveré explícitamente


0. Este es otro número que el humano no verá, pero podríamos
tener acceso a él a través de las interfaces gráficas de
usuario.

0 o su equivalente True, significa todo está bien. 1 o False


significa que algo salió mal.

Compilo y ejecuto el programa:

gcc -o salida salida.c cs50.c

./salida

Y obtengo: "Falta el argumento de la línea de comandos".

Si ejecuto:

./salida Dany

Ahora veo "Hola, Dany".

Si ejecuto:

./salida Adriano

Ahora obtengo "Hola, Adriano".

Si deseamos podemos ver estos valores devueltos o sus


equivalentes booleanos:

Ejecuto:
./salida

Y obtengo este mensaje de error: "Falta el argumento de la


línea de comandos".

Luego escribo:
$?

que es una forma de decir, ¿cuál era mi estado de salida? Y


si presiono Enter,

Y obtengo False equivalente booaleno de 1, en este caso.


Si ejecuto:
./salida Dany,

obtengo "Hola, Dany",

Ahora escribo
$?,

Y ahora obtengo True equivalente booleano de 0, en este caso.

Al escribir programas lo que a menudo haremos es retornar


desde main 0 o 1 o tal vez 2 o 3 o 4 en función de los
problemas que podrían haber salido mal. Es una forma efectiva
de manejar los errores de manera estándar.

Lección 44

Legibilidad.

Cuando estás leyendo un libro, un ensayo o un trabajo, ¿cómo


reconocemos si el nivel de lectura es de grado 3 o de grado
12 o de nivel universitario?

Deducimos que, si la fuente es grande y las palabras cortas,


probablemente sea para niños más pequeños. Si se trata de
palabras con mucho vocabulario y temas que quizá no sabemos,
probablemente sea para un público universitario.

Podemos cuantificar esto con algunas definiciones.

Por ejemplo:
"La historia de la Segunda Guerra Mundial es una de las más importantes y trágicas de la
historia de la humanidad. Este conflicto global, que tuvo lugar entre 1939 y 1945, involucró
a países de todo el mundo y resultó en la muerte de millones de personas”. Las causas de
la Segunda Guerra Mundial fueron complejas y multifacéticas, pero algunos de los factores
clave incluyeron el resurgimiento del nacionalismo, el auge del fascismo y el nazismo en
Europa y Asia, y los problemas económicos y políticos que surgieron después de la Primera
Guerra Mundial. Aunque la Segunda Guerra Mundial terminó hace décadas, su legado
sigue siendo relevante en el mundo de hoy y es importante recordarla para aprender de
nuestros errores y asegurarnos de que nunca vuelva a suceder algo así."

El texto que proporcioné se considera de grado 7 porque está escrito en un nivel de


vocabulario y complejidad sintáctica que es adecuado para los estudiantes de séptimo
grado (aproximadamente 12-13 años). El texto contiene varias oraciones complejas y
utiliza vocabulario más avanzado como "trágico", "multifacético" y "resurgimiento".
Además, el tema de la Segunda Guerra Mundial es relevante para los estudiantes de
séptimo grado, ya que a menudo estudian la historia mundial en esta etapa educativa. En
resumen, el texto es apropiado para estudiantes de séptimo grado en términos de su nivel
de vocabulario y complejidad sintáctica, así como por su relevancia para su plan de
estudios.

¿qué tiene este texto que lo sitúa en el nivel de lectura de


grado 7?

Esto se relaciona con las palabras del vocabulario, con la


longitud de las oraciones, la cantidad de puntuación, la
cantidad total de caracteres.

Lo podemos cuantificar basándonos en el aspecto y la estética


del texto.

"La relatividad general, propuesta por Albert Einstein en 1915, es una de las teorías más
importantes en la historia de la física teórica. La teoría se basa en la idea de que el espacio
y el tiempo están intrínsecamente entrelazados y se describen como un continuo espacio-
tiempo de cuatro dimensiones. Además, la teoría propone que la gravedad no es una
fuerza que actúa a distancia, sino una manifestación de la curvatura del espacio-tiempo
causada por la presencia de materia y energía. La relatividad general ha sido confirmada
en numerosos experimentos y observaciones, y ha llevado a descubrimientos importantes
como la existencia de agujeros negros y ondas gravitacionales. La teoría sigue siendo
fundamental para la física moderna y ha influido en la comprensión de la cosmología, la
astrofísica y la estructura del universo en general."

Esto se califica en el nivel de lectura de grado 16. debido a


su complejidad técnica y vocabulario especializado. Está
dirigido a estudiantes avanzados de física y ciencias
relacionadas, con conocimientos previos en matemáticas y
física. El texto contiene términos y conceptos avanzados,
como "continuo espacio-tiempo", "curvatura del espacio-
tiempo" y "ondas gravitacionales". Además, la comprensión de
la teoría de la relatividad general requiere conocimientos
avanzados de matemáticas y física. En resumen, este texto es
apropiado para estudiantes avanzados de física y ciencias
relacionadas, y es de un nivel de complejidad adecuado para
estudiantes universitarios o científicos profesionales.

De la sofisticación de las oraciones, la longitud de las


mismas y las palabras que contienen. Esa es una forma de
evaluar la legibilidad de un texto incluso si no tiene acceso
a un diccionario con el que averiguar cuáles son las palabras
grandes o pequeñas.
Criptografía

Es muy importante cuando usamos software, como WhatsApp,


Telegram, Messenger o un sitio web con el que estés
interactuando, que admitan el cifrado o codificado en la
comunicación con otras personas.

La criptografía es el arte de codificar u ocultar


información.

Tenemos los componentes básicos para representar y manipular


texto. Los caracteres en mayúsculas nos permiten comenzar a
mutar el texto.

¿qué significa cifrar la información?

Si usamos el modelo de informática, la entrada es el texto


sin formato, el mensaje que queremos enviar a otra persona.

En la caja del medio habrá un algoritmo que codifica tu


entrada para producir un texto codificado que un tercero no
pueda entender.

El texto codificado será la salida que será enviada al


destinatario, quien podrá revertir el proceso de codificado
para averiguar el contenido del mensaje.

Lo principal para usar la criptografía es tener una clave


secreta.

Quizá en la escuela primaria, coqueteando con alguien


enviaste una nota en un papel con el mensaje “Te amo”
codificado, y luego lo pasaste a todos tus amigos o al
destinatario final.

Tal vez hiciste algo como, una A se convierte en una B. B se


convierte en una C. C se convierte en una D. para que, si
alguien lo interceptaba y miraba, probablemente no pueda
descubrir qué significaba eso. Pero si alguien sabía que
cambiaste A por B, B por C, es decir agregando 1 a cada
letra, podría revertir ese proceso y descifrarlo.

Con el mensaje "TE AMO" y como clave el número 1, averigüemos


el texto codificado o la salida.

"TE AMO" es una cadena que, es un array de caracteres.


Del gráfico de ASCII, los caracteres podemos representarlos
con números enteros decimales: T es 84, E es 69, A es 65, M
es 77, O es 79. Así convertimos los caracteres de "TE AMO" en
los números enteros respectivos.

84 69 65 77 79

Empleando el operador + en C, agrego 1 a cada uno de estos


caracteres, encriptando así mi mensaje. Podría enviarle a un
amigo estos números.

85 70 66 78 80

Podría hacerlo más fácil de usar y convertirlo a caracteres.

El texto codificado para "TE AMO", con clave de 1, significa


cambiar de T a U, de E a F, por lo que los desplazo un lugar.
Con lo que obtenemos:

U F B N P

Y así 1 es la entrada como clave. "TE AMO" es la entrada como


texto sin formato. Y la salida es esta frase impronunciable
que, si el profesor o algún amigo intercepta, probablemente
no sepa lo que significa.

Esta es la esencia de la criptografía. Los algoritmos que


protegen nuestros correos electrónicos, mensajes de texto e
información financiera y de salud son mucho más sofisticados
que este algoritmo. Pero se reduce al mismo proceso: una
clave de entrada, un texto de entrada y un texto codificado
como salida.

Esto ha estado con nosotros durante décadas, a veces de forma


mecánica. En el pasado, podías conseguir estos dispositivos
circulares que tenían letras del alfabeto en un lado y otras
letras del alfabeto en el otro lado. Y si gira uno u otro, A
podría alinearse con B, B podría alinearse con C.

Puede tener incluso una encarnación física de la


criptografía, tal como fue popular en una película en la
televisión, al menos aquí en los EE. UU. durante la época
navideña. Y es posible que reconozcas si has visto A
Christmas Story una de esas miradas. Así que veamos a esta
encarnación de la criptografía en el mundo real.

[REPRODUCCIÓN DE VÍDEO]
- "Que todos sepan que Ralph Parker es nombrado miembro del
círculo secreto de Little Orphan Annie y tiene derecho a
todos los honores y beneficios que se le otorgan".

- "Firmado, la pequeña huérfana Annie". "Revisado, Pierre


Andre", en tinta. Honores y beneficios ya a los nueve años.

- (EN LA RADIO) ¡Atención!¡por la borda!

- (EN LA RADIO) Vamos ¡Se pasó de la raya!

- Vamos, sigamos adelante. No necesito todo ese jazz sobre


contrabandistas y piratas.

- (EN RADIO) Escuche mañana por la noche la aventura final


del Barco Pirata Negro. Ahora es el momento del mensaje
secreto de Annie para los miembros del círculo secreto.
Recuerden niños, solo los miembros de cualquier círculo
secreto pueden decodificar cualquier mensaje secreto.
Recuerda, Annie depende de ti. Establezca sus pines en B-2.
Aquí está el mensaje. 12, 11, 2, 8--

- Estoy en mi primera reunión secreta.

- (EN LA RADIO) --25, 14, 11, 18, 16, 23--

- El viejo Pierre tenía una gran voz esta noche.

- (EN LA RADIO) --12, 23--

- Me di cuenta de que el mensaje de esta noche era muy


importante.

- (EN RADIO) --21, 3, 25. Ese es un mensaje de la propia


Annie. Recuerda, no le digas a nadie.

- 90 segundos después, estoy en la única habitación de la


casa donde un niño de nueve años puede sentarse en privado y
decodificar. Ajá, B. Fui a la siguiente, E. La primera
palabra es "ser". S, ahora era más fácil. U. 25, ese es R.

- Oh, vamos, Ralphie, tengo que irme.

- Vamos.

- ¡Bajo enseguida, mamá!


- Caramba.

- T, O. "Asegúrate de hacerlo". ¿Estar seguro de qué? ¿Qué


estaba tratando de decir la pequeña huérfana Annie? ¿Estar
seguro de qué?

- Ralphie, Randy tiene que irse. ¿Podrías salir, por favor?

- ¡Está bien, mamá! ¡Ya saldré!

- Me estaba acercando ahora. La tensión era terrible. ¿Qué


era? El destino del planeta puede estar en juego.

-¡Ralphie! ¡Randy tiene que irse!

- ¡Enseguida saldré, por clamar en voz alta!

- [RISAS] Ya casi llegamos. Mis dedos volaron. Mi mente era


una trampa de acero, cada poro vibraba. Estaba casi claro. Si
si SI SI.

- "Asegúrese de tomar su Ovaltine." Ovaltino? ¿Un comercial


de mala muerte?

Hijo de puta.

También podría gustarte