Escolar Documentos
Profissional Documentos
Cultura Documentos
Cada tipo de segmento contiene datos que se interpretan como números, caracteres, imágenes, etc. A veces un tipo de dato tiene varias
interpretaciones, por ejemplo, un dato que contenga 1000001 puede ser interpretado como 65 10 o como 4116 o como la letra A; para
hacer esto, solo escribimos:
int n = 65;
Memoria RAM n
...01011 1000001 01100..
0x7ff...c (dirección de n en base hexadecimal)
En la realidad solo existen pulsos interpretados como 0s y 1s, los objetos en rojo no existen, sólo sirven para informar a los humanos, so-
bre el contenido digital y organizativo de la RAM y su interpretación. En tiempo de ejecución, el sistema operativo, ubica a n en una posi-
ción disponible, por ejemplo, 0x7ff...c, es casi seguro que esta posición cambie si se reprocesa el programa. El programador no asigna
estas posiciones, solamente puede referirse a ellas, preparemos las herramientas para ello. Ya hemos utilizado en modo limitado dos
operadores:
Operador &: suministra la dirección de una variable, por ejemplo:
printf(“%p\n", &n);
Salida: 0x7ff...c
En resumen:
Operador Descripción
& Operador de dirección (referencia) : &n = dirección de n = 0x7ff...c
* Operador de indirección (dereferencia): *&n = valor de la dirección de n = 65
* es casi el inverso de &: *&n = *(&n) = n; pero &*n no compila.
Apuntador
Sea una variable:
int n = 65; // En tiempo de ejecución n es aloja en una posición hexadecimal, ejemplo: 0x7ff...c .
RAM n
65
0x7ff...c
Aplicando el operador de referencia, se cumple:
&n = 0x7ff...c
Supongamos una variable pn, de tipo hexadecimal, podemos asignar:
pn = &n; // = 0x7ff...c
Aplicando el operador de desreferencia, se cumple:
*pn // = *0x7ff...c = 65
¿Cómo se define pn?, podría ser:
hexa pn; // puede ser; pero NO hay tipo hexadecimal
Más bien aplicaremos un artificio muy elegante, definiendo a *pn:
int *pn = &n; // *pn es de tipo int, y pn = &n
Se cumplirá:
RAM pn n
0x7ff...c 65
0x7ff...0 0x7ff...c
Un apuntador pn es una variable, como cualquier otra; ocupa 8 bytes de memoria; almacena un valor hexadecimal, el cual es la direc -
ción de otra variable. pn puede ser interpretada como un variable espía de n, con las siguientes capacidades:
Sabe la dirección de n: pn → 0x7ff...c
Sabe el valor de n : *pn → 65
Puede cambiar el valor de n: *pn = 66; // asigna 66 a n.
PÁGINA: 1
Lenguaje C
El formato de impresión es p: printf("%p", pn);
Se puede leer un apuntador : scanf("%p", pn); // compila y ejecuta pero no tiene sentido en tiempo de ejecución.
Definición de un apuntador
int n;
int *pn = &n; // *pn es el valor (de tipo int) apuntado por pn
Se lee así:
El valor apuntado por pn es de tipo int
int *pn = &n;
valor de pn = dirección de n
pn = 0x7ff...C; // No compila porque no podemos manejar directamente a la memoria; esta es tarea del sistema operativo.
*pn = 0x7ff...c; // No compila , además *pn es de tipo entero.
n = 0x6ff...c; // No compila , además n es de tipo entero, no hexadecimal
2) & y * son opuestos para apuntadores; pero no para otro tipo de variables:
pn es apuntador: *&pn = pn = 0x7ff...c; &*pn = pn = 0x7ff...c
n no es apuntador: *&n = n = 65; pero &*n, no compila, por que *n no compila por no ser posición de memoria.
p1 p2 n m
0x7ff...a 0x7ff...a 2 8
0x7ff...a
#include<stdio.h>
void main(void){
int n=2 , *p1= &n, *p2= &n, m = 8; // declaración y asignación de valores
if(p1==p2) printf("%d %d\n", *p1, *p2); // Igualdad de apuntadores
}
Salida:
2 2
Observaciones:
El valor de n puede cambiar de 3 modos:
n = 4;
*p1 = 4;
*p2 = 4;
Aritmética de apuntadores
Dado que un apuntador apunta a una dirección sobre una línea, sólo se puede desplazar hacia adelante o atrás, mediante sumas y
restas:
ptr; // == 0x7ff...0. Apunta a arr[0]
*ptr; // == 10
ptr + 1; // Apunta a la siguiente posición de tipo int: avanza 4 bytes: 0x7ff...4
// si ptr apuntara a un tipo long, sumará 8 bytes, para char sumará 1 byte; etc.
*(ptr+1); // == 20
ptr+n; // Apunta n posiciones más adelante
*(ptr+n); // valor n posiciones más adelante
Asignaciones:
ptr = ptr + n; // equivale a ptr += n;
ptr = ptr - n; // equivale a ptr -= n;
ptr + 2*3+1; // = pptr + 7; apunta 7 posiciones de bytes, es decir 7*4 bytes, más adelante.
Ptr++; // suma 1.
*ptr++;
printf("%d\n", *ptr++); // printf("%d\n", *ptr); ptr++;
printf("%d\n",*(ptr++)); // printf("%d\n", *ptr); ptr++;
printf("%d\n", *(ptr+1)++); // error de compilación
Apuntador constante
int a=10, b=20;
int * const p = &a; // p apunta solo a la variable a.
*p = 15; // Correcto: asigna 15 a a, el valor apuntado es variable.
p=&b; // ERROR: El valor de p es constante
p a b
0x7ff...a 10 20
0x7ff...a
PÁGINA: 4
Lenguaje C
Arreglo de una dimensión visto como apuntador
Un arreglo de una dimensión es un bloque de n datos del mismo tipo almacenados en modo consecutivo que se comporta como un
puntero constante que apunta a su primer elemento, ejemplo:
2) El valor de ptr y &ptr son diferentes; el valor de arr y &arr son iguales;
ptr tiene una dirección y apunta a otra dirección, no puede apuntarse a sí mismo
printf("%p\n", &ptr); // dirección de ptr: 0x7fa...b
printf("%p\n", ptr); // apunta a 0x7ff...0
3) La función sizeof() que da el tamaño de una variable en bytes, funciona de modo distinto, ejemplo:
#include <stdio.h>
void miFun(int arr[ ], int *ptr);
void main(void){
int arr[5]= {1,2,3,4,5}, *ptr = arr;
printf("%lu\n", sizeof(arr)); // salida 20 = 5*4, 4 es el sizeof de cada variable entera
printf("%lu\n", sizeof(ptr)); // salida 8, el sizeof de una variable de tipo apuntador.
miFun(arr, ptr);
}
PÁGINA: 5
Lenguaje C
void miFun(int arr[ ], int *ptr){
printf("%lu\n", sizeof(arr)); // salida 8, el sizeof de un apuntador, porque arr se comporta como apuntador
printf("%lu\n", sizeof(ptr)); // salida 8, el sizeof de un apuntador.
}
PÁGINA: 6
Lenguaje C
Ejemplo:
// 06_02.c : Dado un puntero float *f1. Verificar que al sumar n a f1 (f2 = f1 = n); la dirección aumenta a:
n*sizeof(float) bytes*/
#include<stdio.h>
void main(void){
float f, *f1 = &f, *f2;
int n = 2;
printf("\nPrimera dirección apuntada: %p (f1)\n", f1);
f2 = f1 + n;
printf("Segunda dirección apuntada: %p (f2 = f1 + n; n = 2)\n", f2);
printf(" --------------\n");
printf("Aumento de dirección : %lu bytes\n", (long int)f2 - (long int)f1);
printf("n*sizeof(float) = %d * %lu : %lu bytes\n", n, sizeof(float), n*sizeof(float));
printf("f2 - f1 : %lu posiciones\n", f2 -f1);
}
Salida:
Primera dirección apuntada : 0x7fff329c5ee0 (f1)
Segunda dirección apuntada: 0x7fff329c5ee8 (f1 + 2)
–-------------------
Aumento de dirección : 8 bytes
n*sizeof(float) = 2 * 4 : 8 bytes
f2 – f1 : 2 posiciones
Matriz Línea
j→
i 2 4 6 8 2 4 6 8 10 12 14 16
10 12 14 16
Cada fila i de la matriz aporta 4 datos a la línea + j que aporta j datos: la posición [i][j] ocupa la posición i*4+j en la línea.
En general, para un arreglo arr[m][n], el elemento arr[i][j] ocupa la posición líneal: i*n+j en la RAM.
PÁGINA: 7
Lenguaje C
Los arreglos de dos dimensiones presentan limitaciones:
1. El lenguaje C los concibe como un arreglo de arreglos
Estos problemas se resuelven en modo óptimo trabajando con punteros, asignando memoria dinámicamente y controlando el número de
elementos en cada dimensión; lo cual haremos en dos capítulos más adelante; por el momento hagamos una aproximación trabajando
con arreglos y apuntadores:
Posiciones: 0 1 2 3 4 5
ptr arr[0][0] arr[0][1] arr[0][2] arr[1][0] arr[1][1] arr[1][2]
0x7234… 2 4 6 8 10 12
0x7234...
arr[i][j] es equivalente a *(ptr+ i*3+j) y a ptr[i*3+j]
void mitad(int n){ // entrada valor 2 void mitad(int *n){ // entrada valor 0x7ff...2
n /=2; *n /=2;
} }
Salida: n = 2 Salida: n = 1
PÁGINA: 8
Lenguaje C
Paso de puntero a arreglo
Ejemplo: Calcular la mitad de los elementos de un arreglo:
int arr[4] = {2, 4, 6, 8}, *parr= arr;
parr arr
0x7ff...2 2 4 6 8
0x7ff...0 0x7ff...2
Arreglo de apuntadores
Un puntero es una variable como cualquier otra, por lo tanto podemos tener arreglos de punteros, ejemplo:
int *ptr[4], n = 6;
ptr[0] = ptr[1] = &n;
*ptr[1]; // = 6
ptr n
0xff..c 0xff..c 6
Posiciones 0 1 2 3 0xff..c
PÁGINA: 9
Lenguaje C
int *p2[2]; // arreglo de punteros
p2[0] = &a[0], p2[1] = &a[6];
for(i=0; i<2; i++)
for(j=0; j<6; j++) printf("%d ", p2[i][j]);
printf(" : 2 dimensiones 2 x 6\n");
Apuntador a apuntador
Un puntero es una variable como cualquier otra, por lo tanto podemos definir un puntero a puntero:
Sintaxis:
tipo **nombreApuntador;
ejemplo:
int a = 3, *p1 = &a, **p2 = &p1;
p2 p1 a
0x7234.. 0x8174.. 3
0x65734.. 0x7234.. 0x8174..
Ejemplo: Sea el arreglo arr[5] = {17, 16, 18, 10, 19}, podemos ordenarlo utilizando el método del mínimo; acá vamos a copiar este
método y modificarlo para ordenar las filas de una matriz arr[5][2] por la primera columna, lo haremos de dos modos
• Utilizando arreglos: Se mueven las filas de la matriz para ordenarla
• Utilizando apuntadores en 3 pasos:
1) Se crea un arreglo de apuntadores *p[ ] a las filas de la matriz;
PÁGINA: 10
Lenguaje C
2) Se ordena ascendentement a p, no las filas de la matriz;
3) Se accede a los valores de la matriz a travez de los apuntadores: *p[i]
Paso 1 paso 3
Apuntadores Matriz Apuntadores Matriz
parr arr parr arr
parr[0] 17 3 parr[0] 17 3
parr[1] 16 6 parr[1] 16 6
parr[2] 18 6 parr[2] 18 6
parr[3] 10 1 parr[3] 10 1
parr[4] 19 2 parr[4] 19 2
Recorrido de arreglos:
parr arr
parr[0] 17 3
parr[1] 16 6
parr[2] pi 18 6
parr[3] 10 1
parr[4] 19 2
pj
Ordenar ascendentemente una matriz por la primera columna utilizando el método del mínimo
// 06_05a.c: utilizando arreglos // 06_04b.c: utilizando apuntadores
#include<stdio.h> #include<stdio.h>
void ordenar(int arr[][2], int n, int m); void ordenar(int *parr[], int n, int m);
void reportar(int arr[][2], int n, int m); void reportar(int *parr[], int n, int m);
void main(void){ void main(void){
int n = 5, m = 2, arr[5][2] = {17, 3, 16, 6, 18, 6, 10, int n = 5, m = 2, arr[5][2] = {17, 3, 16, 6, 18, 6, 10, 1, 19, 2};
1, 19, 2}; int i, *parr[n];
for(i=0; i<n; i++) parr[i] = arr[i];
printf("Arreglo original:\n"); printf("Arreglo original:\n");
reportar(arr, n, m); reportar(parr, n, m);
ordenar(arr, n, m); ordenar (parr, n, m);
printf("\nArreglo ordenado:\n"); printf("\nArreglo ordenado:\n");
reportar(arr, n, m); reportar(parr, n, m);
} }
void reportar(int arr[ ][2], int n, int m) { // restricción void reportar(int *parr[ ], int n, int m) {
int i, j; int **pi = parr, **pimax = pi+n, *pj, *pjmax;
for(i=0; i<n; i++) { for(; pi<pimax; pi++) {
for(j=0; j<m; j++) printf("%d\t", arr[i][j]); for(pj=*pi, pjmax= pj+m; pj<pjmax; pj++) printf("%d\t", *pj);
printf("\n"); printf("\n");
} }
} }
void ordenar(int arr[][2], int n, int m) { // restricción void ordenar(int *parr[], int n, int m) {
int i, j, imin, amin; int **pi = parr, **pimax = pi+n, **pj, **pjmax, **imin, amin, *temp;
for(i=0; i<n-1; i++){ for(; pi<pimax-1; pi++) {
imin = i; imin = pi;
amin = arr[i][0]; amin = **pi;
for(j=i+1; j<n; j++) for(pj = pi+1; pj<pimax; pj++)
if (arr[imin][0] > arr[j][0]){ if (amin > **pj){
amin = arr[j][0]; amin = **pj;
imin = j; imin = pj;
} }
if(imin>i){ if(imin>pi){
arr[imin][0] = arr[i][0]; temp = *imin;
arr[i][0] = amin; *imin = *pi;
amin = arr[imin][1]; *pi = temp;
PÁGINA: 11
Lenguaje C
arr[imin][1] = arr[i][1];
arr[i][1] = amin;
} }
} }
} }
Salida:
Arreglo original
17 3
16 6
18 6
10 1
19 2
Arreglo ordenado:
10 1
16 6
17 3
18 6
19 2
Note: la restricción en la segunda dimensión No hay restricción en la segunda dimensión
Ejemplos:
int *p[2];
* y [2] son adyacentes a p, [2] tiene mayor prioridad que * (regla 2)
→ arreglo de 2 punteros a enteros.
int (*p)[2];
(*p) Es un puntero
(*p)[2] Es un puntero a un arreglo de 2 elementos de tipo entero
PÁGINA: 12
Lenguaje C
// 06_06.c: 4 formas, entre otras, para acceder a los datos de una matriz utilizando punteros.
// Aplicación de las precedencias de *, ( ) y [ ]
#include<stdio.h>
void main(void){
int i, j, a[3][2] = {1, 2, 3, 4, 5, 6};
int *p1[6]; int (*p2)[2]; int *p3; int *p3, **p4;
// arreglo de 6 punteros // puntero a arreglo de 2 elementos // puntero a fila // puntero a puntero entero
printf("Arreglo de punteros\n"); printf("\nPuntero a arreglo\n"); printf("\nPuntero a entero\n"); printf("\nPuntero a puntero\n");
p2 = a; // apunta a matriz
for(i=0; i<3; i++) for(i=0; i<3; i++) for(i=0; i<3; i++) { for(i=0; i<3; i++) {
p3 = a[i]; // apunta a fila p3 = a[i];
p4 = &p3; // Apunta a p3
for(j=0; j<2; j++) { for(j=0; j<2; j++) for(j=0; j<2; j++) for(j=0; j<2; j++)
p1[i*2+j] = &a[i][j];
printf("%d\n", *p1[i*2+j]); printf("%d\n", p2[i][j]); printf("%d\n", p3[j]); printf("%d\n", (*p4)[j]);
} } }
} } } }
Salida: 1 2 3 4 5 6
Apuntador a void
La palabra reservada void (se pronuncia void en inglés y significa vacío, nulo, vacante, nada, ...) se utiliza para crear un puntero que
apuntará a un tipo de dato genérico, para usarlo se requiere hacer casting al apuntador, ejemplo para int: pInt = (int *)pVoid;
int in=1;
double db=2.4;
void *pvoid; // un apuntador a void puede apuntar a cualquier tipo de dato:
Ejemplo: La función free( ), para liberar memoria dinámica apuntada por un puntero p de cualquier tipo, tiene la sintaxis:
void free(void *ptr);
Se puede usar:
free(p); // p es un apuntador a cualquier tipo de dato, free() libera la memoria y asigna p = NULL.
¡Apuntaste bien!!!!
Origen: https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQFyz_WY0xkicaiWFizB_rnTM9D-9X8wB9QhPv5jS2h4mQGdPMC6g
Si fallas en punteros, cometerás el famoso error: “violación de segmento", que es difícil de corregir, ya que no se sabe donde ocurrió, te
parecerá que la máquina se volvió loca; para identificar donde perdiste la puntería puedes ayudarte escribiento trazas, ejemplo:
printf(“Estoy en AA %d\n", n); // valor de n en la línea AA
printf(“Estoy en BB %d\n", n); // valor de n en la línea BB
Usando variables de tipo puntero -muy importantes, potentes y peligrosas- pasas del manejo estático (en tiempo de programación) de la
RAM, al manejo dinámico (en tiempo de ejecución). Toda la teoría está bien organizada y completa; para la práctica te recomiendo: 1)
Enamórate del tema y cuéntale a tu novi@ para que no se ponga celos@, 2) Haz una sesión personal especial y trata de entender todos
PÁGINA: 13
Lenguaje C
los detalles, y 3) Reúnete con los amigos para tirar flechas, perdón, para resolver problemas de apuntadores. Ya casi eres un profesio -
nal.
Ejercicios:
1) Para un arreglo tridimensional, por ejemplo:
int a[2][3][2] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} ;
¿Que posición lineal ocupará a[i][j][k]?
2) Una matriz p[4][12] representa una producción de los 12 meses en 4 años. Lea la matriz y use punteros para calcular el promedio de
producción en cada año y en los 12 meses:
Años
1 2 3 4
Promedio: xx xx xx xx
Meses
1 2 3 4 … 12
Promedio: xx xx xx xx xx
PÁGINA: 14