【问题标题】:Datatype independent stack - C Programming数据类型独立堆栈 - C 编程
【发布时间】:2011-07-01 05:00:38
【问题描述】:

C 中的堆栈通常取决于用于声明它们的数据类型。例如,

int arr[5]; //creates an integer array of size 5 for stack use
char arr[5]; //creates a character array of size 5 for stack use

都仅限于分别保存整数和字符数据类型,并假定程序员知道在运行时生成了什么数据。如果我想要一个可以容纳任何数据类型的堆栈怎么办?

我最初考虑将其作为一个联合来实施,但这种方法不仅困难而且存在缺陷。还有其他建议吗?

【问题讨论】:

  • 您确定没有将堆栈(一种数据结构)与分配在堆栈上的变量混淆吗?
  • 一点也不。我想要一个堆栈,我可以在不知道它的类型的情况下推送任何东西。
  • 你需要解释更多你想要什么。
  • 创建一个堆栈类型相当容易(虽然丑陋),该堆栈类型将处理相同类型的对象,而该类型直到运行时才知道。创建一个可以容纳多种类型的对象的堆栈有点困难,而且AFAIK这种结构没有好的用例。在这种情况下,您希望您的 pop 操作如何工作?
  • 嗯,一般的观点认为至少在 C 中实现这样的堆栈是相当困难的。让我看看我是否可以解决这个问题。谢谢大家!

标签: c types stack


【解决方案1】:

我会使用这样的结构:

struct THolder
{
   int dataType; // this is a value representing the type
   void *val; // this is the value 
};

然后使用THolder 的数组来存储您的值。

【讨论】:

  • 同样,这需要事先知道要推送的数据类型。
  • @check123:是的,但是您需要额外的数据,否则,当您访问数组时,您将不知道要取消引用。请记住:您说的是 C,而不是 C++。 C 中没有模板。没有继承(如果您愿意妥协,从一个基类继承所有类,这可能会有所帮助)。
  • @check123 你总是知道你推动的东西的类型。如果这些建议还不够充分,您需要更清楚地说明您的要求。
  • 如果做push的代码模块没有做popping,我怀疑程序设计有缺陷。
  • @Jim 我想你误解了我的意思。对于“代码模块”,我的意思是一堆属于一起的例程,即在没有 class 关键字的 C 中进行 OO 编程的方式。以你的解释器为例:你可以有一个模块处理用户输入和一个模块处理表达式评估。好的程序设计是让表达式评估模块同时处理推送和弹出,将堆栈对象保持为“私有”变量。糟糕的程序设计是拥有一个全局堆栈对象,然后让用户界面推送对象,而评估模块弹出它们。
【解决方案2】:

这实际上只是 Pablo Santa Cruz 答案的变体,但我认为它看起来更整洁:

typedef enum { integer, real, other } type_t;

typedef struct {
    type_t type;
    union {
        int normal_int;     /* valid when type == integer */
        double large_float; /* valid when type == real */
        void * other;       /* valid when type == other */
    } content;
} stack_data_t;

您仍然需要使用某种方法来显式设置存储在每个元素中的数据类型,没有简单的方法。

您可以研究依赖于编译器的 typeof 关键字自动执行此操作的预处理器魔法,但这可能只会破坏可移植性。

【讨论】:

  • 这被称为“变体”。它们被称为对内存空间的严重浪费,没有明显的优势。
  • 以上都使用了结构和链表是一种内存浪费。类型选择器是一个可选字段,如果您警告每个元素有单个字节,请将其删除。这个答案是唯一正确的答案。
  • 下一步是创建一个由这些元素组成的块分配数组,其大小更接近内存分配单元大小(x86 上 4K 的倍数),所有数组块都链接在一个列表中——这就是经典的动态堆栈
  • 如果整个堆栈使用单个静态分配的连续内存区域,则此结构的优点是单个机器命令索引访问
【解决方案3】:

有人推荐了void* 成员。除了该解决方案之外,我还想提供一个替代方案(假设您的堆栈是堆分配结构的链表):

struct stack_node
{
   struct stack_node *next;
   char data[];
};

data[] 是 C99 构造。 data 必须是最后一个成员;这利用了我们可以在结构地址之后填充任意数量的事实。如果您使用的是非 C99 编译器,则可能需要做一些粗略的技巧,例如将其声明为 data[0]

然后你可以这样做:

struct stack_node*
allocate_stack_node(size_t extra_size)
{
   return malloc(sizeof(struct stack_node) + extra_size);
}

/* In some other function... */

struct stack_node *ptr = allocate_stack_node(sizeof(int));

int *p = (int*)ptr->data;

