Оператори, пов'язані з покажчиком: отримання адреси змінних, розташування сховища розіменування та доступ до розташування пам'яті
Оператори покажчика дозволяють приймати адресу змінної (), розіменовувати покажчик ( & * ), порівнювати значення покажчика та додавати чи віднімати покажчики та цілі числа.
Для роботи з покажчиками використовуються такі оператори:
- Унарний оператор & (address-of): для отримання адреси змінної
- Унарний оператор * (непряме звернення до покажчика): для отримання змінної, яку вказує покажчик
- Оператори -> (доступ до членів) та [] (доступ до елементів)
- Арифметичні оператори +, -, ++ і -
- Оператори порівняння == , != , < , >, =
Відомості про типи вказівників див. у розділі Типи вказівників.
Для всіх операцій із покажчиками потрібний небезпечний контекст. Код, який містить небезпечні блоки, повинен компілюватися з параметром компілятора AllowUnsafeBlocks.
Оператор address-of >
Унарний оператор & повертає адресу свого операнда:
unsafe < int number = 27; int* pointerToNumber = &number; Console.WriteLine($"Value of the variable: "); Console.WriteLine($"Address of the variable: "); > // Output is similar to: // Value of the variable: 27 // Address of the variable: 6C1457DBD4
Операнд оператора & має бути фіксованою змінною. Фіксовані Змінні - це змінні в місцях зберігання, на які не поширюється складання сміття. У наведеному вище прикладі локальна змінна number - це фіксована змінна, оскільки вона знаходиться у стеку. Змінні, що знаходяться у місцях зберігання, що обслуговуються збирачем сміття (наприклад, переміщення), називаються переміщуються змінними. Поля об'єкта та елементи масиву є прикладами змінних, що переміщуються.Ви можете отримати адресу змінної, що переміщається, якщо зафіксуєте або закріпите її за допомогою інструкції fixed . Отримана адреса дійсна лише в межах блоку інструкції fixed. У наступному прикладі показано, як використовувати інструкцію fixed та оператор & :
unsafe < byte[] bytes = < 1, 2, 3 >; fixed (byte* pointerToFirst = &bytes[0]) < // The address stored in pointerToFirst // valid only inside this fixed statement block. >>
Отримати адресу константи або значення не можна.
Додаткові відомості про фіксовані та переміщувані змінні див. у розділі Фіксовані та переміщувані змінні у специфікації мови C#.
Бінарний оператор & обчислює логічне І своїх логічних операндів або побітове логічне І своїх цілісних операндів.
Оператор непрямого звернення покажчика *
Унарний оператор непрямого звернення покажчика отримує змінну, на яку вказує операнд. Він також називається оператором розіменування. Операнд оператора повинен мати тип покажчика.
unsafe <char letter = 'A'; char* pointerToLetter = &letter; Console.WriteLine($"Value of the `letter` variable: "); Console.WriteLine($"Address of the `letter` variable: "); *pointerToLetter = 'Z'; Console.WriteLine($"Value of the `letter` variable after update: "); > // Output is similar to: // Value of 'letter' variable: A // Address of 'letter' variable: DCB977DDF4 // Value of 'letter' variable after update: Z
Оператор не можна застосувати до виразу типу void * .
Бінарний оператор * обчислює продукт своїх числових операндів.
Оператор доступу до елемента покажчика ->
Оператор -> об'єднує опосередковане звернення до покажчика та доступ до члена. Тобто якщо x — це покажчик типу T*, а y є доступним членом типу T, вираз форми
У цьому прикладі ілюструється використання оператора -> .
public struct Coords <public int X; public int Y; public override string ToString() =>$"(, )"; > public class PointerMemberAccessExample < public static unsafe void Main() < Coords coords; Coords* p = &coords; p->X = 3; p-> Y = 4; Console.WriteLine(p->ToString()); // output: (3, 4) > >
Оператор не можна застосувати до виразу типу void * .
Оператор доступу до елемента покажчика []
Для вираження p типу покажчика доступ до елементу покажчика форми p[n] обчислюється як *(p + n) , де n повинен мати тип, що неявно перетворюється на int , uint , long або ulong . Відомості про поведінку оператора + з вказівниками див. у розділі Додавання цілого чисельного значення з покажчиком або його віднімання з покажчика.
Наступний приклад демонструє доступ до елементів масиву з покажчиком та оператором [] :
unsafe < char* pointerToChars = stackalloc char[123]; for (int i = 65; i < 123; i++) < pointerToChars[i] = (char)i; >Console.Write("Uppercase letters: "); for (int i = 65; i < 91; i++) < Console.Write(pointerToChars[i]); >> // Output:// Uppercase letters: ABCDEFGHIJKLMNOPQRSTUVWXYZ
У попередньому прикладі вираз stackalloc виділяє блок пам'яті в стеку.
Оператор доступу до елемента покажчика не перевіряє помилки за межами області.
Неможливо використовувати [] для доступу до елемента покажчика з виразом void* .
Арифметичні оператори покажчика
Ви можете виконати такі арифметичні операції з вказівниками:
- Додавання цілого чисельного значення з покажчиком або його віднімання з покажчика
- Віднімання двох покажчиків
- Інкремент або декремент покажчика
Ці операції не можна виконувати з вказівниками типу void*.
Відомості про підтримувані арифметичні операції з числовими типами див. у розділі Арифметичні оператори.
Додавання цілого чисельного значення з покажчиком або його віднімання з покажчика
Для покажчика p типу T* і виразу n типу, що неявно перетворюється в int , uint , long або ulong , додавання та віднімання визначаються таким чином:
- Вирази p + n і n + p створюють покажчик типу T* отриманий додаванням n * sizeof(T) до адреси, наданої p .
- Вираз p - n створює покажчик типу T * отриманий відніманням n * sizeof (T) з адреси, наданого p .
Оператор sizeof отримує розмір типу в байтах.
У наступному прикладі показано використання оператора + із вказівником:
unsafe <const int Count = 3; int[] numbers = new int[Count] < 10, 20, 30>; fixed (int* pointerToFirst = &numbers[0]) < int* pointerToLast = pointerToFirst + (Count - 1); Console.WriteLine($"Value at address"); Console.WriteLine($"Value at address"); > > // Output is similar to: // Value 10 at address 1818345918136 // Value 30 at address 1818345918144
Віднімання покажчиків
Для двох покажчиків p1 і p2 типу T * вираз p1 - p2 знаходить різницю між адресами, наданими p1 і p2, поділену на sizeof(T). Результат має тип long. Тобто p1 - p2 обчислюється як ((long)(p1) - (long)(p2)) / sizeof(T).
У наступному прикладі показано віднімання покажчиків:
unsafe < int* numbers = stackalloc int[] < 0, 1, 2, 3, 4, 5 >; int* p1 = &numbers[1]; int* p2 = &numbers[5]; Console.WriteLine(p2 - p1); // output: 4 >
Інкремент та декремент покажчика
Оператор інкременту ++ додає 1 до свого операнда покажчика. Оператор декремента - віднімає 1 зі свого операнда покажчика.
Обидва оператори підтримуються у двох формах: постфікс (p++ і p--) і префікс (++p і -p). Результат p++ і p - це значення p перед операцією. Результат ++p і --p це значення p після операції.
У наступному прикладі показано поведінку постфіксних та префіксних операторів інкременту:
unsafe < int* numbers = stackalloc int[] < 0, 1, 2 >; int* p1 = &numbers[0]; int * p2 = p1; Console.WriteLine($"Before operation: p1 - , p2 - "); Console.WriteLine($"Postfix increment of p1: <(long)(p1++)>"); Console.WriteLine($"Prefix increment of p2: <(long)(++p2)>"); Console.WriteLine($"After operation: p1 - , p2 - "); > // Output is similar to // До операції: p1 - 816489946512, p2 - 816489946512 // Postfix increment of p1: 816489946512 // Prefix increment of p2: 816489946516 // 48 p2 - 816489946516
Оператори порівняння покажчиків
Ви можете використовувати оператори == , != , < , >, = порівняння операндів будь-якого типу покажчика, включаючи void* . Ці оператори порівнюють адреси, задані двома операндами, ніби вони були призначені цілими числами.
Відомості про поведінку цих операторів для операндів інших типів див. у статтях Оператори рівності та Оператори порівняння.
Пріоритет операторів
У наступному списку оператори, пов'язані з покажчиками, упорядковані від найвищого до найнижчого пріоритету:
- Постфіксні оператори інкременту x++ та декременту x-- та оператори -> та []
- Префіксні оператори інкременту ++x та декременту --x та оператори & та *
- Адитивні оператори + та -
- Оператори порівняння < , >, =
- Оператори рівності == та !=
Використовуйте дужки () , щоб змінити порядок обчислення, що накладається на пріоритет операторів.
Повний список операторів C#, впорядкований за рівнем пріоритету, можна знайти у розділі Пріоритет операторів статті Оператори C#.
Можливість перевантаження оператора
Тип, що визначається користувачем, не може перевантажувати зв'язані оператори & покажчика , * і -> [] .
Специфікація мови C#
Для отримання додаткових відомостей див.у наступних розділах статті Специфікація мови C#:
також
Джерело цього вмісту можна знайти на GitHub, де також можна створювати та переглядати проблеми та запити на витягування. Для отримання додаткових відомостей див. посібник для учасників.
Вказівники та посилання в мові 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). Насамперед вони призначені для використання у високопродуктивних проектах та бібліотеках.