You are on page 1of 19

Lenguaje C++

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

Luego estudiaremos variables más complejas y útiles:


Variables Problemas a resolver
Cadena de caracteres, Gestión de estructuras de variables complejas.
estructuras de datos, archivos, Gestión exacta del tamaño de las variables
etc. Alojamiento de variables de tipo complejo en la RAM (lineal)

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

Para operar direcciones de memoria utilizaremos dos operadores:


& Operador de dirección (referencia) : p = &m = dirección de m = 0x7ff...0
* Operador de indirección (dereferencia): *p = *0x7ff...0 = *&m = m = 66

Operaciones con un apuntador:


printf("%p", p); // 0x7ff...0 Posición de m, usa formato "p"
printf("%d", *p); // 66 Valor de m
p = &n; // ahora p apunta a n
printf("%p", p); // 0x7ff...4 Posición de n, usa formato "p"
printf("%d", *p); // 65 Valor de n

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.

07.02 Definición de Apuntador


Sea una variable:
int m = 66; // En tiempo de ejecución m es alojada en una posición hexadecimal, ejemplo: 0x7ff...0.
RAM m
66
0x7ff...0
Aplicando el operador de referencia obtenemos la dirección de m:
&m = 0x7ff...0
Supongamos una variable p, de tipo hexadecimal, podemos asignar:
p = &m; // = 0x7ff...0
Aplicando el operador de desreferencia, se cumple:
PÁGINA: 1
Lenguaje C++
*p // = *0x7ff...0 = 66
¿Cómo se define a p?, podría ser:
hexa p; // puede ser; pero NO hay tipo hexadecimal en C++
Mas bien aplicaremos un artificio muy elegante, definiendo no al apuntador p sino a *p (valor apuntado por p ≡ indirección de p):
int m = 66, int *p; // la indirección de p es de tipo entero
p = &m; // asigna la dirección de m a p.
Escrito en una sola línea:
int m = 66, int *p = &m; // *p es de tipo int, y p = &m (toma como valor la dirección de m)
Sobre la RAM:
m p
66 0x7ff...0
0x7ff...0 0x7ff...c

p es una variable con información de información: eso es meta-información.


Un apuntador p es una variable, como cualquier otra; ocupa 8 bytes de memoria; tiene un valor hexadecimal, el cual es la dirección de
otra variable (m en este caso). p puede ser interpretado como un variable espía de m, con las siguientes capacidades:
Sabe la dirección de m: p → 0x7ff...0
Sabe el valor de m : *p → 66
Puede cambiar su valor: *p = 65; // asigna 65 a m.
Esta capacidad es de gran potencia y flexibilidad; pero disminuye la seguridad de m.

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

Atento a los detalles


1) Asignación de valor a p:
int m=66, n=65, *p = &m, *q = &m; // p y q apuntan al primer byte de m
m n p q
66 65 0x7ff...0 0x7ff...0
0x7ff...0 0x7ff...4 0x7ff...c

p = &n; // p apunta a n (otra variable)


q = p; // q también apunta a n
p = NULL; // p no apunta a nada, q sigue apuntando a n
p = 0x7ff...C; // No compila: no podemos manejar directamente a la RAM; esta es tarea del sistema operativo.
*p = 0x7ff...c; // No compila: *p es de tipo entero, no hexadecimal
m = 0x6ff...c; // No compila: m es de tipo entero, no hexadecimal

2) Formato de impresión y lectura:


El formato de impresión y scan es "%p":
printf("%p", p); // imprime el valor de p ≡ 0x7ff...0
scanf("%p", p); // compila y ejecuta pero no tiene sentido en tiempo de ejecución.

printf("%d", *p); // imprime el valor de m ≡ 66


scanf("%d", p); // Equivalente: scanf("%d", &m); lee del teclado un nuevo valor para m, recuerde que p = &m

3) Un apuntador tiene tres atributos:


 tipo de dato al que apuntará : int, char, etc. (un solo tipo).
 tipo : apuntador, ocupa 8 bytes, con formato de lectura/impresión: %p;
 valor : dirección de la variable apuntada (en este caso) = 0x7ff...c
tipo dato, tipo, valor

int m = 66, *p = &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

5) Mientras p apunte a m: int m = 66, *p = &m, n;


