Spec-Zone.ru › C++

Объявление указателей

Объявляет переменную типа указателя или указателя на член.

Синтаксис

Объявление указателя — это любое простое объявление, у которого определяющая часть имеет вид

* attr (необязательно) cv (необязательно) определяющая_часть (1)
спецификатор_имени * attr (необязательно) cv (необязательно) определяющая_часть (2)
1) Определяющая часть указателя: объявление S* D; объявляет D как указатель на тип, определяемый последовательностью_спецификаторов_типов S.
2) Указатель на член: объявление S C::* D; объявляет D как указатель на нестатический член C типа, определяемого последовательностью_спецификаторов_типов S.
спецификатор_имени - последовательность имён и операторов разрешения области видимости ::
attr - (с C++11) список атрибутов
cv - квалификаторы const/volatile, которые применяются к указателю, который объявляется (а не к типу, на который указывает указатель, квалификаторы которого являются частью последовательности_спецификаторов_типов)
определяющая_часть - любая определяющая часть, кроме определяющей части ссылки (нет указателей на ссылки). Это может быть и другая определяющая часть указателя (разрешены указатели на указатели)

Нет указателей на ссылки и нет указателей на битовые поля. Обычно упоминания об «указателях» без уточнений не включают указатели на (нестатические) члены.

Указатели

Любое значение типа указателя — это одно из следующего:

  • указатель на объект или функцию (в этом случае говорят, что указатель указывает на объект или функцию), или
  • указатель за концом объекта, или
  • значение «нулевого» указателя для данного типа, или
  • неверное значение указателя.

Указатель, указывающий на объект, представляет адрес первого байта в памяти, занимаемого объектом. Указатель за концом объекта представляет адрес первого байта в памяти после конца хранилища, занимаемого объектом.

Обратите внимание, что два указателя, представляющие один и тот же адрес, могут тем не менее иметь разные значения.

struct C
{
    int x, y;
} c;
 
int* px = &c.x;   // value of px is "pointer to c.x"
int* pxe= px + 1; // value of pxe is "pointer past the end of c.x"
int* py = &c.y;   // value of py is "pointer to c.y"
 
assert(pxe == py); // == tests if two pointers represent the same address
                   // may or may not fire
 
*pxe = 1; // undefined behavior even if the assertion does not fire

Обращение к неверному значению указателя и передача неверного значения указателя функции освобождения имеют неопределённое поведение. Любое другое использование неверного значения указателя имеет поведение, определяемое реализацией. Некоторые реализации могут определить, что копирование неверного значения указателя вызывает системную ошибку во время выполнения.

Указатели на объекты

Указатель на объект можно инициализировать возвращаемым значением оператора адресации, применённого к любому выражению типа объекта, включая другой тип указателя:

int n;
int* np = &n;          // pointer to int
int* const* npp = &np; // non-const pointer to const pointer to non-const int
 
int a[2];
int (*ap)[2] = &a;     // pointer to array of int
 
struct S { int n; };
 
S s = {1};
int* sp = &s.n;        // pointer to the int that is a member of s

Указатели могут выступать в качестве операндов встроенного оператора косвенного обращения (унарный operator*), который возвращает выражение lvalue, определяющее объект, на который указывает указатель:

int n;
int* p = &n;     // pointer to n
int& r = *p;     // reference is bound to the lvalue expression that identifies n
r = 7;           // stores the int 7 in n
std::cout << *p; // lvalue-to-rvalue implicit conversion reads the value from n

Указатели на объекты классов также могут выступать левыми операндами операторов доступа к членам operator-> и operator->*.

Из-за неявного преобразования «массив в указатель», указатель на первый элемент массива может быть инициализирован выражением типа массива:

int a[2];
int* p1 = a; // pointer to the first element a[0] (an int) of the array a
 
int b[6][3][8];
int (*p2)[3][8] = b; // pointer to the first element b[0] of the array b,
                     // which is an array of 3 arrays of 8 ints

Из-за неявного преобразования «производный в базовый» для указателей, указатель на базовый класс может быть инициализирован адресом производного класса:

struct Base {};
struct Derived : Base {};
 
Derived d;
Base* p = &d;

Если Derived является полиморфным, такой указатель может использоваться для вызова виртуальных функций.

