我的一项大学作业要求我们使用struct创建一个程序,以创建一个简单的事件调度程序。该程序仅适用于一天,而不是几天。



样本输入/输出

出于某些原因,选择创建.GIF以显示示例输入/输出:



示例保存文件:





 8 30 dentist_appointment
14 0 pickup_friend_from_airport
17 0 buisness_meeting_at_the_office
20 30 dinner_reservation


 






免责声明/声明:没有适当的信誉,不得复制代码。它已根据cc by-sa 3.0进行许可,并需要注明出处。 >

问题/疑虑

希望自从我上次进行主要代码审查以来,我已经有所改善。我试图使评论保持有用,所以请告诉我我是否仍在冗余或需要调低评论范围。在编写本文时,我需要牢记一些事情,包括尽量避免重复自己。还是在显示菜单时只留下那个?我可以通过哪些方法进行改进,应该做什么,应该做什么?

评论

乍一看:尝试在您的范围内输入字母,atoi将返回0并通过验证;您应该使用二进制排序而不是插入排序,然后在插入事件时始终可以使列表保持排序。另外我也没有注意到您释放内存,这会导致内存泄漏。

是的,strtol应该是您的首选,atoi被认为是不安全的+错误与零值之间没有区别。

@coderodde:对于事件调度程序将使用的简短列表,qsort可能会显得过大。在将新元素添加到已经排序的列表中时,请考虑设置开销和最坏情况下的性能-但是OP可能会因计时这两种方法而获得额外的信誉:-)

@jamesqf我关心的不是性能,而是很难知道您的自动解决方案是否正确。

@疯狂:完全;所有带下划线的大写字母通常用于表示预处理程序的定义,但必须使用前导下划线。

#1 楼

首先:您在此处交付的漂亮代码。好的开始!我认为您做得很好-整体结构牢固,我认为大多数变量的意图对读者来说都很清楚。特殊顺序:方法isNull:我认为方法isNull并不是真正必要的,因为它唯一要做的就是检查是否等于NULL。我认为if (somePointer == NULL)if (isNull(somePointer))一样清晰。您甚至可以使用if (!somePtr),它的功能完全相同,我想也很清楚。我知道该软件与性能无关,但是您绝对可以在这里节省函数调用的额外开销。您甚至已经在saveEventList-if (f != NULL)中做到了。

do-WhileinputRange中是这样的:这部分代码:
通过使用do-while -loop来缩短。这样的事情:

printf(prompt, min, max);

fgets(temp, 21, stdin);
input = atoi(temp);

while (input > max || input < min) { // Data validation
    printf(prompt, min, max);
    fgets(temp, 21, stdin);
    input = atoi(temp);
}


不需要强制转换malloc:例如,您在initEvent中执行此操作,并且不需要显式强制转换。实际上,不鼓励使用显式强制转换,如此处所述。

检查malloc中的NULL:您绝对应该做的一件事就是检查malloc的返回值。出现分配问题时,malloc将返回NULL,您应该可以处理它。在这种情况下,由于试图取消引用NULL,因此这将导致不确定的行为。在这种情况下,initEvent()也可能返回NULL,并且调用者必须对其进行处理(打印警告,无法创建该事件)。

do {
    printf(prompt, min, max);
    fgets(temp, 21, stdin);
    input = atoi(temp);
} while (input > max || input < min);



次要:打印要阅读的内容:
我认为您应该在期望用户输入之前打印初始消息(而不是仅打印范围,而是在小时,分钟内打印要阅读的内容。 )。

event *e = (event*)malloc(sizeof(event));
// e is NULL

e->hour = 0;
e->minute = 0;
strcpy(e->description, "");



为什么要麻烦检查非指针上的NULL ?:我相信,addEventAtIndex()中的这一部分是不必要的:将实际结构传递给函数。尝试取消引用NULL时已经有一个错误(无法无意传递地址为structNULL)。


交换方法:排序功能的交换部分应该在一个额外的函数中进行澄清,我也认为您可以将其缩短很多:



printEvent中很好地使用变量:我认为在printEvent中使用多余的变量而不是将所有算术都放在printf参数中是一件好事。它强烈支持可读性。

最后的想法:对于初学者来说,这可能是一个非常好的练习。你在那里做得很好。您可以考虑使用指向事件而不是事件的指针数组。这样做时,排序将显着改善,因为您不必来回复制整个结构,而只需交换指针-快如闪电。

#2 楼

自我记录代码

char h1 = { (e.hour / 10) + '0' }; // Extract the first digit and convert to char (if any, else 0)


考虑到除以十并且0太神秘了,您添加了评论,这比让读者想知道如此出色的工作要好得多。

更好的选择是在代码中说明您的意图: />
char h1 = convert_to_char(first_digit(e.hour))




char convert_to_char(int x) {
    return x + '0';
}


维护/读取代码和注释是仅代码的两倍。

评论


\ $ \ begingroup \ $
哇,我什至没有想把它拆成这么小! +1
\ $ \ endgroup \ $
–疯狂
16 Mar 24 '16 at 13:13

\ $ \ begingroup \ $
@Insane我真的不确定这种方法。首先,convert_to_char需要包含x +'0'而不是x-'0'。我还认为first_digit会产生误导,因为它意味着要找到第一个数字,但实际上只在传递的整数恰好是两位数时返回正确的结果。我认为您的方法是完全正确和明确的。
\ $ \ endgroup \ $
–user101048
16-3-24在15:20

