2009-02-27 37 views
6

Tôi đã phát triển được một thời gian rồi, và tôi đã không sử dụng con trỏ trong sự phát triển của tôi cho đến nay.Sử dụng con trỏ ở Delphi

Vì vậy, lợi ích của con trỏ là gì? Ứng dụng có chạy nhanh hơn hoặc sử dụng ít tài nguyên hơn không?

Bởi vì tôi chắc chắn rằng con trỏ là quan trọng, bạn có thể “chỉ” cho tôi một số bài viết, cơ bản nhưng tốt để bắt đầu sử dụng con trỏ trong Delphi? Google cho tôi quá nhiều kết quả quá đặc biệt.

Trả lời

30

Con trỏ là một biến trỏ đến một phần bộ nhớ. Những lợi thế là:

  • bạn có thể cung cấp cho bộ nhớ đó kích thước bạn muốn.
  • bạn chỉ phải thay đổi con trỏ để trỏ đến một phần bộ nhớ khác giúp tiết kiệm rất nhiều thời gian sao chép.

Delphi sử dụng nhiều con trỏ ẩn. Ví dụ: nếu bạn đang sử dụng:

var 
    myClass : TMyClass; 
begin 
    myClass := TMyClass.Create; 

myClass là con trỏ đến đối tượng.

Một ví dụ khác là mảng động. Đây cũng là một con trỏ.

Để hiểu thêm về con trỏ, bạn cần hiểu thêm về bộ nhớ. Mỗi phần dữ liệu có thể tồn tại trong các phần dữ liệu khác nhau.

Ví dụ các biến toàn cục:

unit X; 

interface 

var 
    MyVar: Integer; 

Một biến toàn cầu được định nghĩa trong datasegment. Các dữ liệu được cố định. Và trong suốt thời gian của chương trình, các biến này có sẵn. Có nghĩa là bộ nhớ không thể được sử dụng cho các mục đích sử dụng khác.

biến địa phương:

procedure Test; 
var 
    MyVar: Integer; 

Một biến cục bộ tồn tại trên stack. Đây là một phần của bộ nhớ được sử dụng để vệ sinh. Nó chứa các tham số cho hàm (ok một số được đặt trong một thanh ghi nhưng điều đó không quan trọng bây giờ). Nó chứa địa chỉ trả về để CPU biết nơi để trả về nếu chương trình đã kết thúc. Và nó chứa các biến cục bộ được sử dụng trong các hàm. Biến cục bộ chỉ tồn tại trong suốt thời gian tồn tại của một hàm. Nếu chức năng kết thúc, bạn không thể truy cập biến cục bộ một cách đáng tin cậy.

biến Heap:

procedure Test2; 
var 
    MyClass: TMyClass; 
begin 
    MyClass := TMyClass.Create; 

Biến MyClass là một con trỏ (mà là một biến cục bộ được định nghĩa trên stack). Bằng cách xây dựng một đối tượng, bạn cấp phát một phần bộ nhớ trên heap (phần lớn của bộ nhớ 'khác' không được sử dụng cho các chương trình và ngăn xếp). MyClass biến chứa địa chỉ của phần bộ nhớ này. Biến Heap tồn tại cho đến khi bạn giải phóng chúng. Điều đó có nghĩa rằng nếu bạn thoát khỏi Test2 funcion mà không giải phóng đối tượng, đối tượng vẫn tồn tại trên heap. Nhưng bạn sẽ không thể truy cập nó vì địa chỉ (biến MyClass) đã biến mất.

Thực hành tốt nhất

Người ta hầu như luôn luôn tốt nhất để phân bổ và deallocate một biến con trỏ cùng cấp.

Ví dụ:

var 
    myClass: TMyClass; 
begin 
    myClass := TMyClass.Create; 
    try 
    DoSomething(myClass); 
    DoSomeOtherthing(myClass); 
    finally 
    myClass.Free; 
    end; 
end; 

Nếu bạn có thể, cố gắng tránh các chức năng mà trả về một thể hiện của một đối tượng. Nó không bao giờ chắc chắn nếu người gọi cần phải vứt bỏ đối tượng. Và điều này tạo ra rò rỉ bộ nhớ hoặc sự cố.

+0

