C中的函数指针如何工作?

问题描述 投票:1127回答:11

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

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

c function-pointers
11个回答
1377
投票

Function pointers in C

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

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

首先,让我们定义一个指向函数的指针,该函数接收2个ints并返回一个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;
}

4
投票

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

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

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

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

type safe callbacks

之后让我们看看机器如何理解它。在32位架构中了解上述程序的机器指令。

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


0
投票

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

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


286
投票

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

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

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方法将无法访问,同时保持对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方法,通过base变量,这是一个内部存储的String对象。

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

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

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

  1. 添加一个将作为重写length方法的函数。
  2. 转到“构造函数”并将函数指针设置为重写的length方法。

length中添加一个重写的ImmutableString方法可以通过添加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;
}

现在,length类中的ImmutableString类的String方法的行为与length类相同,现在lengthOverrideMethod方法将引用Object-Orientation in C?函数中定义的行为。

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

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


215
投票

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

这些字符串文字是32位x86机器代码的字节。 an x86 ret instructionnasm

你通常不会手工编写这些,你用汇编语言编写,然后使用像int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))(); 这样的汇编程序将它组装成一个平面二进制文件,你可以将它转换为C字符串文字。

  1. 返回EAX寄存器上的当前值 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);
  2. 写一个交换函数 ((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
  3. 将for循环计数器写入1000,每次调用一些函数 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);
  4. 您甚至可以编写一个计数为100的递归函数 .rodata

请注意,编译器将字符串文字放在.rdata部分(或Windows上的mprotect())中,该部分链接为文本段的一部分(以及函数代码)。

文本段具有Read + Exec权限,因此将字符串文字转换为函数指针可以在不需要VirtualProtect()gcc -z execstack系统调用的情况下运行,就像您需要动态分配的内存一样。 (或者// at global scope const char swap[] = "\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"; 将程序与堆栈+数据段+堆可执行文件链接起来,作为快速入侵。)


要反汇编这些,您可以编译它以在字节上放置标签,并使用反汇编程序。

gcc -c -m32 foo.c

使用objdump -D -rwC -Mintel进行编译并使用00000000 <swap>: 0: 8b 44 24 04 mov eax,DWORD PTR [esp+0x4] # load int *a arg from the stack 4: 8b 5c 24 08 mov ebx,DWORD PTR [esp+0x8] # ebx = b 8: 8b 00 mov eax,DWORD PTR [eax] # dereference: eax = *a a: 8b 1b mov ebx,DWORD PTR [ebx] c: 31 c3 xor ebx,eax # pointless xor-swap e: 31 d8 xor eax,ebx # instead of just storing with opposite registers 10: 31 c3 xor ebx,eax 12: 8b 4c 24 04 mov ecx,DWORD PTR [esp+0x4] # reload a from the stack 16: 89 01 mov DWORD PTR [ecx],eax # store to *a 18: 8b 4c 24 08 mov ecx,DWORD PTR [esp+0x8] 1c: 89 19 mov DWORD PTR [ecx],ebx 1e: c3 ret not shown: the later bytes are ASCII text documentation they're not executed by the CPU because the ret instruction sends execution back to the caller 进行反汇编,我们可以获得程序集,并通过破坏EBX(一个保留调用的寄存器)发现此代码违反了ABI,并且通常效率低下。

#include <stdio.h>
#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; i<MAX_COLORS; i++)
        (*fp)(&Colors[i]);
}

void printColor(Color* c) {
    if (c->name)
        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);
}

这个机器代码将(可能)在Windows,Linux,OS X等上以32位代码工作:所有这些操作系统上的默认调用约定都在栈上传递args,而不是在寄存器中更有效。但EBX在所有正常的调用约定中都被调用保留,因此将其用作临时寄存器而不保存/恢复它可以轻松地使调用者崩溃。


105
投票

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

ID

24
投票

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

  • id:*D:ID是一个
  • 指针:D(<parameters>):D指向
  • 功能:<:D函数采取>parametersID返回

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

