首页 > 解决方案 > 带有字符串和整数的堆栈 - C

问题描述

[第 3 次更新]
我对 David 的代码进行了一些更改。我改变了:n->next = s->top;s->top = n;喜欢n->next = NULL;s->top = n;HS 建议的;并且还在主要功能中添加了不同的功能。我试图在程序运行时从用户那里获取 char 名称和 int 值作为输入。程序应不断从用户那里获取数据,将其存储在堆栈中并相应地分配内存。程序仅在用户键入 'end' (char) 或 0 (int) 时结束。代码被编译,但就是这样,在那之后,任何输入和程序都会停止。另外,我在浪费内存fgets(str,20,stdin)吗?如果我不使用所有的尺寸怎么办。非常感谢任何反馈!

///PROGRAM STRUCTURE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct stack_node{
    int value;
    char *name;
    struct stack_node * next;
};
typedef struct stack_node stack_node;

struct stack{
    stack_node *top;
    int size;
};
typedef struct stack stack;

stack *create_stack()
{
    stack *s = malloc (sizeof *s);
    if (!s) {
        perror ("malloc-s");
        return NULL;
    }

    s->size = 0;
    s->top = NULL;

    return s;
}

int empty_stack (stack * s) {
    return s->size == 0;
}


stack_node *push_stack (stack *s, int value, char *name) /// ****
{
    stack_node *n = malloc (sizeof *n);
    if (!n) {
        perror ("malloc-n");
        return NULL;
    }

    n->value = value;
    n->name = malloc (strlen (name) + 1);
    if (!n->name) {
        perror ("malloc-name");
        return NULL;
    }
    strcpy (n->name, name);
    n->next = NULL; /// ****
    s->top = n;
    s->size++;

    return n;
}

stack_node *pop_stack (stack *s)
{
    if (s->size > 0) {
        stack_node *node = s->top;
        s->top = s->top->next;
        s->size--;
        return node;
    }

    return NULL;
}

void free_node (stack_node *n)
{
    if (n->name)
        free (n->name);

    free (n);
}

void free_stack (stack *s)
{
    while (s->size > 0) {
        stack_node *victim = s->top;
        s->top = s->top->next;
        free_node (victim);
        s->size--;
    }

    free (s);
}

int main (void) {

    stack *s = create_stack();
    stack_node *node = NULL;

    char *str; ///NAME
    int vs; ///VALUE
    int i=0;

    if (!s)
        return 1;

    do{
        printf("NAME{%d]: ",i);
        fgets(str,20,stdin); ///***?
        printf("VALUE[%d]: ",i);
        scanf(" %d",&vs);
        if (push_stack (s, vs, str) == NULL) {
            fprintf (stderr, "error: push node failed '%d'.\n", i);
            return 1;
        }
        i++;
    }while(str != 'end' || vs != 0);
   i=0;

    while ((node = pop_stack(s)) != NULL) {
        printf ("value[%d]:  %d    name[%d]: %s\n", i,node->value, i,node->name);
        i++;
        free_node (node);
    }
    free_stack (s);

    return 0;
}

///**********
    do{
        printf("NAME[%d]: ",i);

        if(fgets(buf,sizeof buf,stdin)){
        int rtn = sscanf (buf, "%63[^\n]", name);
        if(rtn == 1){
            printf("VALUE[%d]: ",i);
            scanf(" %d",&vs);

        }
        }
        push_stack(s,vs,name);
        if (s == NULL) {
            fprintf (stderr, "error: push node failed '%d'.\n", i);
            return 1;
        }
        i++;
    }while(node->name != "end" || node->value != 0);

标签: c

解决方案


嗯.. 很明显你已经在你的代码中付出了努力,但很明显你仍然在努力将结构存储为堆栈中的一个节点(以及基本的字符串处理问题)。

