Apuntadores (Punteros) en Programación
Un apuntador (o puntero) es una variable que contiene la dirección de memoria de otro dato o de otra variable. En lugar de almacenar el valor directamente, el puntero “apunta” al lugar de memoria donde ese valor vive. Los punteros permiten manipulación eficiente y flexible de la memoria.
Declaración de un Apuntador
Para declarar un puntero, se especifica el tipo de dato al que apuntará, seguido de un asterisco (*
) y el nombre o identificador del puntero.
En Pseudocódigo
tipo_dato *nombre_puntero
// Ejemplo
entero *puntero;
En Lenguaje C
tipo_dato *nombre_puntero;
// Ejemplo
int *puntero;
Uso de Apuntadores
Los punteros permiten trabajar con memoria de forma directa. Entre sus usos principales están: manipulación indirecta de variables, paso de parámetros por referencia (de salida) y trabajo eficiente con arreglos, así como la asignación dinámica de memoria y las estructuras dinámicas, como listas dinámicas, árboles, etc.
1. Manipulación Indirecta de Variables
Obtener la dirección: usa el operador &
delante del nombre de la variable; se puede aplicar a una variable de cualquier tipo de dtado. Devuelve su dirección de memoria. Ej. si la variable es num
, entonces &num
retorna su dirección.
Desreferenciar: usa el operador *
con un puntero para acceder al contenido de la ubicación a la que apunta (o dirección de memoria que contiene dicha variable). Si puntero
almacena la dirección de num
, entonces *puntero
es el valor de num
.
Ejemplo de aplicación (pseudocódigo)
// En C, se necesitaría include <stdio.h> para printf
Inicio
// Declaración de una variable entera y un puntero a entero
entero num
entero *puntero
// Asignar un valor a num
num ← 100
// Asignar la dirección de memoria de num al puntero
puntero ← &num
// Mostrar la dirección de memoria a la que apunta el puntero
Escribir("La dirección de memoria de num es: ", puntero)
// Mostrar el valor de la variable a través del puntero
Escribir("El valor de num a través del puntero es: ", *puntero)
// Modificar el valor de num a través del puntero
*puntero ← 200
// Mostrar el nuevo valor de num (directamente y a través del puntero)
Escribir("El nuevo valor de num es: ", num)
Escribir("El nuevo valor de num a través del puntero es: ", *puntero)
Fin
Ejemplo en lenguaje C
#include <stdio.h> // Necesario para printf
int main(void) {
// Declaración de una variable entera y un puntero a entero
int num;
int *puntero; //
// Asignar un valor a num
num = 100;
// Asignar la dirección de memoria de num al puntero
puntero = # // El operador '&' obtiene la dirección de memoria
// Mostrar la dirección de memoria a la que apunta el puntero
printf("La dirección de memoria de num es: %p\n", (void*)puntero); // '%p' para direcciones
// Mostrar el valor de la variable a través del puntero
printf("El valor de num a través del puntero es: %d\n", *puntero); // El operador '*' desreferencia
// Modificar el valor de num a través del puntero
*puntero = 200;
// Mostrar el nuevo valor de num (directamente y a través del puntero)
printf("El nuevo valor de num es: %d\n", num);
printf("El nuevo valor de num a través del puntero es: %d\n", *puntero);
return 0;
}
En este ejemplo, puntero
primero almacena la dirección de num
y luego se utiliza *puntero
para ver y cambiar el valor almacenado en num
de forma indirecta.
2. Paso de Parámetros a Funciones (Entrada y Salida)
Debido a que las variables se declaran en el interior de cada función se consideran en el ámbito local de la función. Esto resulta en que todas las variables declaradas son visibles (o conocidas) sólo en la función a la que pertencen. Para comounicar información entre las funciones se usan los parámetros. Estos se clasifican en “de entrada” o “paso por valor” y “de salida” o “paso por referencia”.
Paso por valor: la función recibe una copia del valor; modificarlo dentro de la funicón no afecta la variable original.
Paso por referencia: se pasa la dirección de la variable mediante un puntero; la función puede modificar el valor original mediante dicha referencia.
Pseudocódigo
// En C, se necesitaría include <stdio.h>
// Prototipo de la función que suma dos números y devuelve el resultado (paso por valor)
funcion_entero Sumar(entero a, entero b)
inicio
regresa (a + b)
fin
// Prototipo de la función que resta dos números y guarda el resultado (paso por referencia)
// 'resultado' se pasa como un puntero para modificar la variable original fuera de la función
funcion_nada Restar(entero num1, entero num2, entero *resultado)
inicio
*resultado ← num1 - num2 // Acceder al valor en la dirección y modificarlo
fin
// Programa Principal
principal()
inicio
entero x, y, z, resta_resultado // Declaración de variables
// Paso por valor para la suma
x ← 10
y ← 5
z ← Sumar(x, y) // Se pasa una copia de x e y. Z almacena el resultado.
Escribir("La suma de ", x, " y ", y, " es: ", z)
// Paso por referencia para la resta
// La variable 'resta_resultado' se declara en el main.
// La función 'Restar' modificará su valor directamente.
Restar(x, y, &resta_resultado) // Se pasa la dirección de memoria de resta_resultado
Escribir("La resta de ", x, " y ", y, " es: ", resta_resultado)
fin
Lenguaje C
#include <stdio.h> // Necesario para printf
// Función que suma dos números y devuelve el resultado (paso por valor)
int Sumar(int a, int b) {
return (a + b);
}
// Función que resta dos números y guarda el resultado (paso por referencia)
// 'resultado' se pasa como un puntero para modificar el valor original
void Restar(int num1, int num2, int *resultado) { // 'resultado' es un puntero
*resultado = num1 - num2; // Se desreferencia el puntero para acceder y modificar el valor original
}
int main(void) {
int x, y, z;
int resta_resultado; // Variable donde se almacenará el resultado de la resta
// Paso por valor para la suma
x = 10;
y = 5;
z = Sumar(x, y); // Se pasan copias de x e y
printf("La suma de %d y %d es: %d\n", x, y, z);
// Paso por referencia para la resta
// Se pasa la dirección de memoria de 'resta_resultado' usando '&'
Restar(x, y, &resta_resultado);
printf("La resta de %d y %d es: %d\n", x, y, resta_resultado);
return 0;
}
En el ejemplo Restar
, el parámetro resultado
es un puntero. Al llamar a la función se pasa &resta_resultado
; dentro, *resultado
modifica el valor en la memoria del main()
.
3. Apuntadores para Recorrer o Trabajar con Arreglos
Un arreglo almacena elementos contiguos en memoria. El nombre del arreglo ya es la dirección del primer elemento, por lo que los punteros son ideales para recorrer y manipular sus elementos.
- Declara un puntero y asígnale la dirección del primer elemento de un arreglo.
- Incrementa (++) o decrementa (—) el puntero para moverte por el arreglo. Al incrementar, el puntero salta al siguiente elemento del tipo correspondiente.
- El nombre del arreglo es una constante de dirección; no puede incrementarse como variable.
gets
, pero está desaconsejado. Aquí incluimos una versión con fgets
, que limita el tamaño de lectura.Pseudocódigo (conversión de mayúsculas a minúsculas)
// Se asume la existencia de una función 'toLower' que convierte un carácter a minúscula.
// En C, se necesitaría include <stdio.h> y <string.h> para gets/puts, y <ctype.h> para tolower
principal()
inicio
cadena cadena_original[7] // Declaración de un arreglo de caracteres (cadena)
caracter *puntero_cadena // Declaración de un puntero a carácter
Escribir("Ingrese una cadena en mayúsculas: ")
Leer(cadena_original) // Asumimos una instrucción para leer cadena
// Asignar la dirección del primer elemento de la cadena al puntero
puntero_cadena ← cadena_original // El nombre del arreglo es la dirección del primer elemento
Escribir("La cadena en minúsculas es: ")
// Recorrer la cadena utilizando el puntero hasta el final de la cadena (carácter nulo '\0')
mientras (*puntero_cadena <> '\0') // Mientras el carácter actual no sea el final de la cadena
inicio
// Imprimir el carácter actual después de convertirlo a minúscula
Escribir(toLower(*puntero_cadena)) // Asumiendo 'toLower' es una función que convierte a minúscula
// Incrementar el puntero para moverlo al siguiente carácter de la cadena
puntero_cadena ← puntero_cadena + 1 // O simplemente puntero_cadena++
fin
Escribir("\n") // Salto de línea al final
fin
Lenguaje C (versión segura con fgets
)
#include <stdio.h> // printf, fgets
#include <ctype.h> // tolower
int main(void) {
char cadena_original[7]; // Tamaño fijo del buffer
char *puntero_cadena; // Puntero a carácter
printf("Ingrese una cadena en mayúsculas (máx 6 caracteres): ");
if (fgets(cadena_original, sizeof(cadena_original), stdin) == NULL) {
return 0; // lectura fallida o EOF
}
// Quitar salto de línea si aparece
for (int i = 0; cadena_original[i] != '\0'; ++i) {
if (cadena_original[i] == '\n') { cadena_original[i] = '\0'; break; }
}
// El nombre del arreglo ya es la dirección del primer elemento
puntero_cadena = cadena_original;
printf("La cadena en minúsculas es: ");
while (*puntero_cadena != '\0') {
printf("%c", tolower((unsigned char)*puntero_cadena)); // tolower() convierte a minúscula
puntero_cadena++; // Avanza al siguiente carácter
}
printf("\n");
return 0;
}
Versión original con gets
(no recomendada)
gets
fue eliminada del estándar C por insegura (no controla longitud). Se muestra solo con fines didácticos.#include <stdio.h>
#include <ctype.h>
int main(void) {
char cadena_original[7];
char *puntero_cadena;
printf("Ingrese una cadena en mayúsculas: ");
gets(cadena_original); // NO RECOMENDADO
puntero_cadena = cadena_original;
printf("La cadena en minúsculas es: ");
while (*puntero_cadena != '\0') {
printf("%c", tolower((unsigned char)*puntero_cadena));
puntero_cadena++;
}
printf("\n");
return 0;
}
Ejercicios de Práctica Sugeridos
Implementa en pseudocódigo y en C:
1. Funciones con Parámetros por Referencia
- intercambiar_valores: recibe direcciones de dos enteros y los intercambia. main() lee, llama e imprime.
- Operaciones básicas: función que reciba dos números (por valor) y devuelva suma, resta, multiplicación y división mediante parámetros por referencia (punteros).
- obtener_max_min: recibe un arreglo de enteros y dos punteros; guarda en esas direcciones el máximo y el mínimo.
2. Manipulación de Arreglos con Punteros
- Declara un arreglo de 5 enteros. Usa un puntero para leer los 5 números y otro para imprimirlos en orden inverso.
- copiar_arreglo(origen, destino, n): copia con aritmética de punteros.
3. Funciones con Punteros para Cálculos Específicos
- calcular_promedio_y_suma: recibe arreglo de float y dos punteros a float; calcula y almacena promedio y suma.