Está en la página 1de 44

Lenguaje C

Semana 8

Clase 13
Agenda
1 Pixeles 2 Punteros

3 malloc 4 scanf
Pixeles
★ Podemos aumentar cada vez más y más una imagen, pero pronto
veremos píxeles individuales, como lo siguiente.
★ Puesto que esta imagen está almacenada con un número finito de
bytes, puede que cada uno representando un valor de rojo, verde o
azul por cada píxel, por lo tanto hay un número finito de píxeles.

Una imagen más


simple de una carita
sonriente puede ser
representado con un
solo bit por pixel.
Aquí, el color negro está seleccionado, y vemos que los valores para R, G y B,
o rojo, verde y azul respectivamente, son todos 0. Además, vemos #000000,
que parece representar todos los tres valores. Echemos un vistazo a unos
cuantos colores más:

➢ Blanco, con R: 255, G: 255, B:255 y #FFFFFF


➢ Rojo, con R: 255, G:0, B:0 y #FF0000
➢ Verde, con R: 0, G:255, B:0 y #00FF00
➢ Azul, con R: 0, G:0, B:255 y #0000FF
RGB y Hexadecimal

Adobe photoshop, un
software de edición de
imágenes popular,
incluye un seleccionador
de color que se ve algo
similar a este.
¿Por qué es importante manejar los
canales de colores de una imagen?
Resulta que hay otro sistema base, hexadecimal, o base-16, donde hay 16
dígitos:

0 1 2 3 4 5 6 7 8 9 A B C D E F

Los dígitos empiezan desde 0 hasta 9 y luego continúan desde la A hasta la F


como equivalentes a los valores decimales de 10 hasta 15.
Con dos dígitos, podemos tener un valor máximo de FF, ó:

161 x 15 + 160 x 15 = 16 x 15 + 1 x 15 = 240 + 15 = 255.

★ Los valores en la memoria de una computadora aún son almacenados en


binario, pero esta manera de representarlos ayuda a los humanos a
representar valores con menos dígitos requeridos.
★ Con 8 bits en binario, el valor más grande al que podemos contar también
es 255, con 11111111. Así que dos dígitos en hexadecimal pueden
representar de manera conveniente el valor de un byte en binario.
Punteros
También para la memoria de nuestra computadora, veremos hexadecimal
usado para describir cada dirección.

Escribiendo “0x” al inicio de un valor


hexadecimal, podemos distinguirlos de
valores decimales.
Si creamos una variable llamada n, en la memoria de nuestra computadora,
ahora hay 4 bytes en algún lugar, que tiene el valor de 50, con algunos
valores para su dirección, como 0x123. Con un puntero podemos acceder a
esa dirección de memoria.

Un puntero es una variable que


almacena una dirección en la
memoria, donde alguna otra variable
podría estar almacenada.
El operador & puede ser usado para obtener la dirección de una variable. Y el
operador * declara una variable como puntero, indicando que tenemos una
variable llamada p que apunta a un int.

address.c

1 #include <stdio.h>
2 int main(void)
3 {
4 int n = 50;
5 int *p = &n;
6 printf("%p\n", p);
7 }
★ “%p” es la secuencia de escape para imprimir una dirección con printf. Y
solo tenemos que usar el nombre de la variable p, luego de haberla
declarado.

★ Podemos ejecutar este programa unas cuantas veces, y veremos que la


dirección en memoria de n cambia, debido a que diferentes direcciones
de memoria estarán disponibles en diferentes tiempos.

★ En C, también podemos ir a direcciones específicas de memoria, lo cual


podría causar errores de segmentación, donde hemos intentado leer o
escribir a una parte de la memoria de la que no tenemos permisos.
★ El operador * también es de desreferencia, el cual va a una dirección
de memoria para obtener el valor almacenado en ella.

address.c
Obtendremos algo similar a la
1 #include <stdio.h>
siguiente salida.
2
3 int main(void) {
4
$ make address
int n = 50;
5 int *p = &n; $ ./address
6 printf("%p\n", p); 0x7ffda0a4767c
7 printf("%i\n", *p);
50
8 }
★ En la memoria, podríamos tener una variable p, con el valor de una
dirección almacenada, por ejemplo 0x123, y otra variable, un entero,
con el valor 50, en esa dirección.
Observa que p toma 8 bytes, debido a que en sistemas de computadores
modernos, 64 bits son usados para poder corresponder a las direcciones
con billones de bytes de memoria disponible. Con 32 bits, solo podemos
contar hasta 4 billones de bytes, o alrededor de 4GB de memoria.
Strings

Podemos declarar un string con string s = “HI!”;, lo cual será


almacenado un carácter a la vez en la memoria. Y podemos acceder a
cada carácter con s[0], s[1], s[2] y s[3]:
Pero resulta que cada carácter, puesto que está almacenado en la
memoria, también tienen una dirección única, y s es realmente sólo un
puntero con la dirección del primer carácter:
★ “s” es una variable de tipo string, el cual es un puntero a un carácter.

★ Recuerda que podemos leer un string entero empezando por la


dirección en s, y continuar leyendo un carácter a la vez de la memoria
hasta que lleguemos a \0.

★ Resulta que string s = “HI!” es lo mismo que char *s = “HI!”;. Y podemos


usar strings en C exactamente igual sin utilizar la librería CS50, si
usamos char *.
★ Ahora podemos eliminar la librería CS50 y hacer:

address.c

1 #include <stdio.h>
2
3 int main(void)
4 {
5 char *s = "HI!";
6 printf("%s\n", s);
7 }
★ Obtendremos la misma salida que con cs50.h

