题解 P5707 【【深基2.例12】上学迟到】

· · 个人记录

题目的各种坑点已经有人提醒了,在此就不赘述了。本文重点在于标准库 <time.h> 的使用以及一些细节讨论

<time.h> 中定义了三个用于表达时间的类型:

以及一些处理时间的函数:

借助这些,我们可以轻松解决掉这道题的大多数坑。 (然而标准库本身就是个天坑)

首先,我们需要一个变量作为题目中的最终到达时间,而且这个变量必须要易于设定。 <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);

注意一下,这里有几个容易忽略的坑:

  1. 要用 localtime() 函数而非 gmtime() 函数
  2. 返回值所指向内容的生命周期

第一点:我们应该使用 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;
}