首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >打印日的算法,在提交日期时

打印日的算法,在提交日期时
EN

Code Review用户
提问于 2022-10-17 12:48:50
回答 2查看 110关注 0票数 2

我很想知道你对我们在课堂上写的这个算法的看法。我在考虑如何在不使用库函数的情况下优化它(就代码/复杂性的大小而言)(除了用于读写的<iostream> )。

任务(翻译):

关于闰年的公历规则是在1582年10月15日(星期五)推出的。编写一个程序,当以“dd.MM.yyyy”格式指定日期时,该程序将显示当天的名称。您需要定义并使用枚举类型Weekday {星期一、星期二、…、星期日}。

这是我的代码:

代码语言:javascript
复制
   #include <iostream>


bool isLeapYear(int year){
    if(year % 4 == 0 && year % 100 != 0){
        return true;
    } else if (year % 400 == 0){
        return true;
    } else {
        return false;
    }
}
enum Weeknday {Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday};
int main() {

    // Get date
    int day,mounth,year;
    scanf("%d.%d.%d",&day,&mounth,&year);

    // Count days
    int days = 0;
    for(int i = 1582; i <= year; i++){
        if(i != year) {
            if(isLeapYear(i)){
                days += 366;
            } else {
                days += 365;
            }
        } else {
            for(int j = 1; j <= mounth; j++){
                if(j != mounth){
                    if(j == 1 || j == 3 || j == 5 || j == 7 || j == 8 || j == 10 || j == 12){
                        days += 31;
                    } else if (j == 4 || j == 6 || j == 9 || j == 11){
                        days += 30;
                    } else if (j == 2){
                        if(isLeapYear(i)){
                            days += 29;
                        } else {
                            days += 28;
                        }
                    }
                } else {
                    days += day;
                }
            }
        }
    }



    int a = (days-3)%7;
    Weeknday enum_day;
    switch(a) {
        case 1:
            enum_day = Monday;
            std::cout << "Monday";
            break;
        case 2:
            enum_day = Tuesday;
            std::cout << "Tuesday";
            break;
        case 3:
            enum_day = Wednesday;
            std::cout << "Wednesday";
            break;
        case 4:
            enum_day = Thursday;
            std::cout << "Thursday";
            break;
        case 5:
            enum_day = Friday;
            std::cout << "Friday";
            break;
        case 6:
            enum_day = Saturday;
            std::cout << "Saturday";
            break;
        case 7:
            enum_day = Sunday;
            std::cout << "Sunday";
            break;

    }
    std::cout << enum_day;


}
EN

回答 2

Code Review用户

回答已采纳

发布于 2022-10-17 16:02:39

几乎所有东西,包括输入和输出,都在main()函数中。这使得很难为代码创建自动测试。我建议创建一个无需任何I/O即可测试的函数:

代码语言:javascript
复制
int weekday(int year, int month, int day)

然后,main()可以完成输入的读取和输出的写入。

我们需要包括<cstdio>来定义std::scanf。不需要包含(并使用普通scanf())的情况下,您可以在平台上构建它,这并不意味着它将在其他地方构建,因为这是因为编译器所做的可选选择并不是标准C++强制要求的。

当我们使用std::scanf()时,我们绝对必须检查它转换了多少值,否则我们将使用统一变量。以下是一个建议:

代码语言:javascript
复制
    // Get date
    int day, month, year;
    if (std::scanf("%d.%d.%d", &day, &mounth, &year) != 3) {
        std::cerr << "Input must be in 'day.month.year' format.\n";
        return EXIT_FAILURE;
    }

(我们需要包括<cstdlib>来定义EXIT_FAILURE)。

在打印日期名称的switch中,我们有一个永远无法到达的case 7。我想那是case 0的本意。

我们可以使用一个日期名称数组,而不是switch

如果我们将Weekday枚举定义为以Sunday开头(这将等于零),则可以将剩余的天数直接转换为Weekday (intenum类型是可转换的)。如果不允许更改顺序,则可以更改算法以将值减少1。

总是生产完整的生产线输出。每一行都应该以'\n'结尾。

拼写:在一个小程序中并不重要,但在较大的程序中(特别是作为团队成员工作时),拼写错误会使搜索代码变得困难。对诸如“月”和“平日”这样的单词,要尽量使用正确的拼写。

该算法可以进行改进。我们可以计算出在不同的年份中增加多少天,而不单独循环(并且注意,我们只关心模块7的天数,所以我们每年只需要计算1或2天)。

现在继续使用迭代算法,让我们来检查它的问题。

如果我们进入不到1582年的一年,我们就会给出一个毫无意义的结果--最好是对此进行测试,并给出一个错误消息。(例如,输入一年中的最后两位数字很可能是用户错误)。

我们有一个循环,测试它是否是最后一次迭代。而不是这样做,使用一个循环与一个少的迭代,并移动最后一个迭代代码后的循环。看起来是这样的:

代码语言:javascript
复制
    for(int i = 1582; i < year; i++){
        if(isLeapYear(i)){
            days += 366;
        } else {
            days += 365;
        }
    }

    for(int j = 1; j < mounth; j++) {
        if(j == 1 || j == 3 || j == 5 || j == 7 || j == 8 || j == 10 || j == 12){
            days += 31;
        } else if (j == 4 || j == 6 || j == 9 || j == 11){
            days += 30;
        } else if (j == 2){
            if(isLeapYear(year)){
                days += 29;
            } else {
                days += 28;
            }
        }
    }

    days += day;

这听起来不更合理吗?

“月份”循环中的大if可能更清晰一些,比如switch --或者是数组中的查找。一定要先检查0≤月≤12-更多的错误报告!

这里有一个隐秘的bug:

int a = (days-3)%7;

1582年的头几天将产生负值。这表明了测试“边缘案例”的价值--我们真的应该在第一天就试一试。

我们可以通过编写(days+4) % 7来确保一个正值。或者用值4初始化days (或者更好,使用Thursday,对应于1581-12-31),而不是0,所以我们不需要在最后进行这种调整。

票数 1
EN

Code Review用户

发布于 2022-10-18 14:37:34

一般观测

关于代码,我能说的最好的事情是,至少它在代码中没有using namespace std;

不知道为什么要使用std::function来实现这一点,但是,更多的函数会使这段代码更容易读、写、调试和维护。

在C和C++中,数组和枚举默认为零(0),而不是1。这可以用来简化代码。

代码中没有错误检查,任何具有用户输入的代码都需要进行错误检查。代码不会提示用户输入值,也不会指定用户应该使用的格式。

scanf()函数是一个C库函数,最好使用C++输入,如std::cin。可能代码应该输入一个字符串,然后将字符串转换为数字。

通常,switch/case语句应该包括一个不使用适当值时的default大小写。

不清楚为什么创建枚举Weeknday,因为它没有被实际使用。

幻数

代码中有很多魔术数字(1、3、5、7、12、30、31、28、29、365、366),最好是为它们创建符号常量,以使代码更加可读性和易于维护。这些数字可以在许多地方使用,只需编辑一行就可以更改它们,从而使维护变得更容易。

代码中的数值常数有时被称为幻数幻数,因为它们没有明显的意义。在堆栈过流上对此进行了讨论。

符号常量用法的一些示例:

代码语言:javascript
复制
constexpr int LEAP_YEAR_COUNT = 366;
constexpr int NORMAL_YEAR_COUNT = 356;
constexpr int LEAP_FEBRUARY_COUNT = 29;
constexpr int FEBRUARY_COUNT = 28;

int main() {

    // Get date
    int day, mounth, year;
    scanf("%d.%d.%d", &day, &mounth, &year);

    // Count days
    int days = 0;
    for (int i = 1582; i <= year; i++) {
        if (i != year) {
            if (isLeapYear(i)) {
                days += LEAP_YEAR_COUNT;
            }
            else {
                days += NORMAL_YEAR_COUNT;
            }
        }
        ...

优先优化

在某些情况下,您可以使用条件赋值而不是if then else

代码语言:javascript
复制
        if (i != year) {
            days += (isLeapYear(i)) ? LEAP_YEAR_COUNT : NORMAL_YEAR_COUNT;
        }

有时这被称为三元,在C和C++以及其他一些编程语言中很常见。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

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

复制
相关文章

相似问题

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