C语言中的函数指针是如何工作的?
C语言中的函数指针是如何工作的?
我最近在C语言中使用了一些函数指针的经验。
因此,继续回答自己的问题的传统,我决定为那些需要快速入门该主题的人制作一个小总结。
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; }
其中一个可以注意到的事情是,不存在对象的实例以及实际上是对象一部分的方法的概念,因此必须在每次调用时传入一个“self object”(自身对象)。 (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
对象base
变量来引用String.get
和String.length
方法。
通过函数指针的使用,可以实现从超类继承方法的功能。
我们可以继续了解C中的多态。
例如,如果我们想要由于某种原因在ImmutableString
类中始终返回 0
的长度,我们只需要:
- 添加一个将作为覆盖
length
方法的功能。 - 进入“构造函数”,将函数指针设置为覆盖
length
方法。
通过添加lengthOverrideMethod
,可以在ImmutableString
中添加一个覆盖length
方法:
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; }
现在,与String
类中length
方法的完全相同的行为不同,在ImmutableString
类中的length
方法将参考在lengthOverrideMethod
函数中定义的行为。
我必须声明我仍然在学习如何用面向对象的编程风格在C中编写,所以可能有些点我没有解释好,或者可能在如何最好地实现C中的OOP方面有些离谱。但我的目的是试图说明函数指针的许多用途之一。
有关如何在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; }