C không khó lắm: void (* (* f []) ()) ()


188

Tôi chỉ thấy một hình ảnh ngày hôm nay và nghĩ rằng tôi đánh giá cao lời giải thích. Vì vậy, đây là hình ảnh:

một số mã c

Tôi thấy điều này khó hiểu và tự hỏi nếu các mã như vậy là bao giờ thực tế. Tôi đã googled hình ảnh và tìm thấy một hình ảnh khác trong mục reddit này , và đây là hình ảnh:

một số lời giải thích thú vị

Vì vậy, "đọc tinh thần" này là một cái gì đó hợp lệ? Đây có phải là cách trình biên dịch C phân tích cú pháp?
Sẽ thật tuyệt nếu có những giải thích đơn giản hơn cho mã kỳ lạ này.
Ngoài tất cả, các loại mã này có thể hữu ích? Nếu vậy, ở đâu và khi nào?

một câu hỏi về "quy tắc xoắn ốc", nhưng tôi không chỉ hỏi về cách áp dụng hoặc cách đọc các biểu thức với quy tắc đó. Tôi đang nghi ngờ sử dụng các biểu thức như vậy và tính hợp lệ của quy tắc xoắn ốc. Về những điều này, một số câu trả lời tốt đẹp đã được đăng.


9
Làm thế nào giải thích có thể đơn giản hơn? Nó bao gồm tất cả các khía cạnh của định nghĩa fw / một vài từ cho mỗi điểm chính.
Scott Hunter

29
Có lẽ C khó? Điều đó thực sự tuyên bố flà một loạt các con trỏ tới các hàm có thể đưa ra bất kỳ đối số nào .. nếu có void (*(*f[])(void))(void);, thì đúng, đó sẽ là các hàm không có đối số ...
txtechhelp

18
Trong thực tế, không mã hóa mã tối nghĩa như vậy. Sử dụng typedef cho chữ ký
Basile Starynkevitch

4
bất kỳ tuyên bố liên quan đến con trỏ chức năng có thể khó khăn. Điều đó không có nghĩa là C hoặc C ++ bình thường khó theo cách tương tự. Các ngôn ngữ khác giải quyết vấn đề này theo nhiều cách khác nhau bao gồm cả việc không có con trỏ hàm, có thể là một thiếu sót đáng kể trong một số trường hợp
Kate Gregory

20
Nếu bạn nheo mắt, nó trông giống như LISP.
2023861

Câu trả lời:


117

Có một quy tắc gọi là "Quy tắc theo chiều kim đồng hồ / xoắn ốc" để giúp tìm ra ý nghĩa của một tuyên bố phức tạp.

Từ c-faq :

Có ba bước đơn giản để làm theo:

  1. Bắt đầu với phần tử chưa biết, di chuyển theo hướng xoắn ốc / chiều kim đồng hồ; khi gặp các yếu tố sau thay thế chúng bằng các câu tiếng Anh tương ứng:

    [X]hoặc []
    => Kích thước mảng X của ... hoặc kích thước không xác định của ...

    (type1, type2)
    => hàm truyền type1 và type2 trả về ...

    *
    => con trỏ tới ...

  2. Tiếp tục làm điều này theo hướng xoắn ốc / theo chiều kim đồng hồ cho đến khi tất cả các mã thông báo đã được bảo hiểm.

  3. Luôn luôn giải quyết bất cứ điều gì trong ngoặc đơn đầu tiên!

Bạn có thể kiểm tra các liên kết ở trên cho ví dụ.

Cũng lưu ý rằng để giúp bạn, cũng có một trang web được gọi là:

http://www.cdecl.org

Bạn có thể nhập một khai báo C và nó sẽ cho ý nghĩa tiếng Anh của nó. Dành cho

void (*(*f[])())()

nó xuất ra:

khai báo f là mảng của con trỏ tới hàm trả về con trỏ thành hàm trả về void

BIÊN TẬP:

Như Random832 đã chỉ ra trong các nhận xét , quy tắc xoắn ốc không giải quyết được mảng và sẽ dẫn đến kết quả sai trong (hầu hết) các khai báo đó. Ví dụ cho int **x[1][2];quy tắc xoắn ốc bỏ qua thực tế []có quyền ưu tiên cao hơn *.

