Kaip funkcijos rodomos C?

Pastaruoju metu turėjau šiek tiek patirties su funkcijų rodikliais C.

Taigi, tęsiant tradiciją atsakyti į savo klausimus, aš nusprendžiau pateikti nedidelę pagrindinių pamatų santrauką tiems, kuriems reikalingas greitas panardinimas į šią temą.

1018
08 мая '09 в 18:49 2009-05-08 18:49 nustatė Yuval Adam gegužės 08'09, 18:49 2009-05-08 18:49
@ 12 atsakymų

Funkcijų rodyklės C

Pradėkime nuo pagrindinės funkcijos, kurią nurodysime:

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

Pirma, leiskite žymekliui nustatyti funkciją, kuri užima 2 int ir grąžina int :

 int (*functionPtr)(int,int); 

Dabar galime saugiai nurodyti savo funkciją:

 functionPtr = > 

Dabar, kai turime funkcijų rodyklę, naudokite jį:

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

Rodyklės perdavimas kitai funkcijai iš esmės yra tas pats:

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

Taip pat galime naudoti funkcijų rodykles grįžimo vertei (pabandykite išlaikyti, tai tampa neįprastu):

 // 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) =  return functionPtr; } 

Tačiau daug maloniau naudoti „ 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 =  return functionPtr; } 
1253
08 мая '09 в 18:49 2009-05-08 18:49 atsakymą pateikė Yuval Adam gegužės 08 d. 09:18:49 2009-05-08 18:49

Funkciniai rodikliai C gali būti naudojami atlikti į objektą orientuotą programavimą C.

Pvz., C eilutėje yra šios eilutės:

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

Taip, -> ir new operatoriaus nebuvimas yra miręs smūgis, bet atrodo, kad tai reiškia, kad mes nustatome String klasės tekstą kaip "hello" .

Naudodami funkcijų rodykles, galite sekti metodus C.

Kaip tai pasiekiama?

String klasė iš tikrųjų yra struct su funkcijų rodyklėmis, kurios veikia kaip būdas imituoti metodus. Toliau pateikiama dalinė String klasės deklaracija:

 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(); 

Kaip matote, String klasės metodai iš tikrųjų rodo deklaruotos funkcijos funkciją. Ruošiant String egzempliorių, funkcija „ newStringnewString nustatyti nuorodas į jų atitinkamas funkcijas:

 String newString() { String self = (String)malloc(sizeof(struct String_Struct)); self->get =  self->set =  self->length =  self->set(self, ""); return self; } 

Pvz., getString funkcija, kurią get gavimo metodas, apibrėžiama taip:

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

Vienas dalykas, kurį galima pamatyti, yra tai, kad nėra objekto egzemplioriaus ir metodų, kurie iš tikrųjų yra objekto dalis, todėl kiekvienam skambučiui pats objektas turi būti perduotas. (Ir internal yra tik paslėpta struct kuri buvo praleista iš ankstesnio kodų sąrašo - tai būdas paslėpti informaciją, tačiau tai netaikoma funkcijų rodyklėms.)

Taigi vietoj to, kad s1->set("hello"); , turite eiti į objektą, kad atliktumėte veiksmą s1->set(s1, "hello") .

Su tokiu mažu paaiškinimu, kuris turi būti perduotas saitui į save, pereisime prie kitos dalies, kuri yra paveldėjimas C.

Tarkime, kad norime ImmutableString String , tarkim, „ ImmutableString . Jei norite keisti eilutę, set metodas nebus pasiekiamas, tačiau vis tiek get prieigą ir get length , ir privers konstruktorių priimti 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); 
border=0

Iš esmės visiems poklasiams galimi metodai vėl veikia kaip funkcijų rodikliai. Šį kartą nėra set metodo deklaracijos, todėl jis negali būti vadinamas „ ImmutableString .

Kalbant apie „ ImmutableString įgyvendinimą, vienintelis tinkamas kodas yra konstruktoriaus funkcija, 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; } 

Kuriant „ ImmutableString funkciją, String.length metodus „ get ir „ length iš tikrųjų reiškia „ String.get ir „ String.length metodus, kurie pereina per kintamą base , kuri yra viduje saugoma String .

Funkcijų rodyklės naudojimas gali paveldėti metodą iš super klasės.

Toliau galime tęsti polimorfizmą C.

Jei, pavyzdžiui, norėjome pakeisti length metodo elgseną, kad dėl kokios nors priežasties grąžintume 0 visą laiką ImmutableString klasėje, viskas, ką reikia padaryti, yra:

  • Pridėkite funkciją, kuri bus svarbiausias metodas.
  • Eikite į konstruktorių ir nustatykite žymiklį į length nepaisymo metodą.

