Vous êtes sur la page 1sur 11

Lenguaje C

Apuntadores (punteros - pointers en inglés)


Variables y Memoria
La memoria del computador es como una larguísima línea (1 dimensión) en la que se guardan pulsos (dígitos 0 y 1), allí solo se puede
avanzar para adelante y para atrás; lo cual es muy limitado para guardar datos del mundo real: números, caracteres, dibujos, sonidos,
imágenes, video, etc. Para ello se clasifica la memoria en segmentos:

tipo 1 tipo 2 tipo 3

Cada tipo de segmento pueden interpretarse como números, caracteres, imágenes, etc. A veces un tipo de segmento tiene varias
interpretaciones, por ejemplo, un segmento 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 los números 0 y 1.
Las indicaciones en rojo no existen realmente, solo sirven para darnos información que nos ayuda a comprender lo que hay en la RAM.
En tiempo de ejecución, el sistema operativo, ubica a n en una posició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

Operador *: suministra el contenido de una dirección, por ejemplo:


printf(“%d\n", *&n);
Salida: 65

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
Un apuntador ptr es una variable, como cualquier otra; ocupa 8 bytes de memoria; su formato de impresión es p: printf(“%p”, ptr);
almacena valores hexadecimales, los cuales son la dirección de otra variable, ejemplo, dada una variable:
int n = 65;

Podemos guardar en ptr la dirección de n:


ptr = &n; // Se dice que ptr apunta a n.

RAM ptr n
0x7ff...c 65
0x7ff...0 0x7ff...c
Tiene una dirección en la RAM = 0x7ff...0

ptr puede ser interpretado como un variable espía benévola de n, con las siguientes capacidades:
Sabe la dirección de n: ptr → 0x7ff...c
Sabe el valor de n : *ptr → 65
Puede cambiar el valor de n: *ptr = 66; // asigna 66 a n.

Jmm, estamos usando ptr, pero todavía no lo hemos definido, lo cual es un error. Definir a ptr no es tan sencillo, podríamos definir y
asignar valor a la variable ptr así:
pointer ptr = &n;

Para mayor precisión, podemos indicar que ptr guarda direcciones de variables de tipo int:
int pointer ptr = &n;

PÁGINA: 1
Lenguaje C
Lo mismo sucedería para otros tipos de variables, ejemplo:
float datoFloat;
float puntero ptrf = &datoFloat; // para cada tipo de dato se tendría un puntero

En la práctica estamos duplicando los tipos de datos:


int puntero ptr;
float puntero ptrf;
…..
Resulta exagerado duplicar el número de tipos de variables, por culpa de los apuntadores; veamos un artificio que nos da una solución
corta y elegante.
int n;
int *ptr = &n; // *ptr es el valor (de tipo int) apuntado por ptr

Se lee así:
El valor apuntado por ptr es de tipo int
int *ptr = &n;
valor de ptr = dirección de n

Se puede definir y asignar por separado:


int n = 65;
int *ptr;
ptr = &n; // ptr toma como valor la dirección de n
O definir todo junto:
int n = 65, *ptr = &n;

Atento a los detalles


1) ¿Para qué tanto artificio? Para resolver el siguiente problema: Al momento de programación trabajamos con variables, lo cual es
estático: el programador prevé (adivina) tamaños de arreglos (bloques) de datos; si se queda corto, no hay garantía de que los
resultados sean correctos y el programa podría ser abortado; si se alarga, desperdicia memoria cara. El uso de apuntadores permite
manejar la longitud de los arreglos en tiempo de ejecución, lo cual es dinámico; por ejemplo si define un puntero a un bloque de 20
elementos y se necesitan 40 más, puede redimensionarlo a 60 con suma facilidad; esto se aplica mucho para cadenas de caracteres;
esto no se puede hacer usando arreglos. Adicionalmente, los punteros reflejan en modo directo la naturaleza lineal de la memoria
RAM.

2) Al asignar valor a ptr:


int n, m, *ptr;
ptr = &n; // ptr apunta a n (al primer byte)
ptr = &m; // ptr apunta a m (otra variable)
ptr = NULL; // ptr no apunta a nada

