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

Ausencia de punteros en Python

En algunas aplicaciones sencillas que me ha tocado escribir en C/C++ he visto la


facilidad con la que se resuelven ciertas tareas utilizando punteros. Ahora, más
interesado en otro lenguaje: Python, he notado la ausencia de este concepto. ¿A
qué se debe esta ausencia? Siendo Python un lenguaje muy poderoso y utilizado,
entonces, ¿qué concepto lo sustituye?, ¿está implícito en los tipos de datos, en las
asignaciones, en la instanciación de una clase?
Un ejemplo extremadamente sencillo sería que en C podemos codificar cosas
como esta:
#include <stdio.h>

int main(void) {
// your code goes here
int a = 5;
int *b = &a;
printf("a = %d; b = %d\n", a, *b); // (1)

a = 6;
printf("a = %d; b = %d\n", a, *b); // (2)
return 0;
}
(1): a = 5; b = 5

(2): a = 6; b = 6
b apunta a la dirección de memoria de a, cualquier modificación en a podrá ser
observada al desreferenciar b. Cualquier asignación por indirección *b =
<valor>; modificará a.
Pero, en Python:

a=5
b=a
print "a = {0:d}; b = {1:d}".format(a,b) # (1)
b is a # resultado: True

a=6
print "a = {0:d}; b = {1:d}".format(a,b) # (2)
b is a # resultado: False
(1): a = 5; b = 5

(2): a = 6; b = 5
Al principio a y b hacen referencia al mismo objeto. Luego, cuando a es modificado
se crea un nuevo objeto; entonces, ambos hacen referencia a objetos diferentes y
de valores diferentes.
No hay una forma de hacer lo que en C en Python con este tipo de dato, pero es
posible hacer algo similar con tipos de datos mutables; sin embargo, solo se puede
cuando hacemos modificaciones internas del dato mutable, por ejemplo: cambiar el
valor de un elemento de una lista.

a = [1, 2]
b=a
print b is a # resultado: True

b.append(3)

print b is a # resultado: True

a = [4, 5]

print b is a # resultado: False

Punteros
Un puntero es un tipo de datos cuyo valor es una dirección de memoria.

Nunca olvide esta sencilla definición. Los punteros son un concepto que suele
causar mucha confusión a quienes están aprendiendo C. Sin embargo, no se trata
de un concepto difícil si uno comprende cómo están representadas las variables
en la memoria.

Por otra parte, el uso incorrecto de punteros es una fuente muy común de errores
críticos, y que no siempre son fáciles de depurar. Por esto es importante siempre
entender muy bien lo que se está haciendo cuando hay punteros involucrados.

Cuando una variable de tipo puntero tiene almacenada una dirección de


memoria, se dice que «apunta» al valor que está en esa dirección.

┌──────────┐
0x3ad900 │ 398 │ int n
├──────────┤
0x3ad904 │ ???????? │
├──────────┤
0x3ad908 │ 0x3ad900 │ int *p
├──────────┤
0x3ad90c │ 0x3ad904 │ float *q
├──────────┤
0x3ad910 │ 2.717 │ float x
└──────────┘

En general no importa cuál es valor exacto de un puntero, sino que basta con
comprender qué es lo que hay «al otro lado». Por esto, en los diagramas de la
memoria se suele preferir usar flechas en lugar de las direcciones explícitas:

┌──────────┐
int n │ 398 │◂────┐
├──────────┤ │
│ ???????? │ │
├──────────┤ │
int *p │ ●────┼─────┘
├──────────┤
float *q │ ●────┼────┐
├──────────┤ │
float x │ 2.717 │◂───┘
└──────────┘

Un valor especial llamado NULL puede ser asignado a cualquier puntero para
indicar aún no está apuntando a ninguna parte de la memoria.

Declaración de punteros
La siguiente es la manera de declarar un puntero que apunte a un entero:

int *x;

Esto se puede leer «lo apuntado por x es un entero». En este caso, * no es una
multiplicación, sino una derreferenciación, como veremos más abajo.

Una vez declarada x de la manera ya mostrada, los únicos valores válidos que se
puede asignar a xson NULL o una dirección de memoria donde haya un entero:

int a, b, c;
float z;
int *p;
int *q;

p = NULL; /* valido */
p = &a; /* valido */
p = &b; /* valido */
p = &z; /* invalido (z no es un entero) */
p = 142857; /* invalido (142857 no es una dirección de memoria) */

q = &b; /* valido */
q = p; /* valido */
q = NULL; /* valido */
q = &p; /* invalido (p no es un entero) */

Por supuesto, es posible cambiar int por cualquier otro tipo para declarar
punteros a datos de otra naturaleza.

Ojo con la siguiente sutileza al declarar varios punteros de una vez:

int *x, *y; /* x e y son punteros */


int *x, y; /* x es puntero, y es entero */

Derreferenciación de punteros
El operador unario * de los punteros es el operador de derreferenciación. Lo que
hace es entregar el valor que está en la dirección de memoria.

En otras palabras, * significa «lo apuntado por».

Al derreferenciar un puntero a entero, se obtiene un entero. El puntero


derreferenciado puede ser usado en cualquier contexto en que un entero sea
válido:

int x, y;
int *p;

x = 5;
p = &x;

printf("%d\n", *p); /* imprime 5 */


y = *p * 10 - 7; /* y toma el valor 43 */
*p = 9; /* x toma el valor 9 */

En la última sentencia, se está asignando el valor 9 en la memoria que está


reservada para la variable x, por lo que la asignacion cambia en efecto el valor de
la variable x de manera indirecta.

Derreferenciar el puntero NULL no está permitido. Al hacerlo, lo más probable es


que el programa se termine abruptamente y sin razón aparente. Errores de este
tipo son muy fastidiosos, pues son difíciles de detectar, e incluso pueden ocurrir
en un programa que ha estado funcionando correctamente durante mucho
tiempo.

Si existe alguna remota posibilidad de que un puntero pueda tener el valor NULL,
lo sensato es revisar su valor antes de derreferenciarlo:

if (p != NULL)
hacer_algo(*p);

Ejercicio
¿Qué imprime el siguiente programa?

#include <stdio.h>

int main() {
float w, z;
float *p, *q;

w = 20;
p = &z;
q = p;
*q = 7;
z += *q;
w -= *p;
p = &w;
*q += *p;
z += *(&w);
p = q;
*p = *q;

printf("%f %f %f %f\n", w, z, *p, *q);


return 0;
}

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