C++ 时间处理与格式化输出:从 Linux 时间函数到 Timestamp 封装

一、Linux 时间获取函数对比

函数 精度 返回类型 用途 推荐程度
time() time_t 获取时间戳 一般
gettimeofday() 微秒 struct timeval 计算时间差 ⚠️ 过时
clock_gettime() 纳秒 struct timespec 高精度计时 ✅ 推荐
clock() 微秒(近似) clock_t 进程 CPU 时间 特殊场景
times() 滴答 struct tms 进程 CPU 时间(含子进程) 特殊场景
ftime() 毫秒 struct timeb 老式时间获取 ❌ 废弃
clock_getres() --- struct timespec 获取时钟精度 辅助函数
getrusage() 微秒 struct rusage 进程资源使用时间 特殊场景

详细说明

1. time():获取秒级时间戳,最简单常用。

cpp 复制代码
time_t t = time(nullptr);  // 1970-01-01 至今的秒数

2. gettimeofday():获取微秒级时间,但 POSIX 2008 已标记为废弃,新代码不推荐使用。

cpp 复制代码
struct timeval tv;
gettimeofday(&tv, nullptr);  // tv_sec(秒),tv_usec(微秒)

3. clock_gettime() :纳秒级精度,支持多种时钟类型,推荐使用

cpp 复制代码
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);  // ts_sec(秒),ts_nsec(纳秒)

4. clock():测量进程占用的 CPU 时间,不是墙上时间。

cpp 复制代码
clock_t start = clock();
// ... 执行代码 ...
clock_t end = clock();
double cpu_time = (double)(end - start) / CLOCKS_PER_SEC;

5. times():获取进程及子进程的 CPU 时间,单位是"滴答"。

cpp 复制代码
struct tms buf;
times(&buf);  // buf.tms_utime(用户时间),buf.tms_stime(系统时间)

6. ftime():毫秒级精度,已被 POSIX 废弃,不推荐使用。

cpp 复制代码
struct timeb tb;
ftime(&tb);  // ❌ 已废弃

7. clock_getres():获取指定时钟的精度,用于辅助判断。

cpp 复制代码
struct timespec res;
clock_getres(CLOCK_REALTIME, &res);

8. getrusage():获取进程或子进程的资源使用情况,包含 CPU 时间。

cpp 复制代码
struct rusage usage;
getrusage(RUSAGE_SELF, &usage);
// usage.ru_utime(用户时间),usage.ru_stime(系统时间)

clock_gettime() 时钟类型选择建议

时钟类型 用途 是否受系统时间影响
CLOCK_REALTIME 获取当前时间戳(日志、文件时间) ✅ 会
CLOCK_MONOTONIC 测量时间间隔(代码耗时、超时判断) ❌ 不会
CLOCK_PROCESS_CPUTIME_ID 分析进程 CPU 占用 ❌ 不会
cpp 复制代码
// 示例1:获取当前时间戳(日志用)
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);

// 示例2:测量代码执行耗时
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
// ... 执行代码 ...
clock_gettime(CLOCK_MONOTONIC, &end);
double elapsed = (end.tv_sec - start.tv_sec) + 
                 (end.tv_nsec - start.tv_nsec) / 1e9;

注意CLOCK_REALTIME 受系统时间调整影响(用户改时间、NTP 同步),不适合测量耗时;测量耗时推荐用 CLOCK_MONOTONIC

二、localtime vs localtime_r

localtimelocaltime_r 用于将时间戳转换为 struct tm 结构体。

函数 线程安全 说明
localtime() ❌ 不安全 返回静态内部变量的指针,多线程会互相覆盖
localtime_r() ✅ 安全 可重入版本,需要自己传入 struct tm 缓冲区
cpp 复制代码
#include <time.h>

// ❌ 线程不安全
time_t t = time(nullptr);
struct tm* ptm = localtime(&t);  // 返回静态指针,多线程会覆盖

// ✅ 线程安全(推荐)
struct tm dtm;
localtime_r(&t, &dtm);  // 结果存入 dtm,不依赖静态变量

补充知识点

  • localtime 的替代函数:

    • localtime_r(Linux/Unix)

    • localtime_s(Windows)

三、struct tm 结构体

struct tm 用于存储分解后的时间信息:

cpp 复制代码
struct tm {
    int tm_sec;    // 秒(0-60,60 是闰秒)
    int tm_min;    // 分(0-59)
    int tm_hour;   // 时(0-23)
    int tm_mday;   // 日(1-31)
    int tm_mon;    // 月(0-11,0 表示一月)
    int tm_year;   // 年(1900 起,2026 年 → 126)
    int tm_wday;   // 星期(0-6,0 表示周日)
    int tm_yday;   // 一年中的第几天(0-365)
    int tm_isdst;  // 夏令时标志
};

