Buscar..
Introducción
Un puntero es una dirección que se refiere a una ubicación en la memoria. Se utilizan comúnmente para permitir que las funciones o estructuras de datos conozcan y modifiquen la memoria sin tener que copiar la memoria a la que se hace referencia. Los punteros se pueden utilizar con tipos primitivos (integrados) o definidos por el usuario.
Los punteros hacen uso de la "desreferencia" *
, "dirección de" &
, y "flecha" ->
operadores. Los operadores '*' y '->' se usan para acceder a la memoria a la que se apunta, y el operador &
se usa para obtener una dirección en la memoria.
Sintaxis
- <Tipo de datos> * <Nombre de variable>;
- <Tipo de datos> * <Nombre de variable> = & <Nombre de variable del mismo tipo de datos>;
- <Tipo de datos> * <Nombre de variable> = <Valor del mismo tipo de datos>;
- int * foo; // Un puntero que apunta a un valor entero
- int * bar = & myIntVar;
- barra larga * [2];
- long * bar [] = {& myLongVar1, & myLongVar2}; // Igual a: barra larga * [2]
Observaciones
Tenga en cuenta los problemas al declarar múltiples punteros en la misma línea.
int* a, b, c; //Only a is a pointer, the others are regular ints.
int* a, *b, *c; //These are three pointers!
int *foo[2]; //Both *foo[0] and *foo[1] are pointers.
Fundamentos de puntero
Nota: en todo lo siguiente, se supone la existencia de la constante de C ++ 11 nullptr
. Para versiones anteriores, reemplace nullptr
con NULL
, la constante que solía jugar un rol similar.
Creando una variable de puntero
Se puede crear una variable de puntero utilizando la sintaxis específica *
, por ejemplo, int *pointer_to_int;
.
Cuando una variable es de tipo puntero ( int *
), solo contiene una dirección de memoria. La dirección de la memoria es la ubicación en la que se almacenan los datos del tipo subyacente ( int
).
La diferencia es clara cuando se compara el tamaño de una variable con el tamaño de un puntero al mismo tipo:
// Declare a struct type `big_struct` that contains
// three long long ints.
typedef struct {
long long int foo1;
long long int foo2;
long long int foo3;
} big_struct;
// Create a variable `bar` of type `big_struct`
big_struct bar;
// Create a variable `p_bar` of type `pointer to big_struct`.
// Initialize it to `nullptr` (a null pointer).
big_struct *p_bar0 = nullptr;
// Print the size of `bar`
std::cout << "sizeof(bar) = " << sizeof(bar) << std::endl;
// Print the size of `p_bar`.
std::cout << "sizeof(p_bar0) = " << sizeof(p_bar0) << std::endl;
/* Produces:
sizeof(bar) = 24
sizeof(p_bar0) = 8
*/
Tomando la dirección de otra variable
Los punteros se pueden asignar entre sí como variables normales; en este caso, es la dirección de memoria que se copia de un puntero a otro, no los datos reales a los que apunta un puntero.
Además, pueden tomar el valor nullptr
que representa una ubicación de memoria nula. Un puntero igual a nullptr
contiene una ubicación de memoria no válida y, por lo tanto, no hace referencia a datos válidos.
Puede obtener la dirección de memoria de una variable de un tipo dado prefijando la variable con la dirección del operador &
. El valor devuelto por &
es un puntero al tipo subyacente que contiene la dirección de memoria de la variable (que son datos válidos siempre que la variable no quede fuera del alcance ).
// Copy `p_bar0` into `p_bar_1`.
big_struct *p_bar1 = p_bar0;
// Take the address of `bar` into `p_bar_2`
big_struct *p_bar2 = &bar;
// p_bar1 is now nullptr, p_bar2 is &bar.
p_bar0 = p_bar2;
// p_bar0 is now &bar.
p_bar2 = nullptr;
// p_bar0 == &bar
// p_bar1 == nullptr
// p_bar2 == nullptr
En contraste con las referencias:
- asignar dos punteros no sobrescribe la memoria a la que se refiere el puntero asignado;
- Los punteros pueden ser nulos.
- La dirección del operador se requiere explícitamente.
Accediendo al contenido de un puntero.
Como tomar una dirección requiere &
, también el acceso al contenido requiere el uso del operador de referencia *
, como un prefijo. Cuando se hace referencia a un puntero, se convierte en una variable del tipo subyacente (en realidad, una referencia a él). Luego se puede leer y modificar, si no const
.
(*p_bar0).foo1 = 5;
// `p_bar0` points to `bar`. This prints 5.
std::cout << "bar.foo1 = " << bar.foo1 << std::endl;
// Assign the value pointed to by `p_bar0` to `baz`.
big_struct baz;
baz = *p_bar0;
// Now `baz` contains a copy of the data pointed to by `p_bar0`.
// Indeed, it contains a copy of `bar`.
// Prints 5 as well
std::cout << "baz.foo1 = " << baz.foo1 << std::endl;
La combinación de *
y el operador .
es abreviado por ->
:
std::cout << "bar.foo1 = " << (*p_bar0).foo1 << std::endl; // Prints 5
std::cout << "bar.foo1 = " << p_bar0->foo1 << std::endl; // Prints 5
Desreferenciación de punteros inválidos
Al desreferenciar un puntero, debe asegurarse de que apunta a datos válidos. La anulación de la referencia de un puntero no válido (o un puntero nulo) puede provocar una violación de acceso a la memoria, o leer o escribir datos de basura.
big_struct *never_do_this() {
// This is a local variable. Outside `never_do_this` it doesn't exist.
big_struct retval;
retval.foo1 = 11;
// This returns the address of `retval`.
return &retval;
// `retval` is destroyed and any code using the value returned
// by `never_do_this` has a pointer to a memory location that
// contains garbage data (or is inaccessible).
}
En tal escenario, g++
y clang++
emiten correctamente las advertencias:
(Clang) warning: address of stack memory associated with local variable 'retval' returned [-Wreturn-stack-address]
(Gcc) warning: address of local variable ‘retval’ returned [-Wreturn-local-addr]
Por lo tanto, se debe tener cuidado cuando los punteros son argumentos de funciones, ya que podrían ser nulos:
void naive_code(big_struct *ptr_big_struct) {
// ... some code which doesn't check if `ptr_big_struct` is valid.
ptr_big_struct->foo1 = 12;
}
// Segmentation fault.
naive_code(nullptr);
Operaciones de puntero
Hay dos operadores para punteros: Dirección de operador (&): devuelve la dirección de memoria de su operando. Operador de contenido de (desreferencia) (*): devuelve el valor de la variable ubicada en la dirección especificada por su operador.
int var = 20;
int *ptr;
ptr = &var;
cout << var << endl;
//Outputs 20 (The value of var)
cout << ptr << endl;
//Outputs 0x234f119 (var's memory location)
cout << *ptr << endl;
//Outputs 20(The value of the variable stored in the pointer ptr
El asterisco (*) se usa para declarar un puntero con el único propósito de indicar que es un puntero. No confunda esto con el operador de desreferencia , que se utiliza para obtener el valor ubicado en la dirección especificada. Son simplemente dos cosas diferentes representadas con el mismo signo.
Aritmética de puntero
Incremento / Decremento
Un puntero puede ser incrementado o decrementado (prefijo y postfix). Incrementar un puntero avanza el valor del puntero al elemento en la matriz, un elemento más allá del elemento apuntado actualmente. Disminuir un puntero lo mueve al elemento anterior en la matriz.
La aritmética de punteros no está permitida si el tipo al que apunta el puntero no está completo. void
es siempre un tipo incompleto.
char* str = new char[10]; // str = 0x010
++str; // str = 0x011 in this case sizeof(char) = 1 byte
int* arr = new int[10]; // arr = 0x00100
++arr; // arr = 0x00104 if sizeof(int) = 4 bytes
void* ptr = (void*)new char[10];
++ptr; // void is incomplete.
Si se incrementa un puntero al elemento final, entonces el puntero apunta a un elemento más allá del final de la matriz. Tal puntero no puede ser referenciado, pero puede ser disminuido.
Incrementar un puntero al elemento uno más allá del final en la matriz, o disminuir un puntero al primer elemento en una matriz produce un comportamiento indefinido.
Un puntero a un objeto no de matriz puede tratarse, a los efectos de la aritmética de punteros, como si fuera una matriz de tamaño 1.
Suma resta
Se pueden agregar valores enteros a los punteros; actúan como incrementos, pero por un número específico en lugar de por 1. Los valores enteros también se pueden sustraer de los punteros, actuando como disminución del puntero. Al igual que con el incremento / decremento, el puntero debe apuntar a un tipo completo.
char* str = new char[10]; // str = 0x010
str += 2; // str = 0x010 + 2 * sizeof(char) = 0x012
int* arr = new int[10]; // arr = 0x100
arr += 2; // arr = 0x100 + 2 * sizeof(int) = 0x108, assuming sizeof(int) == 4.
Diferencia de puntero
La diferencia entre dos punteros del mismo tipo se puede calcular. Los dos punteros deben estar dentro del mismo objeto de matriz; De lo contrario, el comportamiento no definido resulta.
Dados dos punteros P
y Q
en la misma matriz, si P
es el elemento i
th en la matriz, y Q
es el elemento j
th, entonces P - Q
será i - j
. El tipo del resultado es std::ptrdiff_t
, desde <cstddef>
.
char* start = new char[10]; // str = 0x010
char* test = &start[5];
std::ptrdiff_t diff = test - start; //Equal to 5.
std::ptrdiff_t diff = start - test; //Equal to -5; ptrdiff_t is signed.