Khi ở trước mảng mảng, trước tiên người ta có thể thêm dấu ngoặc đơn rõ ràng trước khi áp dụng quy tắc xoắn ốc. Ví dụ: int **x[1][2];giống như int **(x[1][2]);(cũng là C hợp lệ) do quyền ưu tiên và quy tắc xoắn ốc sau đó đọc chính xác nó là "x là mảng 1 của mảng 2 của con trỏ tới con trỏ tới int", đó là khai báo tiếng Anh chính xác.

Lưu ý rằng vấn đề này cũng đã được đề cập trong câu trả lời này của James Kanze (được chỉ ra bởi haccks trong các bình luận).


5
Tôi ước cdecl.org tốt hơn
Người chơi Grady

8
Không có "quy tắc xoắn ốc" ... "int *** foo [] [] []" định nghĩa một mảng các mảng của các con trỏ tới con trỏ tới con trỏ. "Vòng xoắn ốc" chỉ xuất phát từ thực tế là tuyên bố này đã xảy ra để nhóm các thứ trong ngoặc đơn theo cách khiến chúng thay thế. Đó là tất cả mọi thứ ở bên phải, sau đó bên trái, trong mỗi bộ dấu ngoặc đơn.
Random832

1
@ Ngẫu nhiên với những tuyên bố phức tạp. IMHO, nó cực kỳ hữu ích và giúp bạn tiết kiệm khi gặp khó khăn hoặc khi cdecl.org không thể phân tích cú pháp khai báo. Tất nhiên người ta không nên lạm dụng các tuyên bố như vậy, nhưng thật tốt khi biết chúng được phân tích cú pháp như thế nào.
vsoftco

5
@vsoftco Nhưng nó không "di chuyển theo hướng xoắn ốc / chiều kim đồng hồ" nếu bạn chỉ quay đầu lại khi chạm tới dấu ngoặc đơn.
Random832

2
ouah, bạn nên đề cập rằng quy tắc xoắn ốc không phải là phổ quát .
haccks

105

Loại quy tắc "xoắn ốc" rơi ra khỏi các quy tắc ưu tiên sau:

T *a[]    -- a is an array of pointer to T
T (*a)[]  -- a is a pointer to an array of T
T *f()    -- f is a function returning a pointer to T
T (*f)()  -- f is a pointer to a function returning T

Các toán tử []gọi hàm và hàm con ()có độ ưu tiên cao hơn unary *, do đó *f()được phân tích cú pháp *(f())*a[]được phân tích cú pháp như *(a[]).

Vì vậy, nếu bạn muốn một con trỏ tới một mảng hoặc một con trỏ tới một hàm, thì bạn cần phải nhóm một cách rõ ràng *với mã định danh, như trong (*a)[]hoặc (*f)().

Sau đó, bạn nhận ra rằng afcó thể là các biểu thức phức tạp hơn chỉ là các định danh; trong T (*a)[N], acó thể là một định danh đơn giản hoặc có thể là một hàm gọi như (*f())[N]( a-> f()) hoặc nó có thể là một mảng như (*p[M])[N], ( a-> p[M]) hoặc nó có thể là một mảng các con trỏ tới các hàm như (*(*p[M])())[N]( a-> (*p[M])()), Vân vân.

Sẽ thật tuyệt nếu toán tử gián tiếp *là postfix thay vì unary, điều này sẽ làm cho các khai báo dễ đọc hơn từ trái sang phải ( void f[]*()*();chắc chắn chảy tốt hơn void (*(*f[])())()), nhưng thực tế không phải vậy.

Khi bạn gặp một khai báo lông như thế, hãy bắt đầu bằng cách tìm định danh ngoài cùng bên trái và áp dụng các quy tắc ưu tiên ở trên, áp dụng đệ quy chúng cho bất kỳ tham số chức năng nào:

         f              -- f
         f[]            -- is an array
        *f[]            -- of pointers  ([] has higher precedence than *)
       (*f[])()         -- to functions
      *(*f[])()         -- returning pointers
     (*(*f[])())()      -- to functions
void (*(*f[])())();     -- returning void

