C काम में फ़ंक्शन पॉइंटर्स कैसे करते हैं?


1232

सी में फ़ंक्शन पॉइंटर्स के साथ मुझे कुछ अनुभव हुआ।

तो अपने स्वयं के सवालों के जवाब देने की परंपरा के साथ, मैंने बहुत मूल बातें का एक छोटा सा सारांश बनाने का फैसला किया, जिनके लिए विषय को त्वरित गोता लगाने की आवश्यकता है।


34
इसके अलावा: सी पॉइंटर्स के गहन विश्लेषण के लिए, blogs.oracle.com/ksplice/entry/the_ksplice_pointer_challenge देखें । साथ ही, ग्राउंड अप से प्रोग्रामिंग से पता चलता है कि वे मशीन स्तर पर कैसे काम करते हैं। समझना सी "स्मृति मॉडल" कैसे समझ सी काम सूचक मात्र के लिए बहुत उपयोगी है।
अब्बाफी

8
बढ़िया जानकारी। हालांकि शीर्षक से, मुझे वास्तव में "फंक्शन पॉइंटर्स के काम" की व्याख्या देखने की उम्मीद होगी, न कि वे कैसे कोडित हैं :)
बोगडान एलेक्जेंड्रू

जवाबों:


1477

सी में फंक्शन पॉइंटर्स

आइए एक मूल कार्य से शुरू करें, जिसकी ओर हम इशारा करेंगे :

int addInt(int n, int m) {
    return n+m;
}

पहली बात, चलो एक सूचक को एक फ़ंक्शन को परिभाषित करते हैं जो 2 intएस प्राप्त करता है और एक रिटर्न देता है int:

int (*functionPtr)(int,int);

अब हम सुरक्षित रूप से अपने कार्य को इंगित कर सकते हैं:

functionPtr = &addInt;

अब हमारे पास फ़ंक्शन के लिए एक पॉइंटर है, आइए इसका उपयोग करें:

int sum = (*functionPtr)(2, 3); // sum == 5

किसी अन्य फ़ंक्शन के लिए पॉइंटर पास करना मूल रूप से समान है:

int add2to3(int (*functionPtr)(int, int)) {
    return (*functionPtr)(2, 3);
}

हम रिटर्न पॉइंट्स में फंक्शन पॉइंटर्स का उपयोग कर सकते हैं (ऊपर रखने की कोशिश करें, यह गड़बड़ हो जाता है):

// this is a function called functionFactory which receives parameter n
// and returns a pointer to another function which receives two ints
// and it returns another int
int (*functionFactory(int n))(int, int) {
    printf("Got parameter %d", n);
    int (*functionPtr)(int,int) = &addInt;
    return functionPtr;
}

लेकिन इसका उपयोग करने के लिए बहुत अच्छा है typedef:

typedef int (*myFuncDef)(int, int);
// note that the typedef name is indeed myFuncDef

myFuncDef functionFactory(int n) {
    printf("Got parameter %d", n);
    myFuncDef functionPtr = &addInt;
    return functionPtr;
}

19
शानदार जानकारी के लिए धन्यवाद। क्या आप कुछ अंतर्दृष्टि जोड़ सकते हैं जहां फ़ंक्शन पॉइंटर्स का उपयोग किया जाता है या विशेष रूप से उपयोगी हो सकता है?
रिच.कारपेंटर

326
"functionPtr = & addInt;" भी लिखा जा सकता है (और अक्सर है) "functionPtr = addInt;" जो मानक से यह भी कहता है कि इस संदर्भ में एक फ़ंक्शन का नाम फ़ंक्शन के पते में बदल जाता है।
होवडाल

22
hlovdal, इस संदर्भ में यह व्याख्या करना दिलचस्प है कि यह वही है जो फ़ंक्शन लिखने के लिए सक्षम करता है Ptr = ****************** addInt;
जोहान्स शाउब -

105
@ Rich.Carpenter मुझे पता है कि यह 4 साल बहुत देर हो चुकी है, लेकिन मुझे लगता है कि अन्य लोगों को इससे फायदा हो सकता है: फ़ंक्शन पॉइंटर्स अन्य कार्यों के मापदंडों के रूप में गुजरने के लिए उपयोगी हैं । किसी विषम कारण के लिए मुझे उस उत्तर को खोजने में बहुत खोज हुई। तो मूल रूप से, यह सी छद्म प्रथम श्रेणी की कार्यक्षमता देता है।
विशाल १ '

22
@ रिच.कारपेंटर: रनटाइम सीपीयू डिटेक्शन के लिए फंक्शन पॉइंटर्स अच्छे हैं। स्टार्टअप में SSE, popcnt, AVX, आदि का लाभ लेने के लिए कुछ फ़ंक्शन के कई संस्करण हैं। अपने फ़ंक्शन को वर्तमान CPU के लिए प्रत्येक फ़ंक्शन के सर्वश्रेष्ठ संस्करण में सेट करें। आपके अन्य कोड में, केवल हर जगह सीपीयू सुविधाओं पर सशर्त शाखाएं होने के बजाय फ़ंक्शन पॉइंटर के माध्यम से कॉल करें। तब आप यह तय करने के बारे में जटिल तर्क कर सकते हैं, भले ही यह सीपीयू समर्थन करता है pshufb, यह धीमा है, इसलिए पहले का कार्यान्वयन अभी भी तेज है। x264 / x265 बड़े पैमाने पर इसका उपयोग करते हैं, और खुले स्रोत हैं।
पीटर कॉर्ड्स

303

C में फ़ंक्शन पॉइंटर्स का उपयोग C में ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग करने के लिए किया जा सकता है।

उदाहरण के लिए, निम्नलिखित पंक्तियाँ C में लिखी गई हैं:

String s1 = newString();
s1->set(s1, "hello");

हां, ->और एक newऑपरेटर की कमी एक मृत दे रही है, लेकिन यह निश्चित रूप से लगता है कि हम कुछ Stringवर्ग के पाठ को सेट कर रहे हैं "hello"

फ़ंक्शन पॉइंटर्स का उपयोग करके, सी में तरीकों का अनुकरण करना संभव है

यह कैसे पूरा किया जाता है?

Stringवर्ग वास्तव में एक है structसमारोह संकेत दिए गए जो अनुकरण तरीकों के लिए एक रास्ता के रूप में कार्य के एक समूह के साथ। निम्नलिखित Stringवर्ग की आंशिक घोषणा है :

typedef struct String_Struct* String;

struct String_Struct
{
    char* (*get)(const void* self);
    void (*set)(const void* self, char* value);
    int (*length)(const void* self);
};

char* getString(const void* self);
void setString(const void* self, char* value);
int lengthString(const void* self);

String newString();

जैसा कि देखा जा सकता है, Stringवर्ग के तरीके वास्तव में घोषित फ़ंक्शन के फ़ंक्शन हैं। के उदाहरण को तैयार करने के लिए String, newStringफ़ंक्शन को उनके संबंधित कार्यों के लिए फ़ंक्शन पॉइंटर्स सेट करने के लिए कहा जाता है:

String newString()
{
    String self = (String)malloc(sizeof(struct String_Struct));

    self->get = &getString;
    self->set = &setString;
    self->length = &lengthString;

    self->set(self, "");

    return self;
}

उदाहरण के लिए, विधि को getStringलागू करने के द्वारा कहा जाने वाला फ़ंक्शन getनिम्नलिखित के रूप में परिभाषित किया गया है:

char* getString(const void* self_obj)
{
    return ((String)self_obj)->internal->value;
}

एक बात जिस पर ध्यान दिया जा सकता है वह यह है कि किसी वस्तु के उदाहरण की कोई अवधारणा नहीं है और ऐसी विधियाँ हैं जो वास्तव में एक वस्तु का एक हिस्सा हैं, इसलिए प्रत्येक आह्वान पर एक "आत्म वस्तु" को पारित किया जाना चाहिए। (और internalयह सिर्फ एक छिपी हुई बात है structजिसे पहले कोड लिस्टिंग से हटा दिया गया था - यह सूचना छिपाने का एक तरीका है, लेकिन यह फ़ंक्शन पॉइंटर्स के लिए प्रासंगिक नहीं है।)

इसलिए, ऐसा करने में सक्षम होने के बजाय s1->set("hello");, व्यक्ति को कार्रवाई करने के लिए ऑब्जेक्ट में पास होना चाहिए s1->set(s1, "hello")

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

मान लीजिए कि हम एक उपवर्ग बनाना चाहते हैं String, एक कहते हैं ImmutableString। स्ट्रिंग को अपरिवर्तनीय बनाने के लिए, यह setविधि सुलभ नहीं होगी, जबकि एक्सेस करने के लिए getऔर length"कंस्ट्रक्टर" को बनाए रखने के लिए मजबूर करती है char*:

typedef struct ImmutableString_Struct* ImmutableString;

struct ImmutableString_Struct
{
    String base;

    char* (*get)(const void* self);
    int (*length)(const void* self);
};

ImmutableString newImmutableString(const char* value);

मूल रूप से, सभी उपवर्गों के लिए, उपलब्ध विधियाँ एक बार फिर से कार्य बिंदु हैं। इस बार, setविधि के लिए घोषणा मौजूद नहीं है, इसलिए, इसे ए में नहीं बुलाया जा सकता है ImmutableString

के कार्यान्वयन के लिए ImmutableString, एकमात्र प्रासंगिक कोड "निर्माता" फ़ंक्शन है newImmutableString:

ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = self->base->length;

    self->base->set(self->base, (char*)value);

    return self;
}

त्वरित रूप से ImmutableStringकार्य करने में , फ़ंक्शन पॉइंटर्स getऔर lengthविधियों को वास्तव में चर के माध्यम से जाकर String.getऔर String.lengthविधि को संदर्भित करता है , baseजो कि एक आंतरिक संग्रहित Stringवस्तु है।

एक फ़ंक्शन पॉइंटर का उपयोग एक सुपरक्लास से एक विधि का उत्तराधिकार प्राप्त कर सकता है।

हम आगे C में बहुरूपता जारी रख सकते हैं ।

यदि उदाहरण के लिए हम किसी कारण से कक्षा में हर समय lengthलौटने के लिए विधि के व्यवहार को बदलना चाहते थे , तो यह करना होगा:0ImmutableString

  1. एक फ़ंक्शन जोड़ें जो ओवरराइडिंग lengthविधि के रूप में काम करने वाला है।
  2. "कंस्ट्रक्टर" पर जाएं और फ़ंक्शन पॉइंटर को ओवरराइडिंग lengthविधि पर सेट करें ।

ओवरराइडिंग lengthविधि ImmutableStringजोड़ने से प्रदर्शन किया जा सकता है lengthOverrideMethod:

int lengthOverrideMethod(const void* self)
{
    return 0;
}

फिर, lengthकंस्ट्रक्टर में विधि के लिए फंक्शन पॉइंटर को नीचे झुका दिया जाता है lengthOverrideMethod:

ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = &lengthOverrideMethod;

    self->base->set(self->base, (char*)value);

    return self;
}

अब कक्षा lengthमें विधि के लिए समान व्यवहार रखने के बजाय , अब विधि फ़ंक्शन में परिभाषित व्यवहार को संदर्भित करेगी ।ImmutableStringStringlengthlengthOverrideMethod

मुझे एक डिस्क्लेमर जोड़ना होगा कि मैं अभी भी सीख रहा हूं कि सी में ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग स्टाइल के साथ कैसे लिखना है, इसलिए संभवतः ऐसे बिंदु हैं जो मैंने अच्छी तरह से नहीं समझाए, या ओओपी को लागू करने के लिए सबसे अच्छा होने के संदर्भ में केवल चिह्न हो सकता है। सी। लेकिन मेरा उद्देश्य फ़ंक्शन पॉइंटर्स के कई उपयोगों में से एक का वर्णन करने का प्रयास करना था।

C में ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग कैसे करें, इस बारे में अधिक जानकारी के लिए, कृपया निम्नलिखित प्रश्नों का संदर्भ लें:


22
यह उत्तर भयानक है! इतना ही नहीं इसका तात्पर्य यह है कि OO किसी भी तरह डॉट नोटेशन पर निर्भर करता है, यह आपकी वस्तुओं में कबाड़ डालने को भी प्रोत्साहित करता है!
अलेक्सी एवरचेंको

27
यह सब ठीक है, लेकिन कहीं भी C- शैली OO के पास नहीं है। आपने जो तोड़-मरोड़ कर लागू किया है वह जावास्क्रिप्ट-शैली का प्रोटोटाइप-आधारित OO है। C ++ / पास्कल-शैली OO प्राप्त करने के लिए, आपको निम्न की आवश्यकता होगी: 1. आभासी सदस्यों के साथ प्रत्येक वर्ग की एक आभासी तालिका के लिए एक कास्ट संरचना है । 2. पॉलीमॉर्फिक ऑब्जेक्ट्स में उस स्ट्रक्चर के लिए पॉइंटर रखें। 3. आभासी तरीकों को वर्चुअल टेबल के माध्यम से कॉल करें, और अन्य सभी तरीकों को सीधे - आमतौर पर कुछ ClassName_methodNameफ़ंक्शन नामकरण सम्मेलन से चिपकाकर । तभी आपको C ++ और पास्कल में समान रनटाइम और स्टोरेज लागत मिलती है।
मोनिका

19
एक ऐसी भाषा के साथ OO का काम करना जिसका उद्देश्य OO होना नहीं है, हमेशा एक बुरा विचार है। यदि आप OO चाहते हैं और अभी भी C C C के साथ काम करते हैं।
rbaleksandar

20
@rbaleksandar लिनक्स कर्नेल डेवलपर्स को बताएं। "हमेशा एक बुरा विचार" सख्ती से आपकी राय है, जिसके साथ मैं दृढ़ता से असहमत हूं।
जोनाथन रेनहार्ट

6
मुझे यह उत्तर पसंद है, लेकिन
कैट

227

निकाल दिया जाने वाला मार्गदर्शिका: हाथ से अपना कोड संकलित करके x86 मशीनों पर GCC में फ़ंक्शन पॉइंटर्स का दुरुपयोग कैसे करें:

ये स्ट्रिंग शाब्दिक 32-बिट x86 मशीन कोड के बाइट्स हैं। 0xC3है एक x86 retअनुदेश

आप सामान्य रूप से इन्हें हाथ से नहीं लिखेंगे, आप असेंबली भाषा में लिखेंगे और फिर एक असेंबलर का उपयोग nasmकरके इसे एक फ्लैट बाइनरी में इकट्ठा कर सकते हैं जिसे आप सी स्ट्रिंग शाब्दिक में हेक्सडंप करते हैं।

  1. EAX रजिस्टर पर वर्तमान मूल्य लौटाता है

    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
  2. एक स्वैप फ़ंक्शन लिखें

    int a = 10, b = 20;
    ((void(*)(int*,int*))"\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b")(&a,&b);
    
  3. प्रत्येक बार कुछ फ़ंक्शन को कॉल करते हुए 1000 के लिए एक फॉर-लूप काउंटर लिखें

    ((int(*)())"\x66\x31\xc0\x8b\x5c\x24\x04\x66\x40\x50\xff\xd3\x58\x66\x3d\xe8\x03\x75\xf4\xc3")(&function); // calls function with 1->1000
  4. आप एक पुनरावर्ती फ़ंक्शन भी लिख सकते हैं जो 100 तक गिना जाता है

    const char* lol = "\x8b\x5c\x24\x4\x3d\xe8\x3\x0\x0\x7e\x2\x31\xc0\x83\xf8\x64\x7d\x6\x40\x53\xff\xd3\x5b\xc3\xc3 <- Recursively calls the function at address lol.";
    i = ((int(*)())(lol))(lol);
    

ध्यान दें कि संकलक स्ट्रिंग शाब्दिक को .rodataअनुभाग (या .rdataविंडोज पर) में रखता है , जो पाठ खंड के भाग के रूप में जुड़ा हुआ है (कार्यों के लिए कोड के साथ)।

टेक्स्ट सेगमेंट में Read + Exec अनुमति है, इसलिए स्ट्रिंग फ़ंक्शन को इंगित करने के लिए स्ट्रिंग शाब्दिक की आवश्यकता के बिना काम करता है mprotect()या VirtualProtect()सिस्टम कॉल जैसे कि आपको गतिशील रूप से आवंटित मेमोरी की आवश्यकता होगी। (या gcc -z execstackएक त्वरित हैक के रूप में स्टैक + डेटा सेगमेंट + हीप एक्जीक्यूटेबल के साथ प्रोग्राम को लिंक करता है।)


इन्हें अलग करने के लिए, आप इसे बाइट्स पर एक लेबल लगाने के लिए संकलित कर सकते हैं, और एक डिस्सेम्बलर का उपयोग कर सकते हैं।

// at global scope
const char swap[] = "\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b";

के साथ संकलन gcc -c -m32 foo.cऔर असहमति के साथ objdump -D -rwC -Mintel, हम असेंबली प्राप्त कर सकते हैं, और यह पता लगा सकते हैं कि यह कोड EBX (कॉल-संरक्षित रजिस्टर) को क्लोबबेरिंग द्वारा एबीआई का उल्लंघन करता है और आम तौर पर अक्षम है।

00000000 <swap>:
   0:   8b 44 24 04             mov    eax,DWORD PTR [esp+0x4]   # load int *a arg from the stack
   4:   8b 5c 24 08             mov    ebx,DWORD PTR [esp+0x8]   # ebx = b
   8:   8b 00                   mov    eax,DWORD PTR [eax]       # dereference: eax = *a
   a:   8b 1b                   mov    ebx,DWORD PTR [ebx]
   c:   31 c3                   xor    ebx,eax                # pointless xor-swap
   e:   31 d8                   xor    eax,ebx                # instead of just storing with opposite registers
  10:   31 c3                   xor    ebx,eax
  12:   8b 4c 24 04             mov    ecx,DWORD PTR [esp+0x4]  # reload a from the stack
  16:   89 01                   mov    DWORD PTR [ecx],eax     # store to *a
  18:   8b 4c 24 08             mov    ecx,DWORD PTR [esp+0x8]
  1c:   89 19                   mov    DWORD PTR [ecx],ebx
  1e:   c3                      ret    

  not shown: the later bytes are ASCII text documentation
  they're not executed by the CPU because the ret instruction sends execution back to the caller

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


8
नोट: यदि डेटा निष्पादन रोकथाम सक्षम है (जैसे Windows XP SP2 +) पर यह काम नहीं करता है, क्योंकि C स्ट्रिंग्स को आम तौर पर निष्पादन योग्य के रूप में चिह्नित नहीं किया जाता है।
सिक्योरिटीमैट

5
हे मैट! अनुकूलन स्तर के आधार पर, GCC अक्सर तार खंडों में स्ट्रिंग स्थिरांक को इनलाइन करेगा, इसलिए यह विंडोज़ के नए संस्करण पर भी काम करेगा, बशर्ते कि आप इस प्रकार के अनुकूलन को अस्वीकार न करें। (IIRC, दो साल पहले की मेरी पोस्ट के समय MINGW संस्करण, डिफ़ॉल्ट ऑप्टिमाइज़ेशन स्तर पर स्ट्रिंग इनरल्स में)
ली

10
क्या कोई समझा सकता है कि यहाँ क्या हो रहा है? वे अजीब दिखने वाले स्ट्रिंग लिटरल क्या हैं?
अजय

