指针相关运算符 - 获取变量的地址、取消引用存储位置和访问内存位置
使用指针运算符可以获取变量的地址 (&
)、取消引用指针 (*
)、比较指针值以及添加或减去指针和整数。
使用以下运算符来使用指针:
- 一元
&
(address-of) 运算符:用于获取变量的地址 - 一元
*
(指针间接)运算符:用于获取指针指向的变量 ->
(成员访问)和[]
(元素访问)运算符- 算术运算符
+
、-
、++
和--
- 比较运算符
==
、!=
、<
、>
、<=
和>=
有关指针类型的信息,请参阅指针类型。
注意
任何带指针的运算都需要使用 unsafe 上下文。 必须使用 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# 语言规范的固定变量和可移动变量部分。
二进制 &
运算符计算其布尔操作数的逻辑 AND 或其整型操作数的位逻辑 AND。
指针间接运算符 *
一元指针间接运算符 *
获取其操作数指向的变量。 它也称为取消引用运算符。 *
运算符的操作数必须是指针类型。
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*
的指针执行这些操作。
有关带数值类型的受支持的算术运算的信息,请参阅算术运算符。
向指针增加或从指针中减少整数值
对于类型为 T*
的指针p
和可隐式转换为 int
、uint
、long
或 ulong
的类型的表达式 n
,加法和减法定义如下:
p + n
和n + p
表达式都生成T*
类型的指针,该指针是将n * sizeof(T)
添加到p
给出的地址得到的。p - n
表达式生成类型为T*
的指针,该指针是从p
给出的地址中减去n * sizeof(T)
得到的。
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
指针减法
对于类型为 T*
的两个指针 p1
和 p2
,表达式 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# 语言规范的以下部分: