C o kadar da zor değil: void (* (* f []) ()) ()


188

Bugün bir resim gördüm ve açıklamaları takdir edeceğimi düşünüyorum. İşte resim:

bazı c kodu

Bunu kafa karıştırıcı buldum ve böyle kodların hiç pratik olup olmadığını merak ettim. Resmi googled ve bu reddit girişinde başka bir resim buldum ve işte bu resim:

bazı ilginç açıklamalar

Peki bu "spiral okuma" geçerli bir şey mi? C derleyicileri bu şekilde ayrışır mı?
Bu garip kod için daha basit açıklamalar olması harika olurdu.
Her şeyden öte, bu tür kodlar faydalı olabilir mi? Varsa, nerede ve ne zaman?

Var bir soru "sarmal kuralı" hakkında, ama sadece o kuralla okunur nasıl uygulanacağına ya da nasıl ifadelerin hakkında sormuyorum. Bu tür ifadelerin kullanımını ve spiral kuralın geçerliliğini de sorgularım. Bunlarla ilgili olarak bazı güzel cevaplar zaten yayınlanmıştır.


9
Açıklama nasıl daha basit olabilir? Her anahtar nokta için fw / birkaç kelime tanımının tüm yönlerini kapsar.
Scott Hunter

30
Belki C zor mu? Bu aslında fherhangi bir argüman alabilen fonksiyonlara bir işaretçi dizisi olarak ilan edilir .. Eğer void (*(*f[])(void))(void);öyleyse, evet, hiç argüman almayan fonksiyonlar olurdu ...
txtechhelp

18
Uygulamada, bu tür belirsiz kodları kodlamayın. İmzalar için typedef kullanın
Basile Starynkevitch

4
işlev işaretçileri içeren herhangi bir bildirim zor olabilir. Bu, normal C veya C ++ 'nın aynı şekilde zor olduğu anlamına gelmez. Diğer diller, bazı durumlarda önemli bir eksiklik olan işlev işaretçileri olmamak da dahil olmak üzere bunu farklı şekillerde çözmektedir
Kate Gregory

20
Gözlerinizi kısarsanız, LISP'ye benziyor.
user2023861

Yanıtlar:


117

Karmaşık bir bildirimin anlamını bulmanıza yardımcı olmak için "Clockwise / Spiral Rule" adlı bir kural vardır .

Gönderen c-sss :

İzlenmesi gereken üç basit adım vardır:

  1. Bilinmeyen elemandan başlayarak spiral / saat yönünde hareket edin; aşağıdaki öğeleri karşılıyorken, bunları karşılık gelen İngilizce ifadelerle değiştirin:

    [X]veya []
    => Dizi X boyutu ... veya Dizi tanımlanmamış boyutu ...

    (type1, type2)
    => tip1 ve tip2 geri dönen fonksiyon ...

    *
    => işaretçi (ler) e ...

  2. Tüm jetonlar kaplanana kadar bunu spiral / saat yönünde yapmaya devam edin.

  3. Önce parantez içindeki her şeyi çözün!

Örnekler için yukarıdaki bağlantıyı kontrol edebilirsiniz.

Ayrıca, size yardımcı olacak bir web sitesi de bulunduğunu unutmayın:

http://www.cdecl.org

Bir C beyanı girebilirsiniz ve bu İngilizce anlamını verecektir. İçin

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

çıktılar:

f işlevini döndüren işlev için işaretçi dizisi olarak bildir

DÜZENLE:

Random832 tarafından yapılan yorumlarda belirtildiği gibi, spiral kuralı diziler dizisini ele almaz ve bu bildirimlerde (çoğunun) yanlış bir sonuca yol açar. Örneğin int **x[1][2];spiral kural [], daha yüksek önceliğe sahip olan gerçeği göz ardı eder *.

Dizilerin önündeyken, önce spiral kuralı uygulanmadan önce açık parantez eklenebilir. Örneğin: önceliğe bağlı int **x[1][2];olarak int **(x[1][2]);(geçerli C) ile aynıdır ve spiral kuralı doğru olarak "x işaretçisi int'e işaretçi dizisinin 2. dizisinin bir dizisidir" olan doğru ingilizce bildirimdir.

Bu sorunu da bu kaplı edildiğini Not cevap tarafından James Kanze (tarafından işaret haccks yorumlarda).


5
Keşke cdecl.org daha iyi olsaydı
Grady Oyuncu

8
Hiçbir "sarmal kuralı" ... "int *** foo [] [] []" işaretçiler dizilerinden işaretçilere işaretçiler dizilerinden oluşan bir dizi tanımlar. "Spiral" sadece bu bildirimin parantez içindeki şeyleri değişime neden olacak şekilde gruplandırdığı gerçeğinden kaynaklanmaktadır. Her parantez içinde sağdaki, sonra soldaki her şey.
Random832

1
@ Random832 Bir "sarmal kural" vardır ve az önce bahsettiğiniz durumu kapsar, yani parantez / dizilerle nasıl başa çıkılacağı hakkında konuşur. Elbette Standart C kuralı değil, nasıl başa çıkılacağını anlamak için iyi bir anımsatıcı karmaşık beyanlarla. IMHO, son derece kullanışlıdır ve belada olduğunda veya cdecl.org bildirimi ayrıştıramadığında sizi kurtarır . Tabii ki bu tür bildirimleri kötüye kullanmamak gerekir, ancak bunların nasıl ayrıştırıldığını bilmek iyidir.
vsoftco

5
@vsoftco Ancak yalnızca parantezlere ulaştığınızda geriye dönerseniz "spiral / saat yönünde hareket etmek" olmaz.
Random832


105

"Spiral" kural türü aşağıdaki öncelik kurallarından çıkar:

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

Alt simge []ve işlev çağrısı ()işleçleri, tekli olandan daha yüksek önceliğe sahiptir *, bu nedenle *f()şu şekilde ayrıştırılır *(f())ve *a[]şu şekilde ayrıştırılır*(a[]) .

Bu nedenle, bir diziye bir işaretçi veya bir işleve bir işaretçi istiyorsanız, açık bir şekilde *tanımlayıcıyla (*a)[]veya(*f)() .

O zaman bunu fark edersiniz ave fsadece tanımlayıcılardan daha karmaşık ifadeler olabilir; içinde T (*a)[N], abasit bir tanımlayıcı olabilir veya (*f())[N]( a-> f()) gibi bir işlev çağrısı olabilir veya (*p[M])[N], ( a-> p[M]) gibi bir dizi olabilir veya (*(*p[M])())[N]( a-> (*p[M])()) gibi işlevlere yönelik bir işaretçi dizisi olabilir , vb.

Dolaylı işlecin *tekli yerine postfix olması güzel olurdu , bu da bildirimleri soldan sağa okumayı biraz daha kolay hale getirir ( void f[]*()*();kesinlikle daha iyi akar void (*(*f[])())()), ama değil.

Böyle kıllı bir bildirimle karşılaştığınızda, en soldaki tanımlayıcıyı bulun ve yukarıdaki öncelik kurallarını uygulayın ve bunları herhangi bir işlev parametresine tekrar tekrar uygulayın:

         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

signalStandart kütüphanede fonksiyon muhtemelen cinnet bu tür tipi örnektir:

       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

Bu noktada çoğu insan "typedefs kullan" der ki bu kesinlikle bir seçenektir:

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

innerfunc *f[N];

Fakat...

Bir ifadede nasıl kullanırsınız f ? Bunun bir işaretçi dizisi olduğunu biliyorsunuz, ancak bunu doğru işlevi yürütmek için nasıl kullanıyorsunuz? Typedefs üzerinden gitmek ve doğru sözdizimi bulmaca gerekir. Buna karşılık, "çıplak" sürüm oldukça göz alıcıdır, ancak tam olarak bir ifadede nasıl kullanılacağını anlatır f(yani, (*(*f[i])())();hiçbir fonksiyonun argüman almadığını varsayarsak).


7
Bu tür şeylerin vahşi doğada ortaya çıktığını gösteren 'sinyal' örneğini verdiğiniz için teşekkür ederiz.
Justsalt

Bu harika bir örnek.
Casey

fYavaşlama ağacınızı beğendim , önceliği açıkladım ... bir sebepten dolayı ASCII sanatından her zaman bir tekme alıyorum, özellikle de şeyleri açıklamaya gelince :)
txtechhelp

1
işlevlerin hiçbirinin bağımsız değişken almadığını varsayarsak : voidişlev parantezinde kullanmak zorunda kalırsınız , aksi takdirde herhangi bir bağımsız değişken alabilir.
16:22