56
@ajay ऐसा लग रहा है कि वह कच्चे हेक्सिडेसिमल मान लिख रहा है (उदाहरण के लिए '\ x00' '/ 0' के समान है, वे दोनों 0 के बराबर हैं), फिर स्ट्रिंग को C फ़ंक्शन पॉइंटर में डालना, फिर निष्पादित करना सी फ़ंक्शन पॉइंटर क्योंकि वह शैतान है।
ejk314

3
हाय FUZxxl, मुझे लगता है कि यह संकलक और ऑपरेटिंग सिस्टम संस्करण के आधार पर भिन्न हो सकता है। उपरोक्त कोड codepad.org पर ठीक चल रहा है; codepad.org/FMSDQ3ME
ली

115

फंक्शन पॉइंटर्स के लिए मेरा पसंदीदा उपयोग सस्ता और आसान पुनरावृत्तियों के रूप में है -

#include <stdio.h>
#define MAX_COLORS  256

typedef struct {
    char* name;
    int red;
    int green;
    int blue;
} Color;

Color Colors[MAX_COLORS];


void eachColor (void (*fp)(Color *c)) {
    int i;
    for (i=0; i<MAX_COLORS; i++)
        (*fp)(&Colors[i]);
}

void printColor(Color* c) {
    if (c->name)
        printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue);
}

int main() {
    Colors[0].name="red";
    Colors[0].red=255;
    Colors[1].name="blue";
    Colors[1].blue=255;
    Colors[2].name="black";

    eachColor(printColor);
}

7
यदि आप किसी तरह पुनरावृत्तियों से किसी भी आउटपुट को निकालना चाहते हैं, तो आपको उपयोगकर्ता द्वारा निर्दिष्ट डेटा के लिए एक संकेतक भी पास करना चाहिए (विचार बंद करें)।
अलेक्सी एवेर्चेन्को

1
माना। मेरी iterators के सभी इस तरह दिखेगा: int (*cb)(void *arg, ...)। पुनरावृत्त का मान भी मुझे जल्दी बंद कर देता है (यदि गैर-शून्य)।
जोनाथन रेनहार्ट

24

एक बार मूल घोषणाकर्ता होने के बाद फ़ंक्शन पॉइंटर्स घोषित करना आसान हो जाता है:

  • आईडी: ID: आईडी एक है
  • सूचक: *D: करने के लिए डी सूचक
  • फंक्शन: D(<parameters>): डी समारोह लेने <मानकों >लौटने

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

typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);

जैसा कि आप देख रहे हैं, यह बहुत आसान है इसे टाइपडिफ का उपयोग करके बनाना। बिना टाइपराइफ़ के, यह उपरोक्त घोषणा नियमों के साथ कठिन नहीं है, लगातार लागू किया जाता है। जैसा कि आप देखते हैं कि मैं सूचक बिंदु को याद करता हूं और फ़ंक्शन वापस लौटता है। यह घोषणा के बहुत बाईं ओर दिखाई देता है, और ब्याज की नहीं है: यह अंत में जोड़ा जाता है अगर कोई पहले से ही घोषणाकर्ता का निर्माण करता है। चलो करते हैं। इसका लगातार निर्माण करना, पहली चिंता - संरचना का उपयोग करके दिखाना [और ]:

function taking 
    [pointer to [function taking [void] returning [int]]] 
returning
    [pointer to [function taking [char] returning [int]]]

जैसा कि आप देखते हैं, एक एक के बाद एक घोषणाकर्ताओं को जोड़कर एक प्रकार का पूरी तरह से वर्णन कर सकता है। निर्माण दो तरह से किया जा सकता है। एक नीचे-ऊपर है, बहुत सही चीज (पत्तियों) के साथ शुरू होता है और पहचानकर्ता तक रास्ता काम करता है। दूसरा तरीका टॉप-डाउन है, जो पहचानकर्ता पर शुरू होता है, पत्तियों तक नीचे जाने का काम करता है। मैं दोनों तरह से दिखाऊंगा।

नीचे से ऊपर

निर्माण दाईं ओर की चीज़ से शुरू होता है: लौटाई गई चीज़, जो कि चार ले रही फंक्शन है। घोषणाकर्ताओं को अलग रखने के लिए, मैं उन्हें नंबर देने जा रहा हूं:

D1(char);

चार पैरामीटर सीधे डाला, क्योंकि यह तुच्छ है। द्वारा सूचक को सूचक द्वारा प्रतिस्थापित D1करके जोड़ा जा रहा है *D2। ध्यान दें कि हमें कोष्ठक को चारों ओर लपेटना है *D2। जिसे *-operatorफंक्शन-कॉल ऑपरेटर की पूर्ववर्ती स्थिति को देखकर जाना जा सकता है ()। हमारे कोष्ठक के बिना, कंपाइलर इसे पढ़ेगा *(D2(char p))। लेकिन यह *D2निश्चित रूप से अब तक डी 1 की जगह नहीं होगी । कोष्ठकों को हमेशा घोषणाकर्ताओं के आसपास अनुमति दी जाती है। यदि आप वास्तव में उनमें से बहुत कुछ जोड़ते हैं, तो आप कुछ भी गलत नहीं करते हैं।

(*D2)(char);

वापसी प्रकार पूरा हो गया है! अब, आइए, वापसी कीD2 घोषणा करने वाले फ़ंक्शन डिक्लेरेटर फ़ंक्शन<parameters> द्वारा प्रतिस्थापित करते हैं , जो D3(<parameters>)कि हम अभी हैं।

(*D3(<parameters>))(char)

ध्यान दें कि किसी भी कोष्ठक की आवश्यकता नहीं है, क्योंकि हम इस समय एक फ़ंक्शन-घोषणाकर्ता बनना चाहते हैं D3 और सूचक नहीं। महान, केवल एक चीज बची है, इसके लिए पैरामीटर हैं। पैरामीटर ठीक उसी तरह से किया जाता है जैसे हमने रिटर्न टाइप किया है, बस charद्वारा प्रतिस्थापित किया गया है void। तो मैं इसे कॉपी करूँगा:

(*D3(   (*ID1)(void)))(char)