Определены некоторые операторы сложения, вычитания, инкремента и декремента для указателей на элементы массивов: такие указатели удовлетворяют требованиям LegacyRandomAccessIterator и позволяют библиотечным функциям C++ работать с сырыми массивами.

Операторы сравнения определены для указателей на объекты в некоторых ситуациях: два указателя, представляющие один и тот же адрес, сравниваются как равные, два нулевых значения указателей сравниваются как равные, указатели на элементы одного и того же массива сравниваются так же, как индексы элементов в массиве, и указатели на нестатические данные члены с одним и тем же доступом к членам сравниваются в порядке объявления этих членов.

Многие реализации также предоставляют строгое полное упорядочение указателей произвольного происхождения, например, если они реализованы как адреса в непрерывном виртуальном адресном пространстве. Реализации, которые этого не делают (например, где не все биты указателя являются частью адреса памяти и должны быть проигнорированы при сравнении, или требуется дополнительное вычисление, или в ином случае указатель и целое число не являются взаимно однозначным соответствием), предоставляют специализацию std::less для указателей, которая гарантирует это свойство. Это позволяет использовать все указатели произвольного происхождения в качестве ключей в стандартных ассоциативных контейнерах, таких как std::set или std::map.

Указатели на void

Указатель на объект любого типа может быть неявно преобразован в указатель на (возможно квалифицированный) void; значение указателя при этом не изменяется. Обратное преобразование, которое требует static_cast или явного преобразования, возвращает исходное значение указателя:

int n = 1;
int* p1 = &n;
void* pv = p1;
int* p2 = static_cast<int*>(pv);
std::cout << *p2 << '\n'; // prints 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; // same as &f

В отличие от функций или ссылок на функции, указатели на функции являются объектами и, следовательно, могут храниться в массивах, копироваться, присваиваться и т. д.

void (a[10])(int);  // Error: array of functions
void (&a[10])(int); // Error: array of references
void (*a[10])(int); // OK: array of pointers to functions

Примечание: объявления, включающие указатели на функции, часто можно упростить с помощью псевдонимов типов:

using F = void(int); // named type alias to simplify declarations
F a[10];  // Error: array of functions
F& a[10]; // Error: array of references
F* a[10]; // OK: array of pointers to functions

Указатель на функцию может использоваться в качестве левого операнда оператора вызова функции, что вызывает указанную функцию:

int f(int n)
{
    std::cout << n << '\n';
    return n * n;
}
 
int main()
{
    int (*p)(int) = f;
    int x = p(7);
}

Разыменование указателя на функцию возвращает выражение lvalue, определяющее указанную функцию:

int f();
int (*p)() = f;  // pointer p is pointing to f
int (&r)() = *p; // the lvalue that identifies f is bound to a reference
r();             // function f invoked through lvalue reference
(*p)();          // function f invoked through the function lvalue
p();             // function f invoked directly through the pointer

Указатель на функцию может быть инициализирован набором перегрузок, который может включать функции, специализации шаблонов функций и шаблоны функций, если только одна перегрузка соответствует типу указателя (см. адрес перегруженной функции для получения дополнительных подробностей):

template<typename T>
T f(T n) { return n; }
 
double f(double n) { return n; }
 
int main()
{
    int (*p)(int) = f; // instantiates and selects f<int>
}

Операторы сравнения на равенство определены для указателей на функции (они сравниваются как равные, если указывают на одну и ту же функцию).

Указатели на члены

Указатели на данные члены

Указатель на нестатический член-объект m, который является членом класса C, может быть инициализирован выражением &C::m точно. Выражения, такие как &(C::m) или &m внутри функции-члена C не образуют указателей на члены.

Такой указатель может быть использован в качестве правого операнда операторов доступа к членам-указателям операторов доступа к членам-указателям operator.* и operator->*:

struct C { int m; };
 
int main()
{
    int C::* p = &C::m;          // pointer to data member m of class C
    C c = {7};
    std::cout << c.*p << '\n';   // prints 7
    C* cp = &c;
    cp->m = 10;
    std::cout << cp->*p << '\n'; // prints 10
}

Указатель на член данных доступного однозначного невиртуального базового класса может быть неявно преобразован в указатель на тот же член данных производного класса:

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'; // prints 1 1
}

