C वह कठिन नहीं है: शून्य (* (* f []) ()) ()


188

मैंने आज सिर्फ एक तस्वीर देखी और मुझे लगा कि मैं स्पष्टीकरण की सराहना करूंगा। तो यहाँ तस्वीर है:

कुछ सी कोड

मुझे यह भ्रामक लगा और मुझे आश्चर्य हुआ कि क्या ऐसे कोड कभी व्यावहारिक होते हैं। मैंने इस तस्वीर को देखा और इस रेडिट प्रविष्टि में एक और चित्र पाया , और यहाँ वह चित्र है:

कुछ दिलचस्प व्याख्या

तो यह "स्पिरिटली पढ़ना" कुछ मान्य है? यह कैसे सी कंपाइलर्स पार्स है?
यह बहुत अच्छा होगा अगर इस अजीब कोड के लिए सरल स्पष्टीकरण हैं।
सभी के अलावा, क्या इस तरह के कोड उपयोगी हो सकते हैं? यदि हां, तो कहां और कब?

"सर्पिल नियम" के बारे में एक सवाल है , लेकिन मैं इस बारे में नहीं पूछ रहा हूं कि यह कैसे लागू होता है या उस नियम के साथ अभिव्यक्ति कैसे पढ़ी जाती है। मैं इस तरह के भाव और सर्पिल नियम की वैधता के उपयोग पर सवाल उठा रहा हूं। इनके बारे में, कुछ अच्छे उत्तर पहले से ही पोस्ट किए गए हैं।


9
स्पष्टीकरण सरल कैसे हो सकता है? यह fw की परिभाषा के सभी पहलुओं / प्रत्येक प्रमुख बिंदु के लिए शब्दों के एक जोड़े को शामिल करता है।
स्कॉट हंटर

30
शायद C कठिन है? यह वास्तव में fकिसी भी तर्क ले सकता है कि कार्यों के लिए संकेत की एक सरणी के रूप में घोषित करता है .. अगर यह थे void (*(*f[])(void))(void);, तो हाँ, यह ऐसे कार्य होंगे जो कोई तर्क नहीं लेते हैं ...
txtechhelp

18
व्यवहार में, इस तरह के अस्पष्ट कोड को कोड न करें। हस्ताक्षरों के लिए
टंकण का

4
फंक्शन पॉइंटर्स से जुड़ी कोई भी घोषणा कठिन हो सकती है। इसका मतलब यह नहीं है कि सामान्य सी या सी ++ उसी तरह से कठिन है। अन्य भाषाएं इसे अलग-अलग तरीकों से हल करती हैं, जिसमें फ़ंक्शन पॉइंटर्स नहीं होते हैं, जो कुछ मामलों में एक महत्वपूर्ण चूक हो सकती है
केट ग्रेगरी

20
यदि आप अपनी आँखें निचोड़ते हैं, तो यह LISP जैसा दिखता है।
user2023861

जवाबों:


117

एक नियम है जिसे जटिल घोषणा के अर्थ को खोजने में मदद करने के लिए "दक्षिणावर्त / सर्पिल नियम" कहा जाता है ।

से सी-faq :

पालन ​​करने के लिए तीन सरल चरण हैं:

  1. अज्ञात तत्व के साथ शुरू, एक सर्पिल / घड़ी की दिशा में आगे बढ़ें; जब निम्नलिखित तत्वों को समान रूप से अंग्रेजी के बयानों से बदल दिया जाए:

    [X]या []
    => एरे का एक्स आकार ... या एरे अपरिभाषित आकार ...

    (type1, type2)
    => फ़ंक्शन पासिंग टाइप 1 और टाइप 2 रिटर्निंग ...

    *
    => सूचक (ओं) को ...

  2. इसे एक सर्पिल / घड़ी की दिशा में तब तक करते रहें जब तक कि सभी टोकन कवर न हो जाएं।

  3. हमेशा पहले कोष्ठक में कुछ भी हल करें!

आप उदाहरण के लिए ऊपर दिए गए लिंक की जांच कर सकते हैं।

यह भी ध्यान दें कि आपकी मदद करने के लिए एक वेबसाइट भी है जिसे कहा जाता है:

http://www.cdecl.org

