设想这样一种情形,假如我们需要维护一个单链表,链表的每个结点中包括两个有效元素:一个函数指针,一个指向下一个结点的指针。要求是,这个函数指针所指向函数的参数个数是不同的。如何处理?
1. 按键常规的思维,或许你会定义类似下面结构体的数据结构:
struct node {
void (*pfunc0)(void);
void (*pfunc1)(int);
void (*pfunc2)(int,int);
struct node* next;
}
pfunc0 指向的函数不带参数, pfunc1 指向的函数带一个参数。在初始化的时候将该结点的函数指针部分进行赋值,如果该结点的函数指针指向一个不带参数的函数,则将 pfunc0 赋值为一个不带参数的函数,而将 pfunc1 赋值为 null ,同理,当该结点的函数指针指向一个带一个参数的函数时,则将 pfunc1 赋值为一个带一个参数的函数,而将 pfunc0 赋值为 null ,如此往下。这样在程序遍历链表时,在某个节点处直接执行其函数指针不为 null 的那个函数即可,函数参数由遍历链表的函数传递。
这样的做法有些问题。如果结点中的函数指针需要指向带更多参数的函数,那这里就要往结点的数据结构中加入更多的函数指针成员,但有效的函数指针又只有一个,这样带来的结构性开销会很大,对于嵌入式系统而言将是很大的浪费,当然如果你是 PC 甚至是大型机 RAM 大到令人发指那就不必考虑了。 ^_^
2. 或许你想到类似下面的方式:
#include <stdio.h>
struct node0 {
void (*pfunc)(void);
struct node2* next;
};
struct node1 {
void (*pfunc)(int);
struct node2* next;
};
struct node2 {
void (*pfunc)(int,int);
struct node2* next;
};
void func0(void) {
printf("function call without parameter/n");
}
void func1(int p) {
printf("function call with 1 parameter,/nthe parameter is: %d/n",p);
}
void func2(int p1,int p2) {
printf("function call with 2 parameters,/nthe parameters are: %d,%d/n",p1,p2);
}
int main(void) {
struct node0 mynode0;
struct node1 mynode1;
struct node2 mynode2;
mynode0.pfunc = func0;
mynode0.next = 0;
mynode1.pfunc = func1;
mynode1.next = (struct node2*)&mynode0;
mynode2.pfunc = func2;
mynode2.next = (struct node2*)&mynode1;
mynode2.pfunc(1,2);//mynode2 就是链表头
mynode2.next->pfunc(1,2);
mynode2.next->next->pfunc(1,2);
return 0;
}
两个结点数据结构的 RAM 开销是相同的,一个函数指针 ( 通常是 4 个字节 ) ,一个指向下一个结点的指针 ( 通常是 4 个字节 ) 。
可以发现上面的函数在调用 func0 , func1 , func2 的时候都需要传递参数,因为链表中指向下一个结点的指针类型都是 struct node2 ,编译器会强制所有的函数都被强制转化成有两个输入函数的定义形式。
这样的做法仍然有些不妥。
第一,如果要在里面另加一个结点,例如叫 struct node3 ,该结点中的函数指针指向的函数的个数为 3 个,这样一来就需要修改 struct node0 , struct node1 , struct node2 中的指向下一个结点的指针的类型,否则编译会出错。
第二,难道你不会对给一个不需要接受任何参数或者接受的参数个数并没有所传递的参数那么多的函数传递如此多的参数这种情况感到别扭吗?
第三,传参会浪费栈的,如果你不小心嵌套了很多层,栈会溢出的。
3. 或许你想到了用可变参数形式的函数调用:
#include <stdio.h>
struct node {
void (*pfunc)(int,...);
struct node* next;
};
void func1(int p) {
printf("call function with 1 parameter: %d/n",p);
}
void func2(int p1,int p2) {
printf("call function with 2 parameters: %d,%d/n",p1,p2);
}
int main (void) {
struct node mynode1,mynode2;
mynode1.pfunc = func1;
mynode1.next = 0;
mynode2.pfunc = func2;
mynode2.next = &mynode1;
(*mynode2.pfunc)(1,2);
(*mynode2.next->pfunc)(1);
return 0;
}
这样的作法可以说已经很好了,但还是有些不妥。可变参数的函数形式要求使用到的函数至少带有一个参数,对于不带参数的函数就不能用这种方式了。
4. 终于要讲到我今天想到的一种方法了:
#include <stdio.h>
struct node {
int para_num;
long func_addr;
struct node* next;
};
void func0() {
printf("call function without parameter./n");
}
void func1(int p) {
printf("call function with 1 parameter: %d./n",p);
}
void func2(int p1,int p2) {
printf("call function with 2 parameters: %d,%d./n",p1,p2);
}
void caller0(struct node *p) {
while(p) {
if(p->para_num == 0) {
((void(*)(void))p->func_addr)();
}
if(p->next != 0) {
p = p->next;
}
else
return;
}
}
void caller1(struct node *p) {
while(p) {
if(p->para_num == 1) {
((void(*)(int))p->func_addr)(1);
}
if(p->next != 0) {
p = p->next;
}
else
return;
}
}
void caller2(struct node *p) {
while(p) {
if(p->para_num == 2) {
((void(*)(int,int))p->func_addr)(1,2);
}
if(p->next != 0) {
p = p->next;
}
else
return;
}
}
int main(void) {
struct node mynode0,mynode1,mynode2,header;
mynode0.para_num = 0;
mynode0.func_addr = (long)func0;
mynode0.next = 0;
mynode1.para_num = 1;
mynode1.func_addr = (long)func1;
mynode1.next = &mynode0;
mynode2.para_num = 2;
mynode2.func_addr = (long)func2;
mynode2.next = &mynode1;
header.para_num = -1;
header.func_addr = 0;
header.next = &mynode2;
caller0(&header);
caller1(&header);
caller2(&header);
//((void (*)(int,int))mynode2.func_addr)(1,2);
//((void (*)(int))mynode2.next->func_addr)(1);
return 0;
}
结构体中的 para_num 域记录指向函数接收的参数个数, func_addr 为所指向函数的地址,注意这里并不是用的函数指针。由函数指针的调用者去负责将其进行强制转换及参数传递。
这种作法的明显缺点目前还没有想到 ^_^ ,呵呵……其实还是有的,用这种方式使限制函数参数,如果你参数不是你的强制转换时用的参数类型,在执行该函数时从栈中取数据会发生错误。
不过我同事倒是对我的算法很不屑,觉得效率很低,其实这只是一种演示,如果真的需要提高效率那就为每个 caller 函数维护一个链表咯,事实上,在我们的项目我正是这样做的。
之所以会想到这些,是因为最近的嵌入式项目需要做一些比较友好的接口给驱动程序开发人员使用。