Funktionspekare

En funktionspekare , även kallad en subrutinpekare eller procedurpekare , är en pekare som pekar på en funktion. I motsats till att referera till ett datavärde pekar en funktionspekare på exekverbar kod i minnet. Bortreferensering av funktionspekaren ger den refererade funktionen , som kan anropas och skickas argument precis som i ett vanligt funktionsanrop. Ett sådant anrop är också känt som ett "indirekt" anrop, eftersom funktionen anropas indirekt genom en variabel istället för direkt genom en fast identifierare eller adress.

Funktionspekare kan användas för att förenkla kod genom att tillhandahålla ett enkelt sätt att välja en funktion att köra baserat på körtidsvärden.

Funktionspekare stöds av tredje generationens programmeringsspråk (som PL/I , COBOL , Fortran , dBASE dBL och C ) och objektorienterade programmeringsspråk (som C++ , C# och D ).

Enkla funktionspekare

Den enklaste implementeringen av en funktion (eller subrutin) pekare är som en variabel som innehåller adressen till funktionen i det körbara minnet. Äldre tredje generationens språk som PL/I och COBOL , såväl som mer moderna språk som Pascal och C implementerar i allmänhet funktionspekare på detta sätt.

Exempel i C

Följande C-program illustrerar användningen av två funktionspekare:

  • func1 tar en dubbel precision (dubbel) parameter och returnerar en annan dubbel, och tilldelas en funktion som omvandlar centimeter till tum.
  • func2 tar en pekare till en konstant teckenuppsättning såväl som ett heltal och returnerar en pekare till ett tecken, och tilldelas en C-stränghanteringsfunktion som returnerar en pekare till den första förekomsten av ett givet tecken i en teckenuppsättning.
 
 

   
	   





  
	   
	       
	   
	
	 0
 #include  <stdio.h>  /* för printf */  #include  <string.h>  /* för strchr */  double  cm_to_inches  (  double  cm  )  {  return  cm  /  2.54  ;  }  // "strchr" är en del av C-stränghanteringen (dvs inget behov av deklaration)  //  Se https://en.wikipedia.org/wiki/C_string_handling#Functions  int  main  (  void )   {  double  (  *  func1  ) (  dubbel  )  =  cm_to_inches  ;  char  *  (  *  func2  )(  const  char  *  ,  int  )  =  strchr  ;  printf  (  "%f %s"  ,  func1  (  15.0  ),  func2  (  "Wikipedia"  ,  'p' )  );  /* skriver ut "5.905512 pedia" */  return  ;  } 

Nästa program använder en funktionspekare för att anropa en av två funktioner ( sin eller cos ) indirekt från en annan funktion ( compute_sum , beräknar en approximation av funktionens Riemann-integration ). Programmet fungerar genom att ha funktionen huvudanrop funktionen compute_sum två gånger, skicka en pekare till biblioteksfunktionen sin första gången och en pekare till funktion cos andra gången. Funktionen compute_sum anropar i sin tur en av de två funktionerna indirekt genom att därav referera dess funktionspekarargument funcp flera gånger, summera värdena som den anropade funktionen returnerar och returnerar den resulterande summan. De två summorna skrivs till standardutgången av main .

 
 


       
       

    
     
       0     
        
                   
           
          
    
           


   
        


  
      

    
        
     

    
        
     

    
        
     

     0
 #include  <math.h>  #include  <stdio.h>  // Funktion som tar en funktionspekare som argument  double  compute_sum  (  double  (  *  funcp  )(  double  ),  double  lo  ,  double  hi  )  {  double  summa  =  0.0  ;  // Lägg till värden som returneras av funktionen "*funcp"  int  i  ;  for  (  i  =  ;  i  <=  100  ;  i  ++  )  {  // Använd funktionspekaren 'funcp' för att anropa funktionen  double  x  =  i  /  100.0  *  (  hi  -  lo  )  +  lo  ;  dubbel  y  =  funcp  (  x  );  summa  +=  y  ;  }  retursumma  /  101,0  *  (  hej  -  lo  )  ;  }  dubbel  kvadrat  (  dubbel  x  )  {  return  x  *  x  ;  }  int  main  (  void  )  {  dubbelsumma  ;  _  // Använd standardbiblioteksfunktionen 'sin()' som den pekade på funktionen  sum  =  compute_sum  (  sin  ,  0.0  ,  1.0  );  printf  (  "summa(sin): %g  \n  "  ,  summa  );  // Använd standardbiblioteksfunktionen 'cos()' som den pekade på funktionen  sum  =  compute_sum  (  cos  ,  0.0  ,  1.0  );  printf  (  "sum(cos): %g  \n  "  ,  summa  );  // Använd den användardefinierade funktionen 'square()' som den pekade på funktionen  sum  =  compute_sum  (  square  ,  0.0  ,  1.0  );  printf  (  "summa(kvadrat): %g  \n  "  ,  summa  );  återvända  ;  } 

Funktioner

Funktioner, eller funktionsobjekt, liknar funktionspekare och kan användas på liknande sätt. En funktor är ett objekt av en klasstyp som implementerar funktionsanropsoperatorn , vilket gör att objektet kan användas inom uttryck som använder samma syntax som ett funktionsanrop. Funktioner är mer kraftfulla än enkla funktionspekare, eftersom de kan innehålla sina egna datavärden och låter programmeraren efterlikna stängningar . De används också som återuppringningsfunktioner om det är nödvändigt att använda en medlemsfunktion som återuppringningsfunktion.

Många "rena" objektorienterade språk stöder inte funktionspekare. Något liknande kan implementeras i dessa typer av språk, dock med hjälp av referenser till gränssnitt som definierar en enda metod (medlemsfunktion). CLI-språk som C# och Visual Basic .NET implementerar typsäkra funktionspekare med delegater .

På andra språk som stöder förstklassiga funktioner betraktas funktioner som data och kan skickas, returneras och skapas dynamiskt direkt av andra funktioner, vilket eliminerar behovet av funktionspekare.

Om du använder funktionspekare i stor utsträckning för att anropa funktioner kan det leda till en långsammare för koden på moderna processorer, eftersom en förgreningsprediktor kanske inte kan ta reda på var den ska förgrena sig (det beror på värdet på funktionspekaren vid körning) även om denna effekt kan överskattas eftersom den ofta kompenseras ordentligt av avsevärt minskade icke-indexerade tabelluppslagningar.

Metodpekare

C++ innehåller stöd för objektorienterad programmering , så klasser kan ha metoder (vanligtvis kallade medlemsfunktioner). Icke-statiska medlemsfunktioner (instansmetoder) har en implicit parameter ( denna pekare) som är pekaren till objektet det arbetar på, så objektets typ måste inkluderas som en del av typen av funktionspekaren. Metoden används sedan på ett objekt av den klassen genom att använda en av "pekare-till-medlem"-operatorerna: . * eller ->* (för ett objekt respektive en pekare till objekt). [ tveksamt ]

Även om funktionspekare i C och C++ kan implementeras som enkla adresser, så att vanligtvis sizeof(Fx)==sizeof(void *) implementeras medlemspekare i C++ ibland som " fettpekare ", vanligtvis två eller tre gånger så stor som en enkel funktionspekare, för att hantera virtuella metoder och virtuellt arv [ citat behövs ] .

I C++

I C++ är det, förutom metoden som används i C, även möjligt att använda C++ standardbiblioteksklassmall std::function , där instanserna är funktionsobjekt:

 
 

         
         
         
         
         


    
       


  
       
                
     0
 #include  <iostream>  #include  <functional>  statisk  dubbelderivata  (  const  std  ::  function  <  double  (  double  )  >  &  f  ,  double  x0  ,  double  eps  )  {  double  eps2  =  eps  /  2  ;  _  dubbel  lo  =  x0  -  eps2  ;  dubbel  hi  =  x0  +  eps2  ;  return  (  f  (  hej  )  -  f  (  lo  ))  /  eps  ;  }  static  double  f  (  double  x  )  {  return  x  *  x  ;  }  int  main  ()  {  double  x  =  1  ;  std  ::  cout  <<  "d/dx(x ^ 2) [@ x = "  <<  x  <<  "] = "  <<  derivata  (  f  ,  x  ,  1e-5  )  <<  std  ::  endl  ;  återvända  ;  } 

Pekare till medlemsfunktioner i C++

Så här använder C++ funktionspekare när det hanteras medlemsfunktioner i klasser eller strukturer. Dessa anropas med hjälp av en objektpekare eller ett detta anrop. De är typsäkra genom att du bara kan anropa medlemmar i den klassen (eller derivator) med en pekare av den typen. Det här exemplet visar också användningen av en typedef för funktionen "pekare till medlem" som lagts till för enkelhetens skull. Funktionspekare till statiska medlemsfunktioner görs i den traditionella "C"-stilen eftersom det inte krävs någon objektpekare för detta anrop.

 
  

  


         
         
    
         
         
    
        
         
    


        
     


 

         
     


 

     
     


  
     
            
            
           
     0
 #include  <iostream>  med  namnutrymme  std  ;  class  Foo  {  public  :  int  add  (  int  i  ,  int  j  )  {  return  i  +  j  ;  }  int  mult  (  int  i  ,  int  j  )  {  return  i  *  j  ;  }  statisk  int  negate  (  int  i  )  {  return  -  i  ;  }  };  int  bar1  (  int  i  ,  int  j  ,  Foo  *  pFoo  ,  int  (  Foo  ::*  pfn  )(  int  ,  int  ))  {  return  (  pFoo  ->*  pfn  )(  i  ,  j  );  }  typedef  int  (  Foo  ::*  Foo_pfn  )(  int  ,  int  );  int  bar2  (  int  i  ,  int  j  ,  Foo  *  pFoo  ,  Foo_pfn  pfn  )  {  return  (  pFoo  ->*  pfn  )(  i  ,  j  );  }  typedef  int  (  *  PFN  )(  int  );  int  bar3  (  int  i  ,  PFN  pfn  )  {  return  pfn  (  i  );  }  int  main  ()  {  Foo  foo  ;  cout  <<  "Foo::add(2,4) = "  <<  bar1  (  2  ,  4  ,  &  foo  ,  &  Foo  ::  add  )  <<  endl  ;  cout  <<  "Foo::mult(3,5) = "  <<  bar2  (  3  ,  5  ,  &  foo  ,  &  Foo  ::  mult  )  <<  endl  ;  cout  <<  "Foo::negate(6) = "  <<  bar3  (  6  ,  &  Foo  ::  negate  )  <<  endl  ;  återvända  ;  } 

Alternativ C och C++ syntax

C- och C++-syntaxen ovan är den kanoniska som används i alla läroböcker - men den är svår att läsa och förklara. Även ovanstående typedef- exempel använder denna syntax. Varje C- och C++-kompilator stöder dock en mer tydlig och koncis mekanism för att deklarera funktionspekare: använd typedef , men lagra inte pekaren som en del av definitionen. Observera att det enda sättet som den här typen av typedef faktiskt kan användas på är med en pekare - men det framhäver pekaren i den.

C och C++


  


   


         


   


     
    
 


       





  


   
     // Detta deklarerar 'F', en funktion som accepterar ett 'char' och returnerar ett 'int'. Definitionen finns någon annanstans.   int  F  (  charc  )  ;  // Detta definierar 'Fn', en typ av funktion som accepterar ett 'char' och returnerar ett 'int'.  typedef  int  Fn  (  char  c  );  // Detta definierar 'fn', en variabel av typen pointer-to-'Fn', och tilldelar adressen 'F' till den.  Fn  *  fn  =  &  F  ;  // Notera att '&' inte krävs - men det framhäver vad som görs.  // Detta anropar 'F' med 'fn', och tilldelar resultatet till variabeln 'a'  int  a  =  fn  (  'A' )  ;  // Detta definierar 'Call', en funktion som accepterar en pekare-to-'Fn', anropar den och returnerar resultatet  int  Call  (  Fn  *  fn  ,  char  c  )  {  return  fn  (  c  );  }  // Call(fn, c)  // Detta anropar funktionen 'Call', skickar in 'F' och tilldelar resultatet till 'call'  int  call  =  Call  (  &  F  ,  'A'  );  // Återigen, '&' krävs inte  // LEGACY: Observera att för att behålla befintliga kodbaser kan definitionsstilen ovan fortfarande användas först;  // då kan den ursprungliga typen definieras utifrån den med den nya stilen.  // Detta definierar 'PFn', en typ av pekare-till-typ-Fn.  typedef  Fn  *  PFn  ;  // 'PFn' kan användas där 'Fn *' kan  PFn  pfn  =  F  ;  int  CallP  (  PFn  fn  ,  char  c  ); 

C++

Dessa exempel använder ovanstående definitioner. Observera särskilt att definitionen ovan för Fn kan användas i definitioner av pekare-till-medlemsfunktioner:



  

   
  
  


   



  





   


   


   




       
    
 




       
    
 





  


   
     // Detta definierar 'C', en klass med liknande statiska och medlemsfunktioner,  // och skapar sedan en instans som kallas 'c'  class  C  {  public  :  static  int  Static  (  char  c  );  int  Member  (  char  c  );  }  c  ;  // C  // Detta definierar 'p', en pekare till 'C' och tilldelar adressen 'c' till den  C  *  p  =  &  c  ;  // Detta tilldelar en pekare-till-'Static' till 'fn'.  // Eftersom det inte finns något 'detta' är 'Fn' den korrekta typen; och 'fn' kan användas enligt ovan.   fn  =  &  C  ::  Statisk  ;  // Detta definierar 'm', en pekare-till-medlem-av-'C' med typen 'Fn',  // och tilldelar adressen till 'C::Member' till den.  // Du kan läsa den från höger till vänster som alla pekare:  // "'m' är en pekare till medlem av klassen 'C' av typen 'Fn'"  Fn  C  ::*  m  =  &  C  ::  Member  ;  // Detta använder 'm' för att anropa 'Member' i 'c', och tilldelar resultatet till 'cA'  int  cA  =  (  c  .  *  m  )(  'A' )  ;  // Detta använder 'm' för att anropa 'Member' i 'p', och tilldelar resultatet till 'pA'  int  pA  =  (  p  ->*  m  )(  'A' )  ;  // Detta definierar 'Ref', en funktion som accepterar en referens-till-'C',  // en pointer-to-member-of-'C' av typen 'Fn', och en 'char',  // anropar funktionen och returnerar resultatet  int  Ref  (  C  &  r  ,  Fn  C  ::*  m  ,  char  c  )  {  return  (  r  .  *  m  )(  c  );  }  // Ref(r, m, c)  // Detta definierar 'Ptr', en funktion som accepterar en pekare-till-'C', //  en pekare-till-medlem-av-'C' av typen 'Fn' ', och ett 'char',  // anropar funktionen och returnerar resultatet  int  Ptr  (  C  *  p  ,  Fn  C  ::*  m  ,  char  c  )  {  return  (  p  ->*  m  )(  c  );  }  // Ptr(p, m, c)  // LEGACY: Observera att för att bibehålla befintliga kodbaser kan definitionsstilen ovan fortfarande användas först;  // då kan den ursprungliga typen definieras utifrån den med den nya stilen.  // Detta definierar 'FnC', en typ av pointer-to-member-of-class-'C' av typen 'Fn'  typedef  Fn  C  ::*  FnC  ;  // 'FnC' kan användas där 'Fn C::*' kan  FnC  fnC  =  &  C  ::  Member  ;  int  RefP  (  C  &  p  ,  FnCm  ,  char  c  )  ;   

Se även

externa länkar