指针相关运算符 - 获取变量的地址、取消引用存储位置和访问内存位置

使用指针运算符可以获取变量的地址 (&)、取消引用指针 (*)、比较指针值以及添加或减去指针和整数。

使用以下运算符来使用指针:

有关指针类型的信息,请参阅指针类型

注意

任何带指针的运算都需要使用 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* 的表达式。

指针元素访问运算符 []

对于指针类型的表达式 pp[n] 形式的指针元素访问计算方式为 *(p + n),其中 n 必须是可隐式转换为 intuintlongulong 的类型。 有关带有指针的 + 运算符的行为的信息,请参阅向指针中增加或从指针中减少整数值部分。

以下示例演示如何使用指针和 [] 运算符访问数组元素:

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和可隐式转换为 intuintlongulong 的类型的表达式 n,加法和减法定义如下:

  • p + nn + 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* 的两个指针 p1p2,表达式 p1 - p2 生成 p1p2 给出的地址之间的差除以 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# 语言规范的以下部分:

另请参阅