मैंने बदल दिया D2है ID1, क्योंकि हम उस पैरामीटर के साथ समाप्त हो गए हैं (यह पहले से ही एक फ़ंक्शन के लिए एक संकेतक है - किसी अन्य घोषणाकर्ता की आवश्यकता नहीं है)। ID1पैरामीटर का नाम होगा। अब, मैंने ऊपर बताया कि अंत में एक प्रकार का जोड़ा जाता है, जिसे उन सभी घोषणाकर्ता संशोधित करते हैं - प्रत्येक घोषणा के बहुत बाईं ओर दिखाई देने वाला। फ़ंक्शंस के लिए, वह रिटर्न प्रकार बन जाता है। पॉइंटर्स टू टाइप आदि के लिए ... यह दिलचस्प है जब टाइप के नीचे लिखा जाता है, तो यह विपरीत क्रम में दिखाई देगा, बिल्कुल दाईं ओर :) वैसे भी, इसे प्रतिस्थापित करने से पूर्ण घोषणा प्राप्त होती है। दोनों बार intबेशक।

int (*ID0(int (*ID1)(void)))(char)

मैंने ID0उस उदाहरण में फ़ंक्शन के पहचानकर्ता को बुलाया है ।

ऊपर से नीचें

यह पहचानकर्ता में टाइप के विवरण में बहुत बाईं ओर शुरू होता है, उस घोषणाकर्ता को लपेटता है क्योंकि हम दाईं ओर से चलते हैं। फ़ंक्शन के साथ प्रारंभ करें जो कि रिटर्निंग पैरामीटर ले रहा है<>

ID0(<parameters>)

विवरण में अगली बात ("लौटने के बाद") सूचक थी । आइए इसे शामिल करें:

*ID0(<parameters>)

फिर अगली चीज थी फंक्शनलन रिटर्निंग <पैरामीटर> । पैरामीटर एक साधारण चार है, इसलिए हम इसे फिर से सही में डालते हैं, क्योंकि यह वास्तव में तुच्छ है।

(*ID0(<parameters>))(char)

हमारे द्वारा जोड़े गए कोष्ठकों पर ध्यान दें, क्योंकि हम फिर से चाहते हैं कि *पहले बांधें, और फिर(char)। अन्यथा इसे पढ़ा होगा समारोह लेने <मापदंडों >समारोह लौटने ... । कार्य, रिटर्निंग फ़ंक्शंस की अनुमति भी नहीं है।

अब हमें सिर्फ <पैरामीटर लगाने की जरूरत है >। मैं विचलन का एक छोटा संस्करण दिखाऊंगा, क्योंकि मुझे लगता है कि आप पहले से ही अब तक यह विचार कर चुके हैं कि यह कैसे करना है।

pointer to: *ID1
... function taking void returning: (*ID1)(void)

बस घोषणाकर्ताओं के intसामने रखें जैसे हमने नीचे-ऊपर के साथ किया था, और हम समाप्त कर रहे हैं

int (*ID0(int (*ID1)(void)))(char)

अच्छी बात है

क्या बॉटम-अप या टॉप-डाउन बेहतर है? मैं नीचे-ऊपर का उपयोग कर रहा हूं, लेकिन कुछ लोग टॉप-डाउन के साथ अधिक सहज हो सकते हैं। मुझे लगता है कि यह स्वाद की बात है। संयोग से, यदि आप उस घोषणा में सभी ऑपरेटरों को लागू करते हैं, तो आप एक इंट प्राप्त कर रहे हैं:

int v = (*ID0(some_function_pointer))(some_char);

यह C में घोषणाओं की एक अच्छी संपत्ति है: घोषणा यह दावा करती है कि यदि उन ऑपरेटरों को पहचानकर्ता का उपयोग करके अभिव्यक्ति में उपयोग किया जाता है, तो यह बहुत बाईं ओर के प्रकार का उत्पादन करता है। यह सरणियों के लिए भी ऐसा ही है।

आशा है आपको यह छोटा सा ट्यूटोरियल पसंद आया होगा! अब हम इससे लिंक कर सकते हैं जब लोग कार्यों के अजीब घोषणा सिंटैक्स के बारे में आश्चर्य करते हैं। मैंने यथासंभव कम सी इंटर्नल्स डालने की कोशिश की। इसमें चीजों को संपादित / ठीक करने के लिए स्वतंत्र महसूस करें।


24

फ़ंक्शन पॉइंटर्स के लिए एक और अच्छा उपयोग:
संस्करणों के बीच दर्द रहित रूप से स्विच करना

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

version.h

// First, undefine all macros associated with version.h
#undef DEBUG_VERSION
#undef RELEASE_VERSION
#undef INVALID_VERSION


// Define which version we want to use
#define DEBUG_VERSION       // The current version
// #define RELEASE_VERSION  // To be uncommented when finished debugging

#ifndef __VERSION_H_      /* prevent circular inclusions */
    #define __VERSION_H_  /* by using protection macros */
    void board_init();
    void noprintf(const char *c, ...); // mimic the printf prototype
#endif

// Mimics the printf function prototype. This is what I'll actually 
// use to print stuff to the screen
void (* zprintf)(const char*, ...); 

// If debug version, use printf
#ifdef DEBUG_VERSION
    #include <stdio.h>
#endif

// If both debug and release version, error
#ifdef DEBUG_VERSION
#ifdef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

// If neither debug or release version, error
#ifndef DEBUG_VERSION
#ifndef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

#ifdef INVALID_VERSION
    // Won't allow compilation without a valid version define
    #error "Invalid version definition"
#endif

में version.cमैं उपस्थित 2 फंक्शन प्रोटोटाइप को परिभाषित करूंगाversion.h

version.c

#include "version.h"

/*****************************************************************************/
/**
* @name board_init
*
* Sets up the application based on the version type defined in version.h.
* Includes allowing or prohibiting printing to STDOUT.
*
* MUST BE CALLED FIRST THING IN MAIN
*
* @return    None
*
*****************************************************************************/
void board_init()
{
    // Assign the print function to the correct function pointer
    #ifdef DEBUG_VERSION
        zprintf = &printf;
    #else
        // Defined below this function
        zprintf = &noprintf;
    #endif
}

/*****************************************************************************/
/**
* @name noprintf
*
* simply returns with no actions performed
*
* @return   None
*
*****************************************************************************/
void noprintf(const char* c, ...)
{
    return;
}

ध्यान दें कि फ़ंक्शन पॉइंटर को किस version.hरूप में प्रोटोटाइप किया गया है

void (* zprintf)(const char *, ...);

जब इसे एप्लिकेशन में संदर्भित किया जाता है, तो यह इंगित करने वाले स्थान पर निष्पादित करना शुरू कर देगा, जिसे अभी तक परिभाषित नहीं किया गया है।

में version.c, में नोटिस board_init()समारोह जहां zprintfएक अनूठा समारोह (जिसका कार्य हस्ताक्षर से मेल खाता है) असाइन किया गया है संस्करण के आधार पर कि में परिभाषित किया गया हैversion.h

