为什么队列和堆栈被声明为指针?

时间:2012-10-30 14:47:10

标签: c pointers data-structures stack queue

我正在研究数据结构,我不知道为什么堆栈和队列需要声明为:

struct stack *Stack;

(忘记struct语法)

我的意思是,为什么它总是被声明为指针?

5 个答案:

答案 0 :(得分:4)

他们并不总是这样宣称!

通常,将变量声明为指针对于以后动态分配它很有用。这可能是由于以下几个原因造成的:

  • 变量对于程序堆栈来说太大了
  • 您想从函数
  • 返回该变量

在您的情况下,让我们考虑两种不同的堆栈实现:

struct stack
{
    void *stuff[10000];
    int size;
};

这是一个糟糕的实现,但假设你有这样一个,那么你很可能不想把它放在程序堆栈上。

或者,如果你有:

struct stack
{
    void **stuff;
    int size;
    int mem_size;
};

无论如何,你动态地改变stuff的大小,因此在程序堆栈上声明类型struct stack的变量绝对没有坏处,例如:

struct stack stack;

除非你想从函数中返回它。例如:

struct stack *make_stack(int initial_size)
{
    struct stack *s;

    s = malloc(sizeof(*s));
    if (s == NULL)
        goto exit_no_mem;

    if (initial_size == 0)
        initial_size = 1;
    s->stuff = malloc(initial_size * sizeof(*s->stuff));
    if (s->stuff == NULL)
        goto exit_no_stuff_mem;

    s->size = 0;
    s->mem_size = initial_size;

    return s;
exit_no_stuff_mem:
    free(s);
exit_no_mem:
    return NULL;
}

但就个人而言,我会声明这样的功能:

int make_stack(struct stack *s, int initial_size);

并在程序堆栈上分配struct stack

答案 1 :(得分:1)

这取决于您的堆栈结构的定义方式(不仅仅是struct的布局,还包括操作它的操作)。

完全可以将堆栈定义为简单的数组和索引,例如

struct stack_ {
  T data[N]; // for some type T and size N
  size_t stackptr; // Nobody caught that error, so it never existed, right? ;-)
} stack;

stack.stackptr = N; // stack grows towards 0

// push operation
if (stack.stackptr)
  stack.data[--stack.stackptr] = some_data();
else
  // overflow

// pop operation
if (stack.stackptr < N)
  x = stack.data[stack.stackptr++];
else
  // underflow

但是,固定大小的数组是有限的。实现堆栈的一种简单方法是使用列表结构:

struct stack_elem {
  T data;
  struct stack_elem *next;
};

这个想法是列表的头部是堆栈的顶部。将项目推入堆栈会在列表的头部添加一个元素;弹出一个项目会从列表的头部删除该元素:

int push(struct stack_elem **stack, T data)
{
  struct stack_elem *s = malloc(sizeof *s);
  if (s)
  {
    s->data = data;   // new element gets data
    s->next = *stack; // set new element to point to current stack head
    *stack = s;       // new element becomes new stack head
  }
  return s != NULL;
}

int pop(struct stack_elem **stack, T *data)
{
  int stackempty = (*stack == NULL);

  if (!stackempty)
  {
    struct stack_elem *s = *stack; // retrieve the current stack head
    *stack = (*stack)->next;       // set stack head to point to next element
    *data = s->data;               // get the data
    free(s);                       // deallocate the element
  }

  return r;
}

int main(void)
{
  struct stack_elem *mystack = NULL; // stack is initially empty
  T value;
  ...
  if (!push(&mystack, some_data()))
    // handle overflow
  ...
  if (!pop(&mystack, &value))
    // handle underflow
  ...
}  

由于pushpop需要能够将新指针值写入mystack,我们需要传递指向它的指针,因此stack的双重间接指向在pushpop中。

答案 2 :(得分:0)

不,它们不必被声明为指针。

还可以将堆栈和队列分配为全局变量:

struct myHash { int key; int next_idx; int data[4]; } mainTable[65536];
struct myHash duplicates[65536*10];
int stack[16384];

myHash还包含使用索引的重复条目的链接列表。

但正如评论中所述,如果必须在结构中添加更多元素,这是最初计划的,那么指针就会派上用场。

将结构声明为指针的另一个原因是它通常使用指针可以访问整个结构,结构的任何单个元素或元素的某个子集。这使得语法更加通用。此外,当结构作为参数传递给某个外部函数时,指针是不可避免的。

答案 3 :(得分:0)

实际上没有必要用指针实现堆栈和队列 - 其他人已经清楚地说明了这个事实。看看@JohnBode关于如何使用数组完美实现堆栈的答案。问题在于使用指针对某些数据结构(例如堆栈,队列和链表)进行建模,允许您在执行速度和内存消耗方面以非常有效的方式对它们进行编程。

通常,如果你的用例需要频繁随机访问结构中的元素,那么用于保存数据结构的底层数组是非常好的实现选择,因为它的位置索引(这是一个带有数组的FAST)。然而,增加结构超过其初始容量可能是昂贵的,并且您使用阵列中未使用的元素浪费内存。插入和删除操作也可能非常昂贵,因为您可能需要重新排列元素以压缩结构或为新元素腾出空间。

由于队列和堆栈没有这种随机访问要求,并且您没有在它们中间插入或删除元素,因此动态分配每个单独的元素是一个更好的实现选择。 ,在需要新元素时请求内存(这是malloc所做的),并将其作为元素释放。这很快,并且不会消耗比数据结构实际需要的内存更多的内存。

答案 4 :(得分:0)

正如aleady指出的那样取决于结构的大小。

另一个原因是封装。堆栈实现可能不会在其头文件中公开struct stack的定义。这隐藏了用户的实现细节,缺点是需要免费存储分配。