1
@haccks: beyan için evet; Ben fonksiyon çağrısından bahsediyordum.
John Bode

57

C dilinde, beyanname kullanımı yansıtmaktadır — standartta bu şekilde tanımlanmaktadır. Deklarasyon:

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

İfadenin (*(*f[i])())()tür sonucu oluşturduğuna dair bir iddiadır void. Bunun anlamı:

  • f dizine ekleyebileceğiniz için bir dizi olmalıdır:

    f[i]
  • fOnlarının referansını kaldırabileceğiniz için öğeleri işaretçiler olmalıdır:

    *f[i]
  • Bu işaretçiler, argüman almayan işlevlere işaret etmelidir, çünkü onları çağırabilirsiniz:

    (*f[i])()
  • Bu işlevlerin sonuçları da işaretçiler olmalıdır, çünkü bunları kaldırabilirsiniz:

    *(*f[i])()
  • Bu işaretçiler, argüman almayan işlevlere de işaret etmelidir , çünkü bunları çağırabilirsiniz:

    (*(*f[i])())()
  • Bu işlev işaretçileri geri dönmelidir void

“Spiral kural” aynı şeyi anlamanın farklı bir yolunu sunan bir anımsatıcıdır.


3
Daha önce hiç görmediğim harika bir bakış açısı. +1
tbodt

4
Güzel. Bu şekilde göründüğünde, gerçekten basit . Gibi bir şey daha Aslında oldukça kolay vector< function<function<void()>()>* > fsen eklemek, özellikle std::s. (İyi, örnek Ama edilir yapmacık ... hatta f :: [IORef (IO (IO ()))]tuhaf görünüyor.)
leftaroundabout

1
@TimoDenk: Bildirim a[x], ifadenin a[i]ne zaman geçerli olduğunu gösterir i >= 0 && i < x. Oysa, a[]boyutu belirtilmemiş olarak bırakır ve bu nedenle aşağıdakilerle aynıdır *a: ifadenin a[i](veya eşdeğer olarak *(a + i)) bazı aralıklar için geçerli olduğunu gösterir i.
Jon Purdy

4
Bu, C tipleri hakkında düşünmenin en kolay yoludur, bunun için teşekkürler
Alex Ozer

4
Bunu seviyorum! Akıl almaz spirallere göre akıl yürütmek çok daha kolay. (*f[])()dizine ekleyebileceğiniz, sonra dereference, sonra çağrı yapabileceğiniz bir türdür, bu nedenle işlevlere yönelik bir işaretçi dizisidir.
Lynn

32

Peki bu "spiral okuma" geçerli bir şey mi?

Spiral kural uygulamak veya cdecl kullanmak her zaman geçerli değildir. Her ikisi de bazı durumlarda başarısız olur. Spiral kural birçok durumda çalışır, ancak evrensel değildir .

Karmaşık bildirimleri deşifre etmek için şu iki basit kuralı hatırlayın:

  • Bildirimleri her zaman içten dışa okuyun : Varsa en içteki parantezden başlayın. Bildirilen tanımlayıcıyı bulun ve bildirimi deşifre etmeye buradan başlayın.

  • Bir seçenek, her zaman iyilik []ve ()aşırı* : If* Tanımlayıcıdan önce gelir ve []onu izlerse, tanımlayıcı bir işaretçi değil, bir diziyi temsil eder. Benzer şekilde, *tanımlayıcıdan önce gelir ve ()onu izlerse, tanımlayıcı bir işaretçiyi değil bir işlevi temsil eder. (Parantez her zaman normal öncelik geçersiz kılmak için kullanılabilir []ve ()fazla *).

Bu kural aslında tanımlayıcının bir tarafından diğer tarafına zikzak çizmeyi içerir .

Şimdi basit bir beyanı deşifre etmek

int *a[10];

Kural uygulanıyor:

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`".  
^^^      

Gibi karmaşık beyanı deşifre edelim

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

Yukarıdaki kuralları uygulayarak:

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`"  
^^^^

İşte nasıl gittiğini gösteren bir GIF (daha büyük görüntü için resme tıklayın):

resim açıklamasını buraya girin


Burada sözü edilen kurallar , KN KING tarafından C Modern Yaklaşım Programlama kitabından alınmıştır .


