-1

我有一个使用结构的链表,现在用户必须将等级从最低到最高进行排序或从最高到最低。我无法编写第三个条件,例如用户输入 ACB,程序会崩溃,所以我不知道问题出在哪里。

我知道我的代码一团糟:D,我也很抱歉,请原谅我的英语不好。

这里是结构:

typedef struct student
{
char sname[32];
char sid[9];
struct student *snext;
struct course *current;
struct course *head;
struct course *tail;
} STUDENT;

typedef struct course
{
char cname[15];
char grade;
struct course *next;
} COURSE;

这是功能:

void add_course(STUDENT *current){

int hx,cy,tmpz;
COURSE *tmp = current->current;
if(current->head!=NULL){
hx = current->head->grade;
cy = current->current->grade;}

    //here the first element will be added
    if(current->head==NULL){
    current->head = current->current;
    current->tail = current->current;
    current->tail->next = NULL;
    }
    else{
       //here it compares the head grade and the element i want to add
       //so if the ascii number of head's grade is greater than the current's grade
       //then the current will be the head and the previous head will be after the
       //current head 
        if(hx>cy){
        current->current->next = current->head;
        current->head = current->current;
        current->current = current->head;
        while(current->current->next!=NULL)
        current->current = current->current->next;
        current->tail->next = current->current;
        current->tail = current->current;
        current->tail->next = NULL;
        }

        else{
           //what i am trying to do here is e.g. if i have three elements already and 
           //their grades are ( first element: A  second element: C  third element:  D) 
           //and i want to add a new element and its grade is B
           //so the problem here it doesnt sort them it only crash in this condition
           //i dont know what i am really doing here lol
           //third condition
            current->current = current->head;
            hx = current->current->grade;
            tmpz = tmp->grade;
            while(current->current->next!= NULL && tmpz>hx){
            current->current = current->current->next;
            hx = current->current->next->grade;
            }
        current->current->next = tmp->next;
        current->current->next = tmp;
        current->current = tmp;

        }
    }

}
4

1 回答 1

3

在原始代码中,这个片段是可疑的:

