一、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
localtime 和 localtime_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) |
标准输入输出(键盘/屏幕) | 输入/输出 |
f(fprintf/fscanf) |
file(文件) | 输入/输出 |
s(sprintf/sscanf) |
string(字符串) | 输出/输入 |
一句话总结
-
输出 (
printf/fprintf/sprintf):数据从程序流出去 -
输入 (
scanf/fscanf/sscanf):数据从外面流进来
sprintf 和 sscanf 容易混淆: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;
方向:字符串 → 变量
执行过程:
-
iss >> x:从iss中读取第一个数据"100",转换成int,赋值给x -
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 |
既可写也可读 | 双向 |
类比:
-
ostringstream像sprintf(变量 → 字符串) -
istringstream像sscanf(字符串 → 变量)
使用建议:
-
只需要格式化输出 →
ostringstream -
只需要解析字符串 →
istringstream -
需要同时读写 →
stringstream
七、补充知识点
8.1 为什么时间戳要用微秒?
日志系统需要记录事件的精确顺序,秒级精度不够(同一秒内多个事件无法区分)。微秒级可以满足大多数场景。
8.2 localtime_r 的第二个参数为什么是 &dtm?
localtime_r 需要传入一个 struct tm 的指针,函数会将结果写入这个结构体。这样不依赖静态变量,多线程安全。
8.3 snprintf 比 sprintf 安全在哪里?
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 家族 | snprintf 比 sprintf 安全 |
| stdout vs stderr | stderr 无缓冲,错误信息立即输出 |
| 重定向 | > 重定向 stdout,2> 重定向 stderr |
| stringstream | ostringstream(写)、istringstream(读)、stringstream(读写) |
一句话总结:
Linux 时间处理推荐用
clock_gettime()和localtime_r,格式化输出优先用snprintf,错误信息用stderr。stringstream系列提供内存中的字符串读写功能。