John,您的代码很尴尬,place 和 out 命令无法按预期工作,out 或 back to classroom free 您分配的任何内存都没有。现在养成跟踪您所做的每个分配的习惯,然后在程序结束前跟踪每个分配free。是的,当您的程序退出时,内存被释放,但是当您开始编写分配内存的函数(如这里)时,您必须free 该内存或随着代码的增长,内存管理将迅速变得难以管理。 (所以帮自己一个忙,从一开始就学会这样做)
接下来,在您编写的任何界面中,您希望用户输入输入,提示用户输入。否则,用户只能看着屏幕上闪烁的光标,想知道程序是否卡住了??
思考您需要哪些数据并在逻辑上向用户提供提示将帮助您以一种不那么尴尬的方式布置您的代码。
Do not typedef pointers。 (例如typedef Line *LinePtr;)这只会让你对指针间接的实际级别感到困惑,而且它也使你的代码几乎不可能让其他人阅读你的代码。 (这将成为一个更大的问题,因为您的代码分布在 10 个不同的头文件和源文件之间)尤其是在您学习 C 时不要typedef指针
让我们转向您的主程序循环。首先,由于您没有使用int argc, char *argv[],因此main 的正确调用是int main(void),明确表示不需要任何参数。应用上面讨论的提示,我的评论和重新排序的样式发生了变化,以使其更有意义,您的主要内容可能如下所示:
#define ORDC 25 /* if you need constants, define them or use an enum */
#define NAMC 100 /* (don't use magic numbers in your code!) */
...
int main (void) {
char order[ORDC] = "", /* initialize all strings to zeros */
name1[NAMC] = "",
name2[NAMC] = "";
line *startptr = NULL;
/* provide an initial prompt showing valid orders to place */
printf ("orders [add, out, first, place, reverse, back to classroom]\n");
for (;;) { /* loop until done or user cancels input */
printf ("\norder: "); /* prompt for order: each iteration */
if (!fgets (order, sizeof order, stdin)) { /* use fgets for user input */
fprintf (stderr, "note: user canceled input.\n");
return 1;
}
rmcrlf (order); /* trim the trailing '\n' (returning the length) */
if (strcmp (order, "back to classroom") == 0) { /* are we done? */
backtoclassroom (startptr);
break;
}
if (strcmp (order, "reverse") == 0) { /* reverse takes no name */
reverse (&startptr);
continue;
}
printf ("name : "); /* every other function takes a student name */
if (!fgets (name1, sizeof name1, stdin)) {
fprintf (stderr, "note: user canceled input.\n");
return 1;
}
rmcrlf (name1);
if (strcmp (order, "add") == 0) /* use if, else if, else logic */
add (&startptr, name1);
else if (strcmp (order, "out") == 0)
out (&startptr, name1);
else if (strcmp (order, "first") == 0)
first (&startptr, name1);
else if (strcmp (order, "place") == 0) { /* place takes two names */
printf ("after: ");
if (!fgets (name2, sizeof name2, stdin)) { /* get name2 here */
fprintf (stderr, "note: user canceled input.\n");
return 1;
}
rmcrlf (name2);
place (startptr, name1, name2);
}
else /* handle the Bad Input case */
fprintf (stderr, "error: invalid order, try again.\n");
}
return 0;
}
当您将数组作为参数传递给函数时,第一级间接寻址(即数组名称后面的第一个[..])将转换为指针(实际上对于数组的任何访问,除了sizeof、_Alignof 或一元 & 运算符,或者是用于初始化数组的字符串文字,发生这种转换,请参阅:C11 Standard - 6.3.2.1 Lvalues, arrays, and function designators (p3))。所以你的函数只需要一个指向数组的指针作为参数,例如
void first (line **lptr, char *name);
void place (line *lptr, char *name1, char *name2);
(并为变量使用描述性名称,name 而不是 array 更有意义)
对于您的out 函数,您只有两种情况要处理,(1)我要删除第一个节点吗? (如果是这样,列表地址将成为第二个节点的地址),或者(2)迭代直到我找到要删除的节点然后prev->nextptr = current->nextptr; free (current);。你可以这样做:
void out (line **lptr, char *name)
{
line *iter = *lptr,
*prev = *lptr;
if (strcmp ((*lptr)->name, name) == 0) { /* name is first node */
iter = iter->nextptr; /* save pointer to next */
free (*lptr); /* free first */
*lptr = iter; /* set first = saved */
return;
}
while (iter && strcmp (iter->name, name)) { /* find node with name */
prev = iter; /* save previousptr */
iter = iter->nextptr;
}
if (!iter) { /* handle name not found */
fprintf (stderr, "error: %s not in list - can't remove.\n", name);
return;
}
prev->nextptr = iter->nextptr; /* previousptr = nextptr */
free (iter); /* free current */
}
对于您的 place 函数,您需要 2 个姓名,(1) name1,新学生的姓名,和 (2) name2,将它们放在后面的人的姓名。将所需的参数添加到place,您可以执行以下操作:
void place (line *lptr, char *name1, char *name2)
{
line *iter = lptr;
line *newptr = malloc (sizeof *newptr);
strcpy (newptr->name, name1);
newptr->nextptr = NULL;
while (iter && strcmp (iter->name, name2)) /* locate after: name */
iter = iter->nextptr;
if (!iter) { /* handle name2 not found */
fprintf (stderr, "error: %s not in list - can't place %s.\n",
name2, name1);
return;
}
newptr->nextptr = iter->nextptr;
iter->nextptr = newptr;
}
这解决了您的两个紧迫问题。您可能需要调整一两个极端情况,但是将所有部分放在一起(并在函数名称和 ( 之间添加空格,以便老年人可以更好地阅读您的代码,您可以执行以下操作:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ORDC 25 /* if you need constants, define them or use an enum */
#define NAMC 100 /* (don't use magic numbers in your code!) */
typedef struct line {
char name[NAMC];
struct line *nextptr;
} line;
size_t rmcrlf (char *s);
void add (line **lptr, char *name);
void out (line **lptr, char *name);
int isempty (line *lptr);
void backtoclassroom (line *currentptr);
void first (line **lptr, char *name);
void place (line *lptr, char *name1, char *name2);
static void reverse (line **lptr);
int main (void) {
char order[ORDC] = "", /* initialize all strings to zeros */
name1[NAMC] = "",
name2[NAMC] = "";
line *startptr = NULL;
/* provide an initial prompt showing valid orders to place */
printf ("orders [add, out, first, place, reverse, back to classroom]\n");
for (;;) { /* loop until done or user cancels input */
printf ("\norder: "); /* prompt for order: each iteration */
if (!fgets (order, sizeof order, stdin)) { /* use fgets for user input */
fprintf (stderr, "note: user canceled input.\n");
return 1;
}
rmcrlf (order); /* trim the trailing '\n' (returning the length) */
if (strcmp (order, "back to classroom") == 0) { /* are we done? */
backtoclassroom (startptr);
break;
}
if (strcmp (order, "reverse") == 0) { /* reverse takes no name */
reverse (&startptr);
continue;
}
printf ("name : "); /* every other function takes a student name */
if (!fgets (name1, sizeof name1, stdin)) {
fprintf (stderr, "note: user canceled input.\n");
return 1;
}
rmcrlf (name1);
if (strcmp (order, "add") == 0) /* use if, else if, else logic */
add (&startptr, name1);
else if (strcmp (order, "out") == 0)
out (&startptr, name1);
else if (strcmp (order, "first") == 0)
first (&startptr, name1);
else if (strcmp (order, "place") == 0) { /* place takes two names */
printf ("after: ");
if (!fgets (name2, sizeof name2, stdin)) { /* get name2 here */
fprintf (stderr, "note: user canceled input.\n");
return 1;
}
rmcrlf (name2);
place (startptr, name1, name2);
}
else /* handle the Bad Input case */
fprintf (stderr, "error: invalid order, try again.\n");
}
return 0;
}
/** remove newline or carriage-return from 's'.
* returns new length. 's' must not be NULL.
*/
size_t rmcrlf (char *s)
{
char *p = s;
if (!*s) /* s is empty-string */
return 0;
/* find eol or nul-terminating char */
for (; *p && *p != '\n' && *p != '\r'; p++) {}
if (*p == '\n' || *p == '\r') /* validate eol & overwrite */
*p = 0;
else /* warn - no end-of-line */
fprintf (stderr, "rmcrlf() warning: no eol detected.\n");
return (size_t)(p - s);
}
int isempty (line *lptr)
{
return (lptr == NULL);
}
void backtoclassroom (line *currentptr)
{
printf ("\nline returning to classroom:\n\n");
if (isempty (currentptr)) {
printf ("line is empty.\n");
}
else {
while (currentptr != NULL) {
line *victim = currentptr; /* ptr to node to free */
printf (" %s\n", currentptr->name);
currentptr = currentptr->nextptr;
free (victim); /* free your memory! */
}
}
}
static void reverse (line **lptr)
{
line *previousptr = NULL,
*currentptr = *lptr,
*afterptr;
while (currentptr != NULL) {
afterptr = currentptr->nextptr;
currentptr->nextptr = previousptr;
previousptr = currentptr;
currentptr = afterptr;
}
*lptr = previousptr;
}
void out (line **lptr, char *name)
{
line *iter = *lptr,
*prev = *lptr;
if (strcmp ((*lptr)->name, name) == 0) { /* name is first node */
iter = iter->nextptr; /* save pointer to next */
free (*lptr); /* free first */
*lptr = iter; /* set first = saved */
return;
}
while (iter && strcmp (iter->name, name)) { /* find node with name */
prev = iter; /* save previousptr */
iter = iter->nextptr;
}
if (!iter) { /* handle name not found */
fprintf (stderr, "error: %s not in list - can't remove.\n", name);
return;
}
prev->nextptr = iter->nextptr; /* previousptr = nextptr */
free (iter); /* free current */
}
void first (line **lptr, char *name)
{
line *newptr = malloc (sizeof *newptr); /* set size on current *ptr */
strcpy (newptr->name, name);
newptr->nextptr = *lptr;
*lptr = newptr;
}
void place (line *lptr, char *name1, char *name2)
{
line *iter = lptr;
line *newptr = malloc (sizeof *newptr);
strcpy (newptr->name, name1);
newptr->nextptr = NULL;
while (iter && strcmp (iter->name, name2)) /* locate after: name */
iter = iter->nextptr;
if (!iter) { /* handle name2 not found */
fprintf (stderr, "error: %s not in list - can't place %s.\n",
name2, name1);
return;
}
newptr->nextptr = iter->nextptr;
iter->nextptr = newptr;
}
void add (line **lptr, char *name)
{
line *newptr = malloc (sizeof *newptr); /* set size on current *ptr */
line *lastptr = *lptr;
strcpy (newptr->name, name);
newptr->nextptr = NULL;
if (*lptr == NULL) {
*lptr = newptr;
return;
}
while (lastptr->nextptr != NULL) {
lastptr = lastptr->nextptr;
}
lastptr->nextptr = newptr;
}
使用/输出示例
$ ./bin/classline
orders [add, out, first, place, reverse, back to classroom]
order: add
name : john doe
order: add
name : mary smith
order: first
name : nancy first
order: place
name : sally second
after: nancy first
order: out
name : john doe
order: back to classroom
line returning to classroom:
nancy first
sally second
mary smith
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放。
您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。
对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。
$ valgrind ./bin/classline
==22574== Memcheck, a memory error detector
==22574== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==22574== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==22574== Command: ./bin/classline
==22574==
orders [add, out, first, place, reverse, back to classroom]
<snip>
line returning to classroom:
nancy first
sally second
mary smith
==22574==
==22574== HEAP SUMMARY:
==22574== in use at exit: 0 bytes in 0 blocks
==22574== total heap usage: 4 allocs, 4 frees, 448 bytes allocated
==22574==
==22574== All heap blocks were freed -- no leaks are possible
==22574==
==22574== For counts of detected and suppressed errors, rerun with: -v
==22574== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认您已释放已分配的所有内存并且没有内存错误。
查看一下,如果您还有其他问题,请告诉我。