当前位置: 首页 > news >正文

C++ 的时间库之六:日历和时区

前面提到,C++ 11 的时间库提供了各种时钟、时间点以及时间间隔的计算与表达,但是却没有提供日期相关的类型,也没有提供与时区有关的本地时间转换等支持组件,所以用起来不是很顺手。直到 C++ 20 终于补齐了这块短板,时间库具备是时间、日期和时区的完整支持,终于可以替代不安全的 C 函数接口。本篇主要就是介绍 C++ 20 新增的日历和时区相关内容。

1 Time of day

​ 设想一个与时间显式有关的场景,我们拿到了一个当前系统时间的时间点,希望在 UI 界面上显式这个时间。C++ 11 的方法就是先用时间点的 to_time_t() 方法将其转成一个 time_t 类型的秒的计数,然后再用 C 标准库提供的 localtime() 或 gmtime() 系列函数转成离散的时间数据结构 tm,这样就得到以本地时间表示的具体的时、分、秒以及日期信息,可用于界面的显示。这个过程存在两个问题,首先是与 C 的接口互转显得很突兀,其次是将高精度的时间点转成以秒为单位的 time_t 类型计数器会丢失时间精度。当然,我们也可以硬刚 chrono 库,借助时间点和时间间隔的运算符重载拆出具体的时间信息,比如:

auto sys_now_dura = system_clock::now().time_since_epoch();
auto rest_of_day = sys_now_dura % 24h;
auto hour = rest_of_day / 60min;
auto rest_of_hours = rest_of_day % 60min;
auto minute = rest_of_hours / 60s;
auto rest_of_minutes = rest_of_hours % 60s;
auto second = duration_cast<seconds>(rest_of_minutes).count();

//输出 hour:minute:second 

​ C++ 20 提供的hh_mm_ss能更简单地将一个时间间隔分解成时、分、秒的形式,形如其名,定义如下:

template< class Duration >
class hh_mm_ss;

hh_mm_ss提供了几个成员函数,可以很方便地获取分离出来的时、分、秒信息。尽管它有一个 duration 类型的模板参数,但是在使用的时候,编译器可以根据构造函数的参数类型推导出这个模板参数,所以可以省略,比如:

hh_mm_ss hms(5h+43min+12s);  //自动推导的 duration 类型是 seconds

assert(hms.hours().count() == 5);
assert(hms.minutes().count() == 43);
assert(hms.seconds().count() == 12);

利用hh_mm_ss的这个特点,获得当前时间的时、分、秒的代码就非常简单了:

hh_mm_ss hms(system_clock::now().time_since_epoch() % 24h);

auto hour = hms.hours().count(); //整数类型的小时数
auto minute = hms.minutes().count(); //整数类型的分钟数
auto second = hms.seconds().count(); //整数类型的秒数

​ 除了hh_mm_ss类,C++ 20 还提供了判断时间是上午时间还是下午时间的函数,以及转换 12 小时制和 24 小时制的函数。使用也是非常简单:

hh_mm_ss hms(11h+43min+12s);

assert(is_am(hms.hours()));
assert(is_pm(hours(16)));

auto half_h = make12(hours(16));
assert(half_h.hours().count() == 4);

auto full_h = make24(hours(6), true);
assert(full_h.count() == 18);

hh_mm_ss类的构造函数和主要的成员函数都是 constexpr 类型,这意味着可以声明 constexpr 类型的hh_mm_ss对象,同时也可以在其他常量表达式或常量函数中使用hh_mm_ss

2 日历

2.1 基本日历单位

2.1.1 year、month 和 day

