【问题标题】:Modeling a classroom line using linked-list使用链表对教室线建模
【发布时间】:2018-01-24 08:19:38
【问题描述】:

我想用我的程序模拟一条简单的教室线。

  • 函数first:将学生x放在行前。

  • 函数out:从行中删除学生x

  • 函数backToClassroom:打印行中的学生。

  • 函数reverse:颠倒学生在行中的顺序。

  • 函数place:学生x为他/她身后的学生y占位,然后如果学生y想要要加入队伍,他/她应该落后于x

  • 函数add:在行尾添加学生x,除非学生自己占位。

我的问题是:

我不知道如何正确编码函数addplace,正如我在顶部解释的那样。


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

struct line {
    char name[100];
    struct line *nextPtr;
};

typedef struct line Line;
typedef Line *LinePtr;

void add(LinePtr *lPtr, char array[100]);
void out(LinePtr *lPtr, char array[100]);
int isEmpty(LinePtr lPtr);
void backToClassroom(LinePtr currentPtr);
void first(LinePtr* lPtr, char array[100]);
void place(LinePtr previousPtr, char array[100]);
static void reverse(LinePtr* lPtr);

int main(int argc, char *argv[]) {

    char order[25];
    LinePtr startPtr = NULL;
    char tempName1[100];
    char tempName2[100];

    gets(order);

    while(strcmp(order, "back to classroom") != 0) {

        if(strcmp(order, "add") == 0) {
            scanf("%s", tempName1);
            add(&startPtr, tempName1);
        }

        if(strcmp(order, "out") == 0) {
            scanf("%s", tempName1);
            out(&startPtr, tempName1);
        }

        if(strcmp(order, "first") == 0) {
            scanf("%s", tempName1);
            first(&startPtr, tempName1);
        }

        if(strcmp(order, "place") == 0) {
            scanf("%s", tempName1);
            scanf("%s", tempName2);
            place(startPtr, tempName2);
        }

        if(strcmp(order, "reverse") == 0) {
            reverse(&startPtr);
        }

        gets(order);
    }

    if(strcmp(order, "back to classroom") == 0) {
        backToClassroom(startPtr);
    }

    return 0;
}


int isEmpty(LinePtr lPtr) {
    return (lPtr == NULL);
}

void backToClassroom(LinePtr currentPtr) {
    if(isEmpty(currentPtr)) {
        printf("line is empty.\n");
    }
    else {
        while(currentPtr != NULL) {
            printf("%s\n", currentPtr->name);
            currentPtr = currentPtr->nextPtr;
        }
    }
}

static void reverse(LinePtr* lPtr) {
    LinePtr previousPtr = NULL;
    LinePtr currentPtr = *lPtr;
    LinePtr afterPtr;
    while(currentPtr != NULL) {
        afterPtr  = currentPtr->nextPtr;  
        currentPtr->nextPtr = previousPtr;   
        previousPtr = currentPtr;
        currentPtr = afterPtr;
    }
    *lPtr = previousPtr;
}

void out(LinePtr *lPtr, char array[100]) {
    LinePtr tempPtr;
    if(strcmp(array, (*lPtr)->name) == 0) {
        tempPtr = *lPtr;
        *lPtr = (*lPtr)->nextPtr;
        free(tempPtr);
    }
    else {
        LinePtr previousPtr = *lPtr;
        LinePtr currentPtr = (*lPtr)->nextPtr;
        while((currentPtr != NULL) && ((strcmp(currentPtr->name, array)) != 0)) {
            previousPtr = currentPtr;
            currentPtr = currentPtr->nextPtr;
        }
        if(currentPtr != NULL) {
            tempPtr = currentPtr;
            previousPtr->nextPtr = currentPtr->nextPtr;
            free(tempPtr);
        }
    }
}

void first(LinePtr* lPtr, char array[100]) {
    LinePtr newPtr = malloc(sizeof(Line));
    strcpy(newPtr->name, array);
    newPtr->nextPtr = (*lPtr);
    (*lPtr) = newPtr;
}

void place(LinePtr previousPtr, char array[100]) {
    if(previousPtr == NULL) { 
       printf("the given previous node cannot be NULL");
       return;      
    }  
    LinePtr newPtr = (LinePtr) malloc(sizeof(Line));
    strcpy(newPtr->name, array);
    newPtr->nextPtr = previousPtr->nextPtr; 
    previousPtr->nextPtr = newPtr;
}

void add(LinePtr *lPtr, char array[100]) {
    LinePtr newPtr = malloc(sizeof(Line));
    LinePtr lastPtr = *lPtr;
    strcpy(newPtr->name, array);
    newPtr->nextPtr = NULL;
    if(*lPtr == NULL) {
       *lPtr = newPtr;
       return;
    }  
    while(lastPtr->nextPtr != NULL) {
        lastPtr = lastPtr->nextPtr;
    }
    lastPtr->nextPtr = newPtr;    
}

【问题讨论】:

  • 请编辑您的帖子以使用正确的大小写和标点符号。在当前状态下很难阅读。
  • @user694733 你好,现在可以了吗?
  • 从不,从不,从不使用gets(),它非常不安全并且容易出现缓冲区溢出,因此已从 C11 库中删除。了解如何使用 fgets 并修剪它包含的尾随 '\n'。它需要不超过 5 行代码。如果您的教授建议gets(),请停止 - 趁还有时间放弃课程 - 并找到一位称职的教授来教您 C。
  • @DavidC.Rankin 嗨,实际上我认为我应该按照您的建议去做。我解决了这个问题并改用 sscanf 。谢谢。
  • typedef Line *LinePtr; 您需要查看:Is it a good idea to typedef pointers?。虽然不是错误,但 C 的标准编码风格避免使用 camelCaseMixedCase 变量名,以支持所有 小写,同时保留 大写用于宏和常量的名称。例如:NASA - C Style Guide, 1994

