C funksiyası göstəriciləri C-də necə işləyir?

Son vaxtlarda C-də funksiya göstəriciləri ilə bir sıra təcrübə keçirdim.

Belə ki, öz suallarınıza cavab vermək ənənəsini davam etdirirəm, bu mövzuda tez daldırma ehtiyacı olanlar üçün təməllərin kiçik bir xülasəsini verməyə qərar verdim.

1018
08 мая '09 в 18:49 2009-05-08 18:49 Yuval Adəm tərəfindən mayın 08-də saat 09: 00-da 18:49, 2009-05-08 18:49
@ 12 cavab

C nöqtəsində fəaliyyət göstəriciləri

Əsas işlə başlasaq, bunu göstərəcəyik:

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

Birincisi, pointer 2 int alır və bir int qaytarır funksiyası müəyyən edilsin:

 int (*functionPtr)(int,int); 

İndi işimizi təhlükəsiz şəkildə göstərə bilərik:

 functionPtr = > 

İndi bir funksiya göstəricisi olduğumuzdan istifadə edin:

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

Başqa bir işə bir göstərici keçmək əsasən eynidır:

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

Biz də funksiyalar göstəricilərini geri dəyərlərdən istifadə edə bilərik (davam etdirməyə çalışın, səhv olur):

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

Lakin typedef istifadə etmək çox xoşdur:

 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 Cavab Yuval Adəm tərəfindən 08 may 09: 09-da 18:49, 2009-05-08 18:49

C-də fəaliyyət göstəriciləri C-də obyekt yönümlü proqramlaşdırma aparmaq üçün istifadə edilə bilər.

Məsələn, aşağıdakı xətlər C yazılır:

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

Bəli, ->new operatorun olmaması ölü zərbədir, amma bu, String sinifinin mətni "hello" kimi təyin etdiyimizə aiddir.

Funksiya göstəricilərindən istifadə edərək , C.

Bu necə əldə edilir?

String sinfi həqiqətən metodları simulyasiya etmək üçün bir yol kimi çıxış edən funksiya göstəriciləri bir dəstə ilə bir struct . Aşağıdakı String sinifinin qismən bir bəyanatıdır:

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

Gördüyünüz kimi, String sinifinin üsulları bəyan edilmiş funksiyanın funksiyasına aiddir. Bir String örneğini hazırlayarkən, yeni String funksiyası onların funksiyalarına göstəriciləri qurmaq üçün newString :

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

Məsələn, get metodu ilə getString funksiyası aşağıdakı kimi müəyyən edilir:

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

Görülə biləcək bir şey, bir obyektin nümunəsi və bir obyektin əslində bir hissəsi olan metodların heç bir konsepsiyası olmadığından hər bir çağırış üçün obyektin özü qəbul edilməlidir. (Və internal kodlar əvvəlki siyahılardan çıxarılmış gizli bir struct - bu məlumatları gizlətmək üçün bir vasitədir, lakin bu funksiya göstəricilərinə aid deyil.)

Beləliklə, s1->set("hello"); yerinə s1->set("hello"); , s1->set(s1, "hello") üzərindəki hərəkətləri yerinə yetirmək üçün obyektə getmək lazımdır.

Özünə bir keçid verilməsi lazım olan bu kiçik izahatla, biz C-miras olan növbəti hissəyə keçirik.

ImmutableString ki, subclass String , ImmutableString . Bir simli dəyişməz qalmaq üçün set metod əlçatmaz olacaq və hələ də əldə etmək və length əldə etmək get char* qəbul edən 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

Prinsipcə, bütün alt siniflər üçün, mövcud üsullar yenə funksiya göstəriciləri kimi xidmət edir. Bu dəfə set metod üçün heç bir bəyanat yoxdur, belə ki, ImmutableString ilə çağırıla bilməz.

ImmutableString tətbiqinə gəldikdə, yalnız müvafiq kod constructor funksiyası 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 getlength metodlarını işarə edən funksiya ImmutableString həqiqətən, String.getString.length metodlarına istinad edir və daxili saxlanan String olan dəyişən base keçir.

Bir funksiya göstəricisi istifadə edərək, bir superclassdan bir üsul devrala bilər.

Daha sonra C polimorfizmini davam etdirə bilərik .

Məsələn, biz length üsulun davranışını hər hansı bir səbəblə ImmutableString sinifində hər dəfə 0 ə qayıtmaq ImmutableString , bunların hamısı yerinə yetirilməlidir:

  • Üstünlük length metodu kimi xidmət edəcək bir funksiya əlavə edin.
  • Konstruktorun üzərinə gedin və göstəriciyi length dayandırma metoduna qoyun.

