提问



我最近在C中使用了函数指针。


继续回答你自己的问题的传统,我决定对那些需要快速深入研究这个主题的人进行一些基本的总结。

最佳参考


C

中的函数指针

让我们从一个基本功能开始,我们将指向:


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


首先,让我们定义一个指向函数的指针,该函数接收2 int并返回int:


int (*functionPtr)(int,int);


现在我们可以安全地指出我们的功能:


functionPtr = &addInt;


现在我们有了一个指向函数的指针,让我们使用它:


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


将指针传递给另一个函数基本相同:


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


我们也可以在返回值中使用函数指针(尝试跟上,它会变得混乱):


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


但是使用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 = &addInt;
    return functionPtr;
}

其它参考1


C中的函数指针可用于在C中执行面向对象的编程。


例如,以下行用C编写:


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


是的,->new运算符的缺失是一个死的赠品,但它肯定暗示我们将某些String类的文本设置为"hello"


通过使用函数指针,可以在C 中模拟方法。


这是如何完成的?


String类实际上是struct,带有一堆函数指针,用作模拟方法的方法。以下是String类的部分声明:


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


可以看出,String类的方法实际上是声明函数的函数指针。在准备String的实例时,调用newString函数以设置指向各自函数的函数指针:


String newString()
{
    String self = (String)malloc(sizeof(struct String_Struct));

    self->get = &getString;
    self->set = &setString;
    self->length = &lengthString;

    self->set(self, "");

    return self;
}


例如,通过调用get方法调用的getString函数定义如下:


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


可以注意到的一件事是,没有对象实例的概念,并且具有实际上是对象的一部分的方法,因此必须在每次调用时传入自身对象。 (而internal只是一个隐藏的struct,它在前面的代码清单中被省略了 - 它是一种执行信息隐藏的方法,但这与函数指针无关。)


因此,不必能够做s1->set("hello");,而是必须传入对象以对s1->set(s1, "hello")执行操作。


由于这个小的解释必须通过引用自己的方式,我们将移动到下一部分,这是继承在C


假设我们要创建String的子类,比如说ImmutableString。为了使字符串不可变,set方法将无法访问,同时保持对ImmutableString的访问权限。 getlength,并强制构造函数接受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);


基本上,对于所有子类,可用的方法再次是函数指针。这次,set方法的声明不存在,因此,它不能在ImmutableString中调用。


至于ImmutableString的实现,唯一相关的代码是构造函数函数,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方法的函数指针实际上是指String.getString.length方法,通过ImmutableString]]变量,它是一个内部存储的String对象。


使用函数指针可以实现从超类继承方法。


我们可以进一步继续 C中的多态性


例如,如果我们想要length方法的行为由于某种原因在ImmutableString类中一直返回0,那么所有必须做的就是:



  1. 添加一个将用作覆盖length方法的函数。

  2. 转到构造函数并将函数指针设置为覆盖length方法。



ImmutableString中添加覆盖length方法可以通过添加lengthOverrideMethod来执行:


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


然后,构造函数中length方法的函数指针连接到lengthOverrideMethod:


ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = &lengthOverrideMethod;

    self->base->set(self->base, (char*)value);

    return self;
}


现在,ImmutableString类中的length方法的length方法与String类不同,现在length方法将引用length方法中定义的行为。 90]]功能。


我必须添加一个免责声明,我仍然在亚博2018平台如何使用C语言中的面向对象编程风格进行编写,因此可能有一点我没有解释清楚,或者可能只是在如何最好地实现OOP方面脱离了标记在C.但我的目的是试图说明函数指针的许多用法之一。


有关如何在C中执行面向对象编程的更多信息,请参阅以下问题:



  • C中的面向对象

  • 你能用C编写面向对象的代码吗?


其它参考2


被解雇的指南:如何通过手动编译代码来滥用x86机器上的GCC中的函数指针:



  1. 返回EAX寄存器的当前值


    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
    

  2. 编写交换功能


    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")(&a,&b);
    

  3. 将for循环计数器写入1000,每次调用一些函数


    ((int(*)())"\x66\x31\xc0\x8b\x5c\x24\x04\x66\x40\x50\xff\xd3\x58\x66\x3d\xe8\x03\x75\xf4\xc3")(&function); // calls function with 1->1000
    

  4. 您甚至可以编写一个计数为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);
    


