Вы находитесь на странице: 1из 18

Lenguaje C

Apuntadores (pointers en inglés; también punteros en español)


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

Más adelante estudiaremos variables complejas:


Variables Problemas a resolver
Cadena de caracteres, Gestión exacta del tamaño de las cadenas
estructuras, archivos, pilas, Gestión de estructuras de datos complejos.
colas, 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; // se almacenan en binario: m = 1001001, n = 1000001, se interpretan en decimal: 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
1001001 1000001
Dirección física en base hexadecimal: 0x7ff...0 0x7ff...8 (direcciones físicas de m y n)
Estas direcciones cambian cada vez que se ejecuta el programa
Atento: Fisicamente 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 definir un nuevo tipo de variable que contenga información de la dirección física de m al momento de ejecución:
p m n
0x7ff...0 1001001 1000001
0x7ff...0 0x7ff...8
m y n son variables con información de datos: m = 66, n = 65;
p es una variable con información de la información, esto se llama metainformación.
p es una variable como cualquier otra, y se le aplican las mismas reglas.
p está especializada en manejar direcciones de variables (sus datos son las variables), lo cual le da poderes y limitaciones adicionales.
p puede ser pensada como una supervariable (jefa), como un hacker que conoce los datos de una variable y puede cambiar su valor, sin
que se entere.

Operaciones con un apuntador


printf(“%p”, p); // 0x7ff...0 Posición de m
printf(“%d”, *p); // 66 Valor de m
p = &n; // ahora p apunta a n
printf(“%p”, p); // 0x7ff...8 Posición de n
printf(“%d”, *p); // 65 Valor de n

Muy interesante; pero ¿para qué sirve la metainformación?


1) un apuntador tiene la capacidad de manejar variables en modo completo: valor, dirección, sus datos son direcciones de variables.
Apunta: p = &m // asigna a p la dirección de m: p = 0x7ff...0
sabe la posición de m // p contiene la dirección de m: 0x7ff...0
Operar como si fuera m: *p = 4 // asigna 4 a m
Apuntar: p = &n // asigna a p la dirección de n: p = 0x7ff...8 (recorre las variables)
2) Un apuntador conoce la información física de una variable y puede operar por ella, entonces es más potente:
• Integra el manejo físico y el lógico: lo cual es muy importante para superar la limitación física de que la RAM: es
unidimensional.
• Permite gestionar con precisión el número de elementos de los arreglos multidimensionales, con lo cual se evita el
desperdicio o la falta de elementos de las dimensiones, al declarar:
int arr[5]; // ¿será 5 el número preciso de elementos de arr?

PÁGINA: 1
Lenguaje C
• Actúa como índice (físico) de arreglos:
int arr[5] = {2, 4, 6, 8, 10}, n = 5, i, *p = arr, q = p+n;
p q arr
0x7ff...00 0x7ff...24 2 4 6 8 10
Direcciones: 0X7ff...00 0x7ff...24
Indices: 0 1 2 3 4

for(; i<n ; i++ ) pritnf(“%d ”, arr[i]); // índice lógico: imprime los elementos de arr.
for(; p<q; p++) pritnf(“%d ”, *p); // índice físico : la línea 3 hace lo mismo que la instrucción anterior.
Salida: 2 4 6 8 10
2 4 6 8 10
• Al conocer la información la física (ejemplo de una tabla) permite ordenarla lógicamente por filas sin moverla físicamente si
alterar la tabla:

Resumen de operadores
Operador Descripción
& Operador de dirección (referencia) : p = &m = dirección de m = 0x7ff...0
* Operador de indirección (dereferencia): *p = *0x7ff...0 = *&m = m = 66
Ejemplos:
printf(“%p", p); // note el formato %p, no lo confunda con la p de apuntador
Salida: 0x7ff...0
printf(“%d", *p); // note el formato %d, para enteros
Salida: 66

Definición de 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 en C
Mas bien aplicaremos un artificio muy elegante, definiendo no al apuntador pn sino a *pn (valor apuntado por pn):
int n = 65, int *pn;
pn = &n;
Escrito en una sola línea:
int n = 65, int *pn = &n; // *pn es de tipo int, y pn = &n (toma como valor la dirección de n)
Sobre la RAM:
RAM pn n
0x7ff...c 65
0x7ff...0 0x7ff...c
Un apuntador pn es una variable, como cualquier otra; ocupa 8 bytes de memoria; tiene un valor hexadecimal, el cual es la dirección de
otra variable. pn puede ser interpretado 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.

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 apuntador
int n;
int *pn = &n; // *pn es el valor (de tipo int) apuntado por pn

PÁGINA: 2
Lenguaje C
Se lee así:
El valor apuntado por pn es de tipo int
int *pn = &n;
valor de pn = dirección de n

Atento a los detalles


1) Al asignar valor a pn:
int n, m, *pn;
pn = &n; // pn apunta al primer byte de n
pn = &m; // p apunta a m (otra variable)
pn = NULL; // pn no apunta a nada

pn = 0x7ff...C; // No compila: no podemos manejar directamente a la RAM; esta es tarea del sistema operativo.
*pn = 0x7ff...c; // No compila: *pn es de tipo entero, no hexadecimal
n = 0x6ff...c; // No compila: n es de tipo entero, no hexadecimal

2) Sea:
int n = 65, *pn = &n;
Tipo de variable Propiedad Ejemplo
Apuntador &y* son opuestos *&pn = pn = 0x7ff...0
&*pn = pn = 0x7ff...0
Distinto de apuntador & y * no son opuestos *&n = n = 65
&*n No compila: no se define *n porque n es int.

3) Un apuntador tiene tres atributos:


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

int n = 65, *pn = &n;

4) Variables y valores del ejemplo:


n = 65 pn = 0x7ff...c
&n = 0x7ff...c &pn = 0x7ff...0
*n error no compila: n es de tipo int *pn = 65

5) Mientras pn apunte a n: int n = 65, *pn = &n, m;


Se cumplen las siguientes relaciones:
Variables Relación Ejemplo
&n y pn Equivalentes para scanf(“%d", &n);
leer valor scanf(“%d", pn);
No equivalentes pn = &m; // apunta a otra variable
para asignar valor &n = &m; // error: no se asigna posición de memoria a una variable
n y *pn Equivalentes para n = 65; *pn = 65;
asignar y escribir n += 2; *pn += 2;
valores printf(“%d", n); printf(“%d", *pn); // son equivalentes

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


miFun(&n); // el valor de n puede ser cambiado en miFun().
Llama a:
void miFun(int *n) {*n= 3;} // cambia el valor de n a 3..
Los apuntadores se usan mayormente para apuntar a bloques de variables; no para apuntar a variables simples.

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


* : Indirección (operador unario)
* : Multiplicación (operador binario)

PÁGINA: 3
Lenguaje C
El operador de indirección * tiene mayor prioridad que el operador de multiplicación *, ejemplo:
int n=2, *pn= &n, b;
b = *pn * 2; // es equivalente a: b = *pn*2; b = 2 * *pn; b = 2**pn; b = 2 * * pn; (no importa la cantidad de espacios)
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.

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); // compara valores de apuntadores
}

Salida:
2 2

Observaciones:
El valor de n puede cambiar de 3 modos:
n = 4;
*p1 = 4;
*p2 = 4;

En un instante, un puntero apunta a 0 o 1 variable; pero puede cambiar de variable apuntada:


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

Retorno de más de un valor por una función


Sabemos que una función retorna 0 o 1 valor en modo directo (por valor); pero en modo indirecto (por referencia) puede retornar más,
veamos un ejemplo:
#include<stdio.h>
int fun(int m, int *var1, int *var2, int *var3){ // utiliza apuntadores
*var1 = 1;
*var2 = 2;
*var3 = 3;
return 0;
}
void main(void){
int m, var1, var2, var3;
m = fun(m, &var1, &var2, &var3); // m se pasa por valor; var1, var2 y var3 se pasan por referencia
printf("%d %d %d %d\n", m, var1, var2, var3);
}
Salida: 0 1 2 3 // m recibe el valor 0 en modo directo (por valor)
// var1, var2 y var3 reciben valores en modo indirecto (por referencia)

Aplicación: Sintaxis de la función scanf()


int scanf("format", &var1, &var2, ….); // los argumentos son las direcciones de var1, var2, ...
Descripción: Lee valores tipeados de acuerdo a format
Entradas : format: formato con el que se leen los valores tipeados para var1, var2, …
&var1, &var2, …: lista de variables que recibirán los valores leídos
Salida : int: retorna el número de datos leídos: 1, 2, … ; si no lee, retorna -1.
var1, var2, …. reciben valores leídos //Atento no son salida