Bu tıpkı standardın yaklaşımı gibidir, yani "beyan ayna kullanımı". Bu noktada başka bir şey daha sormak istiyorum: KN King'in kitabını önerir misin? Kitap hakkında çok güzel yorumlar görüyorum.
Motun

1
Evet. Bu kitabı öneririm. O kitaptan programlamaya başladım. İyi metinler ve sorunlar var.
59'da haccks

Beyannameyi anlamayan bir cdecl örneği verebilir misiniz? CDecl'in derleyicilerle aynı ayrıştırma kurallarını kullandığını ve her zaman işe yaradığını söyleyebildiğim kadarıyla düşündüm.
Fabio, Monica

@FabioTurati; Bir işlev dizileri veya işlevi döndüremez. char (x())[5]sözdizimi hatasına neden gerekirdi ama, olarak ayrıştırmak CDECL: declare xişlevi dizisini 5 dönmeden olarakchar .
16:16

12

Bu sadece bir "spiral" dir, çünkü bu beyanda, her bir parantez düzeyi içinde her iki tarafta yalnızca bir operatör vardır. "Spiral olarak" ilerlediğinizi iddia etmek int ***foo[][][], gerçekte tüm dizi seviyelerinin herhangi bir işaretçi seviyesinden önce gelmesi durumunda , bildirideki diziler ve işaretçiler arasında geçiş yapmanızı önerir .


Eh, "sarmal yaklaşımı" içinde, en sağdaki sen olabildiğince sonra kadarıyla vb sol Ama genellikle ... yanlışlıkla anlatılmış, mümkün olduğunca gitmek
Lynn

7

Bunun gibi yapıların gerçek hayatta herhangi bir yararı olabileceğinden şüpheliyim. Hatta düzenli geliştiriciler için röportaj soruları olarak (hatta derleyici yazarlar için sorun yok) onları nefret ediyorum. Bunun yerine typedefs kullanılmalıdır.


3
Yine de, typedef'in nasıl ayrıştırılacağını bilseler bile, nasıl ayrıştırılacağını bilmek önemlidir!
inetknght

1
@inetknght, typedefs ile yaptığınız yol onları ayrıştırmaya gerek kalmayacak kadar basit yapmaktır.
SergeyA

2
Mülakatlar sırasında bu tür sorular soran insanlar bunu sadece Egos'larını felç etmek için yaparlar.
Casey

1
@JohnBode ve işlevin dönüş değerini typedefing yaparak kendinize bir iyilik yaparsınız.
SergeyA

1
@ JohnBode, tartışmaya değmez kişisel bir seçim meselesi buluyorum. Tercihini görüyorum, hala benim var.
SergeyA

7

Rastgele bir trivia factoid olarak, C bildirimlerinin nasıl okunduğunu açıklamak için İngilizce'de gerçek bir kelime olduğunu bilmek eğlenceli olabilir: Boustrophedonically , yani sağdan sola, sağdan sola değişiyor .

Referans: Van der Linden, 1994 - Sayfa 76


1
Bu kelime göstermez içinde Pars ya da tek bir hat üzerinde iç içe olduğu gibi. Bir LTR çizgisi ve ardından bir RTL çizgisi olan bir "yılan" kalıbını açıklar.
Potatoswatter

5

Bunun yararına gelince, shellcode ile çalışırken bu yapıyı çok fazla görüyorsunuz:

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

Sözdizimsel olarak karmaşık olmasa da, bu özel desen çok ortaya çıkıyor.

Bu SO sorusunda daha eksiksiz bir örnek .

Bu nedenle, orijinal resimdeki kapsamın yararlılığı sorgulanabilir olsa da (herhangi bir üretim kodunun büyük ölçüde basitleştirilmesi gerektiğini söyleyebilirim), biraz ortaya çıkan bazı sözdizimsel yapılar var.


5

Deklarasyon

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

sadece belirsiz bir söylem şekli

Function f[]

ile

typedef void (*ResultFunction)();

typedef ResultFunction (*Function)();

Uygulamada, ResultFunction ve Function yerine daha açıklayıcı adlara ihtiyaç duyulacaktır . Mümkünse parametre listelerini de şöyle belirtirim void.


4

Bruce Eckel tarafından açıklanan yöntemi yararlı ve takip etmesi kolay buldum:

Bir işlev işaretçisi tanımlama