if(current->head!=NULL){
hx = current->head->grade;
cy = current->current->grade;}
else{
}
    if(current->head==NULL){

缩进表明它自己的行上的右大括号是一个闯入者;您不需要一个空else块,并且缩进表明if (current->head == NULL)代码应该是主体的一部分,else而不是一组新的条件。如果删除标识为闯入者的行,则可能需要在函数末尾添加一个额外的右大括号。

如果你干净地缩进你的代码,那么很容易看到块是如何排列的。当你把缩进弄得一团糟时,很难发现这样的错误。缩进用于使代码易于阅读和理解,误导性缩进意味着代码难以阅读,难以理解和正确。


在修改后的代码中,去掉了原始注释,重新格式化和缩进以便于阅读,并为讨论点添加了新的注释,我们有:

void add_course(STUDENT *current)
{
    int hx,cy,tmpz;                        // P1
    COURSE *tmp = current->current;        // P2

    if (current->head != NULL)             // P3
    {
        hx = current->head->grade;
        cy = current->current->grade;
    }

    if (current->head == NULL)
    {
        current->head = current->current;  // P4
        current->tail = current->current;
        current->tail->next = NULL;        // P5
    }
    else
    {
        if (hx > cy)                       // P6
        {
            current->current->next = current->head;
            current->head = current->current;
            current->current = current->head;
            while (current->current->next != NULL)
                current->current = current->current->next;
            current->tail->next = current->current;
            current->tail = current->current;
            current->tail->next = NULL;
        }
        else
        {                                  // P7
            current->current = current->head;
            hx = current->current->grade;
            tmpz = tmp->grade;
            while (current->current->next != NULL && tmpz > hx)
            {
                current->current = current->current->next;
                hx = current->current->next->grade;
            }
            current->current->next = tmp->next;
            current->current->next = tmp;
            current->current = tmp;
        }
    }
}

用于块的开放式大括号的位置是有争议的。有些人,包括 K&R,喜欢与if, else, while, for,switch等在同一行的左大括号。我非常喜欢Allman缩进风格,但在必要时使用本地约定。当我大量重新格式化某些东西时,它会进入 Allman 风格——但你不应该将其解释为我的反常性以外的任何东西。更改不是对您的代码的任何批评的一部分。

您没有向我们展示没有课程记录的学生记录是如何表示的。最合理的表示是head,tailcurrent都初始化为 NULL。

您的函数需要学生记录,但(令人惊讶的是)不是要添加的课程,尽管名称add_course(). 鉴于使用current->currentat P4,我们必须假设您current->current在调用该函数之前通过设置为新课程部分添加了课程,并且您需要该函数来确定新课程在列表中的head位置结束于tail. 这在功能上不是很有凝聚力——这是一个糟糕的设计。理想情况下,新的课程记录应该作为单独的参数传递给函数,并且该next字段应该为 NULL(并且应该初始化课程名称,并且必须初始化成绩):

void add_course(STUDENT *student, COURSE *new_course);

我们可以观察到 P1 和 P2 处的变量直到 P6 或 P7 才使用,因此 P1、P2 和 P3 处的代码应该移到 P6 之前或更晚。我将坚持使用 C89 代码,而不是使用 C99(以及 C11 和 C++)支持的“在任何地方定义”规则。

P4 到 P5 的段落应该处理一个空列表。它可以写成使用tmp(在这种情况下,P2 的定义应该保留在原处)。写起来可能更清楚:

 COURSE *new_course = current->current;

 if (current->head == NULL)
 {
     current->head = new_course;
     current->tail = new_course;
     new_course->next = NULL;
 }

如果在调用函数之前正确初始化课程,则最终分配应该是不必要的。

假设已经有一门课程与学生相关联,并且通过调用代码将新课程的值更改current->current为新课程并调用此函数来添加新课程。有几种可能的情况需要考虑:

  1. 新课程是(该)现有课程的副本(应该被拒绝 - 但该函数不返回值,因此无法指示该课程被拒绝)。
  2. 新课程成绩低于现有课程成绩。
  3. 新课程成绩高于现有课程成绩。
  4. 新课程成绩与现有课程成绩相同。

问题没有具体说明第一种和最后一种情况应该怎么做。当出现第二种情况时,应在现有课程之前插入新课程;当第三个出现时,应在现有课程之后插入新课程。为明确起见,如果新成绩相同,则应按字母顺序列出课程。处理重复项涉及在整个列表中搜索匹配项;它可能应该被封装成一个函数:

COURSE *find_course(STUDENT *student, COURSE *course);

该函数获取一个学生,从head到搜索列表tail,将课程名称与列表中的课程进行比较。如果有,则返回列表中匹配的元素;此处的代码只要求函数返回 NULL 表示未找到该名称。

COURSE *find_course(STUDENT *student, COURSE *course)
{
    COURSE *next_student = student->head;
    while (next_student != NULL && strcmp(next_student->cname, course->cname) != 0)
        next_student = next_student->next; 
    return next_student;
}

将此函数接口和实现更改为:

COURSE *find_course(COURSE *course, const char *cname)
{
    while (course != NULL)
    {
        if (strcmp(course->cname, cname) == 0)
            return(course);
        course = course->next;
    }
    return(course);
}

这可用于搜索任何正确构建的课程列表,例如有效课程列表(因此您可以拒绝无效课程)。

我们还应该回顾当有多个现有课程时应该发生什么,这样我们就可以避免重复代码。重复的课程检查是相同的,仍然应该排在第一位。由于我们可以通过归纳安全地假设空列表是有序的,而单元素列表是有序的,我们可以决定add_course()始终确保课程列表是有序的。

但是,我将把它留给你处理。

我们将需要一个课程比较功能。我们可以使用与 相同的约定strcmp(),如果第一个参数应该在第二个之前返回一个负数,如果第二个应该在第一个之前返回一个正数;如果两个课程相同,则(名义上)为零:

int cmp_course(const COURSE *c1, const COURSE *c2)
{
    if (c1->grade < c2->grade)
        return -1;
    else if (c1->grade > c2->grade)
        return +1;
    else
        return(strcmp(c1->cname, c2->cname));
}

继续

[...长时间的停顿...24小时或更长时间...]

这是您的代码,经过注释,包装到工作,编译,运行(C99)程序中。除了轻微的重新格式化和添加断言之外,我根本没有更改代码add_course()

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>

typedef struct student
{
    char sname[32];
    char sid[9];
    struct student *snext;
    struct course *current;
    struct course *head;
    struct course *tail;
} STUDENT;

typedef struct course
{
    char cname[15];
    char grade;
    struct course *next;
} COURSE;

extern void add_course(STUDENT *current);

void add_course(STUDENT *current)
{
    int hx,cy,tmpz;
    COURSE *tmp = current->current;
    assert(tmp != 0);
    if (current->head != NULL)
    {
        hx = current->head->grade;
        cy = current->current->grade;
    }

    if (current->head == NULL)
    {
        current->head = current->current;
        current->tail = current->current;
        current->tail->next = NULL;
    }
    else
    {
        if (hx > cy)
        {
            current->current->next = current->head;
            current->head = current->current;
            current->current = current->head;
            while (current->current->next != NULL)
                current->current = current->current->next;
            current->tail->next = current->current;
            current->tail = current->current;
            current->tail->next = NULL;
        }
        else
        {
            current->current = current->head;
            hx = current->current->grade;
            tmpz = tmp->grade;
            while (current->current->next != NULL && tmpz>hx)
            {
                current->current = current->current->next;
                hx = current->current->next->grade;
            }
            current->current->next = tmp->next;
            current->current->next = tmp;
            current->current = tmp;
        }
    }
}

static void dump_student(FILE *fp, const char *tag, const STUDENT *student)
{
    fprintf(fp, "Student: %s\n", tag);
    fprintf(fp, "Name: %s; ID: %s\n", student->sname, student->sid);
    fprintf(fp, "Next:    0x%" PRIXPTR "\n", (uintptr_t)student->snext);
    fprintf(fp, "Current: 0x%" PRIXPTR "; ", (uintptr_t)student->current);
    fprintf(fp, "Head: 0x%" PRIXPTR "; ", (uintptr_t)student->head);
    fprintf(fp, "Tail: 0x%" PRIXPTR "\n", (uintptr_t)student->tail);
    COURSE *cp = student->head;
    while (cp != 0)
    {
        fprintf(fp, "Course: %-14s (%c) (0x%.16" PRIXPTR ")\n",
                cp->cname, cp->grade, (uintptr_t)cp->next);
        cp = cp->next;
    }
}

int main(void)
{
    STUDENT s1 = { "Yours Truly", "me", 0, 0, 0, 0 };
    COURSE  c[] =
    {
        { "Math",    'B', 0 },
        { "English", 'A', 0 },
        { "Science", 'D', 0 },
        { "History", 'C', 0 },
        { "French",  'C', 0 },
    };

    dump_student(stdout, "Before", &s1);

    for (int i = 0; i < 5; i++)
    {
        char buffer[8];
        sprintf(buffer, "After%d", i+1);
        s1.current = &c[i];
        add_course(&s1);
        dump_student(stdout, buffer, &s1);
    }
    return(0);
}

注意dump_student()功能;我发现具有这种接口的函数非常有用,并且经常将它们保留在代码中以供以后调试。该FILE *参数意味着可以将输出发送到标准错误(或日志文件)和标记以识别正在运行的调用的发生情况。如果需要,您可以在界面中添加文件、行、函数名;我通常不会那样做。

只有几个地方的代码是 C99;中的for循环main()和课程指针定义dump_student();如果您的 C 编译器不支持 C99 语法,您可以移动变量定义。

这是 Mac OS X 10.7.4 上 64 位编译的示例输出。

Student: Before
Name: Yours Truly; ID: me
Next:    0x0
Current: 0x0; Head: 0x0; Tail: 0x0
Student: After1
Name: Yours Truly; ID: me
Next:    0x0
Current: 0x7FFF643D84E0; Head: 0x7FFF643D84E0; Tail: 0x7FFF643D84E0
Course: Math           (B) (0x0000000000000000)
Student: After2
Name: Yours Truly; ID: me
Next:    0x0
Current: 0x7FFF643D84E0; Head: 0x7FFF643D84F8; Tail: 0x7FFF643D84E0
Course: English        (A) (0x00007FFF643D84E0)
Course: Math           (B) (0x0000000000000000)
Student: After3
Name: Yours Truly; ID: me
Next:    0x0
Current: 0x7FFF643D8510; Head: 0x7FFF643D84F8; Tail: 0x7FFF643D84E0
Course: English        (A) (0x00007FFF643D84E0)
Course: Math           (B) (0x00007FFF643D8510)
Course: Science        (D) (0x0000000000000000)
Student: After4
Name: Yours Truly; ID: me
Next:    0x0
Current: 0x7FFF643D8528; Head: 0x7FFF643D84F8; Tail: 0x7FFF643D84E0
Course: English        (A) (0x00007FFF643D84E0)
Course: Math           (B) (0x00007FFF643D8528)
Course: History        (C) (0x0000000000000000)
Student: After5
Name: Yours Truly; ID: me
Next:    0x0
Current: 0x7FFF643D8540; Head: 0x7FFF643D84F8; Tail: 0x7FFF643D84E0
Course: English        (A) (0x00007FFF643D84E0)
Course: Math           (B) (0x00007FFF643D8540)
Course: French         (C) (0x0000000000000000)

请注意,前几次插入很好,但之后出现问题。我跑了下来valgrind,这给了代码一个干净的健康状况(尽管系统库之外没有动态内存分配)。

我建议您追查为什么在第三次插入后列表没有正确扩展。

于 2012-08-03T20:20:15.323 回答