आप एक सी घोषणा में प्रवेश कर सकते हैं और यह इसका अंग्रेजी अर्थ देगा। के लिये

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

यह आउटपुट:

पॉइंटर लौटने के लिए पॉइंटर को कार्य करने के लिए पॉइंटर की सरणी के रूप में घोषित करें

संपादित करें:

जैसा कि यादृच्छिक 832 द्वारा टिप्पणियों में बताया गया है , सर्पिल नियम सरणी की सरणी को संबोधित नहीं करता है और उन घोषणाओं में (अधिकांश) गलत परिणाम देगा। int **x[1][2];सर्पिल नियम के लिए उदाहरण के लिए इस तथ्य की उपेक्षा करता है कि []अधिक पूर्वता है *

ऐरे के सरणी के सामने जब कोई सर्पिल नियम लागू करने से पहले स्पष्ट कोष्ठक जोड़ सकता है। उदाहरण के लिए: पूर्ववर्तीता और सर्पिल नियम के कारण ( int **x[1][2];जैसा int **(x[1][2]);भी मान्य सी) के समान है, इसे सही ढंग से पढ़ता है क्योंकि "x सूचक के लिए सूचक 2 से सरणी 2 का एक सरणी 1 है" जो कि सही अंग्रेजी घोषणा है।

ध्यान दें कि यह मुद्दा भी इस में शामिल किया गया है इस सवाल का जवाब द्वारा जेम्स Kanze (द्वारा बताया haccks टिप्पणी में)।


5
काश cdecl.org बेहतर होता
ग्रैडी प्लेयर

8
कोई "सर्पिल नियम" नहीं है ... "int *** foo [] [] [] []" इंगित करता है सारणी के बिंदुओं को इंगित करने वाले को सरणियों के सरणियों की एक सरणी। "सर्पिल" केवल इस तथ्य से आता है कि यह घोषणा एक प्रकार से कोष्ठकों में समूह चीजों के लिए हुई, जिससे उन्हें वैकल्पिक रूप से नुकसान हुआ। यह कोष्ठक के प्रत्येक सेट के भीतर दाईं ओर, बाईं ओर सब कुछ है।
रैंडम 32३२

1
@ Random832 एक "सर्पिल नियम" है, और यह आपके द्वारा बताए गए मामले को कवर करता है, यानी कोष्ठक / सरणियों आदि से निपटने के तरीके के बारे में बात करता है बेशक एक मानक सी नियम नहीं है, लेकिन यह पता लगाने के लिए एक अच्छा mnemonic है कि कैसे निपटें। जटिल घोषणाओं के साथ। IMHO, यह अत्यंत उपयोगी है और जब मुसीबत में या cdecl.org घोषणा को पार्स नहीं कर सकता है तब आपको बचाता है । बेशक किसी को ऐसी घोषणाओं का दुरुपयोग नहीं करना चाहिए, लेकिन यह जानना अच्छा है कि उन्हें कैसे रोका जाता है।
१/५५ पर vsoftco

5
@vsoftco लेकिन यह "एक सर्पिल / घड़ी की दिशा में आगे बढ़ना" नहीं है यदि आप केवल जब आप कोष्ठक तक पहुँचते हैं तो चारों ओर मुड़ते हैं।
रैंडम 832

2
ouah, आपको उल्लेख करना चाहिए कि सर्पिल नियम सार्वभौमिक नहीं है
haccks

105

"सर्पिल" नियम निम्न पूर्ववर्ती नियमों से बाहर आता है:

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

सबस्क्रिप्ट []और फ़ंक्शन कॉल ()ऑपरेटर्स में यूनरी की तुलना में अधिक पूर्वता होती है *, इसलिए *f()इसे पार्स किया जाता है *(f())और *a[]इसे पार्स किया जाता है *(a[])

इसलिए यदि आप किसी फ़ंक्शन के लिए एक पॉइंटर या एक पॉइंटर को पॉइंटर चाहते हैं, तो आपको *पहचानकर्ता के साथ स्पष्ट रूप से समूह बनाने की आवश्यकता है , जैसे कि (*a)[]या में (*f)()

