现代C:标准库:日期、时间与实用函数

发布时间:2026/9/12 1:23:14

现代C:标准库:日期、时间与实用函数 引言在前面的几讲中我都以较大的篇幅介绍了 C 标准库中的一些重要概念和相关接口的使用方式。除此之外标准库中还有一些功能十分明确使用方式也十分简单的常用接口这些接口也为日常的 C 应用开发提供了重要支持。因此在接下来的两讲中我将围绕这部分内容展开介绍。今天我们先来看看标准库中与日期、时间以及实用函数有关的内容。其中日期与时间的相关接口由头文件 time.h 提供而实用函数的功能则可被进一步细分为字符串与数值转换、随机数生成、动态内存管理以及进程控制等不同的几类这些功能对应的编程接口均由头文件 stdlib.h 提供。下面我们就来分别看看这两类接口的使用方式以及它们背后的一些基本原理。日期与时间首先来看由头文件 time.h 提供的日期与时间相关接口。那么在 C 语言中日期与时间的概念是怎样体现的又应该如何对它们进行操作和转换呢在构建应用程序时我们经常会用到日期与时间这两种概念。比如在记录日志时通常需要保存每个事件的确切发生日期和时间在进行优化时则需要通过测量代码的运行时间来寻找性能痛点而在生成随机数时甚至需要使用当前时间作为不同的随机种子。看到这里你可能已经发现了这一讲中我们提到的“时间”有两种不同的含义。一种是指时间上的跨度而另一种则指以小时、分钟、秒组成的确切时间点。至于具体是哪一种含义需要你结合上下文来理解。当然在可能会引起误解的地方我也会特别说明下。日历时间在 C 语言中时间可以被分为“日历时间Calendar Time”与“处理器时间Processor Time”。其中前者是指从世界协调时间UTC1970 年 1 月 1 日 00 时 00 分 00 秒到当前 UTC 时间所经历的秒数其中不包括闰秒。在 C 标准库中该值由自定义的类型关键字 time_t 表示。该类型通常对应于一个整数类型在某些老版本的标准库中可能被实现为 32 位有符号整型。同时time.h 头文件也提供了一个非常直观的名称为 time 的方法可用于获取这个值。来看下面这个例子#include stdio.h #include time.h int main(void) { time_t currTime time(NULL); if(currTime ! (time_t)(-1)) printf(The current timestamp is: %ld(s), currTime); return 0; }这里我们将 time 方法调用后的返回值保存在了变量 currTime 中。而当方法调用成功后即返回值不为 -1该值通过 printf 函数被打印了出来。不仅如此在获取到这个整型时间值后借由标准库提供的其他时间与日期处理函数我们还可以对它做进一步处理。比如将它格式化为本地时间并以特定的格式输出。继续来看下面这个例子#include stdio.h #include time.h int main(void) { time_t currTime time(NULL); if(currTime ! (time_t)(-1)) { char buff[64]; struct tm* tm localtime(currTime); if (strftime(buff, sizeof buff, %A %c, tm)) printf(The current local time is: %s, buff); // The current local time is: Saturday Sat Jan 8 16:30:49 2022. } return 0; }在这段代码中我们用与之前类似的方式获取了当前的日历时间该值被存放到变量 currTime 中。在代码的第 7 行通过使用名为 localtime 的方法我们可以将该日历时间转换成与本地时间相关的多种信息。这些信息将以不同字段的形式被存放在名为 tm 的结构对象中。接着通过调用 strftime 方法我们可以继续对这个时间对象进行格式化。该方法调用后会将生成的结果字符串存放到由变量 buff 对应的字符数组中。这里传入的第三个参数为一个包含有格式控制占位符的字符串。其中%A 用于显示完整的周工作日名称%c 用于显示标准日期和时间字符串。strftime 方法将根据占位符字符串的具体组成格式来输出相应的结果字符串。处理器时间接着我们再来看处理器时间CPU Time。顾名思义处理器时间即 CPU 资源被调度以支持程序在某段时间内正常运作所花费的时间。需要注意的是在默认情况下这个时间应该是应用运行所涉及的所有独立 CPU 所消耗时间的总和。C 标准库也为我们提供了一个直观的名称为 clock 的方法可用于返回这个值。来看下面这个例子#include time.h #include stdio.h int main(void) { clock_t startTime clock(); for(int i 0; i 10000000; i) {} clock_t endTime clock(); printf(Consumed CPU time is%fs\n, (double)(endTime - startTime) / CLOCKS_PER_SEC); return 0; }位于代码第 4 行的 clock 方法在调用后会返回类型为 clock_t 的值。该类型由标准库的具体实现定义因此其值可能为整数也可能为浮点数。不同于日历时间的是为了更精确地计算 CPU 耗时处理器时间并不直接以“秒”为单位而是以 “clock tick” 为单位。为了将这个时间换算为秒你需要将它除以标准库中提供的宏常量 CLOCKS_PER_SEC。该常量表明了在当前系统上每 1 秒钟对应的 clock tick 次数。需要注意的是这里我们提到的 clock tick与程序运行所在计算机的实际物理 CPU 频率没有直接关系。应用程序可以通过读取计算机上的硬件定时器来获得对应进程的 CPU 使用时间。对于程序运行来说一段时间内花费的处理器时间与墙上时钟时间Wall-clock Time可能并不一致。前者依赖于程序使用的线程数量、所在平台的物理 CPU 核数以及操作系统调度 CPU 的具体策略等。而后者则是现实世界的时间流逝也就是一个恒定递增的值。因此调用一次 clock 方法所返回的处理器时间一般没有太多意义。通常我们会按照上面例子中的方式来使用这个时间即在一段代码的前后分两次获取处理器时间并通过计算两者之间的差值来了解 CPU 执行这段代码所花费的时间。当然为了方便你更好地理解处理器时间与墙上时钟时间的区别你可以在具有多核 CPU 的计算机上编译和运行下面这段代码并在评论区告诉我你的运行结果。关于代码的具体实现细节你可以尝试参考注释进行理解如果有问题也可以在评论区随时跟我讨论。#include stdio.h #include time.h #include stdlib.h #include threads.h typedef struct timespec ts_t; int run(void* data) { // 模拟的耗时任务 volatile double d 0; for (int n 0; n 10000; n) for (int m 0; m 10000; m) d d * n * m; return 0; } int main(void) { // 首次记录日历时间与处理器时间 ts_t ts1; timespec_get(ts1, TIME_UTC); clock_t t1 clock(); // 创建两个线程做一些耗时任务 thrd_t thr1, thr2; thrd_create(thr1, run, NULL); thrd_create(thr2, run, NULL); thrd_join(thr1, NULL); thrd_join(thr2, NULL); // 再次记录日历时间与处理器时间 ts_t ts2; timespec_get(ts2, TIME_UTC); clock_t t2 clock(); // 分别计算和打印处理器时间与墙上时钟时间耗时 printf(CPU time used (per clock()): %.2f ms\n, 1000.0 * (t2 - t1) / CLOCKS_PER_SEC); printf(Wall time passed: %.2f ms\n, 1000.0 * ts2.tv_sec 1e-6 * ts2.tv_nsec - (1000.0 * ts1.tv_sec 1e-6 * ts1.tv_nsec)); return 0; }其他相关处理函数除了我在上面介绍过的一些常见日期与时间处理函数外C 标准库还提供了另外一些相关函数我将它们整理在了下面的表格中供你参考。当然这里并没有包含那些已经被标记为“废弃”的接口。你可以点击这个链接来查看更多信息。Y2038 问题到这里我讲完了如何在 C 代码中使用日期和时间操作的相关函数接下来我想和你讨论一个可能由 time_t 类型引发的问题。我在讲日历时间的时候提到过某些旧版本的 C 标准库在实现用于存放日历时间的 time_t 类型时可能会采用 32 位有符号整数。而在这种情况下time_t 所能够表示的时间跨度便会大大缩小并会在不久之后的 UTC 时间 2038 年 1 月 19 日 03 时 14 分 08 秒发生上溢出。当该类型变量溢出后其表示的具体日期和时间将会从 1901 年开始“重新计时”。你可以通过下图图片来自 Wikipedia来观察这个问题的发生过程。可以说这是一个全球性问题严重性甚至可以与 Y2K 等问题比肩。由于 C 语言被广泛应用在各类软硬件系统中因此从常见的交通设施、通信设备到某些早期的计算机操作系统它们都可能会在那时受到 Y2038 问题的影响。看完了与头文件 time.h 相关的内容接着我们再来看看 stdlib.h 头文件提供的众多实用函数。由于这些函数的功能十分混杂我将它们分为了几个不同的类别来分别为你介绍。首先来看数值与字符串转换的相关接口。字符串到数值的转换这类接口的使用方式都十分简单直接来看下面这段代码#include stdio.h #include stdlib.h #include errno.h int main(void) { // 一次性字符串到数值转换 const char* strA 1.0; printf(%f\n, atof(strA)); // 带溢出检查的转换函数执行后会保存不能被转换部分的地址 const char* strB 200000000000000000000000000000.0; char* end; double num strtol(strB, end, 10); if (errno ERANGE) { // 判断转换结果是否发生溢出 printf(Range error, got: ); errno 0; } printf(%f\n, num); return 0; }stdlib.h 头文件提供了众多函数可用于将一个字符串转换为特定类型的数字值。在上面代码的第 7 行我们使用名为 atof 的函数将字符串 strA 转换为一个双精度浮点数。实际上以字母 “a” 开头的这类函数只能对字符串进行一次性转换。相对地在代码的第 11 行名为 strtol 的函数将字符串 strB 转换为了对应的长整型数值。而这一类以 “str” 开头的函数会在每次执行时判断转换结果是否发生溢出同时保存不能被转换部分的地址。在这种情况下通过再次调用这类函数我们便能够对剩余部分的字符串继续进行转换直至将整个字符串处理完毕。关于这些函数的更多信息你可以点击这个链接进行查看。生成随机数作为实用函数的一部分“随机数生成”是一个不可或缺的重要功能。同样地我们也可以通过配合使用 stdlib.h 提供的 rand 与 srand 方法来生成随机数。它们的基本用法如下所示#include stdio.h #include stdlib.h #include time.h int main (void) { srand(time(NULL)); // 初始化随机数种子 while (getchar() \n) printf(%d, rand() % 10); // 生成并打印 0-9 的随机数 return 0; }可以看到我们首先在代码的第 5 行使用 srand 方法设定了程序每次运行时需要使用的随机数种子。在代码第 7 行rand 函数的调用会产生范围为 [0, RAND_MAX] 的随机数。通过对它进行求余处理可以将结果限定到一个指定的范围。对于大多数 C 标准库实现来说rand 函数在内部会采用“线性同余发生器Linear Congruential Generator”等伪随机算法来计算函数每次调用时需要产生的随机数。这也就意味着该函数产生的随机数本质上并不是随机的。如果我们没有使用 srand 函数设置新的随机数种子那么当每次程序重新运行时通过 rand 函数产生的随机数序列都将会是相同的。而这个种子便会作为 rand 函数在计算下一个随机数时所采用算法的输入参数。事实上计算机无法生成“真正的随机数”。正如 MIT 教授 Steve Ward 说的那样“传统计算机系统最不擅长的一件事就是抛硬币”。计算机软件的执行会按照既定的算法展开因此当输入和算法不变时输出结果就变得有迹可循。即使我们可以用更复杂的算法来让输出的变化模式变得难以琢磨但无论如何这都并非真正的随机。而伪随机数算法之所以可被用来生成随机数则是因为从统计学角度来讲其生成的数字符合随机数在均匀性、独立性等特征上的要求。并且伪随机数的生成不需要特殊的硬件支持。同时在大多数场景中伪随机数也可以满足基本的使用需求。动态内存管理动态内存管理本质上就是堆内存管理。我曾在 08 讲 中介绍过 VAS 中堆的概念以及如何使用 malloc 与 free 函数来在堆上分配和释放一段内存空间。但实际上除这两个函数外C 标准库还为我们提供了另外一些函数可用于在分配堆内存时进行更加精确的控制。你可以通过下面的表格来了解这几个函数的基本功能。由于它们的使用方式较为简单这里我就不详细介绍了。进程控制接下来我们来看看实用函数中与进程控制相关的内容。虽然 C 标准库为我们提供了进程控制的相关能力但这个能力实际上却十分有限。借助标准库提供的接口我们可以控制程序的退出形式正常终止、异常终止、快速终止等获取当前系统的环境变量或是与宿主机上的命令处理器进行交互。但除此之外我们无法再控制进程的其他行为比如创建进程或使用进程间通信。对于其中几个函数的使用方式你可以参考下面这个例子#include stdio.h #include stdlib.h void exitHandler() { printf(%s\n, getenv(PATH)); } int main(void) { if (!atexit(exitHandler)) { exit(EXIT_SUCCESS); } return 0; }这里在代码的第 7 行我们使用函数 atexit 为程序注册了一个回调函数。这个函数会在程序显式调用 exit 函数时或从 main 函数内正常退出时被触发。在对应的回调函数 exitHandler 中我们使用 getenv 函数获取并打印了当前宿主机上环境变量 PATH 的值。当回调函数注册成功后返回整型数值 0通过显式调用函数 exit我们正常退出了当前程序。此时回调函数被调用环境变量 PATH 的值被打印了出来。还有一些我没提到的函数它们的使用方式也很简单。同样地我将它们整理在了下面的表格中供你参考。这里需要你注意exit、quick_exit、_Exit以及 abort 这四个可用于终止程序运行的函数它们实际上对应着不同的使用场景。其中exit 函数在退出程序时会进行一系列资源清理工作比如冲刷并关闭各种 IO 流、移除由函数 tmpfile 创建的临时文件等。除此之外它还会在中途触发用户注册的回调函数以进行自定义的收尾工作。但相对地quick_exit 函数在终止程序时并不会进行上述资源清理工作。它仅会通过回调函数来执行用户自定义的收尾工作。 _Exit 函数则更加彻底它会直接终止程序的执行而不做任何处理。不同于这三类函数abort 函数在调用时会向当前程序发送信号 SIGABRT并根据情况选择终止程序或执行相应的信号处理程序。其他接口除了上面提到的这几类重要接口外stdlib.h 中还包含有一些与搜索排序、整数算数、宽字符串转换相关的接口。它们的使用方式也十分简单这里我就不一一介绍了。你可以参考下表来了解这些最常用接口的名称与功能也可以点击这个链接来了解有关它们的更多信息。总结好了讲到这里今天的内容也就基本结束了。最后我来给你总结一下。这一讲我主要介绍了 C 标准库中与时间日期处理、字符串和数值转换、随机数生成、动态内存管理、进程控制以及搜索排序等功能相关的接口。其中第一部分功能的接口由 time.h 提供其余部分由 stdlib.h 提供。C 语言中的时间可以被分为日历时间与处理器时间。通过名为 time 与 clock 的两个接口我们可以分别获得与它们对应的两个值。进一步借助 localtime 与 strftime 等接口日历时间可以被转换为本地时间并按照指定的形式进行格式化。而处理器时间由于在默认情况下以 clock tick 为单位因此需要将它除以宏常量 CLOCKS_PER_SEC从而得到以秒为单位的值。通过使用 atof 与 strtol 等接口我们可以实现字符串到数字值的转换。其中前一类以 “a” 开头的接口仅能对字符串进行一次转换而后一类以 “str” 开头的接口则可在对字符串进行数值转换的基础上同时检查转换结果是否发生溢出并保存不能被转换部分的地址。通过配合使用 rand 与 srand 接口我们可以在 C 程序中生成伪随机数。在大多数标准库中rand 函数会使用伪随机算法来实现。因此为了使每次调用 rand 函数生成的随机数序列都不尽相同在调用该接口前可以配合使用 srand 与 time 函数来为其设置随时间变化的不同随机数种子。malloc、calloc以及 free 等接口的实现方便了我们在程序中动态操作堆内存。而通过调用 exit、abort、quick_exit 等接口我们可以精确地控制程序在退出时的具体行为。除此之外abs、qsort、bsearch、mblen 等接口的提供也使得标准库在搜索排序、整数算数、宽字符串转换等方面为 C 编程提供了一定帮助。
延伸阅读