ptr = 0x7ff...C; // No compila porque no podemos manejar directamente a la memoria; esta es tarea del sistema operativo.
n = 0x6ff...c; // No compila , además n es de tipo entero, no hexadecimal
*ptr = 0x7ff...c; // No compila , además *ptr es de tipo entero.

3) & y * son opuestos, pero no por completo, atento:


Son opuestos para apuntadores: *&ptr = ptr = 0x7ff...c; &*ptr = ptr = 0x7ff...c

No Opuestos para variables no apuntadoras : *&n = n = 65; pero &*n, no compila, ya que *n no funciona porque n no es una
posición de memoria.

4) Un apuntador tiene tres atributos:


 tipo de dato al que apuntará : int, char, etc. (un solo tipo).
 tipo : apuntador, ocupa 8 bytes, el formato de impresión es %p: printf(“%p", ptr);
 valor : dirección de la variable o bloque apuntado (en este caso) = 0x7ff...c
tipo dato, tipo, valor

int n = 65, *ptr = &n;

5) Variables y valores del ejemplo:


n = 65 ptr = 0x7ff...c
&n = 0x7ff...c &ptr = 0x7ff...0
*n error, n no es posición de memoria *ptr = 65
PÁGINA: 2
Lenguaje C
6) Mientras ptr apunte a n: int n = 65, *ptr = &n, m;
Se cumplen las siguientes relaciones:
Variables Relación Ejemplo
&n y ptr Equivalentes Leer valor de n:
scanf(“%d", &n);
scanf(“%d", ptr);
No equivalentes ptr = &m; // apunta a otra variable
&n = &m; // error: no se asigna posición de memoria a una variable
n y *ptr Equivalentes Escribir valor de n
printf(“%d", n); printf(“%d", *ptr);

Asignar valor cualquiera a n:


n = 65; *ptr = 65;
n += 2; *ptr += 2;

7) Ya utilizamos apuntadores para pasar valores por referencia en funciones :


miFun(&n); // el valor de n cambiará a 3.
Llama a:
void miFun(int *n) {*n= 3;}
Pero el uso más frecuente es para apuntar a bloques de datos, como veremos luego.

8) Un solo operador * representa a dos operaciones distintas:


* : Indirección
* : Multiplicación
El operador de indirección * tiene mayor prioridad que el operador de multiplicación *, ejemplo:
int n=2, *ptr= &n, b;
b = *ptr * 2; // es equivalente a: b = *ptr*2; b = 2 * *ptr; b = 2**ptr; b = 2 * * ptr; (no importa la cantidad de espacios)
No abuse de la escritura confusa, de ser necesario use paréntesis para aclararla. Más adelante estudiaremos las reglas de
priorización.

Ejemplo: Una variable puede ser apuntada por varios apuntadores:

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;

Un puntero apunta a 0 o 1 variable; pero puede cambiar de variable apuntada:


p1 = &m; // y deja de apuntar a n

Apuntador un a arreglo de una dimensión


Un apuntador puede apuntar a un arreglo, ejemplo:
int arr[3] = {10, 20 ,30}, arr1[4], *ptr = arr; // Atento, no es necesario &, ya lo explicaremos luego.

memoria ptr arr


0x7ff...0 10 20 30
Direcciones 0x7fa...b 0x7ff...0 0x7ff...4
Indices: 0 1 2
PÁGINA: 3
Lenguaje C

Aritmética de punteros
Dada la naturaleza de un puntero -apunta a una dirección sobre una línea-, 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

ptr-n; // Apunta n posiciones previas


*(ptr-n); // valor n posiciones previas

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.

Se permite la resta de dos punteros:


int nn[10], *ptr1, *ptr2, n;
ptr1 = nn; // equivalente a: ptr1 = &nn[0];
// apunta a nn[0].
ptr2 = &nn[2]; // apunta a nn[2].
n = ptr2 - ptr1; // diferencia (en posiciones enteras) = 2.

No se permiten otro tipo de operaciones


ptr = ptr * 2; // Error: No se admiten multiplicaciones de valores de memoria
ptr1 = ptr1 + ptr2; // Error

Pre y postoperadores, funcionan como siempre; atento, aplique las precedencias:


Operador Descripción Asociatividad
() 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 valor a valor temporal de type)
* Desreferencia
& Dirección (de operador)
sizeof Determine el tamaño en bytes

ptr++; // aumenta 1 a ptr;


printf("%p\n", ptr++); // printf("%p\n", ptr); ptr++;
printf("%p\n", (ptr+1)++); // error de compilación, no se puede postoperar a una expresión (ptr+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

++ptr; // aumenta 1 a ptr;


printf("%p\n", ++ptr); // ++ptr; printf("%p\n", ptr);
printf("%p\n", ++(ptr+1)); // error de compilación

++*ptr; // aumenta 1 a *ptr;


printf("%d\n", ++*ptr); // aumenta 1 a *ptr; printf("%d\n", *ptr) ;
printf("%d\n",++(*ptr)); // aumenta 1 a *ptr; printf("%d\n", *ptr) ;
printf("%d\n", ++*(ptr+1)); // aumenta 1 a *(ptr+1); printf("%d\n", *(ptr+1));

PÁGINA: 4
Lenguaje C
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

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:

int arr[3] = {10, 20 ,30}, *ptr = arr;


memoria ptr arr
0x7ff...0 10 20 30
Direcciones 0x7fa...b 0x7ff...0 0x7ff...4
Indices: 0 1 2

arr == &arr[0] // arr apunta a su primer elemento.


*arr == arr[0] // 10

En modo general un arreglo de una dimensión y un apuntador se comportan casi equivalentemente:


Arreglo Puntero Valor
Notación arreglo Notación de Notación arreglo Notación de
punteros punteros
arr arr ptr ptr 0x7ff...0
arr[0] *arr // *(arr+0) ptr[0] *ptr // *(ptr+0) 10
arr[1] *(arr+1) ptr[1] *(ptr+1) 20
arr[2] *(arr+2) ptr[2] *(ptr+2) 30
arr[1] = 80; *(arr+1) = 80; ptr[1] = 80; *(ptr+1) = 80; 80
n = fun(arr, 3); n = fun(arr, 3); n = fun(ptr, 3); n = fun(ptr, 3); Llama a la función fun()
int fun(int arr[ ], int n){ int fun(int *arr, int n) { int fun(int ptr[ ], int n){ int fun(int *ptr, int n) { Define a la función fun()
arr[0] = 2; *arr = 2; ptr[0] = 2; *ptr = 2;
arr[1] = 3; *(arr+1) = 3; ptr[1] = 3; *(ptr+1) = 3;
*(arr+2) = 4; arr[2] = 4; *(ptr+2) = 4; ptr[2] = 4;
return arr[2]; return *(arr+2); return ptr[2]; return *(ptr+2);
} } } }

Mientras ptr apunte a un arreglo de 1 dimensión arr, por ejemplo:


int arr[3] = {10, 20, 30}, *ptr = arr;
Son equivalentes (sinónimos) por completo:
arr[i] = = ptr[i] = = *(arr+i) = = *(ptr+i)

Pero hay tres diferencias:


1) ptr es un puntero y puede apuntar a a cualquier variable; arr es como un puntero constante y solo se apunta a sí mismo:
ptr = arreglo1; ptr = &var1 // es válido, ya que ptr es una variable
arr = algunaCosa; // no compila, ya que arr es constante y no puede cambiar de valor

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

arr tiene una dirección y se apunta a sí misma;


printf("%p\n", &arr); // dirección de ptr: 0x7ff...0
printf("%p\n", arr); // apunta a 0x7ff...0

PÁGINA: 5
Lenguaje C
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);
}
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.
}