其它参考3


我最喜欢的函数指针之一是使用廉价且简单的迭代器 -


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

其它参考4


拥有基本声明符后,函数指针变得易于声明:



  • id:ID: ID是

  • 指针:*D:指向
  • 的指针
  • 功能:D(): D功能<参数>返回



而D是使用相同规则构建的另一个声明符。最后,在某个地方,它以ID结尾(参见下面的示例),这是声明的实体的名称。让我们尝试构建一个函数,它接受一个函数的指针,不带任何东西并返回int,并返回一个指向函数的指针,该函数接受一个char并返回int。使用type-defs就像这样


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


如您所见,使用typedef构建它非常容易。没有typedef,使用上述声明符规则并不难,应用一致。如你所见,我错过了指针指向的部分,以及函数返回的东西。这就是声明最左边出现的内容,并没有意义:如果已经建立了声明者,最后会添加它。让我们这样做。建立起来,首先罗嗦 - 用[]显示结构:


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


如您所见,可以通过一个接一个地附加声明符来完全描述类型。施工可以通过两种方式完成。一个是自下而上的,从正确的事物(叶子)开始,一直到标识符。另一种方式是自上而下,从标识符开始,一直向下到叶子。我将展示两种方式。


自下而上



构造从右边的东西开始:返回的东西,这是采取char的函数。为了使声明者保持不同,我将对它们进行编号:


D1(char);


直接插入char参数,因为它很简单。通过用*D2替换D1来添加指向声明符的指针。注意我们必须在*D2周围括起括号。这可以通过查找*-operator和函数调用运算符()的优先级。如果没有我们的括号,编译器会将其读作*(D2(char p))。但这不是D1的简单替换*D2当然。在声明者身边总是允许使用括号。所以如果添加太多的话,你实际上并没有做错。


(*D2)(char);


退货类型齐全!现在,让我们用函数声明器函数替换D2 返回,这是我们现在的D3()


(*D3())(char)


请注意,不需要括号,因为我们希望 D3成为函数声明符,而不是这次的指针声明符。太棒了,唯一剩下的就是它的参数。参数完成与我们完成返回类型完全相同,只需将char替换为void。所以我将复制它:


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


我已经用ID1替换了D2,因为我们已经完成了该参数(它已经是一个指向函数的指针 - 不需要另一个声明符)。 ID1将是参数的名称。现在,我在上面告诉我们最后一个添加了所有那些声明者修改的类型 - 出现在每个声明的最左边的那个。对于函数,它将成为返回类型。对于指向类型等的指针...当写下类型时它很有意思,它会以相反的顺序出现,在最右边:)无论如何,替换它会产生完整的声明。两次int当然。


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


在那个例子中,我已经调用了函数ID0的标识符。


自上而下



这从类型描述最左边的标识符开始,在我们向右走过时包装该声明符。以功能开始,<参数>返回


ID0()


描述中的下一个内容(在返回之后)是指向的指针。让我们加入它:


*ID0()


接下来的事情是 functon <参数>返回。参数是一个简单的char,所以我们马上把它放进去,因为它真的很小。


(*ID0())(char)


注意我们添加的括号,因为我们再次希望*首先绑定,然后然后 (char)。否则它将读取函数<参数>返回函数... 。 Noes,甚至不允许返回函数的函数。


现在我们只需要<参数>。我将展示一个简短的衍生版本,因为我认为你现在已经知道如何做到这一点。


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


只需将int放在声明器之前,就像我们自下而上一样,我们就完成了


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


好事



自下而上还是自上而下更好?我习惯自下而上,但有些人可能会更自在地进行自上而下。这是我认为的品味问题。顺便提一下,如果你在该声明中应用所有运算符,最终会得到一个int:


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


这是C中声明的一个很好的属性:声明声明如果使用标识符在表达式中使用这些运算符,那么它会在最左边产生类型。对阵列来说也是如此。