ImmutableString length metodunu ImmutableString əlavə etmək lengthOverrideMethod əlavə etməklə edilə bilər:

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

Daha sonra konstruktordakı length metodu üçün funksiya göstəricisi length bağlıdır:

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

İndi, String sinifi olaraq ImmutableString sinifində length metodu üçün eyni davranışa malik olmağın əvəzinə, length metodu funksiya length müəyyən edilmiş davranışı istinad edir.

Mən hələ obyektə yönəldilən C proqramlaşdırma tərzindən istifadə edərək necə yazacağımı öyrənmək üçün bir imtina etməliyəm, buna görə də çox yaxşı şərh etməmiş bir neçə nöqtə var və ya nə qədər yaxşı deyə bilmərəm C. amma FOTO tətbiqləri mənim məqsədi funksioner göstəricilərindən bir çoxunu istifadə etməyə çalışmaq idi.

Object-oriented C proqramlaşdırmağı necə yerinə yetirmək haqqında daha ətraflı məlumat üçün aşağıdakı suallara baxın:

268
08 мая '09 в 19:32 2009-05-08 19:32 Cavab coobird tərəfindən verilir May 08 '09 19:32 2009-05-08 19:32

Başlanğıc Kılavuzu: Kodunuzu əl ilə tərtib edərək x86 maşınları üzərində GCC funksiyası göstəricilərindən necə istifadə etmək olar?

  • EAX reyestrindəki cari dəyəri qaytarır

     int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))(); 
  • Qeyd dəyişdirmə funksiyası

     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")(> 
  • Hər dəfə bir funksiyaya zəng edən for-loop sayğac 1000 yazın

     ((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 
  • Hətta 100 nəfərlik bir recursive funksiyası yaza bilərsiniz

     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 апр. Cavab 09 aprel tarixində Lee tərəfindən verilir. 2011-04-09 03:51 '11 at 3:51 2011-04-09 03:51

İşlev göstəriciləri üçün mənim ən sevdiyim istifadələrdən biri ucuz və sadə yineleyicilərdir -

 #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 Nick Van Brunt'a cavab 08.05.2009 19:26 2009-05-08 19:26

Əsas bəyannamələriniz olduğunda fəaliyyət göstəriciləri bəyan etmək daha asan olur:

  • id: ID : id
  • Göstərici: *D : D göstəricisi
  • D(<parameters>) funksiyası: D(<parameters>) : < parametrlər > geri dönən D funksiyası

Bu günə qədər D eyni qaydalarla qurulan başqa bir deklaratordur. Nəticədə, bir yerdə elan edilmiş obyektin adı olan bir ID ilə başa çatır (aşağıdakı nümunəyə baxın). Bir funksiyaya işarə etmədən bir funksiyanı qurmaq üçün cəhd edin və bir int qaytarır və funksiyaya bir göstəriciyi qaytarır, bir xətt alır və int qaytarır. Tip-defs ilə aşağıdakı kimi görünür:

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

Gördüyünüz kimi typedefs ilə yaratmaq olduqca asandır. Typedefs olmadan, ya da deklaratorun yuxarıda göstərilən qaydalarından istifadə edərək daim tətbiq edilmir. Gördüyünüz kimi göstərici və funksiyanı geri qaytardığım işarə göstərilən hissəni buraxdım. Bəyannamənin sol küncündə görünən heç bir maraq yoxdur: əgər deklarator artıq yaradılmışdırsa, sonda əlavə edilmişdir. Mənə bunu edək. Ardıcıl olaraq, ilk sözcük yaradın - strukturu [] göstərərək göstərin:

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

Gördüyünüz kimi, bəyan edənləri tək-tək əlavə edərək bu tipi tam təsvir edə bilərsiniz. Tikinti iki yolla edilə bilər. Bir - ən doğru (yarpaq) ilə başlayaraq, aşağıdan yuxarı və identifikatora yol açır. Başqa bir şəkildə, üstdən aşağıya, identifikatordan başlayaraq, yarpaqlara qədər işləyir. İki yol göstərəcəyəm.

Aşağı yuxarı

Tikinti hüququ olan şeylə başlanır: maddə geri qaytarılır, bu da char qəbul edən funksiyadır. Bildirişləri fərqləndirmək üçün onları sayıracağam:

 D1(char); 

Çarpma parametrini birbaşa əlavə edin, çünki bu əhəmiyyətsizdir. D1 əvəzini *D2 ilə əvəz edən deklaratora bir göstərici əlavə edin. *D2 ətrafındakı parantezləri əlavə etməməyi unutmayın. Bunu *-operator və funksional zəng operatorunun prioritetinə baxaraq tapa bilərsiniz () . Bizim parantezlər olmadan, derleyici onu *(D2(char p)) kimi oxuyurdu. Amma bu, əlbəttə ki, D1 ilə *D2 nin sadə bir əvəzi olmayacaq. Parantezlər həmişə dekleratorlar ətrafında icazə verilir. Buna görə də, siz çox şey əlavə etdiyiniz təqdirdə səhv bir şey etməzsiniz.

 (*D2)(char); 

Geri qaytarma növü tamamlandı! İndi D2 indi olan D3(<parameters>) olan <parameters> qaytaran funksiyanın deklarator funksiyası ilə əvəz <parameters> .

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

Qeyd: D3 bir göstərici funksiyası olmadığı üçün bir parantez tələb edilməməlidir. Böyük, yalnız qalanı parametrlərdir. Parametr, qaytarma növünü etdiyimiz kimi tam şəkildə yerinə yetirilir, yalnız char əvəzsiz void . Buna görə də onu kopyalayacağam:

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

D2 ID1 ilə əvəz ID1 , çünki biz bu parametr ilə sona ID1 (artıq bir funksiya göstəricisidir - başqa bir ID1 ehtiyac yoxdur). ID1 parametrin adı olacaq. İndi yuxarıda dedim ki, sonunda bütün bu deklarantların dəyişdiyi növ - hər bir reklamın sol küncündə görünən bir növ əlavə edilir. Funksiyalar üçün bu, bir dönüş növü olur. Göstəricilər tipini və s. Bir növü yazıldığında maraqlıdır, tərs düzənliyə, sağ tərəfə baxacaq :) Hər halda, onu əvəzləyirik, tam bir bəyannamə alırıq. Hər iki dəfə də int , əlbəttə.

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

Bu nümunədə ID ID0 funksiyasını ID0 .

Üst aşağı

Bu, sağ tərəfdən keçdiyimizdə bu deklarantı bağlayaraq, tip təsvirində soldan birində bir identifikator ilə başlayır. < Parametrləri qəbul edən bir funksiyadan başlayın, geri qayıdır

 ID0(<parameters>) 

Təsvirdə ("geri döndükdən sonra") aşağıdakılar göstərici idi. Onu buraxın:

 *ID0(<parameters>) 

Sonra aşağıdakı parametrləri < > qaytarır funksional idi. Parametr basit bir xarakterdir, ona görə də biz onu yenidən yerləşdirdik, çünki bu, həqiqətən, çox əhəmiyyətsizdir.

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

Əlavə etdiyimiz parantezlərə diqqət yetirək, çünki biz yenidən istədiklərimizdən əvvəl * və sonra (char) . Əks təqdirdə, < parametrlər > funksiyasını oxumaq funksiyasını oxuyacaqdır .... Noes, funksiyaların qaytarma funksiyaları belə icazə verilmir.

İndi yalnız parametrləri < > təyin etmək lazımdır. Tərtibin qısa bir versiyasını təqdim edəcəyəm, çünki düşünürəm ki, bunun necə bir fikri var.

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

Bəyannaməçilər qarşısında bir int yerləşdirdilər, biz böyüdükcə etdiyimiz kimi, bitirdik

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

Yaxşı şey

Daha yuxarıya və ya aşağıya doğru mu? Mən aşağıya qədər istifadə edirəm, amma bəziləri aşağıya doğru daha rahat ola bilər. Düşünürəm ki, bu, bir daddır. Yeri gəlmişkən, bu bəyannamədə bütün ifadələri tətbiq edərsən, bir int alırsınız:

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

Bu, C-də bəyannamələrin yaxşı mülkiyyətidir: bəyanatda bildirilir ki, əgər bu operatorlar bir identifikatordan istifadə edərək ifadədə istifadə edərsə, o zaman sola növ verir. Bu dizilərə bənzəyir.

Bu kiçik təlimçidən xoşunuza gəlir! İnsanlar funksiya formullarının qəribə sintaksisindən merak edəndə bu sözlərə müraciət edə bilərik. Cüncünü içəri yerləşdirmək üçün mümkün qədər az çalışdım. Bunun içində olanları düzəltmək / düzəltməkdən çəkinməyin.

23
09 мая '09 в 5:05 2009-05-09 05:05 Johannes Schaub'a cavab verdi - May 09, '09, 5:05 'da 2009-05-09 05:05

İşaretçiləri üçün digər yaxşı istifadə:
Versiyasını ağrısız keçir

Müxtəlif vaxtlarda və ya inkişafın müxtəlif mərhələlərində fərqli funksiyalara ehtiyac olduqda istifadə etmək çox rahatdır. Məsələn, bir konsol ilə ana kompüterdə bir proqram inkişaf edirəm, amma proqramın son versiyası Avnet ZedBoard (ekran və konsollar üçün portlara malikdir, lakin onlar lazım deyil / Final Release üçün lazım deyil) yerləşdiriləcək. Beləliklə, inkişaf zamanı mən printf və səhv mesajlarını görmək üçün printf istifadə edəcəyəm, ancaq işim bitdiyində bir şey yazmaq istəmirəm. İşdə budur:

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 -də təqdim olunan 2 funksiyanın prototiplərini müəyyənləşdirirəm

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

Function version.h necə prototipləşdirildiyinə diqqət yetirin

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

Bir müraciətdə müraciət edildikdə, hələ müəyyən edilməmiş olduğu yerdən asılı olmayaraq, icraya başlayacaqdır.

board_init() də, board_init() versiyasında müəyyən edilmiş versiyaya əsasən unikal funksiyanı (funksiyanı imzası olan) təyin etdiyi board_init() funksiyasını qeyd edin.

zprintf = > zprintf, hata ayıklama məqsədləri üçün printf çağırır.

və ya

zprintf = > zprintf sadəcə qayıdır və lazımsız kodu çalıştırmaz.

Kodun çalıştırılması bu kimi görünür:

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

Yuxarıda göstərilən kod printf -i diskussiya rejimində istifadə edəcək və ya azad rejimində heç bir şey etməyəcək. Bütün layihədən keçmək və kodu şərh etmək və ya silməkdən daha asandır. version.h gereken version.h versiyasını dəyişdirməkdir və kodun qalan hissəsi yoxdur!

21
10 июня '13 в 22:56 2013-06-10 22:56 Cavab Zack Sheffield tərəfindən 10 İyun 2013 tarixində 10:56 2013-06-10 22:56

Bir funksiya göstəricisi adətən typedef ilə müəyyən edilir və parametrenin dəyəri və qaytarılma dəyəri kimi istifadə olunur,

Yuxarıda göstərilən cavablar çoxdan izah edilmişdir, mən sizə tam bir nümunə verəcəyəm:

 #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 tərəfindən Nov 10 verdiyi cavab 2014-11-10 11:50 '14 da 11:50 2014-11-10 11:50

C funksiyası göstəriciləri üçün istifadə edilən böyüklərdən biri, iş vaxtında seçilən funksiya çağırışıdır. Məsələn, C runtime kitabxanasında iki prosedur vardır: qsort və bsearch, iki növlü elementləri müqayisə etmək üçün bir funksiyaya işarə edir; bu istifadə etmək istədiyiniz hər hansı meyarlara əsasən, sırasıyla, sıralamaq və ya axtarış etməyə imkan verir.

Çox sadə bir nümunə: print (int x, int y) adlı bir funksiya varsa, öz növbəsində oxşar tiplərə malik add () və ya sub () funksiyasını çağırmağı tələb edə bilər, onda nə edəcəyik? print () funksiyasına göstəriciyə bir arqument əlavə edin: -

 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 нояб. Cavab 17 noyabr Vamsi tərəfindən verilir . 2014-11-17 21:24 '14 at 21:24 2014-11-17 21:24

Sıfırdan başlayaraq, funksiyanın icrasına başlayan yerdən bir neçə yaddaş ünvanı var. Məclis dilində onlar ("funksiya yaddaş ünvanı" adlanır) deyilir. Artıq C funksiyasına geri qayıdın. Funksiyanın bir yaddaş ünvanı varsa, onda siz C ilə göstəriciləri manipulyasiya edə bilərsiniz. Buna görə qaydalara görə C

1. Əvvəlcə bir funksiya göstərici elan etməlisiniz. 2. İstədiyiniz funksiyanın ünvanını daxil edin

**** Qeyd-> funksiyalar eyni tipdə olmalıdır ****

Bu sadə proqram hər şeyi göstərir.

 #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 сент. Mohit Dabas 26 sentyabrda cavab verin . 2014-09-26 11:09 '14 at 11:09 2014-09-26 11:09

Funksiyanın göstəricisi funksiyanın ünvanını ehtiva edən bir dəyişəndir. Bəzi məhdud xüsusiyyətlərlə olsa da bu göstərici dəyişən olduğundan, məlumat strukturlarında hər hansı digər göstərici dəyişən kimi istifadə edə bilərsiniz.

Düşündüyüm yeganə istisna bir dəyərdən başqa bir şeyin göstəricisi olaraq funksiya istinadına istinad edir. Выполнение арифметики указателя путем увеличения или уменьшения указателя функции или добавления/вычитания смещения на указатель функции не является какой-либо функцией, поскольку указатель функции указывает только на одну вещь, точку входа функции.

Размер переменной указателя функции, количество байтов, занимаемых переменной, может варьироваться в зависимости от базовой архитектуры, например x32 или x64 или любого другого.

Объявление переменной указателя функции должно указывать ту же информацию, что и объявление функции, чтобы компилятор C выполнял те проверки, которые обычно выполняются. Если вы не укажете список параметров в объявлении/определении указателя функции, компилятор C не сможет проверить использование параметров. Бывают случаи, когда эта нехватка проверки может быть полезной, но просто помните, что защитная сетка была удалена.

Bəzi nümunələr:

 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 - это указатель на функцию, которому присваивается адрес функции, которая принимает int и char * и возвращает 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, ...); 