如果这看起来很丑陋和 hacky,它是......但是这里的优点是你仍然可以获得通用的优点而不引入更多的间接性(因此 ptr->data 的访问时间比 void* 指向与结构不同的位置。)

更新:我还想指出,如果您的机器对int 的对齐要求与char 的对齐要求不同,我提供的代码示例可能会出现问题。这是一个说明性的例子; YMMV。

【讨论】:

  • 您添加了动态内存分配,然后声称您的代码会更快?
  • @Lundin - 叹息。这符合 Stack Overflow 上人们过于从字面上接受建议的趋势。我只是想让提问者意识到可能性,而不是承诺在所有情况下哪个是最好的。注意我说:“假设您的堆栈是堆分配结构的链接列表”。 如果您已经在为您的结构进行堆分配,那么是的,这是更少的指针取消引用并且应该更快。但是那个“如果”可能不适合你的场景。
  • 很公平,但代码仍然很糟糕...一个结构可能包含任意数量的填充字节(在这种情况下,在大多数平台上它可能会)并且您的代码将覆盖这些填充字节与数据。即使这样的可疑代码不会导致程序崩溃,您仍然分配了比需要更多的数据。严格来说你应该分配 sizeof(stack_node*) + extra_size。
  • 或offsetof(struct stack_node, data) + extra_size。
  • 它是一个带有一个固定类型 char[] 元素的链表,而不是一个异构堆栈
【解决方案4】:

您可以使用宏和“容器”类型将“类型”从每个元素减少到整个容器。 (下面是C99代码)

#define GENERIC_STACK(name, type, typeid, elements) \
  struct name##_stack { \
    unsigned int TypeID; \
    type Data[elements]; \
  } name = { .TypeID = typeid }

当然,您的“TypeID”必须允许您期望的所有可能的约定类型;如果您打算使用整个结构或其他用户定义的类型,可能会出现问题。

我意识到为每个变量设置一个唯一命名的结构类型很奇怪,而且可能没有用...哎呀。

【讨论】:

  • 所以问题仍然悬而未决!
【解决方案5】:

我创建了一个适用于任何数据类型的库:

    List new_list(int,int);

创建新列表,例如:

    List list=new_list(TYPE_INT,sizeof(int));
    //This will create an list of integers
    Error append(List*,void*);

将一个元素附加到列表中。 *追加accpts两个指针作为参数,如果要存储指向列表的指针,请不要通过指针传递指针

例如:

    //using the int list from above

      int a=5;
      Error err;
      err=append(&list,&a)

      //for an list of pointers
      List listptr=new_list(TYPE_CUSTOM,sizeof(int*));
      int num=7;
      int *ptr=#

      append(&listptr,ptr);


      //for list of structs
      struct Foo
      {
        int num;
        float *ptr;
      };

      List list=new_list(TYPE_CUSTOM,sizeof(struct Foo));
      struct Foo x;
      x.num=9;
      x.ptr=NULL;

      append(&list,&x);

错误获取(List*,int);

获取指定索引处的数据。调用时列表的当前指针将指向数据。

例如:

    List list=new_list(TYPE_INT,sizeof(int));

    int i;
    for(i=1;i<=10;i++)
      append(&list,&i);

    //This will print the element at index 2
    get(&list,2);
    printf("%d",*(int*)list.current);

错误弹出(List*,int);

来自指定索引的弹出和元素

例如:

      List list=new_list(TYPE_INT,sizeof(int));

      int i;
      for(i=1;i<=10;i++)
        append(&list,&i);

      //element in the index 2 will be deleted, 
      //the current pointer will point to a location that has a copy of the data  

      pop(&list,2);
      printf("%d",*(int*)list.current);

      //To use the list as stack, pop at index list.len-1
      pop(&list,list.len-1);

      //To use the list as queue, pop at index 0
      pop(&list,0);

错误合并(List ,List);

合并两个相同类型的列表。如果类型不同,则会在它返回的 Error 对象中返回错误消息;

例如:

     //Merge two elements of type int
     //List 2 will come after list 1
     Error err;
     err=merge(&list1,&list2);
    Iterator get_iterator(List*);

获取列表的迭代器。初始化时会有一个指向列表第一个元素的指针。

例如:

    Iterator ite=get_iterator(&list);
    Error next(Iterator*);

获取列表的下一个元素。

例如:

//如何迭代一个整数列表

      Iterator itr;
      for(itr=get_iterator(&list);  ite.content!=NULL;  next(ite))
        printf("%d",*(int*)ite.content);

https://github.com/malayh/C-List

【讨论】:

    猜你喜欢
    • 2014-04-22
    • 1970-01-01
    • 1970-01-01
    • 2017-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-27
    相关资源
    最近更新 更多