[

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

D1(char);

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

自下而上

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

D1

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

D2

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

D3

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

D2

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

ID0

我在这个例子中调用了函数<的标识符。

自顶向下

这从类型描述最左边的标识符开始,在我们向右走过时包装该声明符。从>parametersID0(<parameters>) 返回的函数开始

*ID0(<parameters>)

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

<

接下来的事情就是让>parameters(*ID0(<parameters>))(char) 回归的功能。参数是一个简单的char,所以我们再次把它放进去,因为它真的是微不足道的。

*

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

现在我们只需要放>parameterspointer to: *ID1 ... function taking void returning: (*ID1)(void) 。我将展示一个简短的衍生版本,因为我认为你现在已经知道如何做到这一点。

int

只需将int (*ID0(int (*ID1)(void)))(char) 放在声明器之前,就像我们自下而上一样,我们就完成了

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

好事

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

printf

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

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


23
投票

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

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

version.h

version.c

version.h中,我将定义#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; } 中存在的2个函数原型

version.c

version.h

注意函数指针是如何在void (* zprintf)(const char *, ...);中原型化的

version.c

当它在应用程序中被引用时,它将在它指向的任何地方开始执行,这尚未定义。

board_init()中,请注意zprintffunction,其中version.h被赋予一个唯一的函数(其函数签名匹配),具体取决于zprintf = &printf;中定义的版本

zprintf = &noprint; zprintf调用printf进行调试

要么

#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; } zprintf只返回并且不会运行不必要的代码

运行代码将如下所示:

mainProg.c

printf

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


14
投票

函数指针通常由#include <stdio.h> #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 &sum; } // test - use function pointer as variable, void test_pointer_as_variable() { // create a pointer to function, two_num_operation sum_p = &sum; // 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; } 定义,并用作参数和返回值。

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

qsort

13
投票

C中函数指针的一个重要用途是调用在运行时选择的函数。例如,C运行时库有两个例程,bsearchprint(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; } 添加一个函数指针参数功能如下图所示:

int func (int a, char *pStr);    // declares a function

int (*pFunc)(int a, char *pStr);  // declares or defines a function pointer

int (*pFunc2) ();                 // declares or defines a function pointer, no parameter list specified.

int (*pFunc3) (void);             // declares or defines a function pointer, no arguments.

输出是:

价值是:410 价值是:390


5
投票

函数指针是包含函数地址的变量。由于它是一个带有一些受限属性的指针变量,因此您可以像使用数据结构中的任何其他指针变量一样使用它。

我能想到的唯一例外是将函数指针视为指向除单个值之外的其他东西。通过递增或递减函数指针或向函数指针添加/减去偏移量来进行指针运算实际上并不具有任何实用性,因为函数指针仅指向单个事物,即函数的入口点。

函数指针变量的大小,即变量占用的字节数,可以根据底层架构而变化,例如: x32或x64或其他。

函数指针变量的声明需要指定与函数声明相同类型的信息,以便C编译器执行它通常执行的各种检查。如果未在函数指针的声明/定义中指定参数列表,则C编译器将无法检查参数的使用。有些情况下,这种缺乏检查可能有用,但请记住已删除安全网。

一些例子:

func

前两个声明有点相似:

  • int是一个函数,需要一个char *和一个int并返回一个pFunc
  • int是一个函数指针,它被赋予一个函数的地址,该函数接受char *int并返回一个func()

因此,从上面我们可以得到一个源代码行,其中函数pFunc的地址被赋值给pFunc = func;中的函数指针变量int *pfunc(int a, char *pStr); // declares a function that returns int pointer int (*pFunc)(int a, char *pStr); // declares a function pointer that returns an int

请注意与函数指针声明/定义一起使用的语法,其中括号用于克服自然运算符优先级规则。

int (*pFunc) (int a, char *pStr);    // declare a simple function pointer variable
int (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointers
int (**pFunc)(int a, char *pStr);    // declare a pointer to a function pointer variable
struct {                             // declare a struct that contains a function pointer
    int x22;
    int (*pFunc)(int a, char *pStr);
} thing = {0, func};                 // assign values to the struct variable
char * xF (int x, int (*p)(int a, char *pStr));  // declare a function that has a function pointer as an argument
char * (*pxF) (int x, int (*p)(int a, char *pStr));  // declare a function pointer that points to a function that has a function pointer as an argument

几个不同的用法示例

使用函数指针的一些示例:

int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);

您可以在函数指针的定义中使用可变长度参数列表。

int  sum ();      // nothing specified in the argument list so could be anything or nothing
int (*psum)();
int  sum2(void);  // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);

或者您根本无法指定参数列表。这可能很有用,但它消除了C编译器对提供的参数列表执行检查的机会。

int sum (int a, char *b);
int (*psplsum) (int a, int b);
psplsum = sum;               // generates a compiler warning
psplsum = (int (*)(int a, int b)) sum;   // no compiler warning, cast to function pointer
psplsum = (int *(int a, int b)) sum;     // compiler error of bad cast generated, parenthesis are required.

C风格的演员表

您可以将C样式转换与函数指针一起使用。但请注意,C编译器可能对检查或提供警告而不是错误松懈。

if

比较功能指针与平等

您可以使用static int func1(int a, int b) { return a + b; } static int func2(int a, int b, char *c) { return c[0] + a + b; } static int func3(int a, int b, char *x) { return a + b; } static char *func4(int a, int b, char *c, int (*p)()) { if (p == func1) { p(a, b); } else if (p == func2) { p(a, b, c); // warning C4047: '==': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)' } else if (p == func3) { p(a, b, c); } return c; } 语句检查函数指针是否等于特定函数地址,但我不确定它有多大用处。其他比较运算符似乎具有更低的效用。

void

一系列功能指针

如果你想要一个函数指针数组,每个元素的参数列表都有差异,那么你可以定义一个函数指针,参数列表未指定(不是int(*p[])() = { // an array of function pointers func1, func2, func3 }; int(**pp)(); // a pointer to a function pointer p[0](a, b); p[1](a, b, 0); p[2](a, b); // oops, left off the last argument but it compiles anyway. func4(a, b, 0, func1); func4(a, b, 0, func2); // warning C4047: 'function': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)' func4(a, b, 0, func3); // iterate over the array elements using an array index for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) { func4(a, b, 0, p[i]); } // iterate over the array elements using a pointer for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) { (*pp)(a, b, 0); // pointer to a function pointer so must dereference it. func4(a, b, 0, *pp); // pointer to a function pointer so must dereference it. } ,这意味着没有参数,但只是未指定),如下所示您可能会看到来自C编译器的警告。这也适用于函数的函数指针参数:

namespace

C风格struct使用带功能指针的全局static

您可以使用namespace关键字指定名称为文件范围的函数,然后将其分配给全局变量,以提供类似于C ++的typedef struct { int (*func1) (int a, int b); // pointer to function that returns an int char *(*func2) (int a, int b, char *c); // pointer to function that returns a pointer } FuncThings; extern const FuncThings FuncThingsGlobal; 功能的方法。

在头文件中定义将作为我们的命名空间的结构以及使用它的全局变量。

#include "header.h"

// the function names used with these static functions do not need to be the
// same as the struct member names. It's just helpful if they are when trying
// to search for them.
// the static keyword ensures these names are file scope only and not visible
// outside of the file.
static int func1 (int a, int b)
{
    return a + b;
}

static char *func2 (int a, int b, char *c)
{
    c[0] = a % 100; c[1] = b % 50;
    return c;
}

const FuncThings FuncThingsGlobal = {func1, func2};

然后在C源文件中:

const

然后通过指定全局结构变量的完整名称和成员名称来访问该函数。 int abcd = FuncThingsGlobal.func1 (a, b); 修饰符用于全局,因此不会意外更改。

namespace

功能指针的应用领域

DLL库组件可以执行类似于C风格的struct方法,其中从库接口中的工厂方法请求特定库接口,该接口支持创建包含函数指针的typedef struct { HMODULE hModule; int (*Func1)(); int (*Func2)(); int(*Func3)(int a, int b); } LibraryFuncStruct; int LoadLibraryFunc LPCTSTR dllFileName, LibraryFuncStruct *pStruct) { int retStatus = 0; // default is an error detected pStruct->hModule = LoadLibrary (dllFileName); if (pStruct->hModule) { pStruct->Func1 = (int (*)()) GetProcAddress (pStruct->hModule, "Func1"); pStruct->Func2 = (int (*)()) GetProcAddress (pStruct->hModule, "Func2"); pStruct->Func3 = (int (*)(int a, int b)) GetProcAddress(pStruct->hModule, "Func3"); retStatus = 1; } return retStatus; } void FreeLibraryFunc (LibraryFuncStruct *pStruct) { if (pStruct->hModule) FreeLibrary (pStruct->hModule); pStruct->hModule = 0; } 。此库接口加载所请求的DLL版本,使用必要的函数指针创建一个结构,然后将结构返回给请求调用者以供使用。

LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
//  ....
myLib.Func1();
//  ....
FreeLibraryFunc (&myLib);

这可以用作:

qsort()

可以使用相同的方法为使用底层硬件的特定模型的代码定义抽象硬件层。函数指针由工厂填充硬件特定功能,以提供实现抽象硬件模型中指定的功能的硬件特定功能。这可以用于提供由软件使用的抽象硬件层,其调用工厂函数以获得特定硬件功能接口,然后使用提供的函数指针来执行底层硬件的动作,而无需知道关于特定目标的实现细节。 。

用于创建委托,处理程序和回调的函数指针

您可以使用函数指针作为委派某些任务或功能的方法。 C中的经典示例是与标准C库函数bsearch()void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) { unsigned char *pList = pArray; unsigned char *pListEnd = pList + nItems * sizeItem; for ( ; pList < pListEnd; pList += sizeItem) { p (pList); } return pArray; } int pIncrement(int *pI) { (*pI)++; return 1; } void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) { unsigned char *pList = pArray; unsigned char *pListEnd = pList + nItems * sizeItem; for (; pList < pListEnd; pList += sizeItem) { p(pList, pResult); } return pArray; } int pSummation(int *pI, int *pSum) { (*pSum) += *pI; return 1; } // source code and then lets use our function. int intList[30] = { 0 }, iSum = 0; ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement); ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation); 一起使用的比较委托函数指针,用于提供排序项目列表或对排序的项目列表执行二进制搜索的排序顺序。比较函数委托指定排序或二进制搜索中使用的排序规则算法。

另一种用法类似于将算法应用于C ++标准模板库容器。

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

另一个例子是GUI源代码,其中通过提供在事件发生时实际调用的函数指针来注册特定事件的处理程序。带有消息映射的Microsoft MFC框架使用类似的方法来处理传递给窗口或线程的Windows消息。

需要回调的异步函数类似于事件处理程序。异步函数的用户调用异步函数来启动某个操作,并提供一个函数指针,异步函数将在操作完成后调用该函数指针。在这种情况下,事件是完成其任务的异步函数。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.