1、文件IO
1.1 文件IO和标准IO的区别
| 特性 | 标准 IO(stdio) | 文件 IO(系统调用 IO) |
|---|---|---|
| 缓存机制 | 有用户态缓存(库层缓存) | 无用户态缓存(但内核有页缓存(磁盘缓存),目的是减少磁盘 IO) |
| 实现层面 | 库函数(C 库提供,如 fopen/fread) | 系统调用(内核提供,如 open/read) |
| 操作对象 | 适合普通文件(文本 / 二进制) | 适合设备文件(如 /dev/tty)、管道 / 套接字等通信文件,也支持普通文件 |
| 接口标识 | 文件指针(FILE*) | 文件描述符(int 型非负整数) |
| 跨平台性 | 好 | 差(依赖具体系统,如 Linux/UNIX) |
| 效率(小数据) | 高(缓存减少系统调用) | 低(频繁系统调用开销大) |

1.2 系统调用
**定义:**Linux 内核暴露给用户层的函数接口,是用户程序与内核交互的唯一方式。
**特点:**执行时会触发 "内核态 / 用户态" 切换,开销比库函数大;由内核直接实现,无额外封装。
**示例:**open、close、read、write、lseek 等。
1.3 库函数
**定义:**基于系统调用封装的上层函数(属于 C 标准库),对用户更友好。
**核心逻辑:**标准 IO 库函数(如 fread)会先操作用户态缓存,当缓存满 / 空或主动刷新(fflush)时,才调用底层系统调用(如 read)与内核交互。
**优势:**减少系统调用次数,提升小数据读写效率;接口统一,跨平台性好。
**示例:**fopen、fclose、fread、fwrite、fprintf 等。
1.4 文件IO接口
Linux 文件 IO 的核心是无缓冲 IO (直接与内核交互),所有操作围绕文件描述符(fd) 展开,核心接口包括 open/close/read/write 等。
1.4.1 open --- 打开/创建文件
1.4.1.1 函数原型
cpp
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// 仅打开已存在的文件
int open(const char *pathname, int flags);
// 打开文件,若文件不存在则创建(需配合O_CREAT标志)
int open(const char *pathname, int flags, mode_t mode);
1.4.1.2 功能
打开指定路径的文件,或在文件不存在时创建新文件(需配合O_CREAT标志),并返回一个唯一的非负整数文件描述符(fd), 后续通过该描述符对文件进行读写、关闭等操作。若失败返回-1,并设置errno(错误码)。
关键:open 是 Linux 内核提供的系统调用,无用户态缓存,直接与内核交互,是操作设备文件、通信文件的核心接口。
1.4.1.3 参数
| 参数 | 说明 |
|---|---|
| pathname | 字符串类型,文件的路径(绝对路径 / 相对路径),如"/home/test.txt" |
| flags | 打开文件的标志(必选 + 可选),多个标志用 `|`拼接 |
| mode | 仅当flags包含O_CREAT时生效,指定新文件的权限(如0644) |
flags 核心取值(必选其一):
O_RDONLY:只读打开O_WRONLY:只写打开O_RDWR:读写打开
flags 可选补充:
O_CREAT:文件不存在时创建,需配合mode参数O_EXCL:与O_CREAT配合,若文件已存在则 open 失败(避免覆盖)O_TRUNC:文件存在且以写 / 读写打开时,清空文件内容O_APPEND:写操作时,数据追加到文件末尾(而非覆盖开头)O_NONBLOCK:非阻塞模式打开(读写不会阻塞,比如打开管道 / 设备文件时常用)O_ASYNC:异步 I/O 通知,当文件描述符对应的文件有可读写数据时,内核会向指定的进程 / 线程发送异步通知信号(默认是SIGIO)
mode 权限说明:
- ASYNC取值为 8 进制数(以 0 开头),如
0644:- 所有者(user):读 + 写(6=4+2)
- 所属组(group):只读(4)
- 其他用户(other):只读(4)
- 最终权限 = mode & ~umask(umask 是系统默认权限掩码,默认 002/022)
r O_RDONLY
r+ O_RDWR
w O_WRONLY | O_CREAT | O_TRUNC, 0664
w+ O_RDWR | O_CREAT | O_TRUNC, 0664
a O_WRONLY | O_CREAT | O_APPEND, 0664
a+ O_RDWR | O_CREAT | O_APPEND, 0664
1.4.1.4 返回值
- 成功:返回一个非负整数文件描述符(fd)(系统分配的最小可用 fd);
- 失败:返回
-1,并设置全局变量errno(可通过perror()打印错误原因)。
1.4.2 close --- 关闭文件
1.4.2.1 函数原型
cpp
#include <unistd.h>
int close(int fd);
1.4.2.2 功能
关闭已打开的文件描述符,释放该 fd 对应的内核资源(如文件表项),关闭后 fd 不可再使用。
1.4.2.3 参数
fd:open 返回的文件描述符。
1.4.2.4 返回值
- 成功:返回
0; - 失败:返回
-1(如 fd 已关闭、fd 无效),并设置errno。
1.4.2.5 注意事项
- 进程退出时,内核会自动关闭所有未关闭的 fd,但显式调用 close 是良好编程习惯;
- 重复关闭同一个 fd 会导致未定义行为(可能崩溃)
1.4.3 write --- 向文件写入数据
1.4.3.1 函数原型
cpp
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
1.4.3.2 功能
将buf中的数据写入到fd对应的文件中,无用户态缓存,直接写入内核缓冲区(最终由内核刷到磁盘)。
1.4.3.3 参数
fd:文件描述符;buf:待写入数据的缓冲区(如字符数组);count:期望写入的字节数。
1.4.3.4 返回值
- 成功:返回实际写入的字节数(可能小于 count,如磁盘满、管道被关闭);
- 失败:返回
-1,并设置errno。
1.4.4 read --- 从文件读取数据
1.4.4.1 函数原型
cpp
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
1.4.4.2 功能
从fd对应的文件中读取最多count字节的数据,存入buf缓冲区。
1.4.4.3 参数
fd:文件描述符;buf:存储读取数据的缓冲区(需提前分配空间);count:期望读取的字节数。
1.4.4.4 返回值
- 成功:返回实际读取的字节数 ;
- 返回
0:表示已读到文件末尾(EOF); - 返回值 < count:可能是剩余数据不足,或被信号中断;
- 返回
- 失败:返回
-1,并设置errno。
1.4.5 lseek --- 偏移量控制函数
1.4.5.1 函数原型
cpp
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
1.4.5.2 功能
调整文件描述符fd对应的内核文件偏移量 ,决定后续read/write操作从文件的哪个位置开始读写;
额外作用:可用于获取当前文件偏移量 (offset=0,whence=SEEK_CUR)、扩展文件大小(创建空洞)
1.4.5.3 参数
| 参数 | 说明 |
|---|---|
| fd | 由open()返回的文件描述符 |
| offset | 偏移量(字节数):正数表示向文件末尾偏移,负数表示向文件开头偏移 |
| whence | 偏移的基准位置 ,仅能取以下三个宏之一(定义在<unistd.h>) |
whence 核心取值:
| 宏 | 含义 | 约束 / 说明 |
|---|---|---|
| SEEK_SET | 从文件开头开始偏移 | offset 必须 ≥ 0 |
| SEEK_CUR | 从当前偏移位置开始偏移 | offset 可正可负(正向后 / 负向前) |
| SEEK_END | 从文件末尾开始偏移 | offset 负数 = 向文件开头偏移;正数 = 指向文件末尾外(形成空洞) |
1.4.5.4 返回值
- 成功:返回调整后的新偏移量(相对于文件开头的字节数);
- 失败:返回
-1,并设置errno(可通过perror()打印错误,如 fd 无效、偏移量非法)。
1.4.5.5 关键注意事项
不适用的文件类型 :lseek仅对可定位文件 有效(如普通文件),对管道、套接字、字符设备文件等不可定位文件调用会失败(返回 - 1);
O_APPEND 标志的影响 :若open时指定了O_APPEND,则每次write前内核会自动将偏移量重置到文件末尾,lseek调整的偏移量会被覆盖;
偏移量范围:偏移量可以超过文件大小,但仅当写入数据后,文件大小才会实际扩展。
1.5 系统默认打开三个描述符
进程启动时,内核会自动打开 3 个标准文件描述符,对应终端设备:
| fd | 名称 | 对应宏 | 作用 |
|---|---|---|---|
| 0 | 标准输入 | STDIN_FILENO | 从终端读取输入(如键盘) |
| 1 | 标准输出 | STDOUT_FILENO | 向终端输出内容(如屏幕) |
| 2 | 标准错误 | STDERR_FILENO | 向终端输出错误信息 |
示例:直接向标准输出写数据(等价于 printf)
cpp
#include <unistd.h>
int main() {
write(STDOUT_FILENO, "Hello Standard Output!\n", 22);
return 0;
}
1.6 文件描述符的特性
上限限制 :每个进程的文件描述符数量有上限(可通过ulimit -n查看 )
分配规则 :新产生的文件描述符总是选择当前进程中最小且未被使用的非负整数(例如:关闭 fd=1 后,下一次 open 会分配 fd=1)
唯一性:文件描述符仅在当前进程内有效,不同进程的 fd 可以相同(指向不同文件)
继承性:子进程会继承父进程的 fd(指向相同文件)
2、目录IO
2.1 opendir --- 打开目录
2.1.1 函数原型
cpp
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
2.1.2 功能
打开指定路径的目录,返回一个目录流指针(DIR*),后续通过该指针读取目录内的文件 / 子目录信息。
- 核心:仅能打开目录(不能打开普通文件),权限不足或路径不存在时会失败。
2.1.3 参数
name:目录的路径(绝对路径 / 相对路径),如"/home/test"、"./tmp"。
2.1.4 返回值
- 成功:返回非空的DIR* 目录流指针;
- 失败:返回
NULL,并设置errno(可通过perror()打印错误,如权限不足、目录不存在)。
2.2 closedir --- 关闭目录流
2.2.1 函数原型
cpp
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
2.2.2 功能
关闭已打开的目录流指针,释放内核资源,关闭后dirp不可再使用。
2.2.3 参数
dirp:由opendir()返回的目录流指针。
2.2.4 返回值
- 成功:返回
0; - 失败:返回
-1(如dirp无效、已关闭),并设置errno。
2.3 readdir --- 读取目录项
2.3.1 函数原型
cpp
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
2.3.2 功能
从目录流dirp中按顺序读取下一个目录项(文件 / 子目录 / 隐藏文件),每次调用返回一个目录项的信息,直到目录末尾。
2.3.3 参数
dirp:由opendir()返回的目录流指针。
2.3.4 返回值
- 成功:返回
struct dirent*结构体指针(指向当前目录项的信息); - 失败 / 读到目录末尾:返回
NULL(读到末尾时errno不变,失败时errno会被设置)。
2.3.5 核心结构体 struct dirent 详解
cpp
struct dirent {
ino_t d_ino; /* 索引节点号(Inode number):文件在磁盘中的唯一标识 */
off_t d_off; /* 当前目录项在整个目录流中的偏移量 */
unsigned short d_reclen; /* 该结构体的总长度(字节):不同文件类型长度可能不同 */
unsigned char d_type; /* 文件类型(关键!):快速判断文件类型 */
char d_name[256]; /* 文件名(以'\0'结尾):核心字段,如"test.txt"、"subdir" */
};
d_type 核心取值(文件类型):
| 取值 | 含义 | 说明 |
|---|---|---|
| DT_REG --- 8 | 普通文件 | 如.txt、.c、可执行文件 |
| DT_DIR --- 4 | 目录文件 | 子目录,如 "subdir" |
| DT_LNK --- 10 | 符号链接文件 | 软链接 |
| DT_CHR --- 2 | 字符设备文件 | 如 /dev/tty、/dev/stdin |
| DT_BLK --- 6 | 块设备文件 | 如 /dev/sda1 |
| DT_FIFO --- 1 | 管道文件 | 命名管道 |
| DT_SOCK --- 12 | 套接字文件 | 本地套接字 |
| DT_UNKNOWN --- 0 | 未知类型 | 部分文件系统不支持 d_type |
2.3.6 关键注意事项
readdir()会读取.(当前目录)和..(上级目录),需手动过滤;- 目录流是 "单向的",读取后无法回退,若需重新读取需关闭后重新
opendir;
2.4 mkdir --- 创建目录
2.4.1 函数原型
cpp
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
2.4.2 功能
创建指定路径的目录文件,若路径中父目录不存在则失败(需逐级创建)。
2.4.3 参数
| 参数 | 说明 |
|---|---|
| pathname | 目录路径(如"./new_dir"、"/home/test/subdir") |
| mode | 目录权限(8 进制数),通常设为0777,最终权限 = mode & ~umask |
2.4.4 目录权限的特殊含义
r(读):是否能够查看目录内的文件 (如ls命令);w(写):是否能够在目录内创建 / 删除 / 重命名文件 (如touch、rm);x(执行):是否能够进入该目录 (如cd命令);- 注意:目录必须有
x权限才能访问内部文件,仅有r权限无法cd进入。
2.4.5 返回值
- 成功:返回
0; - 失败:返回
-1(如父目录不存在、权限不足、目录已存在),并设置errno。
2.5 rmdir --- 删除空目录
2.5.1 函数原型
cpp
#include <unistd.h>
int rmdir(const char *pathname);
2.5.2 功能
删除指定路径的空目录 (目录内必须无任何文件 / 子目录,包括.和..之外的所有项)。
2.5.3 参数
pathname:待删除的空目录路径。
2.5.4 返回值
- 成功:返回
0; - 失败:返回
-1(如目录非空、权限不足、目录不存在),并设置errno。
2.6 chdir --- 切换当前工作目录
2.6.1 函数原型
cpp
#include <unistd.h>
int chdir(const char *path);
2.6.2 功能
修改当前进程的工作目录 (等价于终端的cd命令),后续文件操作的相对路径基于此目录。
2.6.3 参数
path:目标目录路径(绝对 / 相对路径)。
2.6.4 返回值
- 成功:返回
0; - 失败:返回
-1(如目录不存在、权限不足),并设置errno。
2.7 getcwd --- 获取当前工作目录
2.7.1 函数原型
cpp
#include <unistd.h>
char *getcwd(char *buf, size_t size);
2.7.2 功能
获取当前进程的绝对工作目录路径 ,并存储到buf缓冲区中。
2.7.3 参数
| 参数 | 说明 |
|---|---|
| buf | 用于存储路径的缓冲区首地址(需提前分配空间,如char buf[1024];) |
| size | 缓冲区的最大字节数(需大于路径长度,否则失败) |
2.7.4 返回值
- 成功:返回
buf的首地址(与传入的 buf 一致); - 失败:返回
NULL(如缓冲区太小、权限不足),并设置errno。
2.8 示例1:遍历目录下所有文件(过滤.和..)
cpp
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
int main() {
// 1. 打开目录
DIR *dirp = opendir("./test_dir");
if (dirp == NULL) {
perror("opendir error");
return -1;
}
// 2. 循环读取目录项
struct dirent *entry;
while ((entry = readdir(dirp)) != NULL) {
// 过滤.(当前目录)和..(上级目录)
if (entry->d_name[0] == 0) {
continue;
}
// 打印文件名和类型
printf("文件名:%-10s | 类型:", entry->d_name);
switch (entry->d_type) {
case DT_REG: printf("普通文件\n"); break;
case DT_DIR: printf("目录\n"); break;
case DT_LNK: printf("软链接\n"); break;
default: printf("未知类型\n"); break;
}
}
// 3. 关闭目录流
closedir(dirp);
return 0;
}
2.9 示例 2:创建目录 + 切换工作目录 + 获取当前路径
cpp
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main() {
// 1. 创建目录(权限0777,最终受umask影响)
if (mkdir("./my_dir", 0777) == -1) {
perror("mkdir error");
return -1;
}
printf("目录创建成功\n");
// 2. 切换到新创建的目录
if (chdir("./my_dir") == -1) {
perror("chdir error");
return -1;
}
// 3. 获取当前工作目录
char buf[1024] = {0};
if (getcwd(buf, sizeof(buf)) == NULL) {
perror("getcwd error");
return -1;
}
printf("当前工作目录:%s\n", buf); // 输出:xxx/xxx/my_dir
// 4. 切回上级目录,删除空目录
chdir("..");
if (rmdir("./my_dir") == -1) {
perror("rmdir error");
return -1;
}
printf("目录删除成功\n");
return 0;
}
3、时间相关接口
3.1 time --- 获取系统时间戳(秒级)
3.1.1 函数原型
cpp
#include <time.h>
time_t time(time_t *tloc); // typedef long time_t
3.1.2 功能
获取从 1970-01-01 00:00:00 UTC 到当前时刻的秒数(称为 Unix 时间戳),是时间操作的基础。
3.1.3 参数
tloc:- 非 NULL:将获取到的秒数存入该指针指向的内存空间;
- NULL:仅通过返回值返回秒数(最常用方式)。
3.1.4 返回值
- 成功:返回 Unix 时间戳(
time_t本质是long类型); - 失败:返回
(time_t)-1,并设置errno。
3.1.5 使用示例
cpp
#include <time.h>
#include <stdio.h>
int main() {
// 方式1:仅通过返回值获取
time_t ts = time(NULL);
printf("当前时间戳(秒):%ld\n", ts); // 输出:1740000000(示例值)
// 方式2:通过参数+返回值获取
time_t ts2;
time(&ts2);
printf("当前时间戳:%ld\n", ts2); // 和ts值一致
return 0;
}
3.2 localtime --- 时间戳转本地日历时间
3.2.1 函数原型
cpp
#include <time.h>
struct tm *localtime(const time_t *timep);
3.2.2 功能
将timep指向的 Unix 时间戳,转换为本地时区 的日历时间(年月日时分秒),返回struct tm结构体指针。
- 核心:会自动适配本地时区(如东八区 UTC+8),是最常用的 "时间戳转可读时间" 接口。
3.2.3 参数
timep:指向 Unix 时间戳的指针(如time(NULL)的结果)。
3.2.4 返回值
- 成功:返回指向
struct tm的指针(该结构体是静态内存,后续调用会覆盖,非线程安全);
这类函数(比如 localtime())内部会维护一个全局的、静态的 struct tm 结构体 ,每次调用函数时,不会新建结构体,而是把计算好的时间值覆盖写入这个静态结构体,然后返回该结构体的地址。
线程安全的核心是:多线程同时操作同一份资源时,不会出现数据错乱。
而静态的 struct tm 是全局资源,多线程调用 localtime() 时,会出现 "线程 A 刚写一半,线程 B 就覆盖" 的情况
cpp
#include <stdio.h>
#include <time.h>
int main() {
time_t t1 = time(NULL); // 获取当前时间戳
time_t t2 = t1 + 3600; // 1小时后的时间戳
// 第一次调用:返回静态结构体的指针,内容是当前时间
struct tm *tm1 = localtime(&t1);
// 第二次调用:覆盖静态结构体的内容,变成1小时后
struct tm *tm2 = localtime(&t2);
// 打印tm1:本应是当前时间,却变成了1小时后(被覆盖)
printf("tm1: %d时\n", tm1->tm_hour);
// 打印tm2:和tm1值一样(指向同一个内存)
printf("tm2: %d时\n", tm2->tm_hour);
return 0;
}
原因: tm1 和 tm2 指向同一个静态结构体 ,第二次调用 localtime() 把结构体内容覆盖了,所以 tm1 的值也变了。
- 失败:返回
NULL,并设置errno。
3.2.5 struct tm 结构体详解
cpp
struct tm {
int tm_sec; /* 秒(0-60):60是闰秒,通常用0-59 */
int tm_min; /* 分(0-59) */
int tm_hour; /* 时(0-23):24小时制 */
int tm_mday; /* 日(1-31):每月的第几天,从1开始 */
int tm_mon; /* 月(0-11):0=1月,11=12月,使用时需+1 */
int tm_year; /* 年:存储的是「年份-1900」,如2026则存126,使用时需+1900 */
int tm_wday; /* 星期(0-6):0=周日,1=周一,...,6=周六 */
int tm_yday; /* 年内天数(0-365):0=1月1日 */
int tm_isdst; /* 夏令时标志:1=启用,0=未启用,-1=未知 */
};
3.2.6 使用示例
cpp
#include <time.h>
#include <stdio.h>
int main() {
time_t ts = time(NULL);
// 时间戳转本地日历时间
struct tm *local_tm = localtime(&ts);
if (local_tm == NULL) {
perror("localtime error");
return -1;
}
// 格式化输出(注意tm_mon+1、tm_year+1900)
printf("本地时间:%04d年%02d月%02d日 %02d:%02d:%02d 星期%d\n",
local_tm->tm_year + 1900, // 2026
local_tm->tm_mon + 1, // 2月
local_tm->tm_mday, // 9日
local_tm->tm_hour, // 小时
local_tm->tm_min, // 分钟
local_tm->tm_sec, // 秒
local_tm->tm_wday); // 星期1(示例)
return 0;
}
3.3 mktime --- 日历时间转时间戳
3.3.1 函数原型
cpp
#include <time.h>
time_t mktime(struct tm *tm);
3.3.2 功能
将struct tm格式的日历时间(本地时区),转换回 Unix 时间戳(秒数),是localtime的逆操作。
- 额外作用:自动修正非法的日历时间(如 tm_mday=32 会自动转为下月 1 日)。
3.3.3 参数
tm:指向struct tm的指针(需填充年月日时分秒等字段)。
3.3.4 返回值
- 成功:返回转换后的 Unix 时间戳;
- 失败:返回
(time_t)-1,并设置errno。
3.3.5 使用示例
cpp
#include <time.h>
#include <stdio.h>
int main() {
// 构造2026年2月9日 10:30:00的日历时间
struct tm tm = {0};
tm.tm_year = 2026 - 1900; // 必须-1900
tm.tm_mon = 2 - 1; // 必须-1(2月对应1)
tm.tm_mday = 9;
tm.tm_hour = 10;
tm.tm_min = 30;
tm.tm_sec = 0;
// 日历时间转时间戳
time_t ts = mktime(&tm);
if (ts == (time_t)-1) {
perror("mktime error");
return -1;
}
printf("2026-02-09 10:30:00 的时间戳:%ld\n", ts);
return 0;
}
3.4 ctime --- 时间戳转可读字符串(带本地时区)
3.4.1 函数原型
cpp
#include <time.h>
char *ctime(const time_t *timep);
3.4.2 功能
将 Unix 时间戳直接转换为人类可读的字符串 (包含本地时区),字符串格式固定:"Wed Feb 9 10:30:00 2026\n"。
- 底层:等价于
asctime(localtime(timep)),自动处理时区和格式化。
3.4.3 参数
timep:指向 Unix 时间戳的指针。
3.4.4 返回值
- 成功:返回指向格式化字符串的指针(静态内存,后续调用会覆盖);
- 失败:返回
NULL
3.4.5 使用示例
cpp
#include <time.h>
#include <stdio.h>
int main() {
time_t ts = time(NULL);
// 时间戳转可读字符串
char *time_str = ctime(&ts);
if (time_str == NULL) {
perror("ctime error");
return -1;
}
printf("当前时间(ctime):%s", time_str); // 输出:Mon Feb 9 10:30:00 2026
return 0;
}
3.5 asctime --- 日历时间转可读字符串(无时区)
3.5.1 函数原型
cpp
#include <time.h>
char *asctime(const struct tm *tm);
3.5.2 功能
将struct tm格式的日历时间,转换为固定格式的可读字符串,格式与ctime一致:"Wed Feb 9 10:30:00 2026\n"。
- 核心区别:
asctime仅格式化struct tm,不处理时区 ;而ctime会先转本地时区再格式化。
3.5.3 参数
tm:指向struct tm的指针。
3.5.4 返回值
- 成功:返回指向格式化字符串的指针(静态内存,非线程安全);
- 失败:返回
NULL。
3.5.5 使用示例
cpp
#include <time.h>
#include <stdio.h>
int main() {
time_t ts = time(NULL);
struct tm *local_tm = localtime(&ts);
// 日历时间转可读字符串
char *time_str = asctime(local_tm);
if (time_str == NULL) {
perror("asctime error");
return -1;
}
printf("当前时间(asctime):%s", time_str); // 输出和ctime一致
return 0;
}
3.6 各类时间之间的关系
