我有一个使用结构的链表,现在用户可以添加从最低到最高的等级,或从最高到最低。 我无法写出第三个条件 例如用户进入A C B程序将崩溃所以我不知道是什么问题。
我知道我的代码是一团糟: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;
}
}
}
答案 0 :(得分:3)
在原始代码中,这个片段是可疑的:
if(current->head!=NULL){
hx = current->head->grade;
cy = current->current->grade;}
else{
}
if(current->head==NULL){
压痕表明它自己的紧密支撑是一个闯入者;你不需要一个空的else
块,并且缩进表明if (current->head == NULL)
代码应该是else
的主体的一部分,而不是一组新的条件。如果删除标识为interloper的行,则可能需要在函数末尾添加一个额外的大括号。
如果您干净地缩进代码,那么很容易看出这些块是如何排列的。当你弄乱了缩进时,很难发现这样的错误。缩进用于使代码易于阅读和理解,误导性缩进意味着代码难以阅读,难以理解和正确。
在修订后的代码中,删除了原始评论,重新格式化并缩小了易读性,并注释了对讨论点的新评论,我们有:
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 Style,但在必要时使用当地的约定。当我重新格式化某些东西时,它会变成奥尔曼风格 - 但你不应该把它解释为除了我之外的其他任何东西。此更改不是对您的代码进行任何批评的一部分。
您没有向我们展示如何代表没有课程记录的学生记录。最合理的表示是head
,tail
和current
都被初始化为NULL。
您的功能需要学生记录,但(令人惊讶的是)不是要添加的课程,尽管名称为add_course()
。考虑到在P4使用current->current
,我们必须假设您在调用函数之前通过将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
的值更改为新课程,并调用此函数。有几种可能的情况需要考虑:
问题没有说明应该对第一个和最后一个情况做什么。当出现第二种情况时,应在现有课程之前插入新课程;当第三个出现时,应在现有课程之后插入新课程。确切地说,如果新等级相同,那么课程应按字母顺序列出。处理重复项涉及搜索整个列表以进行匹配;它应该被封装成一个函数:
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
下运行,这为代码提供了一个干净的健康状况(尽管系统库外没有动态内存分配)。
我建议您在第三次插入后跟踪列表未正确扩展的原因。