Pointer declaration
Объявляет переменную типа указатель или указатель-на-член.
Содержание |
Синтаксис
Объявление указателя — это любое простое объявление, чей декларатор имеет вид
*
attr
(необязательно)
cv
(необязательно)
declarator
|
(1) | ||||||||
nested-name-specifier
*
attr
(необязательно)
cv
(необязательно)
declarator
|
(2) | ||||||||
S
.
C
с типом, определяемым последовательностью спецификаторов объявления
S
.
| nested-name-specifier | - |
a
последовательность имен и операторов разрешения области видимости
::
|
| attr | - | (since C++11) список атрибутов |
| cv | - | квалификация const/volatile, которая применяется к объявляемому указателю (не к указываемому типу, чьи квалификаторы являются частью последовательности спецификаторов объявления) |
| declarator | - | любой декларатор |
Не существует указателей на ссылки и не существует указателей на битовые поля . Обычно упоминания "указателей" без уточнения не включают указатели на (нестатические) члены.
Указатели
Каждое значение указательного типа является одним из следующих:
- указатель на объект или функцию (в этом случае говорят, что указатель указывает на объект или функцию), или
- указатель за пределами объекта , или
- нулевое значение указателя для данного типа, или
- некорректное значение указателя .
Указатель, который указывает на объект, представляет адрес первого байта в памяти, занимаемого объектом. Указатель за пределами объекта представляет адрес первого байта в памяти после конца области памяти, занимаемой объектом.
Обратите внимание, что два указателя, представляющие один и тот же адрес, тем не менее могут иметь разные значения.
struct C { int x, y; } c; int* px = &c.x; // значение px - "указатель на c.x" int* pxe= px + 1; // значение pxe - "указатель за концом c.x" int* py = &c.y; // значение py - "указатель на c.y" assert(pxe == py); // == проверяет, представляют ли два указателя один адрес // может сработать или не сработать *pxe = 1; // неопределенное поведение, даже если утверждение не срабатывает
Разыменование через недействительное значение указателя и передача недействительного значения указателя функции освобождения памяти приводит к неопределенному поведению. Любое другое использование недействительного значения указателя имеет поведение, определяемое реализацией. Некоторые реализации могут определять, что копирование недействительного значения указателя вызывает системную ошибку времени выполнения.
Указатели на объекты
Указатель на объект может быть инициализирован возвращаемым значением оператора взятия адреса , применённого к любому выражению объектного типа, включая другой тип указателя:
int n; int* np = &n; // указатель на int int* const* npp = &np; // неконстантный указатель на константный указатель на неконстантный int int a[2]; int (*ap)[2] = &a; // указатель на массив int struct S { int n; }; S s = {1}; int* sp = &s.n; // указатель на int, который является членом s
Указатели могут выступать в качестве операндов встроенного оператора разыменования (унарный operator * ), который возвращает lvalue expression , идентифицирующий указываемый объект:
int n; int* p = &n; // указатель на n int& r = *p; // ссылка привязывается к lvalue-выражению, идентифицирующему n r = 7; // сохраняет целое число 7 в n std::cout << *p; // неявное преобразование lvalue-to-rvalue считывает значение из n
Указатели на объекты классов также могут выступать в качестве левых операндов операторов доступа к членам
operator->
и
operator->*
.
Из-за array-to-pointer неявного преобразования, указатель на первый элемент массива может быть инициализирован выражением типа массива:
int a[2]; int* p1 = a; // указатель на первый элемент a[0] (типа int) массива a int b[6][3][8]; int (*p2)[3][8] = b; // указатель на первый элемент b[0] массива b, // который является массивом из 3 массивов по 8 элементов типа int
Из-за неявного преобразования указателя derived-to-base указатель на базовый класс может быть инициализирован адресом производного класса:
struct Base {}; struct Derived : Base {}; Derived d; Base* p = &d;
Если
Derived
является
полиморфным
, такой указатель может использоваться для вызова
виртуальных функций
.
Определенные операторы сложения, вычитания , инкремента и декремента определены для указателей на элементы массивов: такие указатели удовлетворяют требованиям LegacyRandomAccessIterator и позволяют алгоритмам стандартной библиотеки C++ algorithms работать с сырыми массивами.
Операторы сравнения определены для указателей на объекты в некоторых ситуациях: два указателя, представляющие один и тот же адрес, сравниваются как равные; два значения нулевых указателей сравниваются как равные; указатели на элементы одного и того же массива сравниваются так же, как индексы этих элементов в массиве; а указатели на нестатические члены данных с одинаковым доступом к членам сравниваются в порядке объявления этих членов.
Многие реализации также предоставляют строгий полный порядок для указателей произвольного происхождения, например, если они реализованы как адреса в непрерывном виртуальном адресном пространстве. Те реализации, которые этого не делают (например, когда не все биты указателя являются частью адреса памяти и должны игнорироваться при сравнении, или требуется дополнительное вычисление, или иным образом указатель и целое число не находятся в отношении 1 к 1), предоставляют специализацию std::less для указателей, которая дает эту гарантию. Это позволяет использовать все указатели произвольного происхождения в качестве ключей в стандартных ассоциативных контейнерах, таких как std::set или std::map .
Указатели на void
Указатель на объект любого типа может быть
неявно преобразован
в указатель на (возможно
cv-квалифицированный
)
void
; значение указателя остается неизменным. Обратное преобразование, которое требует
static_cast
или
явного приведения
, восстанавливает исходное значение указателя:
int n = 1; int* p1 = &n; void* pv = p1; int* p2 = static_cast<int*>(pv); std::cout << *p2 << '\n'; // выводит 1
Если исходный указатель указывает на подобъект базового класса внутри объекта полиморфного типа,
dynamic_cast
может быть использован для получения
void
*
, указывающего на полный объект наиболее производного типа.
Указатели на void имеют тот же размер, представление и выравнивание, что и указатели на char .
Указатели на
void
используются для передачи объектов неизвестного типа, что часто встречается в C-интерфейсах:
std::malloc
возвращает
void
*
,
std::qsort
ожидает пользовательскую функцию обратного вызова, принимающую два аргумента типа
const
void
*
.
pthread_create
ожидает пользовательскую функцию обратного вызова, которая принимает и возвращает
void
*
. Во всех случаях ответственность за приведение указателя к правильному типу перед использованием лежит на вызывающей стороне.
Указатели на функции
Указатель на функцию может быть инициализирован адресом функции-не-члена или статической функции-члена. Из-за неявного преобразования функции в указатель оператор взятия адреса является опциональным:
void f(int); void (*p1)(int) = &f; void (*p2)(int) = f; // то же самое, что &f
В отличие от функций или ссылок на функции, указатели на функции являются объектами и, следовательно, могут храниться в массивах, копироваться, присваиваться и т.д.
void (a[10])(int); // Ошибка: массив функций void (&a[10])(int); // Ошибка: массив ссылок void (*a[10])(int); // OK: массив указателей на функции
Примечание: объявления с указателями на функции часто можно упростить с помощью псевдонимов типов:
using F = void(int); // именованный псевдоним типа для упрощения объявлений F a[10]; // Ошибка: массив функций F& a[10]; // Ошибка: массив ссылок F* a[10]; // OK: массив указателей на функции
Указатель на функцию может использоваться в качестве левого операнда оператора вызова функции , это вызывает указанную функцию:
int f(int n) { std::cout << n << '\n'; return n * n; } int main() { int (*p)(int) = f; int x = p(7); }
` и `` оставлен без изменений, как и требовалось в инструкциях. HTML-разметка и атрибуты также сохранены в оригинальном виде.
Разыменование указателя на функцию даёт lvalue, идентифицирующее указываемую функцию:
int f(); int (*p)() = f; // указатель p указывает на f int (&r)() = *p; // lvalue, идентифицирующее f, связывается со ссылкой r(); // функция f вызывается через lvalue-ссылку (*p)(); // функция f вызывается через lvalue функции p(); // функция f вызывается напрямую через указатель
Указатель на функцию может быть инициализирован из набора перегруженных функций, который может включать функции, специализации шаблонов функций и шаблоны функций, если только одна перегрузка соответствует типу указателя (см. взятие адреса перегруженной функции для более подробной информации):
template<typename T> T f(T n) { return n; } double f(double n) { return n; } int main() { int (*p)(int) = f; // создает экземпляр и выбирает f<int> }
Операторы сравнения на равенство определены для указателей на функции (они сравниваются как равные, если указывают на одну и ту же функцию).
Указатели на члены
Указатели на члены данных
Указатель на нестатический член-объект
m
, который является членом класса
C
, может быть инициализирован выражением
&
C
::
m
точно. Выражения такие как
&
(
C
::
m
)
или
&
m
внутри функции-члена класса
C
не формируют указатели на члены.
Такой указатель может использоваться в качестве правого операнда для операторов доступа к члену по указателю operator. * и operator - > * :
Указатель на член данных доступного однозначного невиртуального базового класса может быть неявно преобразован в указатель на тот же член данных производного класса:
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'; // выводит 1 1 }
Преобразование в обратном направлении, из указателя на член данных производного класса в указатель на член данных однозначного невиртуального базового класса, разрешено с помощью
static_cast
и
явного приведения
, даже если базовый класс не имеет этого члена (но самый производный класс имеет, когда указатель используется для доступа):
Тип, на который указывает указатель на член, сам может быть указателем на член: указатели на члены могут быть многоуровневыми и могут иметь разные cv-квалификаторы на каждом уровне. Также допускаются смешанные многоуровневые комбинации указателей и указателей на члены:
struct A { int m; // константный указатель на неконстантный член int A::* const p; }; int main() { // неконстантный указатель на член данных, который является константным указателем на неконстантный член int A::* const A::* p1 = &A::p; const A a = {1, &A::m}; std::cout << a.*(a.*p1) << '\n'; // выводит 1 // обычный неконстантный указатель на константный указатель-на-член int A::* const* p2 = &a.p; std::cout << a.**p2 << '\n'; // выводит 1 }
Указатели на функции-члены
Указатель на нестатическую функцию-член
f
которая является членом класса
C
может быть инициализирован выражением
&
C
::
f
точно. Выражения такие как
&
(
C
::
f
)
или
&
f
внутри функции-члена класса
C
не формируют указатели на функции-члены.
Такой указатель может использоваться в качестве правого операнда операторов доступа к члену по указателю operator. * и operator - > * . Результирующее выражение может использоваться только в качестве левого операнда оператора вызова функции:
struct C { void f(int n) { std::cout << n << '\n'; } }; int main() { void (C::* p)(int) = &C::f; // указатель на функцию-член f класса C C c; (c.*p)(1); // выводит 1 C* cp = &c; (cp->*p)(2); // выводит 2 }
Указатель на функцию-член базового класса может быть
неявно преобразован
в указатель на ту же функцию-член производного класса:
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); }
` и `` оставлен без изменений, как и требовалось. HTML-разметка и атрибуты также сохранены в оригинальном виде.
Преобразование в обратном направлении, из указателя на член-функцию производного класса в указатель на член-функцию однозначного невиртуального базового класса, разрешено с помощью
static_cast
и
явного приведения
, даже если базовый класс не имеет этой функции-члена (но самый производный класс имеет, когда указатель используется для доступа):
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); // корректно: выводит 1 Base b; (b.* bp)(2); // неопределенное поведение }
Указатели на функции-члены могут использоваться как колбэки или как функциональные объекты, часто после применения std::mem_fn или 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'; }
Вывод:
1 2 3
Нулевые указатели
Указатели любого типа имеют специальное значение, известное как нулевое значение указателя этого типа. Указатель со значением null не указывает на объект или функцию (поведение при разыменовании нулевого указателя не определено) и сравнивается равным со всеми указателями того же типа, значение которых также является нулевым .
Нулевая константа указателя может использоваться для инициализации указателя значением null или присвоения нулевого значения существующему указателю, это одно из следующих значений:
- Целочисленный литерал со значением ноль.
|
(since C++11) |
Макрос NULL также может использоваться, он раскрывается в определяемую реализацией константу нулевого указателя.
Zero-initialization и value-initialization также инициализируют указатели их нулевыми значениями.
Нулевые указатели могут использоваться для обозначения отсутствия объекта (например, std::function::target() ), или как индикаторы других ошибочных состояний (например, dynamic_cast ). В общем случае, функция, получающая аргумент-указатель, почти всегда должна проверять, является ли значение нулевым, и обрабатывать этот случай отдельно (например, delete expression не выполняет никаких действий при передаче нулевого указателя).
Некорректные указатели
Значение указателя p является допустимым в контексте вычисления e если удовлетворяется одно из следующих условий:
- p является нулевым указателем.
- p является указателем на функцию.
- p является указателем на объект или за его пределами o , и e находится в периоде времени хранения для o .
Если значение указателя p используется в вычислении e , и p не является допустимым в контексте e , тогда:
- Если e является операцией косвенного обращения или вызовом функции освобождения памяти , поведение не определено.
- В противном случае поведение определяется реализацией.
int* f() { int obj; int* local_ptr = new (&obj) int; *local_ptr = 1; // OK, вычисление "*local_ptr" находится // в периоде хранения "obj" return local_ptr; } int* ptr = f(); // период хранения "obj" завершился, // поэтому "ptr" является недействительным указателем в следующих контекстах int* copy = ptr; // поведение, определяемое реализацией *ptr = 2; // неопределенное поведение: разыменование недействительного указателя delete ptr; // неопределенное поведение: освобождение памяти из недействительного указателя
Константность
-
Если
cv
указывается перед
*в объявлении указателя, это является частью последовательности спецификаторов объявления и применяется к указываемому объекту. -
Если
cv
указывается после
*в объявлении указателя, это является частью декларатора и применяется к объявляемому указателю.
| Синтаксис | Значение |
|---|---|
| const T * | указатель на константный объект |
| T const * | указатель на константный объект |
| T * const | константный указатель на объект |
| const T * const | константный указатель на константный объект |
| T const * const | константный указатель на константный объект |
// pc - неконстантный указатель на константный int // cpc - константный указатель на константный int // ppc - неконстантный указатель на неконстантный указатель на константный int const int ci = 10, *pc = &ci, *const cpc = pc, **ppc; // p - неконстантный указатель на неконстантный int // cp - константный указатель на неконстантный int int i, *p, *const cp = &i; i = ci; // корректно: значение константного int копируется в неконстантный int *cp = ci; // корректно: неконстантный int (на который указывает константный указатель) может быть изменён pc++; // корректно: неконстантный указатель (на константный int) может быть изменён pc = cpc; // корректно: неконстантный указатель (на константный int) может быть изменён pc = p; // корректно: неконстантный указатель (на константный int) может быть изменён ppc = &pc; // корректно: адрес указателя на константный int является указателем на указатель на константный int ci = 1; // ошибка: константный int не может быть изменён ci++; // ошибка: константный int не может быть изменён *pc = 2; // ошибка: указываемый константный int не может быть изменён cp = &ci; // ошибка: константный указатель (на неконстантный int) не может быть изменён cpc++; // ошибка: константный указатель (на константный int) не может быть изменён p = pc; // ошибка: указатель на неконстантный int не может указывать на константный int ppc = &p; // ошибка: указатель на указатель на константный int не может указывать на // указатель на неконстантный int
В общем случае неявное преобразование из одного многоуровневого указателя в другое следует правилам, описанным в квалификационных преобразованиях .
Составной тип указателя
Когда операнд оператора сравнения или любой из второго и третьего операндов условного оператора является указателем или указателем-на-член, составной тип указателя определяется как общий тип этих операндов.
Даны два операнда
p1
и
p2
с типами
T1
и
T2
соответственно,
p1
и
p2
могут иметь составной тип указателя только при выполнении любого из следующих условий:
|
(until C++14) | ||
|
(since C++14) |
Составной тип указателя
C
для
p1
и
p2
определяется следующим образом:
|
(до C++11) |
|
(начиная с C++11) |
- В противном случае, если выполняются все следующие условия:
-
-
T1илиT2является "указателем на cv1 void ". -
Другой тип является "указателем на
cv2
T", гдеTявляется объектным типом или void .
-
-
Cявляется "указателем на cv12 void ", где cv12 является объединением cv1 и cv2 .
|
(начиная с C++17) |
- В противном случае, если выполняются все следующие условия:
-
-
T1является "указателем наC1". -
T2является "указателем наC2". -
Один из
C1иC2является reference-related по отношению к другому.
-
-
Cявляется-
qualification-combined type
для
T1иT2, еслиC1является reference-related кC2, или -
qualification-combined type для
T2иT1, еслиC2является reference-related кC1.
-
qualification-combined type
для
|
(начиная с C++17) |
- В противном случае, если выполняются все следующие условия:
-
-
T1является «указателем на членC1не функционального типаM1». -
T2является «указателем на членC2не функционального типаM2» -
M1иM2идентичны за исключением cv-квалификаций верхнего уровня. -
Один из
C1иC2является ссылочно-связанным с другим.
-
-
Cявляется-
типом с объединённой квалификацией
T2иT1, еслиC1является ссылочно-связанным сC2, или -
типом с объединённой квалификацией
T1иT2, еслиC2является ссылочно-связанным сC1.
-
типом с объединённой квалификацией
-
В противном случае, если
T1иT2являются схожими типами ,Cявляется типом с объединённой квалификациейT1иT2. -
В противном случае,
p1
и
p2
не имеют составного типа указателя, и программа, требующая определения
Cкак такого типа, является некорректной.
using p = void*; using q = const int*; // Определение составного типа указателя для "p" и "q" // подпадает под случай ["указатель на cv1 void" и "указатель на cv2 T"]: // cv1 = пусто, cv2 = const, cv12 = const // подставляем "cv12 = const" в "указатель на cv12 void": // составной тип указателя - "const void*" using pi = int**; using pci = const int**; // Определение составного типа указателя для "pi" и "pci" // подпадает под случай [указатели на схожие типы "C1" и "C2"]: // C1 = int*, C2 = const int* // это ссылочно-родственные типы (в обоих направлениях), поскольку они схожи // составной тип указателя - это тип с объединенной квалификацией // "p1" и "pc1" (или "pci" и "pi"): "const int**"
Отчеты о дефектах
Следующие отчеты об изменениях в поведении, содержащие описания дефектов, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Applied to | Behavior as published | Correct behavior |
|---|---|---|---|
| CWG 73 | C++98 |
указатель на объект никогда не сравнивается равным
с указателем на элемент за концом массива |
для ненулевых и не функциональных указателей,
сравниваются адреса, которые они представляют |
| CWG 903 | C++98 |
любое целочисленное константное выражение,
вычисляемое в 0, было константой нулевого указателя |
ограничено целочисленными
литералами со значением 0 |
| CWG 1438 | C++98 |
поведение использования невалидного значения указателя
любым способом было неопределенным |
поведения, отличные от разыменования и
передачи функциям освобождения памяти, являются определяемыми реализацией |
|
CWG 1512
( N3624 ) |
C++98 |
правило составного типа указателя было неполным и, таким образом,
не позволяло сравнение между int ** и const int ** |
сделано полным |
| CWG 2206 | C++98 |
указатель на
void
и указатель на функцию
имели составной тип указателя |
они не имеют такого типа |
| CWG 2381 | C++17 |
преобразования указателей на функции не разрешались
при определении составного типа указателя |
разрешены |
| CWG 2822 | C++98 |
достижение конца времени жизни области
памяти могло инвалидировать значения указателей |
валидность указателей основывается
на контексте вычисления |
| CWG 2933 | C++98 | указатели на функции всегда были невалидными | они всегда валидны |
Смотрите также
|
Документация C
для
Объявление указателей
|