Объявление указателя
Объявляет переменную типа указатель или указатель на элемент.
Содержание |
[править]Синтаксис
Объявление указателя это любое простое объявление, декларатор которого имеет вид
* атрибуты (необязательно)cv (необязательно)декларатор | (1) | ||||||||
спецификатор-вложенного-имени* аттрибуты (необязательно)cv (необязательно)декларатор | (2) | ||||||||
D
как указатель на тип, определённый последовательностью-спецификаторов-объявленияS
.D
как указатель на нестатический элемент C
типа, определённого последовательностью-спецификаторов-объявленияS
.спецификатор-вложенного-имени | — | последовательность имён и операторов разрешения области видимости :: |
атрибуты | — | (начиная с C++11) список атрибутов |
cv | — | квалификация const/volatile, которая применяется к объявляемому указателю (не к типу, на который указывает указатель, чьи квалификации являются частью последовательности-спецификаторов-объявления) |
декларатор | — | любой декларатор, кроме декларатора ссылки (указателей на ссылки не существует). Это может быть другой декларатор указателя (допускается указатель на указатель) |
Нет указателей на ссылки и нет указателей на битовые поля. Как правило, упоминания об "указателях" без уточнения не включают указатели на (нестатические) элементы.
[править]Указатели
Каждое значение типа указателя является одним из следующего:
- указатель на объект или функцию (в этом случае говорят, что указатель указывает на объект или функцию), или
- указатель за конец объекта, или
- нулевое значение указателя для этого типа или
- недопустимое значение указателя.
Указатель, указывающий на объект, представляет адрес первого байта в памяти, занимаемой объектом. Указатель за конец объекта представляет адрес первого байта в памяти после окончания памяти, занимаемой объектом.
Обратите внимание, что два указателя, представляющие один и тот же адрес, тем не менее могут иметь разные значения.
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;// указатель на intint*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, идентифицирующее объект, на который указывает указатель:
int n;int* p =&n;// указатель на nint& r =*p;// ссылка связана с выражением lvalue, которое идентифицирует n r =7;// сохраняет int 7 в nstd::cout<<*p;// неявное преобразование lvalue-в-rvalue считывает значение из n
Указатели на объекты класса также могут появляться как левые операнды операторов доступа к элементам operator->
и operator->*
.
Из-за неявного преобразования массива в указатель, указатель на первый элемент массива может быть инициализирован выражением типа массива:
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
Из-за неявного преобразования указателей производного в базовый, указатель на базовый класс может быть инициализирован адресом производного класса:
struct Base {};struct Derived : Base {}; Derived d; Base* p =&d;
Если Derived
является полиморфным, такой указатель может использоваться для выполнения вызовов виртуальных функций.
Определённые операторы сложения, вычитания, инкремента и декремента определены для указателей на элементы массивов: такие указатели соответствуют требованиям LegacyRandomAccessIterator и позволяют алгоритмам библиотеки C++ работать с сырыми массивами.
Операторы сравнения в некоторых ситуациях определены для указателей на объекты: два указателя, представляющие один и тот же адрес, при сравнении равны, два нулевых значения указателя при сравнении равны, указатели на элементы одного и того же массива сравниваются так же, как индексы массива этих элементов, и указатели на нестатические элементы данных с одинаковым доступом к элементам сравниваются в порядке объявления этих элементов.
Многие реализации также обеспечивают строгий общий порядок указателей случайного происхождения, например, если они реализованы как адреса в непрерывном виртуальном адресном пространстве. Те реализации, которые этого не делают (например, когда не все биты указателя являются частью адреса памяти и должны игнорироваться для сравнения, или требуется дополнительное вычисление, или указатель и целое число не являются отношением 1 к 1), обеспечивают специализацию std::less для указателей с такой гарантией. Это позволяет использовать все указатели произвольного происхождения в качестве ключей в стандартных ассоциативных контейнерах, таких как std::set или std::map.
[править]Указатели на void
Указатель на объект любого типа может быть неявно преобразован в указатель на void (необязательно cv-квалифицированный); значение указателя не изменяется. Обратное преобразование, для которого требуется static_cast
или explicit 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 ожидает предоставленный пользователем обратный вызов, который принимает два аргумента constvoid*. 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);}
Разыменование указателя на функцию даёт lvalue, идентифицирующее указанную функцию:
int f();int(*p)()= f;// указатель p указывает на fint(&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
и explicit 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);}
Преобразование в обратном направлении, из указателя на функцию-элемент производного класса в указатель на функцию-элемент однозначного невиртуального базового класса, разрешено с static_cast
и explicit 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);// ok: печатает 1 Base b;(b.*bp)(2);// неопределённое поведение}
Указатели на функции-элементы могут использоваться как обратные вызовы или как объекты функции, часто после применения std::mem_fn или std::bind:
#include <algorithm>#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.
Чтобы инициализировать указатель нулевым значением или присвоить нулевое значение существующему указателю, можно использовать литерал нулевого указателя nullptr, константу нулевого указателя NULL или неявное преобразование из целочисленного литерала с значением 0.
Инициализация нулём и значением также инициализирует указатели их нулевыми значениями.
Нулевые указатели можно использовать для обозначения отсутствия объекта (например, std::function::target()) или в качестве других индикаторов состояния ошибки (например, dynamic_cast). В общем, функция, которая получает аргумент-указатель, почти всегда должна проверять, является ли значение нулевым, и обрабатывать этот случай по-другому (например, выражение delete ничего не делает, когда передаётся нулевой указатель).
[править]Константность
- Если cv появляется перед
*
в объявлении указателя, он является частью последовательности-спецификаторов-объявления и применяется к указанному объекту. - Если cv появляется после
*
в объявлении указателя, он является частью декларатора и применяется к объявляемому указателю.
Синтаксис | значение |
---|---|
const T* | указатель на константный объект |
T const* | указатель на константный объект |
T*const | константный указатель на объект |
const T*const | константный указатель на константный объект |
T const*const | константный указатель на константный объект |
// pc неконстантный указатель на const int// cpc константный указателем на const int// ppc неконстантный указатель на неконстантный указатель на const intconstint ci =10, *pc =&ci, *const cpc = pc, **ppc;// p неконстантный указатель на неконстантный int// cp константный указатель на неконстантный intint i, *p, *const cp =&i; i = ci;// ok: значение const int копируется в неконстантный int*cp = ci;// ok: неконстантный int (указывается константным указателем) можно изменить pc++;// ok: неконстантный указатель (на const int) можно изменить pc = cpc;// ok: неконстантный указатель (на const int) можно изменить pc = p;// ok: неконстантный указатель (на const int) можно изменить ppc =&pc;// ok: адрес указателя на const int является указателем на указатель// на const int ci =1;// ошибка: const int не может быть изменён ci++;// ошибка: const int не может быть изменён*pc =2;// ошибка: указуемый const int не может быть изменён cp =&ci;// ошибка: константный указатель (на неконстантный int) не может быть изменён cpc++;// ошибка: константный указатель (на const int) не может быть изменён p = pc;// ошибка: указатель на неконстантный int не может указывать на const int ppc =&p;// ошибка: указатель на указатель на const int не может указывать на указатель// на неконстантный int
В общем, неявное преобразование из одного многоуровневого указателя в другой следует правилам, описанным в квалификационные преобразования и в операторы сравнения указателей.
[править]Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
CWG 73 | C++98 | указатель на объект никогда не равен при сравнении указателю на первый элемент за концом массива | для ненулевых и нефункциональных указателей сравниваются адреса, которые они представляют |
CWG 903 | C++98 | любое целочисленное константное выражение, которое оценивается как 0, было константой нулевого указателя | ограничено целыми литералами со значением 0 |
CWG 1438 | C++98 | поведение при использовании недопустимого значения указателя каким-либо образом было неопределённым | поведение, отличное от косвенности и передачи в функции освобождения, определяется реализацией |
[править]Смотрите также
Документация C по Объявление указателя |