Hy vọng điều này là đủ thông tin, tôi có thể thêm thông tin nếu bạn muốn. –

+0

+1 Giải thích tuyệt vời. Làm tốt lắm! –

+0

Cảm ơn, tôi muốn giúp đỡ. Nhưng nó luôn luôn là tốt đẹp nếu nó được apreciated. –

2

Bạn có thể đã sử dụng con trỏ, nhưng bạn không biết điều đó. Một biến lớp là một con trỏ, một chuỗi là một con trỏ, một mảng động là một con trỏ, Delphi chỉ giấu nó cho bạn. Bạn sẽ thấy chúng khi bạn đang thực hiện các cuộc gọi API (truyền chuỗi sang PChar), nhưng thậm chí sau đó Delphi có thể ẩn rất nhiều.

Xem Gamecats trả lời để biết các ưu điểm của con trỏ.

Trong số About.com article bạn có thể tìm thấy giải thích cơ bản về con trỏ trong Delphi.

2

Con trỏ là cần thiết cho một số cấu trúc dữ liệu. Ví dụ đơn giản nhất là danh sách được liên kết. Ưu điểm của cấu trúc như vậy là bạn có thể kết hợp lại các phần tử mà không cần di chuyển chúng trong bộ nhớ. Ví dụ bạn có thể có một danh sách liên kết các đối tượng phức tạp lớn và hoán đổi bất kỳ hai đối tượng nào trong số chúng rất nhanh bởi vì bạn thực sự phải điều chỉnh hai con trỏ thay vì di chuyển các đối tượng này.

Điều này áp dụng cho nhiều ngôn ngữ bao gồm Object Pascal (Delphi).

10

Bạn đã được đưa ra rất nhiều câu trả lời tốt cho đến nay, nhưng bắt đầu với câu trả lời mà bạn đang đối phó với con trỏ khi bạn sử dụng chuỗi dài, mảng năng động và đối tượng tài liệu tham khảo bạn nên bắt đầu tự hỏi tại sao bạn sẽ sử dụng con trỏ, thay vì chuỗi dài, mảng động và tham chiếu đối tượng. Có lý do gì để vẫn sử dụng con trỏ, cho rằng Delphi làm một công việc tốt ẩn chúng khỏi bạn, trong nhiều trường hợp?

Hãy để tôi cung cấp cho bạn hai ví dụ về sử dụng con trỏ trong Delphi. Bạn sẽ thấy rằng điều này có lẽ không phù hợp với bạn nếu bạn chủ yếu viết các ứng dụng kinh doanh. Tuy nhiên, nó có thể trở nên quan trọng nếu bạn cần sử dụng các hàm API của Windows hoặc của bên thứ ba mà không được nhập bởi bất kỳ đơn vị Delphi chuẩn nào và không có đơn vị nhập (ví dụ) các thư viện JEDI. Và nó có thể là chìa khóa để đạt được tốc độ cuối cùng cần thiết trong mã xử lý chuỗi.

Con trỏ có thể được sử dụng để đối phó với các kiểu dữ liệu có kích thước khác nhau (chưa biết tại thời gian biên dịch)

xem xét Windows kiểu dữ liệu bitmap. Mỗi hình ảnh có thể có chiều rộng và chiều cao khác nhau và có các định dạng khác nhau từ màu đen và trắng (1 bit trên mỗi pixel) trên 2^4, 2^8, 2^16, 2^24 hoặc thậm chí 2^32 giá trị hoặc màu xám . Điều đó có nghĩa là nó không biết tại thời gian biên dịch bao nhiêu bộ nhớ một bitmap sẽ chiếm.

Trong windows.pas có là TBitmapInfo loại:

type 
    PBitmapInfo = ^TBitmapInfo; 
    tagBITMAPINFO = packed record 
    bmiHeader: TBitmapInfoHeader; 
    bmiColors: array[0..0] of TRGBQuad; 
    end; 
    TBitmapInfo = tagBITMAPINFO; 

Các TRGBQuad yếu tố mô tả một điểm ảnh duy nhất, nhưng bitmap không tất nhiên chứa nhiều hơn một pixel.Do đó người ta sẽ không bao giờ sử dụng một biến địa phương của loại TBitmapInfo, nhưng luôn luôn là một con trỏ đến nó:

var 
    BmpInfo: PBitmapInfo; 
begin 
    // some other code determines width and height... 
    ... 
    BmpInfo := AllocMem(SizeOf(TBitmapInfoHeader) 
    + BmpWidth * BmpHeight * SizeOf(TRGBQuad)); 
    ... 
end; 

Bây giờ sử dụng con trỏ, bạn có thể truy cập vào tất cả các pixel, mặc dù TBitmapInfo không chỉ có một duy nhất. Lưu ý rằng đối với mã như vậy bạn phải vô hiệu hóa kiểm tra phạm vi.

Các nội dung như vậy cũng có thể được xử lý với lớp TMemoryStream, về cơ bản là một trình bao bọc thân thiện xung quanh một con trỏ tới một khối bộ nhớ.

Và tất nhiên, việc tạo một TBitmap và chỉ định định dạng chiều rộng, chiều cao và pixel của nó dễ dàng hơn nhiều. Để nhà nước nó một lần nữa, VCL Delphi loại bỏ hầu hết các trường hợp con trỏ nếu không sẽ là cần thiết.

Con trỏ tới ký tự có thể được sử dụng để tăng tốc độ hoạt động chuỗi

Đây là, giống như hầu hết các tối ưu hóa vi, một cái gì đó chỉ được sử dụng trong những trường hợp cực đoan, sau khi bạn đã cấu hình và tìm thấy mã sử dụng chuỗi để tiêu thụ nhiều thời gian.

Thuộc tính tốt đẹp của chuỗi là chúng được tính tham chiếu. Việc sao chép chúng không sao chép bộ nhớ mà chúng chiếm, nó chỉ làm tăng số lượng tham chiếu thay thế. Chỉ khi mã cố sửa đổi một chuỗi có số tham chiếu lớn hơn 1 thì bộ nhớ sẽ được sao chép, để tạo chuỗi có số tham chiếu là 1, sau đó có thể sửa đổi một cách an toàn.

Thuộc tính không tốt đẹp của chuỗi là chúng được tính tham chiếu. Mọi hoạt động có thể có thể sửa đổi chuỗi phải đảm bảo rằng số tham chiếu là 1, bởi vì nếu không sửa đổi chuỗi sẽ là nguy hiểm. Thay thế một ký tự trong một chuỗi là một sửa đổi như vậy. Để đảm bảo rằng số tham chiếu là 1 cuộc gọi đến UniqueString() được trình biên dịch thêm vào bất cứ khi nào một ký tự trong chuỗi được ghi vào. Bây giờ viết n ký tự của một chuỗi trong một vòng lặp sẽ gây UniqueString() được gọi n lần, mặc dù sau khi lần đầu tiên được được đảm bảo rằng số lượng tài liệu tham khảo là 1. Điều này có nghĩa cơ bản n - 1 cuộc gọi UniqueString() được thực hiện không cần thiết.

Sử dụng con trỏ đến các ký tự là một cách phổ biến để tăng tốc các hoạt động chuỗi liên quan đến vòng lặp. Hãy tưởng tượng bạn muốn (cho mục đích hiển thị) để thay thế tất cả các không gian trong một chuỗi với một dấu chấm nhỏ. Sử dụng xem CPU của debugger và so sánh các mã thực thi mã

procedure MakeSpacesVisible(const AValue: AnsiString): AnsiString; 
var 
    i: integer; 
begin 
    Result := AValue; 
    for i := 1 to Length(Result) do begin 
    if Result[i] = ' ' then 
     Result[i] := $B7; 
    end; 
end; 

này với mã này

procedure MakeSpacesVisible(const AValue: AnsiString): AnsiString; 
var 
    P: PAnsiChar; 
begin 
    Result := AValue; 
    P := PAnsiChar(Result); 
    while P[0] <> #0 do begin 
    if P[0] = ' ' then 
     P[0] := $B7; 
    Inc(P); 
    end; 
end; 

Trong chức năng thứ hai sẽ có chỉ có một cuộc gọi đến UniqueString(), khi địa chỉ của ký tự chuỗi đầu tiên được gán cho con trỏ char.

Các vấn đề liên quan