简体   繁体   English

解释C语言中堆栈的工作方式

[英]Explanation of how stacks work in C

I am just wanting a simple explanation of the linking process when pushing data onto a stack. 我只想简单地解释将数据推入堆栈时的链接过程。 I know how to build on using the code from my book, but I am not really sure I understand how the process works when you move the stack head link from one to the next. 我知道如何使用本书中的代码进行构建,但是当您将堆栈头链接从一个移到另一个时,我不确定我是否理解该过程的工作原理。

For stacks like: 对于像这样的堆栈:

typedef struct node
{
    void dataptr;
    struct node* link;
}STRUCT_NODE;

typedef struct
{
    int count;
    STACK_NODE* top;
}STACK;

How do you change the link to point to the new data pushed on the stack. 如何更改链接以指向推入堆栈的新数据。 Also I do not know 我也不知道

Stacks can be implemented in various ways, but given the way you phrase your question I'm assuming your stack is just a linked list, something like 堆栈可以通过多种方式实现,但是考虑到您表达问题的方式,我假设您的堆栈只是一个链表,例如

head
↓
A → B → C → D → 0

"when you move the stack head link from one to the next" the picture just changes to: “当您将堆栈头链接从一个移到另一个时”,图片变为:

    head
    ↓
A → B → C → D → 0

Of course A is not reachable any more in this graph, so you'd better have another pointer to it somewhere (be it only to dispose of it), but this is the gist how how the stack is popped (by making head = head->next if each node in the stack is a struct node with a next field that's a struct node* , and of course head is a struct node* as well). 当然,在该图中A不再可用,因此您最好在某个地方有另一个指向它的指针(因为它只是为了处理它),但这就是要如何弹出堆栈的要点(通过使head = head->next如果堆栈中的每个节点都是struct node ,而next字段是struct node* ,当然head也是struct node* )。

That's for popping something off the stack (and you should free the memory used by A in that case). 那是为了从堆栈中弹出某些东西(在这种情况下,您应该释放A使用的内存)。 In detailed steps, it would be: 详细步骤如下:

1/ Saving the old head. 1 /救老头。

      head
      ↓
old → A → B → C → D → 0

2/ Adjusting the head. 2 /调整头。

          head
          ↓
old → A → B → C → D → 0

3/ Returning the old head (pointed at by old ). 3 /返回旧的头(由old指向)。

If instead you're talking about pushing something onto the stack, that's an operation that involves: 相反,如果你在谈论的东西推堆栈,这是涉及一个操作:

1/ Creating a new element. 1 /创建一个新元素。

    head
    ↓
Z   A → B → C → D → 0

2/ Pointing it towards the current head 2 /指向当前的头部

    head
    ↓
Z → A → B → C → D → 0

3/ Adjusting the head to point to it. 3 /调整头部使其指向。

head
↓
Z → A → B → C → D → 0

Given your data structures representing a node on the stack, and the actual stack itself: 给定代表堆栈上节点的数据结构以及实际堆栈本身:

typedef struct node {
    void        *dataptr;
    struct node *link;
} NODE;

typedef struct {
    int  count;
    NODE *top;
} STACK;

you would initialise a stack as follows: 您将初始化堆栈,如下所示:

STACK myStack;
myStack.count = 0;
myStack.top = NULL;

This basically gives you an empty stack. 这基本上为您提供了一个空堆栈。 I'm going to use top to decide if the stack is empty - you could use either count or top (as 0 or NULL respectively) to do that job but it's a good idea to choose one and stick with it in case you ever write some buggy code in future where the cached count and actual count get out of step :-) 我将使用top来确定堆栈是否为空-您可以使用counttop (分别为0NULL )来完成该工作,但是最好选择一个并坚持使用,以防万一您写将来会出现一些错误的代码,其中缓存的计数和实际计数会脱离步调:-)

To push a node onto the stack, it's a simple operation of: 要将节点压入堆栈,这是一个简单的操作:

  • allocate the new node and set the payload (1). 分配新节点并设置有效负载(1)。
  • point the new node's link to the current head. 将新节点的链接指向当前头。
  • point the head to that new node (3). 将头部指向该新节点(3)。
  • increment the count (4). 递增计数(4)。

The following code shows how you can do it: 以下代码显示了如何执行此操作:

/* 1 */ NODE *newNode = malloc (sizeof (NODE)); // should check for failure.
        newNode->dataptr = NULL;
/* 2 */ newNode->link = myStack.top;
/* 3 */ myStack.top = newNode;
/* 4 */ myStack.count++;

This will push onto either an empty stack or a populated one. 这将压入一个空堆栈或一个已填充的堆栈。 The edge case of an empty stack will see newNode.link set to NULL , then myStack.top set to newNode , which is the correct behaviour. 空堆栈的边缘情况将看到newNode.link设置为NULL ,然后myStack.top设置为newNode ,这是正确的行为。

To pop a node off the stack: 要将节点弹出堆栈:

  • save the current head (1). 保存当前磁头(1)。
  • if the current head isn't NULL, advance head to its link (and decrement count). 如果当前磁头不为NULL,则将磁头前进到其链接(和递减计数)。
  • return the saved current head (3). 返回保存的当前磁头(3)。

which , translated to code, is: 转换为代码的是:

/* 1 */ NODE *popNode = myStack.top;
/* 2 */ if (myStack.top != NULL) {
            myStack.top = myStack.top->link;
            myStack.count--;
        }
/* 3 */ return popNode;

This returns either the address of the popped node, or NULL if the stack was empty. 这将返回弹出节点的地址,如果堆栈为空,则返回NULL

The whole set of operations could be encapsulated as follows. 整个操作集可以封装如下。 Hopefully the comments will make it self-explanatory in conjunction with the comments above but feel free to raise any concerns and I'll address them with an edit. 希望这些评论能够与上面的评论一起变得不言自明,但是可以提出任何疑问,我将通过编辑进行解决。


// Error codes (probably should be enums).

#define STACK_ERR_OKAY       0
#define STACK_ERR_NOTEMPTY   1
#define STACK_ERR_NOPAYLOAD  2
#define STACK_ERR_NOMEMORY   3

// Structures.

typedef struct sNode {
    void         *data;   // Payload pointer.
    struct sNode *next;   // Link to next node.
} tNode;

typedef struct {
    int          count;   // Count for fast sizing.
    NODE         *top;    // First node.
} tStack;

// Make a new stack returning its pointer or NULL on failure.

tStack *stackNew (void) {
    tStack stack = malloc (sizeof (tStack));
    if (stack != NULL) {
        stack->count = 0;
        stack->top = NULL;
    }
    return stack;
}

// Delete a current stack, must be empty first.

int stackDel (tStack *stack) {
    if (stack->top != NULL)
        return STACK_ERR_NOT_EMPTY;
    free (stack);
    return STACK_ERR_OK;
}

// Push a pointer payload (no NULLs allowed) onto the stack.

int stackPush (tStack *stack, void *payload) {
    if (payload == NULL)
        return STACK_ERR_NOPAYLOAD;
    tNode *node = malloc (sizeof (tNode));
    if (node == NULL)
        return STACK_ERR_NOMEMORY;
    node->data = payload;
    node->next = stack->top;
    stack->top = node;
    stack->count++;
    return STACK_ERR_OK;
}

// Pop a pointer payload from the stack. Returns NULL if stack was empty.

int stackPop (tStack *stack) {
    tNode *node = stack->top;
    if (node == NULL) {
        return NULL;
    stack->top = node->next;
    stack->count--;
    return node->data;
}

// Get stack size.

int stackSize (tStack *stack) {
    return stack->count;
}

The reason I've insisted that a stack must be empty before deleting is because the code doesn't know for certain what the payload is. 我坚持删除之前堆栈必须为空的原因是因为代码无法确定有效载荷是什么。 It could be a simple pointer to a block of memory (shallow) in which case you could just use: 它可以是指向内存块(浅)的简单指针,在这种情况下,您可以使用:

void stackDel (tStack *stack) {
    tNode *node = stackPop (stack);
    while (node != NULL) {
        free (node);
        node = stackPop (stack);
    }
    free (stack);
}

but, if it's a pointer to memory holding other pointers to memory, it's more problematic to automatically free the memory it references (it would probably be best done as a callback to the caller of the API since it would be the only beast that knew for sure how to properly free the memory). 但是,如果它是一个指向内存的指针,并持有其他指向内存的指针,则自动释放它所引用的内存会更成问题(最好将其作为对API调用者的回调,因为这将是唯一知道的野兽确定如何正确释放内存)。 Far simpler to make that a pre-requisite on the caller up front. 使其成为呼叫者的先决条件要容易得多。

Say you have some STACK called my_stack, and a STACK_NODE called my_node. 假设您有一些名为my_stack的堆栈,还有一个名为my_node的STACK_NODE。 To add my_node to my_stack, do this: 要将my_node添加到my_stack,请执行以下操作:

my_node.link = my_stack.top;
my_stack.top = &my_node;
my_stack.count = my_stack.count + 1;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM