首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C日期信息程序

C日期信息程序
EN

Code Review用户
提问于 2017-09-05 02:00:45
回答 2查看 213关注 0票数 6

受K&R C(练习5-8和5-9)一节的启发,我决定编写一个程序,它将采用多个日期的数字形式,都是基于DD/MM/YYYY的,从给定的日期确定一些信息。

首先,找出该日期是否有效,即一个有效月份的有效日期,然后再从中找出一些其他的事情。是闰年吗?一周是什么日子?一年中的哪一天?这个月的名字是什么?一旦计算完所有这些,程序就会将日期转换为字符串,并将所有内容打印给用户。

我花了很多时间重新分解我的代码,并试图尽可能多地优化代码大小、内存使用量和潜在的性能瓶颈,但我觉得其中一些可能会适得其反,相互冲突,可能会导致性能下降。这些方法包括,但不限于,对作业使用最小的可能变量类型(即短变量类型,而不是int类型),并尽可能使用三元操作符。如何改进这段代码,使其更快、更高效、更少冗余、更易于阅读?

代码语言:javascript
复制
/* 
 * 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;  
}
EN

回答 2

Code Review用户

发布于 2017-09-05 10:53:08

总之,这段代码看起来非常好。我将谈谈一些可以改进的小事情。这可能是相当混乱的,因为我反复滚动上下,但我会尽我最大的努力保持它的理智。足够的。

线长

我必须在一个1920年像素宽的窗口中水平滚动(主要是因为左边和右边有大量的空空间,但这不是重点)。考虑限制自己的行距,大约80或100个字符。这有多方面的好处:

  • 足够宽到足以应付大多数陈述
  • 让你重新思考很长的队伍
  • 源可以在没有滚动(几乎)任何显示的情况下读取。
  • 看上去..。刮干净的胡子?我感觉到这里和那里都有一些很长的行,使得代码看起来像是要长胡子,但是失败了。

通用程序使用

这可能是非常主观的,但由于它是C代码,我认为它是合适的:不要在一个无休止的循环中从stdin读取(如果没有Ctrl!就不能退出),所以程序将日期作为命令行参数。这样做更符合C的精神

代码语言:javascript
复制
./date_information 6/1/2017
The 6th of January...

而不是

代码语言:javascript
复制
./date_information
Enter date:

至少这是我的看法。

聪明代码

代码语言:javascript
复制
for(int i = 0; i < 5 && sscanf(input, format[i], &date.day, &date.month, &date.year) != 3; i++)
        ;

我承认这句话很聪明。但我不得不停下来想一想到底发生了什么。为什么不做些类似的事

代码语言:javascript
复制
for(int i = 0; i != 5; ++i) {
    if (sscanf(input, format[i], &date.day, &date.month, &date.year) == 3) {
        break;
    }
}

哪个做同样的事情,但直观地显示了发生了什么?但是,支持您将分号包含在否则空行上,以显示空循环体是有意的!不是很多人这么做的。

同样的情况也适用于

代码语言:javascript
复制
return (day += month < 3 ? year-- : year - 2, 23 * month / 9 + day + 4 + year / 4 - year / 100 + year / 400) % 7;

这是..。我甚至不知道。它做了很多事情,甚至有好的老逗号操作员在那里!记住,仅仅因为它写在一行上并不意味着它会更快。编译器仍然生成三地址代码.

(Ab)使用三元运算符也属于这一类。

代码语言:javascript
复制
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]

这是边缘可读的。但这主要是一个格式化的东西!

代码语言:javascript
复制
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可能偏向于那种格式,但我真的很喜欢它。不过,您可以重构整个片段,在局部变量中没有羞耻之处。

俄罗斯方块包装类型

代码语言:javascript
复制
short day = date.day;
short month = date.month;
short year = date.year;
...

现在,我可以挑三拣四,说daymonth都不需要short,而只需要char --这是一个单一的byte。由于它们的最高可能值分别为31和12,您甚至可以将它们塞进一个字节中。一般来说,比起任何填鸭式,你应该更喜欢使用int。它们将得到最有效的处理(因为它们与word一样大) [需要引证]。在一个功能本身相当短(双关意)中使用三条短裤并不是什么大问题,但它可能会影响性能。也可能不会。这是一个如此小的规模,这是很难察觉的。最好使用“整数的默认类型”,而不是“为这4条指令使用尽可能少的空间”。

宣布这是1980年的

main顶部声明所有变量。你在做K&R,所以这是预期的,但不再需要了。此外,如果月份和日子是全球性的,为什么不使格式也是全球性的呢?

printfsprintffprintf

我看到了很多fprintf的用法,而实际上你并不是.格式化一些东西。如果只想打印字符串,请使用putsfputs。但是请注意,puts会自动添加换行符!

代码语言:javascript
复制
fprintf(stderr, "\nError: fgets() read faliure!\n");

变成了

代码语言:javascript
复制
fputs("\nError: fgets() read failure!", stderr);

最终输出

将最终输出格式硬编码在printf

代码语言:javascript
复制
printf( "The %s is a %s and is day number %d of the year %d, which is %sa leap year\n",...

说你想疯狂地推行国际化。或者只是希望用户能够切换到不同的格式。或者..。刚刚意识到你更喜欢“是一年中的第三天”而不是“一年中的第三天”。而不是寻找第115行(是的,我查过了),你可以提取这种格式,并使它也是全球性的。

结论

所有这些都只是指向微小的瑕疵和不均匀的部分。总的来说,您的代码非常好。你的风格是一致的,你的名字很有道理.它甚至处理calloc错误!

票数 6
EN

Code Review用户

发布于 2017-09-05 16:11:44

错误的说明符。临时更改为下面,以查看错误匹配。格式应该是"%hd/%hd/%hd",而不是"%d/%d/%d"

代码语言:javascript
复制
sscanf(input, "%d-%d-%d", &date.day, &date.month, &date.year) != 3; i++)

这意味着测试,不是在32位平台上完成的,也不是用这段代码完成的。

如何改进这段代码,使其更快、更高效、更少冗余、更易于阅读?

首先,使用严格警告使代码更易于阅读和编译。OP所做的任何代码改进都是线性的。为了提高性能,关注更大的问题。

最大的性能问题是sscanf()sprintf()calloc()strncpy()、数组初始化。任何一项改进都只会带来适度的线性改进。

不需要执行类型缩短-没有空间,代码节省预期。在本例中使用函数的类型--返回/使用,size_t。这避免了关于缩短/符号更改的自动警告。

建议使用非引用变量的大小,而不是类型的大小。更容易编写代码、查看和维护。

代码语言:javascript
复制
// 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值更宽容:

代码语言:javascript
复制
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字符的终止依赖于前面的代码而不是那么明显。

代码语言:javascript
复制
strncpy(*string, date_str, date_str_len);
// Alternatives
strcpy(*string, date_str);
memcpy(*string, date_str, date_str_len + 1);

或者更好的是,使用通用的strdup()来完成这里正在做的事情。不是标准,但更容易代码

代码语言:javascript
复制
*string = strdup(date_str);
if (*string == NULL) {
    ...

格言/* Remember kids: Always zero your character strings before you use them */是可爱的,但不是普遍的。要使字符串为零,代码只需要str_var[0] = '\0';。在一般情况下,这是一个性能问题--无论是哪种情况,这里都不是一个主要问题。在这个问题上最好遵循小组的代码指南。

如果编码准则鼓励初始化数组,则通常伴随着初始化所有变量。

代码语言:javascript
复制
char date_str[48] = { '\0' };
// short date_str_len;
short date_str_len = 0;

is_valid_date()不测试一年。

闰年计算在1582年之前的年份是不正确的。这段代码在遥远的岁月里看起来很有弹性,但是即使是对它的测试也是很好的设计实践。建议年检

代码语言:javascript
复制
#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 ...
}

源代码不能很好地用于审查,如

代码语言:javascript
复制
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 */

对比

代码语言:javascript
复制
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 */

源代码应该容忍各种宽度的自动格式化,而不是依靠手工编辑来对齐。

shortintbool能够工作时,对它的使用令人怀疑。short很少生成更快的代码或更小的代码。如果内存空间是一个问题,不清楚为什么代码不使用boolchar。当代码趋向于零填充数组时,也最好是初始化变量,而不是稍后分配它。

代码语言:javascript
复制
 // short leap;
 // leap = is_leap_year(date);
 int /* or bool */ leap = is_leap_year(date);

不清楚为什么代码会将变量复制到辅助程序中。

代码语言:javascript
复制
// 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; 

对比

代码语言:javascript
复制
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;
票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/174827

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档