首先,不需要强制转换 malloc 的返回值,请参阅:我是否强制转换 malloc 的结果?. 接下来,始终根据对象的取消引用指针来调整对象的分配大小。例如stack *s声明s为指向 type 的指针stack。如果s是指向 的指针 stack,那么*s是类型stack。使用实际指针本身调整分配大小,消除任何大小不正确的机会。此外,您有责任通过检查 return is not来验证NULL每个分配。有了这个,你create_stack()可能是:

stack *create_stack()
{
    stack *s = malloc (sizeof *s);  /* size from dereferenced pointer */
    if (!s) {                       /* validate every allocation */
        perror ("malloc-s");
        return NULL;
    }

    s->size = 0;
    s->top = NULL;

    return s;
}

empty_stack的未使用,但其他方面都可以。

您的push_stack职能是许多问题所在。首先,您不能同时为节点和字符串分配!(即使你可以,你也会太小一个字节,因为你忘记了字符串末尾的nul 终止字符的空间)。您声明:

struct stack_node{
    int value;
    char *name;
    struct stack_node * next;
};

您必须首先为您的节点分配存储空间,然后为您的指针分配存储空间name——记住验证分配create_stack(),例如

    stack_node *n = malloc (sizeof *n); /* allocate node only */
    if (!n) {                           /* validate every allocation */
        perror ("malloc-n");
        return NULL;
    }

    n->value = value;
    n->name = malloc (strlen (name) + 1);   /* allocate strlen + 1 chars */
    if (!n->name) {                         /* validate! */
        perror ("malloc-name");
        return NULL;
    }

接下来,您不能在 C 中分配字符串(除了初始化指向字符串文字的指针,例如char *name = "foo";)在 C 中,您必须将字符串复制到您创建的有效存储中n->name。例如:

    strcpy (n->name, name);     /* you cannot assign strings, use strcpy */

push_stack此外,如果您声明为 type ,您将如何向调用者指示您的分配是成功还是失败void

始终选择可以在需要时指示成功/失败的返回类型。考虑到这一点,您可以简单地返回一个指向新节点的指针(无论如何方便立即使用),或者NULL在失败时返回,例如

/* choose return type that can indicate success/failure */
stack_node *push_stack (stack *s, int value, const char *name)
{
    stack_node *n = malloc (sizeof *n); /* allocate node only */
    if (!n) {                           /* validate every allocation */
        perror ("malloc-n");
        return NULL;
    }

    n->value = value;
    n->name = malloc (strlen (name) + 1);   /* allocate strlen + 1 chars */
    if (!n->name) {                         /* validate! */
        perror ("malloc-name");
        return NULL;
    }
    strcpy (n->name, name);     /* you cannot assign strings, use strcpy */
    n->next = s->top;
    s->top = n;
    s->size++;

    return n;
}

您不想要两个单独的pop功能。而不是pop_stack_valueand pop_stack_name,您只需要 apop_stack返回指向顶部节点的指针。然后,您可以根据需要访问值或名称(例如node->valuenode->name)。

声明几个简单的辅助函数来释放不再需要时分配的内存也很有意义。您正在处理节点和堆栈,因此free_node助手和free_stack助手是有意义的。(请记住,您可能希望在堆栈为空之前释放堆栈,因此free_stack相应地编写代码),例如

void free_node (stack_node *n)  /* free node helper function */
{
    if (n->name)
        free (n->name);

    free (n);
}

void free_stack (stack *s)      /* free stack helper (need not be empty) */
{
    while (s->size > 0) {
        stack_node *victim = s->top;
        s->top = s->top->next;
        free_node (victim);
        s->size--;
    }

    free (s);
}

将所有部分放在一个简短的示例中,您可以执行类似于以下的操作:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct stack_node{
    int value;
    char *name;
    struct stack_node * next;
};
typedef struct stack_node stack_node;

struct stack{
    stack_node *top;
    int size;
};
typedef struct stack stack;

stack *create_stack()
{
    stack *s = malloc (sizeof *s);  /* size from dereferenced pointer */
    if (!s) {                       /* validate every allocation */
        perror ("malloc-s");
        return NULL;
    }

    s->size = 0;
    s->top = NULL;

    return s;
}