Các signalchức năng trong thư viện chuẩn có lẽ là mẫu kiểu cho các loại hình của sự điên rồ:

       signal                                       -- signal
       signal(                          )           -- is a function with parameters
       signal(    sig,                  )           --    sig
       signal(int sig,                  )           --    which is an int and
       signal(int sig,        func      )           --    func
       signal(int sig,       *func      )           --    which is a pointer
       signal(int sig,      (*func)(int))           --    to a function taking an int                                           
       signal(int sig, void (*func)(int))           --    returning void
      *signal(int sig, void (*func)(int))           -- returning a pointer
     (*signal(int sig, void (*func)(int)))(int)     -- to a function taking an int
void (*signal(int sig, void (*func)(int)))(int);    -- and returning void

Tại thời điểm này, hầu hết mọi người nói "sử dụng typedefs", đây chắc chắn là một lựa chọn:

typedef void outerfunc(void);
typedef outerfunc *innerfunc(void);

innerfunc *f[N];

Nhưng...

Làm thế nào bạn sẽ sử dụng f trong một biểu thức? Bạn biết đó là một mảng các con trỏ, nhưng làm thế nào để bạn sử dụng nó để thực hiện đúng chức năng? Bạn phải đi qua các typedefs và giải đúng cú pháp. Ngược lại, phiên bản "trần trụi" khá đẹp mắt, nhưng nó cho bạn biết chính xác cách sử dụng f trong một biểu thức (cụ thể là (*(*f[i])())();, giả sử không có hàm nào có đối số).


7
Cảm ơn vì đã đưa ra ví dụ về 'tín hiệu', cho thấy những loại điều này xuất hiện trong tự nhiên.
Jowersalt

Đó là một ví dụ tuyệt vời.
Casey

Tôi thích fcây giảm tốc của bạn , giải thích về quyền ưu tiên ... vì một số lý do tôi luôn bị loại khỏi nghệ thuật ASCII, đặc biệt là khi giải thích mọi thứ :)
txtechhelp

1
giả sử không có hàm nào lấy đối số : thì bạn phải sử dụng voidtrong ngoặc đơn hàm, nếu không nó có thể nhận bất kỳ đối số nào.
haccks

1
@haccks: để khai báo, vâng; Tôi đã nói về cuộc gọi chức năng.
John Bode

57

Trong C, khai báo phản ánh cách sử dụng, đó là cách nó được định nghĩa trong tiêu chuẩn. Khai báo:

void (*(*f[])())()

Là một khẳng định rằng biểu thức (*(*f[i])())()tạo ra kết quả của loại void. Nghĩa là:

  • f phải là một mảng, vì bạn có thể lập chỉ mục cho nó:

    f[i]
  • Các yếu tố fphải là con trỏ, vì bạn có thể hủy bỏ chúng:

    *f[i]
  • Những con trỏ đó phải là con trỏ tới các hàm không có đối số, vì bạn có thể gọi chúng:

    (*f[i])()
  • Kết quả của các hàm đó cũng phải là con trỏ, vì bạn có thể hủy bỏ chúng:

    *(*f[i])()
  • Những con trỏ đó cũng phải là con trỏ tới các hàm không có đối số, vì bạn có thể gọi chúng:

    (*(*f[i])())()
  • Những con trỏ hàm phải trả về void

Quy tắc xoắn ốc của người Viking là một cách ghi nhớ cung cấp một cách hiểu khác nhau về cùng một điều.


3
Cách nhìn tuyệt vời mà tôi chưa từng thấy trước đây. +1
tbodt

4
Đẹp. Nhìn theo cách này, nó thực sự là đơn giản . Trên thực tế khá dễ dàng hơn một cái gì đó như vector< function<function<void()>()>* > f, đặc biệt là nếu bạn thêm vào std::s. (Nhưng tốt, ví dụ này được đưa ra ... thậm chí f :: [IORef (IO (IO ()))]trông có vẻ kỳ lạ.)
leftaroundabout

1
@TimoDenk: Khai báo a[x]chỉ ra rằng biểu thức a[i]hợp lệ khi i >= 0 && i < x. Trong khi đó, a[]để lại kích thước không xác định và do đó giống hệt với *a: nó biểu thị rằng biểu thức a[i](hoặc tương đương *(a + i)) là hợp lệ cho một số phạm vi i.
Jon Purdy