\ $ \ begingroup \ $
@Insane我同意Herickson。函数名称没有足够的描述(如Herickson所述,first_digit仅在非常特殊的情况下有效)。
\ $ \ endgroup \ $
–BalinKingOfMoria恢复CM
16-3-26在15:23

\ $ \ begingroup \ $
您建议的convert_to_char函数实际上计算一位数字的ASCII索引(= Latin-1索引= UTF-8单个表示字节的值)。所以我不会这样称呼。怎么样ascii_from_digit或character_for_digit或类似的东西?
\ $ \ endgroup \ $
– einpoklum
17年1月4日在21:30

\ $ \ begingroup \ $
@einpoklum ascii_number_from_digit也许吗?命名很难
\ $ \ endgroup \ $
– Caridorc
17年1月4日在21:51

#3 楼

即使@Herickson已经提出了大多数要点,但他还是请我注意一件事:strlen(3)返回的size_t类型可以是任何类型(但主要是unsigned long)。您应该从编译器得到警告。

C是一种对脚的渗透性非常危险的语言,您不应通过关闭编译器警告来冒险。错误的类型可能会导致非常意想不到的行为,例如:如果您将带符号和无符号整数,大小不同的整数或在不同类型之间进行转换混合在一起。所有可能有时甚至有用,但仍然很危险。请检查退货。所有回报。总是。 (即使printf(3)在某些情况下以及使用嵌入式处理器也会失败,这是上述规则的例外之一。)
您无需检查fgets(3)得到的字符串的长度(stdin也可能不可用,但这我承认)。
也不检查getc(3)的返回值。如果用户没有输入字母,而是仅enter,则该字母“挂起”给用户,因为您没有检查EOF。它并没有真正挂起,当然,用户可以输入字母并继续操作,但是如果用户在出现错误的情况下没有立即得到响应,那就是糟糕的UX。 (如果可能,还应该使用fgetc(3),因为getc可能已实现为宏。在这里无关紧要,但将来会很重要)。
您自己的函数也应该返回一些指示失败/成功的信息。是的,您让用户知道某件事失败了,还知道什么原因和原因,但是您自己却不知道。
有两个不同的错误流和正常输出流更好。错误的标准流是stderr,输出的标准流是stdout。如果这样做,则用户可以将程序的输出重定向到文件,并且仅查看错误,反之亦然。
常量,尤其是任意常量,应有一个名称。在大多数情况下,预处理器宏就足够了(例如:在99中将deleteEvent设置为标志,可以是宏,或者在21中将inputRange设置为临时字符数组的大小,应该是宏)。
有除了main()中的循环之外,还有一个以上循环等待用户输入。如果可能,应该将它们放在一起。
您不删除列表条目,而只是覆盖它们。但是我认为,链接列表是您接下来要学的内容,所以我现在就闭嘴;-)

全部:很好,没有理由担心下一课您应该真正了解strto*()函数)。

评论


\ $ \ begingroup \ $
所有警告均处于启用状态,当我收到有关size_t的警告时,我始终会进行适当的投射(无论在gcc还是Visual Studio上都不会收到警告)。可能是因为我只是比较而已。和deleteEvent只是最容易使用的术语。我知道它实际上并没有删除。好点,否则:)
\ $ \ endgroup \ $
–疯狂
16 Mar 25'4:30



\ $ \ begingroup \ $
@疯狂。 size_t:不会在您的代码中强制转换。在这种情况下也不必强制转换,也可以直接使用for(size_t i = 0 ...)。只是不要忘记size_t最有可能是无符号的,所以在size_t i = 123 ... if(i> = 0){...})行中的内容可能会给您带来麻烦(但它也应该使您陷入困境)来自编译器的警告)。
\ $ \ endgroup \ $
–deamentiaemundi
16 Mar 25 '16 at 4:38

\ $ \ begingroup \ $
是的,因为我没有收到任何警告,所以未进行强制转换。我不知道为什么,但是,当用于比较(int i = 0; i \ $ \ endgroup \ $
–疯狂
16 Mar 25 '16 at 4:41

\ $ \ begingroup \ $
@Insane如果没有收到警告,则说明size_t是带符号的int或编译器的警告级别不够高。铸造问题如前所述,因此,如果您确切地知道自己在做什么,则可以这样做,但作为初学者,不应该这样做。 “用于size_t和ptrdiff_t的类型的整数转换等级不应大于有符号long int的整数转换等级,除非实现支持足够大的对象以使之必要。” (ISO 9899-2011中的7.19.4)从我的经验来看:标准中的任何“除非”都会带来麻烦;-)
\ $ \ endgroup \ $
–deamentiaemundi
16 Mar 25 '16 at 5:11

#4 楼

一点要点,尽管它有可能使您陷入麻烦:

来自GCC手册第1.3.3节-保留名称:


保留名称包括所有以下划线(_)开头的外部标识符(全局函数和变量)以及所有以两个下划线或下划线后跟大写字母开头的所有标识符(无论使用情况)都是保留名称


我可能对此有误解,但对我而言,这似乎建议您避免使用任何以下划线开头的标识符名称。因此,您可以考虑重命名_MAX_EVENTS_MAX_DESCRIPTION预处理器宏。

#5 楼

 for (int i = 1; i < size; i++) {
        for (int j = i; j > 0 && (list[j - 1].hour > list[j].hour || (list[j - 1].hour == list[j].hour && list[j - 1].minute > list[j].minute)); j--) {


第二个条件的条件很难阅读。您应该将其提取为函数。