受K&R C(练习5-8和5-9)一节的启发,我决定编写一个程序,它将采用多个日期的数字形式,都是基于DD/MM/YYYY的,从给定的日期确定一些信息。
首先,找出该日期是否有效,即一个有效月份的有效日期,然后再从中找出一些其他的事情。是闰年吗?一周是什么日子?一年中的哪一天?这个月的名字是什么?一旦计算完所有这些,程序就会将日期转换为字符串,并将所有内容打印给用户。
我花了很多时间重新分解我的代码,并试图尽可能多地优化代码大小、内存使用量和潜在的性能瓶颈,但我觉得其中一些可能会适得其反,相互冲突,可能会导致性能下降。这些方法包括,但不限于,对作业使用最小的可能变量类型(即短变量类型,而不是int类型),并尽可能使用三元操作符。如何改进这段代码,使其更快、更高效、更少冗余、更易于阅读?
/*
* Filename: date.c
* Date: 5-9-17
* Licence: GNU GPL V3
*
* Reads a date string in the form DD/MM/YYYY and determines if it is a valid date, the day of the year, if that year is a leap year, and converts the date to a string
*
* Options:
* N/A
*
* Return/exit codes:
* 0 EXEC_SUCCESS - Successful execution
* 1 MEM_ERROR - Memory allocation error
* 2 READ_ERROR - fgets() error
*
* Todo:
* N/A
*
* Possible additions:
* - Add option(s) for taking different date forms (i.e. -a switches to American date formats)
*
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#define EXEC_SUCCESS 0
#define MEM_ERROR 1
#define READ_ERROR 2
typedef struct {
short day;
short month;
short year;
} date_t;
const char *months[] = {
NULL ,
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
};
const char *days[] = {
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
};
const short days_per_month[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
char *date_to_string(char **string, date_t date); /* Converts the date in decimal form into a string containing the day and name of the month */
short day_of_year(date_t date); /* Determines the day of the year from a given date */
short day_of_week(date_t date); /* Determines the numerical value of the day of the week (taking Sunday as the first day), only works on dates after 14/09/1752 due to the adoption of the Gregorian calendar in the UK */
bool is_valid_date(date_t date);
bool is_leap_year(date_t date);
int main()
{
date_t date;
char *date_string;
char input[128];
char *format[] = {
"%d/%d/%d",
"%d-%d-%d",
"%d.%d.%d",
"%d,%d,%d",
"%d %d %d"
};
printf("Note: day of week only accurate after 14/09/1752\n");
for(;;) {
printf("\nEnter date: ");
date = (const date_t){ 0 };
if(!fgets(input, sizeof(input), stdin)) {
fprintf(stderr, "\nError: fgets() read faliure!\n");
return READ_ERROR;
}
for(int i = 0; i < 5 && sscanf(input, format[i], &date.day, &date.month, &date.year) != 3; i++)
;
if(!is_valid_date(date)) {
fprintf(stderr, "Invalid date!\n");
continue;
}
if(!date_to_string(&date_string, date)) {
fprintf(stderr, "\nError: Memory allocation faliure!\n");
return MEM_ERROR;
}
printf( "The %s is a %s and is day number %d of the year %d, which is %sa leap year\n",
date_string,
days[day_of_week(date)],
day_of_year(date),
date.year,
is_leap_year(date) ? "" : "not "
);
free(date_string);
}
return EXEC_SUCCESS;
}
char *date_to_string(char **string, date_t date)
{
char date_str[48] = { '\0' };
char *suffix[] = { "st", "nd", "rd", "th" };
short date_str_len;
sprintf(date_str,
"%d%s of %s %d",
date.day,
suffix[ (date.day == 1 || date.day == 21 || date.day == 31) ? 0 :
(date.day == 2 || date.day == 22) ? 1 :
(date.day == 3 || date.day == 23) ? 2 : 3],
months[date.month],
date.year
);
date_str_len = strlen(date_str);
if(!(*string = calloc(date_str_len + 1, sizeof(char)))) { /* Remember kids: Always zero your character strings before you use them */
fprintf(stderr, "Error: Cannot allocate memory!\n");
return NULL;
}
strncpy(*string, date_str, date_str_len);
return *string;
}
short day_of_year(date_t date)
{
short day;
short month;
short leap;
leap = is_leap_year(date);
for(day = 0, month = 1; month < date.month; month++)
day += *(*(days_per_month + leap) + month);
day += date.day;
return day;
}
bool is_valid_date(date_t date)
{
return ((date.month > 0 && date.month < 13) && ((date.day > 0 && date.day <= days_per_month[0][date.month]) || (date.day == 29 && date.month == 2 && is_leap_year(date))));
}
bool is_leap_year(date_t date)
{
return ((date.year % 4 == 0 && date.year % 100 != 0) || date.year % 400 == 0);
}
short day_of_week(date_t date)
{
short day = date.day;
short month = date.month;
short year = date.year;
return (day += month < 3 ? year-- : year - 2, 23 * month / 9 + day + 4 + year / 4 - year / 100 + year / 400) % 7;
}发布于 2017-09-05 10:53:08
总之,这段代码看起来非常好。我将谈谈一些可以改进的小事情。这可能是相当混乱的,因为我反复滚动上下,但我会尽我最大的努力保持它的理智。足够的。
我必须在一个1920年像素宽的窗口中水平滚动(主要是因为左边和右边有大量的空空间,但这不是重点)。考虑限制自己的行距,大约80或100个字符。这有多方面的好处:
这可能是非常主观的,但由于它是C代码,我认为它是合适的:不要在一个无休止的循环中从stdin读取(如果没有Ctrl!就不能退出),所以程序将日期作为命令行参数。这样做更符合C的精神
./date_information 6/1/2017
The 6th of January...而不是
./date_information
Enter date:至少这是我的看法。
for(int i = 0; i < 5 && sscanf(input, format[i], &date.day, &date.month, &date.year) != 3; i++)
;我承认这句话很聪明。但我不得不停下来想一想到底发生了什么。为什么不做些类似的事
for(int i = 0; i != 5; ++i) {
if (sscanf(input, format[i], &date.day, &date.month, &date.year) == 3) {
break;
}
}哪个做同样的事情,但直观地显示了发生了什么?但是,支持您将分号包含在否则空行上,以显示空循环体是有意的!不是很多人这么做的。
同样的情况也适用于
return (day += month < 3 ? year-- : year - 2, 23 * month / 9 + day + 4 + year / 4 - year / 100 + year / 400) % 7;这是..。我甚至不知道。它做了很多事情,甚至有好的老逗号操作员在那里!记住,仅仅因为它写在一行上并不意味着它会更快。编译器仍然生成三地址代码.
(Ab)使用三元运算符也属于这一类。
suffix[ (date.day == 1 || date.day == 21 || date.day == 31) ? 0
(date.day == 2 || date.day == 22) ? 1 :
(date.day == 3 || date.day == 23) ? 2 : 3]这是边缘可读的。但这主要是一个格式化的东西!
suffix[ (date.day == 1 || date.day == 21 || date.day == 31) ? 0
: (date.day == 2 || date.day == 22) ? 1
: (date.day == 3 || date.day == 23) ? 2 : 3]这个看起来(有点)干净。Haskell可能偏向于那种格式,但我真的很喜欢它。不过,您可以重构整个片段,在局部变量中没有羞耻之处。
short day = date.day;
short month = date.month;
short year = date.year;
...现在,我可以挑三拣四,说day和month都不需要short,而只需要char --这是一个单一的byte。由于它们的最高可能值分别为31和12,您甚至可以将它们塞进一个字节中。一般来说,比起任何填鸭式,你应该更喜欢使用int。它们将得到最有效的处理(因为它们与word一样大) [需要引证]。在一个功能本身相当短(双关意)中使用三条短裤并不是什么大问题,但它可能会影响性能。也可能不会。这是一个如此小的规模,这是很难察觉的。最好使用“整数的默认类型”,而不是“为这4条指令使用尽可能少的空间”。
在main顶部声明所有变量。你在做K&R,所以这是预期的,但不再需要了。此外,如果月份和日子是全球性的,为什么不使格式也是全球性的呢?
printf,sprintf,fprintf我看到了很多fprintf的用法,而实际上你并不是.格式化一些东西。如果只想打印字符串,请使用puts或fputs。但是请注意,puts会自动添加换行符!
fprintf(stderr, "\nError: fgets() read faliure!\n");变成了
fputs("\nError: fgets() read failure!", stderr);将最终输出格式硬编码在printf中
printf( "The %s is a %s and is day number %d of the year %d, which is %sa leap year\n",...说你想疯狂地推行国际化。或者只是希望用户能够切换到不同的格式。或者..。刚刚意识到你更喜欢“是一年中的第三天”而不是“一年中的第三天”。而不是寻找第115行(是的,我查过了),你可以提取这种格式,并使它也是全球性的。
所有这些都只是指向微小的瑕疵和不均匀的部分。总的来说,您的代码非常好。你的风格是一致的,你的名字很有道理.它甚至处理calloc错误!
发布于 2017-09-05 16:11:44
错误的说明符。临时更改为下面,以查看错误匹配。格式应该是"%hd/%hd/%hd",而不是"%d/%d/%d"。
sscanf(input, "%d-%d-%d", &date.day, &date.month, &date.year) != 3; i++)这意味着测试,不是在32位平台上完成的,也不是用这段代码完成的。
如何改进这段代码,使其更快、更高效、更少冗余、更易于阅读?
首先,使用严格警告使代码更易于阅读和编译。OP所做的任何代码改进都是线性的。为了提高性能,关注更大的问题。
最大的性能问题是sscanf()、sprintf()、calloc()、strncpy()、数组初始化。任何一项改进都只会带来适度的线性改进。
不需要执行类型缩短-没有空间,代码节省预期。在本例中使用函数的类型--返回/使用,size_t。这避免了关于缩短/符号更改的自动警告。
建议使用非引用变量的大小,而不是类型的大小。更容易编写代码、查看和维护。
// short date_str_len;
size_t date_str_len;
...
date_str_len = strlen(date_str);
// if(!(*string = calloc(date_str_len + 1, sizeof(char)))) {
if(!(*string = calloc(date_str_len + 1, sizeof **string))) {date_to_string()不是静态函数。建议将其设置为静态的,这样它只接收限定的参数,或者2)使函数对意外值的date值更宽容:
char *suffix[] = { "st", "nd", "rd", "th" };
#define SUFFIX_N (sizeof suffix/ sizeof suffix[0])
int ending = abs(date.day % 10); // works for entire range of data.day
if (ending > SUFFIX_N) {
ending = SUFFIX_N;
}
sprintf(date_str,
"%d%s of %s %d",
...
suffix[ending],
...
);strncpy()在这里收获很少,因为null字符的终止依赖于前面的代码而不是那么明显。
strncpy(*string, date_str, date_str_len);
// Alternatives
strcpy(*string, date_str);
memcpy(*string, date_str, date_str_len + 1);或者更好的是,使用通用的strdup()来完成这里正在做的事情。不是标准,但更容易代码。
*string = strdup(date_str);
if (*string == NULL) {
...格言/* Remember kids: Always zero your character strings before you use them */是可爱的,但不是普遍的。要使字符串为零,代码只需要str_var[0] = '\0';。在一般情况下,这是一个性能问题--无论是哪种情况,这里都不是一个主要问题。在这个问题上最好遵循小组的代码指南。
如果编码准则鼓励初始化数组,则通常伴随着初始化所有变量。
char date_str[48] = { '\0' };
// short date_str_len;
short date_str_len = 0;is_valid_date()不测试一年。
闰年计算在1582年之前的年份是不正确的。这段代码在遥远的岁月里看起来很有弹性,但是即使是对它的测试也是很好的设计实践。建议年检
#define YEAR_MIN 1583
#define YEAR_MAX INT_MAX
bool is_valid_date(date_t date)
{
if (date.year < YEAR_MIN || data.year > YEAR_MAX) return false;
return ((date.month > 0 ...
}源代码不能很好地用于审查,如
short day_of_week(date_t date); /* Determines the numerical value of the day of the week (taking Sunday as the first day), only works on dates after 14/09/1752 due to the adoption of the Gregorian calendar in the UK */对比
short day_of_week(date_t date); /* Determines the numerical value
of the day of the week (taking Sunday as the first day), only works on dates
after 14/09/1752 due to the adoption of the Gregorian calendar in the UK */源代码应该容忍各种宽度的自动格式化,而不是依靠手工编辑来对齐。
当short或int或bool能够工作时,对它的使用令人怀疑。short很少生成更快的代码或更小的代码。如果内存空间是一个问题,不清楚为什么代码不使用bool或char。当代码趋向于零填充数组时,也最好是初始化变量,而不是稍后分配它。
// short leap;
// leap = is_leap_year(date);
int /* or bool */ leap = is_leap_year(date);不清楚为什么代码会将变量复制到辅助程序中。
// short day = date.day;
// short month = date.month;
// short year = date.year;
// return (day += month < 3 ? year-- : year - 2, 23 * month / 9 + day + 4 + year / 4 - year / 100 + year / 400) % 7; 对比
return (date.day += date.month < 3 ? date.year-- : date.year - 2, 23
* date.month / 9 + date.day + 4 + date.year / 4 - date.year / 100
+ date.year / 400) % 7;
// or perhaps
date.day += date.month < 3 ? date.year-- : date.year - 2;
return (23 * date.month / 9 + date.day + 4 + date.year / 4 - date.year / 100
+ date.year / 400) % 7;https://codereview.stackexchange.com/questions/174827
复制相似问题