Cambiemos un poquito la función fun( ) anterior:


#include<stdio.h>
int fun(int m, int *var1, int *var2, int *var3){ // utiliza apuntadores
printf("Ingrese 3 enteros: ");
scanf("%d %d %d", var1, var2, var3); // ATENTO: ¿por qué falta &: &var1, &var2, &var3? Ingrese 1 2 3
return 0;
}
PÁGINA: 4
Lenguaje C

void main(void){
int m = 4, var1, var2, var3;
m = fun(m, &var1, &var2, &var3); // se pasa por valor; var1, var2 y var3 se pasan por referencia
printf("%d %d %d %d\n", m, var1, var2, var3);
}
Salida: 0 1 2 3
Respuesta: scanf( ) opera así:
scanf(“formato”, lista de direcciones de memoria<hexadecimal>)
lee los datos y los almacena en las direcciones de memoria suministradas
por lo tanto, dentro de fun( ) se debe escribir:
scanf("%d %d %d", var1, var2, var3); // ya que var1, var2 … ya son direcciones de memoria

Apuntador a un arreglo de una dimensión


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

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

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:
Asignación de valores a parr
parr; // == 0x7ff...0. Apunta al primer byte de arr[0]
parr++; // parr 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 apunta n posiciones más adelante
parr--; // parr apunta a la posición anterior
parr -= n; // parr apunta n posiciones previas
parr += 2*3+1; // = parr + 7; apunta 7 posiciones int (= 7*4 bytes) más adelante.

Atento:
parr = arr; // parr apunta a arr[o]
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, lo cual es un error de lógica y puede corromper los datos.
pa = &a; // pa apunta a a.
pa++; // pa apuntaría un dato desconocido en tiempo de ejecución, lo cual es un error de lógica; aunque compile sin
// error

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; // diferencia (en posiciones enteras) = 2.

No se permiten otro tipo de operaciones


parr = parr * 2; // Error: No se admiten multiplicaciones de valores de memoria
parr1 = parr1 + parr2; // Error

Referencia a valores apuntados por parr


*parr; // muestra 10, parr apunta a arr[0]
*(parr+1); // muestra 20; parr no cambia su valor, siempre apunta a arr[0]
*(parr+n); // muestra 30; parr no cambia su valor, siempre apunta a arr[0]

PÁGINA: 5
Lenguaje C
Operadores utilizados
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 a tipo (type) un dato de otro tipo
* 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 a *p:
Post operador Pre operador
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)

*p++; // aumenta 1 a p ++*p; // aumenta 1 a *p;


printf("%d\n", *p++); // printf("%d\n", *p); p++; printf("%d\n", ++*p); // aumenta 1 a *p; printf("%d\n", *p) ;
printf("%d\n",*(p++)); // printf("%d\n", *p); p++; printf("%d\n", ++(*p)); // aumenta 1 a *p; printf("%d\n", *p) ;
printf("%d\n", (*p)++); // printf("%d\n", *p); aumenta 1 a *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));

Apuntador constante
int a=10, b=20;
int * const pa = &a; // pa apunta solo a la variable a.
*pa = 15; // Correcto: asigna 15 a a, el valor apuntado es variable.
pa=&b; // ERROR: El valor de p es constante
pa 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}, *parr = arr;


memoria parr arr
0x7ff...0 10 20 30
Direcciones 0x7fa...b 0x7ff...0 0x7ff...4 0x7ff...8
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 parr 0x7ff...0
arr[0] *arr // *(arr+0) parr[0] *parr // *(parr+0) 10

