Указатели
Указатели представляют собой объекты, значением которых служат адреса других объектов (переменных, констант, указателей) или функций. Как и ссылки, указатели применяются для косвенного доступа к объекту. Однако в отличие от ссылок указатели обладают большими возможностями.
Определение указателя
Для определения указателя надо указать тип объекта, на который указывает указатель, и символ звездочки *:
тип_данных* название_указателя;
Сначала идет тип данных, на который указывает указатель, и символ звездочки *. Затем имя указателя.
Например, определим указатель на объект типа int:
int* p;
Такой указатель может хранить только адрес переменной типа int , но пока данный указатель не ссылается ни на какой объект и хранит случайное значение. Мы его даже можем попробовать вывести на консоль:
#include int main()
Например, в моем случае консоль вывела «0x8» — некоторый адрес в шестнадцатеричном формате (обычно для представления адресов в памяти применяется шестнадцатеричная форма). Но также можно инициализировать указатель некоторым значением:
int* p<>;
Поскольку конкрентное значение не указано, указатель в качестве значения получает число 0. Это значение представляет специальный адрес, который не указывает не на что. Также можно явным образом инициализировать нулем, например, используя специальную константу nullptr :
int* p;
Хотя никто не запрещает не инициализировать указатели. Однако в общем случае рекомендуется все таки инициализировать, либо каким-то конкретным значением, либо нулем, как выше. Так, к примеру, нулевое значение в будущем позволит определить, что указатель не указывает ни на какой объект.
Cтоит отметить что положение звездочки не влияет на определение указателя: ее можно помещать ближе к типу данных, либо к имени переменной — оба определения будут равноценны:
int* p1<>; int *p2<>;
Также стоит отметить, что размер значения указателя (хранимый адрес) не зависит от типа указателя. Он зависит от конкретной платформы. На 32-разрядных платформах размер адресов равен 4 байтам, а на 64-разрядных — 8 байтам. Например:
#include int main() < int *pint<>; double *pdouble<>; std::cout
В данном случае определены два указателя на разные типы — int и double. Переменные этих типов имеют разные размеры — 4 и 8 байт соответственно. Но размеры значений указателей будут одинаковы. В моем случае на 64-разрядной платформе размер обоих указателей равен 8 байтам.
Получение адреса и оператор &
С помощью операция & можно получить адрес некоторого объекта, например, адрес переменной. Затем этот адрес можно присвоить указателю::
int number ; int *pnumber ; // указатель pnumber хранит адрес переменной number
Выражение &number возвращает адрес переменной number . Поэтому переменная pnumber будет хранить адрес переменной number . Что важно, переменная number имеет тип int, и указатель, который указывает на ее адрес, тоже имеет тип int. То есть должно быть соответствие по типу. Однако также можно использовать ключевое слово auto :
int number ; auto *pnumber ; // указатель pnumber хранит адрес переменной number
Если мы попробуем вывести адрес переменной на консоль, то увидим, что он представляет шестнадцатиричное значение:
#include int main() < int number ; int *pnumber ; // указатель pnumber хранит адрес переменной number std::cout
Консольный вывод программы в моем случае:
number addr: 0x1543bffc74
В каждом отдельном случае адрес может отличаться и при разных запусках программы может меняться. К примеру, в моем случае машинный адрес переменной number — 0x1543bffc74 . То есть в памяти компьютера есть адрес 0x1543bffc74, по которому располагается переменная number. Так как переменная x представляет тип int , то на большинстве архитектур она будет занимать следующие 4 байта (на конкретных архитектурах размер памяти для типа int может отличаться). Таким образом, переменная типа int последовательно займет ячейки памяти с адресами 0x1543bffc74, 0x1543bffc75, 0x1543bffc76, 0x1543bffc77.

