2009-04-17 51 views
37

Khi nào việc sử dụng con trỏ bằng bất kỳ ngôn ngữ nào yêu cầu ai đó sử dụng nhiều hơn một con trỏ, giả sử con trỏ ba. Khi nào nó có ý nghĩa để sử dụng một con trỏ ba thay vì chỉ sử dụng một con trỏ bình thường?Sử dụng cho nhiều cấp độ dereferences con trỏ?

Ví dụ:

char * * *ptr; 

thay vì

char *ptr; 
+19

Đây là câu hỏi rất _indirect_. –

+5

Để trở thành http://c2.com/cgi/wiki?ThreeStarProgrammer –

+0

Bạn cũng có thể thưởng thức [cdecl] (http://cdecl.org/) – bobobobo

Trả lời

57

mỗi ngôi sao cần được đọc là "mà được trỏ đến bởi một con trỏ" để

char *foo; 

là "char mà được trỏ đến bởi một foo trỏ". Tuy nhiên

char *** foo; 

là "char được trỏ tới bởi con trỏ trỏ đến con trỏ foo". Vì vậy foo là một con trỏ. Tại địa chỉ đó là con trỏ thứ hai. Tại địa chỉ được trỏ đến bởi đó là một con trỏ thứ ba. Dereferencing con trỏ thứ ba kết quả trong một char. Nếu đó là tất cả để có nó, nó khó khăn để làm cho nhiều của một trường hợp cho điều đó.

Vẫn có thể thực hiện một số công việc hữu ích. Hãy tưởng tượng chúng tôi đang viết một thay thế cho bash, hoặc một số chương trình kiểm soát quá trình khác. Chúng tôi muốn quản lý các quy trình của các quy trình của chúng tôi theo cách hướng đối tượng ...

struct invocation { 
    char* command; // command to invoke the subprocess 
    char* path; // path to executable 
    char** env; // environment variables passed to the subprocess 
    ... 
} 

Nhưng chúng tôi muốn làm điều gì đó lạ mắt. Chúng tôi muốn có một cách để duyệt tất cả các bộ biến môi trường khác nhau như được thấy bởi mỗi tiến trình con. để làm điều đó, chúng tôi thu thập mỗi bộ env thành viên từ các trường hợp gọi vào một mảng env_list và vượt qua nó để chức năng mà những giao dịch với điều đó:

void browse_env(size_t envc, char*** env_list); 
+4

Xin lỗi, tôi không phải là anglophone và tôi hầu như không hiểu câu _ "char được trỏ đến bởi một con trỏ được trỏ tới con trỏ trỏ tới con trỏ foo "_. Nếu _" foo "_ ở cuối câu, thì không phải câu là _" char được trỏ đến bởi con trỏ được trỏ đến ** BY ** một con trỏ được trỏ tới ** BY ** một con trỏ f oo "_? – eyquem

4

N-chiều mảng được cấp phát động, trong đó N> 3, yêu cầu ba hoặc nhiều hơn mức gián tiếp trong C.

+0

Ngoài ra, khi chuyển mảng hai chiều đến hàm sẽ sửa đổi nó. Nếu bạn có một ** ptr gấp đôi, bạn sẽ chuyển nó thành fn (& ptr), một hàm fn() sẽ có đối số fn (double *** ptr). –

+0

Đây là một chút sai lầm vì myarray [0] [1] [2] thường không thực sự là 3 con trỏ indirections (mặc dù nó có thể được). Bộ nhớ thường tiếp giáp và trình biên dịch thực hiện phép tính số học cho bạn. –

+0

Bản chỉnh sửa ở trên có giải quyết được mối quan tâm của bạn không? Tức là, chúng ta có thể đồng ý rằng mảng 3 chiều được phân bổ động được yêu cầu dereferencing 3+ con trỏ không? –

1
int main(int argc, char** argv); 
+0

Tôi nghĩ rằng nó có phần thành ngữ hơn để viết char * argv []. Đó là, một loạt các con trỏ char. Nhưng sau đó chúng tôi nhận được vào toàn bộ "mảng so với con trỏ" debacle (: –

+7

"Bạn có xem xét việc ăn hàu để được đạo đức và ăn ốc để được vô đạo đức? Tất nhiên là không. Nó là tất cả một vấn đề của hương vị, isn Và hương vị không giống như sự thèm ăn, và do đó không phải là một vấn đề đạo đức .... Hương vị của tôi bao gồm cả ốc và hàu. " – tpdi

3

Sử dụng tiêu chuẩn các con trỏ kép, ví dụ: myStruct ** ptrptr, là con trỏ đến con trỏ. Ví dụ như một tham số hàm, điều này cho phép bạn thay đổi cấu trúc thực tế mà người gọi đang trỏ đến, thay vì chỉ có thể thay đổi các giá trị trong cấu trúc đó.

0

Nếu bạn phải sửa đổi một con trỏ bên trong một hàm, bạn phải chuyển một tham chiếu đến nó.

5

Con trỏ chỉ đơn giản là biến chứa địa chỉ bộ nhớ.

Vì vậy, bạn sử dụng con trỏ trỏ đến con trỏ, khi bạn muốn giữ địa chỉ của biến con trỏ.

Nếu bạn muốn trả lại một con trỏ, và bạn đã sử dụng biến trả về cho một cái gì đó, bạn sẽ chuyển vào địa chỉ của một con trỏ. Hàm này sau đó dereferences con trỏ này để nó có thể thiết lập giá trị con trỏ. I E. tham số của hàm đó sẽ là một con trỏ trỏ đến một con trỏ.

Nhiều mức độ không đồng đều cũng được sử dụng cho các mảng đa chiều. Nếu bạn muốn trả về mảng 2 chiều, bạn sẽ sử dụng một con trỏ ba. Khi sử dụng chúng cho các mảng đa chiều mặc dù cẩn thận để đúc đúng cách khi bạn đi qua từng mức độ bất định.

Dưới đây là một ví dụ về việc trả lại một giá trị con trỏ qua một tham số:

//Not a very useful example, but shows what I mean... 
bool getOffsetBy3Pointer(const char *pInput, char **pOutput) 
{ 
    *pOutput = pInput + 3; 
    return true; 
} 

Và bạn gọi chức năng này như sau:

const char *p = "hi you"; 
char *pYou; 
bool bSuccess = getOffsetBy3Pointer(p, &pYou); 
assert(!stricmp(pYou, "you")); 
+0

bạn có thể bình luận phương thức getOffsetBy3Pointer vì tôi không hiểu làm thế nào con trỏ kép đi đến một con trỏ duy nhất – Jake

+0

* pOutput có kiểu char *. Cũng giống như với int ** p; * p là kiểu int *. –

0

Nó làm cho cảm giác sử dụng một con trỏ đến một con trỏ bất cứ khi nào con trỏ thực sự trỏ về phía một con trỏ (chuỗi này là không giới hạn, do đó "ba con trỏ" vv là có thể).

Lý do tạo mã như vậy là vì bạn muốn trình biên dịch/thông dịch viên có thể kiểm tra đúng loại bạn đang sử dụng (ngăn chặn các lỗi bí ẩn).

Bạn không phải sử dụng các loại như vậy - bạn luôn có thể sử dụng "void *" và typecast đơn giản bất cứ khi nào bạn cần thực sự dereference con trỏ và truy cập dữ liệu mà con trỏ hướng tới. Nhưng đó thường là thực hành xấu và dễ bị lỗi - chắc chắn có những trường hợp sử dụng void * thực sự tốt và làm cho mã thanh lịch hơn nhiều. Hãy nghĩ về nó giống như phương sách cuối cùng của bạn.

=> Phần lớn là để giúp trình biên dịch đảm bảo mọi thứ được sử dụng theo cách chúng được cho là được sử dụng.

1

Chức năng mà gói gọn tạo ra các nguồn lực thường sử dụng con trỏ kép. Nghĩa là, bạn chuyển vào địa chỉ của một con trỏ tới một tài nguyên. Sau đó, hàm có thể tạo tài nguyên được đề cập và đặt con trỏ trỏ đến nó. Điều này chỉ có thể nếu nó có địa chỉ của con trỏ được đề cập, vì vậy nó phải là một con trỏ kép.

0

Thành thật mà nói, tôi hiếm khi thấy một con trỏ ba.

Tôi liếc nhìn tìm kiếm mã google và có someexamples, nhưng không phải là rất sáng. (xem các liên kết ở cuối - SO không thích 'em)

Như những người khác đã đề cập, đôi con trỏ bạn sẽ thấy theo thời gian. Các con trỏ đơn giản là hữu ích vì chúng trỏ đến một số tài nguyên được phân bổ. Đôi con trỏ rất hữu ích vì bạn có thể chuyển chúng vào một hàm và có hàm điền vào con trỏ "đơn giản" cho bạn.

Có vẻ như bạn cần giải thích về con trỏ là gì và cách chúng hoạt động? Bạn cần phải hiểu rằng trước tiên, nếu bạn chưa biết.

Nhưng đó là một separatequestion (:

http://www.google.com/codesearch/p?hl=en#e_ObwTAVPyo/security/nss/lib/ckfw/capi/ckcapi.h&q=***%20lang:c&l=301

http://www.google.com/codesearch/p?hl=en#eVvq2YWVpsY/openssl-0.9.8e/crypto/ec/ec_mult.c&q=***%20lang:c&l=344

2

Bạn sử dụng một cấp thêm về mình - hoặc trỏ - khi cần thiết, không phải vì nó sẽ được vui vẻ Bạn hiếm khi thấy ba. Tôi không nghĩ rằng tôi đã từng nhìn thấy một con trỏ gấp bốn lần (và tâm trí của tôi sẽ boggle nếu tôi đã làm)

Bảng trạng thái có thể được đại diện bởi một mảng 2D của một kiểu dữ liệu thích hợp (con trỏ đến một cấu trúc, ví dụ). Khi tôi đã viết một số mã gần như chung chung để làm các bảng trạng thái, tôi nhớ có một hàm lấy một con trỏ ba - biểu diễn một mảng 2D của các con trỏ tới các cấu trúc. Ouch!

0

Con trỏ trỏ đến hiếm khi được sử dụng trong C++. Họ chủ yếu có hai cách sử dụng.

Cách sử dụng đầu tiên là chuyển một mảng. Ví dụ, char** là một con trỏ trỏ tới char, thường được sử dụng để truyền một chuỗi các chuỗi. Con trỏ tới mảng không hoạt động vì lý do chính đáng, nhưng đó là một chủ đề khác (xem comp.lang.c FAQ nếu bạn muốn biết thêm). Trong một số trường hợp hiếm hoi, bạn có thể thấy một số * thứ ba được sử dụng cho mảng mảng, nhưng thường hiệu quả hơn để lưu trữ mọi thứ trong một mảng liền kề và lập chỉ mục theo cách thủ công (ví dụ: array[x+y*width] thay vì array[x][y]). Tuy nhiên, trong C++, điều này ít phổ biến hơn vì các lớp chứa.

Cách thứ hai là chuyển qua tham chiếu. Tham số int* cho phép hàm sửa đổi số nguyên được trỏ đến bởi hàm gọi và thường được sử dụng để cung cấp nhiều giá trị trả lại. Mẫu tham số truyền qua tham chiếu này cho phép nhiều phép trả về vẫn tồn tại trong C++, nhưng, giống như các cách sử dụng khác của tham chiếu truyền qua, thường được thay thế bằng việc giới thiệu các tham chiếu thực tế. Lý do khác cho việc sao chép qua tham chiếu - tránh việc sao chép các cấu trúc phức tạp - cũng có thể với tham chiếu C++.

C++ có yếu tố thứ ba làm giảm việc sử dụng nhiều con trỏ: nó có string. Tham chiếu đến chuỗi có thể lấy loại char** trong C, do đó hàm có thể thay đổi địa chỉ của biến chuỗi mà nó được truyền, nhưng trong C++, chúng ta thường thấy string& thay thế.

0

Khi bạn sử dụng cấu trúc dữ liệu được phân bổ động (hoặc con trỏ được liên kết) lồng nhau. Những thứ này đều được liên kết bởi con trỏ.

3

Char *** foo có thể được hiểu là con trỏ đến mảng chuỗi hai chiều. Wand

4

của ImageMagicks có một hàm được khai báo là

WandExport char* * * * * * DrawGetVectorGraphics ( const DrawingWand *) 

I am not making this up.

+0

Ôi trời ơi, cái mà họ sử dụng. Đánh dấu các con trỏ 'NULL', gộp tất cả các tham số,' (void) 'các giá trị trả về. Tất cả những điều mà tôi nghĩ là không có lý do nào trong đồng bằng C. –

+0

nó không thể đúng và nó không phải. http://www.aerophile.org/ImageMagick/doxygen/html/drawing__wand_8c-source.html – u0b34a0f6ae

+0

@ u0b34a0f6ae Liên kết 404 của bạn bây giờ, nhưng tôi đã tìm thấy điều này: http://imagemagick.spd.co.il/www/api /MagickWand/drawing-wand_8h.html#85b1ab97c892eda1fc1d493148354ab2 –

10

Nếu bạn làm việc với "đối tượng" trong C, bạn có thể có này:

struct customer { 
    char *name; 
    char *address; 
    int id; 
} typedef Customer; 

Nếu bạn muốn tạo một đối tượng, bạn sẽ làm một cái gì đó như thế này:

Customer *customer = malloc(sizeof Customer); 
// Initialise state. 

Chúng tôi đang sử dụng con trỏ đến số struct tại đây vì struct đối số được chuyển theo giá trị và chúng tôi cần làm việc với một đối tượng. (Ngoài ra: Objective-C, một hướng đối tượng ngôn ngữ wrapper cho C, sử dụng con trỏ trong nội bộ nhưng rõ ràng để struct s.)

Nếu tôi cần phải lưu trữ nhiều đối tượng, tôi sử dụng một mảng:

Customer **customers = malloc(sizeof(Customer *) * 10); 
int customerCount = 0; 

Vì biến mảng trong C điểm cho mục đầu tiên, tôi sử dụng một con trỏ… một lần nữa. Bây giờ tôi có con trỏ kép.

Nhưng bây giờ hãy tưởng tượng tôi có một chức năng lọc mảng và trả về một mảng mới. Nhưng hãy tưởng tượng nó không thể thông qua cơ chế trả về vì nó phải trả về một mã lỗi — hàm của tôi truy cập cơ sở dữ liệu. Tôi cần phải làm điều đó thông qua một đối số tham chiếu.Đây là chữ ký chức năng của tôi:

int filterRegisteredCustomers(Customer **unfilteredCustomers, Customer ***filteredCustomers, int unfilteredCount, int *filteredCount); 

Chức năng mất một mảng của khách hàng và trả về một tham chiếu đến một mảng của khách hàng (mà là con trỏ đến một struct). Nó cũng lấy số lượng khách hàng và trả về số lượng khách hàng được lọc (một lần nữa, đối số tham chiếu).

tôi có thể gọi nó theo cách này:

Customer **result, int n = 0; 
int errorCode = filterRegisteredCustomers(customers, &result, customerCount, &n); 

tôi có thể đi vào tưởng tượng tình huống thêm ... Cái này là nếu không có sự typedef:

int fetchCustomerMatrix(struct customer ****outMatrix, int *rows, int *columns); 

Rõ ràng, tôi sẽ là một kinh khủng và/hoặc nhà phát triển tàn bạo để lại nó theo cách đó. Vì vậy, sử dụng:

typedef Customer *CustomerArray; 
typedef CustomerArray *CustomerMatrix; 

tôi có thể chỉ làm điều này:

int fetchCustomerMatrix(CustomerMatrix *outMatrix, int *rows, int *columns); 

Nếu ứng dụng của bạn được sử dụng trong một khách sạn nơi bạn sử dụng một ma trận cho mỗi cấp độ, có thể bạn sẽ cần một mảng để một ma trận :

int fetchHotel(struct customer *****hotel, int *rows, int *columns, int *levels); 

Hoặc chỉ này:

typedef CustomerMatrix *Hotel; 
int fetchHotel(Hotel *hotel, int *rows, int *columns, int *levels); 

Đừng làm cho tôi thậm chí bắt đầu trên một mảng của khách sạn: (? Một số loại tập đoàn khách sạn lớn)

int fetchHotels(struct customer ******hotels, int *rows, int *columns, int *levels, int *hotels); 

... sắp xếp theo một ma trận:

int fetchHotelMatrix(struct customer *******hotelMatrix, int *rows, int *columns, int *levels, int *hotelRows, int *hotelColumns); 

Những gì tôi đang cố gắng để nói là bạn có thể tưởng tượng các ứng dụng điên rồ cho nhiều người không đồng ý. Chỉ cần đảm bảo bạn sử dụng typedef nếu nhiều con trỏ là ý tưởng hay và bạn quyết định sử dụng chúng.

(Liệu đếm bài đăng này là một ứng dụng cho một SevenStarDeveloper?)

0

Riêng trong phương ngữ đơn luồng của C mà không tích cực sử dụng loại dựa trên phân tích răng cưa, đôi khi nó có thể hữu ích để viết các nhà quản lý bộ nhớ có thể chứa các đối tượng có thể định vị lại. Thay vì cho phép các ứng dụng trỏ trực tiếp tới các phần của bộ nhớ, ứng dụng sẽ nhận các con trỏ vào một bảng các bộ mô tả xử lý, mỗi con trỏ chứa một con trỏ tới một bộ nhớ thực cùng với một từ chỉ ra kích thước của nó. Nếu người ta cần phải phân bổ không gian cho một struct woozle, người ta có thể nói:

struct woozle **my_woozle = newHandle(sizeof struct woozle); 

và sau đó truy cập (hơi lúng túng trong cú pháp C - cú pháp là sạch hơn trong Pascal): (* my_woozle) -> someField = 23 ; điều quan trọng là các ứng dụng không phải là giữ các con trỏ trực tiếp đến bất kỳ mục tiêu xử lý nào qua các cuộc gọi đến các chức năng mà cấp phát bộ nhớ, nhưng nếu chỉ tồn tại một con trỏ duy nhất cho mỗi khối được xác định bằng cách xử lý trình quản lý bộ nhớ sẽ có thể di chuyển mọi thứ xung quanh trường hợp phân mảnh sẽ trở thành một vấn đề.

Cách tiếp cận không hoạt động gần như tốt trong thổ ngữ của C mà tích cực theo đuổi loại dựa trên răng cưa, kể từ khi con trỏ trả về bởi NewHandle không xác định một con trỏ kiểu struct woozle* nhưng thay vào đó xác định một con trỏ loại void* và thậm chí trên các nền tảng mà các loại con trỏ đó sẽ có cùng một biểu diễn tiêu chuẩn không yêu cầu triển khai giải thích con trỏ truyền như một dấu hiệu cho thấy nó có thể xảy ra việc tạo răng cưa .

0

Đôi hướng đơn giản hóa nhiều thuật toán cân bằng cây, nơi thường người ta muốn có thể "bỏ liên kết" một cách tinh tế một cây con từ cha mẹ của nó. Ví dụ, một cây thực hiện AVL có thể sử dụng:

void rotateLeft(struct tree **tree) { 
    struct tree *t = *tree, 
       *r = t->right, 
       *rl = r->left; 
    *tree = r; 
    r->left = t; 
    t->right = rl; 
} 

Nếu không có "con trỏ kép", chúng tôi sẽ phải làm một cái gì đó phức tạp hơn, như giữ một cách rõ ràng theo dõi của cha mẹ của một nút và cho dù đó là một nhánh trái hoặc phải.

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