我又食言了。
上篇说好了明天继续,结果拖到了今天。不过这几天我也没有闲着,天天都有在debug。
不得不说debug真的是考验一个码农的耐心、细心和想象力的事情。
我这三天在闲下来的时候就会想想,到底是什么情况没有考虑到:是还有没想到的边界条件?是HDU服务器上的编译器和VS2010的差别?最后的找到的原因也真的是让我很无语。这里不禁要抱怨一下HDU ACM的Online Judge系统,您就不能把测试用例放出来么?至少把没通过的测试用例放出来行不行啊?到底什么地方错了还要自己去猜,真的是很让人抓狂的啊!
其实上篇给出的程序还是错误的,在一些边界情况下会得到错误的结果。上篇着重讲基础知识,所以这不影响。而正好这可以当成反面教材,用来这次讲debug的过程。
首先,假设你把这个程序放到了Online Judge里面跑,满心欢喜地认为要结束了,结果系统返回了个Wrong Answer! 肿么办!?
1.考虑用更多的测试用例,来检查结果是否正确。但是测试用例并不好取,随机取的话,要手工计算结果也很费事。所以可以考虑用365,3650,36500这些值方便计算。
上面的例子测试都通过了怎么办?
2.这时就要多考虑边界的情况了。因为36500这种值都通过了说明闰年的计算都是没问题的。那么边界的情况有哪些呢?边界情况可以是一年的结束或者结尾,比如2000.12.31,2600.1.1;还可以是平年或者闰年的2月最后一天,比如2004.2.29,2100.2.28,2100.3.1;还可以是多年后(或多年前)的当天(或前天、明天),比如1999.3.24,3000.3.22。另外,有个小技巧值得注意:在判断语句中的>和<号要考虑==情况是不是也能正常运行,对边界问题往往也很有帮助。
如果有例子测试出来结果是错的,但是找不出错在哪怎么办?
3.这时就要借助Visual Studio里面的debug工具了。运用工具,比自己在程序里添加输出语句调试要方便的多,也快速的多。Debug的方法具体参见博客:http://gjianw217.blog.163.com/blog/static/2614418201222994650926/
有了工具,还要说debug的方法。对于出错的例子,可以手算出程序运行每一步,对应变量应该出来的结果是什么,然后在debug的时候进行对照,找出出错的地方。
如果还是有问题怎么办?
4.这个时候可能你看着程序头都大了,不如试着去做代码的简化和模块化,并且做一些注释。这可以调节一下纠结的逻辑混乱的大脑,并且多做一些模块化的工作,比如抽象出来更多的类或者函数,画一张类与函数关系图,这对后续的debug作用和启发也很大。实在不行,把这个代码放几天,每天就花几分钟去想想,不要浪费太多的时间。有时脱身出来,才能想到更广阔的问题。
经过漫长的三天,我的程序终于调试好了!
下面上代码:
1 #include <iostream> 2 #include <iomanip> 3 using namespace std; 4 int year=2013, 5 month=3, 6 day=24; 7 int monthDay[2][13]={ {0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, 8 {0,31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; 9 bool IsLeapYear(int x) 10 { 11 return x%400==0 || x%100!=0&&x%4==0 ; 12 } 13 void Xiaoq(int D) 14 { 15 int delta=D, 16 y=year, 17 m=month, 18 d=day; 19 20 y=year+delta/365; //计算穿越到的大致年份 21 delta=delta-delta/365*365; //计算穿越天到该年份同一天相差的天数delta 22 if( delta==0 ) //保证delta是个正数。如果delta==0,那么就回溯365天。 23 { 24 y--; 25 delta=365; 26 } 27 for(int yit=year; yit<y+1; yit++) //根据穿越过的闰年,进行delta微调,调整时也要保证delta是个正数 28 { 29 if( IsLeapYear(yit) ) 30 { 31 --delta; 32 } 33 } 34 if( delta<0 ) //如果delta<0,那么需要回溯一年 35 { 36 delta= (IsLeapYear(y)?366:365) +delta; 37 y--; 38 } 39 40 while(delta>0) 41 { 42 if( delta>monthDay[IsLeapYear(y)][m] ) //delta肯定小于y年的天数,这时先遍历月份,来减小delta 43 { 44 delta=delta-monthDay[IsLeapYear(y)][m]; 45 46 m= m+1==13?1:m+1; //月份+1时要考虑12月份的边界,以及边界对年的影响 47 if( m==1 ) 48 y++; 49 } 50 else 51 { 52 if( delta>monthDay[IsLeapYear(y)][m]-d) //月份遍历结束后,delta肯定小于当月的天数,这时遍历天数,来减小delta 53 { 54 d=delta-(monthDay[IsLeapYear(y)][m]-d); 55 m= m+1==13?1:m+1; 56 if ( m==1) 57 y++; 58 } 59 else 60 { 61 d=d+delta; 62 } 63 delta=0; 64 } 65 } 66 cout<<setfill('0')<<setw(4)<<y<<"/"<<setw(2)<<m<<"/"<<setw(2)<<d<<" "; 67 } 68 void Hr(int D) 69 { 70 int delta=D, 71 y=year, 72 m=month, 73 d=day, 74 days=0; 75 76 y=year-delta/365; 77 delta=delta-delta/365*365; 78 if( delta==0 ) 79 { 80 y++; 81 delta=365; 82 } 83 for(int yit=year; yit>y; yit--) 84 { 85 if( IsLeapYear(yit) ) 86 delta--; 87 } 88 if( delta<0 ) 89 { 90 delta= (IsLeapYear(y+1)?366:365) +delta; 91 ++y; 92 } 93 94 while(delta>0) 95 { 96 days= m>1?monthDay[IsLeapYear(y)][m-1]:monthDay[IsLeapYear(y-1)][12]; 97 if( delta>days ) 98 { 99 delta=delta-days; 100 m= m-1==0?12:m-1; 101 if( m==12) 102 y--; 103 } 104 else 105 { 106 if( delta>=d ) 107 { 108 d=days-(delta-d); 109 m= m-1==0?12:m-1; 110 if( m==12) 111 y--; 112 } 113 else 114 { 115 d=d-delta; 116 } 117 delta=0; 118 } 119 } 120 cout<<setfill('0')<<setw(4)<<y<<"/"<<setw(2)<<m<<"/"<<setw(2)<<d<<endl; 121 } 122 int main() 123 { 124 int D; 125 int N=0; 126 127 cin>>N; 128 for(int i=0;i<N;i++) 129 { 130 cin>>D; 131 Xiaoq(D); 132 Hr(D); 133 } 134 return 0; 135 }
可以看到,代码比上篇有了翻天覆地的变化。简化的地方不谈了。讲讲错到底都出在哪。
1.在计算完大致年份之后,我认为delta是正的,从而进入后面while循环。然而有两个操作会导致delta<=0,它们是:
delta=delta-delta/365*365;
和后面计算闰年时delta的自减过程。
2.在月份自减和自加的过程中,没有考虑跨越边界时年份的增减。(至少没有全部考虑到……)
3.然后这都没有使代码得到Accepted的结果。突然我意识到用于接收输入的数组只开100个是不是太小了!因为每次程序运行时间只有0ms。(虽然最后通过时时间也只有0ms,说明程序时间复杂度本身就低。)
终于搞定了!生活真美好。