ImmutableString svarbiausio length metodą į ImmutableString galima padaryti pridedant lengthOverrideMethod :

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

Tada funkcijų žymeklis length metodui konstruktoriuje yra prijungtas prie lengthOverrideMethod :

 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->set(self->base, (char*)value); return self; } 

Dabar vietoj to, kad length metodas „ ImmutableString klasėje būtų ImmutableString kaip String klasė, dabar length metodas bus susijęs su elgesiu, apibrėžtu funkcijoje „ lengthOverrideMethod .

Turiu pridėti atsakomybės atsisakymą, kad aš vis dar išmokau rašyti naudojant objekto C programavimo stilių, todėl tikriausiai yra keletas punktų, kurių aš labai nepaaiškinau, arba tiesiog negaliu pasakyti, kiek geriau PLO įgyvendinimas C. Tačiau mano tikslas buvo pabandyti iliustruoti vieną iš daugelio funkcijų rodiklių naudojimo.

Išsamesnės informacijos apie tai, kaip atlikti C objektų orientavimą į programavimą, rasite šiais klausimais:

268
08 мая '09 в 19:32 2009-05-08 19:32 Atsakymą pateikė coobird May 08 '09, 19:32 2009-05-08 19:32

„Startup Guide“ vadovas: kaip piktnaudžiauti GCC funkcijų rodikliais x86 mašinose, rankiniu būdu surenkant kodą:

  • Grąžina dabartinę vertę EAX registre

     int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))(); 
  • Įrašymo apsikeitimo funkcija

     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")(> 
  • Parašykite „for-loop“ skaitiklį 1000, kiekvieną kartą skambinant funkcijai

     ((int(*)())"\x66\x31\xc0\x8b\x5c\x24\x04\x66\x40\x50\xff\xd3\x58\x66\x3d\xe8\x03\x75\xf4\xc3")( // calls function with 1->1000 
  • Jūs netgi galite parašyti rekursinę funkciją, kuri yra 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); 
189
09 апр. Atsakymą pateikė Lee balandžio 9 d. 2011-04-09 03:51 '11 at 3:51 2011-04-09 03:51

Vienas iš mano mėgstamiausių funkcijų rodyklių naudojimo būdų yra pigus ir paprastas iteratorius -

 #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)( } 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); } 
96
08 мая '09 в 19:26 2009-05-08 19:26 atsakė Nick Van Brunt gegužės 08, 09, 19:26 2009-05-08 19:26

Funkcijų rodikliai tampa lengviau deklaruojami, kai turite pagrindines deklaracijas:

  • id: ID : id
  • Rodyklė: *D : D rodyklė į
  • Funkcija: D(<parameters>) : D funkcija, kuri atsiima < parametrus >

Iki šiol D yra kitas deklaratorius, pastatytas pagal tas pačias taisykles. Galų gale kažkur jis baigiasi ID (žr. Šį pavyzdį), kuris yra deklaruoto objekto pavadinimas. Pabandykite sukurti funkciją, paspaudę rodyklę į funkciją, kuri nieko nedaro ir grąžina int, ir grąžina žymeklį į funkciją, paima char ir grąžina int. Naudojant tipo defektus, tai atrodo taip:

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

Kaip matote, tai gana paprasta sukurti su typedefs. Be tipedefs, tai nėra sunku nei taikant pirmiau nurodytas deklaratoriaus taisykles, taikomas nuosekliai. Kaip matote, praleidau žymeklio nurodytą dalį ir funkciją grąžintą dalyką. Tai, kas rodoma pačiame kairiajame deklaracijos kampe, nėra naudinga: jis buvo pridėtas pabaigoje, jei deklaratorius jau buvo sukurtas. Leiskite man tai padaryti. Sukurkite ją nuosekliai, pirmiausia išraiškingai - rodydami struktūrą su [ ir ] :

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

Kaip matote, galite išsamiai aprašyti tipą pridėdami deklaratorius po vieną. Statybą galima atlikti dviem būdais. Vienas - iš apačios į viršų, pradedant nuo teisingiausių (lapų) ir atveria kelią į identifikatorių. Kitas būdas yra nuo viršaus iki apačios, pradedant nuo identifikatoriaus, dirbant iki lapų. Aš parodysiu abu kelius.

Iš apačios į viršų

Statyba prasideda tuo, kas yra dešinėje: elementas grąžinamas, o tai yra funkcija, kuri priima char. Jei norite atskirti deklarantus, aš juos užsirašysiu:

 D1(char); 