तो फिर तुम कि एहसास aऔर fबस पहचानकर्ता की तुलना में अधिक जटिल भाव हो सकता है; में T (*a)[N], aएक साधारण पहचानकर्ता हो सकता है, या यह एक फ़ंक्शन कॉल हो सकता है जैसे (*f())[N]( a-> f()), या यह एक सरणी हो सकता है जैसे (*p[M])[N], ( a-> p[M]), या यह कार्य करने के लिए संकेत का एक सरणी हो सकता है - (*(*p[M])())[N]( a-> (*p[M])()), आदि।

यह अच्छा होगा यदि अप्रत्यक्ष ऑपरेटर *एकात्मक के बजाय पोस्टफ़िक्स था, जो घोषणाओं को बाएं से दाएं पढ़ने में कुछ हद तक आसान बना देगा ( void f[]*()*();निश्चित रूप से बेहतर प्रवाह होता है void (*(*f[])())()), लेकिन यह नहीं है।

जब आप इस तरह के बालों वाली घोषणा पर आते हैं, तो सबसे बाएं पहचानकर्ता को ढूंढना शुरू करें और ऊपर दिए गए पूर्ववर्ती नियमों को लागू करें, उन्हें किसी भी फ़ंक्शन मापदंडों पर पुन: लागू करें:

         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

signalमानक पुस्तकालय में समारोह शायद पागलपन के इस प्रकार के लिए प्रकार नमूना है:

       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

इस बिंदु पर अधिकांश लोगों का कहना है कि "टाइप टाइप एफ" का उपयोग करें, जो निश्चित रूप से एक विकल्प है:

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

innerfunc *f[N];

परंतु...

आप एक अभिव्यक्ति में कैसे उपयोग करेंगे f? आप जानते हैं कि यह पॉइंटर्स की एक सरणी है, लेकिन आप इसे सही फ़ंक्शन को निष्पादित करने के लिए कैसे उपयोग करते हैं? आपको सही सिंटैक्स से टाइपराइफ और पहेली पर जाना होगा। इसके विपरीत, "नग्न" संस्करण बहुत ही आकर्षक है, लेकिन यह आपको बताता है कि अभिव्यक्ति में कैसे उपयोग f किया जाए (अर्थात्, (*(*f[i])())();न तो फ़ंक्शन तर्क मान लेता है)।


7
'सिग्नल' का उदाहरण देने के लिए धन्यवाद, यह दर्शाता है कि इस प्रकार की चीजें जंगली में दिखाई देती हैं।
जस्ट्साल्ट

यह एक महान उदाहरण है।
केसी

मुझे आपकी fमंदी का पेड़ बहुत पसंद आया , जो पहले से बता रहा है ... किसी कारण से मुझे हमेशा
ASCII-

1
यह मानते हुए कि न तो फ़ंक्शन तर्क देता है : तब आपको voidफ़ंक्शंस कोष्ठक में उपयोग करना होगा , अन्यथा यह कोई भी तर्क ले सकता है।
हॉक

1
@ झटके: घोषणा के लिए, हाँ; मैं फंक्शन कॉल के बारे में बात कर रहा था।
जॉन बोड

57

C में, डिक्लेरेशन मिरर्स यूसेज- कि यह कैसे मानक में परिभाषित किया गया है। घोषणा:

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

एक जोर है कि अभिव्यक्ति (*(*f[i])())()प्रकार का एक परिणाम पैदा करता है void। जिसका मतलब है:

  • f एक सरणी होनी चाहिए, क्योंकि आप इसे अनुक्रमित कर सकते हैं:

    f[i]
  • तत्वों के fसंकेत होने चाहिए, क्योंकि आप उन्हें डीरेल कर सकते हैं:

    *f[i]
  • उन बिंदुओं को बिना किसी तर्क के कार्य करने के लिए संकेत होना चाहिए, क्योंकि आप उन्हें कॉल कर सकते हैं:

    (*f[i])()
  • उन कार्यों के परिणाम भी संकेत देने वाले होने चाहिए, क्योंकि आप उन्हें डीरेल कर सकते हैं:

    *(*f[i])()
  • उन बिंदुओं को भी कोई तर्क लेने वाले कार्यों के लिए संकेत होना चाहिए , क्योंकि आप उन्हें कॉल कर सकते हैं:

    (*(*f[i])())()
  • उन फ़ंक्शन पॉइंटर्स को वापस लौटना चाहिए void