Se cumplen las siguientes relaciones:
Tipo de variable Propiedad Ejemplo
Apuntador &y* son opuestos *&p = p = 0x7fb...0
&*p = p = 0x7ff...0
Distinto de & y * no son opuestos *&m = m = 66
apuntador &*m No compila: no se define *m porque m es int.

Variables Relación Ejemplo


&m y p Equivalentes para scanf("%d", &m);
leer valor scanf("%d", p);
No equivalentes p = &m; // apunta a otra variable
para asignar valor &n = &m; // error: no se asigna posición de memoria a una variable que no es puntero
m y *p Equivalentes para m = 66; *p = 66;
asignar y escribir m += 2; *p += 2;
valores printf("%d", m); printf("%d", *p); // son equivalentes

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


* : Indirección (operador unario)
* : Multiplicación (operador binario)
El operador de indirección * tiene mayor prioridad que el operador de multiplicación *, ejemplo:
int m=2, *p= &m, b;
b = *p * 2;
Es equivalente a:
b = *p*2; // el compilador comprime los espacios antes de compilar
b = 2 * *p;
b = 2**p;
b = 2 * * p; // no importa la cantidad de espacios, solo sirven para aclarar al programador.
No abuse de la escritura confusa, use espacios en blanco y paréntesis para aclararla. Más adelante estudiaremos las reglas de priori-
zación.

Primera aplicación de apuntadores: Paso de argumentos por referencia a una función


Una función retorna 0 o 1 solo valor: es el caso de uso más requerido; pero si necesitáramos retornar más de un valor, podemos
hacerlo en modo indirecto utilizando apuntadores, veamos un ejemplo:
int valorMN(int *m, int *n);
main(){
int m =1, n, k;
k = valorMN(&m, &n); // pasamos la dirección (referencia) de: m = 0x7ff...0 y n = 0x7ff...8
// m ≡ 2, n ≡ 3
}
int valorMN(int *m, int *n) { // recibe 0x7ff...0 y 0x7ff...8 y los asigna a los apuntadores m y n
*m = 2; // asigna 2 a m en la main()
*m = 3; // asigna 3 a n en la main()
return 4; // retorna 4 que será asignado a k
}

valorMN() Retornó 4, y asignó m ≡ 2, n ≡ 3, lo cual es equivalente a que retorne 3 valores.


Por este motivo la función scan() requiere los &:
scanf("%d %d %d ...", &var1, &var2, &var3,…); // asigna valores a var1, var2, var3, …

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;

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


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

Peligro: Detección de un error de lógica SEVERO, ejemplo:


01 #include<cstdio>
02 main(){
03 int *pq, m=2, *pm = &m;
04 printf("Ingrese un número: ");
05 scanf("%d", pq); // no hay error de sintaxis; pero en ejecución sucede un evento no controlado
06 printf("%d\n", *pq);
07 }
El comentario de la línea 05 es correcto: ya que en compilación no se controla los valores asignados a las variables; pero en
ejecución se usan esos valores y no se sabe que pasará: En la línea 05, el apuntador pq no apunta a una posición de memoria
definida (controlada explícitamente), apunta a la posición que encontró en tiempo de ejecución; esto corrompe la memoria y el
resultado es incierto: correcto, incorrecto (y no lo sabemos) o "violación de segmento". EL DESARROLLADOR ES RESPONSA-
BLE de:
• Asignar valores a todas las variables: sinples (int, float, chr, etc), arreglos y apuntadores antes de usarlos
• No exceder los límites de las dimensiones de los arreglos
• Controlar que los apuntadores apunten a direcciones válidas.
El procedimiento para detectar y disminuir este tipo de error es el mismo que el recomendado en el capítulo de Programación
secuencial.
Si cambia las líneas 05 y 06 a:
scanf("%d", pm);
printf("%d\n", *pm);
No habrá error.

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.

memoria parr arr pa a


0x7ff...0 10 20 30 0x80a...0 65
Direcciones 0x7fa...b 0x7ff...0 0x7ff...4 0x801...0 0x801..a
Indices: 0 1 2

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.

07.04 Aritmética de apuntadores


