Professional Documents
Culture Documents
Apuntadores 7
(pointers; también punteros)
07.01 Introducción
Hasta el momento hemos aprendido los temas:
Temas Capacidades logradas
Programación estructurada: Programación secuencial, 1) Manipular variables simples de tipo int, float, etc.
estructuras de decisión, estructuras de repetitición y funciones 2) Toda la lógica de programación
Arreglos: secuencia de datos de un mismo tipo Operar arreglos de una o más dimensiones
Para resolver los problemas mencionados, es necesario entender el funcionamiento de la Random Acces Memory (RAM) que utiliza un
programa al momento de ejecución. Supongamos que se declara:
int m = 66, n = 65;
Físicamente estas variables se alojan en la RAM (la cual es como una línea recta):
Memoria RAM:
m n
66 65
Dirección física en base hexadecimal: 0x7ff...0 0x7ff...4 (direcciones físicas de m y n)
Estas direcciones cambian cada vez que se ejecuta el programa
Atento: Físicamente lo único que existe en la RAM es lo que está escrito en negro; lo escrito en rojo no existe; pero se escribe para acla-
rar al lector los componente involucrados.
Podemos tener un nuevo tipo de variable p que contenga información de la dirección física de m al momento de ejecución:
m n p
66 65 0x7ff...0
0x7ff...0 0x7ff...4 0x7ff...c
Aplicaciones de apuntadores: Los apuntadores tienen múltiples aplicaciones con diferentes tipos de datos y las iremos estudiando en
los siguientes capítulos; en el apéndice 2 se coleccionan todas ellas para facilitar una visión integral de los apuntadores.
Definición de apuntador
int m;
int *p = &m; // *p es el valor (de tipo int) apuntado por p
Se lee así:
El valor apuntado por p es de tipo int
int *p = &m;
valor de p = dirección de m
PÁGINA: 2
Lenguaje C++
4) Variables y valores del ejemplo:
m = 66 p = 0x7ff...0
&m = 0x7ff...0 &p = 0x7fb...0
*m error no compila: m es de tipo int *p = 66
PÁGINA: 3
Lenguaje C++
Ejemplo: Una variable puede ser apuntada por varios apuntadores:
p1 p2 n m
0x7ff...a 0x7ff...a 2 8
0x7ff...a
#include<cstdio>
main(){
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); // compara valores de apuntadores
}
Salida:
2 2
Observaciones:
El valor de n puede cambiar de 3 modos:
n = 4;
*p1 = 4;
*p2 = 4;
Apuntador constante
Definición:
int a=10, b=20;
int * const pa = &a; // pa es constante, solo apuntará a la variable a.
*pa = 15; // Correcto: asigna 15 a a, el valor apuntado es variable.
pa=&b; // ERROR: El valor de pa es constante
pa a b
0x7ff...a 10 20
0x7ff...a
PÁGINA: 4
Lenguaje C++
07.03 Apuntador a un arreglo de una dimensión
Un apuntador puede apuntar a un arreglo, ejemplo:
int a = 65, *pa = &a;
int arr[3] = {10, 20 ,30}, *parr = arr; // Atento, no es necesario &: arr suministra la dirección de su primer byte.
Segunda aplicación de apuntadores: Un apuntador se comporta como si fuera arreglo de una dimensión
Definamos:
int arr[3] = {10, 20 ,30}, *parr = arr, a = 65;
Aunque parr no tiene elementos como arr, se comporta como si fuera el arreglo arr:
for(i=0; i<3; i++) printf("%d ", parr[i]);
Salida:
10 20 30
Si apuntamos al elemento 1:
parr = &arr[1]; // apunta al segundo elemento
*parr = 21; // asigna valor
for(i=-1; i<2; i++) printf("%d ", parr[i]); // puede tomar índices: -1, 0 y 1
Salida:
10 21 30
Inclusive si solo apunta a una variable, también se comporta como arreglo:
parr = &a;
printf("%d ", pa[0]); // Salida: 65
En este caso, es un error de lógica SEVERO usar otros índices, como pa[1], pa[-1] …: no controlamos esas direcciones.
Un apuntador no tiene elementos (como un arreglo); pero se comporta como arreglo de una dimensión, de tamaño indefinido, a partir
de la posición que apuntada (no importa si la posición apuntada es o no arreglo) y puede apuntar hacia adelante o atrás.
Atento:
parr = arr; // parr apunta a arr[0]
parr++; // parr apunta a arr[1]
parr++; // parr apunta a arr[2]
parr++; // parr apuntaría a arr[3]: no hay error de compilación; pero en tiempo de ejecución hay una invasión a un dato
// desconocido,
pa = &a; // pa apunta a a.
pa++; // no hay error de compilación; pero en tiempo de ejecución hay una invasión a un dato desconocido.
Presedencias de operadores
Operador Descripción Presedencia Asociativa
() Paréntesis (llamar a funciones) izquierda-a-derecha
[] Corchetes (arreglos)
. selección de miembro vía objeto (ver capítulo de estructuras o clases)
-> selección de miembro vía apuntador (ver capítulo de estructuras)
++ -- Post incremento/decremento
++ -- Pre incremento/decremento Derecha-a-izquierda
+- Operadores unarios más/menos
!~ Negación lógica/bitwise complemento
(type) Casting: convierte a tipo (type) un dato de otro tipo, ejemplo: (int)
* Desreferencia, indirección de dirección de memoria
& referencia, dirección de variable
sizeof Determine el tamaño en bytes
Pre y postoperadores, funcionan como siempre; atento, aplique las precedencias de operadores:
Post operador Pre operador
p++; // aumenta 1 a p ++p; // aumenta 1 a p
(*p)++; // aumenta 1 a *p ++*p; // aumenta 1 a *p
printf("%p\n", p++); // printf("%p\n", p); p++; printf("%p\n", ++p); // ++p; printf("%p\n", p);
printf("%p\n", (p+1)++); // error de compilación, no se puede printf("%p\n", ++(p+1)); // error de compilación
// postoperar a una expresión (p+1)
printf("%d\n", *p++); // printf("%d\n", *p); p++; printf("%d\n", *++p); // ++p; printf("%d\n", *p) ;
printf("%d\n",*(p++)); // printf("%d\n", *p); p++; printf("%d\n", *(++p)); // ++p; printf("%d\n", *p) ;
printf("%d\n", (*p)++); // printf("%d\n", *p); aumenta 1 a *p; printf("%d\n", ++(*p)); // aumenta 1 a *p; printf("%d\n", *p) ;
printf("%d\n", ++*p); // aumenta 1 a *p; printf("%d\n", *p) ;
printf("%d\n", *(p+1)++);// error de compilación printf("%d\n", ++*(p+1)); // aumenta 1 a *(p+1);printf("%d\n", *(p+1));
PÁGINA: 6
Lenguaje C++
Tercera aplicación de apuntadores: Referencias a elementos de un arreglo
Sea la definición:
int a = 1, arr[3] = {10, 20 ,30}, *parr = arr, arr1[3];
Un arreglo de una dimensión se comporta como un apuntador:
arr[i] ≡ *(arr+i) ≡ *(parr+i) ≡ parr[i]
&arr[i] ≡ arr+i ≡ parr+i ≡ &parr[i] // se aplicó el operador &
Ejemplo:
para i = 2:
arr[2] ≡ *(arr+2) ≡ *(parr+2) ≡ parr[2] == 30
&arr[2] ≡ arr+2 ≡ parr+2 ≡ &parr[2] == una dirección de memoria // se aplicó el operador &
para i = 0:
arr[0] ≡ *arr ≡ *parr ≡ parr[0] == 10
&arr[0] ≡ arr ≡ parr ≡ &parr[0] == una dirección de memoria // se aplicó el operador &
PÁGINA: 7
Lenguaje C++
Cuarta aplicación de apuntadores: Como variables de iteración de un arreglo
Observe que:
*parr → 10
parr++; // apunta a 0xff...4
*p → 20
parr++; // apunta a 0xff...8
*p → 30
Un arreglo se puede iterar (recorrer) de varios modos, por ejemplo:
Usando el arreglo arr Usando el apuntador parr
for(i=0; i<3; i++) printf("%d ", arr[i]); for(i=0; i<3; i++) printf("%d ", parr[i]);
for(i=0; i<3; i++) printf("%d ", *(arr+i)); for(i=0; i<3; i++) printf("%d ", *(parr+i));
for(i=0; i<3; i++) printf("%d ", *parr++); // parr apunta al final
for( ; parr<pmax; parr++) printf("%d ", *parr); // parr apunta al final
while(i<3) printf("%d ", arr[i++]); while(parr<pmax) printf("%d ", *parr++); // parr apunta al final
Se puede abusar, por ejemplo, entremezclar las variables, no lo haga:
for( ; parr<pmax; parr++) printf("%d ", arr[i++]);
Salida en todos los casos:
10 20 30
Atento: Un apuntador se mueve con libertad y puede abandonar el rango del arreglo apuntado, ejemplo, imprimir un arreglo 2 veces:
main(){
int a[3] = {1, 2, 3}, *pa= a, i;
for(i=0; i<3; i++) printf("%d ", *pa++); printf("\n"); // los desplazamientos *pa++ hacen que pa abandone el rango de a
pa = a; // se debe reapuntar al inicio de a.
for(i=0; i<3; i++) printf("%d ", *pa++); printf("\n");
}
Salida:
123
123
Ejemplo: Dados:
1: int n;
2: float *f1, *f2;
3: f2 = f1 +n;
Demostrar que f2 – f1 = n* sizeof(float)
Hagamos algunas aclaraciones:
1) En computación no se demuestra proposiciones (semántica); sino que se opera con casos concretos, por lo tanto
reemplazaremos demostrar por verificar.
2) n es un valor leído o asignado, por ejemplo: n = 2; no es genérico.
3) No se puede asignar directamente:
f2 = f1 + n; // f1 es indeterminado, no apunta a nada, por lo tanto f2 no tiene sentido
Vamos a reformular el ejercicio:
Dados:
1: int n = 2;
2: float f, *f1 = &f, *f2; // f1 no es indeterminado, apunta a una dirección
3: f2 = f1 +n; // la dirección apuntada por f1 es aumentada en n
Verificar que: f2 – f1 = n* sizeof(float) = 8 // sizeof(float) = 4 = tamaño de variable flotante.
PÁGINA: 9
Lenguaje C++
Observaciones:
1) C++ concibe a un arreglo de dos dimensiones como un arreglo de arreglos: el primer arreglo son las filas, los segundos las
columnas:
Los problemas de alojamiento físico se resuelven en modo óptimo utilizando dos herramientas:
Apuntadores: Resuelven los problemas de adaptación a la RAM: manipulación de objetos complejos y ordenamientos.
Memoria dinámica: Permite operar con cantidades exactas de datos, que será aprendida en el capítulo de Memoria Dinámica
Los problemas lógicos de mala gestión de los índices, son responsabilidad de los desarrolladores.
PÁGINA: 10
Lenguaje C++
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: 11
Lenguaje C++
¿Cuál de las dos notaciones utilizar? La notación lógica es la que manda porque indica lo que se debe hacer; cuando los desplaza-
mientos lógicos son continuos -sin saltos- ambas notaciones coinciden, entonces se recomienda usar la notación física porque es más
rápida. Hay casos en que se usan ambas, ejemplo, transponer una matriz en otra.
PÁGINA: 12
Lenguaje C++
Atento: Un apuntador a un arreglo puede salirse del rango del arreglo apuntado, ejemplo, imprimir un arreglo bidimensional 2 veces:
#include<cstdio>
void prin2(int m, int n , int *pa);
main(){
int a[2][3] = {1, 2, 3, 4, 5, 6}, *pa= a[0]; // supongamos que pa apunta a 0xff..a
prin2(2, 3 , pa); // pasa los argumentos 2, 3, 0xff..a
}
void prin2(int m, int n, int *pa){ // recibe valores: 2, 3, 0xff..a
int i, j, k=0, *p = pa; // pa y p apuntan a 0xff..a
while(k++<2){
for(i=0; i<m; i++){
for(j=0; j<n; j++) printf("%d ", *p++); // p aumenta
printf("\n");
} // p apunta a 0xff..a + m*n*4, se salió del rango de a[2][3]
p = pa; // p vuelve a apuntar a la posición inicial: 0xff..a
}
}
Salida (2 veces):
123
123
ptr n
0xff..c 0xff..c 6
Posiciones 0 1 2 3 0xff..c
// 2 dimensiones: 2 x 6
int *p2[2]; // arreglo de 2 punteros
p2[0] = &a[0], p2[1] = &a[6];
printf("\n2 dimensiones 2 x 6: ");
for(i=0; i<2; i++)
for(j=0; j<6; j++) printf("%d ", p2[i][j]); // vista de 2 dimensiones
// de 3 dimensiones: 2 x 3 x 2
int *p3[2][3]; // matriz de 2x3 punteros
p3[0][0] = &a[0], p3[0][1] = &a[2], p3[0][2] = &a[4];
p3[1][0] = &a[6], p3[1][1] = &a[8], p3[1][2] = &a[10];
printf("\n3 dimensiones 2 x 3 x 2: ");
for(i=0; i<2; i++)
for(j=0; j<3; j++)
for(k=0; k<2; k++) printf("%d ", p3[i][j][k]); // vista de 3 dimensiones
printf("\n");
}
Salida:
1 dimensión de 12 elementos: 1 2 3 4 5 6 7 8 9 10 11 12
PÁGINA: 13
Lenguaje C++
2 dimensiones de 2 x 6 : 1 2 3 4 5 6 7 8 9 10 11 12
3 dimensiones de 2 x 3 x 2 : 1 2 3 4 5 6 7 8 9 10 11 12
p2 p1 a
0x7234.. 0x8174.. 3
0x65734.. 0x7234.. 0x8174..
Ejemplo: Ordenar las filas de una matriz arr[5][2] por la primera columna (utilizar el método de mínimos), lo haremos en dos versiones:
• Utilizando arreglos: se mueven las filas de la matriz
• Utilizando apuntadores (no se mueven las filas de la matriz), en 3 pasos:
1) Se crea un arreglo de apuntadores *p[ ] a las filas de la matriz;
2) Se ordena ascendentement a p en base a la primera columna de arr, la matriz arr no es alterada;
3) Se accede a los valores de la matriz a través de los apuntadores: *p[i]
De esta forma se tienen 2 vistas de arr:
Estática: arr no cambia en nada y se la ve como es.
ordenada (lógica): con la ayuda de los apuntadores, arr aparece ordenada por la primera columna
Paso 1 paso 2
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
PÁGINA: 14
Lenguaje C++
Paso 3:
parr arr
parr[0] 17 3
parr[1] 16 6
parr[2] pi 18 6
parr[3] 10 1
parr[4] 19 2
pj
Séptima aplicación de apuntadores: Paso de argumento de tipo arreglo de apuntadores a una función
/* Si una función pasa como argumento un arreglo de apuntadores; en la función llamada, la
variable receptora es un apuntador a apuntador
*/
void reportar(int n, int m, int **parr) { // apuntador de apuntador
int i, j;
for(i=0; i<n; i++){
for(j=0; j<m; j++) printf("%d\t", parr[i][j]);
printf("\n");
}
}
main(){
int n = 3, m = 2, arr[5][2] = {17, 3, 16, 6, 18, 6}, i, *parr[n];
for(i=0; i<n; i++) parr[i] = arr[i];
reportar(n, m, parr); // paso de arreglo de apuntadores
}
Ordenar ascendentemente una matriz por la primera columna utilizando el método del mínimo
// 07_05a.c: utilizando arreglos // 07_05b.c: utilizando apuntadores
#include<cstdio> #include<cstdio>
void ordenar(int n, int m, int arr[][2]); void ordenar(int n, int m, int **p);
void reportar(int n, int m, int arr[][2]); void reportar(int n, int m, int **p);
main(){ main(){
int n = 5, m = 2, arr[5][2] = {17, 3, 16, 6, 18, 6, 10, 1, 19, 2}; int n = 5, m = 2, arr[5][2] = {17, 3, 16, 6, 18, 6, 10, 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(n, m, arr); reportar(n, m, parr);
ordenar(n, m, arr); ordenar (n, m, parr);
printf("\nArreglo ordenado por la primera columna:\n"); printf("\nArreglo ordenado por la primera columna:\n");
reportar(n, m, arr); reportar(n, m, parr);
} }
void reportar(int n, int m, int arr[ ][2]) { void reportar(int n, int m, int **p) {
int i, j; int i, j, *pi;
for(i=0; i<n; i++) { for(i=0; i<n; i++){
pi = p[i];
for(j=0; j<m; j++) printf("%d\t", arr[i][j]); for(j=0; j<m; j++) printf("%d\t", *pi++); /// ≡ p[i][j]
printf("\n"); printf("\n");
} }
} }
void ordenar(int n, int m, int arr[][2]) { void ordenar(int n, int m, int **p) {
int i, j, imin, vmin; int i, j, **pmin, vmin, **pj, *temp;
for(i=0; i<n-1; i++){ for(i=0; i<n-1; i++, p++) {
imin = i; pmin = p;
vmin = arr[i][0]; vmin = **p;
for(j=i+1; j<n; j++) // mínimo valor for(j=i+1, pj=p+1; j<n; j++, pj++) // mínimo valor
if (vmin > arr[j][0]){ if (vmin > **pj){
vmin = arr[j][0]; vmin = **pj;
imin = j; pmin = pj;
} }
if(imin>i){ // intercambio de filas if(pmin>p){ // intercambio de apuntadores
PÁGINA: 15
Lenguaje C++
temp = *pmin;
arr[imin][0] = arr[i][0]; *pmin = *p;
arr[i][0] = vmin; *p = temp;
vmin = arr[imin][1];
arr[imin][1] = arr[i][1];
arr[i][1] = vmin; }
} }
} }
}
Salida:
Arreglo original Arreglo ordenado:
17 3 10 1
16 6 16 6
18 6 17 3
10 1 18 6
19 2 19 2
Identificar:
int *p[2];
* y [2] son adyacentes a p, [2] tiene mayor prioridad que * (regla 2)
→ arreglo de 2 punteros a enteros.
Identificar:
int (*p)[2];
(*p) Es un puntero
(*p)[2] Es un puntero a un arreglo de 2 elementos de tipo entero
// 07_06.c: Algunas formas de acceder a los datos de una matriz utilizando punteros y las precedencias de *, ( ) y [ ].
//Al llamar a una función, hay varias formas de apuntar a una matriz.
Argumento: arreglo Argumento: primera fila Argumento: apuntador
Parámetro: puntero a arreglo de n elementos Parámetro: puntero a la dirección inicial de un arreglo de 2 dimensiones
#include<cstdio> #include<cstdio> #include<cstdio>
void mitad(int m, int n, int (*p)[2]); void mitad(int m, int n, int *p); void mitad(int m, int n, int *p);
main(){ main(){ main(){
int a[3][2] = {2, 4, 6, 8, 10, 12}; int a[3][2] = {2, 4, 6, 8, 10, 12}; int a[3][2] = {2, 4, 6, 8, 10, 12}, *p = a[0];
mitad(3, 2, a); mitad(3, 2, a[0]); mitad(3, 2, p);
} } }
void mitad(int m, int n, int (*p)[2]){ void mitad(int m, int n, int *p){
int i, j; int *pmax = p+m*n;
for(i=0; i<m; i++)
for(j=0; j<n; j++){ while(p<pmax){
p[i][j] /=2; *p /=2;
printf("%d ", p[i][j]); printf("%d ", *p++);
PÁGINA: 16
Lenguaje C++
} }
printf("\n"); printf("\n");
} }
Salida: 1 2 3 4 5 6
Ejemplo:
01 int in = 1; float fl = 2.4;
02 // asignar a void asignar void imprimir
03 pvoid = ∈ int *pin = (int *)pvoid; printf("%d\n", *pin); // salida: 1
04 pvoid = &fl; float *pfl = (float *)pvoid; printf("%lf\n", *pfl); // salida: 2.4
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:
int *p = ….;
…
free(p); // p es un apuntador a cualquier tipo de dato, free() libera la memoria y asigna p = NULL.
PÁGINA: 17
Lenguaje C++
¡Apuntaste bien!!!!
Fuente: https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQFyz_WY0xkicaiWFizB_rnTM9D-9X8wB9QhPv5jS2h4mQGdPMC6g
En computación no somos tan dramáticos, si no apuntas bien, solo tendrás invisibles errores de lógica que ya sabes como corregirlos. Si
no quieres equivocarte, 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 los detalles, y 3) Reúnete con los amigos para tirar flechas, perdón, para resolver problemas
de apuntadores. ¡Ya casi eres un profesional!
07.13 Ejercicios
1) Sea la definición siguiente:
char a[2][3] = {'a', 'b', 'c', 'd', 'e', 'f'}, *pa = &a[0][0];
Imprima la matriz:
a b c
d e f
Imprima de 3 maneras con las funciones:
void imprimirMat (char a[][3], int m, int n); // Notación de arreglos
void imprimirPtr1(char *pa, int m, int n); // Notacion de apuntadores y recorrido con índices
void imprimirPtr2(char *pa, int m, int n); // Notacion de apuntadores y recorrido con apuntadores
Matriz duplicada:
2 4 6 8 10 12 14 16 18 20 22 24
5) 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
6) Lea las temperaturas t diarias de un lugar tropical, termine cuando t <= 0 o haya leido 100 días; cada vez que lee t imprima todas
las t.
PÁGINA: 19