使用示例

cpp 复制代码
time_t t = time(nullptr);
struct tm dtm;
localtime_r(&t, &dtm);

printf("年: %d\n", dtm.tm_year + 1900);  // 需要加 1900
printf("月: %d\n", dtm.tm_mon + 1);      // 需要加 1
printf("日: %d\n", dtm.tm_mday);
printf("时: %d\n", dtm.tm_hour);
printf("分: %d\n", dtm.tm_min);
printf("秒: %d\n", dtm.tm_sec);

注意tm_year 从 1900 年开始计数,tm_mon 从 0 开始计数,显示时需要分别加 1900 和 1。

四、printf 家族函数

函数 目标 说明 方向
printf() 标准输出(stdout) 输出到屏幕 输出 →
fprintf() 指定文件流 输出到文件或 stderr 输出 →
sprintf() 字符串缓冲区 ⚠️ 不安全(不检查边界) 输出 →
snprintf() 字符串缓冲区 ✅ 安全(指定最大长度) 输出 →
sscanf() 从字符串解析 从字符串读取数据 输入 ←

输入输出方向总结

输出函数(往哪里写)

函数 方向 目标
printf 屏幕(stdout)
fprintf 文件或 stderr
sprintf / snprintf 字符串缓冲区
cpp 复制代码
// 输出:数据 → 屏幕
printf("hello");

// 输出:数据 → 文件
fprintf(fp, "hello");

// 输出:数据 → 字符串
char buf[100];
sprintf(buf, "hello");  // "hello" 被写入 buf

输入函数(从哪读)

函数 方向 来源
scanf 键盘(stdin)
fscanf 文件
sscanf 字符串缓冲区
cpp 复制代码
// 输入:键盘 → 变量
scanf("%d", &a);

// 输入:文件 → 变量
fscanf(fp, "%d", &a);

// 输入:字符串 → 变量
char str[] = "10 20";
sscanf(str, "%d %d", &a, &b);  // 从 str 读取数据到 a 和 b

记忆方法

前缀 含义 输入/输出
无前缀(printf/scanf 标准输入输出(键盘/屏幕) 输入/输出
ffprintf/fscanf file(文件) 输入/输出
ssprintf/sscanf string(字符串) 输出/输入

一句话总结

  • 输出printf/fprintf/sprintf):数据从程序流出去

  • 输入scanf/fscanf/sscanf):数据从外面流进来

sprintfsscanf 容易混淆:sprintf 到字符串,sscanf 是从字符串

代码示例

cpp 复制代码
#include <stdio.h>

int a = 10, b = 20;

// printf:输出到 stdout
printf("a=%d b=%d\n", a, b);

// fprintf:输出到指定文件流
fprintf(stdout, "a=%d b=%d\n", a, b);   // 等价于 printf
fprintf(stderr, "错误信息\n");            // 输出到 stderr

// sprintf:输出到字符串(不安全)
char buf[100];
sprintf(buf, "a=%d b=%d", a, b);

// snprintf:安全版本,指定最大长度(推荐)
char safe_buf[100];
snprintf(safe_buf, sizeof(safe_buf), "a=%d b=%d", a, b);

// sscanf:从字符串解析
char str[] = "10.20";
int x, y;
sscanf(str, "%d.%d", &x, &y);  // x=10, y=20

安全建议

  • snprintf 替代 sprintf,避免缓冲区溢出

  • 错误信息用 fprintf(stderr, ...),确保立即输出(stderr 无缓冲)

五、stdout vs stderr(缓存区别)

1. 缓存区别

缓冲类型 说明
stdout 行缓冲 遇到 \n 或缓冲区满才输出
stderr 无缓冲 立即输出,不缓存

2. 代码演示

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("stdout 信息");      // 没有 \n,不会立即显示
    fprintf(stderr, "stderr 错误"); // 立即显示
    
    sleep(2);  // 等待 2 秒
    
    printf("\n");  // 加上 \n 后,之前的内容才显示
    return 0;
}

运行效果

  • 立即显示:stderr 错误

  • 2 秒后显示:stdout 信息

3. 为什么 stderr 不带缓冲?

错误信息需要立即输出。如果程序崩溃了,带缓冲的 stdout 可能还没来得及输出,错误信息就丢失了。stderr 无缓冲,保证错误信息能立即看到。

4. 重定向测试

bash 复制代码
./program > out.txt 2> err.txt
  • > out.txt:把 stdout 重定向到 out.txt 文件(屏幕上不显示)

  • 2> err.txt:把 stderr 重定向到 err.txt 文件(屏幕上不显示)

测试

bash 复制代码
# 编译并运行
g++ test.cpp -o program
./program > out.txt 2> err.txt