Или вы не можете указать список параметров вообще. Это может быть полезно, но это исключает возможность компилятора C выполнить проверки предоставленного списка аргументов.

 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-стиль при помощи указателей функций. Однако имейте в виду, что компилятор 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 аргументом, который не означает никаких аргументов, а просто неопределенными), что-то вроде следующего, хотя вы можете увидеть предупреждения от компилятора C. Это также работает для параметра указателя функции для функции:

 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 стиля C Использование глобальной struct с указателями функций

Вы можете использовать ключевое слово static чтобы указать функцию, имя которой является областью файлов, а затем назначить ее глобальной переменной как способ предоставления чего-то, подобного функциональности namespace C++.

В файле заголовка определите структуру, которая будет нашим пространством имен вместе с глобальной переменной, которая ее использует.

 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; 

Затем в исходном файле C:

 #include "header.h" // the function names used with these static functions do not need to be the // same as the struct member names. It 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); 

Области применения указателей функций

Компонент библиотеки DLL мог бы сделать что-то похожее на подход namespace стиля C, в котором конкретный интерфейс библиотеки запрашивается из фабричного метода в интерфейсе библиотеки, который поддерживает создание 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.Func1(); // .... FreeLibraryFunc (> 

Такой же подход можно использовать для определения абстрактного аппаратного уровня для кода, который использует определенную модель базового оборудования. Функциональные указатели заполняются специальными функциями оборудования на заводе, чтобы предоставить аппаратную специфическую функциональность, которая реализует функции, указанные в абстрактной аппаратной модели. Это можно использовать для предоставления абстрактного аппаратного уровня, используемого программным обеспечением, которое вызывает заводскую функцию, чтобы получить конкретный интерфейс функциональных функций, затем использует указатели функций, предоставляемые для выполнения действий для базового оборудования, без необходимости знать подробности реализации конкретной цели,

Указатели функций для создания делегатов, обработчиков и обратных вызовов

Вы можете использовать указатели функций как способ делегирования некоторой задачи или функциональности. Классический пример в 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]),  pSummation); 

Другим примером является исходный код GUI, в котором обработчик для определенного события регистрируется путем предоставления указателя функции, который фактически вызывается, когда происходит событие. Структура Microsoft MFC с картами сообщений использует нечто похожее на обработку сообщений Windows, которые доставляются в окно или поток.

Асинхронные функции, требующие обратного вызова, аналогичны обработчику событий. Пользователь асинхронной функции вызывает асинхронную функцию для запуска некоторого действия и предоставляет указатель на функцию, который будет вызывать асинхронная функция после завершения действия. В этом случае событие представляет собой асинхронную функцию, выполняющую свою задачу.

1
ответ дан Richard Chambers 20 авг. '18 в 8:47 2018-08-20 08:47

Так как указатели на функции часто вводят обратные вызовы, вы можете посмотреть тип безопасных обратных вызовов . То же самое относится к точкам входа и т.д. Функций, которые не являются обратными вызовами.

C довольно изменчив и прощает одновременно:)

0
ответ дан Tim Post 09 мая '09 в 16:56 2009-05-09 16:56

указатели функций полезны во многих ситуациях, например:

  • Члены COM-объектов являются указателями на функцию ag: This->lpVtbl->AddRef(This); AddRef - это указатель на функцию.
  • обратный вызов функции, например, пользовательская функция для сравнения две переменные, которые должны быть переданы как обратный вызов специальной функции сортировки.
  • очень полезно для реализации плагина и SDK приложения.
-8
ответ дан milevyo 02 янв. '16 в 16:15 2016-01-02 16:15

Другие вопросы по меткам или Задайте вопрос