​ 既然时间有hh_mm_ss,那么日期应该有year_month_day吧?没错,不仅有year_month_day,还有year_monthmonth_dayyear_month_weekday等等。day、month 和 year 提供了单独的年、月、日基本日历单位的表达,这些对象本身并没有复杂的功能,只是提供了基本的构造、加减运算和比较运算。比如 day,表示一个月内的日期,正常情况下,其值的范围应该是 [1-31] 区间,但是可以用 0 -255 范围内的值初始化一个 day 对象,它不抱怨并不表示它是个有效的日期,可以用成员函数 ok() 来判断一个 day 对象是否是一个有效的日期:

day md{ 15 };
assert(md.ok());
md -= 4d; //前四天是几号?
assert(md == 11d);
md += 21d; //加上 21 天,变成 32
assert(!md.ok()); // 不是合法的日子

注意 std::chrono::daystd::chrono::days 的区别,前者是日历单位,后者是一个以天为计数单位的 duration。day 还支持 << 输出运算符,以及 from_stream() 函数,使用起来也很简单:

day td{21};
std::cout << td << std::endl; //21

std::istringstream is{ "22" };
std::chrono::from_stream(is, "%d", td);
assert(td == 22d);

​ 至于 month,一年 12 个月是固定的,chrono 库也定义了 12 个 month 类型的常量,分别表示 12 个月,可以在代码中直接使用它们:

constexpr month January{1};
constexpr month February{2};
constexpr month March{3};
constexpr month April{4};
constexpr month May{5};
constexpr month June{6};
constexpr month July{7};
constexpr month August{8};
constexpr month September{9};
constexpr month October{10};
constexpr month November{11};
constexpr month December{12};

需要注意,month 的 ++ 和 – 运算符重载是带回绕的,十二月的下一个月是一月,这很容易理解,但是写出这样的代码就会循环到地老天荒:

for(month m = January; m <= December; m++) { ... }

​ year 也是作为占位符类型配合 year_month_dayyear_month_weekday 等类型使用,比如:

year_month ym2{ year{2019}, month{5} };
//更推荐这种写法:year_month ym{ 2019y/May };

当然,year 有一个判断闰年的成员函数,还是比较贴心的:

year last_year{ 2023 };

assert(!last_year.is_leap());

2.1.2 weekday

​ weekday 和 weekday_indexed 表示星期,其中 weekday_indexed 表示第几个星期“几”。这里需要记得几个关于星期的常量以及它们的值,在代码中使用它们会展示更好的可读性:

constexpr weekday Sunday{0};
constexpr weekday Monday{1};
constexpr weekday Tuesday{2};
constexpr weekday Wednesday{3};
constexpr weekday Thursday{4};
constexpr weekday Friday{5};
constexpr weekday Saturday{6};

weekday 支持 << 运算符,默认输出格式是星期的英文缩写,当然,这和 locale 的设置有关,设置中文 locale,也可以让它显式中文星期,比如:

weekday wi = Monday;
std::cout << wik << '\n';  // 输出 Mon
std::locale zh_loc("zh_CN");
std::cout.imbue(zh_loc);
std::cout << wik << '\n';  // 输出 周一    

from_stream() 函数可以从输入流中初始化一个 weekday 对象,比如下面的代码展示了从字符流中初始化 weekday 对象:

weekday wi;
std::istringstream is{ "Friday" };
std::chrono::from_stream(is, "%a", wi);
assert(wi == Friday);

weekday 还重载了operator [],返回一个 weekday_indexed 对象或 weekday_last 对象。如果调用时指定的参数是表示 index 的整数值,则返回值类型是 weekday_indexed,表示某个月的第几个星期“几”。如果调用参数是 last 标志(tag),则返回值类型是 weekday_last,表示某个月的最后一个星期“几”。weekday_indexed 和 weekday_last 主要是配合 year_month_weekday 或 month_weekday 这些日历类型,组合成某个月的第几个星期“几” 这样的效果。比如下面这两个例子:

