İşlev işaretçilerinin amacı nedir?


96

İşlev işaretçilerinin faydasını görmekte güçlük çekiyorum. Bazı durumlarda faydalı olabileceğini tahmin ediyorum (sonuçta varlar), ancak bir işlev işaretçisi kullanmanın daha iyi veya kaçınılmaz olduğu bir durum düşünemiyorum.

İşlev işaretçilerinin (C veya C ++ 'da) iyi kullanımına ilişkin bazı örnekler verebilir misiniz?


1
Bu ilgili SO sorusunda işlev işaretçileri hakkında epeyce tartışma bulabilirsiniz .
itsmatt

20
@itsmatt: Pek sayılmaz. "Bir TV nasıl çalışır?" "TV ile ne yapacağım?" sorusundan oldukça farklı bir sorudur.
sbi

6
C ++ 'da bunun yerine muhtemelen bir functor ( en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B ) kullanırsınız.
kennytm

11
C ++ 'nın C'ye "derlendiği" eski karanlık günlerde, sanal yöntemlerin nasıl uygulandığını görebiliyordunuz - evet, işlev işaretçileriyle.
sbk

1
C ++ 'ı yönetilen bir C ++ veya C # ile kullanmak istediğinizde çok önemlidir, yani: delegeler ve geri aramalar
Maher

Yanıtlar:


109

Örneklerin çoğu geri çağırmalara dönüşür : Başka bir işlevin f()adresini ileten bir işlevi çağırırsınız g()ve belirli bir görevi f()çağırırsınız g(). Eğer geçerseniz f()adresini h()yerine, daha sonra f()yine arar h()yerine.

Temel olarak, bu bir işlevi parametrize etmenin bir yoludur : Davranışının bir kısmı sabit kodlanmış f()değil, geri arama işlevine kodlanmıştır . Arayanlar, f()farklı geri arama işlevlerini ileterek farklı davranabilir. Bir klasik, qsort()sıralama kriterini bir karşılaştırma işlevine işaretçi olarak alan C standart kitaplığındandır.

C ++ 'da, bu genellikle işlev nesneleri (aynı zamanda işlevler olarak da adlandırılır) kullanılarak yapılır . Bunlar, işlev çağrısı operatörünü aşırı yükleyen nesnelerdir, böylece onları bir işlevmiş gibi çağırabilirsiniz. Misal:

class functor {
  public:
     void operator()(int i) {std::cout << "the answer is: " << i << '\n';}
};

functor f;
f(42);

Bunun arkasındaki fikir, bir işlev işaretçisinin aksine, bir işlev nesnesinin yalnızca bir algoritma değil, aynı zamanda veri de taşıyabilmesidir:

class functor {
  public:
     functor(const std::string& prompt) : prompt_(prompt) {}
     void operator()(int i) {std::cout << prompt_ << i << '\n';}
  private:
     std::string prompt_;
};

functor f("the answer is: ");
f(42);

Diğer bir avantaj, bazen işlev nesnelerine satır içi çağrıların işlev işaretçileri aracılığıyla yapılan çağrılardan daha kolay olmasıdır. Bu, C ++ 'da sıralamanın bazen C'de sıralamadan daha hızlı olmasının bir nedenidir.


1
+1, başka bir örnek için bu yanıta da bakın: stackoverflow.com/questions/1727824/…
sharptooth

Sanal işlevleri unuttunuz, aslında bunlar aynı zamanda işlev işaretçileridir (derleyicinin ürettiği bir veri yapısıyla birleştiğinde). Dahası, saf C'de, Linux çekirdeğinin VFS katmanında (ve birçok başka yerde) görüldüğü gibi nesne yönelimli kod yazmak için bu yapıları kendiniz oluşturabilirsiniz.
Florian

2
@krynr: Sanal işlevler yalnızca derleyici uygulayıcılarına yönelik işlev işaretçileridir ve ne için iyi olduklarını sormanız gerekiyorsa, muhtemelen (umarım!) bir derleyicinin sanal işlev mekanizmasını uygulamaya ihtiyaç duyma olasılığınız düşüktür.
sbi

@sbi: Elbette haklısın. Ancak, soyutlamanın içinde neler olup bittiğini anlamanın yardımcı olduğunu düşünüyorum. Ayrıca, kendi vtable'ınızı C'de uygulamak ve nesneye yönelik kod yazmak gerçekten iyi bir öğrenme deneyimi sağlar.
Florian

yaşama, evrene ve her şeye yanıtı ve OP tarafından
istenenleri

41

Genelde onları (profesyonel olarak) atlama tablolarında kullanırım (ayrıca bu StackOverflow sorusuna da bakın ).

Sıçrama tabloları, sonlu durum makinelerinde veri odaklı olmalarını sağlamak için yaygın olarak (ancak özel olarak değil) kullanılır . Yuvalanmış anahtar / durum yerine

  switch (state)
     case A:
       switch (event):
         case e1: ....
         case e2: ....
     case B:
       switch (event):
         case e3: ....
         case e1: ....

2 boyutlu bir işlev işaretçisi dizisi oluşturabilir ve yalnızca handleEvent[state][event]


24

Örnekler:

  1. Özel sıralama / aramalar
  2. Farklı modeller (Strateji, Gözlemci gibi)
  3. Geri aramalar

1
Atlama tablosu bunun önemli kullanımlarından biridir.
Ashish

Bazı çalışılmış örnekler olsaydı, bu benim olumlu oyumu alırdı.
Donal Fellows

1
C ++ mevcutsa, strateji ve gözlemci muhtemelen sanal işlevler kullanılarak daha iyi uygulanır. Aksi takdirde +1.
Billy ONeal

İşlev işaretçilerinin akıllıca kullanılmasının gözlemciyi daha kompakt ve hafif hale getirebileceğini düşünüyorum
Andrey

@BillyONeal Yalnızca, Javaismlerin sızdığı GoF tanımına sıkı sıkıya bağlı kalırsanız. Ben tarif edersiniz std::sorts' compparametresi Stratejisi olarak
Caleth

10

İşlev işaretçilerinin kullanışlılığına ilişkin "klasik" örnek qsort(), Hızlı Sıralama uygulayan C kütüphanesi işlevidir. Kullanıcının bulabileceği tüm veri yapıları için evrensel olmak için, sıralanabilir verilere birkaç boşluk işaretçisi ve bu veri yapılarının iki öğesinin nasıl karşılaştırılacağını bilen bir işleve bir işaretçi gerekir. Bu, iş için tercih ettiğimiz işlevi oluşturmamızı sağlar ve hatta çalışma zamanında karşılaştırma işlevini seçmemize izin verir, örneğin artan veya azalan sıralama için.


7

Yukarıdakilerin hepsine katılıyorum, artı .... Çalışma zamanında dinamik olarak bir dll yüklediğinizde, işlevleri çağırmak için işlev işaretçilerine ihtiyacınız olacaktır.


1
Bunu her zaman Windows XP'yi desteklemek için yapıyorum ve hala Windows 7 güzelliklerini kullanıyorum. +1.
Billy ONeal

7

Burada akıntıya karşı çıkacağım.

C'de, işlev işaretçileri, özelleştirmeyi gerçekleştirmenin tek yoludur, çünkü OO yoktur.

C ++ 'da, aynı sonuç için işlev işaretçileri veya işlevcileri (işlev nesneleri) kullanabilirsiniz.

Functor'lar, özellikle nesne yapıları nedeniyle ham fonksiyon işaretçilerine göre bir takım avantajlara sahiptir:

  • Birkaç aşırı yük sunabilirler. operator()
  • Mevcut değişkenlere durum / başvuru olabilirler
  • Yerinde inşa edilebilirler ( lambdave bind)

Ben şahsen işaretçilerin işlev görmesini tercih ederim (ortak kod koduna rağmen), çünkü çoğunlukla işlev işaretçilerinin sözdizimi kolayca tüylü olabilir ( İşlev İşaretçisi Eğitiminden ):

typedef float(*pt2Func)(float, float);
  // defines a symbol pt2Func, pointer to a (float, float) -> float function

typedef int (TMyClass::*pt2Member)(float, char, char);
  // defines a symbol pt2Member, pointer to a (float, char, char) -> int function
  // belonging to the class TMyClass

Fonksiyon işaretçilerinin kullanılamadığı durumlarda kullanılan fonksiyon işaretçilerinin Boost.Spirit'te olduğunu gördüğüm tek zaman. Tek bir şablon parametresi olarak rastgele sayıda parametreyi geçirmek için sözdizimini tamamen kötüye kullandılar.

 typedef SpecialClass<float(float,float)> class_type;

Ancak değişken şablonlar ve lambdalar köşede olduğundan, uzun süredir saf C ++ kodunda işlev işaretçileri kullanacağımızdan emin değilim.


İşlev işaretçilerinizi görmemeniz, onları kullanmadığınız anlamına gelmez. Her seferinde (derleyici onu optimize edemediği sürece) sanal bir işlevi çağırırsınız, boost kullanırsınız bindveya functionişlev işaretçileri kullanırsınız. Akıllı işaretçiler kullandığımız için C ++ 'da işaretçi kullanmadığımızı söylemek gibi. Her neyse, ben kusuyorum.
Florian

3
@krynr: Kibarca katılmıyorum. Önemli olan budur bakın ve tipi kullanmanızı sözdizimi olduğunu. Her şeyin perde arkasında nasıl çalıştığı sizin için önemli olmamalı: soyutlama bununla ilgili.
Matthieu M.

5

C'de klasik kullanım, dördüncü parametrenin sıralama içinde sıralamayı gerçekleştirmek için kullanılacak bir işleve işaret ettiği qsort işlevidir . C ++ 'da, bu tür şeyler için functors (işlevlere benzeyen nesneler) kullanma eğilimi vardır.


2
@KennyTM: Bunun C standart kitaplığındaki diğer tek örneğine işaret ediyordum. Alıntı yaptığınız örnekler, üçüncü taraf kitaplıklarının parçasıdır.
Billy ONeal

5

Son zamanlarda bir soyutlama katmanı oluşturmak için işlev işaretçileri kullandım.

Katıştırılmış sistemlerde çalışan, saf C'de yazılmış bir programım var. Birden çok donanım varyantını destekler. Üzerinde çalıştığım donanıma bağlı olarak, bazı işlevlerin farklı sürümlerini çağırması gerekiyor.

Başlatma zamanında, program hangi donanım üzerinde çalıştığını anlar ve işlev işaretlerini doldurur. Programdaki tüm üst düzey rutinler sadece işaretçiler tarafından referans verilen işlevleri çağırır. Üst düzey rutinlere dokunmadan yeni donanım varyantları için destek ekleyebilirim.

Uygun işlev sürümlerini seçmek için anahtar / durum deyimlerini kullanırdım, ancak program gittikçe daha fazla donanım değişkenini destekleyecek şekilde büyüdükçe bu pratik olmadı. Her yere vaka açıklamaları eklemek zorunda kaldım.

Hangi işlevi kullanacağımı bulmak için ara işlev katmanlarını da denedim, ancak pek yardımcı olmadılar. Yeni bir varyant eklediğimizde yine de vaka açıklamalarını birden çok yerde güncellemem gerekiyordu. İşlev işaretçileriyle, yalnızca başlatma işlevini değiştirmem gerekiyor.


3

Gibi zengin sakladığı işlev bazı adresini başvurmak için Windows fonksiyonları göstericiler için çok normal olan, yukarıda söyledi.

C languageWindows platformunda programlama yaparken , temel olarak bazı DLL dosyalarını birincil belleğe yüklersiniz (kullanarak LoadLibrary) ve DLL'de depolanan işlevleri kullanmak için işlev işaretçileri oluşturmanız ve bu adresleri (kullanarak GetProcAddress) işaret etmeniz gerekir .

Referanslar:


2

Fonksiyon işaretçileri, programlanacak bir arayüz oluşturmak için C'de kullanılabilir. Çalışma zamanında ihtiyaç duyulan belirli işlevselliğe bağlı olarak, işlev işaretçisine farklı bir uygulama atanabilir.


2

Bunları ana kullanımım CALLBACKS oldu: daha sonra aramak için bir işlev hakkındaki bilgileri kaydetmeniz gerektiğinde .

Bomberman yazdığını söyle. Kişi bombayı düşürdükten 5 saniye sonra patlamalıdır ( explode()işlevi çağırın ).

Şimdi bunu yapmanın 2 yolu var. Bunun bir yolu, ana döngüde patlamaya hazır olup olmadıklarını görmek için ekrandaki tüm bombaları "araştırmaktır".

foreach bomb in game 
   if bomb.boomtime()
       bomb.explode()

Başka bir yol da saat sisteminize bir geri arama eklemektir. Bir bomba yerleştirildiğinde, doğru zaman geldiğinde bomb.explode () 'u çağırması için bir geri arama eklersiniz .

// user placed a bomb
Bomb* bomb = new Bomb()
make callback( function=bomb.explode, time=5 seconds ) ;

// IN the main loop:
foreach callback in callbacks
    if callback.timeToRun
         callback.function()

İşte callback.function()olabilir herhangi bir işlev , bir işlev işaretçisi olduğundan,.


Soru [C] ve [C ++] ile etiketlendi, başka herhangi bir dil etiketi ile değil. Bu nedenle, başka bir dilde kod parçacıkları sağlamak biraz konu dışıdır.
cmaster - eski haline getirmek monica

2

Fonksiyon işaretçisinin kullanımı

Fonksiyonu kullanıcı girdisine göre dinamik olarak çağırmak için . Bu durumda dizge ve işlev göstericisinin bir haritasını oluşturarak.

#include<iostream>
#include<map>
using namespace std;
//typedef  map<string, int (*)(int x, int y) > funMap;
#define funMap map<string, int (*)(int, int)>
funMap objFunMap;

int Add(int x, int y)
{
    return x+y;
}
int Sub(int x, int y)
{
        return x-y;
}
int Multi(int x, int y)
{
        return x*y;
}
void initializeFunc()
{
        objFunMap["Add"]=Add;
        objFunMap["Sub"]=Sub;
        objFunMap["Multi"]=Multi;
}
int main()
{
    initializeFunc();

    while(1)
    {
        string func;
        cout<<"Enter your choice( 1. Add 2. Sub 3. Multi) : ";
        int no, a, b;
        cin>>no;

        if(no==1)
            func = "Add";
        else if(no==2)
            func = "Sub";
        else if(no==3)
            func = "Multi";
        else 
            break;

        cout<<"\nEnter 2 no :";
                cin>>a>>b;

        //function is called using function pointer based on user input
        //If user input is 2, and a=10, b=3 then below line will expand as "objFuncMap["Sub"](10, 3)"
        int ret = objFunMap[func](a, b);      
        cout<<ret<<endl;
    }
    return 0;
}

Bu şekilde gerçek şirket kodumuzda işlev işaretçisini kullandık. Bu yöntemi kullanarak 'n' sayıda fonksiyon yazabilir ve çağırabilirsiniz.

ÇIKTI:

    Seçiminizi girin (1. Ekle 2. Alt 3. Çoklu): 1
    2 hayır girin: 2 4
    6
    Seçiminizi girin (1. Ekle 2. Alt 3. Çoklu): 2
    2 hayır girin: 10 3
    7
    Seçiminizi girin (1. Ekle 2. Alt 3. Çoklu): 3
    2 no: 3 girin 6
    18

2

Buradaki diğer iyi cevaplara ek olarak farklı bir bakış açısı:

C'de, (doğrudan) işlevleri değil, yalnızca işlev işaretçileri kullanırsınız.

Demek istediğim, fonksiyonlar yazarsın ama fonksiyonları değiştiremezsin . Kullanabileceğiniz bir işlevin çalışma zamanı temsili yoktur. "Bir işlev" bile diyemezsin. Yazarken:

my_function(my_arg);

aslında söylediğiniz şey " my_functionbelirtilen bağımsız değişkenle işaretçiye bir çağrı gerçekleştirin ". Bir işlev işaretçisi aracılığıyla arama yapıyorsunuz. Bu fonksiyon, bir göstericiyi bozunma aşağıdaki komutlar önceki işlev çağrısına eşdeğerdir aracı:

(&my_function)(my_arg);
(*my_function)(my_arg);
(**my_function)(my_arg);
(&**my_function)(my_arg);
(***my_function)(my_arg);

ve benzeri (teşekkürler @LuVinhPhuc).

Yani, zaten değer olarak işlev işaretçileri kullanıyorsunuz . Açıkçası, bu değerler için değişkenlere sahip olmak istersiniz - ve işte diğer metionların tüm kullanımları burada devreye girer: Çok biçimlilik / özelleştirme (qsort'ta olduğu gibi), geri çağırmalar, atlama tabloları vb.

C ++ 'da işler biraz daha karmaşıktır, çünkü lambdalarımız ve nesnelerimiz operator()ve hatta bir std::functionsınıfımız olduğu için, ancak prensip hala çoğunlukla aynıdır.


2
daha da ilginci, işlevin çağırabilir olarak (&my_function)(my_arg), (*my_function)(my_arg), (**my_function)(my_arg), (&**my_function)(my_arg), (***my_function)(my_arg)... çünkü fonksiyonları işlev işaretçileri eksilmesini
phuclv

1

OO dilleri için, perde arkasında polimorfik çağrılar gerçekleştirmek için (bu, tahmin ettiğim bir noktaya kadar C için de geçerlidir).

Dahası, çalışma zamanında başka bir işleve (foo) farklı davranışlar enjekte etmek için çok kullanışlıdırlar. Bu, işlevi foo üst düzey işlev yapar. Esnekliğinin yanı sıra, bu foo kodunu daha okunabilir kılar çünkü "if-else" şeklindeki ekstra mantığı ondan çıkarmanıza izin verir.

Python'da jeneratörler, kapaklar vb. Gibi diğer birçok yararlı şeyi etkinleştirir


0

1 baytlık işlem kodları olan mikroişlemcileri taklit etmek için kapsamlı olarak işlev işaretçileri kullanıyorum. 256 işlev işaretçisi dizisi bunu uygulamanın doğal yoludur.


0

İşlev göstericisinin bir kullanımı, işlevin çağrıldığı kodu değiştirmek istemeyebileceğimiz yer olabilir (bu nedenle çağrı koşullu olabilir ve farklı koşullar altında farklı türde işlemler yapmamız gerekir). Burada fonksiyon işaretçileri çok kullanışlıdır, çünkü fonksiyonun çağrıldığı yerde kodu değiştirmemize gerek yoktur. Fonksiyonu, uygun argümanlarla fonksiyon göstericisini kullanarak çağırırız. İşlev işaretçisi, koşullu olarak farklı işlevleri işaret edecek şekilde yapılabilir. (Bu, başlatma aşamasında herhangi bir yerde yapılabilir). Dahası, yukarıdaki model, çağrıldığı yerde kodu değiştirecek durumda değilsek çok yararlıdır (bunun bir kütüphane API'sini değiştiremeyeceğimizi varsayalım). API, uygun kullanıcı tanımlı işlevi çağırmak için bir işlev işaretçisi kullanır.


0

Burada biraz kapsamlı bir liste vermeye çalışacağım:

  • Geri aramalar : Kullanıcı tarafından sağlanan kodla bazı (kitaplık) işlevlerini özelleştirin. Birincil örnek qsort(), ancak olayları işlemek için (tıklandığında bir geri aramayı çağıran bir düğme gibi) veya bir iş parçacığı başlatmak için gerekli ( pthread_create()) yararlıdır .

  • Çok biçimlilik : Bir C ++ sınıfındaki vtable, işlev işaretçileri tablosundan başka bir şey değildir. Ve bir C programı, bazı nesneleri için bir vtable sağlamayı da seçebilir:

    struct Base;
    struct Base_vtable {
        void (*destruct)(struct Base* me);
    };
    struct Base {
        struct Base_vtable* vtable;
    };
    
    struct Derived;
    struct Derived_vtable {
        struct Base_vtable;
        void (*frobnicate)(struct Derived* me);
    };
    struct Derived {
        struct Base;
        int bar, baz;
    }
    

    Yapıcısı Deriveddaha sonra belirleyecek vtableen türetilen sınıfın uygulamalarına sahip küresel nesneye üye değişkeni destructve frobnicateve imha için gerekli kod struct Base*basitçe çağırır base->vtable->destruct(base)sınıf türetilmiş bağımsız olan yıkıcı ilgili doğru sürümünü çağırır, baseaslında noktaları .

    İşlev işaretçileri olmadan, polimorfizmin bir anahtar yapı ordusu ile kodlanması gerekirdi.

    switch(me->type) {
        case TYPE_BASE: base_implementation(); break;
        case TYPE_DERIVED1: derived1_implementation(); break;
        case TYPE_DERIVED2: derived2_implementation(); break;
        case TYPE_DERIVED3: derived3_implementation(); break;
    }
    

    Bu oldukça hızlı bir şekilde hantal hale geliyor.

  • Dinamik olarak yüklenen kod : Bir program bir modülü belleğe yüklediğinde ve kodunu çağırmaya çalıştığında, bir işlev işaretçisinden geçmelidir.

Gördüğüm işlev işaretçilerinin tüm kullanımları, doğrudan bu üç geniş sınıftan birine giriyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.