Hiểu và sử dụng con trỏ trong Delphi

0
31


Mặc dù con trỏ không quan trọng trong Delphi như trong C hoặc C++ , nhưng chúng là một công cụ “cơ bản” đến mức hầu hết mọi thứ liên quan đến lập trình đều phải xử lý con trỏ theo một cách nào đó.

Vì lý do đó, bạn có thể đọc về cách một chuỗi hoặc đối tượng thực sự chỉ là một con trỏ hoặc trình xử lý sự kiện như OnClick thực sự là một con trỏ tới một thủ tục.

Con trỏ tới kiểu dữ liệu

Nói một cách đơn giản, một con trỏ là một biến chứa địa chỉ của bất kỳ thứ gì trong bộ nhớ.

Để làm cho định nghĩa này cụ thể hơn, hãy nhớ rằng mọi thứ mà một ứng dụng sử dụng được lưu trữ ở đâu đó trong bộ nhớ của máy tính. Bởi vì một con trỏ chứa địa chỉ của một biến khác, nên nó được gọi là trỏ đến biến đó.

Hầu hết thời gian, con trỏ trong Delphi trỏ đến một loại cụ thể:

var
iValue, j : số nguyên ;pIntValue : ^số nguyên;
bắt đầu
iValue := 2001;pIntValue := @iValue;...j:= pIntValue^;
kết thúc
;

pháp khai báo kiểu dữ liệu con trỏ sử dụng dấu mũ (^) . Trong đoạn mã trên, iValue là một biến số nguyên và pIntValue là một con trỏ số nguyên. Vì một con trỏ không gì khác hơn là một địa chỉ trong bộ nhớ, nên chúng ta phải gán cho nó vị trí (địa chỉ) của giá trị được lưu trữ trong biến số nguyên iValue.

Toán tử @ trả về địa chỉ của một biến (hoặc một hàm hoặc thủ tục như sẽ thấy bên dưới). Tương đương với toán tử @ là hàm Addr . Lưu ý rằng giá trị của pIntValue không phải là 2001.

Trong mã mẫu này, pIntValue là một con trỏ số nguyên đã nhập. Một phong cách lập trình tốt là sử dụng các con trỏ đã nhập càng nhiều càng tốt. Kiểu dữ liệu Con trỏ là một kiểu con trỏ chung; đại diện cho một con trỏ tới bất kỳ dữ liệu nào.

Lưu ý rằng khi “^” xuất hiện sau một biến con trỏ, nó hủy đăng ký con trỏ; nghĩa là nó trả về giá trị được lưu trữ tại địa chỉ bộ nhớ mà con trỏ có. Trong ví dụ này, biến j có cùng giá trị với iValue. Điều này có vẻ như không có mục đích gì khi chúng ta chỉ có thể gán iValue cho j, nhưng đoạn mã này nằm sau hầu hết các lệnh gọi Win API.

Ghi đè con trỏ

Con trỏ chưa được phân bổ là nguy hiểm. Vì con trỏ cho phép chúng tôi làm việc trực tiếp với bộ nhớ của máy tính, nếu chúng tôi cố gắng (do nhầm lẫn) ghi vào một vị trí được bảo vệ trong bộ nhớ, chúng tôi có thể gặp lỗi vi phạm quyền truy cập. Đây là lý do tại sao chúng ta phải luôn khởi tạo một con trỏ tới NIL.

NIL là một hằng số đặc biệt có thể được gán cho bất kỳ con trỏ nào. Khi nil được gán cho một con trỏ, con trỏ không tham chiếu gì cả. Ví dụ, Delphi trình bày một mảng động trống hoặc một chuỗi dài dưới dạng một con trỏ null.

con trỏ ký tự

Các loại cơ bản PAnsiChar và PWideChar đại diện cho các con trỏ tới các giá trị AnsiChar và WideChar. PChar chung đại diện cho một con trỏ tới biến Char.

