我最近对C中的函数指针有一些经验。
因此,继续回答您自己的问题的传统,我决定为需要快速深入学习该主题的人做一些基本的总结。
我最近对C中的函数指针有一些经验。
因此,继续回答您自己的问题的传统,我决定为需要快速深入学习该主题的人做一些基本的总结。
Answers:
让我们从一个基本功能开始,我们将指向:
int addInt(int n, int m) {
return n+m;
}
首先,让我们定义一个指向接收2 int
s并返回的函数的指针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;
}
pshufb
,它也可以执行复杂的逻辑来确定是否正确,因为它很慢,因此较早的实现仍然更快。x264 / x265广泛使用此功能,并且是开源的。
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
方法将不可访问,同时保持对get
和的访问length
,并强制“构造函数”接受一个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
,指向get
和length
方法的函数指针实际上通过遍历内部存储的变量来引用String.get
和String.length
方法。base
String
使用函数指针可以实现从超类继承方法。
我们可以进一步在C中继续多态性。
例如,如果由于某种原因我们想要更改length
方法的行为以使其0
始终返回类中的所有时间,则ImmutableString
要做的就是:
length
方法的函数。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
功能。
我必须添加一个免责声明,即我仍在学习如何使用C进行面向对象的编程风格进行编写,因此可能有些地方我并没有很好地解释,或者就如何最好地实现OOP而言可能已超出标准但是我的目的是试图说明函数指针的许多用法之一。
有关如何在C中执行面向对象编程的更多信息,请参考以下问题:
ClassName_methodName
函数命名约定。只有这样,您才能获得与C ++和Pascal相同的运行时和存储成本。
被解雇的指南:如何通过手动编译代码来滥用x86机器上GCC中的函数指针:
这些字符串文字是32位x86机器代码的字节。 0xC3
是x86 ret
指令。
通常,您不会手动编写这些代码,而是使用汇编语言编写代码,然后使用汇编程序nasm
将其汇编为平面二进制文件,然后将其十六进制转储为C字符串文字。
返回EAX寄存器上的当前值
int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
编写交换功能
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);
写一个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
您甚至可以编写一个递归函数,计数为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);
请注意,编译器将字符串文字放在该.rodata
部分(或.rdata
在Windows中)中,该部分作为文本段的一部分链接(以及功能代码)。
文本段具有读+ Exec的权限,所以铸造字符串文字函数指针,而无需工作mprotect()
或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
,我们可以得到汇编,并发现此代码通过破坏EBX(保留调用的寄存器)违反了ABI,通常效率低下。
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
该机器码(可能)将在Windows,Linux,OS X等上以32位代码运行:所有这些OS上的默认调用约定都将args传递给堆栈,而不是更有效地在寄存器中传递。但是EBX在所有正常的呼叫约定中都保留了呼叫,因此将其用作暂存寄存器而不进行保存/恢复会很容易使呼叫者崩溃。
我最喜欢的函数指针用法之一是便宜又简单的迭代器-
#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);
}
int (*cb)(void *arg, ...)
。迭代器的返回值还让我提前停止(如果非零)。
一旦有了基本的声明器,函数指针就变得易于声明:
ID
::ID是一个*D
::D指针D(<parameters>)
:d功能拍摄<
参数,>
返回D是使用相同规则构建的另一个声明符。最后,它以某处结尾ID
(请参见下面的示例),这是已声明实体的名称。让我们尝试构建一个函数,该函数使用指向不带任何内容并返回int的函数的指针,并返回指向带char并返回int的函数的指针。使用type-def就是这样
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参数,因为它很简单。通过替换D1
为添加声明符的指针*D2
。请注意,我们必须在周围加上括号*D2
。通过查找*-operator
和函数调用运算符的优先级可以知道这一点()
。没有括号,编译器会将其读取为*(D2(char p))
。但是,那当然不再是D1的简单替代*D2
。声明符周围总是允许有括号。因此,实际上,如果添加太多,就不会出错。
(*D2)(char);
返回类型完成!现在,让我们来取代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
在该示例中,我称为函数的标识符。
它从类型描述的最左边的标识符开始,在我们通过右边的方式包装该声明符。从函数返回参数开始<
>
ID0(<parameters>)
描述中的下一件事(“返回”之后)是的指针。让我们合并一下:
*ID0(<parameters>)
然后,接下来是functon接受<
参数>
返回。该参数是一个简单的char,因此我们马上将其重新放置,因为它确实很简单。
(*ID0(<parameters>))(char)
请注意,我们添加了括号,因为我们想再次的*
结合第一,而随后的(char)
。否则,它会读取功能以<
参数>
返回函数...。是的,甚至不允许返回函数。
现在我们只需要放入<
参数>
。我将展示派生的简短版本,因为我认为您现在已经知道如何执行此操作。
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内部构件。随时编辑/修复其中的内容。
当您在不同的时间或不同的开发阶段需要不同的功能时,它们非常方便使用。例如,我正在具有控制台的主机上开发应用程序,但是该软件的最终版本将放在Avnet ZedBoard上(该端口具有用于显示和控制台的端口,但是不需要/不需要它们用于)。最终版本)。因此,在开发过程中,我将使用它printf
来查看状态和错误消息,但完成后,我什么都不想打印了。这是我所做的:
// 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.c
我将定义2个函数原型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;
}
注意函数指针是如何在原型version.h
作为
void (* zprintf)(const char *, ...);
当在应用程序中引用它时,它将在其指向的任何位置(尚未定义)开始执行。
在中version.c
,请注意,在board_init()
函数中,zprintf
根据在中定义的版本,为该函数分配了一个唯一功能(其功能签名匹配)。version.h
zprintf = &printf;
zprintf调用printf进行调试
要么
zprintf = &noprint;
zprintf只是返回而不会运行不必要的代码
运行代码如下所示:
#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
在调试模式下使用,或者在释放模式下不执行任何操作。这比遍历整个项目并注释掉或删除代码要容易得多。我需要做的就是更改版本,version.h
其余代码将由代码完成!
函数指针通常由定义typedef
,并用作参数和返回值。
上面的答案已经解释了很多,我只举一个完整的例子:
#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 ∑
}
// 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;
}
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,如果函数具有内存地址,则可以由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包含函数的所需地址。
函数指针是包含函数地址的变量。由于它是一个指针变量,但是具有某些受限制的属性,因此您可以像使用数据结构中的任何其他指针变量一样使用它。
我能想到的唯一例外是将函数指针视为指向单个值以外的其他东西。通过递增或递减函数指针或对函数指针增加/减去偏移量来进行指针算术并不是真正的实用程序,因为函数指针仅指向单个对象,即函数的入口点。
函数指针变量的大小,该变量占用的字节数可能会根据基础架构(例如x32或x64或其他)而有所不同。
函数指针变量的声明需要指定与函数声明相同的信息,以便C编译器执行通常执行的检查。如果在函数指针的声明/定义中未指定参数列表,则C编译器将无法检查参数的使用。在某些情况下,缺乏检查很有用,但是请记住,安全网已被移除。
一些例子:
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.
前两个声明的相似之处在于:
func
是一个接受a int
和a char *
并返回an 的函数int
pFunc
是一个函数指针,分配给该函数的函数的地址是a int
和a char *
并返回aint
因此从上面我们可以得到一个源代码行,其中函数的地址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, ...);
或根本无法指定参数列表。这可能很有用,但是它消除了C编译器对提供的参数列表执行检查的机会。
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风格演员表
您可以将C样式强制转换与函数指针一起使用。但是请注意,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.
比较功能指针与相等性
您可以使用一条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
这并不意味着没有参数,只是未指定),如下所示:可能会从C编译器看到警告。这也适用于函数的函数指针参数:
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
使用Globalstruct
您可以使用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;
然后在C源文件中:
#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};
然后,通过指定全局struct变量的完整名称和成员名称来使用该名称,以访问该函数。该const
改性剂的全球化,所以它不能被意外的改变。
int abcd = FuncThingsGlobal.func1 (a, b);
功能指针的应用领域
DLL库组件可以执行与C样式namespace
方法类似的操作,在C 方法中,从支持创建struct
包含函数指针的库接口中的工厂方法中请求特定的库接口。此库接口加载所请求的DLL版本,具有必要函数指针的结构,然后将该结构返回给发出请求的调用方以供使用。
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;
}
这可以用于:
LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
// ....
myLib.Func1();
// ....
FreeLibraryFunc (&myLib);
可以使用相同的方法为使用底层硬件的特定模型的代码定义抽象硬件层。工厂用功能特定的函数填充功能指针,以提供实现特定抽象硬件模型中指定功能的功能。这可以用来提供由软件使用的抽象硬件层,该软件调用工厂函数以获取特定的硬件功能接口,然后使用提供的功能指针对基础硬件执行操作,而无需了解有关特定目标的实现细节。
用于创建委托,处理程序和回调的函数指针
您可以使用函数指针来委派某些任务或功能。在C中的典型的例子是与标准C库函数使用的比较委托函数指针qsort()
和bsearch()
用于排序的项目的列表或执行过的项目的排序列表二进制搜索提供归类顺序。比较函数委托指定排序或二进制搜索中使用的排序规则算法。
另一个用途类似于将算法应用于C ++标准模板库容器。
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);
另一个示例是GUI源代码,其中通过提供一个函数指针来注册特定事件的处理程序,该函数指针在事件发生时实际被调用。Microsoft MFC框架及其消息映射使用类似的方法来处理传递到窗口或线程的Windows消息。
需要回调的异步函数类似于事件处理程序。异步函数的用户调用异步函数以启动某些操作,并提供一个函数指针,一旦操作完成,异步函数将调用该函数指针。在这种情况下,事件是异步功能完成其任务。