И указатель pnumber будет ссылаться на адрес, по которому располагается переменная number, то есть на адрес 0x1543bffc74.
Итак, указатель pnumber хранит адрес переменной number, а где хранится сам указатель pnumber? Чтобы узнать это, мы также можем применить к переменной pnumber операцию &:
#include int main() < int number ; int *pnumber ; // указатель pnumber хранит адрес переменной number std::cout
Консольный вывод программы в моем случае:
number addr: 0xe1f99ff7cc pnumber addr: 0xe1f99ff7c0
Здесь мы видим, что переменная number располагается по адресу 0xe1f99ff7cc , а указатель, который хранит этот адрес, — по адресу 0xe1f99ff7c0 . Из вывода видно, что обе переменные хранятся совсем рядом в памяти
Получение значения по адресу
Но так как указатель хранит адрес, то мы можем по этому адресу получить хранящееся там значение, то есть значение переменной number. Для этого применяется операция * или операция разыменования («indirection operator» / «dereference operator»). Результатом этой операции всегда является объект, на который указывает указатель. Применим данную операцию и получим значение переменной number:
#include int main() < int number ; int *pnumber ; std::cout Address = 0x44305ffd4c Value = 25
Значение, которое получено в результате операции разыменования, можно присвоить другой переменной:
int n1 ; int *pn1 ; // указатель pn1 хранит адрес переменной n1 int n2 < *pn1>; // n2 получает значение, которое хранится по адресу в pn1 std::cout int x = 10; int *px = &x; *px = 45; std::cout
Указатели и ссылки в языке C++
Указатели представляют собой объекты, значением которых служат адреса других объектов:
- переменных
- констант
- функций
- других указателей
Объявление указателей
<тип> *<имя_переменной>[,*<имя_переменной>].
Синтаксис объявления указателей аналогичен объявлению переменных, за исключением того, что между типом данных и именем переменной должен быть указан символ "*" ("звездочка").
Инициализация указателей
Указателю можно присвоить адрес объекта, полученный с помощью оператора взятия адреса &. Стоит отметить, что оператор & не возвращает напрямую адрес своего операнда. Вместо этого он возвращает указатель, содержащий адрес.
Указателю нельзя присвоить адрес переменной другого типа. То есть нельзя указателю типа int* присвоить адрес переменной типа double.
Также указателю можно присвоить значение другого указателя.
Указатель также может быть проинициализирован пустым значением. Это можно сделать несколькими способами:
- использовать значение 0 или макроопределение NULL
- использовать значение nullptr
- использовать значение std::nullptr_t (C++ 11)
В некоторых случаях, использование значения 0 в качестве аргумента функции может привести к проблемам, так как компилятор не сможет определить, используется ли нулевой указатель или целое число. Поэтому использование значения nullptr является предпочтительным способом присвоить указателю пустое значение.
Тип std::nullptr_t может иметь только одно значение - nullptr. Использование этого типа поможет в тех редких случаях, когда существуют перегруженные функции и требуется передать нулевой указатель. В этом случае непонятно какую именно функцию нужно будет вызвать. Поэтому в таком случае в функции можно задать аргумент с типом std::nullptr_t.
Напрямую записать адрес в указатель можно только с помощью операций преобразования типов, либо операции reinterpret_cast.
int a = 0; int *p = &a; double v = 0.1; double *pv = &v; char *pc = nullptr;
Разыменование указателей
Для получения значения переменной, на которую ссылается указатель, используется операция разыменования указателя. Эта операция записывается как символ * (звездочка), написанный перед указателем.
int a = 123; int *p = &a; int b = *p; // b присваивается значение 123
Арифметические действия с указателями
С указателем можно производить следующие арифметические действия:
- сложение и вычитание с целым числом
- операции инкремента/декремента
При использовании арифметических операций, указатель изменяется на величину кратную размеру типа указателя. Например, если указатель имеет тип 32-разрядного int, то увеличение указателя на 1 приведет к увеличению значения адреса в указателе на 4.
Указатель на указатель
В языке C++ можно объявить указатель, который будет указывать на другой указатель.
Синтаксис объявления такой же, как и у объявления указателя, за исключением того, что ставится два символа * (звездочка).
<тип> **<имя_переменной>[,**<имя_переменной>].
Указатель на указатель работает подобно обычному указателю: его можно разыменовать для получения значения, на которое он указывает. И, поскольку этим значением является другой указатель, для получения исходного значения потребуется выполнить разыменование еще раз. Разыменования можно выполнять последовательно:
int value = 1234; int *p = &value; int **pp = &p; int val = **pp; // 1234
Использовать указатели на указатели может потребоваться, например для создания массива из массивов, и в частности массива из строк.
Язык C++ также позволяет работать с указателями на указатели на указатели, или сделать еще большую вложенность. Их можно объявлять просто увеличивая количество символов * (звездочек). Однако на практике такие указатели используются крайне редко.
Неконстантный указатель на неконстантное значение
int val1 = 10; int val2 = 20; int* ptr = &val1; std::coutВ этом случае можно изменять как сам указатель, так и значение, на которое он указывает.
Неконстантный указатель на константное значение
const int val1 = 10; const int val2 = 20; const int* ptr = &val1; std::coutВ этом случае указатель можно изменять. Но само значение, на которое он указывает изменять нельзя.
То же самое поведение можно получить, даже если переменные указаны как неконстантные. Для этого достаточно сам указатель объявить таким образом, чтобы он якобы указывал на константное значение:
int val1 = 10; int val2 = 20; const int* ptr = &val1; std::coutКонстантный указатель на неконстантное значение
int val1 = 10; int val2 = 20; int* const ptr = &val1; std::coutВ этом случае можно изменять значение, на которое указывает указатель. Но нельзя изменять сам указатель.
Кроме того указатель при объявлении нужно сразу инициализировать.
Константный указатель на константное значение
int val1 = 10; int val2 = 20; const int* const ptr = &val1; std::coutВ этом случае нельзя менять ни указатель, ни значение, на которое он указывает.
Ссылки
Ссылка - это тип переменной в языке C++, который работает как псевдоним другого объекта или значения. При объявлении ссылки перед её именем ставится символ амперсанда &. Сама же ссылка не может быть пустой, и должна быть обязательно проинициализирована именем переменной, на которую она ссылается. Изменить значение ссылки после инициализации невозможно.
<тип> &<имя_ссылки> = <имя_переменной>[, &<имя_ссылки> = <имя_переменной>].При создании ссылки на константную переменную, ссылка тоже должна быть создана как константная. Можно также создать константную ссылку на обычную переменную: в этом случае изменить переменную через ссылку не получится.
Любые действия со ссылкой трактуются компилятором как действия, которые будут выполняться над объектом, на который она ссылается.
Ссылки чаще всего используются в качестве параметров в функциях. В этом контексте ссылка-параметр работает как псевдоним аргумента, а сам аргумент не копируется при передаче в параметр. Это в свою очередь улучшает производительность, если аргумент слишком большой или затратный для копирования.
Также ссылка может использоваться и при возврате значений из функции, однако тут следует быть осторожным, так как, если переменная, на которую ссылается ссылка, выйдет из области видимости (например локальная переменная в функции), то это приведет к неопределенному поведению программы.
int value = 123; int &refval = value; refval = 12345; std::coutСсылки r-value
В стандарте C++11 ввели новый тип ссылок - ссылки r-value. Ссылки r-value - это ссылки, которые инициализируются только значениями r-values. Объявляются такие ссылки, в отличие от обычных, с помощью двух символов амперсанда &&.
<тип> &&<имя_ссылки> = <выражение r-value>[, &&<имя_ссылки> = <выражение r-value>].Ссылки r-value, в отличие от обычных ссылок, ссылаются не на постоянный, а на временный объект, созданный при инициализации ссылки r-value.
Такие ссылки обладают двумя важными свойствами:
- продолжительность жизни объекта, на который ссылается ссылка увеличивается до продолжительности жизни самой ссылки
- неконстантные ссылки r-value позволяют менять значение r-values, на который они ссылаются
int &&ref = 10; ref = ref + 20; std::coutСсылки r-value - позволяют избегать логически ненужного копирования и обеспечивать возможность идеальной передачи (perfect forwarding). Прежде всего они предназначены для использования в высокопроизводительных проектах и библиотеках.
- Уголок в Вконтакте
- Уголок в Телеграм
- Уголок в YouTube
- Все справочники
- Справочник по JavaScript
- Справочник по Ассемблеру
- Справочник по C++
- - Типы данных
- - Переменные
- - Указатели и ссылки
- - Массивы
- - Циклы и ветвления
- - Перечисления
- - Структуры и объединения
- - Классы
- - Наследование классов
- - Перегрузка операторов
Указатели на члены
Объявления указателей на члены — это особый случай объявлений указателей. Они объявляются с помощью следующей последовательности:
описатель класса storage-class-opt cv-qualifiers opttype-specifierms-modifier optqualified-name ::* cv-qualifiersoptidentifier pm-initializer opt ;
- Спецификатор объявления:
- Необязательный спецификатор класса хранения.
- Необязательные const и volatile описатели.
- Спецификатор типа: имя типа. Это тип элемента, на который нужно указать, а не класс.
- Декларатор:
- Необязательный модификатор, используемый в системах Microsoft. Дополнительные сведения см. в разделе "Модификаторы для конкретного майкрософт".
- Полное имя класса, содержащего члены, на которые должен указывать указатель.
- Оператор :: .
- Оператор * .
- Необязательные const и volatile описатели.
- Идентификатор, задающий имя указателя на член.
- Необязательный инициализатор указателя на член:
- Оператор = .
- Оператор & .
- Полное имя класса.
- Оператор :: .
- Имя нестатитического члена класса соответствующего типа.
Как обычно, в одном объявлении допускается несколько деклараторов (и любые связанные инициализаторы). Указатель на член может не указывать на статический элемент класса, член ссылочного типа или void .
Указатель на член класса отличается от обычного указателя: он содержит сведения о типе элемента и для класса, к которому принадлежит член. Обычный указатель идентифицирует только один объект в памяти (содержит адрес этого объекта). Указатель на член класса идентифицирует этот член в любом экземпляре класса. В следующем примере объявляется класс Window и несколько указателей на данные-член.
// pointers_to_members1.cpp class Window < public: Window(); // Default constructor. Window( int x1, int y1, // Constructor specifying int x2, int y2 ); // Window size. bool SetCaption( const char *szTitle ); // Set window caption. const char *GetCaption(); // Get window caption. char *szWinCaption; // Window caption. >; // Declare a pointer to the data member szWinCaption. char * Window::* pwCaption = &Window::szWinCaption; int main()В предыдущем примере pwCaption указатель на любой член класса Window , который имеет тип char* . pwCaption имеет тип char * Window::* . В следующем фрагменте кода объявляются указатели на функции-члены SetCaption и GetCaption .
const char * (Window::* pfnwGC)() = &Window::GetCaption; bool (Window::* pfnwSC)( const char * ) = &Window::SetCaption;Указатели pfnwGC и pfnwSC указывают на функции GetCaption и SetCaption класса Window соответственно. Код копирует информацию непосредственно в заголовок окна с помощью указателя на член pwCaption :
Window wMainWindow; Window *pwChildWindow = new Window; char *szUntitled = "Untitled - "; int cUntitledLen = strlen( szUntitled ); strcpy_s( wMainWindow.*pwCaption, cUntitledLen, szUntitled ); (wMainWindow.*pwCaption)[cUntitledLen - 1] = '1'; // same as // wMainWindow.SzWinCaption [cUntitledLen - 1] = '1'; strcpy_s( pwChildWindow->*pwCaption, cUntitledLen, szUntitled ); (pwChildWindow->*pwCaption)[cUntitledLen - 1] = '2'; // same as // pwChildWindow->szWinCaption[cUntitledLen - 1] = '2';Разница между .* операторами и ->* операторами (операторами указателя на члены) заключается в том, что .* оператор выбирает членов, заданных объектом или ссылкой на объект, а ->* оператор выбирает элементы через указатель. Дополнительные сведения об этих операторах см. в разделе "Выражения" с операторами указателя на члены.
Результатом операторов указателя на член является тип элемента. В этом случае он выглядит так: char * .
В следующем фрагменте кода функции-члены GetCaption и SetCaption вызываются с использованием указателей на члены.
// Allocate a buffer. enum < sizeOfBuffer = 100 >; char szCaptionBase[sizeOfBuffer]; // Copy the main window caption into the buffer // and append " [View 1]". strcpy_s( szCaptionBase, sizeOfBuffer, (wMainWindow.*pfnwGC)() ); strcat_s( szCaptionBase, sizeOfBuffer, " [View 1]" ); // Set the child window's caption. (pwChildWindow->*pfnwSC)( szCaptionBase );Ограничения указателей на члены
Адрес статического элемента не является указателем на элемент. Это обычный указатель на один экземпляр статического элемента. Для всех объектов данного класса существует только один экземпляр статического элемента. Это означает, что вы можете использовать обычные адреса (&) и операторы разыменования (*).
Указатели на члены и виртуальные функции
Вызов виртуальной функции через функцию указателя на член работает так, как если бы функция была вызвана напрямую. Правильная функция ищется в таблице v-table и вызывается.
Ключ для виртуальных функций, работающих как обычно, вызывает их через указатель на базовый класс. (Дополнительные сведения о виртуальных функциях см. в разделе Виртуальные функции.)
В следующем коде показан вызов виртуальной функции через функцию указателя на член.
// virtual_functions.cpp // compile with: /EHsc #include using namespace std; class Base < public: virtual void Print(); >; void (Base::* bfnPrint)() = &Base::Print; void Base::Print() < cout class Derived : public Base < public: void Print(); // Print is still a virtual function. >; void Derived::Print() < cout int main() < Base *bPtr; Base bObject; Derived dObject; bPtr = &bObject; // Set pointer to address of bObject. (bPtr->*bfnPrint)(); bPtr = &dObject; // Set pointer to address of dObject. (bPtr->*bfnPrint)(); > // Output: // Print function for class Base // Print function for class DerivedКак получить указатель на объект c
Указатели поддерживают ряд операций: присваивание, получение адреса указателя, получение значения по указателю, некоторые арифметические операции и операции сравнения.
Присваивание адреса
Указателю можно присвоить адрес объекта того же типа, либо значение другого указателя. Для получения адреса объекта используется операция & :
int a ; int *pa ; // указатель pa хранит адрес переменной aПри этом указатель и переменная должны иметь один и тот же тип, в данном случае это тип int.
Разыменование указателя
Операция разыменования указателя представляет выражение в виде *имя_указателя . Эта операция позволяет получить объект по адресу, который хранится в указателе.
#include int main() < int a ; int *pa ; // хранит адрес переменной a std::cout #include int main() < int a ; int b ; int *pa ; // указатель на переменную a int *pb ; // указатель на переменную b std::cout pa: address=0x56347ffc5c value=10 pb: address=0x56347ffc58 value=2 pa: address=0x56347ffc58 value=2 b value=125Нулевые указатели
Нулевой указатель (null pointer) - это указатель, который не указывает ни на какой объект. Если мы не хотим, чтобы указатель указывал на какой-то конкретный адрес, то можно присвоить ему условное нулевое значение. Для определения нулевого указателя можно инициализировать указатель нулем или константой nullptr :
int *p1; int *p2<>;Ссылки на указатели
Так как ссылка не является объектом, то нельзя определить указатель на ссылку, однако можно определить ссылку на указатель. Через подобную ссылку можно изменять значение, на которое указывает указатель или изменять адрес самого указателя:
#include int main() < int a ; int b ; int *p<>; // указатель int *&pRef
; // ссылка на указатель pRef = &a; // через ссылку указателю p присваивается адрес переменной a std::cout &:
int a ; int *pa ; std::cout >, >=, , ,==, !=. Операции сравнения применяются только к указателям одного типа. Для сравнения используются номера адресов:
#include int main() < int a ; int b ; int *pa ; int *pb ; if(pa > pb) std::cout
Консольный вывод в моем случае:
pa (0xa9da5ffdac) is greater than pb (0xa9da5ffda8)Приведение типов
Иногда требуется присвоить указателю одного типа значение указателя другого типа. В этом случае следует выполнить операцию приведения типов с помощью операции (тип_указателя *) :
#include int main() < char c ; char *pc ; // указатель на символ int *pd <(int *)pc>; // указатель на int void *pv ; // указатель на void std::cout std::cout