Các con trỏ ký tự này được sử dụng để thao tác các chuỗi có đầu cuối bằng 0 . Hãy nghĩ về một PChar như một con trỏ tới một chuỗi kết thúc null hoặc mảng đại diện cho một chuỗi.

con trỏ tới bản ghi

Khi chúng ta xác định một bản ghi hoặc kiểu dữ liệu khác, thông thường chúng ta sẽ xác định một con trỏ tới kiểu đó. Điều này giúp dễ dàng thao tác với các thể hiện của loại mà không cần sao chép các khối bộ nhớ lớn.

Khả năng có con trỏ tới bản ghi (và mảng) giúp thiết lập cấu trúc dữ liệu phức tạp như danh sách và cây được liên kết dễ dàng hơn nhiều.


pNextItem = ^TLinkedListItem
TLinkedListItem = bản ghi sName : String;iValue : Integer;NextItem : pNextItem;
kết thúc
;

Ý tưởng đằng sau danh sách được liên kết là cung cấp cho chúng tôi khả năng lưu trữ địa chỉ của mục được liên kết tiếp theo trong danh sách trong trường bản ghi NextItem.

Ví dụ, con trỏ bản ghi cũng có thể được sử dụng khi lưu trữ dữ liệu tùy chỉnh cho từng mục trong chế độ xem dạng cây.

Các chỉ số về thủ tục và phương pháp

Một khái niệm con trỏ quan trọng khác trong Delphi là con trỏ thủ tục và phương thức.

Con trỏ trỏ đến địa chỉ của thủ tục hoặc hàm được gọi là con trỏ thủ tục. Con trỏ phương thức tương tự như con trỏ thủ tục. Tuy nhiên, thay vì trỏ đến các thủ tục độc lập, chúng nên trỏ đến các phương thức của lớp.

Con trỏ phương thức là một con trỏ chứa thông tin về tên và đối tượng được gọi.

API và con trỏ Windows

Việc sử dụng phổ biến nhất các con trỏ trong Delphi là để giao tiếp với mã C và C++, bao gồm quyền truy cập vào Windows API.

Các hàm Windows API sử dụng một số kiểu dữ liệu có thể không quen thuộc với lập trình viên Delphi. Hầu hết các tham số trong việc gọi hàm API là con trỏ tới một số loại dữ liệu. Như đã lưu ý ở trên, chúng tôi sử dụng các chuỗi kết thúc null trong Delphi khi gọi các hàm Windows API.

Trong nhiều trường hợp, khi một lệnh gọi API trả về một giá trị trong bộ đệm hoặc một con trỏ tới cấu trúc dữ liệu, ứng dụng phải phân bổ các bộ đệm và cấu trúc dữ liệu này trước khi thực hiện lệnh gọi API. Chức năng SHBrowseForFolder Windows API là một ví dụ.

Con trỏ và cấp phát bộ nhớ

Sức mạnh thực sự của con trỏ đến từ khả năng dự trữ bộ nhớ trong khi chương trình đang chạy.

Đoạn mã này đủ để chứng minh rằng làm việc với con trỏ không khó như lúc đầu. Nó được sử dụng để thay đổi văn bản (tiêu đề) của điều khiển bằng mã định danh được cung cấp.

thủ tục GetTextFromHandle(hWND: THandle) ; 
var
pText: PChar; // một con trỏ tới char (xem ở trên) TextLen : số nguyên;
bắt đầu

{ lấy độ dài văn bản }
TextLen:=GetWindowTextLength(hWND) ;
{cấp phát bộ nhớ}

GetMem(pText,TextLen) ; // lấy con trỏ
{lấy văn bản của điều khiển}
GetWindowText(hWND, pText, TextLen + 1) ;
{hiển thị văn bản}
ShowMessage(String(pText))
{bộ nhớ trống}
FreeMem(pText) ;
kết thúc
;