C# - C Sharp: Con trỏ (Pointer) trong C#

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực

Có những trường hợp ta cần truy xuất bộ nhớ trực tiếp khi ta muốn truy xuất vào các hàm bên ngoài (không thuộc .NET) mà đòi hỏi con trỏ được truyền vào như tham số (ví dụ như các hàm API ), hoặc là vì ta muốn truy nhập vào nội dung bộ nhớ để sửa lỗi, ... Trong phần này ta sẽ xem xét cách C# đáp ứng những điều này như thế nào.

Con trỏ (Pointer)

Con trỏ (pointer) đơn giản là một biến lưu  địa chỉ của một thành phần khác theo cùng một cách như là một tham chiếu. Ở đây có sự khác biệt so với C/C++ là cú pháp C# trong tham chiếu không cho phép ta truy xuất vào địa chỉ bộ nhớ.

Ưu điểm của con trỏ:

· Cải thiện sự thực thi : cho ta biết những gì ta đang làm, đảm bảo rằng dữ liệu được truy xuất hay thao tác theo cách hiệu quả nhất - đó là lí do mà C và C++ cho phép dùng con trỏ  trong ngôn ngữ của mình.

· Khả năng tương thích với các phần trước (Backward compatibility) - đôi khi ta phải sử dụng lại các hàm API cho mục đích của ta. Mà các hàm  API được viết bằng C, ngôn ngữ dùng con trỏ rất nhiều, nghĩa là nhiều hàm lấy con trỏ như tham số, hoặc là các DLL  do một hãng nào đó cung cấp chứa các hàm lấy con trỏ làm tham số . Trong nhiều trường hợp ta có thể viết các khai báo DLlImport theo cách tránh sử dụng con trỏ, ví dụ như dùng lớp System.IntPtr.

· Ta có thể cần tạo ra các địa chỉ vùng nhớ có giá trị cho người dùng - ví dụ nếu ta muốn phát triển một ứng dụng mà cho phép người dùng tương tác trực tiếp đến bộ nhớ, như là một debugger.

Nhược điểm của con trỏ:

· Cú pháp để lấy các hàm phức tạp hơn

· Con trỏ khó sử dụng

· Nếu không cẩn thận ta có thể viết chồng biến, làm tràn stack, mất thông tin, đụng độ, ...

· C# có thể từ chối thi hành những đoạn mã không an toàn này (đoạn mã có sử dụng con trỏ).

Ta có thể đánh dấu đoạn mã có sử dụng con trỏ bằng cách dùng từ khoá unsafe

Ví dụ:

Dùng cho hàm:

unsafe int GetSomeNumber()
{
   // code that can use pointers
}

Dùng cho lớp  hay struct:

unsafe class MyClass
{
   // any method in this class can now use pointers
}

Dùng cho một trường:

class MyClass
{
   unsafe int *pX;   // declaration of a pointer field in a class
}

Dùng cho một khối mã:

void MyMethod()
{
   // code that doesn't use pointers
   unsafe
   {
      // unsafe code that uses pointers here
   }
   // more 'safe' code that doesn't use pointers
}

Tuy nhiên ta không thể đánh dấu 1 biến cục bộ là unsafe:

int MyMethod()
{
   unsafe int *pX;   // WRONG
}

Để biên dịch các mã chứa khối unsafe ta dùng lệnh sau:

csc /unsafe MySource.cs

Hoặc:

csc -unsafe MySource.cs

Cú pháp con trỏ

Kiểu_dữ_liệu* Tên_con_trỏ1, Tên_con_trỏ1, ...;

Ví dụ:

int* pWidth, pHeight; //khai báo hai con trỏ pWidth và pHeight có cùng kiểu int

double *pResult; //khai báo con trỏ pResult có kiểu double

Cách dùng con trỏ:

Tên_con_trỏ: chứa địa chỉ

*Tên_con_trỏ : chứa giá trị (nội dung)

Ví dụ:

int x = 10;

int* pX, pY; //khai báo hai con trỏ pX và pY

pX = &x; //cho con trỏ pX trỏ tới biến x (pX sẽ chứa địa chỉ của biến x)

pY = pX; //cho con trỏ pY cũng trỏ tới biến x

*pY = 20; //gán 20 cho biến x

uint y = (uint)pX; //ép kiểu (tường minh) sang kiểu uint

int* pD = (int*)y; //ép kiểu sang kiểu int rồi gán cho pD