Bağımsız değişkeni ve dönüş değeri olmayan bir işleve bir işaretçiyi tanımlamak için şunu söylersiniz:

void (*funcPtr)();

Bunun gibi karmaşık bir tanıma baktığınızda, ona saldırmanın en iyi yolu ortadan başlamak ve çıkış yapmaktır.“Ortadan başlamak” funcPtr olan değişken adından başlamak anlamına gelir. “Çıkış yolunda çalışmak”, en yakın öğenin sağına bakmak (bu durumda hiçbir şey; sağ parantez sizi kısa durdurur), daha sonra sola (yıldız işaretiyle gösterilen bir işaretçi) bakmak, sonra sağa (bir argüman almayan bir fonksiyonu gösteren boş argüman listesi), sonra sola bakıldığında (fonksiyonun dönüş değeri olmadığını gösteren void). Bu sağ-sol-sağ hareket çoğu bildirimle çalışır.

“Ortadan başla” (“funcPtr bir ...”) incelemek için sağa gidin (orada hiçbir şey - sağ parantez tarafından durduruldunuz), sola gidin ve '*' (“ ... işaretçi bir ... ”), sağa gidin ve boş bağımsız değişken listesini (“ ... bağımsız değişken almayan işlev ... ”) bulun, sola gidin ve boşluğu bulun (“ funcPtr is argüman almayan ve void döndüren bir işleve işaretçi ”).

* FuncPtr'in neden parantez gerektirdiğini merak edebilirsiniz. Bunları kullanmadıysanız, derleyici şunu görür:

void *funcPtr();

Bir değişkeni tanımlamak yerine bir işlevi (void * döndüren) bildiriyor olursunuz. Derleyiciyi, bir bildirimin veya tanımın ne olması gerektiğini anladığında yaptığınız aynı işlemden geçtiğini düşünebilirsiniz. Bu parantezlerin “karşısına çıkması” gerekir, bu nedenle sola dönüp sağa devam etmek ve boş argüman listesini bulmak yerine '*' karakterini bulur.

Karmaşık bildirimler ve tanımlar

Bir yana, C ve C ++ bildirimi sözdiziminin nasıl çalıştığını anladıktan sonra çok daha karmaşık öğeler oluşturabilirsiniz. Örneğin:

//: 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() {} ///:~ 

Her birini gözden geçirin ve çözmek için sağ-sol kılavuzu kullanın. 1 numaralı “fp1, bir tamsayı bağımsız değişkeni alan ve 10 geçersiz işaretçi dizisine bir işaretçi döndüren bir işleve bir işaretçi” diyor.

Sayı 2 , “fp2 üç bağımsız değişken (int, int ve float) alan ve tamsayı bağımsız değişken alan ve kayan nokta döndüren bir işleve bir işaretçi döndüren bir işaretçi” diyor.

Çok sayıda karmaşık tanım oluşturuyorsanız, typedef kullanmak isteyebilirsiniz. Sayı 3 , bir typedef'in karmaşık açıklamayı yazmaya her seferinde nasıl tasarruf ettiğini gösterir. “Bir fp3, hiçbir argüman almayan ve hiçbir argüman almayan ve iki katına dönen işlevlere 10 işaretli bir diziye bir işaretçi döndüren bir işlevin işaretçisidir.” Sonra “a bu fp3 tiplerinden biridir” diyor. typedef genellikle basit olanlardan karmaşık açıklamalar oluşturmak için kullanışlıdır.

Sayı 4 , değişken tanımı yerine bir işlev bildirimidir. “F4, tamsayılar döndüren işlevlere 10 işaretli bir diziye bir işaretçi döndüren bir işlevdir” diyor.

Bunlar gibi karmaşık bildirimlere ve tanımlara ihtiyaç duymanız nadiren gerekecektir. Ancak, bunları çözme egzersizinden geçerseniz, gerçek hayatta karşılaşabileceğiniz biraz karmaşık olanlardan hafif bir şekilde rahatsız olmazsınız.

Alınan: C ++ Cilt 1, ikinci baskı, bölüm 3, bölüm "İşlev Adresleri", Bruce Eckel.


4

C beyanları için bu kuralları hatırlayın
Ve öncelik asla şüphe duymayacaktır:
Sonekle başlayın, önekle devam edin ve
Her iki seti de içeriden, dışarıdan okuyun.
- ben, 1980'lerin ortaları

