我最近在C中使用了函数指针。
继续回答你自己的问题的传统,我决定对那些需要快速深入研究这个主题的人进行一些基本的总结。
我最近在C中使用了函数指针。
继续回答你自己的问题的传统,我决定对那些需要快速深入研究这个主题的人进行一些基本的总结。
让我们从一个基本功能开始吧 指向:
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;
}
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
class实际上是声明函数的函数指针。在准备实例时 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.但我的目的是试图说明函数指针的许多用法之一。
有关如何在C中执行面向对象编程的更多信息,请参阅以下问题:
被触发的指南:如何通过手动编译代码来滥用x86机器上的GCC中的函数指针:
返回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);
我最喜欢的函数指针之一是使用廉价且简单的迭代器 -
#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);
}
拥有基本声明符后,函数指针变得易于声明:
ID
: ID是一个*D
: D指针D(<parameters>)
: 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参数,因为它很简单。通过替换添加指向声明符的指针 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>)
然后接下来就是 功能 <
参数>
回国。参数是一个简单的char,所以我们马上把它放进去,因为它真的很简单。
(*ID0(<parameters>))(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内部装置。随意编辑/修复其中的内容。
当您在不同时间或不同开发阶段需要不同功能时,它们非常便于使用。例如,我正在一台带有控制台的主机上开发应用程序,但该软件的最终版本将放在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;
}