Sea la definición:
int a=7, arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, *parr = arr;
// parr apunta a arr[0]: parr = & arr[0], sumpongamos a la posición: 0x7ff...0
podemos sumar 2 posiciones enteras (8 bytes) a parr:
parr = parr + 2; // equivalente: parr = &arr[2]; apunta a la posición 0x7ff...8
en general podemos sumar y restar posiciones enteras a parr:
parr = arr; // == 0x7ff...0. Apunta al primer byte de arr[0]
parr++; // parr = parr + 1; apunta a la siguiente posición de tipo int: avanza 4 bytes: 0x7ff...4
// si parr apuntara a un tipo long, sumará 8 bytes, para char sumará 1 byte; etc.
parr += n; // parr = parr + n; parr apunta n posiciones más adelante
parr--; // parr apunta a la posición anterior
parr -= n; // parr apunta a la n posición previa
parr += 2*3+1; // parr = parr + 7; apunta a la posición 7 más adelante: int (= 7*4 = 28 bytes) más adelante.

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.

Se permite la resta de dos punteros:


int arr[10], *parr1, *parr2, n;
parr1 = arr; // apunta al primer byte de arr[0], equivalente a: parr1 = &arr[0]
parr2 = &arr[2]; // apunta a arr[2].
n = parr2 - parr1; // = 2: entre arr[2] y arr[0] hay dos elementos.

No se permiten otro tipo de operaciones


PÁGINA: 5
Lenguaje C++
parr = parr * 2; // Error: No se admiten multiplicaciones de valores de memoria
parr1 = parr1 + parr2; // Error

Valores apuntados por parr


int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, *parr = arr;
*parr; // ≡ 1 ≡ parr[0]; parr apunta a arr[0]
*(parr+1); // ≡ 2 ≡ parr[1]; parr sigue apuntando a arr[0]
*(parr+4); // ≡ 5 ≡ parr[4]; parr sigue apuntando a arr[0]
En general
*(parr+n) ≡ parr[n] ≡ arr[n]
parr+n ≡ &parr[n] ≡ & arr[n]

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));

07.05 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}, *parr = arr; // equivalente: *parr = &arr[0]


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

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 &

Pero hay diferencias:


El arreglo tiene elementos, el apuntador no.
El arreglo es apuntador constante que se auto-apunta; por lo tanto
parr = &a; // es válido en tiempo de compilación y de ejecución
parr = arr1; // es válido en tiempo de compilación y de ejecución
arr = arr1; // error de compilación; arr es como un apuntador constante: no se puede re-asignar.

Paso de argumento apuntador a una función


Si se pasa un arreglo o su apuntador, la variable correspondiente -en la función llamada- es un apuntador.
01 #include<cstdio>
02 void miFun(int n, int *parr ){ // parr es apuntador
03 //void miFun(int n, int parr[ ]){ // equivalente a la línea 02, parr es apuntador
04
05 // en ambos casos parr es un apuntador, ya no es arreglo
06
07 }
08 main(){
09 int n = 3, arr[3] = {10, 20 ,30}, *parr = arr;
10 miFun(n, arr); // paso del primer byte de arr
11 miFun(n, parr); // paso del primer byte de arr
12 }

Retornar un apuntador de una función


Analicemos el programa:
01 #include<cstdio>
02 int *miArreglo(int n){ // retorna un apuntador
03 int a[n], *pa = a;
04 a[0] = 1; a[1] = 2; a[2] = 3;
05 return pa; // retorna la dirección de p
06 }
07 main(){
08 int *p = miArreglo(3); // recibe valores en miArreglo()
09 printf("%d %d %d\n", p[0], p[1], p[2]);
10 }
Salida esperada: 1 2 3 // pero no siempre es así.
Según lo aprendido: al ejecutarse la línea 05, se retorna el valor de pa = dirección de a, luego se termina de ejecutar miArreglo(): se
eliminan todas las variables definidas en la línea 03. Finalmente, en la línea 09: la dirección asignada al puntero p ya no está disponible.
Por eso debe evitarse retornar un apuntador, excepto cuando apunta a una memoria dinámica, como veremos más adelante.

PÁGINA: 7
Lenguaje C++
Cuarta aplicación de apuntadores: Como variables de iteración de un arreglo

Un apuntador es un variable: tiene un valor = la dirección de otra variable:


int m, *p = &m; // se dice que p apunta a m
Puede acceder y operar con los valores de m, *p es equivalente a m: *p = 5; es equivalente a m = 5;
También puede apuntar a un arreglo:
int i, arr[] = {10, 20, 30}, *p = arr // se dice que p apunta a arr

Un apuntador p se comporta en modo dual, dependiendo del contexto:


1) Como variable simple, ejemplo:
*p = 5;
2) Como si fuera un arreglo de una dimensión, a partir de la posición apuntada, ejemplo:
for(i=0; i<3; i++) printf(“%d ”, p[i]); // salida: 10 20 30
Inclusive puede tomar índices negativos.
No importa si la dirección apuntada es, o no, un arreglo; queda bajo la responsabilidad del desarrollador, la validez lógica de la
dirección apuntada.

Un apuntador puede combinar ambos comportamientos. Supongamos:


int i=0, arr[3] = {10, 20 ,30}, *parr = arr, *pmax = parr+3;
i parr arr pmax RAM
0 0x7ff...0 10 20 30 0x7ff..12
Direcciones físicas: 0x7ff...0 0x7ff...4 0x7ff...8 0x7ff...12
Indices lógicos: i 0 1 2

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

Ejemplo: recorrer un arreglo de una dimensión de subida y de bajada en modo simultaneo:


// 07_01a.c Notación de arreglos // 07_01b.c Notación de apuntadores
#include<cstdio>
main(){
int n = 3, arr[3] = {1, 2, 3};
int i, j; // Recorre arreglo con variables i y j int *p = arr, *q = p + n -1, *pmax = p+n;
i = 0; // para iniciar el arreglo // q apunta a la posición final: arr[2]
// pmax apunta después de la posición final de arr
j = n -1; // fin de arrglo for(; p < pmax; p++, q--)
for(; i < n; i++, j--) printf("Subiendo: %d Bajando: %d\n", *p, *q);
printf("Subiendo: %d Bajando: %d\n", arr[i], arr[j]); }
}
Salida:
Subiendo: 1 Bajando: 3
Subiendo: 2 Bajando: 2
Subiendo: 3 Bajando: 1
PÁGINA: 8
Lenguaje C++
Atento: El arreglo arr tiene una posición de inicio y un número de elementos
Las variables i y j permiten el recorrido de arr Los apuntadores *p y *q recorren físicamente a arr

Otro ejemplo similar utilizando funciones


// Arreglo de una dimensión: Crear un arreglo, reportarlo, sumarle 2 a cada elemento y reportarlo
// 07_01c.c : Notación de arreglos // 07_01d.c : Notación de apuntadores
#include<cstdio>
void reporte(int n, int arr[]); // void reporte(int n, int *p)
void suma2(int n, int arr[]); // void suma2(int n, int *p)
main(){
int n=3, arr[3] = {0, 1, 2};
printf("Datos iniciales: ");
reporte(n, arr);
suma2(n, arr);
printf("Datos + 2 : ");
reporte(n, arr);
}
void reporte(int n, int arr[ ]){ void reporte(int n, int *p){
int i=0; // Los desplazamientos son lógicos int *pmax = p+n; // Los desplazamientos son físicos
while(i<n) printf("%d ", arr[i++]); while(p<pmax) printf("%d ", *p++);
printf("\n");
}
void suma2(int n, int arr[ ]){ void suma2(int n, int *p){
int i=0; int *pmax = p+n; // define y asigna *pmax = p+n
while(i<n) arr[i++] +=2; // I: variable de recorrido while(p<pmax) *p++ +=2; // p: variable de recorrido
}
Salida:
Datos iniciales: 0 1 2
Datos + 2 :234

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

/* 07_02.c : Dados dos punteros float *f1, *f2; si asignamos f2 = f1 + n,


verificar que respecto a f1, la dirección de f2 aumenta: n*sizeof(float) bytes
*/
#include<cstdio>
main(){
float f, *f1 = &f, *f2;
int n = 2;
f2 = f1 + n;
printf("n = f2 - f1 : %lu posiciones\n", f2 -f1);
printf("sizeof(float) : %lu bytes\n", sizeof(float));
printf("Primera dirección apuntada: %p (f1)\n", f1);
printf("Segunda dirección apuntada: %p (f2 = f1 + n; n = 2)\n", f2);
printf(" --------------\n");
printf("Aumento de dirección: f2-f1 = %lu bytes\n", (long int)f2 - (long int)f1);
}
Salida:
n = f2 – f1 : 2 posiciones
sizeof(float) : 4 bytes
Primera dirección apuntada : 0x7fff329c5ee0 (f1)
Segunda dirección apuntada: 0x7fff329c5ee8 (f2 = f1 + 2)
–-------------------
Aumento de dirección: f2-f1 = 8 bytes

07.06 Arreglo de dos dimensiones y apuntadores


Podemos visualizar con facilidad una matriz en un papel (dos dimensiones):
2 4 6 8
10 12 14 16
La RAM es de una sola dimensión y aloja los datos fila por fila:
int arr[2][4] = {2, 4, 6, 8, 10, 12, 14, 16}, *parr = &arr[0][0]; // Equivalente: *parr = arr[0], arr[0] es un arreglo
Matriz Línea
j→ arr parr RAM
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: la posición [i][j] en la matriz ocupa la posición lineal (real) i*4+j:
arr[i][j] ≡ parr[i*4+j] ≡ *(parr+i*4+j)

Quinta aplicación de apuntadores: Apuntador a un arreglo de dos dimensiones


Para:
int arr[m][n], *parr = arr[0];
el elemento arr[i][j] ocupa la posición líneal: i*n+j en la RAM; de este modo:
arr[i][j] ≡ parr[i*n+j] ≡ *(parr+i*n+j)

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:

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) La solución matricial presenta inconvenientes:
En tiempo de programación se debe estimar el número de filas y columnas: si las estimaciones son excedentes, se desperdicia
memoria; si son pequeñas, se deben aumentar y recompilar; y si excedemos los límites de los arreglos, no se reportan errores
de compilación, pero al ejecutar los resultados pueden ser errados, este es un error SEVERO.
3) Un arreglo de más de una dimensión ya no se comporta como un apuntador; pero un apuntador si puede apuntar a un arreglo de
dos o más dimensiones "linealizado".

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

07.07 Paso de argumento apuntador a una función


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

// 07_03a.c: Paso de argumento


Por valor Por referencia (ver primera Puntero a variable
aplicación de apuntadores)
#include <cstdio> #include <cstdio> #include <cstdio>
void mitad(int n); // prototipo void mitad(int *n); // prototipo void mitad(int *pn); // prototipo
main(){ main(){ main(){
int n = 2; int n = 2; int n = 2, *pn = &n;
mitad(n); // pasa 2 mitad(&n); // pasa 0x7ff...2 mitad(pn); // pasa 0x7ff...2
printf("n = %d\n", n); printf("n = %d\n", n); printf("n = %d\n", n);
} } }

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

Paso de puntero a arreglo


Ejemplo: Calcular la mitad de los elementos de un arreglo:
int arr[4] = {2, 4, 6, 8}, *parr= &arr[0][0], *pmax = p+4; // Se define y asigna *pmax = p+4 = posición siguiente al fin del arreglo

pmax parr arr


0x7ff...10 0x7ff...00 2 4 6 8
0x7fe...0 0x7ff..00 0x7ff..10 // números en exadecimal

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


Usando punteros Sin usar punteros
1 dimensión 2 dimensiones
#include <cstdio> #include <cstdio> #include <cstdio>
void mitad(int n, int *parr); void mitad(int m, int n, int *parr); void mitad(int m, int n, int parr[][2]);
main(){ main(){ main(){
int arr[4] = {2, 4, 6, 8}, i; int arr[2][2] = {2,4,6,8}, *parr = arr[0], i, j; int arr[2][2] = {2, 4, 6, 8}, i, j;
// int *parr = &arr[0][0]; Warning: *parr = arr
mitad(4, arr); //pasan 4 y 0x7ff...2 mitad(2, 2, parr); // pasan: 2,2 y 0x7ff...2 mitad(2, 2, arr); // pasan: 2,2 y 0x7ff...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", *parr++); for(j=0; j < 2; j++) printf("%d\t", arr[i][j]);
printf("\n"); printf("\n"); printf("\n");
} } }
void mitad(int n, int *p){ void mitad(int m, int n, int *p){ void mitad(int m, int n, int arr[][2]){
// entran los valores 4 y 0x7ff...2 // entran los valores 2, 2 y 0x7ff...2 // entran los valores 2, 2, 0x7ff...2
int *pmax = p + n; int *pmax = p + m*n; int i, j;
for(i=0; i < m; i++)
while(p<pmax) *p++ /=2; while(p < pmax) *p++ /=2; for(j=0; j < n; j++) arr[i][j] /=2;
} } }
Atento a la restricción:
void mitad(int m, int n, int arr[][2])
Salida: 1 2 3 4

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

PÁGINA: 11
Lenguaje C++

07.08 Resumen del uso de arreglos y apuntadores:


Operación 1 dimensión 2 dimensiones
Definición int arr[n], *parr = arr, *pmax=parr+n; int arr[m][n], *parr = arr[0], *pmax=parr+m*n;
// Atento: int *parr = arr[0] es equivalente a: int &arr[0][0];
Recorrido Lógico: usa índices: es más fácil de entender; pero más lento al ejecutar porque cualquier referencia lógica a un arr[i][j]
del arreglo es un salto físico a la posición [i][j]j; lo cual toma tiempo.
arr[i] ≡ *(arr+i) ≡ parr[i] ≡ *(parr+i) arr[i][j] ≡ parr[i*n+j] ≡ *(parr+i*n+j)
Físico: se desplaza para adelante o atrás en la RAM: es más rápido al ejecutar,

parr++, ++parr, parr--. --parr parr++, ++parr, parr--. --parr


*parr++, *++parr, *parr--, *--parr *parr++, *++parr, *parr--, *--parr
Un arreglo de una o más dimensiones mantiene su dirección de inicio CONSTANTE -no la pierde nunca-.
Un apuntador tiene varios comportamientos:
1) Apunta a una dirección cualquiera, la posición apuntada puede ser un arreglo de una dimensión: p = arr, una matriz: p = mat[0], un
elemento de arreglo: p = &arr[k], elemento de matriz: p = &mat[i][j]; una variable: p = &var, la dirección de otro apuntador: p1 = p2,
etc.
2) A partir de una posición apuntada, el apuntador se comporta como un arreglo de una dimensión, inclusive los índices pueden ser
negativos, ejemplo: p[3], p[-2].
3) Puede desplazarse para adelante o atrás: p++, p--, p +=n, p-=n
Es responsabilidad del programador que el apuntador apunte a una dirección válida; ni el compilador ni en tiempo de ejecución se veri-
fica esta condición, por lo tanto el programador no sabe que su programa tiene un error de lógica SEVERO.

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

// 07_04.c: Transponer una matriz en otra


#include<cstdio>
void imp(int m, int n, int *pa);
void tran(int m, int n, int *pa, int *pb);
main(){
int a[2][3] = {1,2,3,4,5,6}, b[3][2], i, j, *pa= a[0], *pb = &b[0][0];
printf("Matriz inicial\n");
imp(2, 3, pa);
tran(2, 3, pa, pb);
printf("Matriz transpuesta\n");
imp(3, 2, pb);
}
void imp(int m, int n, int *p){
int i, j;
for(i=0; i<m; i++){
for(j=0; j<n; j++) printf("%d ", *p++); // desplazamiento lineal de puntero
printf("\n");
}
}
// Transpuesta
// b[i][j] = a[j][i]
// b: será recorrida linealmente (físico): *pb++
// a: se DEBE recorrer por índices (lógico): a[j][i] = *(pa+j*n+i)
void tran(int m, int n, int *pa, int *pb){
int i, j;
for(i=0; i<n; i++)
for(j=0; j<m; j++) *pb++ = *(pa+j*n+i); // notación física: *pb++, y, lógica: *(pa+j*n+i)
}
Salida:
Matriz inicial Matriz transpuesta
123 14
456 25
36

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

07.09 Arreglo de apuntadores


Un puntero es una variable como cualquier otra, por lo tanto se puede definir arreglos de punteros, ejemplo:
int *ptr[4], n = 6; // define un arreglo de 4 apuntadores
ptr[0] = ptr[1] = &n; // ambos apuntan a n
*ptr[1]; // = 6

ptr n
0xff..c 0xff..c 6
Posiciones 0 1 2 3 0xff..c

Sexta aplicación de apuntadores: Arreglo de apuntadores


Organizar un espacio de 12 posiciones en la RAM para ser visto como: arreglo de
1, 2 o 3 dimensiones*/
#include<cstdio>
main(){
int i, j, k, a[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12};
// 1 dimensión: 12 elementos
int *p1 = &a[0]; // 1 puntero
printf("1 dimensión de 12 elementos: ");
for(i=0; i<12; i++) printf("%d ", p1[i]); // vista de 1 dimensión

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

07.10 Apuntador a apuntador


Un puntero es una variable como cualquier otra, por lo tanto se puede definir un puntero a un 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 // operador unario: actúa con un solo operando, ejem: *p
** Para indicar puntero a puntero: int **p2 // operador unario
* Operador de multiplicación // operador binario: actúa con dos operandos, ejem: 2 * 4
Expresión 2***p2; // es válida
El compilador identifica los operadores unario o binario y opera correctamente: 2***p2 => 2 * **p2
El programador se puede confundir, no abuse, use espacios en blanco y/o paréntesis para separar.

Ejemplo: Apuntador de apuntador a arreglo:


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

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

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


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];