4
Đây là cách dễ nhất để nghĩ về các loại C, cảm ơn vì điều này
Alex Ozer

4
Tôi thích điều này! Dễ dàng hơn nhiều để lý do hơn so với xoắn ốc ngớ ngẩn. (*f[])()là một loại bạn có thể lập chỉ mục, sau đó chọn tham chiếu, sau đó gọi, vì vậy nó là một mảng các con trỏ tới các hàm.
Lynn

32

Vì vậy, "đọc tinh thần" này là một cái gì đó hợp lệ?

Áp dụng quy tắc xoắn ốc hoặc sử dụng cdecl không phải lúc nào cũng hợp lệ. Cả hai đều thất bại trong một số trường hợp. Quy tắc xoắn ốc hoạt động cho nhiều trường hợp, nhưng nó không phải là phổ quát .

Để giải mã các khai báo phức tạp, hãy nhớ hai quy tắc đơn giản sau:

  • Luôn đọc các khai báo từ trong ra ngoài : Bắt đầu từ trong cùng, nếu có, dấu ngoặc đơn. Xác định vị trí định danh đang được khai báo và bắt đầu giải mã khai báo từ đó.

  • Khi có lựa chọn, luôn luôn ưu tiên []()hơn* : Nếu *đi trước định danh và []theo sau nó, định danh đại diện cho một mảng, không phải là một con trỏ. Tương tự, nếu *đi trước định danh và ()theo sau nó, định danh đại diện cho một chức năng, không phải là một con trỏ. (Dấu ngoặc luôn có thể được sử dụng để ghi đè lên ưu tiên bình thường của []()trên *.)

Quy tắc này thực sự liên quan đến việc ngoằn ngoèo từ một bên của định danh sang bên kia.

Bây giờ giải mã một khai báo đơn giản

int *a[10];

Áp dụng quy tắc:

int *a[10];      "a is"  
     ^  

int *a[10];      "a is an array"  
      ^^^^ 

int *a[10];      "a is an array of pointers"
    ^

int *a[10];      "a is an array of pointers to `int`".  
^^^      

Hãy giải mã khai báo phức tạp như

void ( *(*f[]) () ) ();  

bằng cách áp dụng các quy tắc trên:

void ( *(*f[]) () ) ();        "f is"  
          ^  

void ( *(*f[]) () ) ();        "f is an array"  
           ^^ 

void ( *(*f[]) () ) ();        "f is an array of pointers" 
         ^    

void ( *(*f[]) () ) ();        "f is an array of pointers to function"   
               ^^     

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer"
       ^   

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function" 
                    ^^    

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function returning `void`"  
^^^^

Đây là một GIF thể hiện cách bạn đi (nhấp vào hình ảnh để xem lớn hơn):

nhập mô tả hình ảnh ở đây


Các quy tắc được đề cập ở đây được lấy từ cuốn sách Lập trình C Hiện đại của KN KING .


Điều này giống như cách tiếp cận của tiêu chuẩn, tức là "sử dụng gương khai báo". Tôi muốn hỏi một điều khác vào thời điểm này: Bạn có đề xuất cuốn sách KN King không? Tôi đang xem rất nhiều đánh giá tốt đẹp về cuốn sách.
Motun

1
Vâng. Tôi đề nghị cuốn sách đó. Tôi bắt đầu lập trình từ cuốn sách đó. Văn bản tốt và các vấn đề trong đó.
haccks

Bạn có thể cung cấp một ví dụ về cdecl không hiểu một tuyên bố? Tôi nghĩ cdecl đã sử dụng các quy tắc phân tích cú pháp tương tự như trình biên dịch và theo như tôi có thể nói nó luôn hoạt động.
Fabio nói Phục hồi lại

@FabioTurati; Một hàm không thể trả về mảng hoặc hàm. char (x())[5]sẽ dẫn đến lỗi cú pháp, nhưng cdecl phân tích nó thành: khai báo xlà hàm trả về mảng 5 củachar .
haccks

12