PÁGINA: 6
Lenguaje C
arr[1] *(arr+1) parr[1] *(parr+1) 20
arr[2] *(arr+2) parr[2] *(parr+2) 30
arr[1] = 80; *(arr+1) = 80; parr[1] = 80; *(parr+1) = 80; 80
n = fun(arr, 3); n = fun(arr, 3); n = fun(parr, 3); n = fun(parr, 3); Llama a la función fun()
int fun(int arr[ ], int n){ int fun(int *arr, int n) { int fun(int parr[], int n){ int fun(int *parr, int n) { Define a la función fun()
arr[0] = 2; *arr = 2; parr[0] = 2; *parr = 2;
arr[1] = 3; *(arr+1) = 3; parr[1] = 3; *(parr+1) = 3;
*(arr+2) = 4; arr[2] = 4; *(parr+2) = 4; parr[2] = 4;
return arr[2]; return *(arr+2); return parr[2]; return *(parr+2);
} } } }
Los elementos del arreglo toman dos formas equivalentes: arr[i] == *(arr+i).
El apuntador parr se parece a un “arreglo libre”: no tiene elementos y apunta a cualquier variable o arreglo. Mientras parr apunte a un
arreglo de 1 dimensión arr, por ejemplo:
int arr[3] = {10, 20, 30}, *parr = arr;
Se puede referir a un elemento i de 4 modos equivalentes:
arr[i] = = parr[i] = = *(arr+i) = = *(parr+i) // el desarrollador controla de no salirse del rango del arr
Si pa apunta a una variable simple:
int a = 10, *pa = &pa;
Se puede utilizar: pa[i] == *(pa+i) // Compila bien; pero ya no tiene sentido lógico: en tiempo de ejecución estamos invadiendo un
// área no controlada

Diferencias entre arreglo y apuntador


1) Semántica:
arr es un conjunto de n elementos del mismo tipo; el compilador no verifica si el elemento arr[i] excede (a derecha o izquierda) sus
elementos, ejemplo: arr[-2], arr[n+3]; el programador es responsable de no excederse en tiempo de ejecución.
Dentro de su ámbito se comporta en modo dual:
Como un apuntador constante que apunta a su primer elemento
Como un conjunto de n elementos: sizeof(arr) = n*(tamaño del tipo de elemento)
Fuera de su ámbito: al pasar su dirección inicial a una función, el parámetro es un apuntador, por ejemplo:
int arr[3] = {10, 20 ,30}; // supongamos que su primer byte es 0x7ff...0
printf("%lu\n", sizeof(arr)); // imprime 12 = 3*4 (3 int de tamaño 4 byte cada uno)
miFun(arr); // pasa el valor 0x7ff...0
...
void miFun(int nn[ ]){ // equivalente: void miFun1(int *nn){
// nn es un apuntador
printf("%lu\n", sizeof(nn)); // imprime 8 = tamaño de una variable tipo apuntador
}

parr es un apuntador que apunta (toma su dirección) a cualquier variable simple o arreglo (no es constante, salvo especificación),
no tiene elementos; sin embargo se comporta como un arreglo a partir de la posición apuntada: parr[m] == *(parr+m).

Si parr apunta a arr se puede usar la notación *(parr+n) == parr[n] == arr[n] == *(arr+n); el programador es responsable de
mantenerse en el rango de arr.

2) El valor de parr y &parr son diferentes; el valor de arr y &arr son iguales;
parr tiene una dirección y apunta a otra dirección, no puede apuntarse a sí mismo
printf("%p\n", &parr); // dirección de parr: 0x7fa...b
printf("%p\n", parr); // apunta a 0x7ff...0

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


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

Aplicaciones de los apuntadores


Supongamos:
int arr[3] = {1, 2 ,3}, i=0, *parr = arr, a, *pa;
i parr arr pa a RAM
0 0x7ff...0 1 2 3 0x8ff..0 6
Direcciones (recorido físico : parr) 0x7ff...0 0x7ff...4 0x7ff...8 0x8ff..0
Indices (recorrido lógico: i) 0 1 2

PÁGINA: 7
Lenguaje C
Un apuntador puede tener 3 aplicaciones:
1) Como apuntador a una variable o arreglo
int arr[3], *parr = arr, a, *pa = &a; // parr y pa son apuntatodes
pa = &parr[1]; // ahora pa apunta al elento 1: 0x7ff...4.
parr < pa // 0x7ff...0 < 0x7ff...4
*parr < *pa // 1 < 2

2) Como arreglo “libre” de una dimensión: no tiene elementos y apunta a cualquier arreglo:
int arr[3], *parr = arr;
Se puede referir a un elemento i de 4 modos equivalentes:
arr[i] = = parr[i] = = *(arr+i) = = *(parr+i) // para i = 2:
arr[2] = = parr[2] = = *(arr+2) = = *(parr+2) = 3

3) Como variable de recorrido de un arreglo.


Imprimiremos a arr de varias maneras:
1 2 3
Un apuntador puede recorrer un arreglo de dos modos:
1) Lógico: con índices:
int imax = i+3;
while(i<imax) printf(“%d ”, parr[i++]); // Equivalente: for (i= 0; i<3; i++) printf(“%d ”, arr[i++]);
2) Físico : que será más rápido:
int *parr = arr, *pmax = parr+3;
while(parr<pmax) printf(“%d ”, *parr++);