int empty_stack (stack * s) {       /* s->size is type 'int' */
    return s->size == 0;
}

/* choose return type that can indicate success/failure */
stack_node *push_stack (stack *s, int value, const char *name)
{
    stack_node *n = malloc (sizeof *n); /* allocate node only */
    if (!n) {                           /* validate every allocation */
        perror ("malloc-n");
        return NULL;
    }

    n->value = value;
    n->name = malloc (strlen (name) + 1);   /* allocate strlen + 1 chars */
    if (!n->name) {                         /* validate! */
        perror ("malloc-name");
        return NULL;
    }
    strcpy (n->name, name);     /* you cannot assign strings, use strcpy */
    n->next = s->top;
    s->top = n;
    s->size++;

    return n;
}

stack_node *pop_stack (stack *s)    /* return the node on pop */
{
    if (s->size > 0) {
        stack_node *node = s->top;
        s->top = s->top->next;
        s->size--;
        return node;    /* caller is responsible for freeing node */
    }

    return NULL;
}

void free_node (stack_node *n)  /* free node helper function */
{
    if (n->name)
        free (n->name);

    free (n);
}

void free_stack (stack *s)      /* free stack helper (need not be empty) */
{
    while (s->size > 0) {
        stack_node *victim = s->top;
        s->top = s->top->next;
        free_node (victim);
        s->size--;
    }

    free (s);
}

int main (void) {

    const char *str[] = { "john", "jack", "jill", "sally", "sue" };
    int n = sizeof str/sizeof *str;
    stack *s = create_stack();
    stack_node *node = NULL;

    if (!s)         /* validate !!! */
        return 1;

    for (int i = 0; i < n; i++)
        if (push_stack (s, i+1, str[i]) == NULL) {
            fprintf (stderr, "error: push node failed '%d'.\n", i);
            return 1;
        }

    while ((node = pop_stack(s)) != NULL) {
        printf ("value:  %2d    name: %s\n", node->value, node->name);
        free_node (node);   /* free node */
    }
    free_stack (s);     /* free stack */

    return 0;
}

示例使用/输出

$ ./bin/stack_struct
value:   5    name: sue
value:   4    name: sally
value:   3    name: jill
value:   2    name: jack
value:   1    name: john

内存使用/错误检查

在您编写的任何动态分配内存的代码中,您对分配的任何内存块都有两个责任:(1)始终保留指向内存块起始地址的指针,(2)它可以在没有时被释放需要更长的时间。

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的范围,尝试读取或基于未初始化值的条件跳转,最后确认释放所有分配的内存。

对于 Linuxvalgrind是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/stack_struct
==4204== Memcheck, a memory error detector
==4204== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==4204== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==4204== Command: ./bin/stack_struct
==4204==
value:   5    name: sue
value:   4    name: sally
value:   3    name: jill
value:   2    name: jack
value:   1    name: john
==4204==
==4204== HEAP SUMMARY:
==4204==     in use at exit: 0 bytes in 0 blocks
==4204==   total heap usage: 11 allocs, 11 frees, 161 bytes allocated
==4204==
==4204== All heap blocks were freed -- no leaks are possible
==4204==
==4204== For counts of detected and suppressed errors, rerun with: -v
==4204== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放所有已分配的内存并且没有内存错误。

最后,为了避免大多数这些问题,请始终在启用警告的情况下进行编译,并且在没有警告的情况下干净地编译之前不要接受代码。要启用警告,请添加到您的或编译字符串。(添加几个额外的警告)。对于,您可以使用. 对于VS(在windoze 上),添加(或使用但你会得到很多无关的窗口非代码相关警告)。阅读并理解每个警告。他们将识别任何问题,以及它们发生的确切线路。-Wall -Wextragccclang-pedanticclang-Weverythingcl.exe/W3/Wall

您可以通过聆听编译器告诉您的内容来尽可能多地了解编码,就像您从大多数教程中一样。

如果您还有其他问题,请仔细查看并告诉我。


接受value输入,然后name