Преобразование в обратном направлении, от указателя на член данных производного класса к указателю на член данных однозначного невиртуального базового класса, разрешено с помощью static_cast и явного преобразования, даже если базовый класс не имеет этого члена (но наиболее производный класс имеет, когда указатель используется для доступа):

struct Base {};
struct Derived : Base { int m; };
 
int main()
{
    int Derived::* dp = &Derived::m;
    int Base::* bp = static_cast<int Base::*>(dp);
 
    Derived d;
    d.m = 7;
    std::cout << d.*bp << '\n'; // okay: prints 7
 
    Base b;
    std::cout << b.*bp << '\n'; // undefined behavior
}

Тип указываемого объекта указателя на член может быть указателем на член: указатели на члены могут быть многоуровневыми и иметь разные квалификаторы cv на каждом уровне. Также разрешены смешанные многоуровневые комбинации указателей и указателей на члены:

struct A
{
    int m;
    // const pointer to non-const member
    int A::* const p;
};
 
int main()
{
    // non-const pointer to data member which is a const pointer to non-const member
    int A::* const A::* p1 = &A::p;
 
    const A a = {1, &A::m};
    std::cout << a.*(a.*p1) << '\n'; // prints 1
 
    // regular non-const pointer to a const pointer-to-member
    int A::* const* p2 = &a.p;
    std::cout << a.**p2 << '\n'; // prints 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; // pointer to member function f of class C
    C c;
    (c.*p)(1);                  // prints 1
    C* cp = &c;
    (cp->*p)(2);                // prints 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);
}

Преобразование в обратном направлении, от указателя на функцию-член производного класса к указателю на функцию-член однозначного невиртуального базового класса, разрешено с помощью 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); // okay: prints 1
 
    Base b;
    (b.*bp)(2); // undefined behavior
}

Указатели на функции-члены могут быть использованы в качестве обратных вызовов или функций-объектов, часто после применения 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

Нулевые указатели

Указатели любого типа имеют специальное значение, известное как нулевое значение указателя этого типа. Указатель, значение которого равно нулю, не указывает на объект или функцию (поведение разыменования нулевого указателя не определено), и сравнивается с любыми указателями того же типа, значение которых также нулевое.

Нулевая константа указателя может быть использована для инициализации указателя нулём или для присвоения нулевого значения существующему указателю, это одно из следующих значений:

  • Целочисленная литеральная константа со значением ноль.
  • Значение типа std::nullptr_t (обычно nullptr).
(с C++11)

Также может быть использован макрос NULL, он расширяется до определяемой реализацией нулевой константы указателя.

Инициализация нулём и инициализация значением также инициализируют указатели их нулевыми значениями.

Нулевые указатели могут использоваться для обозначения отсутствия объекта (например, std::function::target()) или в качестве индикаторов других ошибок (например, dynamic_cast). В общем случае функция, принимающая аргумент указателя, почти всегда должна проверять, является ли значение нулевым, и обрабатывать этот случай по-другому (например, выражение delete ничего не делает, когда передаётся нулевой указатель).

Постоянство

  • Если cv появляется перед * в объявлении указателя, он является частью decl-specifier-seq и относится к указываемому объекту.
  • Если cv появляется после * в объявлении указателя, он является частью declarator и относится к объявляемому указателю.
Синтаксис Значение
const T* указатель на постоянный объект
T const* указатель на постоянный объект
T* const постоянный указатель на объект
const T* const постоянный указатель на постоянный объект
T const* const постоянный указатель на постоянный объект
// pc is a non-const pointer to const int
// cpc is a const pointer to const int
// ppc is a non-const pointer to non-const pointer to const int
const int ci = 10, *pc = &ci, *const cpc = pc, **ppc;
// p is a non-const pointer to non-const int
// cp is a const pointer to non-const int
int i, *p, *const cp = &i;
 
i = ci;    // okay: value of const int copied into non-const int
*cp = ci;  // okay: non-const int (pointed-to by const pointer) can be changed
pc++;      // okay: non-const pointer (to const int) can be changed
pc = cpc;  // okay: non-const pointer (to const int) can be changed
pc = p;    // okay: non-const pointer (to const int) can be changed
ppc = &pc; // okay: address of pointer to const int is pointer to pointer to const int
 