zprintf = &printf; zprintf डीबगिंग उद्देश्यों के लिए printf कॉल करता है

या

zprintf = &noprint; zprintf सिर्फ लौटता है और अनावश्यक कोड नहीं चलाएगा

कोड चलाना इस तरह होगा:

mainProg.c

#include "version.h"
#include <stdlib.h>
int main()
{
    // Must run board_init(), which assigns the function
    // pointer to an actual function
    board_init();

    void *ptr = malloc(100); // Allocate 100 bytes of memory
    // malloc returns NULL if unable to allocate the memory.

    if (ptr == NULL)
    {
        zprintf("Unable to allocate memory\n");
        return 1;
    }

    // Other things to do...
    return 0;
}

उपरोक्त कोड printfडिबग मोड में उपयोग करेगा , या यदि रिलीज़ मोड में कुछ भी नहीं है। यह पूरी परियोजना के माध्यम से जाने और कोड को हटाने या हटाने से बहुत आसान है। मुझे जो कुछ करने की ज़रूरत है वह संस्करण को बदलना है version.hऔर कोड बाकी काम करेगा!


4
यू प्रदर्शन का बहुत समय खोने के लिए खड़ा है। इसके बजाय आप एक मैक्रो का उपयोग कर सकते हैं जो डिबग / रिलीज़ के आधार पर कोड के एक अनुभाग को सक्षम और अक्षम करता है।
अल्फा गोकू

19

फ़ंक्शन पॉइंटर आमतौर पर द्वारा परिभाषित किया जाता है typedef, और इसका उपयोग परम और रिटर्न वैल्यू के रूप में किया जाता है।

उपरोक्त उत्तर पहले से ही बहुत कुछ समझाते हैं, मैं सिर्फ एक पूर्ण उदाहरण देता हूं:

#include <stdio.h>

#define NUM_A 1
#define NUM_B 2

// define a function pointer type
typedef int (*two_num_operation)(int, int);

// an actual standalone function
static int sum(int a, int b) {
    return a + b;
}

// use function pointer as param,
static int sum_via_pointer(int a, int b, two_num_operation funp) {
    return (*funp)(a, b);
}

// use function pointer as return value,
static two_num_operation get_sum_fun() {
    return &sum;
}

// test - use function pointer as variable,
void test_pointer_as_variable() {
    // create a pointer to function,
    two_num_operation sum_p = &sum;
    // call function via pointer
    printf("pointer as variable:\t %d + %d = %d\n", NUM_A, NUM_B, (*sum_p)(NUM_A, NUM_B));
}

// test - use function pointer as param,
void test_pointer_as_param() {
    printf("pointer as param:\t %d + %d = %d\n", NUM_A, NUM_B, sum_via_pointer(NUM_A, NUM_B, &sum));
}

// test - use function pointer as return value,
void test_pointer_as_return_value() {
    printf("pointer as return value:\t %d + %d = %d\n", NUM_A, NUM_B, (*get_sum_fun())(NUM_A, NUM_B));
}

int main() {
    test_pointer_as_variable();
    test_pointer_as_param();
    test_pointer_as_return_value();

    return 0;
}

14

C में फ़ंक्शन पॉइंटर्स के लिए एक बड़ा उपयोग रन-टाइम में चयनित फ़ंक्शन को कॉल करना है। उदाहरण के लिए, सी रन-टाइम लाइब्रेरी में दो रूटीन हैं, qsortऔर bsearch, जो एक फ़ंक्शन को एक पॉइंटर लेते हैं जिसे दो वस्तुओं को क्रमबद्ध करने के लिए कहा जाता है; यह आपको किसी भी मानदंड के आधार पर क्रमबद्ध या खोज करने की अनुमति देता है, जिसका आप उपयोग करना चाहते हैं।

एक बहुत ही बुनियादी उदाहरण, अगर कोई एक फ़ंक्शन है जिसे print(int x, int y)बदले में फ़ंक्शन (या तो , add()या sub()जो एक ही प्रकार के हैं) को कॉल करने की आवश्यकता हो सकती है तो हम क्या करेंगे, हम फ़ंक्शन को एक फ़ंक्शन पॉइंटर जोड़ देंगे print()जैसा कि नीचे दिखाया गया है :

#include <stdio.h>

int add()
{
   return (100+10);
}

int sub()
{
   return (100-10);
}

void print(int x, int y, int (*func)())
{
    printf("value is: %d\n", (x+y+(*func)()));
}

int main()
{
    int x=100, y=200;
    print(x,y,add);
    print(x,y,sub);

    return 0;
}

आउटपुट है:

मूल्य है: 410
मूल्य है: 390


10

स्क्रैच फ़ंक्शन से शुरू होने वाले कुछ मेमोरी एड्रेस हैं जहां से वे निष्पादित करना शुरू करते हैं। असेंबली लैंग्वेज में उन्हें कहा जाता है ("फ़ंक्शन का मेमोरी एड्रेस")

1. पहले आप कार्य करने के लिए एक सूचक घोषित करने की आवश्यकता है 2. वांछित समारोह का पता दर्ज करें

**** नोट-> फ़ंक्शन समान प्रकार के होने चाहिए ****

यह सरल कार्यक्रम हर बात पर प्रकाश डालेगा।

#include<stdio.h>
void (*print)() ;//Declare a  Function Pointers
void sayhello();//Declare The Function Whose Address is to be passed
                //The Functions should Be of Same Type
int main()
{
 print=sayhello;//Addressof sayhello is assigned to print
 print();//print Does A call To The Function 
 return 0;
}

void sayhello()
{
 printf("\n Hello World");
}

यहां छवि विवरण दर्ज करेंउसके बाद देखते हैं कि कैसे मशीन उन्हें देती है। 32 बिट आर्किटेक्चर में उपरोक्त प्रोग्राम की मशीन निर्देश की झलक।

लाल निशान क्षेत्र दिखा रहा है कि पते का आदान-प्रदान कैसे किया जा रहा है और बाज में संग्रहीत किया जा रहा है। तब उनका ईगल पर कॉल निर्देश है। eax में फ़ंक्शन का वांछित पता होता है।


8

एक फंक्शन पॉइंटर एक वैरिएबल है जिसमें एक फंक्शन का पता होता है। चूंकि यह एक पॉइंटर वैरिएबल है, हालांकि कुछ प्रतिबंधित प्रॉपर्टीज के साथ, आप इसे बहुत ज्यादा इस्तेमाल कर सकते हैं जैसे कि आप डेटा स्ट्रक्चर्स में कोई अन्य पॉइंटर वैरिएबल है।

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