weekday wik = Monday;
wik += 2d; //wik = Tuesday
//wix 表示第2个星期三(Tuesday)
weekday_indexed wix = wik[2]; 
year_month_weekday tmw{ 2024y/October/wix }; //2024年10月的第2个星期三
auto ltp = local_days(tmw); //转成以天为时间间隔单位的本地时间点
std::cout << ltp << std::endl; //2024-10-09

//wdl 表示最后一个星期三(Tuesday)
weekday_last wdl = wik[last]; 
year_month_weekday tmw2{ 2024y/October/wdl }; //2024年10月的最后一个星期三
auto ltp2 = local_days(tmw2); 
std::cout << ltp2 << std::endl; //2024-10-30

2.2 日期

2.2.1 month_day 和 year_month_day

​ 表示几月几日形式的日期,可用 month_day,如果带上年份,可以用 year_month_day 或 year_month,使用起来可以自由组合,十分灵活。可以这么理解,month_day 类型表示一个相对时间,year_month 也不具体到日期,只有 year_month_day 才代表一个具体的日期。构造或初始化 year_month_day 需要指定具体的日期,比如:

year_month_day ymd{ October/01/2021 };
//或者
year_month_day ymd{ 2021y, October, 1 };

也可以使用 month_day 或 year_month “拼接”一个具体的日期,比如:

year_month ym{ 2019y/May };
year_month_day ymd2{ ym/12 }; //2019年5月12日

month_day md{ February/12 };
year_month_day ymd6{ 2012y/md }; //2012年2月12日
//或者:year_month_day ymd6 = 2012y/md; 

当然,也可以从以天为计算单位的时间点构造 year_month_day,比如:

year_month_day ymd{ floor<days>(system_clock::now()) };
assert(ymd.year() == 2021y);
assert(ymd.month() == month(12));
assert(ymd.day() == 27d);

上一篇介绍过 floor()函数,floor<days> 除了数学上的 floor 的意义,还做了转型操作,其结果是得到不超过这个时间的最大整数天数,因为 year_month_day 的构造函数只接受 duration 类型是 days 的时间点。

​ month_day、year_month_day、year_month 这些类型都支持 <<输出,也支持 from_stream() 从一个输入流中初始化对象:

month_day mday{ month{4}, day{8} };
std::cout << mday << std::endl;// Apr/08

std::istringstream is{ "03-27" };
std::chrono::from_stream(is, "%m-%d", mday);
assert(mday.day() == 27d);

不同的 locale 设置可能会影响输出的样式,比如月份信息,默认中性环境或英文环境,月份信息一般是月份的英文缩写,但是如果设置 locale 是中文,怎会显式中文月份名字,比如“三月”、“五月”。

2.2.2 operator / 操作符

​ 上一节的代码能正常工作还有一个原因是 chrono 库重载了 operator / 操作符,这个操作符重载不仅适用于 year_month_day,也适用于其他形式的日期,比如:

year_month ym{ 2019y/May }; //2019-05
month_day md{ February/12 }; //02-12

除了初始化日历元素,也可以在代码中直接使用由/组装的日期,比如:

auto nowdays = time_point_cast<days>(system_clock::now());//类似 floor<days> 
if (nowdays == October/01/2021) {
    // 开始放假
}

2.3 某一天是哪一天

2.3.1 last_spec 与 last

​ 前面已经用过 last 标签(tag)了,这里具体介绍一下 last_spec 和 last。last_spec 是一个空的 class 或 struct,它是一个 tag,是个标记,需要和其他日历类型配合使用,指示序列中的最后一个东西。根据类型上下文,它可以是一个月的最后一天,或者是一个月的最后一个星期几。而 last 则是一个 last_spec 类型的常量,这类似于 true 和 bool 之间的关系,一个是值,一个是类型:

constexpr last_spec last{};

last 通常作为占位符使用,它的意义与它出现的位置有关,当处在“天”的位置的时候,它就代表最后一天,比如:

year_month_day ymd{ February/last/2021 }; //ymd 表示 2021年2月的最后一天
assert(ymd.day() == 28d); // 平年2月的最后一天是28

