C数组结构体函数指针

3
如何使用结构体表创建函数指针,例如:
static struct {
  int pid;
  int queue[MAXPROCS];
} semtab[MAXSEMS];

我认为我理解了如何使用函数指针在C中实现面向对象,参考这篇文章。但是当我的结构体是一个数组时,我该怎么做呢?我对语法还有一点困惑。
是不是像这样:
```c struct MyStruct arr[10]; void (*func_ptr)(int) = &MyFunc; for(int i=0; i<10; i++) { arr[i].func = func_ptr; } ```
static struct {
  int pid;
  int queue[MAXPROCS];

  void (*fncPtr_enqueue)(int) = enqueue;
                 // or is it void(*enqueue)(semtable[]*) ?
  int (*fcnPtr_dequeue)() = dequeue;
} semtab[MAXSEMS];

void enqueue(int i) { /* code */ }
int dequeue() { /* code */ }


// then to use it, it would be like this?
void foo() {
  semtab[5].enqueue(6);
}

我不确定你真正想问什么,能否请你澄清一下你的问题? - Morten Jensen
你必须在使用函数之前声明它们。你不能在结构体定义的主体中创建初始化器。在标准C中,你不能在初始化器上使用重复计数,这是一个麻烦。GCC提供了一个非标准扩展来支持重复的初始化器。 - Jonathan Leffler
1
对于不带参数的函数,应该使用(void);在 C 中,空的参数列表不形成原型,这意味着任何数量的参数都可以匹配(并在运行时导致未定义的行为)。 - M.M
在C语言中这样做没有意义,除非你计划在某个时候分配不同的函数指针。现在你只是为了没有理由创建了一个额外的间接层。此外,请注意你的enqueue和dequeue函数需要带上额外的参数,因为在C语言中没有隐式的“self”或“this”参数。 - JS1
3个回答

1

使用

static struct {
  int pid;
  int queue[MAXPROCS];

  void (*fncPtr_enqueue)(int); // This defines a member fncPtr_enqueue
  int (*fncPtr_dequeue)();     // Note that you had fcnPtr_ in your post.
                               // I have fncPtr_ here.
} semtab[MAXSEMS];

void enqueue(int i) { /* code */ }
int dequeue() { /* code */ }

需要具有有效函数指针的semtab中的每个对象都需要更新。

semtab[0].fncPtr_enqueue = enqueue;
semtab[0].fncPtr_dequeue = dequeue;

这有点像新手问题。比如我想要enqueue(5),那么queue[tail++] = input。当我调用函数semtab[5].enqueue(5)时,那个队列就是semtab[5]的队列,对吗? - Sugihara
1
enqueue() 一些关于你想要使用哪个队列的具体信息可能是一个好主意。现在它没有任何想法。你需要显式地传递一个指向 semtab[5]queue 指针,或者一个指向 semtab[5] 本身的指针。 - Crowman
是的,我想我会传递另一个参数并执行类似于 semtab[s].queue[semtab[s].tail++] = input 的操作。我不确定为什么,但编译器抱怨它不知道变量 queuetail 是什么。 - Sugihara
@Jack:嗯,C不是C++,所以你不能直接从enqueue()函数内部引用成员变量,例如queuepid。你总是需要通过指针来访问它们。在C++成员函数调用中有一个隐式的this参数,但是C没有这个概念,所以你总是需要显式地提供它。 - Crowman
换句话说,你需要(1)给你的结构体命名,例如 struct queue_struct { ...};(2)将 enqueue 的签名更改为 void enqueue(struct queue_struct * this, int i);;(3)像这样调用它 semtab[5].enqueue(semtab[5], 6);;(4)在该函数中,执行类似于 this->queue[this->tail++] = i; 的操作。 - Crowman

1
你可以使用:

static struct
{
    int pid;
    int queue[MAXPROCS];
    void (*enqueue)(int);
    int (*dequeue)(void);
} semtab[MAXSEMS];

void enqueue(int i) { /* code */ }
int dequeue(void) { /* code */ }

void foo(void)
{
    semtab[5].enqueue(6);
}

更改包括:
  1. 结构体成员指针的系统命名(而不是混合使用 fncPtrfcnPtr 前缀)。
  2. 在结构体定义中不尝试初始化。
  3. 在函数原型中添加 void 表示没有参数。在 C 中(与 C++ 相反),空括号对表示“一个带有未定义数量参数的函数,但不是具有 ... 省略号的可变参数列表的函数”。
  4. 由于(1),原始调用是可以的。(使用原始代码,你需要 semtab[5].fncPtr_enqueue(6); - 或者甚至是 (*semtab[5].fncPtr_enqueue)(6);

您仍然需要确保表格中的函数指针都已初始化。

使用 GCC 和 C99 或 C11 编译,您可以使用以下方式初始化数组:

static struct
{
    int pid;
    int queue[MAXPROCS];
    void (*enqueue)(int);
    int (*dequeue)(void);
} semtab[MAXSEMS] =
{
    [0 ... MAXSEMS-1] = { .enqueue = enqueue, .dequeue = dequeue }
};

"[0 ... MAXSEMS-1]" 部分是GCC的扩展。请注意,在 0 后面需要加一个空格以避免与“最大匹配”规则产生问题。

1
如JS1在评论中提到的那样,使用你所拥有的示例做这个是相当无意义的,因为如果你不改变那些指针的值,通过间接寻址不会实现任何东西。
话虽如此,这里有一个使用栈的示例(因为逻辑比队列更容易,这是一个简单的示例)。请注意,您必须向每个成员函数传递一个指向堆栈的指针,因为虽然C++成员函数具有隐式的“this”参数,但C函数从来没有。您还需要为您的“struct”命名,否则您将无法在抽象中引用它,这是您需要做的。
该程序使用相同的“struct”来实现两种变体的堆栈,一种是正常的堆栈,另一种则在您推送或弹出时不必要地向您大喊大叫。
#include <stdio.h>
#include <stdlib.h>

enum {
    STACKSIZE = 1024
};

struct stack {
    int stack[STACKSIZE];
    size_t top;
    void (*push)(struct stack *, int);
    int (*pop)(struct stack *);
    void (*destroy)(struct stack *);
};

void stack_push(struct stack * this, int i)
{
    if ( this->top == STACKSIZE ) {
        fprintf(stderr, "Queue full!\n");
        exit(EXIT_FAILURE);
    }

    this->stack[this->top++] = i;
}

void stack_push_verbose(struct stack * this, int i)
{
    stack_push(this, i);
    printf("** PUSHING %d ONTO STACK! **\n", i);
}

int stack_pop(struct stack * this)
{
    if ( this->top == 0 ) {
        fprintf(stderr, "Stack empty!\n");
        exit(EXIT_FAILURE);
    }

    return this->stack[--this->top];
}

int stack_pop_verbose(struct stack * this)
{
    const int n = stack_pop(this);
    printf("** POPPING %d FROM STACK! **\n", n);
    return n;
}

void stack_destroy(struct stack * this)
{
    free(this);
}

struct stack * stack_create(void)
{
    struct stack * new_stack = malloc(sizeof * new_stack);
    if ( !new_stack ) {
        perror("Couldn't allocate memory");
        exit(EXIT_FAILURE);
    }

    new_stack->top = 0;
    new_stack->push = stack_push;
    new_stack->pop = stack_pop;
    new_stack->destroy = stack_destroy;

    return new_stack;
}

struct stack * stack_verbose_create(void)
{
    struct stack * new_stack = stack_create();
    new_stack->push = stack_push_verbose;
    new_stack->pop = stack_pop_verbose;

    return new_stack;
}

int main(void)
{
    struct stack * stack1 = stack_create();
    struct stack * stack2 = stack_verbose_create();

    stack1->push(stack1, 4);
    stack1->push(stack1, 3);
    stack1->push(stack1, 2);

    printf("Popped from stack1: %d\n", stack1->pop(stack1));

    stack2->push(stack2, 5);
    stack2->push(stack2, 6);

    printf("Popped from stack2: %d\n", stack2->pop(stack2));
    printf("Popped from stack1: %d\n", stack1->pop(stack1));
    printf("Popped from stack1: %d\n", stack1->pop(stack1));
    printf("Popped from stack2: %d\n", stack2->pop(stack2));

    stack1->destroy(stack1);
    stack2->destroy(stack2);

    return 0;
}

带有输出的:
paul@horus:~/src/sandbox$ ./stack
Popped from stack1: 2
** PUSHING 5 ONTO STACK! **
** PUSHING 6 ONTO STACK! **
** POPPING 6 FROM STACK! **
Popped from stack2: 6
Popped from stack1: 3
Popped from stack1: 4
** POPPING 5 FROM STACK! **
Popped from stack2: 5
paul@horus:~/src/sandbox$ 

请注意,我们对两种类型的堆栈使用完全相同的struct stack - 它们之间的差异是通过在每种情况下将函数指针指向不同的函数来实现的。对用户而言唯一可见的区别是一个使用stack_create()创建,另一个使用stack_create_verbose()创建。在所有其他方面,它们被完全相同地使用,因此您可以看到多态性的作用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接