我最近在C语言中有一些函数指针的经验。

因此,按照回答自己问题的传统,我决定对最基本的问题做一个简短的总结,以供那些需要快速切入主题的人使用。


当前回答

一旦有了基本的声明器,函数指针就很容易声明:

id:id:id是指针:*D:D指针指向函数:D(<parameters>):D函数获取<parameter>返回

而D是使用相同规则构建的另一个声明器。最后,在某个地方,它以ID结尾(参见下面的示例),这是声明实体的名称。让我们尝试构建一个函数,该函数获取指向不获取任何内容并返回int的函数的指针,并返回指向获取char并返回int函数的指针

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。通过查找*-运算符和函数调用运算符()的优先级可以知道这一点。如果没有括号,编译器会将其读为*(D2(char p))。当然,这不会再简单地用*D2取代D1。声明符周围始终允许使用括号。实际上,如果你添加了太多,你不会出错。

(*D2)(char);

返回类型已完成!现在,让我们用返回<parameters>的函数声明器函数来替换D2,这就是我们现在的D3(<parameter>)。

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

注意,不需要括号,因为我们希望D3这次是函数声明器,而不是指针声明器。很好,唯一剩下的就是它的参数。该参数的处理方式与我们处理返回类型的处理方式完全相同,只是将char替换为void。所以我会复制它:

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

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

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

在该示例中,我调用了函数ID0的标识符。

自上而下

这从类型描述中最左边的标识符开始,当我们从右边走过时,将该声明符包装起来。从函数获取<parameters>开始返回

ID0(<parameters>)

描述中的下一件事(在“返回”之后)是指针指向。让我们合并它:

*ID0(<parameters>)

然后,下一件事是functon获取<parameters>返回。该参数是一个简单的字符,因此我们再次将其放入,因为它非常简单。

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

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

现在我们只需要放置<parameters>。我将展示一个简短的嘲笑版本,因为我认为你现在已经知道该怎么做了。

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内部构件。随意编辑/修复其中的内容。

其他回答

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

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

#include <stdio.h>

int add()
{
   return (100+10);
}

int sub()
{
   return (100-10);
}

void print(int x, int y, int (*func)())
{
    printf("value is: %d\n", (x+y+(*func)()));
}

int main()
{
    int x=100, y=200;
    print(x,y,add);
    print(x,y,sub);

    return 0;
}

输出为:

值为:410值为:390

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

“从头开始”功能具有“开始执行的内存地址”。在汇编语言中,它们被称为(调用“函数的内存地址”)。现在回到C。如果函数有内存地址,那么它们可以由C中的指针操作

1.首先需要声明一个指向函数的指针2.传递所需函数的地址

****注意->函数应为相同类型****

这个简单的计划将说明一切。

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

之后,让我们来看看机器是如何理解它们的。32位体系结构的上述程序的机器指令一瞥。

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

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

C相当善变,同时也很宽容:)

函数指针的另一个好用法:在版本之间轻松切换

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

版本.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 <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

在版本c中,我将定义版本h中的2个函数原型

版本.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;
}

注意函数指针在版本.h中如何原型化为void(*zprintf)(constchar*,…);当它在应用程序中被引用时,它将在其指向的任何位置开始执行,这一点尚未定义。在版本.c中,注意在board_init()函数中,zprintf被分配了一个唯一的函数(其函数签名匹配),这取决于版本中定义的版本

运行代码将如下所示:

主程序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;
}

如果处于调试模式,上述代码将使用printf,如果处于发布模式,则不执行任何操作。这比浏览整个项目并注释或删除代码要容易得多。我所需要做的就是在版本.h中更改版本,其余的都由代码完成!