更多相关文章

2026/9/9 9:04:24

现代C:标准库:信号与操作系统软中断有什么关系?

相信你在第一次学习编程时&#xff0c;也写出过与下面这段类似的代码&#xff1a;#include <stdio.h> int main(void) {int x 10;int y 0;printf("%d", x / y);return 0; }可以很明显地看到&#xff0c;这里在代码中&#xff0c;我们通过 printf 函数打印出了…

2026/9/7 7:43:27

WSL2与Ubuntu24.04开发环境搭建与优化指南

1. WSL2与Ubuntu24.04环境搭建全攻略最近在Windows系统上折腾开发环境&#xff0c;发现WSL2&#xff08;Windows Subsystem for Linux 2&#xff09;配合Ubuntu24.04 LTS的组合特别香。相比传统虚拟机&#xff0c;WSL2性能更好、资源占用更低&#xff0c;还能直接访问Windows文…

2026/9/10 18:39:01

AI Agent 面试题 650:如何实现RAG系统的检索延迟优化?

&#x1f525; AI Agent 面试题 650&#xff1a;如何实现RAG系统的检索延迟优化&#xff1f;摘要&#xff1a;本文深入解析了「如何实现RAG系统的检索延迟优化&#xff1f;」这一 AI Agent 领域的核心面试题。文章从 检索排序优化 的基本概念出发&#xff0c;系统性地剖析了 延…

