具有不同数据类型字段的相同数据结构机制(C)

fnx2tebb  于 2023-10-16  发布在  其他
关注(0)|答案(3)|浏览(146)

假设我想用C实现一个堆栈,它有一些不同的特性,比如当它以某种方式扩展时,决定如何重新分配,等等。比如说

typedef struct stack_int {
    unsigned length;
    unsigned allocated_length;
    int *data_pointer;
} stack_int;

extern int stack_int_pop(stack *l);

但是在我的程序中,我想重用它,上面的int被我自己的5个数据结构(我使用的一些结构)所取代。在所有情况下,我希望细节都是一样的。

问题:什么是“正确的”,或“惯用的C”,这样做的方式?

我可以想到各种各样的循环,比如制作一个通用堆栈,带有一个字段element_size,但是在弹出和追加时,接口必然是丑陋的,而且据我所知也没有优化。我还发现了一个环形交叉口,

#define CONCAT_PRE(X, Y) X ## _ ## Y
#define CONCAT(X, Y) CONCAT_PRE(X, Y)
#define STACK_TYPE CONCAT(stack, STACK_ELEMENT_TYPE)

typedef struct STACK_TYPE {
    unsigned length;
    unsigned allocated_length;
    STACK_ELEMENT_TYPE *data_pointer;
} STACK_TYPE;

STACK_ELEMENT_TYPE CONCAT(STACK_TYPE, pop)(stack *l) {
...
}

然后把它包括几次:

#define STACK_ELEMENT_TYPE int
#include "stack.c"
#define STACK_ELEMENT_TYPE my_struct
#include "stack.c"

但这似乎不像是一个“惯用的C”的方式,是非常混乱的人谁会读它,防止一个单一的头文件,等等等等.

ryhaxcpt

ryhaxcpt1#

我不得不承认,我不完全确定这里“最惯用”的事情是什么,但它肯定不会多次包含相同的头,请不要这样做,因为它可能会创建依赖循环,如果你使用比Editor.exe更好的IDE,你应该在.h文件中已经有一个包含保护。
然而,对于你的问题,我认为应该使用一个**(void *)**和一个由无符号字符组成的类型字段,至少这是Java对其数据结构进行分类的方式。此外,您只需定义与您自己的结构相关的数字。
当然,当你已经有了指向你提到的结构的指针时,这个方法特别有用。
如果您不热衷于使用这种方法(诚然有点笨拙),那么始终欢迎您创建并发存在的多个堆栈。

6ojccjat

6ojccjat2#

类似这样的东西,使用void*传递指针,使用unsigned char *进行地址计算。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
struct VoidStack {
    unsigned used_length;
    unsigned allocated_length;
    unsigned elem_bytes;
    void *data;
};
 
void *stack_pop(struct VoidStack *s) {
    return (unsigned char *)s->data + (--s->used_length * s->elem_bytes);
}
 
void stack_push(struct VoidStack *s, void *src) {
    if (s->used_length == s->allocated_length) {
        // grow data and update allocated_length
    }
    memcpy((unsigned char *)s->data + (s->used_length++ * s->elem_bytes),
          src, s->elem_bytes);
}
 
// etc
 
int main(void) {
    struct VoidStack intStack;
    intStack.used_length = 0;
    intStack.allocated_length = 4;
    intStack.elem_bytes = sizeof (int);
    intStack.data = malloc(4 * intStack.elem_bytes);
    int foo = 42, bar = -1, baz = 0, quux = 2023;
    stack_push(&intStack, &foo);
    stack_push(&intStack, &bar);
    stack_push(&intStack, &baz);
    stack_push(&intStack, &quux);
    printf("unstack: %d", *(int*)stack_pop(&intStack));
    printf(" %d", *(int*)stack_pop(&intStack));
    printf(" %d", *(int*)stack_pop(&intStack));
    printf(" %d\n", *(int*)stack_pop(&intStack));
    free(intStack.data);
    return 0;
}

参见在https://ideone.com/4HRYn5上运行的代码(使用xx而不是intStack,抱歉)

neskvpey

neskvpey3#

下面是实现通用堆栈机制并使用宏示例化结构和方法的经典方法。它很容易扩展到其他标量或非标量类型,并允许在宏生成的代码不足的情况下实现变化。

stack.h中的声明:

#define DEFINE_STACK_TYPE(T, name)  \
typedef struct name {   \
    int pos;            \
    int size;           \
    T *data;            \
} name

DEFINE_STACK_TYPE(int, stack_int);
DEFINE_STACK_TYPE(double, stack_double);
DEFINE_STACK_TYPE(char *, stack_string);

/* pop an element from the stack, return 0 if stack is empty */
extern int stack_int_pop(stack_int *s);
extern double stack_double_pop(stack_double *s);
extern char *stack_string_pop(stack_string *s);

/* push an element on the stack, return the index or -1 if allocation failed */
extern int stack_int_push(stack_int *s, int val);
extern int stack_double_push(stack_double *s, double val);
extern int stack_string_push(stack_string *s, char *val);

#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 201112L
/* generic interface using _Generic() */
#define stack_push(s, val)  _Generic(s,         \
    stack_int *:    stack_int_push(s, val),     \
    stack_double *: stack_double_push(s, val),  \
    stack_string *: stack_string_push(s, val),  \
)
#define stack_pop(s)  _Generic(s,           \
    stack_int *:    stack_int_pop(s),       \
    stack_double *: stack_double_pop(s),    \
    stack_string *: stack_string_pop(s),    \
)
#endif

stack.c中的定义:

#include <stdlib.h>
#include "stack.h"

#define DEFINE_STACK_POP(T, stype, fname)       \
T fname(stype *s) {                             \
    return s->pos > 0 ? s->data[--s->pos] : 0;  \
}

#define DEFINE_STACK_PUSH(T, stype, fname)              \
int fname(stype *s, T val) {                            \
    T *data = s->data;                                  \
    while (s->pos >= s->size) {                         \
        int size = s->size + (s->size >> 1) + 32;       \
        data = realloc(data, sizeof(T) * size);         \
        if (data == NULL)                               \
            return -1;                                  \
        s->data = data;                                 \
        s->size = size;                                 \
    }                                                   \
    s->data[s->pos] = val;                              \
    return s->pos++;                                    \
}

DEFINE_STACK_POP(int, stack_int, stack_int_pop)
DEFINE_STACK_POP(double, stack_double, stack_double_pop)
DEFINE_STACK_POP(char *, stack_string, stack_string_pop)

DEFINE_STACK_PUSH(int, stack_int, stack_int_pop)
DEFINE_STACK_PUSH(double, stack_double, stack_double_pop)
DEFINE_STACK_PUSH(char *, stack_string, stack_string_pop)

可以使用宏来模拟参数化类型,并将DEFINE_STACK_TYPE声明替换为:

#define stack(T) \
struct {         \
    int pos;     \
    int size;    \
    T *data;     \
}

typedef stack(int) stack_int;
typedef stack(double) stack_double;
typedef stack(char *) stack_string;

但请注意,stack(int)只应用于定义typedef类型,因为如果在多个上下文中使用,它将定义不同的类型,例如int stack_int_pop(stack(int) *s);

相关问题