Операторы, связанные с указателем: получение адреса переменных, расположения хранилища разыменовки и доступ к расположениям памяти
Операторы указателя позволяют принимать адрес переменной (), разыменовывать указатель (&
*
), сравнивать значения указателя и добавлять или вычитать указатели и целые числа.
Для работы с указателями используются следующие операторы:
- Унарный оператор
&
(address-of): для получения адреса переменной - Унарный оператор
*
(косвенное обращение к указателю): для получения переменной, на которую указывает указатель - Операторы
->
(доступ к членам) и[]
(доступ к элементам) - Арифметические операторы
+
,-
,++
и--
- Операторы сравнения
==
,!=
,<
,>
,<=
и>=
Сведения о типах указателей см. в разделе Типы указателей.
Примечание.
Для всех операций с указателями требуется небезопасный контекст. Код, содержащий небезопасные блоки, должен компилироваться с параметром компилятора AllowUnsafeBlocks.
Оператор address-of >
Унарный оператор &
возвращает адрес своего операнда:
unsafe
{
int number = 27;
int* pointerToNumber = &number;
Console.WriteLine($"Value of the variable: {number}");
Console.WriteLine($"Address of the variable: {(long)pointerToNumber:X}");
}
// 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
// is valid only inside this fixed statement block.
}
}
Получить адрес константы или значения нельзя.
Дополнительные сведения о фиксированных и перемещаемых переменных см. в разделе Фиксированные и перемещаемые переменные в спецификации языка C#.
Бинарный оператор &
вычисляет логическое И своих логических операндов или побитовое логическое И своих целочисленных операндов.
Оператор косвенного обращения указателя *
Унарный оператор косвенного обращения указателя *
получает переменную, на которую указывает операнд. Он также называется оператором разыменования. Операнд оператора *
должен иметь тип указателя.
unsafe
{
char letter = 'A';
char* pointerToLetter = &letter;
Console.WriteLine($"Value of the `letter` variable: {letter}");
Console.WriteLine($"Address of the `letter` variable: {(long)pointerToLetter:X}");
*pointerToLetter = 'Z';
Console.WriteLine($"Value of the `letter` variable after update: {letter}");
}
// Output is similar to:
// Value of the `letter` variable: A
// Address of the `letter` variable: DCB977DDF4
// Value of the `letter` variable after update: Z
Оператор нельзя применить *
к выражению типа void*
.
Бинарный оператор *
вычисляет продукт своих числовых операндов.
Оператор доступа к элементу указателя —>
Оператор ->
объединяет косвенное обращение к указателю и доступ к члену. То есть если x
— это указатель типа T*
, а y
является доступным членом типа T
, выражение формы
x->y
эквивалентно правилу
(*x).y
В следующем примере иллюстрируется использование оператора ->
.
public struct Coords
{
public int X;
public int Y;
public override string ToString() => $"({X}, {Y})";
}
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 {*pointerToFirst} at address {(long)pointerToFirst}");
Console.WriteLine($"Value {*pointerToLast} at address {(long)pointerToLast}");
}
}
// 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 - {(long)p1}, p2 - {(long)p2}");
Console.WriteLine($"Postfix increment of p1: {(long)(p1++)}");
Console.WriteLine($"Prefix increment of p2: {(long)(++p2)}");
Console.WriteLine($"After operation: p1 - {(long)p1}, p2 - {(long)p2}");
}
// Output is similar to
// Before operation: p1 - 816489946512, p2 - 816489946512
// Postfix increment of p1: 816489946512
// Prefix increment of p2: 816489946516
// After operation: p1 - 816489946516, p2 - 816489946516
Операторы сравнения указателей
Вы можете использовать операторы ==
, !=
, <
, >
, <=
и >=
для сравнения операндов любого типа указателя, включая void*
. Эти операторы сравнивают адреса, заданные двумя операндами, как если бы они не были назначены целыми числами.
Сведения о поведении этих операторов для операндов других типов см. в статьях Операторы равенства и Операторы сравнения.
Приоритет операторов
В следующем списке операторы, связанные с указателями, упорядочены от самого высокого до самого низкого приоритета:
- Постфиксные операторы инкремента
x++
и декрементаx--
и операторы->
и[]
- Префиксные операторы инкремента
++x
и декремента--x
и операторы&
и*
- Аддитивные операторы
+
и-
- Операторы сравнения
<
,>
,<=
и>=
- Операторы равенства
==
и!=
Используйте скобки ()
, чтобы изменить порядок вычисления, накладываемый приоритетом операторов.
Полный список операторов C#, упорядоченный по уровню приоритета, можно найти в разделе Приоритет операторов статьи Операторы C#.
Возможность перегрузки оператора
Определяемый пользователем тип не может перегружать связанные операторы &
указателя , *
и ->
[]
.
Спецификация языка C#
Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:
- Фиксированные и перемещаемые переменные
- Оператор address-of
- Косвенное обращение к указателю
- Доступ к членам указателей
- Доступ к элементам указателей
- Расчеты указателей
- Инкремент и декремент указателя
- Сравнение указателей