y là uint. Sau đó ta chuyển ngược lại thành biến con trỏ pD.

Một lý do để ta phải ép kiểu là Console.WriteLine không có overload nào nhận thông số là con trỏ do đó ta phải ép nó sang kiểu số nguyên int.

Console.WriteLine("Address is" + pX);   // wrong - will give a compilation error

Console.WriteLine("Address is" + (uint) pX);   // OK

Ép kiểu giữa những kiểu con trỏ 

Ta cũng có thể chuyển đổi tường minh giữa các con trỏ trỏ đến1 kiểu khác ví dụ :

byte aByte = 8;
byte *pByte= &aByte;
double *pDouble = (double*)pByte;
 

void Pointers

Nếu ta muốn giữ 1 con trỏ , nhưng không muốn đặc tả kiểu cho con trỏ ta có thể khai báo co ntrỏ là void:

void *pointerToVoid;
pointerToVoid = (void*)pointerToInt;   // pointerToInt declared as int*

mục đích là khi ta cần gọi các hàm API mà đòi hỏi thông số void*.

Toán tử sizeof 

Lấy thông số là tên của kiểu và trả về số byte của kiểu đó ví dụ :

int x = sizeof(double);

x có giá trị là 8.

Bảng kích thước kiểu :

sizeof(sbyte) = 1;

sizeof(byte) = 1;

sizeof(short) = 2;

sizeof(ushort) = 2;

sizeof(int) = 4;

sizeof(uint) = 4;

sizeof(long) = 8;

sizeof(ulong) = 8;

sizeof(char) = 2;

sizeof(float) = 4;

sizeof(double) = 8;

sizeof(bool) = 1;

Ta cũng có thể dùng sizeof cho struct nhưng không dùng được cho lớp.

Ví dụ sau trình bày cách thao tác trên con trỏ và trình bày kết quả, cho phép ta thấy những gì xảy ra trong bộ nhớ và nơi biến được lưu trữ:

using System;

namespace Wrox.ProCSharp.AdvancedCSharp
{
   class MainEntryPoint
   {
      static unsafe void Main()
      {
         int x=10;
         short y = -1;
         byte y2 = 4;
         double z = 1.5;
         int *pX = &x;
         short *pY = &y;
         double *pZ = &z;

         Console.WriteLine(
            "Address of x is 0x{0:X}, size is {1}, value is {2}",
            (uint)&x, sizeof(int), x);
         Console.WriteLine(
            "Address of y is 0x{0:X}, size is {1}, value is {2}",
            (uint)&y, sizeof(short), y);
         Console.WriteLine(
            "Address of y2 is 0x{0:X}, size is {1}, value is {2}",
            (uint)&y2, sizeof(byte), y2);
         Console.WriteLine(
            "Address of z is 0x{0:X}, size is {1}, value is {2}",
            (uint)&z, sizeof(double), z);
         Console.WriteLine(
            "Address of pX=&x is 0x{0:X}, size is {1}, value is 0x{2:X}",
            (uint)&pX, sizeof(int*), (uint)pX);
         Console.WriteLine(
            "Address of pY=&y is 0x{0:X}, size is {1}, value is 0x{2:X}",
            (uint)&pY, sizeof(short*), (uint)pY);
         Console.WriteLine(
            "Address of pZ=&z is 0x{0:X}, size is {1}, value is 0x{2:X}",
            (uint)&pZ, sizeof(double*), (uint)pZ);

         *pX = 20;
         Console.WriteLine("After setting *pX, x = {0}", x);
         Console.WriteLine("*pX = {0}", *pX);

         pZ = (double*)pX;
         Console.WriteLine("x treated as a double = {0}", *pZ);

         Console.ReadLine();
      }
   }
}

Mã gồm 3 biến 

· int x

· short y

· double z

Cùng với các con trỏ trỏ đến các giá trị này.sau đó ta trình bày giá trị của các biến và kích thước,địa chỉ của nó.Ta dùng đặc tả {0:X} trong Console.WriteLine để  địa chỉ  bộ nhớ được trình bày theo định dạng số bát phân.

Cuối cùng ta dùng con trỏ pX thay đổi giá trị của x thành 20, và thử ép kiểu biến x thành 1 double để xem điều gì sẻ xảy ra.

Biên dịch mã, ta có kết quả sau :
csc PointerPlayaround.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.