//换个闰年试试
year_month_day ymd4{ February/last/2020 };
assert(ymd4.year().is_leap());
assert(ymd4.day() == 29d);

换个位置,它就表示另一个意思,比如作为星期的下标参数:

//tmw 代表2021年10月的最后一个星期日
year_month_weekday tmw{ 2021y/October/Sunday[last] }; 

//输出 5,表示那个月有 5 个星期日
std::cout << tmw.weekday_indexed().index() << std::endl; 

2.3.2 weekday_last 和 weekday_indexed

​ 前面已经提到过,weekday_indexed 表示某个月的第几个星期“几”,构造和初始化 weekday_indexed 需要指定星期“几”和第几个,比如构造一个表示第 2 个星期四的 weekday_indexed 对象,可以这样做:

weekday_indexed wdi{ Thursday, 2 }; 
//效果等价于
weekday_indexed wdi = Thursday[2];

借助于 weekday 的[]运算符,可以更方便地表示 weekday_indexed,比如 Thursday[2]。

​ weekday_last 表示某个月的最后一个星期“几”,至于“几”是什么,则通过 weekday_last 类的构造函数指定。下面的代码构造了一个 weekday_last,代表最后一个星期一,当然,也可以用 weekday 的[]运算符配合 last 标签达到同样的效果:

weekday_last wdl{ Monday }; 
//效果等价于
weekday_last wdl = Monday[last];

​ 前面介绍过,weekday_indexed 一般配合 month_weekday 和 year_month_weekday 使用,weekday_last 则配合 month_weekday_last 和 year_month_weekday_last 使用,后面介绍这些类型的时候还会给出具体的例子代码。

2.3.3 month_weekday 和 month_weekday_last

​ month_weekday 和 month_weekday_last 都是一个相对时间的概念,一般要配合 year 才能具体确定是哪一天。month_weekday 表示某个月的第几个星期“几”,构造和初始化 month_weekday 必须使用 weekday_indexed,不能使用 weekday,比如:

month_weekday mwd{ October/Monday[1] }; //mwd 表示10月的第1个星期一
month_weekday mwd2{ October/Monday }; //错误,不能使用 weekday

​ month_weekday_last 表示某个月的最后一个星期“几”,构造和初始化 month_weekday_last 需要用到 weekday_last,比如构造一个表示4月的最后一个星期一的对象,可以这么做:

weekday_last wdl{ Monday };//最后一个星期一
month_weekday_last mwd{ April/wdl }; //用 “/” 组合一下
//或者直接一点
month_weekday_last mwd{ April/Monday[last] };

​ chrono 定义了表示月份和星期的常量,建议在代码中直接使用这些常量,有助于代码可读性的提高。对于某个月的第几个星期“几”这种时间表达形式,结合这些常量和下标运算符,可以非常灵活地表达时间。比如这段代码判断当前日期是否是三个常见的美国节日:

auto thisYear = 2024y;
year_month_day ymd{ floor<days>(system_clock::now()) };
//母亲节、父亲节和感恩节
for (auto&& mw : { May/Sunday[2], June/Sunday[3], November/Thursday[4] }) {
    //结合年份后再转成时间点
    auto cncDay = sys_days(thisYear/mw);
    if (ymd == cncDay) {
        //...
    }
}

2.3.4 year_month_weekday 和 year_month_weekday_last

​ year_month_weekday 表示某个某年某月的第几个星期“几”,可以用 “year + month + weekday_indexed” 或 “year + month + weekday_last” 构造和初始化 year_month_weekday 对象,比如:

year_month_weekday ymw{ 2021y/October/Sunday[last] }; 
year_month_weekday ymw2{ 2018y/April/Monday[2] }; 

也可以借助 “year + month_weekday” 间接构造,比如:

month_weekday mwd{ October/Monday[1] };
year_month_weekday ymw3{ 2021y/mwd };

​ year_month_weekday_last 表示某个某年某月的最后一个星期“几”,只能用 “year + month + weekday_last” 方式或“year + month_weekday_last” 方式构造和初始化 year_month_weekday_last 对象,比如:

year_month_weekday_last ymwl{ 2021y/October/Sunday[last] };
//或
month_weekday_last mwd{ April/Monday[last] };
year_month_weekday_last ymwl2{ 2018y/mwd };

​ year_month_weekday 和 year_month_weekday_last 已经可以具体表示到哪个日期了,这两个类都提供了 operator sys_daysoperator local_days 类型转换操作符,这两个操作符可以将它们转换成一个 time_point(前面的例子代码已经用到了)。这个 time_point 的 duration 类型是 std::chrono::daysoperator sys_days 得到的时钟类型是 system_clock,表示系统时间或 UTC 时间,operator local_days得到的是 local_t 时钟,local_t 也是一个占位符,表示本地时间的时钟。比如上面的代码构造的 ymwl 表示 2021年10月的最后一个星期日,如果想知道具体是10月几号,可以使用 local_days 将其转换成一个 time_point<local_t, days> 类型的时间点,然后再转换成 year_month_day :

//根据时间点转成year_month_day
year_month_day ymd{ local_days(ymwl) }; 
//输出10月31日,可知 2021年10月的最后一个星期日是10月31日
std::cout << ymd.day() << std::endl; 

2.3.5 month_day_last 和 year_month_day_last

​ month_day_last 表示某个月的最后一天,也是个相对的日期:

month_day_last mdl{ February/last };

year_month_day_last 表示具体某年某月的最后一天,这是个确定的日期,可以直接指定年份和月份,比如:

year_month_day_last ymdl{ February/last/2020y }; //2020年2月的最后一天

也可以用 “year_month + last” 或“year + month_day_last” 拼接日期,比如:

year_month ym{ 2019y/May };
year_month_day_last ymdl1{ ym/last }; //2019年5月的最后一天

month_day_last mdl{ April/last };
year_month_day_last ymdl2{ 2022y/mdl }; //2022年4月的最后一天

​ year_month_day_last 也是个具体日期了,所以它也提供了 operator sys_daysoperator local_days 类型转换操作符,可以将这个时间转换成一个具体的时间点。这两个类型转换操作符的使用和 year_month_weekday_last、year_month_weekday 一样,比如获取 2020年2月的最后一天的具体日期,可以这样操作:

year_month_day_last ymdl{ February/last/2020 };
year_month_day ymd{ local_days(ymdl) }; //根据时间点转成year_month_day
std::cout << ymd.day() << std::endl; //2月29日,2020 年是闰年

当然,找一年中某个月的最后一天也可以直接用 year_month_day,因为这个类有一个将 year_month_day_last 转换成 year_month_day 的构造函数:

constexpr year_month_day(const year_month_day_last& _Ymdl) noexcept;

借助这个构造函数,可以直接用一个 year_month_day_last 类型的变量初始化 year_month_day 对象,从而直接得到这个日期,不需要 local_days 或 sys_days 做一次转换。

3 时区

​ 时区是 C++ 20 对 chrono 库的重要补充,很多人抱怨 C++ 11 的 chrono 库居然不提供系统时间转成本地时间的方法,以至于大家不得不借助于 C 的 localtime() 系列函数做这个转换。实际上,它们只是没赶上 C++ 11 这趟火车, C++ 20 补上时区这个东西后,chrono 的功能才算完整。借助于时区的概念进行时间转换,比呆板的 localtime() 函数更灵活。

3.1 时区数据库

​ IANA (Internet Assigned Numbers Authority)维护着一份包含地球上所有地区的时区数据库,这个数据库会定期更新。可以通过 get_tzdb_list()函数获得标准库中的数据库列表,一般最新的版本排在列表的第一位。提供这个函数的目的是为了兼容性,如果当前时区数据库因种种原因(领土争端)无法使用,可以从列表中找到其他版本的数据库。get_tzdb()函数可以直接获取当前数据库,如果不出意外,他应该就是数据库列表中的第一个。

​ tzdb 是 chrono 定义的一个类型,它代表了 IANA 数据库中的信息。这个数据结构中有些信息比较有意思,其中之一就是所有时区的列表,其实它的类型就是 std::vector<std::chrono::time_zone>,通过遍历这个列表,可以得到当前数据库中所有的时区信息。下面的代码打印所有的时区名字:

const tzdb& zdb = get_tzdb();
for (const auto& z : zdb.zones)
    std::cout << z.name() << std::endl;

tzdb 还包含了从 1972 年到现在所有闰秒的信息,下面的演示代码输出迄今为止所有闰秒发生的时间:

for (const auto& ls : dbz.leap_seconds)
    std::cout << ls.date() << std::endl;

3.2 time_zone

​ chrono 定义的 time_zone 是作为一个抽象类型使用的,所以不支持直接构造 time_zone,但是可以通过 chrono 库提供的 locate_zone() 函数或 current_zone() 函数获得一个 const 类型的 time_zone 指针。比如下面的代码例子:

const time_zone* tzShanghai = current_zone();
const time_zone *tzParis = locate_zone("Europe/Paris");
//等效于
const tzdb& dbz = get_tzdb();
const time_zone* tzShanghai = dbz.current_zone();
const time_zone *tzParis = dbz.locate_zone("Europe/Paris");

时区的名字不是随便起的,只能使用 IANA 数据库中支持的名字,上一节已经介绍过从 tzdb 中获取所有时区名字的方法了。

​ chrono 库初始化的时候就创建了所有 time_zone,所以你通过上述方法得到的 time_zone 指针不需要做释放处理。获取 time_zone 的主要目的有两个,一个是做系统时间(UTC)和本地时间的转换,另一个是构造 zoned_time。我们将在下一节介绍 zoned_time,本节先介绍如果做时间转换。time_zone 提供了两个成员函数用于系统时间和本地时间的转换。to_sys() 函数可以将一个本地时间转成系统时间,to_local() 函数可以将系统时间转换成本地时间,转换的依据就是 time_zone 自己所代表的时区信息。

​ 使用 time_zone 做时间转换,不会有时间精度丢失的问题,下面的代码演示了将系统时间转成本地时间的方法:

const time_zone* tzCurrent = current_zone();
std::cout << tzCurrent->to_local(system_clock::now()) << std::endl;//输出本地时间

当然,也可以将系统时间转成其他时区的时间,比如巴黎时间。下面的代码例子演示了如何将北京时间转换成巴黎时间:

//构造一个秒级的北京时间
local_time<seconds> beijingTp = {local_days{2024y/October/12d} + 14h + 18min + 22s};
auto sysTp = current_zone()->to_sys(locTp); 
auto parisTp = locate_zone("Europe/Paris")->to_local(sysTp); 

3.3 zoned_time 和 local_t

​ 处理时间相关的代码,最困扰的问题就是拿到一个 time_point,不知道是按照本地时间处理还是按照系统时间处理。在《C++ 的时间库之四:Clock》介绍时钟类型的时候,我们提到过 local_t,它不是真正的时钟类型,chrono 库用它作为本地时钟类型的占位符(或 tag)使用。有了这个类型占位符,一些显而易见类型错误就可以让编译器帮你检查:

local_time<seconds> localTp = ...;
//错误,to_local() 要求参数是 system_clock 类型的时间点
auto beijingTp = current_zone()->to_local(localTp); //编译错误

​ 但是很多运行时状况是编译器无能为力的,我们也不希望在每个使用时间的地方都做类型检查。zoned_time 就是为了解决这个问题而存在的,这是 zoned_time 的定义,它实际上是将一个时间间隔是 Duration 类型的时间点和一个时区绑定在一起:

template<class Duration, class TimeZonePtr = const time_zone*>
class zoned_time ;  

zoned_time 只限定了时间点的 Duration 类型,没有限定 Clock 类型,它通过构造函数自适应 Clock 类型,所以 local_t 类型占位符可以帮助 zoned_time 识别系统时间和本地时间。下面的代码演示了用系统时间构造一个当前时区的 zoned_time,并检查通过这个 zoned_time 获取本地时间和系统时间是否正确。

auto sys_now = system_clock::now();
zoned_time zonedtime{"Asia/Shanghai", sys_now}; //使用系统时间构造
//等效于:zoned_time zonedtime{locate_zone("Asia/Shanghai"), sys_now};
assert(zonedtime.get_sys_time() == sys_now);//true

//利用 time_zone 提供的方法获得本地时间
auto local_now = locate_zone("Asia/Shanghai")->to_local(sys_now); 
assert(zonedtime.get_local_time() == local_now);//true

使用 zoned_time 的好处就是不需要人肉记住每个时间点是系统时间还是本地时间,在使用系统时间或本地时间的地方相应地调用 get_sys_time() 或 get_local_time() 即可。

4 格式化支持

​ 本次修订将 format 格式支持部分放在独立的《C++ 的时间库之八:format 与格式化》一篇介绍,具体的例子请参考这一篇了解,本节就作为占位符放在这里了。

参考资料

[1] Marc Gregoire, Professional C++ (Fifth Edition), John Wiley & Sons, Inc., 2021

[2] Nicolai M. Josuttis, C++20 - The Complete Guide, http://leanpub.com/cpp20’

[3] P1636R2: Formatters for librarytypes

[4] D0355R4: Extending to Calendars and Time Zones

[5] https://en.cppreference.com/w/cpp/chrono

[6] https://en.cppreference.com/w/cpp/chrono/system_clock/formatter#Format_specification

关注作者的算法专栏
https://blog.csdn.net/orbit/category_10400723.html

关注作者的出版物《算法的乐趣(第二版)》
https://www.ituring.com.cn/book/3180

相关文章:

  • ArcGIS Pro技巧实战:高效矢量化天地图地表覆盖图
  • 使用python做http代理请求
  • Metal学习笔记八:纹理
  • Springboot基础篇(3):控制反转与Bean对象
  • 深度生成模型(二)——基本概念与数学建模
  • 4. 示例:创建带约束的随机地址生成器(范围0x1000-0xFFFF)
  • Vue+Element UI table表格,数据展示错位(已解决)
  • Gatling介绍
  • ArcGIS Pro可见性分析:精通地形视线与视域分析
  • 力扣-动态规划-139 单词拆分
  • 接上文 延申应用 halcon及代码
  • C#装箱拆箱机制详解
  • 可编辑73页PPT | DeepSeek自学手册-从理论模型训练到实践模型应用
  • Express + MongoDB 实现更新用户时用户名变化验证数据库是否存在,不变不验证
  • 使用Vue-Flow创建一个流程图可视化节点坐标查询器
  • SikuliX使用
  • java泛型是对范型参数类型的擦除
  • 自然语言处理:文本规范化
  • GDidees CMS v3.9.1本地文件泄露漏洞(CVE-2023-27179)
  • MSSQL2022的一个错误:未在本地计算机上注册“Microsoft.ACE.OLEDB.16.0”提供程序
  • 做便民网站都需要哪些模块/优化设计答案四年级上册语文
  • 北京市建委网站官网/凡科建站客服电话
  • 二次元网站开发的意义/免费二级域名注册申请
  • 哪些网站不扣流量/广东网络优化推广
  • 合肥网站建设政务区/活动推广朋友圈文案
  • 思勤传媒网站建设公司/精品成品网站入口