Ejemplo: recorrer un arreglo de subida y de bajada:


// 06_01a.c Notación de arreglos // 06_01b.c Notación de apuntadores
#include<stdio.h>
void main(void){
int n = 3, arr[3] = {1, 2, 3};
Int i, j; // Recorre arreglo con variables i y j Int *p, *q, *pmax; // Recorre arreglo con apuntadores p y q
i = 0; // para iniciar el arreglo *p = arr; // apunta al inicio del arreglo
*pmax = p + n; // apunta una posición más allá del fin
j = n -1; // finaliza el arrglo *q = pmax -1; // apunta a la posición final: arr[2]
for(; i < n; i++, j--) for(; p < pmax; p++, q--)
printf("Subiendo: %d Bajando: %d\n", arr[i], arr[j]); printf("Subiendo: %d Bajando: %d\n", *p, *q);
} }
Salida:
Subiendo: 1 Bajando: 3
Subiendo: 2 Bajando: 2
Subiendo: 3 Bajando: 1
Atento: El arreglo arr, base de los datos es fijo, tiene una posición de inicio y un número de elementos; es responsabilidad del
programador:
No perder la posición de inicio
No excederse ni a la izquierda ni a la derecha de arr.
Las variables i y j permiten el recorrido de arr Los apuntadores *p y *q recorren físicamente el arreglo

Otro ejemplo similar utilizando funciones


// Arreglo de una dimensión: Crear un arreglo, reportarlo, sumarle 2 a cada elemento y reportarlo
// 06_01c.c : Notación de arreglos // 06_01d.c : Notación de apuntadores
#include<stdio.h>
void reporte(int n, int arr[]); // void reporte(int n, int *p)
void suma2(int n, int arr[]); // void suma2(int n, int *p)
void main(void){
int n=3, arr[3] = {0, 1, 2};
printf("Datos iniciales: ");
reporte(n, arr);
suma2(n, arr);
printf("Datos + 2 : ");
reporte(n, arr);
}