Ejemplos:
Arreglo de una dimensión: Crear un arreglo, reportarlo, sumarle 2 a cada elemento y reportarlo
// 06_01a.c : Notación de arreglos // 0601b.c : Notación de apuntadores
#include<stdio.h>
void reporte(int arr[], int n);
void suma2(int arr[], int n);
void main(void){
int n=10, arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("Datos iniciales\n");
reporte(arr, n);
suma2(arr, n);
printf("Datos aumentados\n");
reporte(arr, n);
}
void reporte(int arr[ ], int n){ // Equivalente: void reporte(int *arr, int n){ arr[ ] es un puntero
// Los desplazamientos son lógicos // Los desplazamientos son físicos
int i; int *p, *pmax = arr+n; // el puntero p es una variable de
//recorrido del arreglo de inicio a fin
for(i=0; i<n; i++) printf("%d ", arr[i]); for(p=arr; p<pmax; p++) printf("%d ", *p);
printf("\n");
}
void suma2(int arr[ ], int n){ // Equivalente: void reporte(int *arr, int n){ arr[ ] es un puntero
// Los desplazamientos son lógicos // Los desplazamientos son físicos
int i; int *p, *pmax = arr+n; // p es variable de recorrido
for(i=0; i<n; i++) arr[i] +=2; for(p=arr; p<pmax; p++) *p +=2;
}
Salida:
Datos iniciales
1 2 3 4 5 6 7 8 9 0
Datos aumentados
3 4 5 6 7 8 9 10 11 2

ATENCION, ATENCION, Gran cambio de modo de pensar: Hasta este momento, el uso de apuntadores es artificioso, problemático,
antipático; pero anímate, hay buenas noticias:
• Adaptación de la lógica al hardware: Manejo directo de la RAM, que es como una cinta larguísima que contiene 0s y 1s.
• Rapidez: Es más rápido trabajar sobre el hardware
• Potencia: Puedes definir datos complejos y manejarlos directamente con tus propias funciones, ejemplo: Puedes manejar
paramétricamente arreglos de más de una dimensión, cambiar el tamaño de los mismos; puedes tener arreglos grandes de
estructuras y visualizarlos ordenados por diferentes campos sin mover un dato de la estructuras: un gran arreglo de datos de los
estudiantes (código, nombre, notas, etc.) puede ser visto ordenado por código, nombre, etc. al mismo tiempo.
• Flexibilidad: Apunta a todo tipo de variable
Por lo anterior, de acá en adelante, preferiremos usar apuntadores para manejar arreglos de todo tipo de datos; considerando la
limitación: Un apuntador es apenas, una variable que apuntará al inicio del arreglo; el programador es responsable del control de las
dimensiones.

PÁGINA: 6
Lenguaje C

// 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

Arreglo de dos dimensiones


Podemos visualizar con facilidad una matriz, basta representarla en un papel (dos dimensiones):
2 4 6 8
10 12 14 16
La memoria del computador es de una sola dimensión, no queda otra alternativa que adaptar las dos dimensiones a una:
int arr[2][4] = {2, 4, 6, 8, 10, 12, 14, 16};

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.

Los arreglos de dos dimensiones nos ayudan pero presentan limitaciones:


1. El lenguaje C los concibe como un arreglo de arreglos

arr[0]: fila 0 // arr[0][0] → 2 arr[0][1] → 4 arr[0][2] → 6 arr[0][3] → 8


arr[1]: fila 1 // arr[1][0] → 10 arr[1][1] → 12 arr[1][2] → 14 arr[1][3] → 16
2. Esta solución no es unidimensional y presentará inconvenientes más adelante.
Se debe estimar el número de filas y columnas por exceso, lo cual es un gran desperdicio de memoria; por otra parte si nos
excedemos, no hay advertencia de ello, y nos esperan errores y/o el aborto del programa.
3. Al pasar arreglos a funciones se pierde parametrización a partir de la segunda dimensión.
4. Un arreglo de más de una dimensión ya no se comporta como un apuntador, como lo hace el de una sola dimensión; pero un
apuntador si puede apuntar a un arreglo de dos dimensiones (con warning, pero lo hace).

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:

Apuntador a arreglos de dos dimensiones


int arr[2][3] = {2, 4, 6, 8, 10, 12}, *ptr = arr; // no se requiere &. ptr y arr apuntan a la misma dirección.

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]
PÁGINA: 7
Lenguaje C

En general para: int arr[m][n];


arr[i][j] ocupa la posición lineal: i*n+j.
arr[i][j] es equivalente a *(ptr+i*n+j) y a p[ptr+i*n+j]

La notación de punteros es engorrosa para referirse a los elementos de un arreglo; pero su uso permite parametrizar todas las
dimensiones.

Paso de argumentos a función


Paso de una variable
Ejemplo: Calcular la mitad de un número:
int n = 2, *pn= &n;
pn n
0x7ff...2 2
0x7ff...0 0x7ff...2

// 06_03a.c: Paso de argumento


Por valor Por referencia Puntero a variable
#include <stdio.h> #include <stdio.h> #include <stdio.h>
void mitad(int n); // prototipo void mitad(int *n); // prototipo void mitad(int *pn); // prototipo
void main(void){ void main(void){ void main(void){
int n = 2; int n = 2; int n = 2, *pn = &n;
mitad(n); // llamando mitad(&n); // llamando mitad(pn); // llamando
printf("n = %d\n", n); printf("n = %d\n", n); printf("n = %d\n", n);
} } }
void mitad(int n){ // función void mitad(int *n){ // función void mitad(int *pn){ // función
// entra el valor 2 // entra el valor 0x7ff...2 // entra el valor 0x7ff...2
n /=2; *n /=2; *pn /=2;
} } }
Salida: n = 2 Salida: n = 1; Salida: n = 1;

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

// 06_03b.c: Calcular la mitad de los elementos de un arreglo


Usando punteros Sin usar punteros
1 dimensión 2 dimensiones
#include <stdio.h> #include <stdio.h> #include <stdio.h>
void mitad(int *parr, int n); void mitad(int *parr, int m, int n); void mitad(int parr[][2], int m, int n);
void main(void){ void main(void){ void main(void){
int arr[4] = {2, 4, 6, 8}, *parr = arr, i; int arr[2][2] = {2,4,6,8}, *parr = &arr[0][0], i, j; int arr[2][2] = {2, 4, 6, 8}, i, j;
mitad(parr, 4); mitad(parr, 2, 2); mitad(arr, 2, 2);
for(i=0; i < 4; i++) for(i=0; i < 2; i++) for(i=0; i < 2; i++)
printf("%d\t", arr[i]); for(j=0; j < 2; j++) printf("%d\t", arr[i][j]); for(j=0; j < 2; j++) printf("%d\t", arr[i][j]);
printf("\n"); printf("\n"); printf("\n");
} } }
void mitad(int *parr, int n){ void mitad(int *parr, int m, int n){ void mitad(int arr[][2], int m, int n){
// entran los valores 0x7ff...2 y 4 // entran los valores 0x7ff...2, 2 y 2 // entran los valores 0x7ff...2, 2 y 2
int i; int i, j; int i, j;
for(i=0; i < m; i++) for(i=0; i < m; i++)
for(i=0; i < n; i++) *(parr+i) /=2; for(j=0; j < n; j++) *(parr+i*n+j) /=2; for(j=0; j < n; j++) arr[i][j] /=2;
//funciona for(j=0; j < n; j++) parr[i*n+j] /=2;
} } }
Salida: 1 2 3 4 Salida: 1 2 3 4 Salida: 1 2 3 4
Nota: 1 Nota: 2 Nota: 3
PÁGINA: 8
Lenguaje C
Notas:
1: El programa es paramétrico en una dimensión.
2: El programa es paramétrico en las dos dimensiones, note que *parr = &arr[0][0], también se puede hacer *parr = arr; pero compila
con warning (advertencia), debido a que un apuntador de tipo int (lineal) apunta a una matriz (arreglo de arreglos) de tipo int; pero
ejecuta bien. Otra forma es usar solo punteros y memoria dinámica y lo estudiaremos dos capítulos más adelante.
3: El programa es paramétrico solo en la primera dimensión, más no en la segunda.

Ejercicio: Definir un arreglo de dos dimensiones, calcular el mínimo, la suma y la media

Veamos dos temas que usaremos más adelante.

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

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..

printf("%d\n", a); // resultado: 3


printf("%d\n", *p1); // resultado: 3
printf("%d\n", **p2); // resultado: 3
a = 4; // es equivalente a: *p1 = 4; **p2 = 4;

Atento: el operador * tiene diferentes usos:


Uso
* Para indicar puntero: *p1
** Para indicar puntero a puntero: int **p2
* Operador de multiplicación
Expresión 2 ***p2; // es válida y el compilador la reconoce, pero ud. se puede confundir, no abuse, use paréntesis para separar.

Ejemplo: Definir, asignar valores y representar la memoria física:


int arr[4] = {15, 13, 11, 9}, *p1 = arr, **p2 = &p1;

La implantación en memoria sería:


p2 p1 arr
0x7234.. 0x8174.. 15 13 11 9
0x65734.. 0x7234.. 0x8174..

printf("%d\n", *arr); // resultado: 15


printf("%d\n", *p1); // resultado: 15
printf("%d\n", **p2); // resultado: 15

Reglas de precedencia de los modificadores *, ( ) y [ ]


Ver apéndice 1.
PÁGINA: 9
Lenguaje C
Un nombre (identificador), ejemplo nn, se puede modificar de varios modos:
*nn Indica el apuntador nn
**nn Indica el apuntador a apuntador nn
nn() Indica la función nn
nn[ ] indica el arreglo[ ]

Se puede utilizar más de un modificador al mismo tiempo, por ejemplo en la declaración:


int *nn[2][3];

Los modificadores tienen sus reglas de precedencia:


1) Cercanía del modificador (derecha o izquierda) al identificador
2) ( ) y [ ] tienen mayor prioridad que *
3) Use paréntesis para dar mayor prioridad, como en: (3 + 4) * 2

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

Ejemplos
// 06_04.c: 4 formas, entre otras, para acceder a los datos de una matriz utilizando punteros.
// Aplicación de las precedencias de *, () y [] para punteros
#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 punteros // puntero a arreglo de 2 elementos // puntero a entero // 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;
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]; p3 = a[i];
p4 = &p3; // Apunta a
//arreglo de 2 elementos
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

Estas reglas se aplican también para funciones:


int *miFun(); // miFun() retorna un puntero a int
int miFun(int *ptr); // El parámetro de miFun() es de tipo puntero a int

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:

pvoid = &in; int *pin = (int *)pvoid; printf("%d\n", *pin); // = 1


pvoid = &db; double *pdb = (double *)pvoid; printf("%lf\n", *pdb); // = 2.4

pin pdb pvoid in db


1 2.4

PÁGINA: 10
Lenguaje C
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.

Ventajas, desventajas de los apuntadores


Ventajas 1) Permiten adaptarse a la memoria lineal del computador y manejarla eficientemente (en tiempo de ejecución y no
de programación) en cada caso específico; el programador desarrolla los algoritmos para: alojar, liberar, acceder,
pasar, mover, ordenar, proteger/desproteger, etc. datos, en especial datos complejos y grandes: arreglos,
estructuras, pilas, árboles, etc.
2) Para manipular bloques de datos solo se requiere suministrar: el inicio del bloque (dado por el puntero), el
tamaño total del bloque en posiciones/bytes y el algoritmo para hacer la tarea necesaria.
3) Ahorrar memoria para grandes cantidades de datos: se especifica la cantidad exacta (también se puede
modificar) en tiempo de ejecución.
4) Ubicar la memoria en un área (de uso múltiple) diferente del segmento de RAM asignado al programa (de uso
específico) en tiempo de ejecución.
5) Ahorrar tiempo de proceso, por ejemplo para ordenar una matriz (multidimensional) grande, en lugar de mover los
bloques de datos (filas, planos, cubos, etc.) se definen arreglos de punteros y solo se mueven punteros de 8
bytes de longitud.
6) Al pasar valores por referencia en funciones, permite modificar el valor de la variable, lo cual no se puede hacer si
se pasa el valor.
Desventajas 1) La notación de punteros es un poco más compleja y no se debe usar para manipulación de datos simples.
2) Puede ser un poco más lento ejecutar con arreglos simples. Ya que esta parte es optimizada por C, mientras que
los apuntadores son manejados por el programador.

¡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
los detalles, y 3) Reúnete con los amigos para tirar flechas, perdón, para resolver problemas de apuntadores. Ya casi eres un
profesional.

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

3) Visite la página: http://www.cimat.mx/~alram/cpa/pointersC.pdf

PÁGINA: 11

Vous aimerez peut-être aussi