Đó chỉ là một "vòng xoắn ốc" bởi vì trong phần khai báo này, chỉ có một toán tử ở mỗi bên trong mỗi cấp của dấu ngoặc đơn. Khẳng định rằng bạn tiến hành "theo hình xoắn ốc" thường sẽ đề nghị bạn xen kẽ giữa các mảng và con trỏ trong khai báo int ***foo[][][]khi trong thực tế tất cả các mức mảng đều xuất hiện trước bất kỳ mức con trỏ nào.


Chà, trong "cách tiếp cận xoắn ốc", bạn đi càng xa càng tốt, sau đó càng xa bên trái càng tốt, v.v. Nhưng nó thường được giải thích một cách sai lầm ...
Lynn

7

Tôi nghi ngờ các công trình như thế này có thể có bất kỳ sử dụng trong cuộc sống thực. Tôi thậm chí còn ghét chúng như những câu hỏi phỏng vấn cho các nhà phát triển thông thường (có thể OK cho các nhà văn biên dịch). typedefs nên được sử dụng thay thế.


3
Tuy nhiên, điều quan trọng là phải biết cách phân tích cú pháp, ngay cả khi chỉ biết cách phân tích cú pháp typedef!
inetknght

1
@inetknght, cách bạn làm điều đó với typedefs là làm cho chúng đủ đơn giản để không yêu cầu phân tích cú pháp.
SergeA

2
Những người hỏi những loại câu hỏi này trong các cuộc phỏng vấn chỉ làm điều đó để vuốt ve Egos của họ.
Casey

1
@JohnBode, và bạn sẽ tự làm cho mình bằng cách gõ giá trị trả về của hàm.
SergeyA

1
@JohnBode, tôi thấy đó là vấn đề lựa chọn cá nhân không đáng để bàn cãi. Tôi thấy sở thích của bạn, tôi vẫn có của tôi.
SergeyA

7

Như một sự thật nhỏ nhặt ngẫu nhiên, bạn có thể thấy thật thú vị khi biết rằng có một từ thực sự bằng tiếng Anh để mô tả cách đọc các khai báo C: Boustrophedonally , nghĩa là xen kẽ từ phải sang trái với trái sang phải.

Tham khảo: Van der Linden, 1994 - Trang 76


1
Từ đó không biểu thị bên trong như được lồng bởi parens hoặc trên một dòng duy nhất. Nó mô tả một mô hình "con rắn", với một dòng LTR theo sau là một dòng RTL.
Potatoswatter

5

Về tính hữu ích của việc này, khi làm việc với shellcode bạn thấy cấu trúc này rất nhiều:

int (*ret)() = (int(*)())code;
ret();

Mặc dù không hoàn toàn phức tạp về mặt cú pháp, mẫu đặc biệt này xuất hiện rất nhiều.

Ví dụ đầy đủ hơn trong câu hỏi SO này .

Vì vậy, trong khi tính hữu dụng trong phạm vi của bức tranh gốc là đáng nghi ngờ (tôi muốn đề xuất rằng bất kỳ mã sản xuất nào nên được đơn giản hóa mạnh mẽ), có một số cấu trúc cú pháp xuất hiện khá nhiều.


5

Khai báo

void (*(*f[])())()

chỉ là một cách nói mơ hồ

Function f[]

với

typedef void (*ResultFunction)();

typedef ResultFunction (*Function)();

Trong thực tế, sẽ cần nhiều tên mô tả hơn thay vì Hàm kết quảHàm . Nếu có thể tôi cũng sẽ chỉ định danh sách tham số là void.


4

Tôi thấy phương pháp được mô tả bởi Bruce Eckel là hữu ích và dễ làm theo:

Xác định một con trỏ hàm

Để xác định một con trỏ tới một hàm không có đối số và không có giá trị trả về, bạn nói:

void (*funcPtr)();

Khi bạn đang xem xét 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à tìm đường ra.Bắt đầu ở giữa có nghĩa là bắt đầu từ tên biến, đó là funcPtr. Bạn làm việc theo cách của bạn ra ngoài danh sách đối số trống chỉ ra một hàm không có đối số), sau đó nhìn sang trái (void, biểu thị hàm không có giá trị trả về). Chuyển động phải-trái-phải này hoạt động với hầu hết các khai báo.