PointerPlayaround.cs(7,26): error CS0227: Unsafe code may only appear if
        compiling with /unsafe

csc /unsafe PointerPlayaround.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.


PointerPlayaround
Address of x is 0x12F8C4, size is 4, value is 10
Address of y is 0x12F8C0, size is 2, value is -1
Address of y2 is 0x12F8BC, size is 1, value is 4
Address of z is 0x12F8B4, size is 8, value is 1.5
Address of pX=&x is 0x12F8B0, size is 4, value is 0x12F8C4
Address of pY=&y is 0x12F8AC, size is 4, value is 0x12F8C0
Address of pZ=&z is 0x12F8A8, size is 4, value is 0x12F8B4
After setting *pX, x = 20
*pX = 20
x treated as a double = 2.63837073472194E-308

Pointer Arithmetic

Ta có thể cộng hay trừ số nguyên trên con trỏ.Ví dụ , giả sử ta có 1 con trỏ trỏ đến số nguyên,và ta thử cộng 1 vào giá trị của nó .trình biên dịch sẽ biết và tăng vùng nhớ lên 4 byte ( do kiểu int có kích thước 4 byte).nếu là kiểu double thì khi cộng 1 sẽ tăng giá trị của con trỏ lên 8 byte.

Ta có thể dùng toán tử +, -, +=, -=, ++,và -- với biến bên phía phải của toán tử này là long hay ulong.

Ví dụ

uint u = 3;
byte b = 8;
double d = 10.0;
uint *pUint= &u;        // size of a uint is 4
byte *pByte = &b;       // size of a byte is 1
double *pDouble = &d;   // size of a double is 8

Giả sử địa chỉ của những con trỏ này trỏ đến là :

· pUint: 1243332

· pByte: 1243328

· pDouble: 1243320

Sau khi thi hành ta có :

++pUint;              // adds 1= 4 bytes to pUint
pByte -= 3;           // subtracts 3=3bytes from pByte
double *pDouble2 = pDouble - 4; // pDouble2 = pDouble - 32 bytes (4*8 bytes)

Con trỏ sẽ có giá trị:

· pUint: 1243336

· pByte: 1243321

· pDouble2: 1243328

Ta cũng có thể trừ 2 con trỏ với nhau .giá trị kết quả là kiểu long bằng giá trị con trỏ chia cho kích thước của kiểu mà nó đại diện .Ví dụ :

double *pD1 = (double*)1243324;   // note that it is perfectly valid to
                                  // initialize a pointer like this.
double *pD2 = (double*)1243300;
long L = pD1-pD2;                 // gives the result 3 (=24/sizeof(double))

Con trỏ đến Struct - Toán tử truy xuất các thành viên con trỏ

Cũng giống như con trỏ trong các kiểu dữ liệu có sẵn. tuy nhiên thêm 1 điều kiện là - Struct không chứa bất kì kiểu tham chiếu nào.Do con trỏ không thể trỏ đến bất kì kiểu tham chiếu nào. để tránh điều này , trình biên dịch sẽ phất cờ lỗi nếu ta tạo ra một con trỏ đến bất kì Struct nào chứa kiểu tham chiếu .

Giả sử ta có struct như sau :

struct MyGroovyStruct
{
   public long X;
   public float F;
}

Sau đó ta định nghĩa con trỏ cho nó :

MyGroovyStruct *pStruct;

Khởi tạo nó :

MyGroovyStruct Struct = new MyGroovyStruct();

pStruct = &Struct;

Cũng có thể truy xuất các giá trị thành viên của 1 struct bằng con trỏ :

(*pStruct).X = 4;

(*pStruct).F = 3.4f;

Tuy nhiên cú pháp này hơi phức tạp. C# định nghĩa 1 toán tử khác cho phép ta truy xuất các thành viên của Struct bằng con trỏ đơn giản hơn , gọi là toán tử truy xuất thành viên con trỏ ,kí hiệu là  ->

Cách dùng :

pStruct->X = 4;
pStruct->F = 3.4f;

Ta cũng có thể thiết đặt trực tiếp con trỏ của kiểu tương đương để trỏ đến các  trường trong Struct

long *pL = &(Struct.X);

float *pF = &(Struct.F);

hay :

long *pL = &(pStruct->X);

float *pF = &(pStruct->F);

Con trỏ đến các thành viên của lớp 