एक फंक्शन पॉइंटर वैरिएबल का आकार, वैरिएबल के कब्जे वाले बाइट्स की संख्या, अंतर्निहित आर्किटेक्चर, जैसे x32 या x64 या जो भी हो, के आधार पर भिन्न हो सकती है।

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

कुछ उदाहरण:

int func (int a, char *pStr);    // declares a function

int (*pFunc)(int a, char *pStr);  // declares or defines a function pointer

int (*pFunc2) ();                 // declares or defines a function pointer, no parameter list specified.

int (*pFunc3) (void);             // declares or defines a function pointer, no arguments.

पहले दो घोषणाएँ कुछ इस तरह हैं:

  • funcएक फ़ंक्शन है जो एक intऔर एक लेता है और एक char *रिटर्न देता हैint
  • pFuncएक फंक्शन पॉइंटर है, जिसे एक फंक्शन का पता सौंपा जाता है, जो inta char *और a को रिटर्न करता हैint

तो ऊपर से हमारे पास एक स्रोत रेखा हो सकती है जिसमें फ़ंक्शन func()का पता फ़ंक्शन पॉइंटर वैरिएबल pFuncको सौंपा जाता है pFunc = func;

फ़ंक्शन पॉइंटर घोषणा / परिभाषा के साथ उपयोग किए गए वाक्यविन्यास पर ध्यान दें जिसमें प्राकृतिक ऑपरेटर पूर्ववर्ती नियमों को पार करने के लिए कोष्ठक का उपयोग किया जाता है।

int *pfunc(int a, char *pStr);    // declares a function that returns int pointer
int (*pFunc)(int a, char *pStr);  // declares a function pointer that returns an int

कई अलग-अलग उपयोग उदाहरण

फ़ंक्शन पॉइंटर के उपयोग के कुछ उदाहरण:

int (*pFunc) (int a, char *pStr);    // declare a simple function pointer variable
int (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointers
int (**pFunc)(int a, char *pStr);    // declare a pointer to a function pointer variable
struct {                             // declare a struct that contains a function pointer
    int x22;
    int (*pFunc)(int a, char *pStr);
} thing = {0, func};                 // assign values to the struct variable
char * xF (int x, int (*p)(int a, char *pStr));  // declare a function that has a function pointer as an argument
char * (*pxF) (int x, int (*p)(int a, char *pStr));  // declare a function pointer that points to a function that has a function pointer as an argument

आप फ़ंक्शन पॉइंटर की परिभाषा में चर लंबाई पैरामीटर सूची का उपयोग कर सकते हैं।

int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);

या आप एक पैरामीटर सूची बिल्कुल भी निर्दिष्ट नहीं कर सकते। यह उपयोगी हो सकता है लेकिन यह सी कंपाइलर द्वारा प्रदान की गई तर्क सूची पर जांच करने के अवसर को समाप्त कर देता है।

int  sum ();      // nothing specified in the argument list so could be anything or nothing
int (*psum)();
int  sum2(void);  // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);

सी स्टाइल की कास्ट

आप फंक्शन पॉइंटर्स के साथ C स्टाइल कास्ट्स का उपयोग कर सकते हैं। हालाँकि, इस बात से अवगत रहें कि C संकलक जाँच के बारे में शिथिल हो सकता है या त्रुटियों के बजाय चेतावनी दे सकता है।

int sum (int a, char *b);
int (*psplsum) (int a, int b);
psplsum = sum;               // generates a compiler warning
psplsum = (int (*)(int a, int b)) sum;   // no compiler warning, cast to function pointer
psplsum = (int *(int a, int b)) sum;     // compiler error of bad cast generated, parenthesis are required.

फंक्शन पॉइंटर की समानता से तुलना करें

आप देख सकते हैं कि एक फ़ंक्शन पॉइंटर एक ifबयान का उपयोग करके एक विशेष फ़ंक्शन पते के बराबर है, हालांकि मुझे यकीन नहीं है कि यह कितना उपयोगी होगा। अन्य तुलना संचालकों की उपयोगिता भी कम प्रतीत होगी।

static int func1(int a, int b) {
    return a + b;
}

static int func2(int a, int b, char *c) {
    return c[0] + a + b;
}

static int func3(int a, int b, char *x) {
    return a + b;
}

static char *func4(int a, int b, char *c, int (*p)())
{
    if (p == func1) {
        p(a, b);
    }
    else if (p == func2) {
        p(a, b, c);      // warning C4047: '==': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
    } else if (p == func3) {
        p(a, b, c);
    }
    return c;
}

फंक्शन पॉइंटर्स का एक ऐरे

और यदि आप फ़ंक्शन पॉइंट की एक सरणी रखना चाहते हैं, जिसमें से प्रत्येक तत्व में तर्क सूची में अंतर है, तो आप एक फ़ंक्शन पॉइंटर को तर्क सूची के साथ परिभाषित कर सकते हैं अनिर्दिष्ट ( voidजिसका अर्थ है कि कोई तर्क नहीं है लेकिन सिर्फ अनिर्दिष्ट) निम्नलिखित जैसे कुछ आप सी संकलक से चेतावनी देख सकते हैं। यह भी एक फ़ंक्शन पॉइंटर पैरामीटर फ़ंक्शन के लिए काम करता है:

int(*p[])() = {       // an array of function pointers
    func1, func2, func3
};
int(**pp)();          // a pointer to a function pointer


p[0](a, b);
p[1](a, b, 0);
p[2](a, b);      // oops, left off the last argument but it compiles anyway.

func4(a, b, 0, func1);
func4(a, b, 0, func2);  // warning C4047: 'function': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
func4(a, b, 0, func3);

    // iterate over the array elements using an array index
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
    func4(a, b, 0, p[i]);
}
    // iterate over the array elements using a pointer
for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) {
    (*pp)(a, b, 0);          // pointer to a function pointer so must dereference it.
    func4(a, b, 0, *pp);     // pointer to a function pointer so must dereference it.
}

फंक्शन पॉइंटर्स के साथ namespaceग्लोबल का उपयोग करते हुए सी स्टाइलstruct

आप staticकिसी फ़ंक्शन को निर्दिष्ट करने के लिए कीवर्ड का उपयोग कर सकते हैं जिसका नाम फ़ाइल स्कोप है और फिर इसे namespaceC ++ की कार्यक्षमता के समान कुछ प्रदान करने के तरीके के रूप में एक वैश्विक चर पर असाइन करें ।

एक हेडर फ़ाइल में एक ऐसी संरचना को परिभाषित करते हैं जो एक वैश्विक चर के साथ हमारा नाम स्थान होगा जो इसका उपयोग करता है।

