2010-02-12 78 views
21

Khi làm việc với mảng và con trỏ trong C, người ta nhanh chóng phát hiện ra rằng chúng không có nghĩa là tương đương mặc dù nó có vẻ như vậy ngay từ cái nhìn đầu tiên. Tôi biết về sự khác biệt về giá trị L và giá trị R. Tuy nhiên, thời gian gần đây tôi đã cố gắng để tìm hiểu các loại hình một con trỏ mà tôi có thể sử dụng kết hợp với một mảng hai chiều, tức làGiải thích int (* a) [3]

int foo[2][3]; 
int (*a)[3] = foo; 

Tuy nhiên, tôi không thể tìm ra cách trình biên dịch "hiểu" các loại định nghĩa của một mặc dù các quy tắc ưu tiên toán tử thông thường cho *[]. Nếu thay vào đó tôi đã sử dụng một typedef, vấn đề trở nên đơn giản hơn đáng kể:

int foo[2][3]; 
typedef int my_t[3]; 
my_t *a = foo; 

Tại dòng dưới cùng, ai đó có thể trả lời cho tôi những câu hỏi như thế nào về lâu int (*a)[3] được đọc bởi trình biên dịch? int a[3] rất đơn giản, int *a[3] cũng đơn giản. Nhưng sau đó, tại sao nó không phải là int *(a[3])?

EDIT: Tất nhiên, thay vì "typecast" tôi có nghĩa là "typedef" (nó chỉ là một lỗi đánh máy).

+0

Bạn có thể quan tâm đến việc đọc http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/BitOp/pointer.html. – sand

+0

Chỉ một phần rất nhỏ của liên kết được cung cấp có liên quan đến câu hỏi ban đầu của tôi đã được trả lời chi tiết bằng các câu trả lời bên dưới. Cảm ơn câu trả lời của bạn anyway !! – fotNelton

+0

Xem thêm [Khai báo kiểu đọc C] (http://unixwiz.net/techtips/reading-cdecl.html), [Đọc bản Tuyên bố C: Hướng dẫn cho Bí ẩn] (http://www.ericgiguere.com/articles /reading-c-declarations.html). – outis

Trả lời

18

Trước tiên, ý của bạn là "typedef" not "typecast" trong câu hỏi của bạn.

Trong C, một con trỏ để gõ T có thể trỏ đến một đối tượng kiểu T:

int *pi; 
int i; 
pi = &i; 

Trên đây là đơn giản để hiểu. Bây giờ, hãy làm cho nó phức tạp hơn một chút. Bạn dường như biết sự khác biệt giữa các mảng và con trỏ (tức là, bạn biết rằng các mảng không phải là con trỏ, mặc dù chúng hoạt động giống như chúng). Vì vậy, bạn sẽ có thể hiểu:

int a[3]; 
int *pa = a; 

Nhưng cho đầy đủ vì: trong chuyển nhượng, tên a tương đương với &a[0], ví dụ: một con trỏ đến phần tử đầu tiên của mảng a. Nếu bạn không chắc chắn về cách thức và lý do tại sao các công trình này, có rất nhiều câu trả lời giải thích chính xác khi nào tên của một mảng "phân rã" tới con trỏ và khi nó không:

Tôi chắc chắn có nhiều câu hỏi và câu trả lời như vậy trên SO, tôi vừa đề cập đến một số câu hỏi mà tôi tìm thấy từ tìm kiếm.

Back to the topic: khi chúng ta có:

int foo[2][4]; 

foo là loại "mảng [2] của mảng [3] của int". Điều này có nghĩa là foo[0] là một mảng gồm 3 int s và foo[1] là một mảng gồm 3 int s.

Bây giờ, giả sử chúng tôi muốn khai báo một con trỏ và chúng tôi muốn gán con trỏ đó cho foo[0]. Đó là, chúng tôi muốn làm:

/* declare p somehow */ 
p = foo[0]; 

Trên đây là không có khác nhau ở dạng vào dòng int *pa = a;, bởi vì các loại afoo[0] đều giống nhau. Vì vậy, chúng tôi cần int *p; làm tuyên bố của chúng tôi về p.

Bây giờ, điều chính cần nhớ về mảng là "quy tắc" về tên mảng phân rã thành con trỏ đến phần tử đầu tiên chỉ áp dụng một lần. Nếu bạn có một mảng của một mảng, sau đó trong bối cảnh giá trị, tên của mảng sẽ không phân rã thành loại "con trỏ trỏ tới con trỏ", mà là "con trỏ tới mảng". Trở lại với foo:

/* What should be the type of q? */ 
q = foo; 

Tên foo trên là một con trỏ đến phần tử đầu tiên của foo, ví dụ, chúng ta có thể viết ở trên như:

q = &foo[0]; 

Loại foo[0] là "mảng [3] của int ".Vì vậy, chúng ta cần q là một con trỏ đến một "mảng [3] của int":

int (*q)[3]; 

Dấu ngoặc xung quanh q là cần thiết vì [] liên kết chặt chẽ hơn * trong C, vì vậy int *q[3] tuyên bố q như một mảng của con trỏ, và chúng tôi muốn một con trỏ đến một mảng. int *(q[3]) là, từ trên, tương đương với int *q[3], tức là một mảng gồm 3 con trỏ đến int.

Hy vọng điều đó sẽ hữu ích. Bạn cũng nên đọc C for smarties: arrays and pointers để có hướng dẫn thực sự tốt về chủ đề này.

Giới thiệu về khai báo đọc nói chung: bạn đọc chúng "từ trong ra ngoài", bắt đầu bằng tên của "biến" (nếu có). Bạn đi càng xa càng tốt trừ khi có [] ở ngay bên phải và bạn luôn luôn tôn trọng dấu ngoặc đơn. cdecl sẽ có thể giúp bạn đến một mức độ nào đó:

$ cdecl 
cdecl> declare p as pointer to array 3 of int 
int (*p)[3] 
cdecl> explain int (*p)[3] 
declare p as pointer to array 3 of int 

Để đọc

int (*a)[3]; 

     a   # "a is" 
    (*)   # parentheses, so precedence changes. 
        # "a pointer to" 
     [3]  # "an array [3] of" 
int  ;  # "int". 

Đối

int *a[3]; 

    a    # "a is" 
     [3]   # "an array [3] of" 
    *    # can't go right, so go left. 
        # "pointer to" 
int  ;   # "int". 

Đối

char *(*(*a[])())() 

      a   # "a is" 
      []  # "an array of" 
     *   # "pointer to" 
     ( )() # "function taking unspecified number of parameters" 
     (*  ) # "and returning a pointer to" 
       () # "function" 
char *    # "returning pointer to char" 

(Ví dụ từ c-faq question 1.21 Trong practi. ce, nếu bạn đang đọc một tuyên bố phức tạp như vậy, có điều gì đó nghiêm trọng sai với mã!)

+0

+1 để giải thích việc phân rã tên mảng thành con trỏ thành phần tử đầu tiên chỉ áp dụng một lần. – Andy

+0

Cảm ơn bạn rất nhiều vì câu trả lời chi tiết của bạn. Trong thực tế, tôi đã biết mảng và các công cụ con trỏ, nhưng tôi không biết về quy tắc-khai báo kiểu-đọc-kiểu-đọc. Những ví dụ rất hay! – fotNelton

2

Tại sao nội dung là "không phải int *(a[3])" chính xác (đề cập đến câu hỏi cuối cùng của bạn)? Các int *(a[3]) là giống như đồng bằng int *a[3]. Niềng răng thừa. Nó là một mảng gồm 3 con trỏ đến int và bạn đã nói bạn biết nó có ý nghĩa gì.

int (*a)[3] là con trỏ đến một mảng gồm 3 int (tức là con trỏ đến loại int[3]). Niềng răng trong trường hợp này là quan trọng. Bản thân bạn đã cung cấp một ví dụ chính xác, thực hiện một tuyên bố tương đương thông qua một số trung gian typedef.

Quy tắc đọc khai báo C phổ biến đơn giản trong suốt hoạt động trong trường hợp này là hoàn toàn tốt. Trong tuyên bố int *a[3], chúng tôi sẽ phải bắt đầu từ a và chuyển sang ngay trước, tức là để có được a[3], nghĩa là a là một mảng gồm 3 một cái gì đó (v.v.). () trong số int (*a)[3] thay đổi, vì vậy chúng tôi không thể chuyển sang phải và phải bắt đầu từ *a thay vào đó: a là con trỏ đến một cái gì đó. Tiếp tục giải mã tuyên bố, chúng tôi sẽ đi đến kết luận rằng điều gì đó là một mảng gồm 3 int.

Trong mọi trường hợp, hãy tìm một hướng dẫn tốt về đọc các khai báo C, trong đó có một số ít có sẵn trên Net.

4

Trình biên dịch có thể dễ hiểu hơn so với bạn. Một ngôn ngữ định nghĩa một tập hợp các quy tắc cho những gì cấu thành một biểu thức hợp lệ và trình biên dịch sử dụng các quy tắc này để diễn giải các biểu thức đó (có thể phức tạp nhưng các trình biên dịch đã được nghiên cứu về lâu dài và có rất nhiều thuật toán và công cụ chuẩn hóa sẵn có, bạn có thể muốn đọc khoảng parsing). cdecl là công cụ dịch biểu thức C sang tiếng Anh. Mã nguồn có sẵn trên trang web để bạn có thể kiểm tra. Cuốn sách Ngôn ngữ lập trình C++ bao gồm mã mẫu để viết một chương trình như vậy nếu tôi không lầm.

Như để giải thích các biểu thức chính mình, tôi thấy kỹ thuật tốt nhất để làm điều đó trong tư duy cuốn sách trong C++ khối lượng 1:

Để xác định một con trỏ đến một chức năng mà không có lý lẽ và không có giá trị trả về, bạn nói:

void (* funcPtr)();

Khi bạn đang tìm kiếm một định nghĩa phức tạp như thế này, cách tốt nhất để tấn công nó là bắt đầu ở giữa và làm việc theo cách của bạn. “Bắt đầu từ phần giữa” có nghĩa là bắt đầu tại tên biến số , là funcPtr. “Làm việc theo cách của bạn ra” có nghĩa là tìm sang bên phải cho mục gần (không có gì trong trường hợp này; quyền ngoặc dừng bạn ngắn), sau đó nhìn sang bên trái (một con trỏ ký hiệu bởi dấu hoa thị), sau đó tìm đến số bên phải (danh sách đối số trống cho biết hàm không cần đối số), sau đó tìm đến bên trái (khoảng trống, cho biết hàm không có giá trị trả về). Điều này chuyển động phải-trái-phải hoạt động với hầu hết các tuyên bố.

Rà soát, “bắt đầu ở giữa” (“funcPtr là một ...”), đi bên phải (không có gì ở đó - bạn đang dừng lại bởi các dấu ngoặc đúng ), đi sang bên trái và tìm '*' (“... con trỏ đến ...”), chuyển sang bên phải và tìm danh sách đối số trống rỗng (“... chức năng không có đối số ...”), đi đến bên trái và tìm khoảng trống (“funcPtr là một con trỏ đến hàm không có số đối số và trả về void”).

Bạn có thể tải xuống sách miễn phí trên Bruce Eckel's download site. Hãy thử áp dụng nó cho int (*a)[3]:

  1. Bắt đầu với tên ở giữa, trong trường hợp này là a. Cho đến nay nó đọc: 'a is a ...'
  2. Ở bên phải, bạn gặp phải dấu ngoặc đơn đóng đang dừng bạn, vì vậy bạn hãy chuyển sang bên trái và tìm dấu *. Bây giờ bạn đọc nó: 'a là con trỏ đến ...'
  3. Đi sang bên phải và bạn tìm thấy [3], nghĩa là một mảng gồm 3 phần tử. Biểu thức trở thành: 'a là con trỏ tới mảng 3 ...'
  4. Cuối cùng, bạn chuyển sang bên trái và tìm int, vì vậy cuối cùng bạn nhận được 'a là con trỏ tới một mảng gồm 3 int'.
+0

Cảm ơn, điều đó cũng thu hẹp nó xuống những gì tôi muốn hỏi. – fotNelton

2

Bạn có thể tìm thấy bài viết này hữu ích:

Tôi đã thực hiện này trả lời một cộng đồng wiki (kể từ khi nó chỉ là một liên kết đến một bài viết, không phải là một câu trả lời), nếu có ai muốn bổ sung thêm tài nguyên cho nó.

22

Nó khai báo một con trỏ tới một mảng là 3 int s.

Các dấu ngoặc đơn là cần thiết như sau tuyên bố một mảng của 3 con trỏ để int:

int* a[3]; 

Bạn nhận được khả năng đọc tốt hơn khi sử dụng typedef:

typedef int threeInts[3]; 
threeInts* pointerToThreeInts; 
+1

+1 Để xem xét khả năng đọc. –

+0

Xin lỗi, nhưng tôi không hiểu điểm của bạn. Điều này đã được nói trong OP? – fotNelton

+0

@kapuzineralex: Tất cả các câu trả lời từ ngày 15 tháng 4 đã được hợp nhất ở đây bằng các câu hỏi từ một câu hỏi khác và không thực sự phù hợp ở đây tôi đoán:/ –

53

Mỗi lần bạn có nghi ngờ với tờ khai phức tạp , bạn có thể sử dụng công cụ cdecl trong Unix như hệ thống:

[/tmp]$ cdecl 
Type `help' or `?' for help 
cdecl> explain int (*a)[10]; 
declare a as pointer to array 10 of int 

EDIT:

Ngoài ra còn có một phiên bản trực tuyến của công cụ này có sẵn here.

Nhờ Tiberiu Ana và gf

+3

Rực rỡ, học được điều gì đó, cảm ơn! – Thomas

+3

wow Tôi không biết về điều này, tuyệt vời +1 – ant

+2

Đừng quên cdecl trực tuyến: http://cdecl.org/ –

6

Nó có nghĩa

khai báo một như con trỏ đến mảng 3 của int

Xem cdecl để tham khảo trong tương lai.

+0

Cảm ơn, nhưng tôi đã biết ý nghĩa của nó. Câu hỏi của tôi nhắm vào trình biên dịch chứ không phải ý nghĩa cuối cùng của biểu thức. – fotNelton

2

Trong trường hợp bạn không biết khai báo là gì, tôi thấy rất hữu ích, được gọi là Right-left rule.

BTW. Một khi bạn đã học được nó, những tuyên bố như vậy là hòa bình của bánh :)

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