Ta đã nói rằng không thể tạo ra con trỏ đến lớp vì việc tạo có thể làm cho bộ gom rác hoạt động không đúng. 

Tuy nhiên ta có thể tạo các con trỏ đến các thành viên của lớp. Ta sẽ viết lại struct của ví dụ trước như là lớp :

class MyGroovyClass
{
   public long X;
   public float F;

sau đó ta có thể tạo 1 con trỏ đến các trường của nó ,X và F.tuy nhiên làm như vậy sẽ gây ra lỗi :

MyGroovyClass myGroovyObject = new MyGroovyClass();

long *pL = &( myGroovyObject.X);   // wrong

float *pF = &( myGroovyObject.F);  // wrong

Do X và F nằm trong 1 lớp , mà được đặt trong heap.nghĩa là chúng vẫn gián tiếp chịu sự quản lý của bộ gom rác.cụ thể bộ gom rác có thể quyết định di chuyển MyGroovyClass đến 1 vị trí mới trong bộ nhớ để dọn dẹp heap.Nếu làm điều này thì bộ gom rác tất nhiên sẽ cập nhật tất cả các tham chiếu đến đối tượng ,giả sử như biến myGrooveObject  vẫn sẽ trỏ đến đúng vị trí.Tuy nhiên bộ gom rác không biết gì về con trỏ cả. vì thế nếu di chuyển các đối tượng tham chiếu bởi myGrooveObject,pL và pF sẽ vẫn không thay đôỉ  và kết cuộc là trỏ đến sai vị trí vùng nhớ.

Để giải quyết vấn đề này ta dùng từ khóa fixed , mà cho bộ gom rác biết rằng có thể có con trỏ trỏ đến các thành viên của các thể hiện lớp,vì thế các thể hiện lớp này sẽ không được di chuyển.cú pháp như sau nếu ta chỉ muốn khai báo 1 con trỏ :

MyGroovyClass myGroovyObject = new MyGroovyClass();
// do whatever
fixed (long *pObject = &( myGroovyObject.X))
{
   // do something
}

nếu ta muốn khai báo nhiều hơn 1 con trỏ ta có thể đặt nhiều câu lệnh fixed trước  khối mã giống nhau :

MyGroovyClass myGroovyObject = new MyGroovyClass();
fixed (long *pX = &( myGroovyObject.X))
fixed (float *pF = &( myGroovyObject.F))
{
   // do something
}

Ta có thể lồng các khối fixed nếu ta muốn fix các con trỏ trong các  thời điểm khác nhau

MyGroovyClass myGroovyObject = new MyGroovyClass();

fixed (long *pX = &( myGroovyObject.X))

{

   // do something with pX

   fixed (float *pF = &( myGroovyObject.F))

   {

      // do something else with pF

   }

}

Ta cũng có thể khởi tạo vài biến trong cùng 1 khối fixed :

MyGroovyClass myGroovyObject = new MyGroovyClass();

MyGroovyClass myGroovyObject2 = new MyGroovyClass();

fixed (long *pX = &( myGroovyObject.X), pX2 = &( myGroovyObject2.X))

{

   // etc.

}

Thêm các lớp và Struct đến ví dụ

Trong phần này ta sẽ minh họa việc tính toán trên  con trỏ và các con trỏ đến struct và lớp .Ta  dùng ví dụ 2, PointerPlayaround2:

   struct CurrencyStruct
   {
      public long Dollars;
      public byte Cents;

      public override string ToString()
      {
         return "$" + Dollars + "." + Cents;
      }
   }

   class CurrencyClass
   {
      public long Dollars;
      public byte Cents;