Để đánh giá, bắt đầu ở giữa, giữa ((funcPtr là một ...), đi về bên phải (không có gì ở đó - bạn dừng lại ở dấu ngoặc đơn bên phải), đi bên trái và tìm '*' ( ... con trỏ tới ... ...), đi bên phải và tìm danh sách đối số trống (hàm ... không có đối số ... phạm lỗi), đi bên trái và tìm khoảng trống (funcPtr là một con trỏ tới một hàm không có đối số và trả về void void).

Bạn có thể tự hỏi tại sao * funcPtr yêu cầu dấu ngoặc đơn. Nếu bạn không sử dụng chúng, trình biên dịch sẽ thấy:

void *funcPtr();

Bạn sẽ khai báo một hàm (trả về một khoảng trống *) thay vì xác định một biến. Bạn có thể nghĩ trình biên dịch sẽ trải qua quá trình tương tự như bạn làm khi nó chỉ ra một tuyên bố hoặc định nghĩa được cho là gì. Nó cần những dấu ngoặc đơn đó để nâng cấp lên chống lại vì vậy nó quay về bên trái và tìm '*', thay vì tiếp tục sang phải và tìm danh sách đối số trống.

Khai báo và định nghĩa phức tạp

Bên cạnh đó, một khi bạn tìm ra cách cú pháp khai báo C và C ++ hoạt động, bạn có thể tạo các mục phức tạp hơn nhiều. Ví dụ:

//: C03:ComplicatedDefinitions.cpp

/* 1. */     void * (*(*fp1)(int))[10];

/* 2. */     float (*(*fp2)(int,int,float))(int);

/* 3. */     typedef double (*(*(*fp3)())[10])();
             fp3 a;

/* 4. */     int (*(*f4())[10])();


int main() {} ///:~ 

Đi qua từng cái và sử dụng hướng dẫn bên trái để tìm ra nó. Số 1 cho biết, fp1 là một con trỏ tới một hàm lấy một đối số nguyên và trả về một con trỏ tới một mảng gồm 10 con trỏ rỗng.

Số 2 cho biết, fp2 là một con trỏ tới một hàm có ba đối số (int, int và float) và trả về một con trỏ tới một hàm lấy một đối số nguyên và trả về một float.

Nếu bạn đang tạo ra nhiều định nghĩa phức tạp, bạn có thể muốn sử dụng một typedef. Số 3 cho thấy một typedef lưu cách gõ mô tả phức tạp mỗi lần. Nó nói rằng Một fp3 là một con trỏ tới một hàm không có đối số và trả về một con trỏ tới một mảng gồm 10 con trỏ tới các hàm không có đối số và trả về nhân đôi. Sau đó, nó nói là một trong những loại fp3. typedef thường hữu ích cho việc xây dựng các mô tả phức tạp từ những cái đơn giản.

Số 4 là một khai báo hàm thay vì định nghĩa biến. Nó nói rằng f4 f4 là một hàm trả về một con trỏ tới một mảng gồm 10 con trỏ tới các hàm trả về số nguyên.

Bạn sẽ hiếm khi cần những tuyên bố và định nghĩa phức tạp như vậy. Tuy nhiên, nếu bạn trải qua bài tập tìm ra chúng, bạn thậm chí sẽ không bị làm phiền nhẹ với những điều hơi phức tạp mà bạn có thể gặp trong đời thực.

Lấy từ: Suy nghĩ trong C ++ Tập 1, ấn bản thứ hai, chương 3, phần "Địa chỉ chức năng" của Bruce Eckel.


4

Hãy nhớ các quy tắc này cho C tuyên bố
Và quyền ưu tiên sẽ không bao giờ bị nghi ngờ:
Bắt đầu với hậu tố, tiến hành tiền tố
và đọc cả hai bộ từ trong ra ngoài.
- tôi, giữa những năm 1980

Trừ khi được sửa đổi bởi dấu ngoặc đơn, tất nhiên. Và lưu ý rằng cú pháp khai báo chính xác các cú pháp sử dụng biến đó để lấy một thể hiện của lớp cơ sở.

Nghiêm túc mà nói, điều này không khó để học trong nháy mắt; bạn chỉ cần sẵn sàng dành thời gian để rèn luyện kỹ năng. Nếu bạn sẽ duy trì hoặc điều chỉnh mã C được viết bởi người khác, thì chắc chắn đầu tư vào thời điểm đó. Đây cũng là một thủ thuật tiệc tùng thú vị để làm điên đảo những lập trình viên khác, những người chưa học nó.

Đối với mã của riêng bạn: như mọi khi, thực tế là một cái gì đó có thể được viết dưới dạng một lớp lót không có nghĩa là như vậy, trừ khi đó là một mẫu cực kỳ phổ biến đã trở thành một thành ngữ tiêu chuẩn (chẳng hạn như vòng lặp sao chép chuỗi) . Bạn và những người theo dõi bạn sẽ hạnh phúc hơn nhiều nếu bạn xây dựng các loại phức tạp từ các lớp typedefs và các cuộc hội thảo từng bước thay vì dựa vào khả năng của bạn để tạo ra và phân tích những thứ này "trong một lần sưng". Hiệu suất sẽ tốt như vậy, và khả năng đọc và bảo trì mã sẽ tốt hơn rất nhiều.

Nó có thể tồi tệ hơn, bạn biết đấy. Có một tuyên bố PL / I hợp pháp bắt đầu bằng một cái gì đó như:

if if if = then then then = else else else = if then ...

2
Câu lệnh PL / I đã IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIFđược phân tích thành if (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF).
Cole Johnson

Tôi nghĩ rằng có một phiên bản đã đưa nó đi xa hơn một bước bằng cách sử dụng biểu thức IF / THEN / ELSE có điều kiện (tương đương với C? :), đã đưa tập thứ ba vào hỗn hợp ... nhưng nó đã được vài thập kỷ và có thể có phụ thuộc vào một phương ngữ cụ thể của ngôn ngữ. Điểm vẫn là bất kỳ ngôn ngữ có ít nhất một hình thức bệnh lý.
keshlam

4

Tôi tình cờ là tác giả ban đầu của quy tắc xoắn ốc mà tôi đã viết ồ từ nhiều năm trước (khi tôi có rất nhiều tóc :) và được vinh danh khi nó được thêm vào cfaq.

Tôi đã viết quy tắc xoắn ốc như một cách để giúp học sinh và đồng nghiệp của tôi dễ dàng đọc các tuyên bố C "trong đầu" hơn; tức là, không phải sử dụng các công cụ phần mềm như cdecl.org, v.v. Tôi chưa bao giờ có ý định tuyên bố rằng quy tắc xoắn ốc là cách chính tắc để phân tích các biểu thức C. Mặc dù vậy, tôi rất vui khi thấy rằng quy tắc này đã giúp hàng ngàn sinh viên và học viên lập trình C thực sự trong những năm qua!

Đối với hồ sơ,

Nó đã được "xác định" chính xác nhiều lần trên nhiều trang web, bao gồm cả Linus Torvalds (một người mà tôi vô cùng tôn trọng), rằng có những tình huống mà quy tắc xoắn ốc của tôi "bị phá vỡ". Sinh vật phổ biến nhất:

char *ar[10][10];

Như được chỉ ra bởi những người khác trong chủ đề này, quy tắc có thể được cập nhật để nói rằng khi bạn gặp mảng, chỉ cần tiêu thụ tất cả các chỉ mục như thể được viết như sau:

char *(ar[10][10]);

Bây giờ, theo quy tắc xoắn ốc, tôi sẽ nhận được:

"ar là một mảng hai chiều 10 x 10 của con trỏ tới char"

Tôi hy vọng quy tắc xoắn ốc mang lại sự hữu ích của nó trong việc học C!

Tái bút

Tôi thích hình ảnh "C không khó" :)


3
  • khoảng trống (*(*f[]) ()) ()

Giải quyết void>>

  • (*(*f[]) ()) () = khoảng trống

Đang hồi sinh ()>>

  • (* (*f[]) ()) = trả về hàm (void)

Giải quyết *>>

  • (*f[]) () = con trỏ tới (hàm trả về (void))

Giải quyết ()>>

  • (* f[]) = Hàm trả về (con trỏ tới (hàm trả về (void)))

Giải quyết *>>

  • f[] = con trỏ tới (hàm trả về (con trỏ tới (hàm trả về (void))))

Giải quyết [ ]>>

  • f = mảng của (con trỏ tới (hàm trả về (con trỏ tới (hàm trả về (void)))))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.