typedef struct {
   int (*func1) (int a, int b);             // pointer to function that returns an int
   char *(*func2) (int a, int b, char *c);  // pointer to function that returns a pointer
} FuncThings;

extern const FuncThings FuncThingsGlobal;

फिर सी स्रोत फ़ाइल में:

#include "header.h"

// the function names used with these static functions do not need to be the
// same as the struct member names. It's just helpful if they are when trying
// to search for them.
// the static keyword ensures these names are file scope only and not visible
// outside of the file.
static int func1 (int a, int b)
{
    return a + b;
}

static char *func2 (int a, int b, char *c)
{
    c[0] = a % 100; c[1] = b % 50;
    return c;
}

const FuncThings FuncThingsGlobal = {func1, func2};

इसके बाद फ़ंक्शन को एक्सेस करने के लिए ग्लोबल स्ट्रक्चर वेरिएबल का पूरा नाम और मेंबर का नाम निर्दिष्ट करके उपयोग किया जाएगा। constसंशोधक वैश्विक इतने पर प्रयोग किया जाता है कि यह दुर्घटना से नहीं बदला जा सकता।

int abcd = FuncThingsGlobal.func1 (a, b);

फ़ंक्शन पॉइंटर्स के अनुप्रयोग क्षेत्र

डीएलएल लाइब्रेरी घटक सी शैली के namespaceदृष्टिकोण के समान कुछ कर सकता है जिसमें एक लाइब्रेरी इंटरफ़ेस में एक फैक्ट्री विधि से एक विशेष लाइब्रेरी इंटरफ़ेस का अनुरोध किया जाता है जो एक structफ़ंक्शन फ़ंक्शन के निर्माण का समर्थन करता है .. यह लाइब्रेरी इंटरफ़ेस अनुरोधित DLL संस्करण को लोड करता है, बनाता है आवश्यक फ़ंक्शन बिंदुओं के साथ एक संरचना, और फिर उपयोग के लिए अनुरोध करने वाले कॉलर को संरचना लौटाता है।

typedef struct {
    HMODULE  hModule;
    int (*Func1)();
    int (*Func2)();
    int(*Func3)(int a, int b);
} LibraryFuncStruct;

int  LoadLibraryFunc LPCTSTR  dllFileName, LibraryFuncStruct *pStruct)
{
    int  retStatus = 0;   // default is an error detected

    pStruct->hModule = LoadLibrary (dllFileName);
    if (pStruct->hModule) {
        pStruct->Func1 = (int (*)()) GetProcAddress (pStruct->hModule, "Func1");
        pStruct->Func2 = (int (*)()) GetProcAddress (pStruct->hModule, "Func2");
        pStruct->Func3 = (int (*)(int a, int b)) GetProcAddress(pStruct->hModule, "Func3");
        retStatus = 1;
    }

    return retStatus;
}

void FreeLibraryFunc (LibraryFuncStruct *pStruct)
{
    if (pStruct->hModule) FreeLibrary (pStruct->hModule);
    pStruct->hModule = 0;
}

और इस रूप में इस्तेमाल किया जा सकता है:

LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
//  ....
myLib.Func1();
//  ....
FreeLibraryFunc (&myLib);

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

प्रतिनिधि बिंदु, प्रतिनिधि, हैंडलर और कॉलबैक बनाने के लिए

आप फ़ंक्शन पॉइंटर्स का उपयोग कुछ कार्य या कार्यक्षमता को सौंपने के तरीके के रूप में कर सकते हैं। C में क्लासिक उदाहरण मानक सी लाइब्रेरी फ़ंक्शंस के साथ तुलनात्मक प्रतिनिधि फ़ंक्शन पॉइंटर का उपयोग किया जाता है qsort()और bsearch()आइटमों की सूची को सॉर्ट करने के लिए या आइटमों की एक सॉर्ट की गई सूची पर बाइनरी खोज का प्रदर्शन करने के लिए कोलाजेशन ऑर्डर प्रदान करता है। तुलना फ़ंक्शन प्रतिनिधि, सॉर्ट या बाइनरी खोज में उपयोग किए जाने वाले कोलाज़ एल्गोरिथ्म को निर्दिष्ट करता है।

एक अन्य उपयोग C ++ मानक टेम्प्लेट लाइब्रेरी कंटेनर में एल्गोरिथ्म लागू करने के समान है।

void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for ( ; pList < pListEnd; pList += sizeItem) {
        p (pList);
    }

    return pArray;
}

int pIncrement(int *pI) {
    (*pI)++;

    return 1;
}

void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for (; pList < pListEnd; pList += sizeItem) {
        p(pList, pResult);
    }

    return pArray;
}

int pSummation(int *pI, int *pSum) {
    (*pSum) += *pI;

    return 1;
}

// source code and then lets use our function.
int intList[30] = { 0 }, iSum = 0;

ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement);
ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation);

एक अन्य उदाहरण जीयूआई स्रोत कोड के साथ है जिसमें एक विशेष घटना के लिए एक हैंडलर एक फ़ंक्शन पॉइंटर प्रदान करके पंजीकृत होता है जिसे वास्तव में घटना होने पर कहा जाता है। अपने संदेश मानचित्रों के साथ Microsoft MFC फ्रेमवर्क विंडोज़ संदेशों को संभालने के लिए कुछ इसी तरह का उपयोग करता है जो एक विंडो या थ्रेड पर वितरित किए जाते हैं।

अतुल्यकालिक फ़ंक्शन जिन्हें कॉलबैक की आवश्यकता होती है, वे ईवेंट हैंडलर के समान हैं। एसिंक्रोनस फ़ंक्शन का उपयोगकर्ता कुछ कार्रवाई शुरू करने के लिए एसिंक्रोनस फ़ंक्शन को कॉल करता है और एक फ़ंक्शन पॉइंटर प्रदान करता है जिसे कार्रवाई पूरा होने पर एसिंक्रोनस फ़ंक्शन कॉल करेगा। इस मामले में यह घटना अतुल्यकालिक फ़ंक्शन है जो अपना कार्य पूरा कर रही है।


0

चूंकि फंक्शन पॉइंटर्स अक्सर कॉलबैक टाइप किए जाते हैं, इसलिए आप टाइप सेफ कॉलबैक पर एक नज़र डालना चाहते हैं । यही बात एंट्री पॉइंट पर लागू होती है, ऐसे फंक्शंस के आदि जो कॉलबैक नहीं हैं।

C एक ही समय में काफी चंचल और क्षमाशील है :)

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