Указатели на структуры, члены классов и массивы

Последнее обновление: 19.02.2022

Указатели на типы и операция ->

Кроме указателей на простые типы можно использовать указатели на структуры. А для доступа к полям структуры, на которую указывает указатель, используется операция ->:

unsafe
{
    Point point = new Point(0, 0);
    Console.WriteLine(point);   // X: 0  Y: 0
    Point* p = &point;

    p->X = 30;
    Console.WriteLine(p->X);    // 30

    // разыменовывание указателя
    (*p).Y = 180;
    Console.WriteLine((*p).Y);  // 180

    Console.WriteLine(point);   // X: 30  Y: 180
}
struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
    public Point(int x, int y)
    {
        X = x; Y = y;
    }
    public override string ToString() => $"X: {X}  Y: {Y}";
}

Обращаясь к указателю p->X = 30; мы можем получить или установить значение свойства структуры, на которую указывает указатель. Обратите внимание, что просто написать p.X=30 мы не можем, так как p - это не структура Point, а указатель на структуру.

Альтернативой служит операция разыменования: (*p).X = 30;

Стоит отметить, что указатель может указывать только на те структуры, которые не имеют полей ссылочных типов (в том числе полей, которые генерируются компилятором автоматически для автосвойств).

Указатели на массивы и stackalloc

С помощью ключевого слова stackalloc можно выделить память под массив в стеке. Смысл выделения памяти в стеке в повышении быстродействия кода. Посмотрим на примере вычисления квадратов чисел:

unsafe
{
    const int size = 7;
    int* square = stackalloc int[size]; // выделяем память в стеке под семь объектов int
    int* p = square;
    // вычисляем квадраты чисел от 1 до 7 включая
    for (int i = 1; i <= size; i++, p++)
    {
        // считаем квадрат числа
        *p = i * i;
    }
    for (int i = 0; i < size; i++)
    {
        Console.WriteLine(square[i]);
    }
}

Оператор stackalloc принимает после себя массив, на который будет указывать указатель. int* square = stackalloc int[size];.

Для манипуляций с массивом создаем указатель p: int* p = square;, который указывает на первый элемент массива, в котором всего 7 элементов. То есть с помощью указателя p мы сможем перемещаться по массиву square.

Далее в цикле происходит подсчет квадратов чисел от 1 до 7. В цикле для установки значения (квадрата числа - i * i) по адресу, который хранит указатель, выполняется выражение:

*p = i * i;

Затем происходит инкремент указателя p++, и указатель p смещается вперед на следующий элемент в массиве square.

Чуть более сложный пример - вычисление факториала:

unsafe
{
    const int size = 7;
    int* factorial = stackalloc int[size]; // выделяем память в стеке под семь объектов int
    int* p = factorial;

    *(p++) = 1; // присваиваем первой ячейке значение 1 и
    // увеличиваем указатель на 1
    for (int i = 2; i <= size; i++, p++)
    {
        // считаем факториал числа
        *p = p[-1] * i;
    }
    for (int i = 0; i < size; i++)
    {
        Console.WriteLine(factorial[i]);
    }
}

Также с помощью оператора stackalloc выделяется память для 7 элементов массива. И также для манипуляций с массивом создаем указатель p: int* p = factorial;, который указывает на первый элемент массива, в котором всего 7 элементов

Далее начинаются уже сами операции с указателем и подсчет факториала. Так как факториал 1 равен 1, то присваиваем первому элементу, на который указывает указатель p, единицу с помощью операции разыменования: *(p++)= 1;

Для установки некоторого значения по адресу указателя применяется выражение: *p=1. Но кроме этого тут происходит также инкремент указателя p++. То есть сначала первому элементу массива присваивается единица, потом указатель p смещается и начинает указывать уже на второй элемент. Мы могли бы написать это так:

*p= 1;
p++;

Чтобы получить предыдущий элемент и сместиться назад, можно использовать операцию декремента: Console.WriteLine(*(--p));. Обратите внимание, что операции *(--p) и *(p--) различаются, так как в первом случае сначала идет смещение указателя, а затем его разыменовывание. А во втором случае - наоборот.

Затем вычисляем факториал всех остальных шести чисел: *p = p[-1] *i;. Обращение к указателям как к массивам представляет альтернативу операции разыменовывания для получения значения. В данном случае мы получаем значение предыдущего элемента.

И в заключении, используя указатель factorial, выводим факториалы всех семи чисел.

Оператор fixed и закрепление указателей

Ранее мы посмотрели, как создавать указатели на типы значений, например, int или структуры. Однако кроме структур в C# есть еще и классы, которые в отличие от типов значений, помещают все связанные значения в куче. И в работу данных классов может в любой момент вмешаться сборщик мусора, периодически очищающий кучу. Чтобы фиксировать на все время работы указатели на объекты классов используется оператор fixed.

Допустим, у нас есть класс Point:

class Point
{
    public int x;
    public int y;
    public override string ToString() => $"x: {x}  y: {y}";
}

Зафиксируем указатель с помощью оператора fixed:

unsafe
{
    Point point = new Point();

    // блок фиксации указателя
    fixed (int* pX = &point.x)
    {
        *pX = 30;
    }
    fixed (int* pY = &point.y)
    {
        *pY = 150;
    }
    // можно совместить оба блока
    /*fixed (int* pX = &point.x, pY = &point.y)
    {
        *pX = 30;
        *pY = 150;
    }*/
    Console.WriteLine(point); // x: 30  y: 150
}

Оператор fixed создает блок, в котором фиксируется указатель на поле объекта person. После завершения блока fixed закрепление с переменных снимается, и они могут быть подвержены сборке мусора.

Кроме адреса переменной можно также инициализировать указатель, используя массив, строку или буфер фиксированного размера:

unsafe 
{
    int[] nums = { 0, 1, 2, 3, 7, 88 };
    string str = "Привет мир";
    fixed(int* p = nums)
    {
		int third = *(p+2);     // получим третий элемент
		Console.WriteLine(third); // 2
    }
    fixed(char* p = str)
    {
		char forth = *(p + 3);     // получим четвертый элемент
		Console.WriteLine(forth); // в
    }
}

При инициализации указателей на строку следует учитывать, что указатель должен иметь тип char*.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850