题解 P5707 【【深基2.例12】上学迟到】
题目的各种坑点已经有人提醒了,在此就不赘述了。本文重点在于标准库 <time.h> 的使用以及一些细节讨论
<time.h> 中定义了三个用于表达时间的类型:
- clock_t //用于存储处理器时间
- time_t //用于存储Unix时间戳,即UTC 1970/01/01 00:00:00开始所经过的秒数
- struct tm //用于存储适合阅读的时间和日期
以及一些处理时间的函数:
- char asctime(const struct tm timeptr) // unsafe
- clock_t clock(void)
- char ctime(const time_t timer) // unsafe
- double difftime(time_t time1, time_t time2)
- struct tm gmtime(const time_t timer) // unsafe
- struct tm localtime(const time_t timer) // unsafe
- time_t mktime(struct tm *timeptr)
- size_t strftime(char str, size_t maxsize, const char format, const struct tm *timeptr)
- time_t time(time_t *timer)
借助这些,我们可以轻松解决掉这道题的大多数坑。 (然而标准库本身就是个天坑)
首先,我们需要一个变量作为题目中的最终到达时间,而且这个变量必须要易于设定。 <time.h> 定义的三种类型中,只有 struct tm 符合这一要求。
出于严谨的态度,我们先来看看它包含些什么吧。 (以下代码选自相关头文件,平台:gcc 10.2.0, linux)
/* ISO C `broken-down time' structure. */
struct tm
{
int tm_sec; /* Seconds. [0-60] (1 leap second) */
int tm_min; /* Minutes. [0-59] */
int tm_hour; /* Hours. [0-23] */
int tm_mday; /* Day. [1-31] */
int tm_mon; /* Month. [0-11] */
int tm_year; /* Year - 1900. */
int tm_wday; /* Day of week. [0-6] */
int tm_yday; /* Days in year.[0-365] */
int tm_isdst; /* DST. [-1/0/1]*/
};
从上述内容我们可以看出,其中的 tm_sec, tm_min, tm_hour 正是我们所需的。故而对其初始化。
struct tm arriving_time = {
.tm_sec = 0,
.tm_min = 0,
.tm_hour = 8,
// 以下成员最好进行初始化,以避免因闰秒导致错误(真要在OJ里出现这个问题赶紧买彩票吧)
.tm_mday = 5,
.tm_mon = 6 - 1, // 不要忘记-1
.tm_year = 2021 - 1900
// 其他成员可自行初始化
};
不要奇怪,这种初始化方式叫做“指派符初始化”,可以直观且方便的对结构体成员进行初始化。这一特性从C99开始成为标准的一部分,在此之前作为编译器拓展被gcc支持。
然而,这个变量并不便于计算,故而我们还需将其转化为一个更适合计算的变量;从上面的介绍可以看出,time_t 比较适合计算。因此我们还需要通过 mktime() 这一函数进行转换
time_t t = mktime(&arriving_time);
然后就到关键的计算上了。从样例可以看出,计算所需分钟数时如果出现了余数则要+1。对于余数处理,可使用条件运算符简化代码。记住, time_t 的单位是秒!
time_t seconds_on_way = (s / v + ((s % v) ? 1 : 0) + 10) * 60;
t -= seconds_on_way;
最后,我们还需要将计算结果进行转换输出。
//struct tm leaving_time = *gmtime(&t); // WRONG①
//struct tm *leaving_time = localtime(&t); // DANGEROUS②
//struct tm leaving_time = *localtime(&t); // CORRECT③
// PERFECT④
struct tm leaving_time;
localtime_r(&r, &leaving_time); // POSIX
//localtime_s(&t, &leaving_time); // C11
//localtime_s(&leaving_time, &r); // MSVC
char answer[100] = "\0";
strftime(answer, sizeof(answer), "%H:%M", &leaving_time);
printf("%s\n", answer);
注意一下,这里有几个容易忽略的坑:
- 要用 localtime() 函数而非 gmtime() 函数
- 返回值所指向内容的生命周期
第一点:我们应该使用 localtime() 而非 gmtime() ,gmtime() 会自行根据当前系统时区与UTC的时间偏移运算出UTC时间。而我们这里所写程序中默认是使用localtime的,如果使用 gmtime() ,最终输出的结果会与答案产生8小时的偏差。
第二点:注意到 localtime() 返回值类型为 struct tm * ,我们很自然会将 leaving_time 设为一个指针。然而,这个函数的返回值指向的是一个内部静态寄存器的地址且与 gmtime() 共享。这也意味着:①当我们再次调用 localtime() 或 gmtime() 时之前返回值指向的内容会发生改变!②线程不安全。对于第一个问题,我们可以用一个中间变量将它保存下来(即写法③),但这对于第二个问题无能为力。如果一定要完美解决的话,在洛谷中可以按照④中的POSIX写法解决。
严禁抄袭!!!
#include <stdio.h>
#include <time.h>
int main(void)
{
struct tm leaving_time, arriving_time = {
.tm_sec = 0,
.tm_min = 0,
.tm_hour = 8,
.tm_mday = 5,
.tm_mon = 6 - 1,
.tm_year = 2021 - 1900
};
time_t seconds_on_way, t;
char answer[100] = "\0";
int s, v;
scanf("%d %d", &s, &v);
seconds_on_way = (s / v + ((s % v) ? 1 : 0) + 10) * 60;
t = mktime(&arriving_time);
t -= seconds_on_way;
localtime_r(&t, &leaving_time);
strftime(answer, sizeof(answer), "%H:%M", &leaving_time);
printf("%s\n", answer);
return 0;
}