PÁGINA: 8
Lenguaje C
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; // i es variable de recorrido int *pmax = p+n; // p es variable de
while(i<n) arr[i++] +=2; recorrido
while(p<pmax) *p++ +=2;
}
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:
void main(void){
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

Ventajas y desventajas del uso de apuntadores


Ventajas Desventajas
Permite manejar variables complejas específica: alojar, cambiar de tamaño, liberar, La programación suele ser un poco más
acceder, pasar a funciones, ordenar, etc. compleja
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.
Pueden apuntar arreglos en modo dinámico y hacer las mismas operaciones para Es un poco complejo
distintos arreglos
Permiten el uso de memoria dinámica que aloja y libera memoria Es un poco complejo

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 opera con casos concretos, por lo tanto
reemplazaremos demostrar por verificar.
2) n es un valor leído/asignado, por ejemplo: n = 2.
3) No se puede asignar:
f2 = f1 + n; // f1 es indeterminado, no apunta a nada, debemos apuntar a alguna variable cualquiera.
Vamos a reformular el ejercicio:
Dados:
1: int n = 2;
2: float f, *f1 = &f, *f2;
3: f2 = f1 +n; // la dirección apuntada por f1 es aumentada en n
Verificar que: f2 – f1 = n* sizeof(float) // sizeof(float) = 4 = tamaño de variable flotante.
/* 06_02.c : Dados dos punteros float *f1, *f2; si hacemos f2 = f1 + n,
verificar que respecto a f1, la dirección de f2 aumenta: n*sizeof(float) bytes

PÁGINA: 9
Lenguaje C
*/
#include<stdio.h>
void main(void){
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 : %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 : 8 bytes

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 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 presentan limitaciones:


1. El lenguaje C los concibe como un arreglo (filas) de arreglos (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. Esta solución presenta inconvenientes:
En tiempo de programación se debe estimar el número de filas y columnas: si las estimaciones son excedentes, se
desperdician; si son pequeñas, se deben aumentar y recompilar; y si nos excedemos de los límites, no reporta error de
compilación, pero al ejecutar se produce el error:
Violación de segmento (`core' generado)
y no indica donde se produjo el error.
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.

Estos problemas se resuelven en modo óptimo trabajando con:


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.

PÁGINA: 10
Lenguaje C
Apuntador a arreglos de dos dimensiones
int arr[2][3] = {2, 4, 6, 8, 10, 12}, *parr = &arr[0][0]; // arr[0][0] tiene la misma dirección que arr
Equivalente:
int arr[2][3] = {2, 4, 6, 8, 10, 12}, *parr = arr[0]; // el vector fila arr[0] apunta a la misma dirección que arr

Posiciones: 0 1 2 3 4 5
parr 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 parr[i*3+j], y a *(parr+ i*3+j).

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


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

Paso de apuntador como parámetro de 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

// 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); // 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];
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 n, int *parr); void mitad(int m, int n, int *parr); void mitad(int m, int n, int parr[][2]);
void main(void){ void main(void){ void main(void){
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;
// *parr = &arr[0][0]; no funciona: *parr = arr
mitad(4, arr); // pasa 0x7ff...2, 4 mitad(2, 2, parr); // pasa 0x7ff...2, 4 mitad(2, 2, arr); // pasa 0x7ff...2, 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", *parr++); for(j=0; j < 2; j++) printf("%d\t", arr[i][j]);
printf("\n"); printf("\n"); printf("\n");
} } }

PÁGINA: 11
Lenguaje C
void mitad(int n, int *p){ void mitad(int m, int n, int *p){ void mitad(int m, int n, int arr[][n]){
// 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;
while(p<pmax) *p++ /=2; while(p < pmax) *p++ /=2; for(i=0; i < m; i++)
for(j=0; j < n; j++) arr[i][j] /=2;
} } }
Salida: 1 2 3 4

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

Resumen del uso de arreglos y apuntadores:


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

Un arreglo de una o más dimensiones mantiene su dirección de inicio FIJA -no la pierde nunca-; mientras que un apunta-
dor simplemente apunta a una dirección y puede apuntar a la siguiente: p++; o a la anterior: p--; el programador debe
controlar que el apuntador apunte a la dirección correcta.
¿Cuál de las dos notaciones utilizar? La notación lógica es la que manda porque es lógica; cuando los desplazamientos lógicos son con-
tinuos -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.

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


#include<stdio.h>
void imp(int m, int n, int *pa);
void tran(int m, int n, int *pa, int *pb);
void main(void){
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 y lógica
}
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, ejemplo, imprimir un arreglo bidimensional 2 veces:
#include<stdio.h>
void prin2(int m, int n , int *pa);
void main(void){
int a[2][3] = {1, 2, 3, 4, 5, 6}, *pa= a[0]; // supongamos que pa apunta a 0xff..a
prin2(2, 3 , pa); // argumentos: 2, 3, 0xff..a
}
void prin2(int m, int n, int *pa){ // 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

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

Ejemplo de uso específico: Particionar un espacio en la RAM como arreglo de 1, 2 y 3 dimensiones


// 06_05.c Particionar un espacio en la RAM como arreglo de 1, 2 y 3 dimensiones
#include<stdio.h>
void main(void){
int i, j, k, a[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12};
// 1 dimensión: 12 elementos
int *p = &a[0]; // puntero
for(i=0; i<12; i++) printf("%d ", p[i]);
printf(" : 1 dimensión de 12 elementos\n");

// 2 dimensiones: 2 x 6
int *q[2]; // arreglo de punteros
q[0] = &a[0], q[1] = &a[6];
for(i=0; i<2; i++)
for(j=0; j<6; j++) printf("%d ", q[i][j]);
printf(" : 2 dimensiones 2 x 6\n");

// de 3 dimensiones: 2 x 3 x 2
int *r[2][3]; // matriz de punteros
r[0][0] = &a[0], r[0][1] = &a[2], r[0][2] = &a[4];
r[1][0] = &a[6], r[1][1] = &a[8], r[1][2] = &a[10];
for(i=0; i<2; i++)
for(j=0; j<3; j++)
for(k=0; k<2; k++) printf("%d ", r[i][j][k]);
printf(" : 3 dimensiones 2 x 3 x 2\n");
}
Salida:
1 2 3 4 5 6 7 8 9 10 11 12 : 1 dimensión de 12 elementos
1 2 3 4 5 6 7 8 9 10 11 12 : 2 dimensiones de 2 x 6
1 2 3 4 5 6 7 8 9 10 11 12 : 3 dimensiones 2 x 3 x 2
Ejemplo de uso específico: Particionar un espacio en la RAM como arreglo de 1, 2 y 3 dimensione

PÁGINA: 13
Lenguaje C
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 reconoce el operador 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 no es alterada;
3) Se accede a los valores de la matriz a través de los apuntadores: *p[i]

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

Ordenar ascendentemente una matriz por la primera columna utilizando el método del mínimo
// 06_06a.c: utilizando arreglos // 06_06b.c: utilizando apuntadores
#include<stdio.h> #include<stdio.h>
void ordenar(int n, int m, int arr[][m]); void ordenar(int n, int m, int *parr[m]);
void reportar(int n, int m, int arr[][m]); void reportar(int n, int m, int *parr[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(n, m, arr); reportar(n, m, parr);
ordenar(n, m, arr); ordenar (n, m, parr);
printf("\nArreglo ordenado:\n"); printf("\nArreglo ordenado:\n");
reportar(n, m, arr); reportar(n, m, parr);
} }
void reportar(int n, int m, int arr[ ][m]) { void reportar(int n, int m, int **p) {
int i, j; int **pmax = p+n, *pj, *pjmax;
for(i=0; i<n; i++) { for(;p<pmax; p++){
for(j=0; j<m; j++) printf("%d\t", arr[i][j]); for(pj=*p, pjmax=pj+m; pj<pjmax;pj++) printf("%d\t", *pj);
printf("\n"); printf("\n");
} }
} }
void ordenar(int n, int m, int arr[][m]) { void ordenar(int n, int m, int **p) {
int i, j, imin, amin; int **pmax = p+n, **pj, **pimin, amin, *temp;
for(i=0; i<n-1; i++){ for(; p<pmax-1; p++) {
imin = i; pimin = p;
amin = arr[i][0]; amin = **p;
for(j=i+1; j<n; j++) // mínimo valor for(pj = p+1; pj<pmax; pj++) // mínimo valor
if (amin > arr[j][0]){ if (amin > **pj){
amin = arr[j][0]; amin = **pj;
imin = j; pimin = pj;
} }
if(imin>i){ // intercambio de filas if(pimin>p){ // intercambio de apuntadores
temp = *pimin;
arr[imin][0] = arr[i][0]; *pimin = *p;
arr[i][0] = amin; *p = temp;
amin = arr[imin][1];
arr[imin][1] = arr[i][1];
arr[i][1] = amin;
} }
} }
} }
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

PÁGINA: 15
Lenguaje C

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


Ver apéndice 1.
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

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

// 06_07.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.
// Funciona bien // Funciona con warning // Funciona bien
#include<stdio.h> #include<stdio.h> #include<stdio.h>
void mitad(int m, int n, int (*p)[n]); void mitad(int m, int n, int *p); void mitad(int m, int n, int *p);
void main(void){ void main(void){ void main(void){
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); mitad(3, 2, p);
} } }
void mitad(int m, int n, int (*p)[n]){ void mitad(int m, int n, int *p){ void mitad(int m, int n, int *p){
// p es puntero a arreglo de n ele- // p es puntero y apunta a arreglo // p es puntero y apunta a la dirección inicial
mentos de 2 dimensiones // lo mismo sucede para más de 2 dimensiones
int i, j; int *pmax = p+m*n; int *pmax = p+m*n;
for(i=0; i<m; i++) while(p<pmax){ while(p<pmax){
for(j=0; j<n; j++){ *p /=2; *p /=2;
p[i][j] /=2; printf("%d ", *p++); printf("%d ", *p++);
printf("%d ", p[i][j]);
} } }
printf("\n"); 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

PÁGINA: 16
Lenguaje C
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; float fl=2.4;
void *pvoid = pfl; // apunta a fl void *pvoid = &fl; // apunta a fl
pfl = (float *)pVoid; // Para asignar a otro tipo se requiere casting
printf("%.2f %.2f\n", *pfl, *(float *)pvoid);
Salida: 2.40 2.40

Ejemplo:
int in = 1; float fl = 2.4;
// asignar a void asignar void imprimir
pvoid = &in; int *pin = (int *)pvoid; printf("%d\n", *pin); // = 1
pvoid = &fl; float *pfl = (float *)pvoid; printf("%lf\n", *pfl); // = 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.

¡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!

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

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

5) 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: 18

Вам также может понравиться