ci = 1;    // error: const int cannot be changed
ci++;      // error: const int cannot be changed
*pc = 2;   // error: pointed-to const int cannot be changed
cp = &ci;  // error: const pointer (to non-const int) cannot be changed
cpc++;     // error: const pointer (to const int) cannot be changed
p = pc;    // error: pointer to non-const int cannot point to const int
ppc = &p;  // error: pointer to pointer to const int cannot point to
           // pointer to non-const int

В общем случае неявное преобразование одного многоуровневого указателя в другой следует правилам, описанным в преобразованиях квалификаций.

Составной тип указателя

Когда операнд оператора сравнения или любой из второго и третьего операндов условного оператора является указателем или указателем на член, определяется составной тип указателя как общий тип этих операндов.

Для двух операндов p1 и p2, имеющих типы T1 и T2 соответственно, p1 и p2 могут иметь составной тип указателя только в том случае, если выполняется одно из следующих условий:

  • p1 и p2 являются указателями.
  • Один из p1 и p2 является указателем, а другой операнд — константой нулевого указателя.
  • p1 и p2 являются константами нулевого указателя, и по крайней мере один из T1 и T2 является нецелочисленным типом.
(с C++11)
(до C++14)
  • По крайней мере один из T1 и T2 является типом указателя, типом указателя на член или std::nullptr_t.
(с C++14)

Составной тип указателя C для p1 и p2 определяется следующим образом:

  • Если p1 является нулевой константой указателя, C является T2.
  • В противном случае, если p2 является нулевой константой указателя, C является T1.
(до C++11)
  • Если p1 и p2 являются константами нулевого указателя, C является std::nullptr_t.
  • В противном случае, если p1 является нулевой константой указателя, C является T2.
  • В противном случае, если p2 является нулевой константой указателя, C является T1.
(с C++11)
  • В противном случае, если выполнены все следующие условия:
  • T1 или T2 является “указателем на cv1 void”.
  • Другой тип является “указателем на cv2 T”, где T является типом объекта или void.
C является “указателем на cv12 void”, где cv12 — объединение cv1 и cv2.
  • В противном случае, если выполнены все следующие условия:
  • T1 или T2 является “указателем на тип функции F1”.
  • Другой тип является “указателем на тип функции F2 с noexcept”.
  • F1 и F2 одинаковы за исключением noexcept.
C является “указателем на F1”.
(с C++17)
  • В противном случае, если выполнены все следующие условия:
  • T1 — это «указатель на C1».
  • T2 — это «указатель на C2».
  • Один из C1 и C2 является связанным с ссылкой к другому.
C — это
  • комбинированный тип квалификаций T1 и T2, если C1 связан с ссылкой на C2, или
  • комбинированный тип квалификаций T2 и T1, если C2 связан с ссылкой на C1.
  • В противном случае, если выполняются все следующие условия:
  • T1 или T2 — это «указатель на член C1 типа функции F1».
  • Другой тип — это «указатель на член C2 типа функции noexcept F2».
  • Один из C1 и C2 связан с ссылкой на другой.
  • F1 и F2 одинаковы за исключением noexcept.
C — это
  • «указатель на член C2 типа F1», если C1 связан с ссылкой на C2, или
  • «указатель на член C1 типа F1», если C2 связан с ссылкой на C1.
(с 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*;
// The determination of the composite pointer type of “p” and “q”
// falls into the [“pointer to cv1 void” and “pointer to cv2 T”] case:
// cv1 = empty, cv2 = const, cv12 = const
// substitute “cv12 = const” into “pointer to cv12 void”:
// the composite pointer type is “const void*”
 
using pi = int**;
using pci = const int**;
// The determination of the composite pointer type of “pi” and “pci”
// falls into the [pointers to similar types “C1” and “C2”] case:
// C1 = int*, C2 = const int*
// they are reference-related types (in both direction) because they are similar
// the composite pointer type is the qualification-combined type
// of “p1” and “pc1” (or that of “pci” and “pi”): “const void* const *”

Отчеты об ошибках

Следующие отчеты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Применено к Поведение, как опубликовано Правильное поведение
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 преобразования указателей на функции не были разрешены
при определении составного типа указателя
разрешены

См. также

C документация для Объявление указателя

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/pointer

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API