MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Pointeri către Structuri, Membri ai Claselor și Tablouri

Pointeri către Tipuri și Operația ->

Pe lângă pointerii către tipuri simple, se pot folosi pointeri către structuri. Pentru a accesa câmpurile structurii la care face referire un pointer, se folosește operația ->:

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

   // dereferențierea pointerului
   (*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}";
}

Prin accesarea pointerului p->X = 30;, putem obține sau seta valoarea unei proprietăți a structurii la care face referire pointerul. Este important de menționat că nu putem scrie simplu p.X=30, deoarece p nu este structura Point, ci un pointer către această structură.

O alternativă este operația de dereferențiere: (*p).X = 30;.

Trebuie menționat că un pointer poate face referire doar la acele structuri care nu au câmpuri de tip referențial (inclusiv câmpuri generate automat de compilator pentru proprietățile auto-implementate).

Pointeri către Tablouri și stackalloc

Cu ajutorul cuvântului cheie stackalloc, se poate aloca memorie pentru un tablou în stivă. Alocarea memoriei în stivă este utilă pentru creșterea performanței codului. Să vedem un exemplu de calcul al pătratelor numerelor:

unsafe
{
   const int size = 7;
   int* square = stackalloc int[size]; // alocăm memorie în stivă pentru șapte obiecte de tip int
   int* p = square;
   // calculăm pătratele numerelor de la 1 la 7 inclusiv
   for (int i = 1; i <= size; i++, p++)
   {
       // calculăm pătratul numărului
       *p = i * i;
   }
   for (int i = 0; i < size; i++)
   {
       Console.WriteLine(square[i]);
   }
}

Operatorul stackalloc este urmat de un tablou la care va face referire pointerul: int* square = stackalloc int[size];.

Pentru a manipula tabloul, creăm un pointer p: int* p = square;, care indică primul element al tabloului ce conține șapte elemente. Cu ajutorul pointerului p, putem naviga prin tabloul square.

În continuare, ciclul calculează pătratele numerelor de la 1 la 7. În ciclu, pentru a seta valoarea (pătratul numărului - i * i) la adresa stocată de pointer, se execută expresia:

*p = i * i;

Apoi are loc incrementarea pointerului p++, și pointerul p se deplasează la următorul element din tabloul square.

Un exemplu mai complex - calculul factorialului:

unsafe
{
   const int size = 7;
   int* factorial = stackalloc int[size]; // alocăm memorie în stivă pentru șapte obiecte de tip int
   int* p = factorial;

   *(p++) = 1; // atribuim primei celule valoarea 1 și
   // incrementăm pointerul cu 1
   for (int i = 2; i <= size; i++, p++)
   {
       // calculăm factorialul numărului
       *p = p[-1] * i;
   }
   for (int i = 0; i < size; i++)
   {
       Console.WriteLine(factorial[i]);
   }
}

Cu ajutorul operatorului stackalloc se alocă memorie pentru 7 elemente ale tabloului. De asemenea, pentru manipularea tabloului, creăm un pointer p: int* p = factorial;, care indică primul element al tabloului, ce conține 7 elemente.

În continuare, începem operațiile cu pointerul și calculul factorialului. Deoarece factorialul lui 1 este 1, atribuim primului element indicat de pointerul p valoarea 1 prin operația de dereferențiere: *(p++)= 1;.

Pentru a seta o anumită valoare la adresa indicată de pointer, se folosește expresia: *p=1;. În plus, pointerul este incrementat p++. Așadar, întâi se atribuie valoarea 1 primului element al tabloului, apoi pointerul p se deplasează și începe să indice către al doilea element. Am putea scrie aceasta astfel:

*p= 1;
p++;

Pentru a obține elementul anterior și a ne deplasa înapoi, putem folosi operația de decrementare: Console.WriteLine(*(--p));. Observați că operațiile *(--p) și *(p--) sunt diferite, deoarece în primul caz mai întâi se deplasează pointerul și apoi se dereferențiază, în timp ce în al doilea caz se întâmplă invers.

În final, calculăm factorialul celorlalte șase numere: *p = p[-1] *i;. Accesarea pointerilor ca și cum ar fi tablouri reprezintă o alternativă la operația de dereferențiere pentru a obține valoarea. În acest caz, obținem valoarea elementului anterior.

În final, folosind pointerul factorial, afișăm factorialele celor șapte numere.

Operatorul fixed și Fixarea Pointerilor

Anterior, am văzut cum să creăm pointeri pentru tipuri valoare, cum ar fi int sau structuri. Cu toate acestea, pe lângă structuri, în C# există și clase, care, spre deosebire de tipurile valoare, stochează toate valorile asociate în heap.

În orice moment, colectorul de gunoi poate interveni în operațiunile claselor, curățând periodic heap-ul. Pentru a fixa pointerii pe obiecte ale claselor pe toată durata execuției, se folosește operatorul fixed.

Să presupunem că avem clasa Point:

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

Fixăm pointerul folosind operatorul fixed:

unsafe
{
   Point point = new Point();

   // bloc de fixare a pointerului
   fixed (int* pX = &point.x)
   {
       *pX = 30;
   }
   fixed (int* pY = &point.y)
   {
       *pY = 150;
   }
   // putem combina ambele blocuri
   /*fixed (int* pX = &point.x, pY = &point.y)
   {
       *pX = 30;
       *pY = 150;
   }*/
   Console.WriteLine(point); // x: 30  y: 150
}

Operatorul fixed creează un bloc în care pointerul pe câmpul obiectului point este fixat. După finalizarea blocului fixed, fixarea variabilelor este anulată și acestea pot fi supuse colectării de gunoi.

Pe lângă adresa variabilei, putem inițializa pointerul folosind un tablou, un șir de caractere sau un buffer de dimensiune fixă:

unsafe
{
   int[] nums = { 0, 1, 2, 3, 7, 88 };
   string str = "Привет мир";
   fixed(int* p = nums)
   {
       int third = *(p+2);     // obținem al treilea element
       Console.WriteLine(third); // 2
   }
   fixed(char* p = str)
   {
       char forth = *(p + 3);     // obținem al patrulea element
       Console.WriteLine(forth); // в
   }
}

La inițializarea pointerilor pentru un șir de caractere, trebuie să ținem cont că pointerul trebuie să fie de tip char*.

← Lecția anterioară Lecția următoare →