当您接受用户输入时,请考虑使用fgetsfor 读取名称和值(然后使用sscanf(或strtol) 转换为int)。在匹配失败后,您可以避免许多陷阱scanf和无法清空(这通常会导致无限循环以及未定义的行为stdin

您故意想要不断循环,直到您验证您有您要求的输入 - 并且它满足您的要求(范围等......)。您还必须通过生成手册来防止用户取消输入EOF(例如Ctrl+d在 Linux 或Ctrl+zwindows 上,但请参阅:CTRL+Z 在 Windows 10 中不会生成 EOF

/* if you need constants, #define one (or more), or use a global enum */
#define MAXC    1024    /* max chars for input buffer (don't skimp) */
#define MAXNM     64    /* max length for name buffer (ditto) */
...
int main (void) {

    stack *s = create_stack();
    stack_node *node = NULL;

    if (!s)         /* validate !!! */
        return 1;

    for (;;) {      /* loop continually taking input until exit conditon */
        char buf[MAXC]  = "",   /* line buffer for fgets */
            name[MAXNM] = "";   /* buffer for parsing name from line */
        int value = 0;          /* int to parse from line */

        for (;;) {  /* loop continually until you get valid int */
            fputs ("\nenter value: ", stdout);  /* prompt for value */
            if (fgets (buf, MAXC, stdin)) {     /* validate line read */
                if (sscanf (buf, "%d", &value) == 1) { /* convert to int */
                    if (value == 0) {   /* your exit condition */
                        fputs ("  value == 0 (input done)\n", stderr );
                        goto inputdone; /* jump out of both loops */
                    }
                    break;
                }
                else    /* non-integer (non-numeric) input */
                    fputs ("  error: invalid integer input.\n", stderr);
            }
            else {  /* fgets returned EOF (user canceled) */
                fputs ("(user canceled input)\n", stderr);
                goto inputdone; /* jump out of both loops */
            }
        }

        for (;;) {      /* loop continually until name recieved */
            fputs ("enter name : ", stdout);        /* prompt name */
            if (fgets (name, MAXNM, stdin)) {       /* read name */
                size_t len = strlen (name);         /* get length */
                if (len && name[len-1] == '\n') {   /* last char \n ? */
                    name[--len] = 0;                /* overwrite with \0 */
                    if (len)                    /* check 1 char remains */
                        break;                  /* got good name, go push */
                    else    /* user just pressed [Enter] */
                        fputs ("  error: empty-line.\n", stderr);
                }
                else if (len == MAXNM -1)   /* was input too long? */
                    fputs ("  warning: name truncated.\n", stderr);
            }
            else {  /* fgets returned EOF (user canceled) */
                fputs ("(user canceled input)\n", stderr);
                goto inputdone; /* jump out of both loops */
            }
        }

        push_stack (s, value, name);    /* push value and name on stack */
    }
    inputdone:;

    puts ("\nvalues stored in stack\n");
    while ((node = pop_stack(s)) != NULL) {
        printf ("value:  %2d    name: %s\n", node->value, node->name);
        free_node (node);   /* free node */
    }
    free_stack (s);     /* free stack */

    return 0;
}

注意:Enter当用户按下空行而不是寻找 a时结束输入可能会更好value == 0(因为 anint可以为零))

示例使用/输出

$ ./bin/stack_struct_input_simple

enter value: 1
enter name : Frank Zappa

enter value: 2
enter name :
error: empty-line.
enter name : Jimmy Buffet

enter value: 3
enter name : Grahm Nash

enter value: 4
enter name : John Bonham

enter value: 0
value == 0 (input done)

values stored in stack

value:   4    name: John Bonham
value:   3    name: Grahm Nash
value:   2    name: Jimmy Buffet
value:   1    name: Frank Zappa

看看你是否可以通过上面的输入例程并理解为什么它是这样写的。(我也有一个可以同时接受value name或同时value接受name)只要您遵守规则并验证所有内容,防止手动操作EOF并始终知道输入缓冲区中剩余的内容(例如stdin),您就可以灵活地输入。


推荐阅读