      public override string ToString()
      {
         return "$" + Dollars + "." + Cents;
      }
   }

Bây giờ ta có thể áp dụng con trỏ cho các struct và lớp của ta .ta bắt đầu bằng việc trình bày kích thước của stuct , tạo ra 1 vài thể hiện của nó cùng với con trỏ.ta dùng những con trỏ này để khởi tạo 1 trong những struct Currency ,amount1. và trình  bày các địa chỉ của các biến :

public static unsafe void Main()
{
   Console.WriteLine(
      "Size of Currency struct is " + sizeof(CurrencyStruct));
   CurrencyStruct amount1, amount2;
   CurrencyStruct *pAmount = &amount1;
   long *pDollars = &(pAmount->Dollars);
   byte *pCents = &(pAmount->Cents);

   Console.WriteLine("Address of amount1 is 0x{0:X}", (uint)&amount1);
   Console.WriteLine("Address of amount2 is 0x{0:X}", (uint)&amount2);
   Console.WriteLine("Address of pAmt is 0x{0:X}", (uint)&pAmount);
   Console.WriteLine("Address of pDollars is 0x{0:X}", (uint)&pDollars);
   Console.WriteLine("Address of pCents is 0x{0:X}", (uint)&pCents);
   pAmount->Dollars = 20;
   *pCents = 50;
   Console.WriteLine("amount1 contains " + amount1);

Ta biết rằng amount2 sẽ được lưu trữ ở 1 địa chỉ ngay sau amount1, sizeof ( CurrencyStru) trả về 16, vì vậy CurrencyStruct sẽ nằm ở địa chỉ là bội số của 4 byte.do đó sau khi giảm con trỏ currency , nó sẽ trỏ đến amount2:

   --pAmount;   // this should get it to point to amount2
   Console.WriteLine("amount2 has address 0x{0:X} and contains {1}",
      (uint)pAmount, *pAmount);

Ta trình bày nội dụng của amount2 nhưng chưa khởi tạo nó .Dù trình biên dịch C# ngăn không cho chúng ta dùng các giá trị chưa được khởi tạo nhưng khi dùng con trỏ thì điều này không còn đúng nửa.trình biên dịch không cách nào biết nội của amount2 mà ta trình bày, chỉ có ta biết.

kết tiếp ta sẽ tính toán trên con trỏ pCents,pCents hiện thời trỏ đến amount1.Cents , nhưng mục đích của ta là làm cho nó trỏ đến amount2.Cents .Làm điều này ta cần giảm địa chỉ của nó.ta cần làm một vài ép kiểu  :

   // do some clever casting to get pCents to point to cents
   // inside amount2
   CurrencyStruct *pTempCurrency = (CurrencyStruct*)pCents;
   pCents = (byte*) ( --pTempCurrency );
   Console.WriteLine("Address of pCents is now 0x{0:X}", (uint)&pCents);

Cuối cùng ta dùng vài từ khoá fixed để tạo ra một vài con trỏ mà trỏ đến các trường trong thể hiện lớp,và dùng những con trỏ này để thiết đặt giá trị của thể hiện này.Lưu ý rằng điều này cũng là lần đầu tiên ta thấy địa chỉ của một mục được lưu trữ trên heap hơn là trên stack:

   Console.WriteLine("\nNow with classes");
   // now try it out with classes
   CurrencyClass amount3 = new CurrencyClass();

   fixed(long *pDollars2 = &(amount3.Dollars))
   fixed(byte *pCents2 = &(amount3.Cents))
   {
      Console.WriteLine(
         "amount3.Dollars has address 0x{0:X}", (uint)pDollars2);
      Console.WriteLine(
         "amount3.Cents has address 0x{0:X}", (uint) pCents2);
      *pDollars2 = -100;
      Console.WriteLine("amount3 contains " + amount3);
   }

chạy chương trình ta có :

csc /unsafe PointerPlayaround2.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.

PointerPlayaround2
Size of Currency struct is 16
Address of amount1 is 0x12F8A8
Address of amount2 is 0x12F898
Address of pAmt is 0x12F894
Address of pDollars is 0x12F890
Address of pCents is 0x12F88C
amount1 contains $20.50
amount2 has address 0x12F898 and contains $5340121818976080.102
Address of pCents is now 0x12F88C

Now with classes
amount3.Dollars has address 0xBA4960
amount3.Cents has address 0xBA4968
amount3 contains $-100.0

Dùng con trỏ để tối ưu hoá thực thi

Sau đây ta sẽ áp dụng những hiểu biết về con trỏ và minh họa 1 ví dụ mà ta  thấy rõ lợi ích của việc dùng con trỏ trong thực thi

Tạo ra mảng có nền là Stack

Để tạo ra mảng này ta cần từ khoá  stackalloc. lệnh stackalloc chỉ dẫn thời gian chạy .NET để cấp phát 1 số vùng nhớ trên stack khi ta gọi nó ,ta cần cung cấp cho nó 2 thông tin

· Kiểu của biến mà ta muốn lưu trữ

· Ta cần lưu bao nhiêu biến

trong ví dụ , để cấp  phát đủ vùng nhớ lưu trữ 10 số thập phân decimal , ta viết :

decimal *pDecimals = stackalloc decimal [10];

lệnh này chỉ đơn giản cấp phát vùng nhớ. không khởi tạo bất kì giá trị nào.

Để lưu  20 số double ta viết :

double *pDoubles = stackalloc double [20];

mặc dù dòng mã này đặc tả số biến được lưu là hằng, điều này có thể  là 1 định giá số lượng vào lúc chạy. vì thế ta có thể viết tương đương với ví dụ trên như sau :

int size;
size = 20;   // or some other value calculated at run-time
double *pDoubles = stackalloc double [size];

Kiểu mảng cơ bản nhất mà có thể có là 1 khối bộ nhớ lưu các phần tử như sau :

CSharp: Simple Array

Câu hỏi được đặt ra là làm thế nào ta sử dụng vùng nhớ mà ta vừa tạo.trở lại ví dụ ta vừa nói rằng giá trị trả về từ stackalloc  trỏ đến bắt đầu của vùng nhớ.do đó  cho phép ta có thể lấy vị trí đầu tiên của vùng nhớ được cấp phát.ví dụ để cấp phát các số double  và thiết lập phần tử đầu tiên (phần tử 0 của mảng) giá trị 3.0 ta có thể viết :

double *pDoubles = stackalloc double [20];
*pDoubles = 3.0;

Ta có thể thiết lập phần tử thứ 2 của mảng bằng cách dùng cách tính toán trên con trỏ mà ta đã biết .Ví dụ nếu ta muốn đặt giá trị của phần tử thứ hai  ta làm như sau :

double *pDoubles = stackalloc double [20];
*pDoubles = 3.0;
*(pDoubles+1) = 8.4;

Nó chung ta có thể lấy phần tử thứ X của mảng với biểu thức *(pDoubles+X).

Bên cạnh đó C# cũng định nghĩa 1 cú pháp thay thế .Nếu p là con trỏ và X là kiểu số thì biểu thức p[X] tương đương với *(p+X).

double *pDoubles = stackalloc double [20];
pDoubles[0] = 3.0;   // pDoubles[0] is the same as *pDoubles
pDoubles[1] = 8.4;   // pDoubles[1] is the same as *(pDoubles+1)

Mặc dù mảng của ta có thể được truy xuất theo cùng cách như mảng bình thường, ta cần quan tâm đến cảnh báo sau . đoạn mã sau đây sẽ gây ra 1 biệt lệ:

double [] myDoubleArray = new double [20];
myDoubleArray[50] = 3.0;

Biệt lệ xuất hiện vì ta cố truy xuất vào mảng dùng chỉ mục vượt quá mảng ( chỉ mục là 50 , nhưng giá trị lớn nhất cho phép là 19).Tuy nhiên ,nếu ta khai báo 1 mảng dùng stackalloc , điều đó sẽ không gây ra biệt lệ:

double *pDoubles = stackalloc double [20];
pDoubles[50] = 3.0;

Ví dụ

Ta sẽ thảo luận về con trỏ với stackalloc trong ví dụ QuickArray. ví dụ hỏi  người dùng  bao nhiêu phần tử họ muốn cấp phát cho mảng. sau đó ta dùng stackalloc để cấp phát mảng với độ dài đó.các phần tử của mảng này được gán giá trị là bình phương của chỉ mục của nó .kết quả trình bày bên dưới :

using System;

namespace Wrox.ProCSharp.AdvancedCSharp
{
   class MainEntryPoint
   {
      static unsafe void Main()
      {
         Console.Write("How big an array do you want? \n> ");
         string userInput = Console.ReadLine();
         uint size = uint.Parse(userInput);

         long *pArray = stackalloc long [(int)size];
         for (int i=0 ; i<size ; i++)
            pArray[i] = i*i;

         for (int i=0 ; i<size ; i++)
            Console.WriteLine("Element {0} = {1}", i, *(pArray+i));
      }
   }
}

Kết quả:

How big an array do you want?
15
Element 0 = 0
Element 1 = 1
Element 2 = 4
Element 3 = 9
Element 4 = 16
Element 5 = 25
Element 6 = 36
Element 7 = 49
Element 8 = 64
Element 9 = 81
Element 10 = 100
Element 11 = 121
Element 12 = 144
Element 13 = 169
Element 14 = 196
» Tiếp: Delegate
« Trước: Singleton Design Pattern
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!