Getting started with C
but what does a complete C program look like?
- Para crear un programa necesitas escribir tu código en un archivo
- Normalmente terminan con la extensión
.c{.verbatim}.
- Normalmente terminan con la extensión
- Normalmente empiezan con un comentario.
- Este describe el propósito del código o programa
- Luego viene la sección de
imports{.verbatim}- C es un lenguaje muy pequeño y casi no puede hacer nada sin el uso de librerías externas
- Necesitas decirle al compilador que código externo usar con el
uso de
header files{.verbatim} - Un header común es
stdio.h{.verbatim} este contiene código que permite leer y escribir datos de la terminal
- Lo ultimo que verás en un archivo de c serán las funciones
- Todo el código en C corre dentro de funciones.
- La función más importante es llamada
main(){.verbatim}.- Esta es el punto de inicio de todo el código en tu programa
/*
* Program to calculate the number of cards in the shoe.
* This code is released under the Vegas Public License.
* (c)2014, The College Blackjack Team.
*/
#include <stdio.h>
int main () {
int decks;
puts("Enter a number of decks");
scanf("%i", &decks);
if (decks < 1) {
puts("That is not a valid number of decks");
return 1
}
printf("There are %i cards\n", (decks * 52));
return 0
}La función main{.verbatim}
- La computadora empezará a correr el programa desde la función main
- El nombre es importante, debe de llamarse
main(){.verbatim}.
- El nombre es importante, debe de llamarse
- Esta tiene un tipo de retorno
return type{.verbatim} de entero (int{.verbatim}).- Esto es porque la computadora necesita tener un método de saber
si tu programa ejecuto sin errores.
- La computadora sabe esto verificando el valor de retorno de
la función
main{.verbatim}.
- La computadora sabe esto verificando el valor de retorno de
la función
- Si el programa retorna 0, significa que el programa se ejecuto sin errores.
- Si le dices que retorne algún otro número, significa que hubo un problema.
- Esto es porque la computadora necesita tener un método de saber
si tu programa ejecuto sin errores.
- El nombre de la función se escribe después del tipo de retorno.
- Si la función
main{.verbatim} necesitará algún parámetro estos irían entre los paréntesis(){.verbatim}. - Después viene el cuerpo de la función, el cual debe estar entre
llaves
{}{.verbatim}.
- Si la función
La función printf{.verbatim}
- Es usada para mostrar texto formateado en la terminal.
- Reemplaza caracteres de formato con los valores de las variables.
printf("%s says the count is %s", "ben", 21);%s{.verbatim} será insertado como unstring{.verbatim}.%i{.verbatim} será insertado como unint{.verbatim}.- Y estos van en orden junto con los otros parámetros
"ben"{.verbatim} siendo unstring{.verbatim}.21{.verbatim} siendo unint{.verbatim}.
Ejemplo
// Program to evaluate face values.
#include <stdio.h>
#include <stdlib.h>
int main() {
char card_name[3];
puts("Enter the card_name: ");
scanf("%2s", card_name); // leemos 2 caracteres
int val = 0;
if (card_name[0] == 'K') { // Obtenemos el primer carácter de card_name
val = 10;
} else if (card_name[0] == 'Q') {
val = 10;
} else if (card_name[0] == 'J') {
val = 10;
} else if (card_name[0] == 'A') {
val = 11;
} else {
val = atoi(card_name);
}
printf("The card value is: %i\n" val);
return 0;
}But How do you run the program?
- C es un lenguaje compilado
- Esto significa que necesitas convertir (compilar) el código a código maquina.
- Para hacer esto necesitas un programa llamado compilador.
- Uno de los más populares es gcc.
Suponiendo que se guardo el programa anterior con el nombre
cards.c{.verbatim}:
- Necesitamos escribir:
gcc cards.c -o cards{.verbatim}- Esto dice: compila el archivo
cards.c{.verbatim} en un ejecutable llamadocards{.verbatim}.
- Esto dice: compila el archivo
- ahora podemos correr el programa escribiendo
./cards{.verbatim}.
- Podemos compilar y correr nuestro código en un solo comando
haciendo:
gcc cards.c -o cards && ./cards{.verbatim}- el
&&{.verbatim} básicamente dice "si se completa con éxito, haz esto otro". - En un sistema windows podrías necesitar escribir
cards{.verbatim} solamente en lugar de./cards{.verbatim}.
- el
Teoría de Strings{.verbatim}
C no tiene soporte para strings{.verbatim} por defecto (esto porque es
de más bajo nivel que otros lenguajes), así que tenemos que usar un
arreglo de caracteres para simular un string{.verbatim}.
- Los
strings{.verbatim} son en esencia un arreglo de caracteres individuales. - De esta manera podemos referirnos a los caracteres de un
string{.verbatim} con su índice.
C al ser de más bajo nivel que otros lenguajes no siempre puede saber el que tan largo un arreglo es.
Si C va a escribir algo en la pantalla, necesita saber en donde termina
este string{.verbatim}, y lo hace usando un carácter centinela.
Este carácter es un carácter al final del string{.verbatim} que tiene
el valor \0{.verbatim}. Entonces cuando escribe carácter por carácter
se va a detener cuando encuentre este centinela.
Se suele referir a este como el carácter
null{.verbatim}.
Si tenemos s = "shatner"{.verbatim} C lo guarda en la memoria como
shatner\0{.verbatim}
Esta es la razón por la que en nuestro código usamos
char card_name[3];{.verbatim}. Vamos a leer 2 caracteres, pero ponemos
que nuestro arreglo es de 3 porque hacemos espacio para el centinela.
-
string literals{.verbatim} y arreglos- Los arreglos son numerados desde el 0 y no desde el 1 porque:
- El índice en C es un
offset{.verbatim} una compensación.- La computadora va a guardar caracteres en bytes consecutivos de memoria.
- La computadora puede usar este índice para calcular la
posición de un carácter en la memoria.
- Si
c[0]{.verbatim} esta en la posición de memoria1000000{.verbatim}, C puede calcular quec[96]{.verbatim} esta en1000000 + 96{.verbatim}.
- Si
- El índice en C es un
- Debemos usar comillas simples
''{.verbatim} para los caracteres individuales y las comillas dobles""{.verbatim} para losstrings literals{.verbatim}.- Podemos usar estos como
strings{.verbatim} normales, pero estos son inmutables.- Entonces no podemos cambiar el contenido de estos una
vez son creados.
- Si lo hacemos obtendremos un error al compilar.
- Entonces no podemos cambiar el contenido de estos una
vez son creados.
- Podemos usar estos como
- Los arreglos son numerados desde el 0 y no desde el 1 porque:
Painless Operations
En C, el símbolo de igual (=) es usado para asignaciones. pero un doble igual es usado para verificar igualdad. Pero también lo podemos usar para hacer operaciones
teeth = 4; // asignación
teeth == 4; // igualdad
teeth += 2; // Sumar dos a teeth
teeth -+ 2; // Sestar dos a teeth
theeth ++; // Incrementar teeth 1
theeth --; // Decrementar teeth 1Dos tipos de comandos
Hasta ahora cada comando que se ha visto cae en una de dos categorías:
Haz algo
La mayoría de los comandos en C son expresiones. Estos hacen cosas y nos dicen cosas.
split_hand(); // <- expresión simpleA veces agrupamos expresiones juntas para crear un bloque y estos están entre llaves.
{
deal_first_card();
deal_second_card();
cards_in_hand = 2;
}Haz algo solo si algo más es verdadero
Expresiones de control como el if{.verbatim} verifican una condición
antes de correr el código.
if (value_of_hand <= 16) // Condición
hit(); // Esta expresión correrá si la condición es verdadera.
else
stand(); // Corre esta expresión si la condición es falsa.Si una expresión if{.verbatim} necesita hacer más de una cosa, podemos
agregar las llaves para hacer un bloque
if (dealer_card == 5) {
double_down();
hit();
}There's more to booleans than equals
Hay ocasiones en las cuales queremos ver si varios elementos son verdaderos
&&{.verbatim} verifica si dos condiciones son verdaderas
El operador &&{.verbatim} retorna verdadero, solo si ambas
condiciones dadas son verdaderas.
if ((dealer_up_card == 6) && (hand == 11))
double_down();Si la primera condición es verdadera, entonces se evalúa la segunda. Si este no fuera el caso, la computadora no evalúa la segunda.
||{.verbatim} Evalúa si una de las condiciones es verdadera
El operador or{.verbatim} (||{.verbatim}) retorna verdadero si
cualquiera de las condiciones dadas es verdadera.
if (cupcakes_in_fridge || chips_on_table)
eat_food();!{.verbatim} Invierte el resultado de la condición
!{.verbatim} es el operador not{.verbatim}, este invierte el
resultado de una condición.
if (!brad_on_phone)
answer_phone();booleanos
En C los valores booleanos son representados con números, Para C el número 0 es falso y cualquier número que no sea 0 es tratado como verdadero.
Así que código como el siguiente funciona.
int people_moshing = 35;
if (people_moshing)
take_off_glases();En C también podemos usar |{.verbatim} y &{.verbatim} en lugar de
||{.verbatim} y &&{.verbatim}, pero estos siempre evalúan ambas
condiciones, mientras que los otros puede saltarse evaluar la segunda
condición.
Los operadores | y &{.verbatim} existen porque hacen operaciones de
bit a bit en los bits individuales de un número por ejemplo:
6 & 4{.verbatim} es igual a 4 ya que si verificamos que bits binarios
tienen en común 6 (110 en binario) y 4 (100 en binario) obtenemos (100).
Pulling the ol' switcheroo
A veces cuando escribimos lógica condicional, necesitamos verificar el
valor de la misma variable más de una vez, para evitar tener muchos
if{.verbatim} tenemos el switch{.verbatim}
switch (train) {
case 37:
winnings = winnings + 50;
break;
case 65:
winnings = winnings + 80;
break;
case 12:
winnings = winnings + 20;
break;
default:
winnings = 0;
}Cuando la computadora llega a un switch{.verbatim} verifica el valor
que se le dio y busca un caso que se sea igual. Cuando lo encuentra,
corre todo el código que sigue partir de allí en adelante hasta que
se encuentre un break{.verbatim}
No debemos olvidar poner breaks{.verbatim} cuando los necesitamos
porque nuestro código podría no funcionar como queremos.
Sometimes once is not enough
Usando ciclos while{.verbatim} en C
Ciclos son un tipo especial de sentencias de control. Un ciclo decide cuantas veces una pieza de código será ejecutada.
El ciclo más básico de C es el ciclo while{.verbatim}. Este ejecuta
código una vez tras otra mientras una condición sea verdadera.
while (<some condition>) { // <- Verifica la condición antes de correr el bloque
// Haz algo aquí // <- Si hay una sola linea en el cuerpo no necesitas las llaves.
} // <- Cuando la computadora llega al final del bloque vuelve a verificar si la condición es verdadera.do while{.verbatim}
Hay una variación de el ciclo while{.verbatim} que verifica la
condición del loop después de ejecutar el código.
Por lo tanto el código es ejecutado al menos una vez.
do {
// Algo
} while (have_not_won);Los ciclos a veces siguen la misma estructura
- Hacer algo simple antes del ciclo, como poner un contador
- Tener una condición simple en el ciclo.
- Hacer algo al final del ciclo, como actualizar un contador.
int counter = 1;
while (counter < 11) {
printf("%i green bottles, hanging on a wall\n", counter);
counter++;
}El ciclo for{.verbatim}
Los diseñadores de c crearon el ciclo for{.verbatim} para hacer esta
estructura más consista.
Este es el mismo ejemplo de arriba con un ciclo for{.verbatim}
int counter;
for (counter = 1; counter < 11; counter++){
printf("%i green bottles, hanging on a wall\n", counter);
}En el ciclo for{.verbatim} inicializamos la variable del ciclo
(counter=1{.verbatim}). Damos una condición que debe de ser verificada
(counter < 11{.verbatim}) en cada iteración y tenemos un código que va
a ser ejecutado al final (counter++{.verbatim}).
Usas un break{.verbatim} para salir
Puedes crear ciclos que verifican una condición al inicio o al final de
un bloque de código. Pero también podemos salir del ciclo con la palabra
break{.verbatim}.
while (feeling_hungry) {
eat_cake();
if (fealing_queasy) {
// Salimos del ciclo
break; // El break te saca del ciclo inmediatamente
}
drink_coffee();
}Los breaks{.verbatim} te sacan del ciclo saltándose todo el código que
siga dentro del bloque del ciclo.
Usamos continue{.verbatim} para continuar
Si queremos saltarnos todo el código que sigue en el bloque e ir a la siguiente iteración.
while (feeling_hungry) {
eat_cake();
if (fealing_queasy) {
// Salimos del ciclo
continue; // lo usamos para regresar al inicio del bloque de código
}
drink_coffee();
}Escribiendo funciones
Casi todas las funciones en C siguen el mismo formato, por ejemplo
#include <stdio.h>
int larger (int a, int b) { // <- Recibe dos argumentos a y b
if (a > b)
return a;
return b;
}
int main () {
int greatest = larger(100, 1000);
printf("%i is the largest!\n", greatest);
return 0;
}- La función
larger{.verbatim} es diferente amain{.verbatim} porque recibe dos argumentos.- Un argumento es una variable local (solo pertenece ese bloque de código) que obtiene su valor de cuando se llama.
- La función
larger{.verbatim} toma como argumentos a y b que son enteros y estos deben de ser dados siempre.
Funciones void{.verbatim}
A veces necesitamos crear funciones que no tienen nada útil que
retornar, para esto esta el tipo void{.verbatim}. Con este nuestras
funciones no tienen que tener un return{.verbatim}.
void complain() {
puts("I'm really not happy"); // :c
}En C la palabra void{.verbatim} significa "no importa", cuando tu le
digas al compilador que no te importa retornar un valor en tu función,
no necesitaras un return{.verbatim}.
Encadenando sentencias
Casi todo en C tiene un valor de retorno, no solo llamadas a funciones. De hecho cosas como asignaciones tienen valores de retorno. Por ejemplo
x = 4;Asigna el número 4 a una variable. la parte interesante es que la
expresión x = 4{.verbatim} en si misma tiene el valor que será
asignado
Esto es importante porque significa que puedes encadenar asignaciones.
y = (x = 4); // Ahora y es 4 también.
y = x = 4; // lo mismo que arriba.Se usan sentencias encadenadas para asignar variables que tienen el mismo valor.
What are you pointing at?
El lenguaje C te da mucho más control sobre como tu programa usa la memoria de la computadora de lo normal, es necesario saber como C maneja la memoria.
El código en C incluye Punteros
Un puntero es solo la dirección de una pieza de información en la memoria.
Los punteros son usados por unas cuantas razones:
- En lugar de pasar un copia de los datos, solo pasas el puntero de donde esta la información en memoria.
- Podríamos querer que dos trozos de código trabajen con la misma pieza de información en lugar de con una copia.
Escarbando en la memoria.
Cada vez que se declara una variable, la computadora crea espacio en algún lugar de la memoria para estos datos.
Si declaras una variable dentro de la función main{.verbatim} la
computadora la guardará en una sección de la memoria llamada la pila
(stack{.verbatim}).
Si una variable es declarada afuera de cualquier función será
guardada en la sección global (globals{.verbatim}) de la memoria.
La computadora podría asignar la dirección de memoria de 4,100,000 en la pila para la variable x, si en la variable x guardamos un 4 este se va a guardar en la misma dirección de memoria.
Podemos saber la dirección de memoria de una variable con el operador
&{.verbatim}.
printf("x is stored at %p\n", &n); // &p es usado para dar formato a direcciones.Dandonos algo como esto.
x is stored at 0x3E8FA00x3E8FA0{.verbatim} es 4,100,000 en formato hexadecimal (base 16).
Esta seria la dirección de memoria en donde se guarda nuestra variable, se le llaman punteros porque apuntan a la variable en memoria.
Usando punteros de memoria
Hay 3 cosas que debes de saber para usar punteros para leer y escribir datos.
Obtener la dirección de memoria
Podemos obtener la dirección de memoria de una variable con el operador
&{.verbatim}.
int x = 4;
printf("x lives at %p\n", &x);Pero ya que podemos acceder a la dirección de memoria debemos guardarla en algún lado, para eso necesitamos una variable puntero.
Una variable puntero es una variable que guarda una dirección de memoria.
Cuando declaramos una variable puntero, debemos decir de que tipo de dato esta guardado en esa dirección de memoria.
int *addres_of_x;Leer los contenidos de la variable
Cuando ya tienes la dirección de memoria guardada, querrás leer la
información que esta allí, lo haces con el operador *{.verbatim}.
int value_stored = *addres_of_x;El operador *{.verbatim} y &{.verbatim} son opuestos.
El operador &{.verbatim} toma un pedazo de datos y te dice donde están
guardados. Y el operador *{.verbatim} toma una dirección y te dice que
esta guardado allí.
Los punteros a veces son llamados referencias, el operador
*{.verbatim} se dice que deferencia una asignación.
Cambiar el contenido de una dirección
Si tienes una variable puntero y quieres cambiar la información de la
dirección podemos usar el operador *{.verbatim} de nuevo.
*addres_of_x = 99;Solo debemos de tener en cuenta de usar el operador *{.verbatim} del
lado izquierdo del asignamiento.
Como pasar un string{.verbatim} a una función
Ya podemos pasar argumentos simples como enteros y booleanos, pero que
pasa si queremos pasar algo más complejo, como un string{.verbatim}.
Los strings{.verbatim} son cadenas de caracteres, así que podemos
hacer lo siguiente:
void fortune_cookie(char msg[]) {
printf("Message reads: %s\n", msg);
}
char quote[] = "Cookies make you fat :c";
fortune_cookie(quote);Honey, who shrank the string?
C tiene un operador llamado sizeof{.verbatim} que puede decirte
cuantos bytes de espacio algo tiene en memoria.
sizeof(int); // <- 4
sizeof("Turtles!"); // 9 ya que son 8 caracteres más el \0.Algo extraño pasará si vemos la longitud de un string{.verbatim} que
pasamos a una función
void fortune_cookie(char msg[]) {
printf("Message reads: %s\n", msg);
printf("msg occupies %i bytes\n", sizeof(msg)); // Nos puede dar 4 u 8 bytes
}Variables arreglos son parecidos a los punteros
Cuando creamos una variable arreglo, puede ser usada como un puntero al inicio de el arreglo en memoria.
char quote[] = "Cookies make you fat";La variable quote{.verbatim} representará la dirección del primer
carácter en el string{.verbatim}.
La computadora, apartara el espacio en el stack{.verbatim} para cada
uno de los caracteres en el string{.verbatim} más el carácter
\0{.verbatim}, pero también asociará la dirección del primer carácter
con la variable quote{.verbatim}.
Cada vez que llamemos a esa variable la computadora la sustituirá con
la dirección del primer carácter, en el string{.verbatim}.
La variable arreglo es como un puntero.
printf("The quote string is stored at: %p
\n{=latex}", quote); // Podemos usar quote como un puntero aunque sea un array
Dando algo como esto:
The quote string is stored at: 0x7fff69d4bdd7Así que a nuestra función se le paso un puntero.
Esta es la razón porque sizeof{.verbatim} nos daba un valor no
esperado, estaba recibiendo un puntero.
void fortune_cookie(char msg[]) {
printf("Message reads: %s\n", msg); // msg apunta al mensaje.
printf("msg occupies %i bytes\n", sizeof(msg)) // Nos dará el tamaño del puntero
}En un sistema operativo de 32 bits, un puntero usa 4 bytes y en uno de 64 toma 8 bytes de memoria.
Operadores y funciones
sizeof{.verbatim} es un operador, la diferencia entre un operador y
una función es que el primero es compilado a una secuencia de
instrucciones por el compilador. pero si el código llama a una función
tiene que saltar a una pieza separada de código.
Ejemplo: Dating Game
#include <stdio.h>
int main () {
int contestants[] = {1, 2, 3}; // Creamos el arreglo con 3 elementos
int *choice = contestants; // creamos una variable puntero apuntando al arreglo
contestants[0] = 2;
contestants[1] = contestants[2];
contestants[2] = *choice; // al leer la información del puntero obtendremos el primer elemento del array.
printf("I'm going to pick contestant number %i\n", contestants[2]); // será el 2
return 0;
}Pero variables arreglo no son como punteros
Podemos usar los arreglos como punteros pero hay algunas diferencias.
char s[] = "How big is it?";
char *t = s;sizeof(array){.verbatim} es un el tamaño del arreglo
Ya vimos que si hacemos sizeof{.verbatim} de un arreglo obtendremos 4
u 8, pero si hacemos lo mismo con un arreglo, C nos dará el tamaño de
ese arreglo
char s[] = "How b ..";
sizeof(s); // Nos dará la longitud del array
char *p = s;
sizeof(p); // Nos dará 4 u 8;La dirección del arreglo es la dirección del arreglo
Una variable puntero es solo una variable que guarda una dirección de
memoria, pero si usamos el operador &{.verbatim} en un arreglo, el
resultado es el arreglo en si mismo.
&s == s &t != t&s{.verbatim} es la dirección del arreglo s{.verbatim}, y
&t{.verbatim} es la dirección de la variable t{.verbatim}
Una variable arreglo no puede apuntar a ningún otro lado
Cuando creamos un arreglo, la computadora asignará el espacio de memoria para guardar el arreglo, pero no guardará ninguna memoria para guardar la variable arreglo.
La computadora solo guarda la dirección de memoria al inicio del arreglo.
Pointer decay
Debido a estas diferencias debemos de ser cuidadosos al asignar arreglos a variables puntero.
Si asignamos un arreglo a una variable puntero, entonces la variable solo tendrá la dirección del arreglo.
El puntero no sabe nada sobre el tamaño del arreglo, así que se perdió un poco de información.
A esto se le llama pointer decay (deterioro de puntero)
Cada vez que pasamos un arreglo a una función, deterioramos el arreglo a un puntero,
Esto es inevitable, así que debemos saber donde hay deterioros así en nuestro código para evitar bugs.
Porque los arreglos realmente empiezan en 0
Una variable arreglo puede ser usada como un puntero al primer elemento en un arreglo.
Eso significa que puedes leer el primer elemento de un arreglo usando
los corchetes []{.verbatim} o usando el operador *{.verbatim}.
int drinks[] = {4, 2, 3};
// Todas estas lineas son equivalentes
printf("1st order: %i drinks\n", drinks[0]);
printf("1st order: %i drinks\n", *drinks);Pero porque las direcciones son solo un número, eso significa que podemos hacer aritmética de punteros y sumar valores a una variable puntero para encontrar la siguiente dirección.
En este caso, podemos usar los corchetes para leer el elemento con índice 2 o añadir 2 a la dirección del primer elemento.
printf("3rd order: %i drinks\n", drinks[2]);
printf("3rd order: %i drinks\n", *(drinks + 2));En general, las dos expresiones son equivalentes, es por eso que los arreglos empiezan en 0, El índice es solo el número que se le agrega al puntero para encontrar la dirección del elemento.
Los punteros tienen tipos
La razón por lo que la aritmética de punteros tiene truco, es porque si tu sumas 1 a un puntero de caracteres, el puntero apuntará ahora a el siguiente carácter es porque un carácter ocupa 1 byte de memoria.
Si tenemos un puntero tipo int{.verbatim}, estos usualmente toman 4
bytes de espacio, así que si sumamos 1 a un puntero tipo
int{.verbatim}, el código sumará 4 bytes a la dirección de memoria.
Los tipos en los punteros sirven para que el compilador sepa cuando ajustar la aritmética de punteros.
así que podemos hacer cosas como estas:
int doses[] = { 1, 3, 2, 1000 };
// Todos los ejemplos de abajo son equivalentes.
doses[3] == *(doses + 3) == *(3 + doses) == 3[doses]Usando punteros para entrada de datos
Ya sabemos usar scanf{.verbatim} para leer del teclado.
char name[40];
printf("Enter your name: ");
scanf("%39s", name); // el %39 hace que solo leamos 39 caracteres ya que el 40 es \0scanf{.verbatim} funciona cuando le damos una variable arreglo.
Funciones que necesitan actualizar una variable no necesitan el valor de la variable en si, si no su dirección.
Introduciendo números con scanf{.verbatim}
Para meter datos en un campo numérico le pasamos el puntero a una variable numérica.
int age;
printf("Enter your age: ");
scanf("%i", &age); // %i significa que leeremos números enteros, y le pasamos la dirección de memoria de la variable.También podemos usar scanf{.verbatim} para leer más de una pieza de
información al mismo tiempo.
char fist_name[20];
char last_name[20];
printf("Enter first and last name: ");
scanf("%19 %19", first_name, last_name); // Lee el primer nombre, luego un espacio y después el segundo nombre
printf("First: %s last: %s\n", firt_name, last_name);Se cuidadoso con scanf{.verbatim}
scanf{.verbatim} puede causar desbordamientos si no le ponemos un
límite, como en el siguiente ejemplo.
char food[5]; // Guardamos 5 caracteres
printf("Enter favorite food: ");
scanf("%s", food); // No ponemos limite en la cantidad de caracteres a leer
printf("Favorite food: %s\n", food);Si en el código de arriba, damos una respuesta de más de 5 caracteres
nuestro programa crasheara ya que scanf{.verbatim} escribe más allá de
los 5 caracteres asignados para el arreglo food{.verbatim}.
A esto se le llama buffer overflow{.verbatim} y puede crashear nuestro
programa o crear bugs.
fgets(){.verbatim} como alternativa a scanf{.verbatim}
fgets{.verbatim} es una función parecida a scanf{.verbatim}, pero a
diferencia de scanf{.verbatim} debemos darle una longitud máxima.
char foodp[5];
printf("Enter favorite food: ");
fgets(food, sizeof(food), stdin);fgets{.verbatim} toma, un puntero, después el tamaño máximo a leer, y
después de donde leer estos datos, en este caso de decimos
stdin{.verbatim} que es standard input{.verbatim} o que lea del
teclado.
Usando sizeof{.verbatim} con fgets{.verbatim}
En el código de arriba usamos sizeof{.verbatim} para dar el tamaño.
Debemos de ser cuidadosos con esto, ya que sizeof{.verbatim} nos da la
cantidad de espacio ocupado por una variable.
En el ejemplo, usamos sizeof{.verbatim} porque usado en un arreglo nos
da el tamaño del arreglo, si food{.verbatim} fuera un puntero normal a
una variable sizeof{.verbatim} nos daría el tamaño del puntero.
printf("Enter favorite food: ");
fgets(food, 5, stdin);Generalmente cuando queremos leer datos estructurados usamos
scanf{.verbatim} y si queremos leer un solo string sin estructura es
más conveniente usar fgets{.verbatim}.
En memoria
Si tenemos un código como este:
#include <stdio.h>
int main() {
char *cards = "JQK";
char a_card = cards[2];
cards[2] = cards[1];
cards[1] = cards[0];
cards[0] = cards[2];
cards[2] = cards[1];
cards[1] = a_card;
puts(cards);
return 0;
}obtendremos un error al correr el código, esto porque cards{.verbatim}
es un puntero a un string{.verbatim} literal, y estos son inmutables.
Lo que podríamos hacer es crear un arreglo con este string{.verbatim}
para que podamos modificarlo.
char cards[] = "JQK";Para entender que pasa debemos saber como c y la computadora manejan la memoria.
La computadora carga el string{.verbatim} literal
Cuando la computadora carga el programa en memoria, pone todas las constantes en un bloque de memoria inmutable, allí es donde esta "JQK".
Esta sección es de solo lectura.
El programa crea la variable cards{.verbatim} en el stack{.verbatim}.
El stack{.verbatim} (la pila), es la sección de memoria que la
computadora usa para variables locales.
Las variables locales son variables dentro de funciones, la variable
cards{.verbatim} estará allí.
La variable cards{.verbatim} le es asignada la dirección de memoria de "JQK"
la variable cards{.verbatim} contiene la dirección de memoria del
string{.verbatim} "JQK".
Esta dirección de memoria pertenece a la memoria de solo lectura.
La computadora trata de cambiar el string{.verbatim}
Cuando el programa intenta cambiar el contenido de el
string{.verbatim} al cual apunta la variable cards{.verbatim} no
puede, es de solo lectura.
Si vas a cambiar un string{.verbatim}, haz una copia
Siempre que queramos cambiar los contenidos de un string{.verbatim}
debemos cambiar una copia.
Para crear una copia, creamos un nuevo string{.verbatim} como un
arreglo.
char cards[] = "JQK";cards[]{.verbatim} o cards*{.verbatim}
Si vemos una declaración así, que significa realmente?
char cards[]Depende de donde la veamos.
Si es una declaración de variable normal, Entonces significa que
cards{.verbatim} es un arreglo, y debemos asignarle un valor
inmediatamente ya que no le dimos el tamaño:
int my_function() {
char cards[] = "JQK";
}Pero si cards{.verbatim} es declarado como un argumento de una
función, significa que cards{.verbatim} es un puntero.
void stack_deck(char cards[]) { // <- cards es un puntero
// .....
}
void stack_deck(char *cards) { // Estas dos funciones son equivalentes.
// ...
}Que sucede en memoria
Con el nuevo código, nuestro programa manejara la memoria de manera diferente.
La computadora carga el string{.verbatim} literal
La computadora carga el programa y guarda "JQK" en la memoria de solo lectura.
El programa crea un nuevo arreglo en el stack{.verbatim}
Declaramos un arreglo, así que nuestro programa creara uno del tamaño
necesario para guardar el string{.verbatim} "JQK".
El programa inicializa el arreglo
Además de asignar el espacio la computadora también copia el contenido
del string{.verbatim} literal "JQK".
Recordando como funciona la memoria
Stack{.verbatim} (pila)
Esta sección de memoria es usada para guardar variables locales.
Cada vez que llamamos a una función, todas las variables son creadas en
el stack{.verbatim}.
Se llama stack{.verbatim} porque funciona como una pila, las variables
son añadidas una a una cuando se entras a una función y son retiradas de
este cuando sales.
Heap{.verbatim} (montículo)
La heap{.verbatim} es para memoria dinámica, datos que son creados
cuando el programa esta corriendo y se quedan allí por un tiempo.
Globales
Una variable global son variables que viven afuera de las funciones, y por lo tanto son visibles para todas ellas.
Estas son creadas cuando el programa corre al principio y pueden ser editadas.
Constantes
Las constantes también son creadas cuando el programa corre al principio, pero están en memoria de solo lectura.
Código
La mayoría de los sistemas operativos ponen el código en las direcciones de memoria más bajas.
El segmento de código es de solo lectura. Esta es la parte de la memoria donde el código ensamblado se carga.
Teoría de strings{.verbatim}
Ya sabemos que los strings{.verbatim} en C son solo arreglos de
caracteres, pero para hacer cosas útiles con ellos necesitamos
string.h{.verbatim}.
Este es parte de la librería estándar de C y se dedica a la manipulación
de strings{.verbatim}.
Creando un arreglo de arreglos
En una situación donde queremos guardar muchos strings podemos usar un
arreglo de arreglos. Sabiendo que cada string{.verbatim} es un array
de caracteres.
char tracks[][80] = {
"I left my heart in Harvard Med School",
"Newark, Newark - a wonderful town",
"Dancing with dork",
"From here to maternity",
};En el código anterior, el primer par de corchetes representa el arreglo
de todos los strings{.verbatim}, y el segundo es la cantidad de
caracteres que tiene cada string{.verbatim}.
De esta manera podemos encontrar un string{.verbatim} arbitrario así:
tracks[4] -> "The girl from Iwo Jima"pero también podemos obtener los caracteres individuales de cada
string{.verbatim} de la siguiente manera:
tracks[4][6] -> 'r'Encontrando strings{.verbatim} que contienen una búsqueda
La librería estándar de C es un conjunto de código que obtienes al instalar un compilador de C. La librería de código hace cosas útiles como abrir archivos, hacer matemáticas o manejar memoria.
Esta librería esta dividida en muchas secciones, cada una tiene un header (cabecera).
Esta cabecera lista todas las funciones que viven en una sección particular de la librería.
Hemos usado la cabecera stdio.h{.verbatim} que contiene funciones para
la entrada/salida de datos, como printf{.verbatim} y
scanf{.verbatim}.
La librería estándar contiene código para procesar y manipular
strings{.verbatim}, para acceder a este debemos de agregarla al inicio
de nuestro programa de la siguiente manera:
#include <stdio.h>
#include <string.h> // <- Agregamos esta linea para usar sus funciones.Usando la función strstr(){.verbatim}
Digamos que queremos buscar la palabra "fun" en "dysfunctional":
strstr("dysfunctional", "fun");La función buscará el segundo string{.verbatim} en el primero, cuando
lo encuentre retorna la dirección del string{.verbatim} en memoria.
Si strstr{.verbatim} no puede encontrar el string{.verbatim}
retornará el valor 0.
Ya que 0 para C es equivalente a false{.verbatim}, podemos usar esta
función para verificar la existencia de un string{.verbatim} dentro de
otro.
char s0[] = "dysfunctional";
char s1[] = "fun";
if (strstr(s0, s1))
puts("I found the fun in dysfunctional");Arreglo de arreglos vs arreglo de punteros
Otra opción a un arreglo de arreglos, es un arreglo de punteros, este es
un arreglo de direcciones de memoria, este es útil para crear un una
lista de strings{.verbatim} literales.
char *names_for_dog[] = {"Bowser", "Bonza", "Snodgrass"};Haz una cosa y hazla bien
Cada sistema operativo incluye herramientas pequeñas.
Pequeñas herramientas escritas en C hacen pequeñas tareas especializadas a lo largo del sistema operativo.
Pequeñas herramientas pueden resolver problemas grandes
La mayoría de sistemas operativos vienen con un gran conjunto de pequeñas herramientas que puedes usar desde la terminal (como linux), cuando tenemos un problema grande que debemos resolver, podemos partirlo en una serie de pequeños problemas y escribir una serie de pequeñas herramientas para resolverlos.
Errores estándar
La salida estándar es la manera por defecto de salida de datos de un programa, pero no nos deja manejar errores de manera flexible.
Para esto tenemos el error estándar, este es una segunda salida de datos creada para el manejo de errores.
Por defecto el error estándar es mandado a la pantalla.
El sistema operativo crea el error estándar al mismo tiempo y de la misma manera que la salida estándar.
Esto significa que cuando redireccionamos la salida estándar de nuestro programa para que podamos redirigir la salida a un archivo, el error estándar seguirá mandando los errores a la pantalla.
fprintf(){.verbatim} imprime un stream{.verbatim} de datos.
Ya sabemos que printf(){.verbatim} manda información a la salida
estándar, pero lo que no sabemos es que printf(){.verbatim} es una
versión de otra función más general llamada fprint(){.verbatim}.
Los dos ejemplos siguientes son equivalentes:
printf("I like Tultles"); // Cuando llamamos a printf en realidad llamamos a fprintf
fprintf(stdout, "I like Tultles"); // stdout es la salida estándarEsta función nos permite elegir a donde queremos mandar el texto,
podemos usar la stdout{.verbatim} (salida estándar) o la
stderr{.verbatim} (error estándar).
También existe fscanf{.verbatim} que es la función que es llamada
cuando usamos scanf{.verbatim}, allí leemos de stdin{.verbatim}
(entrada estándar).
Así como podemos redirigir la salida de nuestro programa a un archivo de
texto haciendo ./programa > text.txt{.verbatim}, podemos redirigir el
error estándar haciendo ./programa 2> text.txt{.verbatim}