Įdėkite char parametrą tiesiai, nes jis yra nereikšmingas. Rodyklės pridėjimas prie deklaratoriaus pakeičiant D1 su *D2 . Atkreipkite dėmesį, kad mes turime pridėti skliaustelius aplink *D2 . Tai galite rasti *-operator ir funkcinio skambučių operatoriaus () . Be mūsų skliaustelių kompiliatorius jį perskaitytų kaip *(D2(char p)) . Tačiau tai, žinoma, nebus paprastas D1 keitimas *D2 . Skliaustai visada leidžiami aplink deklaratorius. Todėl jūs nieko nedarote neteisingai, jei iš tikrųjų pridedate per daug.

 (*D2)(char); 

Užpildytas grąžinimo tipas! Dabar pakeiskite D2 funkciją, kuri grąžina <parameters> , kuri yra D3(<parameters>) , kurią mes dabar esame.

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

Pastaba: nereikia skliaustelių, nes norime, kad D3 būtų deklaratoriaus funkcija, o ne rodyklės deklaratorius. Puikiai, tik kiti yra parametrai. Parametras vykdomas lygiai taip pat, kaip ir sugrįžimo tipą, tik su „ char pakeitimu void . Todėl aš jį nukopijuosiu:

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

Aš pakeičiau D2 su ID1 , nes baigėsi šis parametras (tai jau yra funkcijų rodyklė - nėra kito deklaratoriaus). ID1 bus parametro pavadinimas. Dabar, sakiau aukščiau, pabaigoje pridedamas tipas, kurį keičia visi šie deklaratoriai - tas, kuris rodomas pačiame kairiajame kiekvieno skelbimo kampe. Funkcijoms tai tampa grąžinimo tipu. Rodyklės nurodo tipą ir tt Įdomu, kai rašomas tipas, jis atrodys atvirkštine tvarka, dešinėje :) Bet kuriuo atveju, jį pakeisdami, gausime pilną deklaraciją. Žinoma, abu laikai.

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

Šiame pavyzdyje aš pavadinau ID ID0 .

Į viršų žemyn

Jis prasideda kairiajame kairėje esančiame identifikatoriuje tipo aprašas, įpakavęs šį deklaratorių, kai mes einame per dešinę. Pradėkite nuo funkcijos, kuri priima < parametrus > , grįžimą

 ID0(<parameters>) 

Toliau aprašyme (po „grąžinimo“) buvo rodyklė. Leiskite jį įjungti:

 *ID0(<parameters>) 

Tada buvo funkcinis, kuris grąžina parametrus < > . Parametras yra paprastas char'as, todėl mes vėl jį įdėjome, nes tai tikrai nereikšminga.

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

Atkreipkite dėmesį į skliaustelius, kuriuos pridėjome, nes mes vėl norime, kad * susieti pirmiausia ir tada (char) . Priešingu atveju bus perskaityta funkcija, gaunanti < parametrų > grąžinimo funkciją .... Noes, funkcijų grąžinimo funkcijos netgi neleidžiamos.

Dabar reikia tiesiog nustatyti parametrus < > . Parodysiu trumpą išvesties variantą, nes manau, kad jau turite idėją, kaip tai padaryti.

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

Tiesiog padėkite int prieš deklaratorius, kaip mes darėme su kylančiuoju ir baigėme

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

Geras dalykas

Ar tai geriau aukštyn ar žemyn? Aš įpratau prie apačios į viršų, tačiau kai kurie žmonės gali būti patogesni žemyn. Manau, tai yra skonio klausimas. Beje, jei taikysite visus pareiškimus šioje deklaracijoje, gausite int:

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

Tai yra gera C deklaracijų deklaracija: deklaracijoje teigiama, kad jei šie operatoriai yra naudojami išraiškoje, naudojant identifikatorių, tai suteikia tipą kairėje. Jis atrodo kaip masyvai.

Tikimės, kad jums patiko ši maža pamoka! Dabar galime kreiptis į tai, kai žmonės galvoja apie keistą funkcijų formulių sintaksę. Aš stengiausi kuo mažiau įdėti C vidų. Nedvejodami redaguokite / taisykite jame esančius dalykus.

23
09 мая '09 в 5:05 2009-05-09 05:05 atsakė Johannes Schaub - litb gegužės 09, 09, 05:05 2009-05-09 05:05

Kiti naudingi funkcijų rodikliai:
Perjungti neskausmingai skirtingas versijas

Jie yra labai patogūs naudoti, kai reikia skirtingų funkcijų skirtingais laikais ar skirtingais plėtros etapais. Pvz., Sukuriu taikomąją kompiuterį su konsoliu, tačiau galutinė programinės įrangos versija bus patalpinta „Avnet ZedBoard“ (kuriame yra ekranai ir konsolės, tačiau jų nereikia / nereikia galutiniam leidimui). Taigi kūrimo metu printf kad peržiūrėtumėte būsenos ir klaidų pranešimus, bet kai baigsiu, nenoriu nieko spausdinti. Štai ką aš padariau:

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_  #define __VERSION_H_  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 aš apibrėžiau 2 version.h esančių funkcijų prototipus