"सर्पिल नियम" सिर्फ एक मात्र है जो एक ही चीज़ को समझने का एक अलग तरीका प्रदान करता है।


3
इसे देखने का शानदार तरीका जो मैंने पहले कभी नहीं देखा। +1
tbodt

4
अच्छा लगा। इस तरह से देखा, यह वास्तव में सरल है । वास्तव में कुछ की तुलना में आसान है vector< function<function<void()>()>* > f, खासकर यदि आप एस में जोड़ते हैं std::। (लेकिन ठीक है, उदाहरण सेf :: [IORef (IO (IO ()))]
वंचित है

1
@TimoDenk: घोषणा a[x]इंगित करती है कि अभिव्यक्ति a[i]कब मान्य है i >= 0 && i < x। जबकि, a[]आकार अनिर्दिष्ट छोड़ देता है, और इसलिए के समान है *a: यह इंगित करता है कि अभिव्यक्ति a[i](या समतुल्य *(a + i)) के लिए मान्य है कुछ की सीमा i
जॉन पुरडी

4
यह सी प्रकार के बारे में सोचने का अब तक का सबसे आसान तरीका है, इसके लिए धन्यवाद
एलेक्स ओजर

4
मुझे यह पसंद है! मूर्खतापूर्ण सर्पिल की तुलना में बहुत आसान कारण। (*f[])()एक प्रकार है जिसे आप अनुक्रमित कर सकते हैं, फिर डीरेफेरेंस कर सकते हैं, फिर कॉल कर सकते हैं, इसलिए यह कार्यों के लिए पॉइंटर्स का एक सरणी है।
लिन

32

तो यह "स्पिरिटली पढ़ना" कुछ मान्य है?

सर्पिल नियम लागू करना या cdecl का उपयोग करना हमेशा मान्य नहीं होता है। दोनों कुछ मामलों में विफल हो जाते हैं। सर्पिल नियम कई मामलों के लिए काम करता है, लेकिन यह सार्वभौमिक नहीं है

जटिल घोषणाओं को समझने के लिए इन दो सरल नियमों को याद रखें:

  • हमेशा अंदर से घोषणाएं पढ़ें : अंतरतम से शुरू करें, यदि कोई हो, तो कोष्ठक। जो पहचानकर्ता घोषित किया जा रहा है, उसका पता लगाएँ, और वहाँ से घोषणा को रोकना शुरू करें।

  • जब कोई विकल्प होता है, तो हमेशा पक्ष []और ()अधिक* : यदि *पहचानकर्ता से पहले है और []इसका अनुसरण करता है, तो पहचानकर्ता एक सरणी का प्रतिनिधित्व करता है, न कि सूचक का। इसी तरह, यदि *पहचानकर्ता से पहले और ()इसका अनुसरण करता है, तो पहचानकर्ता एक फ़ंक्शन का प्रतिनिधित्व करता है, न कि सूचक का। (कोष्ठक हमेशा []और ()अधिक की सामान्य प्राथमिकता को ओवरराइड करने के लिए इस्तेमाल किया जा सकता है *।)

इस नियम में वास्तव में पहचानकर्ता के एक पक्ष से दूसरे तक zigzagging शामिल है ।

अब एक साधारण घोषणा को टाल देना

int *a[10];

नियम लागू करना:

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

आइए जैसे जटिल घोषणा की व्याख्या करते हैं

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

उपरोक्त नियम लागू करके:

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

यहाँ एक GIF दिखाया गया है कि आप कैसे जाते हैं (बड़े देखने के लिए चित्र पर क्लिक करें):

यहां छवि विवरण दर्ज करें


यहाँ उल्लिखित नियम केएन किंग की पुस्तक सी प्रोग्रामिंग ए मॉडर्न अप्रोच से ली गई है


यह मानक के दृष्टिकोण की तरह है "घोषणा दर्पण उपयोग"। मैं इस बिंदु पर कुछ और पूछना चाहता हूं: क्या आप केएन किंग की पुस्तक का सुझाव देते हैं? मैं पुस्तक के बारे में बहुत सारी अच्छी समीक्षाएँ देख रहा हूँ।
मोटून

1
हाँ। मैं उस पुस्तक का सुझाव देता हूं। मैंने उस पुस्तक से प्रोग्रामिंग शुरू की। वहां अच्छे ग्रंथ और समस्याएं।
haccks

क्या आप घोषणा को समझने में विफल cdecl का उदाहरण दे सकते हैं? मुझे लगा कि cdecl ने संकलक के रूप में एक ही पार्सिंग नियमों का उपयोग किया है, और जहां तक ​​मैं बता सकता हूं कि यह हमेशा काम करता है।
फैबियो का कहना है कि मोनिका

@FabioTurati; एक फ़ंक्शन सरणियों या फ़ंक्शन को वापस नहीं कर सकता। char (x())[5]सिंटैक्स त्रुटि में परिणाम होना चाहिए लेकिन, cdecl इसे पार्स करें: फ़ंक्शन रिटर्न ऐरे 5 के रूप में घोषित करेंxchar
haccks

12

यह केवल एक "सर्पिल" है क्योंकि ऐसा होता है, इस घोषणा में, कोष्ठकों के प्रत्येक स्तर के भीतर प्रत्येक तरफ केवल एक ऑपरेटर होता है। यह दावा करते हुए कि आप "सर्पिल में" आगे बढ़ते हैं, आम तौर पर आपको घोषणा में सरणियों और बिंदुओं के बीच वैकल्पिक रूप से सुझाएगा int ***foo[][][]जब वास्तव में सभी सरणी स्तर किसी भी सूचक स्तर से पहले आते हैं।


ठीक है, "सर्पिल दृष्टिकोण" में, आप जितना हो सके उतना दाईं ओर जाएं, फिर जितना आप कर सकते हैं, आदि के रूप में छोड़ दिया जाता है, लेकिन इसे अक्सर गलत तरीके से समझाया जाता है ...
लिन

7

मुझे संदेह है कि इस तरह के निर्माणों का वास्तविक जीवन में कोई उपयोग हो सकता है। मैं उन्हें नियमित डेवलपर्स (संकलक लेखकों के लिए संभावित रूप से ठीक) के लिए साक्षात्कार प्रश्न के रूप में भी पता लगाता हूं। इसके बजाय टाइपडेफ़ का उपयोग किया जाना चाहिए।


3
फिर भी, यह जानना ज़रूरी है कि इसे पार्स कैसे किया जाए, भले ही केवल यह पता हो कि टाइपराइफ़ को कैसे पार्स किया जाए!
inetknght

1
@inetknght, जिस तरह से आप इसे टाईपडेफ़्स के साथ करते हैं, वह उन्हें इतना सरल है कि किसी भी पार्सिंग की आवश्यकता नहीं होगी।
सर्जिया

2
जो लोग साक्षात्कार के दौरान इस प्रकार के प्रश्न पूछते हैं, वे केवल अपने ईगो को स्ट्रोक करने के लिए करते हैं।
केसी

1
@ जॉनबोड, और आप अपने आप को फंक्शन के रिटर्न वैल्यू टाइप करने के लिए एक एहसान करेंगे।
सर्जिया

1
@ जॉनबोड, मुझे यह व्यक्तिगत पसंद का मामला लगता है जो बहस करने लायक नहीं है। मुझे आपकी पसंद दिखाई देती है, मेरे पास अभी भी है।
सर्जिया

7

रैंडम ट्रिविया फैक्टॉइड के रूप में , आपको यह जानकर आश्चर्य हो सकता है कि अंग्रेजी में एक वास्तविक शब्द है कि यह वर्णन करने के लिए कि कैसे सी घोषणाएं पढ़ी जाती हैं: बाउस्ट्रोफेडोनॉली , यानी , बाएं-से-दाएं के साथ दाएं-से-बाएं।

संदर्भ: वैन डेर लिंडेन, 1994 - पृष्ठ 76


1
यह शब्द परेंस द्वारा या एक लाइन पर नेस्टेड के रूप में इंगित नहीं करता है । यह एक "साँप" पैटर्न का वर्णन करता है, एक एलटीआर लाइन के बाद एक आरटीएल लाइन।
पोटाटोस्वाटर

5

इसकी उपयोगिता के बारे में, जब शेलकोड के साथ काम करते हुए आप इस निर्माण को बहुत देखते हैं:

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

जबकि काफी जटिल रूप से जटिल नहीं, यह विशेष पैटर्न बहुत ऊपर आता है।

इस एसओ प्रश्न में अधिक पूर्ण उदाहरण ।

इसलिए जब मूल चित्र की सीमा तक उपयोगिता संदिग्ध है (मैं सुझाव दूंगा कि किसी भी उत्पादन कोड को बहुत सरल बनाया जाना चाहिए), कुछ वाक्यात्मक निर्माण होते हैं जो काफी कम आते हैं।


5

घोषणा

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

कहने का सिर्फ एक अस्पष्ट तरीका है

Function f[]

साथ में

typedef void (*ResultFunction)();

typedef ResultFunction (*Function)();

व्यवहार में, ResultFunction और फ़ंक्शन के बजाय अधिक वर्णनात्मक नामों की आवश्यकता होगी । यदि संभव हो तो मैं पैरामीटर सूचियों को भी निर्दिष्ट करूंगा void


4

मैंने पाया कि ब्रूस एकेल द्वारा वर्णित विधि उपयोगी और अनुसरण करने में आसान है:

एक फ़ंक्शन पॉइंटर को परिभाषित करना

किसी फ़ंक्शन के लिए एक पॉइंटर को परिभाषित करने के लिए जिसमें कोई तर्क नहीं है और कोई वापसी मूल्य नहीं है, आप कहते हैं:

void (*funcPtr)();

जब आप इस तरह की जटिल परिभाषा को देख रहे हैं, तो उस पर हमला करने का सबसे अच्छा तरीका बीच में शुरू करना है और अपने तरीके से काम करना है। "बीच में शुरू करना" का अर्थ है चर नाम पर शुरू करना, जो कि funcPtr है। "अपने तरीके से काम करना" का अर्थ है निकटतम वस्तु के लिए दाईं ओर देखना (इस मामले में कुछ भी नहीं; सही कोष्ठक आपको कम रोकता है), फिर बाईं ओर देख रहे हैं (तारांकन द्वारा इंगित एक सूचक), फिर दाईं ओर देख रहे हैं ( खाली तर्क सूची एक फ़ंक्शन को इंगित करती है जो कोई तर्क नहीं लेती है), फिर बाईं ओर देख रही है (शून्य, जो इंगित करता है कि फ़ंक्शन का कोई रिटर्न मान नहीं है)। यह दाएं-बाएं-दाएं गति अधिकांश घोषणाओं के साथ काम करता है।

समीक्षा करने के लिए, "बीच में शुरू करें" ("funcPtr एक ..."), दाईं ओर जाएं (वहां कुछ भी नहीं है - आपको सही कोष्ठक द्वारा रोका गया है), बाईं ओर जाएं और '*' खोजें (" ... सूचक को ... "), दाईं ओर जाएं और रिक्त तर्क सूची (" ... फ़ंक्शन जो कोई तर्क नहीं लेता है ... ") ढूंढें, बाईं ओर जाएं और शून्य (" funcPtr) ढूंढें। एक फ़ंक्शन के लिए एक सूचक जो कोई तर्क नहीं लेता है और शून्य रिटर्न करता है ”)।

आपको आश्चर्य हो सकता है कि * funcPtr को कोष्ठक की आवश्यकता क्यों है। यदि आप उनका उपयोग नहीं करते हैं, तो संकलक देखेगा:

void *funcPtr();

आप एक चर को परिभाषित करने के बजाय एक फ़ंक्शन (जो एक शून्य * लौटाते हैं) की घोषणा करेंगे। आप संकलक के बारे में सोच सकते हैं कि आप उसी प्रक्रिया से गुजर रहे हैं जब आप यह आंकलन करते हैं कि घोषणा या परिभाषा क्या होनी चाहिए। इसके लिए उन कोष्ठकों की आवश्यकता है, जिनके खिलाफ "टकराएँ", इसलिए यह बाईं ओर वापस आता है और दाईं ओर जारी रहने और खाली तर्क सूची खोजने के बजाय '*' को ढूँढता है।

जटिल घोषणाएँ और परिभाषाएँ

एक तरफ, एक बार जब आप यह पता लगा लेते हैं कि सी और सी ++ घोषणा सिंटैक्स कैसे काम करता है, तो आप बहुत अधिक जटिल आइटम बना सकते हैं। उदाहरण के लिए:

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

हर एक के माध्यम से चलो और यह पता लगाने के लिए दाएं-बाएं दिशानिर्देश का उपयोग करें। नंबर 1 कहता है "fp1 एक फ़ंक्शन के लिए एक संकेतक है जो पूर्णांक तर्क लेता है और 10 शून्य बिंदुओं की एक सरणी के लिए एक संकेतक लौटाता है।"

नंबर 2 का कहना है कि "fp2 एक फ़ंक्शन के लिए एक पॉइंटर है जो तीन तर्क (इंट, इंट, और फ्लोट) लेता है और एक फ़ंक्शन को एक पॉइंटर देता है जो पूर्णांक तर्क लेता है और एक फ्लोट देता है।"

यदि आप बहुत सी जटिल परिभाषाएँ बना रहे हैं, तो आप एक टाइफेड का उपयोग करना चाह सकते हैं। नंबर 3 दिखाता है कि हर बार एक जटिल विवरण टाइप करने से कैसे एक टाइफाइड बचाता है। यह कहते हैं, "एक fp3 एक फ़ंक्शन के लिए एक संकेतक है जो कोई तर्क नहीं लेता है और 10 पॉइंटर्स के एक सरणी के लिए एक पॉइंटर देता है जो फ़ंक्शन के लिए होता है जो कोई तर्क नहीं लेते हैं और युगल वापस लौटते हैं।" फिर यह कहता है "यह इन fp3 प्रकारों में से एक है।" टाइपेडिफ आम तौर पर सरल लोगों से जटिल विवरण बनाने के लिए उपयोगी है।

संख्या 4 एक चर परिभाषा के बजाय एक फ़ंक्शन घोषणा है। यह कहता है कि "f4 एक फ़ंक्शन है जो पूर्णांक को वापस करने वाले फ़ंक्शन के लिए 10 पॉइंटर्स की एक सरणी के लिए एक पॉइंटर लौटाता है।"

आप शायद ही कभी अगर ऐसी जटिल घोषणाओं और परिभाषाओं की आवश्यकता होगी। हालांकि, यदि आप उन्हें बाहर निकालने की कवायद से गुजरते हैं, तो आप थोड़े जटिल लोगों के साथ हल्के ढंग से परेशान नहीं होंगे, जिनका आप वास्तविक जीवन में सामना कर सकते हैं।

से लिया गया: C ++ वॉल्यूम 1, दूसरा संस्करण, अध्याय 3, ब्रूस एकेल द्वारा "फंक्शन एड्रेस" सेक्शन में सोचना।


4

सी घोषित किए गए इन नियमों को याद रखें
और पूर्ववर्तीता कभी भी संदेह में नहीं होगी:
प्रत्यय से शुरू करें, उपसर्ग के साथ आगे बढ़ें,
और अंदर, बाहर दोनों सेट पढ़ें।
- मुझे, 1980 के मध्य में

कोष्ठक द्वारा संशोधित के अलावा, बिल्कुल। और ध्यान दें कि इन वेरिएंट को घोषित करने के लिए सिंटैक्स, बेस क्लास का एक उदाहरण प्राप्त करने के लिए उस वैरिएबल का उपयोग करने के लिए सिंटैक्स को दर्पण करता है।

गंभीरता से, यह एक नज़र में सीखना मुश्किल नहीं है; आपको बस कौशल का अभ्यास करने के लिए कुछ समय बिताने के लिए तैयार रहना होगा। यदि आप अन्य लोगों द्वारा लिखे गए सी कोड को बनाए रखने या अनुकूलित करने जा रहे हैं, तो यह निश्चित रूप से उस समय के निवेश के लायक है। यह अन्य प्रोग्रामरों को बाहर निकालने के लिए एक मजेदार पार्टी ट्रिक है, जिन्होंने इसे नहीं सीखा है।

अपने स्वयं के कोड के लिए: हमेशा की तरह, तथ्य यह है कि एक-लाइनर के रूप में कुछ लिखा जा सकता है इसका मतलब यह नहीं होना चाहिए, जब तक कि यह एक अत्यंत सामान्य पैटर्न न हो जो एक मानक मुहावरा बन गया है (जैसे कि स्ट्रिंग-कॉपी लूप) । आप और जो आपके पीछे आते हैं, वे बहुत खुश होंगे यदि आप जटिल प्रकार के स्तरित टाइपराइफ और स्टेप-बाय-स्टेप डेरेफेर से बाहर का निर्माण करते हैं, बल्कि ये बनाने के लिए और "एक सूँघने पर" को पार्स करने की क्षमता पर भरोसा करते हैं। प्रदर्शन सिर्फ उतना ही अच्छा होगा, और कोड की पठनीयता और रख-रखाव जबरदस्त रूप से बेहतर होगा।

यह बदतर हो सकता है, आप जानते हैं। एक कानूनी PL / I कथन था जो कुछ इस तरह से शुरू हुआ था:

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

2
पीएल / आई स्टेटमेंट था IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIFऔर इसे पार्स किया गया है if (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF)
कोल जॉनसन

मुझे लगता है कि एक ऐसा संस्करण था जिसने सशर्त IF / THEN / ELSE अभिव्यक्ति (C की तुलना में :) का उपयोग करके इसे एक कदम आगे बढ़ाया, जो तीसरे सेट को मिक्स में मिला ... लेकिन यह कुछ दशक रहा है और हो सकता है भाषा की एक विशेष बोली पर निर्भर करता है। बिंदु यह रहता है कि किसी भी भाषा में कम से कम एक रोग का रूप होता है।
केशलम

4

मैं सर्पिल नियम का मूल लेखक हूं जो मैंने इतने साल पहले ओह लिखा था (जब मेरे बहुत सारे बाल थे :) और जब इसे cqq में जोड़ा गया तो सम्मानित किया गया।

मैंने सर्पिल नियम को अपने छात्रों और सहकर्मियों के लिए सी घोषणाएं "उनके सिर में" पढ़ने के लिए आसान बनाने के तरीके के रूप में लिखा; यानी, बिना cdecl.org जैसे सॉफ्टवेयर टूल्स का उपयोग किए बिना, यह घोषित करना मेरा उद्देश्य कभी नहीं था कि सर्पिल नियम सी के भावों को पार्स करने का विहित तरीका हो। हालांकि, मुझे यह देखकर खुशी हुई कि इस नियम ने वर्षों से हजारों सी प्रोग्रामिंग छात्रों और चिकित्सकों की मदद की है!

रिकार्ड के लिए,

यह कई साइटों पर "सही ढंग से" कई बार पहचाना गया है, जिसमें लिनुस टॉर्वाल्ड्स (कोई जिसे मैं बहुत सम्मान करता हूं) भी शामिल है, कि ऐसी परिस्थितियां हैं जहां मेरा सर्पिल नियम "टूट जाता है"। सबसे आम:

char *ar[10][10];

जैसा कि इस थ्रेड में अन्य लोगों द्वारा बताया गया है, नियम को यह कहने के लिए अद्यतन किया जा सकता है कि जब आप एरेज़ का सामना करते हैं, तो बस सभी इंडेक्सों का उपभोग करें जैसे कि लिखा है:

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

अब, सर्पिल नियम के बाद, मुझे मिलेगा:

"ar चारेर के लिए 10x10 का दो आयामी सरणी है"

मुझे उम्मीद है कि सर्पिल नियम C सीखने में इसकी उपयोगिता पर ध्यान देता है!

पुनश्च:

मैं प्यार करता हूँ "सी मुश्किल नहीं है" छवि :)


3
  • शून्य (*(*f[]) ()) ()

संकल्प करना void>>

  • (*(*f[]) ()) () = शून्य

पुनर्जीवित करना ()>>

  • (* (*f[]) ()) = फंक्शन रिटर्निंग (शून्य)

संकल्प करना *>>

  • (*f[]) () = पॉइंटर टू (फंक्शन रिटर्निंग (शून्य))

संकल्प करना ()>>

  • (* f[]) = फंक्शन रिटर्निंग (पॉइंटर टू (फंक्शन रिटर्निंग))

संकल्प करना *>>

  • f[] = पॉइंटर टू (फंक्शन रिटर्निंग (पॉइंटर रिटर्निंग (शून्य))))

संकल्प करना [ ]>>

  • f = की सरणी (पॉइंटर टू (रिटर्निंग पॉइंटर टू (फंक्शन रिटर्निंग))))
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.