2026/9/12 1:19:24

ML-Papers-of-the-Week 的 3 种用法

ML-Papers-of-the-Week 的 3 种用法 【免费下载链接】AI-Papers-of-the-Week &#x1f525;Highlighting the top ML papers every week. 项目地址: https://gitcode.com/GitHub_Trending/ml/AI-Papers-of-the-Week 周五晚上实验跑完&#xff0c;想看看这周同行发了什么…

2026/9/12 1:19:24

模拟加法器设计实战:从运放虚短虚断到PCB布线调试全解析

很多搞硬件的老哥第一次碰运放加法器&#xff0c;脑子里冒出来的想法基本都是同一个&#xff1a;整两个反相放大器&#xff0c;把输出端并一块儿不就行了&#xff1f;我当年也这么干过&#xff0c;结果输出直接瘫掉&#xff0c;波形面目全非。后来才明白&#xff0c;模拟加法器…

2026/9/12 1:19:24

Guava之事件总线

package bright.redisson;import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe;public class EventBusTest {static class Listener {Subscribepublic void listen(String eventData) {System.out.println("接收到事件数据&#xf…

2026/9/12 1:19:24

27. 数据产品- BI - AI 应用2- AI 模型部署与企业落地

文章目录 前言一、AI概念与当前应用现状二、企业AI落地三大部署方式详解1. ️ 云厂商AI SaaS/API模式2. 本地部署开源大模型3. 混合架构&#xff08;本地数据 云端AI推理&#xff09; 三、三种架构全面对比分析四、企业AI落地方案该如何选择五、总结与思考 前言 系列文章完整串…

2026/9/12 1:19:24

Python+Django构建文旅数据可视化决策平台

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/12 1:14:24

MATLAB在分布式发电配电网仿真中的应用实践

1. 项目背景与核心价值 分布式发电系统接入配电网是当前电力系统转型的重要方向。作为一名长期从事电力系统仿真的工程师&#xff0c;我发现在实际项目中&#xff0c;新能源并网对传统配电网的影响评估往往存在两大痛点&#xff1a;一是缺乏系统性的量化分析工具&#xff0c;二…

2026/9/10 16:39:38

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊&#xff01;#雷神 #复联”这类调侃式短标题&#xff0c;第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里&#xff0c;但细想一下就能发现&#xff0c;它真正碰到的根本不是…

2026/9/10 11:16:38

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊&#xff0c;可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”&#xff0c;你会发现&#xff0c;这场比较本质上是两个不同 IP 策略的长期结果对比&#xff1a;超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介&#xff1a;本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案&#xff0c;聚焦调制信号自动检测与识别这一典型无线通信任务&#xff0c;解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件&#xff08;10.73MB&#xff09;&…

2026/9/12 0:04:17

MATLAB仿生优化框架:长鼻浣熊算法多策略融合实现

简介&#xff1a;本资源是一份面向智能优化算法研究者与MATLAB初学者的仿生智能算法实践代码包&#xff0c;聚焦于长鼻浣熊优化算法&#xff08;COA&#xff09;的多策略改进与性能验证。针对传统COA易陷局部最优、收敛精度不足等问题&#xff0c;作者融合Circle映射初始化提升…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 JavaWeb 的校园一卡通管理系统的设计与实现 基于 JavaWeb 的校园卡业务管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 Java 的图书馆借阅管理平台的搭建与实现 基于 Java 的图书馆综合管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

2026/9/10 12:32:02

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行&#xff0c;Type-C接口算是典型的“看着简单&#xff0c;做起来全坑”的东西。光引脚就24个&#xff0c;高低速信号、电源、控制线全部塞在一个小小的连接器里&#xff0c;如果PCB布局不做规划&#xff0c;打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/10 15:19:50

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/10 15:49:53

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码