C ile bir işlevi parametre olarak nasıl iletirsiniz?


616

Bir veri kümesinde parametre tarafından iletilen bir işlevi gerçekleştiren bir işlev oluşturmak istiyorum. C ile bir işlevi parametre olarak nasıl iletirsiniz?


12
İşlevleri / dizileri değişken olarak kullanıyorsanız, DAİMA kullanın typedef.
Mooing Duck


İşlev adı, işleve bir ponter olmalıdır. C öğrenen çoğu insan er ya da geç qsort'u tam olarak bunu kapsar?
mckenzm

5
@MooingDuck Önerinizi kabul etmiyorum ve öneriniz sonucu desteklemek için gerekçe içermiyor. Ben şahsen asla fonksiyon işaretçileri üzerinde typedef kullanmayı tercih ve kodun daha net ve okunmasını kolaylaştırır düşünüyorum.
andrewrk

2
@andrewrk: Sen tercih void funcA(void(*funcB)(int))ve void (*funcA())()hiç typedef void funcB(); void funcA(funcB)ve funcB funcA()? Ben tersi görmüyorum.
Mooing Duck

Yanıtlar:


747

deklarasyon

Bir işlev parametresini alan bir işlev için bir prototip aşağıdaki gibidir:

void func ( void (*f)(int) );

Bu, parametrenin f, voiddönüş türüne sahip ve tek bir intparametre alan bir işleve bir işaretçi olacağını belirtir . Aşağıdaki işlev ( print), funcuygun tür olduğu için parametre olarak iletilebilen bir işleve örnektir :

void print ( int x ) {
  printf("%d\n", x);
}

İşlev Çağrısı

Bir işlev parametresine sahip bir işlev çağrılırken, iletilen değer bir işleve bir işaretçi olmalıdır. Bunun için işlevin adını (parantez olmadan) kullanın:

func(print);

çağırır funckendisine baskı fonksiyonunu geçen.

İşlev Gövdesi

Herhangi bir parametrede olduğu gibi func, parametrenin değerine erişmek için artık işlev gövdesinde parametrenin adını kullanabilir. Diyelim ki func0-4 arası sayılara aktarılan işlevi uygulayacağız. İlk olarak, doğrudan yazdırmayı çağırmak için döngünün nasıl görüneceğini düşünün:

for ( int ctr = 0 ; ctr < 5 ; ctr++ ) {
  print(ctr);
}

Yana funcbireyin parametre beyanı söylüyor fistenen işlev işaretçisi adıdır, biz eğer o ilk hatırlamak fbir gösterici daha sonra *fbir şey olduğunu f(yani işlevine noktaları printbu durumda). Sonuç olarak, yukarıdaki döngüdeki her baskı oluşumunu aşağıdaki ile değiştirin *f:

void func ( void (*f)(int) ) {
  for ( int ctr = 0 ; ctr < 5 ; ctr++ ) {
    (*f)(ctr);
  }
}

Kaynak


40
İlk ve son kod örneklerinizde * zorunlu değildir. Hem fonksiyon parametresi tanımı hem de ffonksiyon çağrısı f* olmadan olduğu gibi alabilir . Yine de, f parametresinin bir işlev işaretçisi olduğunu açıkça ortaya koymak için bunu yapmak iyi bir fikir olabilir. Ancak okunabilirliği oldukça sık acıtıyor.
Gauthier

5
Örnekler için bakınız [c99, 6.9.1§14]. Her ikisi de elbette doğru, sadece alternatiften bahsetmek istedim.
Gauthier

6
Gerçekten mi? En yüksek puanlı yanıt, typedeffor işlev işaretleyicilerinin kullanımına tek bir başvuruda bulunmuyor mu? Üzgünüm, aşağı oy vermek zorunda.
Jonathon Reinhart

3
@JonathonReinhart, 'typedef' yaklaşımının avantajları nelerdir? Bu sürüm çok daha temiz görünüyor ve ekstra ifadeler de yok. burada oldukça başlangıç.
Abhinav Gauniyal

3
@JonathonReinhart iyi tespit edildi; işaretçinin typedefs kodunu gizlediğini ve bu nedenle kullanılmaması gerektiğini açıkça belirtmek gerekir.
MM

129

Bu soru zaten işlev işaretçileri tanımlamak için cevabı var, ancak özellikle onları uygulamanızın etrafından geçirecekseniz çok karışık olabilirler. Bu tatsızlığı önlemek için, işlev işaretçisini daha okunabilir bir şeye yazmanız önerilir. Örneğin.

typedef void (*functiontype)();

Void döndüren ve bağımsız değişken almayan bir işlevi bildirir. Bu türe bir işlev işaretçisi oluşturmak için artık şunları yapabilirsiniz:

void dosomething() { }

functiontype func = &dosomething;
func();

Bir int döndüren ve bir karakter alan bir işlev için

typedef int (*functiontype2)(char);

ve kullanmak için

int dosomethingwithchar(char a) { return 1; }