version.c

 #include "version.h"   void board_init() { // Assign the print function to the correct function pointer #ifdef DEBUG_VERSION zprintf =  #else // Defined below this function zprintf =  #endif }   void noprintf(const char* c, ...) { return; } 

Atkreipkite dėmesį, kaip funkcijų žymeklis yra prototipas version.h as

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

Kai jis nurodomas paraiškoje, jis pradės vykdyti, nesvarbu, kur jis yra, kuris dar nėra apibrėžtas.

board_init() funkciją board_init() , kur zprintf yra priskirtas unikalus funkcija (funkcijų parašas, kurio atitikmenys), priklausomai nuo versijos, apibrėžtos version.h

zprintf = > zprintf skambina printf derinimo tikslais.

arba

zprintf = > zprintf tiesiog grįžta ir neveiks nereikalingo kodo.

Kodas bus rodomas taip:

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; } 

Pirmiau nurodytas kodas bus naudojamas „ printf jei bus naudojamas derinimo režimu, arba nieko nedarys, jei išjungimo režime. Tai daug lengviau nei eiti per visą projektą ir komentuoti ar ištrinti kodą. Viskas, ką man reikia padaryti, yra pakeisti versiją version.h , o kodas daro likusią!

21
10 июня '13 в 22:56 2013-06-10 22:56 atsakymą pateikė Zackas Šefildas birželio 10 d., 10:56 2013-06-10 22:56

Funkcijų žymeklį paprastai apibrėžia tipedef ir jis naudojamas kaip parametro vertė ir grąžinimo vertė,

Pirmiau minėti atsakymai jau buvo paaiškinti daug, aš tik pateiksiu jums pilną pavyzdį:

 #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  } // test - use function pointer as variable, void test_pointer_as_variable() { // create a pointer to function, two_num_operation sum_p =  // 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,  } // 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; } 
13
10 нояб. Eric Wang atsakymas lapkričio 10 d 2014-11-10 11:50 '14 at 11:50 2014-11-10 11:50

Vienas iš svarbiausių funkcijų rodiklių naudojimo C yra funkcijų skambutis, pasirinktas vykdymo metu. Pvz., „C runtime“ bibliotekoje yra dvi procedūros: qsort ir bsearch, kurios paima rodyklę į funkciją, pavadintą lyginti du rūšiuojamus elementus; tai leidžia surūšiuoti ar ieškoti, nepriklausomai, pagal bet kokius kriterijus, kuriuos norite naudoti.

Labai paprastas pavyzdys: jei yra viena funkcija, vadinama spausdinimu (int x, int y), kuri, savo ruožtu, gali pareikalauti skambinti funkcijai add () arba sub (), kurios turi panašius tipus, tada ką darysime, pridėkite vieną argumentą į žymiklį spausdinimo () funkcijai, kaip parodyta žemiau: -

 int add() { return (100+10); } int sub() { return (100-10); } void print(int x, int y, int (*func)()) { printf("value is : %d", (x+y+(*func)())); } int main() { int x=100, y=200; print(x,y,add); print(x,y,sub); return 0; } 
8
17 нояб. Atsakymą pateikė Vamsi lapkričio 17 d. 2014-11-17 21:24 '14, 21:24, 2014-11-17 21:24

Nuo nulio funkcija turi tam tikrą atminties adresą iš vietos, kurioje jie pradeda vykdyti. Susirinkimo kalba jie vadinami (vadinami „funkcijos atminties adresu“). Dabar grįžkite į funkciją C. Jei funkcija turi atminties adresą, tada galite su jais manipuliuoti C. C rodyklėmis. Pagal taisykles, C

1. Pirmiausia turite deklaruoti funkcijų rodyklę. 2. Nurodykite norimos funkcijos adresą

**** Pastaba-> funkcijos turi būti to paties tipo ****

Ši paprasta programa iliustruos kiekvieną dalyką.

 #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"); } 

2019

3
26 сент. Atsakyti Mohit Dabas 26 sept. 2014-09-26 11:09 '14 at 11:09 2014-09-26 11:09

Funkcijų rodyklė yra kintamasis, kuriame yra funkcijos adresas. Kadangi tai rodyklės kintamasis, nors ir su tam tikromis ribotomis savybėmis, galite naudoti ją kaip ir bet kurį kitą rodyklės kintamąjį duomenų struktūroje.