Pointer declaration
Declara una variable de tipo puntero o puntero-a-miembro.
Contenidos |
Sintaxis
Una declaración de puntero es cualquier declaración simple cuyo declarador tiene la forma
*
attr
(opcional)
cv
(opcional)
declarator
|
(1) | ||||||||
nested-name-specifier
*
attr
(opcional)
cv
(opcional)
declarator
|
(2) | ||||||||
S
.
C
de tipo determinado por la secuencia de especificadores de declaración
S
.
| nested-name-specifier | - |
una
secuencia de nombres y operadores de resolución de ámbito
::
|
| attr | - | (since C++11) una lista de atributos |
| cv | - | calificación const/volatile que se aplica al puntero que se está declarando (no al tipo apuntado, cuyas calificaciones son parte de la secuencia de especificadores de declaración) |
| declarator | - | cualquier declarador |
No hay punteros a referencias y no hay punteros a campos de bits . Normalmente, las menciones de "punteros" sin especificación no incluyen punteros a miembros (no estáticos).
Punteros
Cada valor de tipo puntero es uno de los siguientes:
- un puntero a un objeto o función (en cuyo caso se dice que el puntero apunta a el objeto o función), o
- un puntero más allá del final de un objeto , o
- el valor de puntero nulo para ese tipo, o
- un valor de puntero inválido .
Un puntero que apunta a un objeto representa la dirección del primer byte en memoria ocupado por el objeto. Un puntero más allá del final de un objeto representa la dirección del primer byte en memoria después del final del almacenamiento ocupado por el objeto.
Tenga en cuenta que dos punteros que representan la misma dirección pueden sin embargo tener valores diferentes.
struct C { int x, y; } c; int* px = &c.x; // el valor de px es "puntero a c.x" int* pxe= px + 1; // el valor de pxe es "puntero más allá del final de c.x" int* py = &c.y; // el valor de py es "puntero a c.y" assert(pxe == py); // == prueba si dos punteros representan la misma dirección // puede o no activarse *pxe = 1; // comportamiento indefinido incluso si la aserción no se activa
La indirección a través de un valor de puntero inválido y pasar un valor de puntero inválido a una función de desasignación tiene comportamiento indefinido. Cualquier otro uso de un valor de puntero inválido tiene comportamiento definido por la implementación. Algunas implementaciones pueden definir que copiar un valor de puntero inválido cause un fallo en tiempo de ejecución generado por el sistema.
Punteros a objetos
Un puntero a objeto puede ser inicializado con el valor de retorno del operador de dirección aplicado a cualquier expresión de tipo objeto, incluyendo otro tipo puntero:
int n; int* np = &n; // puntero a int int* const* npp = &np; // puntero no constante a puntero constante a int no constante int a[2]; int (*ap)[2] = &a; // puntero a arreglo de int struct S { int n; }; S s = {1}; int* sp = &s.n; // puntero al int que es miembro de s
Los punteros pueden aparecer como operandos del operador de indirección incorporado (operador unario operator * ), que devuelve la expresión lvalue que identifica el objeto apuntado:
int n; int* p = &n; // puntero a n int& r = *p; // la referencia está ligada a la expresión lvalue que identifica n r = 7; // almacena el int 7 en n std::cout << *p; // conversión implícita lvalue-a-rvalue lee el valor de n
Los punteros a objetos de clase también pueden aparecer como operandos izquierdos de los operadores de acceso a miembros
operator->
y
operator->*
.
Debido a la conversión implícita de array-a-puntero , el puntero al primer elemento de un array puede ser inicializado con una expresión de tipo array:
int a[2]; int* p1 = a; // puntero al primer elemento a[0] (un int) del array a int b[6][3][8]; int (*p2)[3][8] = b; // puntero al primer elemento b[0] del array b, // que es un array de 3 arrays de 8 ints
Debido a la conversión implícita de derivado-a-base para punteros, un puntero a una clase base puede ser inicializado con la dirección de una clase derivada:
struct Base {}; struct Derived : Base {}; Derived d; Base* p = &d;
Si
Derived
es
polimórfico
, dicho puntero puede utilizarse para realizar
llamadas a funciones virtuales
.
Ciertos operadores de suma, resta , incremento y decremento están definidos para punteros a elementos de arrays: dichos punteros cumplen con los LegacyRandomAccessIterator requisitos y permiten que los algoritmos de la biblioteca de C++ funcionen con arrays sin procesar.
Operadores de comparación están definidos para punteros a objetos en algunas situaciones: dos punteros que representan la misma dirección se comparan iguales, dos valores de puntero nulo se comparan iguales, los punteros a elementos del mismo array se comparan igual que los índices del array de esos elementos, y los punteros a miembros de datos no estáticos con el mismo acceso de miembro se comparan en orden de declaración de esos miembros.
Muchas implementaciones también proporcionan un ordenamiento total estricto de punteros de origen aleatorio, por ejemplo, si se implementan como direcciones dentro de un espacio de direcciones virtual continuo. Aquellas implementaciones que no lo hacen (por ejemplo, donde no todos los bits del puntero forman parte de una dirección de memoria y deben ignorarse para la comparación, o se requiere un cálculo adicional o de otra manera el puntero y el entero no tienen una relación 1 a 1), proporcionan una especialización de std::less para punteros que tiene esa garantía. Esto hace posible utilizar todos los punteros de origen aleatorio como claves en contenedores asociativos estándar como std::set o std::map .
Punteros a void
El puntero a objeto de cualquier tipo puede ser
convertido implícitamente
a puntero a (posiblemente
cv-qualified
)
void
; el valor del puntero permanece inalterado. La conversión inversa, que requiere
static_cast
o
cast explícito
, produce el valor original del puntero:
int n = 1; int* p1 = &n; void* pv = p1; int* p2 = static_cast<int*>(pv); std::cout << *p2 << '\n'; // imprime 1
Si el puntero original está apuntando a un subobjeto de clase base dentro de un objeto de algún tipo polimórfico,
dynamic_cast
puede ser utilizado para obtener un
void
*
que está apuntando al objeto completo del tipo más derivado.
Los punteros a void tienen el mismo tamaño, representación y alineación que los punteros a char .
Los punteros a
void
se utilizan para pasar objetos de tipo desconocido, lo cual es común en interfaces de C:
std::malloc
retorna
void
*
,
std::qsort
espera una función de retorno proporcionada por el usuario que acepte dos argumentos
const
void
*
.
pthread_create
espera una función de retorno proporcionada por el usuario que acepte y retorne
void
*
. En todos los casos, es responsabilidad del llamante convertir el puntero al tipo correcto antes de usarlo.
Punteros a funciones
Un puntero a función puede inicializarse con la dirección de una función no miembro o una función miembro estática. Debido a la conversión implícita de función a puntero , el operador de dirección es opcional:
void f(int); void (*p1)(int) = &f; void (*p2)(int) = f; // igual que &f
A diferencia de las funciones o las referencias a funciones, los punteros a funciones son objetos y, por lo tanto, pueden almacenarse en arrays, copiarse, asignarse, etc.
void (a[10])(int); // Error: arreglo de funciones void (&a[10])(int); // Error: arreglo de referencias void (*a[10])(int); // OK: arreglo de punteros a funciones
Nota: las declaraciones que involucran punteros a funciones a menudo pueden simplificarse con alias de tipos:
using F = void(int); // alias de tipo nombrado para simplificar declaraciones F a[10]; // Error: array de funciones F& a[10]; // Error: array de referencias F* a[10]; // OK: array de punteros a funciones
Un puntero a función puede ser utilizado como el operando izquierdo del operador de llamada a función , esto invoca la función apuntada:
int f(int n) { std::cout << n << '\n'; return n * n; } int main() { int (*p)(int) = f; int x = p(7); }
Desreferenciar un puntero a función produce el valor-l que identifica la función apuntada:
int f(); int (*p)() = f; // el puntero p está apuntando a f int (&r)() = *p; // el lvalue que identifica f está vinculado a una referencia r(); // función f invocada mediante referencia lvalue (*p)(); // función f invocada mediante el lvalue de función p(); // función f invocada directamente mediante el puntero
Un puntero a función puede inicializarse desde un conjunto de sobrecargas que puede incluir funciones, especializaciones de plantillas de función y plantillas de función, si solo una sobrecarga coincide con el tipo del puntero (consulte dirección de una función sobrecargada para más detalles):
template<typename T> T f(T n) { return n; } double f(double n) { return n; } int main() { int (*p)(int) = f; // instancia y selecciona f<int> }
Operadores de comparación de igualdad están definidos para punteros a funciones (comparan igual si apuntan a la misma función).
Punteros a miembros
Punteros a miembros de datos
Un puntero a un objeto miembro no estático
m
que es miembro de la clase
C
puede inicializarse exactamente con la expresión
&
C
::
m
. Expresiones como
&
(
C
::
m
)
o
&
m
dentro de la función miembro de
C
no forman punteros a miembros.
Dicho puntero puede utilizarse como el operando derecho de los operadores de acceso a miembro a través de puntero operator. * y operator - > * :
El puntero a miembro de datos de una clase base accesible, no ambigua y no virtual puede ser convertido implícitamente a puntero al mismo miembro de datos de una clase derivada:
struct Base { int m; }; struct Derived : Base {}; int main() { int Base::* bp = &Base::m; int Derived::* dp = bp; Derived d; d.m = 1; std::cout << d.*dp << ' ' << d.*bp << '\n'; // imprime 1 1 }
La conversión en dirección opuesta, desde un puntero a miembro de datos de una clase derivada a un puntero a miembro de datos de una clase base no virtual no ambigua, está permitida con
static_cast
y
conversión explícita
, incluso si la clase base no tiene ese miembro (pero la clase más derivada sí lo tiene, cuando el puntero se utiliza para acceso):
El tipo apuntado por un puntero-a-miembro puede ser a su vez un puntero-a-miembro: los punteros a miembros pueden ser multinivel, y pueden tener calificadores cv diferentes en cada nivel. También se permiten combinaciones multinivel mixtas de punteros y punteros-a-miembros:
struct A { int m; // puntero constante a miembro no constante int A::* const p; }; int main() { // puntero no constante a miembro de datos que es un puntero constante a miembro no constante int A::* const A::* p1 = &A::p; const A a = {1, &A::m}; std::cout << a.*(a.*p1) << '\n'; // imprime 1 // puntero regular no constante a un puntero-constante-a-miembro int A::* const* p2 = &a.p; std::cout << a.**p2 << '\n'; // imprime 1 }
Punteros a funciones miembro
Un puntero a función miembro no estática
f
que es miembro de la clase
C
puede inicializarse exactamente con la expresión
&
C
::
f
. Expresiones como
&
(
C
::
f
)
o
&
f
dentro de la función miembro de
C
no forman punteros a funciones miembro.
Dicho puntero puede utilizarse como operando derecho de los operadores de acceso a miembro mediante puntero operator. * y operator - > * . La expresión resultante solo puede utilizarse como operando izquierdo de un operador de llamada a función:
struct C { void f(int n) { std::cout << n << '\n'; } }; int main() { void (C::* p)(int) = &C::f; // puntero a la función miembro f de la clase C C c; (c.*p)(1); // imprime 1 C* cp = &c; (cp->*p)(2); // imprime 2 }
El puntero a función miembro de una clase base puede ser
convertido implícitamente
a puntero a la misma función miembro de una clase derivada:
struct Base { void f(int n) { std::cout << n << '\n'; } }; struct Derived : Base {}; int main() { void (Base::* bp)(int) = &Base::f; void (Derived::* dp)(int) = bp; Derived d; (d.*dp)(1); (d.*bp)(2); }
La conversión en dirección opuesta, desde un puntero a función miembro de una clase derivada a un puntero a función miembro de una clase base no virtual no ambigua, está permitida con
static_cast
y
explicit cast
, incluso si la clase base no tiene esa función miembro (pero la clase más derivada sí la tiene, cuando el puntero se utiliza para acceso):
struct Base {}; struct Derived : Base { void f(int n) { std::cout << n << '\n'; } }; int main() { void (Derived::* dp)(int) = &Derived::f; void (Base::* bp)(int) = static_cast<void (Base::*)(int)>(dp); Derived d; (d.*bp)(1); // correcto: imprime 1 Base b; (b.*bp)(2); // comportamiento indefinido }
Los punteros a funciones miembro pueden utilizarse como callbacks o como objetos función, frecuentemente después de aplicar std::mem_fn o std::bind :
#include <algorithm> #include <cstddef> #include <functional> #include <iostream> #include <string> int main() { std::vector<std::string> v = {"a", "ab", "abc"}; std::vector<std::size_t> l; transform(v.begin(), v.end(), std::back_inserter(l), std::mem_fn(&std::string::size)); for (std::size_t n : l) std::cout << n << ' '; std::cout << '\n'; }
Salida:
1 2 3
Punteros nulos
Los punteros de cada tipo tienen un valor especial conocido como null pointer value de ese tipo. Un puntero cuyo valor es null no apunta a un objeto o a una función (el comportamiento de desreferenciar un puntero null es indefinido), y se compara igual a todos los punteros del mismo tipo cuyo valor también es null .
Una null pointer constant puede utilizarse para inicializar un puntero a null o para asignar el valor null a un puntero existente, es uno de los siguientes valores:
- Un literal entero con valor cero.
|
(desde C++11) |
La macro NULL también puede utilizarse, se expande a una constante de puntero nulo definida por la implementación.
Inicialización a cero y inicialización de valor también inicializan los punteros a sus valores nulos.
Los punteros nulos pueden utilizarse para indicar la ausencia de un objeto (por ejemplo, std::function::target() ), o como indicadores de otras condiciones de error (por ejemplo, dynamic_cast ). En general, una función que recibe un argumento de tipo puntero casi siempre necesita verificar si el valor es nulo y manejar ese caso de manera diferente (por ejemplo, la expresión delete no hace nada cuando se pasa un puntero nulo).
Punteros inválidos
Un valor de puntero p es válido en el contexto de una evaluación e si se satisface una de las siguientes condiciones:
- p es un valor de puntero nulo.
- p es un puntero a función.
- p es un puntero hacia o más allá del final de un objeto o , y e está en la duración de la región de almacenamiento para o .
Si un valor de puntero p se utiliza en una evaluación e , y p no es válido en el contexto de e , entonces:
- Si e es una indirection o una invocación de una deallocation function , el comportamiento es indefinido.
- De lo contrario, el comportamiento está definido por la implementación.
int* f() { int obj; int* local_ptr = new (&obj) int; *local_ptr = 1; // CORRECTO, la evaluación “*local_ptr” está // en la duración de almacenamiento de “obj” return local_ptr; } int* ptr = f(); // la duración de almacenamiento de “obj” ha finalizado, // por lo tanto “ptr” es un puntero inválido en los siguientes contextos int* copy = ptr; // comportamiento definido por la implementación *ptr = 2; // comportamiento indefinido: indirección de un puntero inválido delete ptr; // comportamiento indefinido: desasignación de almacenamiento desde un puntero inválido
Constancia
-
Si
cv
aparece antes de
*en la declaración del puntero, es parte de la secuencia de especificadores de declaración y se aplica al objeto apuntado. -
Si
cv
aparece después de
*en la declaración del puntero, es parte del declarador y se aplica al puntero que se está declarando.
| Sintaxis | Significado |
|---|---|
| const T * | puntero a objeto constante |
| T const * | puntero a objeto constante |
| T * const | puntero constante a objeto |
| const T * const | puntero constante a objeto constante |
| T const * const | puntero constante a objeto constante |
// pc es un puntero no constante a int constante // cpc es un puntero constante a int constante // ppc es un puntero no constante a puntero no constante a int constante const int ci = 10, *pc = &ci, *const cpc = pc, **ppc; // p es un puntero no constante a int no constante // cp es un puntero constante a int no constante int i, *p, *const cp = &i; i = ci; // correcto: valor de int constante copiado a int no constante *cp = ci; // correcto: int no constante (apuntado por puntero constante) puede modificarse pc++; // correcto: puntero no constante (a int constante) puede modificarse pc = cpc; // correcto: puntero no constante (a int constante) puede modificarse pc = p; // correcto: puntero no constante (a int constante) puede modificarse ppc = &pc; // correcto: dirección de puntero a int constante es puntero a puntero a int constante ci = 1; // error: int constante no puede modificarse ci++; // error: int constante no puede modificarse *pc = 2; // error: int constante apuntado no puede modificarse cp = &ci; // error: puntero constante (a int no constante) no puede modificarse cpc++; // error: puntero constante (a int constante) no puede modificarse p = pc; // error: puntero a int no constante no puede apuntar a int constante ppc = &p; // error: puntero a puntero a int constante no puede apuntar a // puntero a int no constante
En general, la conversión implícita de un puntero multinivel a otro sigue las reglas descritas en conversiones de calificación .
Tipo de puntero compuesto
Cuando un operando de un operador de comparación o cualquiera de los segundos y terceros operandos de un operador condicional es un puntero o puntero-a-miembro, se determina un tipo de puntero compuesto como el tipo común de estos operandos.
Dados dos operandos
p1
y
p2
que tienen tipos
T1
y
T2
, respectivamente,
p1
y
p2
solo pueden tener un tipo de puntero compuesto si se satisface alguna de las siguientes condiciones:
|
(hasta C++14) | ||
|
(desde C++14) |
El
tipo de puntero compuesto
C
de
p1
y
p2
se determina de la siguiente manera:
|
(hasta C++11) |
|
(desde C++11) |
- En caso contrario, si se cumplen todas las siguientes condiciones:
-
-
T1oT2es "puntero a cv1 void ". -
El otro tipo es "puntero a
cv2
T", dondeTes un tipo objeto o void .
-
-
Ces "puntero a cv12 void ", donde cv12 es la unión de cv1 y cv2 .
|
(desde C++17) |
- De lo contrario, si se cumplen todas las siguientes condiciones:
-
-
T1es "puntero aC1". -
T2es "puntero aC2". -
Uno de
C1yC2está reference-related al otro.
-
-
Ces-
el
qualification-combined type
de
T1yT2, siC1está reference-related aC2, o -
el qualification-combined type de
T2yT1, siC2está reference-related aC1.
-
el
qualification-combined type
de
|
(desde C++17) |
- De lo contrario, si se cumplen todas las siguientes condiciones:
-
-
T1es "puntero a miembro deC1de tipo no-funciónM1". -
T2es "puntero a miembro deC2de tipo no-funciónM2" -
M1yM2son iguales excepto por calificadores cv de nivel superior. -
Uno de
C1yC2está relacionado por referencia con el otro.
-
-
Ces-
el tipo combinado de calificación de
T2yT1, siC1está relacionado por referencia conC2, o -
el tipo combinado de calificación de
T1yT2, siC2está relacionado por referencia conC1.
-
el tipo combinado de calificación de
-
De lo contrario, si
T1yT2son tipos similares ,Ces el tipo combinado de calificaciones deT1yT2. -
De lo contrario,
p1
y
p2
no tienen un tipo de puntero compuesto, un programa que requiera la determinación de
Cpara tal tipo está mal formado.
using p = void*; using q = const int*; // La determinación del tipo de puntero compuesto de “p” y “q” // cae en el caso [“puntero a cv1 void” y “puntero a cv2 T”]: // cv1 = vacío, cv2 = const, cv12 = const // sustituir “cv12 = const” en “puntero a cv12 void”: // el tipo de puntero compuesto es “const void*” using pi = int**; using pci = const int**; // La determinación del tipo de puntero compuesto de “pi” y “pci” // cae en el caso [punteros a tipos similares “C1” y “C2”]: // C1 = int*, C2 = const int* // son tipos relacionados por referencia (en ambas direcciones) porque son similares // el tipo de puntero compuesto es el tipo combinado de calificaciones // de “p1” y “pc1” (o el de “pci” y “pi”): “const int**”
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 73 | C++98 |
un puntero a un objeto nunca se compara igual
a un puntero a una posición pasando el final de un array |
para punteros no nulos y no función,
comparar las direcciones que representan |
| CWG 903 | C++98 |
cualquier expresión constante entera que
se evaluara a 0 era una constante de puntero nulo |
limitado a literales enteros
con valor 0 |
| CWG 1438 | C++98 |
el comportamiento de usar un valor de puntero
inválido de cualquier manera era indefinido |
comportamientos distintos a la indirección y
pasar a funciones de desasignación están definidos por la implementación |
|
CWG 1512
( N3624 ) |
C++98 |
la regla del tipo de puntero compuesto era incompleta, y por lo tanto
no permitía la comparación entre int ** y const int ** |
se completó |
| CWG 2206 | C++98 |
un puntero a
void
y un puntero a
función tenían un tipo de puntero compuesto |
no tienen tal tipo |
| CWG 2381 | C++17 |
las conversiones de puntero a función no estaban permitidas
al determinar el tipo de puntero compuesto |
permitidas |
| CWG 2822 | C++98 |
alcanzar el final de la duración de una región
de almacenamiento podía invalidar valores de puntero |
la validez del puntero se basa
en el contexto de evaluación |
| CWG 2933 | C++98 | los punteros a funciones siempre eran inválidos | siempre son válidos |
Véase también
|
Documentación de C
para
Declaración de puntero
|