functiontype2 func2 = &dosomethingwithchar
int result = func2('a');

İşlev işaretleyicilerini güzel okunabilir türlere dönüştürmeye yardımcı olabilecek kütüphaneler vardır. Püskürtme fonksiyonunu kütüphanesi büyük ve iyi çabaya değer!

boost::function<int (char a)> functiontype2;

yukarıdakilerden çok daha güzel.


4
"Bir işlev işaretçisini bir türe dönüştürmek" istiyorsanız, yükseltme kitaplığına ihtiyacınız yoktur. Sadece kullanın typedef; daha basittir ve fazladan kitaplık gerektirmez.
wizzwizz4

72

C ++ 11'den beri , bunu kısa ve öz bir şekilde yapmak için fonksiyonel kütüphaneyi kullanabilirsiniz . Sözdizimi, ör.

std::function<bool (int)>

burada bool, ilk argümanı tür olan tek argüman fonksiyonunun dönüş türü int.

Aşağıda örnek bir program ekledim:

// g++ test.cpp --std=c++11
#include <functional>

double Combiner(double a, double b, std::function<double (double,double)> func){
  return func(a,b);
}

double Add(double a, double b){
  return a+b;
}

double Mult(double a, double b){
  return a*b;
}

int main(){
  Combiner(12,13,Add);
  Combiner(12,13,Mult);
}

Bununla birlikte, bazen bir şablon işlevini kullanmak daha uygundur:

// g++ test.cpp --std=c++11

template<class T>
double Combiner(double a, double b, T func){
  return func(a,b);
}

double Add(double a, double b){
  return a+b;
}

double Mult(double a, double b){
  return a*b;
}

int main(){
  Combiner(12,13,Add);
  Combiner(12,13,Mult);
}

43
Soru C ile ilgili; C ++ burada geçerli değildir.
Süper Kedi

16
C ++ ( stackoverflow.com/questions/6339970/… ) sorusuna burada değinilmektedir, bu yüzden bu cevabın yerinde olduğunu düşünüyorum.
mr_T

8
Belki de C ++ sorusuna burada değinilmemelidir çünkü bu soru C'dir.
Michael Fulton

5
Bu soru ilk olarak "c ++ işlev çağrısı parametre olarak" aradığımda ortaya çıktı, bu nedenle bu cevap burada ne olursa olsun amacına hizmet ediyor.
ufoxDan

25

Geçiş başka bir işlev için parametre olarak bir fonksiyonun adresini , aşağıda gösterildiği gibi

#include <stdio.h>

void print();
void execute(void());

int main()
{
    execute(print); // sends address of print
    return 0;
}

void print()
{
    printf("Hello!");
}

void execute(void f()) // receive address of print
{
    f();
}

Ayrıca fonksiyon işaretçisini kullanarak fonksiyonu parametre olarak geçebiliriz

#include <stdio.h>

void print();
void execute(void (*f)());

int main()
{
    execute(&print); // sends address of print
    return 0;
}

void print()
{
    printf("Hello!");
}

void execute(void (*f)()) // receive address of print
{
    f();
}

1
Tüm kod blokları ile çok güzel bir cevap (her şeyi anlaşılmaz bir karmaşaya dilimlemek yerine). Her iki tekniğin farklılıkları hakkında bilgi verebilir misiniz?
Rafael Eyng

5

Fonksiyonlar göre, işlev işaretçileri olarak "kabul" edilebilir ISO C11 6.7.6.3p8: " '' fonksiyonu geri tipine işaretçi' için ayarlanmalıdır 'fonksiyon tipi dönen' gibi bir parametrenin bir bildirim , 6.3 olarak .2.1. " Örneğin, bu:

void foo(int bar(int, int));

buna eşdeğerdir:

void foo(int (*bar)(int, int));

Hangi belgeden alıntı yapıyorsunuz? Bağlantı var mı?
Rafael Eyng

1
ISO C11 standardından alıntı yapıyorum.
doppelheathen


-2

Bu gerçekten bir işlev değil, ancak yerelleştirilmiş bir kod parçasıdır. Tabii ki kodu sadece sonucu geçmiyor. Daha sonra çalıştırılacak bir olay dağıtıcısına aktarılırsa çalışmaz (sonuç hesaplandığında ve olay gerçekleştiğinde değil). Ama yapmaya çalıştığınız tek şey buysa, kodunuzu tek bir yerde lokalize eder.

#include <stdio.h>

int IncMultInt(int a, int b)
{
    a++;
    return a * b;
}

int main(int argc, char *argv[])

{
    int a = 5;
    int b = 7;

    printf("%d * %d = %d\n", a, b, IncMultInt(a, b));

    b = 9;

    // Create some local code with it's own local variable
    printf("%d * %d = %d\n", a, b,  ( { int _a = a+1; _a * b; } ) );

    return 0;
}

Sadece bir fonksiyon çağırıyorsun. Bunun yerine başka bir işlevi nasıl geçirdiniz IncMultInt?
Tejas Pendse
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.