希望你喜欢这个小教程!现在,当人们想知道函数的奇怪声明语法时,我们可以链接到这个。我试着把尽可能少的C内部装置。随意编辑/修复其中的内容。

其它参考5


函数指针的另一个好用途:
无痛地在版本之间切换



当你想要在不同的时间或不同的开发阶段使用不同的功能时,它们非常方便使用。例如,我在具有控制台的主机上开发应用程序,但该软件的最终版本将是戴上Avnet ZedBoard(它有显示器和控制台的端口,但最终版本不需要/想要它们)。因此在开发过程中,我将使用printf来查看状态和错误消息,但是当我完成后,我不想要打印任何内容。这就是我所做的:


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_      /* prevent circular inclusions */
    #define __VERSION_H_  /* by using protection macros */
    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 
#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中,我将定义version.h中出现的2个函数原型


version.c



#include "version.h"

/*****************************************************************************/
/**
* @name board_init
*
* Sets up the application based on the version type defined in version.h.
* Includes allowing or prohibiting printing to STDOUT.
*
* MUST BE CALLED FIRST THING IN MAIN
*
* @return    None
*
*****************************************************************************/
void board_init()
{
    // Assign the print function to the correct function pointer
    #ifdef DEBUG_VERSION
        zprintf = &printf;
    #else
        // Defined below this function
        zprintf = &noprintf;
    #endif
}

/*****************************************************************************/
/**
* @name noprintf
*
* simply returns with no actions performed
*
* @return   None
*
*****************************************************************************/
void noprintf(const char* c, ...)
{
    return;
}


注意函数指针在version.h中的原型如何
void (* zprintf)(const char *, ...);
当它在应用程序中被引用时,它将在它指向的任何地方开始执行,这尚未定义。

version.c中,注意board_init()函数,其中zprintf被赋予一个唯一函数(其函数签名匹配),具体取决于version.h中定义的版本
zprintf = &printf; zprintf调用printf进行调试
要么
zprintf = &noprint; zprintf只返回并且不会运行不必要的代码



运行代码将如下所示:


mainProg.c



#include "version.h"
#include 
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;
}


如果处于调试模式,上面的代码将使用printf,如果处于释放模式,则不执行任何操作。这比完成整个项目以及注释或删除代码要容易得多。我需要做的就是更改version.h中的版本,代码将完成其余的工作!

其它参考6


函数指针通常由typedef定义,并用作param&回报值,


上面的答案已经解释了很多,我只是给出一个完整的例子:


#include 

#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, &sum));
}

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

其它参考7


C中函数指针的一个重要用途是调用在运行时选择的函数。例如,C运行时库有两个例程,qsort和bsearch,它们指向一个函数,该函数被调用以比较两个被排序的项目;这允许您根据您希望使用的任何条件分别对任何内容进行排序或搜索。


一个非常基本的例子,如果有一个名为print(int x,int y)的函数,它反过来可能需要调用类似类型的add()函数或sub()然后我们将要做的,我们将添加一个函数print()函数的指针参数如下所示: -


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


从头开始,函数有一些内存地址从它们开始执行的位置。在汇编语言中它们被称为(调用函数的内存地址)。现在回到C如果函数有一个内存地址,那么它们可以通过C中的指针来操作。所以按照C的规则


1.首先,您需要声明一个指向函数的指针
2.通过所需功能的地址


****注意 - >功能应该是同一类型****


这个简单的程序将说明每一件事。


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


之后让我们看看机器如何理解它。在32位中查看上述程序的机器指令的一瞥建筑。


红色标记区域显示地址如何交换并存储在eax中。然后它们是eax上的调用指令。 eax包含所需的函数地址

其它参考9


由于函数指针通常是类型化的回调,因此您可能需要查看类型安全回调。这同样适用于非回调函数的入口点等。[144]


C在同一时间非常善变和宽容:)

其它参考10


函数指针在很多情况下都很有用,例如:



  • COM对象成员是函数ag的指针:
    This->lpVtbl->AddRef(This); AddRef是一个指向函数的指针。

  • 函数回调,例如用户定义的函数进行比较
    两个变量作为回调传递给特殊排序函数。

  • 对插件实现和应用程序SDK非常有用。