Reglas de precedencia de los modificadores


1) Cercanía del modificador (derecha o izquierda) al identificador
2) ( ) y [ ] tienen mayor prioridad que *
3) Use paréntesis para dar mayor prioridad, similares que en aritmética: (3 + 4) * 2
Ver apéndice 1 para todas las precedencias

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

Las reglas anteriores 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

// 07_07.c: Funciones que reciben arreglo de 2 dimensiones:


#include<cstdio>
void reportar1(int n, int m, int **parr) { // apuntador a apuntador
int i, j;
for(i=0; i<n; i++){
for(j=0; j<m; j++) printf("%d\t", parr[i][j]);
printf("\n");
}
}
void reportar2(int n, int m, int (*parr)[2]) { // apuntador que apunta a arreglo de 2 dimensiones
// === reportar1: solo cambia la definición
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];
reportar1(n, m, parr); // paso de arreglo de apuntadores
reportar2(n, m, arr); // paso de matriz
}

07.12 Apuntador de tipo void (genérico)


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 cualquier tipo de dato, ejemplo:
float fl=2.4, *pfl = &fl;
void *pvoid = &fl; // apunta a fl
printf("%.2f %.2f %.2f\n", fl, *pfl, *(float *)pvoid); // casting a (float *)
Salida: 2.40 2.40 2.40

Ejemplo:
01 int in = 1; float fl = 2.4;
02 // asignar a void asignar void imprimir
03 pvoid = &in; int *pin = (int *)pvoid; printf("%d\n", *pin); // salida: 1
04 pvoid = &fl; float *pfl = (float *)pvoid; printf("%lf\n", *pfl); // salida: 2.4

pin pfl pvoid in fl


1 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++

Ventajas y desventajas del uso de apuntadores


Ventajas Desventajas
Permite ahorrar memoria de otros objetos, ejemplos: se puede manejar con precisión el Ocupan memoria adicional para su propia
tamaño de los arreglos; una tabla no ordenada puede ser vista en dos modos: La tabla definición;
original, y la tabla ordenada utilizando apuntadores.
Suelen ser más rápidos en ejecución: tienen la información física, están sobre la realidad Requieren tiempo para procesarse así
en el acá y el ahora. mismos.
Permiten que las funciones pasen referencias a variables Disminuye la seguridad de las variables.
Puede apuntar a diferentes posiciones, una a la vez. Es un poco complejo
Permite ordenar arreglos lógicamente, sin mover sus elementos; así se tiene una vista La programación suele ser un poco más
lógica ordenada y otra física estable compleja

Resumen de las aplicaciones de apuntadores


Ver apéndice 2.

¡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

2) Dado un arreglo tridimensional, por ejemplo:


int a[2][3][2] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} , *p=&a[0][0][0];
Imprimirlo de dos modos: recorrerlo con apuntador secuencial (*p++) y con índices *(p+….)

3) ¿Cuáles son los valores de las variables en los comentarios: //?


//T05_03.c
#include<cstdio>
#define PI 3.1416
int m = 1;
float k = 3;
int miFun(int n, int *m);
main(){
int j, m = 2;
j = miFun(4, &m); // m=2, k=4.0
...
}
PÁGINA: 18
Lenguaje C++
int miFun(int n, int *m){
//*m++; // m apunta a la siguiente posición
k++;
printf("%d %d %f\n", n, *m, k);
return (2*k + *m + n); // n = 4, m = valor que encuentra en la posición, j = no declarada, k = 4.0
}

4) Para el arreglo tridimensional:


int a[2][3][2] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, *pa = &a[0][0][0];
Programe 2 funciones:
void imprimir(int *pa, int l, int m, int n); // Imprime el arreglo
void duplicar(int *pa, int l, int m, int n); // duplica el arreglo
Note que está trabajando con un arreglo tridimensional en modo paramétrico, lo cual no podía hacer con arreglos.
Desde la void main( ): imprima la matriz inicial, duplique la matriz y vuélvala a imprimir, la salida será:
Matriz inicial:
12 34 5 6 7 8 9 10 11 12

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

You might also like