标签: c data-structures linked-list


【解决方案1】:

John,您的代码很尴尬,placeout 命令无法按预期工作,outback 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 或一元 &amp; 运算符,或者是用于初始化数组的字符串文字,发生这种转换,请参阅: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-&gt;nextptr = current-&gt;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)

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

查看一下,如果您还有其他问题,请告诉我。

【讨论】:

  • 嗨,非常感谢您的时间和精力。我认真对待您的建议并完全更改程序的代码。但是你能像我解释的那样解决我的问题吗?或提示如何做到这一点? place 实际上应该创建一个虚拟节点。 place john jack 应该在 john 后面为 jack 保留一个位置,然后当用户在此之后键入 add jack b>jack 加入该行并放在 john 之后。
  • 当然,你可以这样做,但它会添加一些代码(比你想象的要多)。您将添加具有固定名称的占位符节点,例如"RESERVED_jack"。需要它的原因是place 只需要添加名称和添加它的人。这意味着输入order: 的下一个循环将再次开始,然后在任何后续add 上,您必须使用strncmp 扫描整个列表以检查每个名称之前的"RESERVED_"。你真的确定这就是你想要的吗?这没有多大意义——但它是可行的。
  • 如果jack 从未加入该行会怎样?那你用虚拟节点做什么呢?您是否修改了所有其他函数以检查并跳过带有 "RESERVED_" 前缀的任何名称?它很快就会变得很麻烦——这就是为什么我认为这不太可能是你应该做的。将“占位符”成员添加到您的 struct 并将其设置为 1 用于虚拟节点可能会更好。这将使检查比检查前缀字符串更容易。
  • 或者更好的是,不要让place 创建任何节点,创建一个包含name1name2 的结构的第二个列表,并让place 将该结构添加到那个列表。有第二个函数put_in_place(这就是我的place 现在所做的)。然后在add 中将该名称与新名称列表中的所有name1s 进行比较,如果找到匹配项,则将name1name2 传递给put_in_place,并从新列表中删除该节点。 (看看为什么我说我不认为这真的是你的任务打算......)
【解决方案2】:

我想你只需要这个:

void place(LinePtr previousPtr, char array[100]) 
{
    return add (previousPtr, array);
}

// If lPtr == null then this is an insertion at the end of the list
// If lPtr != null then this is an insertion into the list after lPtr.
void add(LinePtr *lPtr, char array[100]) 
{
    LinePtr newPtr = malloc(sizeof(Line));
    strcpy(newPtr->name, array);
    newPtr->nextPtr = NULL;

    LinePtr lastPtr = *lPtr;
    if(*lPtr == NULL) 
    {
       *lPtr = newPtr;
       return;
    }  
    while(lastPtr->nextPtr != NULL) {
        lastPtr = lastPtr->nextPtr;
    }
    lastPtr->nextPtr = newPtr;
}

您的添加函数已经执行了添加或插入。

【讨论】:

  • 嗨,我发现我的部分问题是关于 'gets(order);'
  • 你能帮我解决我的第二个问题吗?你说的没有解决。谢谢
  • gets(order) 是否返回新行(您将其视为空字符串)?
【解决方案3】:

place 函数必须知道旧节点。您可以提供新名称和旧名称。

//place before the other name
void place(LinePtr *list, char *newName, char *otherName)
{
    if(list == NULL)
    {
        printf("the given previous node cannot be NULL");
        return;
    }

    Line *walk = *list;
    Line *prev = NULL;
    while(walk)
    {
        if(strcmp(walk->name, otherName) == 0)
        {
            //found
            LinePtr newPtr = (LinePtr)malloc(sizeof(Line));
            strcpy(newPtr->name, newName);
            newPtr->nextPtr = walk;
            if(prev)
                prev->nextPtr = newPtr;
            else
                *list = newPtr;
            break;
        }
        prev = walk;
        walk = walk->nextPtr;
    }
}

void place_after_otherName(LinePtr *list, char *newName, char *otherName)
{
    if(list == NULL)
    {
        printf("the given previous node cannot be NULL");
        return;
    }

    Line *walk = *list;
    while(walk)
    {
        if(strcmp(walk->name, otherName) == 0)
        {
            //found
            LinePtr newPtr = (LinePtr)malloc(sizeof(Line));
            strcpy(newPtr->name, newName);
            newPtr->nextPtr = walk->nextPtr;
            walk->nextPtr = newPtr;
            break;
        }
        walk = walk->nextPtr;
    }
}

add 函数按预期工作。示例:

int main(void) 
{
    LinePtr startPtr = NULL;
    add(&startPtr, "x");
    add(&startPtr, "y");
    add(&startPtr, "z");

    place(&startPtr, "A", "z");
    place_after_otherName(&startPtr, "B", "z");

    LinePtr p = startPtr;
    while(p)
    {
        printf(p->name);
        p = p->nextPtr;
    }

    printf("\n");
    return 0;
}

【讨论】:

  • 嗨,它确实有效。谢谢。但是有一个问题,我仍然需要我解释的那个具体条件。在用户输入 place jack john john 之后不应在线,除非用户输入 add john 然后只有在 john i> 放置在该特定位置。你能告诉我怎么做吗?谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-02
  • 2016-07-21
  • 1970-01-01
相关资源
最近更新 更多