Elbette parantez tarafından değiştirilenler hariç. Ve bunları bildirme sözdiziminin, temel sınıfın bir örneğini almak için söz konusu değişkeni kullanma sözdizimini tam olarak yansıttığını unutmayın.

Cidden, bunu bir bakışta yapmayı öğrenmek zor değil; sadece beceriyi uygulamak için biraz zaman harcamak istiyor olmalısın. Başkaları tarafından yazılan C kodunu koruyacak veya uyarlayacaksanız, kesinlikle o zamana yatırım yapmaya değer. Ayrıca, öğrenmemiş olan diğer programcıları korkutmak için eğlenceli bir parti hilesi.

Kendi kodu için: her zamanki gibi bir şey olması olabilir standart bir deyim haline gelmiştir son derece yaygın desen olmadıkça tek liner does't olarak yazılabilir, olması gerektiği anlamına (örneğin dize kopya döngüsü gibi) . Siz ve sizi takip edenler, bunları "tek bir şişkinlikte" üretme ve ayrıştırma yeteneğinize güvenmek yerine, katmanlı tip tanımlarından ve adım adım dereferences'dan karmaşık türler oluşturursanız çok daha mutlu olacaksınız. Performans da iyi olacak ve kod okunabilirliği ve sürdürülebilirliği çok daha iyi olacak.

Daha kötü olabilir, biliyorsun. Aşağıdaki gibi bir şeyle başlayan yasal bir PL / I beyanı vardı:

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

2
PL / I ifadesi olarak IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIFayrıştırıldı if (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF).
Cole Johnson

Ben düşünüyorum C'ler eşdeğer bir koşullu IF / THEN / ELSE ifadesi (? :) karışımı içine üçüncü setini aldı kullanarak bir adım öteye aldı bir versiyon oldu ... ama birkaç yıl geçti ve sahip olabilirler dilin belirli bir lehçesine bağlıydı. Nokta, herhangi bir dilin en az bir patolojik formu olduğu anlamına gelir.
keshlam

4

Yıllarca önce yazdığım spiral kuralın orijinal yazarı oldum (çok fazla saçım olduğunda :) ve cfaq'a eklendiğinde onurlandırıldım.

Spiral kuralı öğrencilerimin ve meslektaşlarımın "başlarındaki" C bildirimlerini okumasını kolaylaştırmanın bir yolu olarak yazdım; yani, cdecl.org gibi yazılım araçlarını kullanmak zorunda kalmadan. Spiral kuralın C ifadelerini ayrıştırmanın kanonik yolu olduğunu asla söylemek niyetim olmadı. Yine de, kuralın yıllar boyunca binlerce C programlama öğrencisine ve uygulayıcısına yardımcı olduğunu görmekten çok memnunum!

Kayıt için,

Linus Torvalds (son derece saygı duyduğum biri) dahil olmak üzere birçok sitede "doğru" olarak birçok kez "doğru" tanımlanmıştır, spiral kuralımın "bozulduğu" durumlar vardır. En yaygın olanı:

char *ar[10][10];

Bu iş parçacığının başkaları tarafından işaret edildiği gibi, kural dizilerle karşılaştığınızda tüm dizinleri aşağıdaki gibi yazmış gibi tüketecek şekilde güncellenebilir :

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

Şimdi, sarmal kuralı takip ederek şunu elde ederim:

"ar, 10x10 karakterlik iki boyutlu bir işaretçi dizisidir"

Umarım spiral kuralı C öğreniminde yararlılığını sürdürür!

Not:

"C zor değil" imajını seviyorum :)


3
  • geçersiz (*(*f[]) ()) ()

Çözümleniyor void>>

  • (*(*f[]) ()) () = geçersiz

Resoiving ()>>

  • (* (*f[]) ()) = işlev döndürme (geçersiz)

Çözümleniyor *>>

  • (*f[]) () = işaretçi (işlev döndürme (geçersiz))

Çözümleniyor ()>>

  • (* f[]) = işlev döndürme (işaretçi (işlev döndürme (geçersiz)))

Çözümleniyor *>>

  • f[] = işaretçi (işlev geri dönüyor (işaretçi (işlev geri dönüyor (geçersiz)))))

Çözümleniyor [ ]>>

  • f = dizisi (gösterici - (işlev döndürme (işaretçi (işlev döndürme (geçersiz)))))))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.