c - 带有字符串和整数的堆栈 - 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);
解决方案
嗯.. 很明显你已经在你的代码中付出了努力,但很明显你仍然在努力将结构存储为堆栈中的一个节点(以及基本的字符串处理问题)。
首先,不需要强制转换 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_value
and pop_stack_name
,您只需要 apop_stack
返回指向顶部节点的指针。然后,您可以根据需要访问值或名称(例如node->value
或node->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 -Wextra
gcc
clang
-pedantic
clang
-Weverything
cl.exe
/W3
/Wall
您可以通过聆听编译器告诉您的内容来尽可能多地了解编码,就像您从大多数教程中一样。
如果您还有其他问题,请仔细查看并告诉我。
接受value
输入,然后name
当您接受用户输入时,请考虑使用fgets
for 读取名称和值(然后使用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
),您就可以灵活地输入。
推荐阅读
- sql - 监视表的活动(特定行上的选择数)
- java - 使用 OpenOffice java api 保存文档会引发异常
- python - 如何对列表列表中的向量数组求和?
- amazon-s3 - Terraform`aws_iam_policy_document`数据块中多个条件的正确语法是什么
- jboss - 创建数据源时无法识别区域设置错误(与 oracle db 连接)
- swift - 使用 Alamofire 5.0 swift 5 从 UIImagePickerController 将图像上传到服务器
- neo4j - 任何限制节点结果的方法
- javascript - GTM 增强型电子商务 - 未捕获的类型错误:无法读取未定义的属性“购买”
- angular - 离子/角度中 FormGroup 中的输入字段存在问题
- java - 本地通知侦听器未执行操作