# 查看两个文件的内容
cat out.txt   # 看到 "stdout 信息\n"
cat err.txt   # 看到 "stderr 错误"

一句话总结

缓冲 输出时机 用途
stdout 有缓冲 遇到 \n 或程序结束 普通输出
stderr 无缓冲 立即输出 错误信息

六、stringstream 系列

sstream 提供了三种流,用于在内存中的字符串和变量之间交换数据。

1. ostringstream(只写)

作用 :把变量的值写入字符串。

cpp 复制代码
ostringstream oss;
oss << "a = " << 10 << ", b = " << 20;
string str = oss.str();
cout << str;  // 输出 "a = 10, b = 20"

方向:变量 → 字符串

2. istringstream(只读)

作用 :从字符串中读取数据到变量。

cpp 复制代码
string data = "100 200";
istringstream iss(data);  // iss 内部存着 "100 200"

int x, y;
iss >> x >> y;

方向:字符串 → 变量

执行过程

  1. iss >> x:从 iss 中读取第一个数据 "100",转换成 int,赋值给 x

  2. iss >> y:继续读取下一个数据 "200",转换成 int,赋值给 y

3. stringstream(读写)

作用:既可以写入,也可以读取。

cpp 复制代码
stringstream ss;
ss << "hello 123";   // 写入:把 "hello 123" 放进 ss

string s;
ss >> s;             // 读取:从 ss 中读取一个字符串

方向:双向

ss 按空格分割,先读取 "hello",赋值给 s。此时 s = "hello",光标移动到空格后面。

总结:

类型 作用 方向
ostringstream 格式化数据写入字符串 变量 → 字符串
istringstream 从字符串解析数据 字符串 → 变量
stringstream 既可写也可读 双向

类比

  • ostringstreamsprintf(变量 → 字符串)

  • istringstreamsscanf(字符串 → 变量)

使用建议

  • 只需要格式化输出 → ostringstream

  • 只需要解析字符串 → istringstream

  • 需要同时读写 → stringstream

七、补充知识点

8.1 为什么时间戳要用微秒?

日志系统需要记录事件的精确顺序,秒级精度不够(同一秒内多个事件无法区分)。微秒级可以满足大多数场景。

8.2 localtime_r 的第二个参数为什么是 &dtm

localtime_r 需要传入一个 struct tm 的指针,函数会将结果写入这个结构体。这样不依赖静态变量,多线程安全。

8.3 snprintfsprintf 安全在哪里?

sprintf 不检查缓冲区大小,写入超过长度会溢出。snprintf 指定了最大写入长度,超过会被截断。

cpp 复制代码
char buf[10];
sprintf(buf, "1234567890");  // ❌ 溢出
snprintf(buf, sizeof(buf), "1234567890");  // ✅ 安全,最多写9个字符

8.4 gettimeofday 为什么被废弃?

存在时区处理问题,且无法提供纳秒精度。POSIX 2008 已标记为废弃,推荐使用 clock_gettime(CLOCK_REALTIME, &ts)

八、总结

知识点 核心要点
时间函数 clock_gettime() 推荐(纳秒),gettimeofday() 过时
localtime vs localtime_r localtime_r 线程安全,推荐使用
struct tm tm_year 从 1900 起,tm_mon 从 0 起
printf 家族 snprintfsprintf 安全
stdout vs stderr stderr 无缓冲,错误信息立即输出
重定向 > 重定向 stdout,2> 重定向 stderr
stringstream ostringstream(写)、istringstream(读)、stringstream(读写)

一句话总结

Linux 时间处理推荐用 clock_gettime()localtime_r,格式化输出优先用 snprintf,错误信息用 stderrstringstream 系列提供内存中的字符串读写功能。

相关推荐
tankeven7 小时前
HJ176 【模板】滑动窗口
c++·算法
OxyTheCrack7 小时前
【C++】一文详解C++智能指针自定义删除器(以Redis连接池为例)
c++·redis
whitelbwwww8 小时前
C++基础--类型、函数、作用域、指针、引用、文件
开发语言·c++
leaves falling8 小时前
C/C++ const:修饰变量和指针的区别(和引用底层关系)
c语言·开发语言·c++
tod1138 小时前
深入解析ext2文件系统架构
linux·服务器·c++·文件系统·ext
不想写代码的星星8 小时前
C++ 类型萃取:重生之我在幼儿园修炼类型学
c++
比昨天多敲两行8 小时前
C++11新特性
开发语言·c++
xiaoye-duck8 小时前
【C++:C++11】核心特性实战:详解C++11列表初始化、右值引用与移动语义
开发语言·c++·c++11
睡一觉就好了。8 小时前
二叉搜索树
c++