$ make address
$ ./address
HI!
En la librería CS50, un string es definido con typedef char *string;. Con
typedef, estamos creando un tipo de dato personalizado para la palabra
string, haciéndolo equivalente a char*.
Comparar strings
Cuando comparamos dos cadenas, vemos que las mismas entradas hacen que
nuestro programa imprima "Different":
compare.c

1 #include <cs50.h>
2 #include <stdio.h>
$ ./compare
3
4 int main(void) { s: HI!
5 char *s = get_string("s: ");
6
t: HI!
char *t = get_string("t: ");
7 if (s == t) Different
8 printf("Same\n");
9 else
10 printf("Different\n");
11 }
★ Cada string es un puntero, char *, a una ubicación diferente en la
memoria, donde se almacena el primer carácter de cada cadena.

★ Y get_string, todo este tiempo, ha estado devolviendo solo un puntero


al primer carácter de una cadena del usuario.

★ Para comparar dos string o char *, debemos de usar una función de


string.h llamada strcmp, el cual retorna “0” si ambas direcciones de
memoria contienen el mismo valor.
Aritmética de punteros

La aritmética de punteros es el proceso de aplicar operaciones


matemáticas a punteros, usandolos justo como si fueran números
(lo cual, lo son).
Podemos declarar un arreglo de números y acceder a ellos con aritmética
de punteros:
address.c

1 #include <stdio.h>
2
3 int main(void) {
4 int numbers[] = {4, 6, 8, 2, 7, 5, 0};
5 printf("%i\n", *numbers);
6 printf("%i\n", *(numbers + 1));
7 printf("%i\n", *(numbers + 2));
8 printf("%i\n", *(numbers + 3));
9 printf("%i\n", *(numbers + 4));
10 printf("%i\n", *(numbers + 5));
11 printf("%i\n", *(numbers + 6));
12 }
★ Obtendremos la siguiente salida:

$ make address
$ ./address
4
6
8
2
7
5
0
★ Resulta que solo necesitamos agregar 1 a las direcciones de numbers,
en lugar de 4 (incluso a pesar de que los ints tienen un tamaño de 4
bytes), debido a que el compilador ya conoce que el tipo de cada valor
en numbers es 4 bytes. Con + 1, estamos diciéndole al compilador que
se mueva al siguiente valor en el arreglo, no el siguiente byte.

★ Y observa qué numbers es un arreglo, pero podemos usarlo como un


puntero con *numbers.
malloc
★ “malloc” es una función que sirve para asignar una cierta cantidad de
bytes en la memoria. Y usaremos free para señalar la memoria como
usable cuando hayamos terminado con ella.

★ Nuestras computadoras pueden ponerse lentas si un programa que


ejecutamos tiene un error en el que asigna más y más memoria pero
nunca la libera. El sistema operativo tardará más en encontrar
suficiente memoria disponible para nuestro programa.
Tratemos de copiar un string a otro.

copy.c

1 #include <cs50.h>
2 #include <ctype.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 int main(void) {
8 char *s = get_string("s: ");
9 char *t = malloc(strlen(s) + 1);
10 for (int i = 0, n = strlen(s) + 1; i < n; i++)
11 t[i] = s[i];
12
13 t[0] = toupper(t[0]);
14 printf("s: %s\n", s);
15 printf("t: %s\n", t);
16 }
★ Creamos una nueva variable para apuntar a un nuevo string con char *t.
El argumento de malloc es el número de bytes que nos gustaría usar.
Sabemos la longitud de s, pero necesitamos agregar 1 para el carácter
nulo o ‘\0’.

★ Luego, copiamos cada carácter, uno a la vez, con un for. Usamos


strlen(s) + 1 ya que queremos copiar el ‘\0’ para finalizar la cadena.
Podemos mejorar el código usando strcpy.

copy.c

1 #include <cs50.h>
2 #include <ctype.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 int main(void) {
8 char *s = get_string("s: ");
9 char *t = malloc(strlen(s) + 1);
10 strcpy(t, s);
11 t[0] = toupper(t[0]);
12 printf("s: %s\n", s);
13 printf("t: %s\n", t);
14 free(t);
15 }
También debemos evitar posibles errores.

copy.c

. . .

char *t = malloc(strlen(s) + 1);


if (t == NULL)
{
return 1;
}

. . .
scanf
Podemos pedir datos usando la librería estándar, stdio.h, con la función scanf.

scanf.c

1 #include <stdio.h>
2
3 int main(void)
4 {
5 int x;
6 printf("x: ");
7 scanf("%i", &x);
8 printf("x: %i\n", x);
9 }
★ scanf toma un formato, %i, por lo que la entrada se lee para ese
formato. También pasamos la dirección en la memoria donde
queremos que vaya esa entrada “&x”.

$ make scanf
$ ./scanf
x: 50
x: 50
Intentemos nuevamente, pero ahora para strings.

scanf.c

1 #include <stdio.h>
2
3 int main(void)
4 {
5 char *s;
6 printf("s: ");
7 scanf("%s", s);
8 printf("s: %s\n", s);
9 }
★ make previene de cometer este error, así que lo usaremos clang para
demostrarlo.
★ No hemos asignado ninguna memoria para “s”, por lo que se está
escribiendo en una dirección desconocida en la memoria.

$ clang -o scanf scanf.c


$ ./scanf
s: HI!
s: (null)
Podemos llamar a “malloc” para asignar memoria.

scanf.c

1 #include <stdio.h> De esta forma evitamos el error.


2 #include <stdlib.h>
3
4 int main(void) $ clang -o scanf scanf.c
5 { $ ./scanf
6 char *s = malloc(4);
7 printf("s: "); s: HI!
8 scanf("%s", s);
9 printf("s: %s\n", s);
s: HI!
10 }
¿Preguntas?

También podría gustarte