标准 IO 基础
核心概念
-
POSIX 标准:统一不同操作系统的访问接口,让遵循该标准的程序能在 Linux、Windows 等系统间移植(比如标准 C 库的 IO 函数在多系统通用)。
-
标准 C 库:遵循 POSIX 标准的函数集合,包含标准 IO 函数(如
fopen
、fread
),需通过头文件<stdio.h>
调用。 -
<stdio.h>
路径:Linux 系统下路径为/usr/include/stdio.h
。 -
FILE 结构体:标准 IO 库管理文件的核心结构体,本质是 “文件信息容器”,包含:
- 文件描述符(
_fileno
):关联内核中的文件。 - 缓冲区指针(如
_IO_read_ptr
读指针、_IO_write_ptr
写指针):暂存读写数据,减少 IO 设备访问次数。 - 状态标志(
_flags
):标记文件是否出错、是否到末尾等。 - 链表指针(
chain
):内核用链表管理所有打开的文件,每个 FILE 结构体通过chain
连接。
- 文件描述符(
-
默认打开的 3 个流:程序启动时,内核自动打开 3 个标准流,均为
FILE*
类型:stdin
:标准输入(默认对应键盘),行缓冲。stdout
:标准输出(默认对应屏幕),行缓冲。stderr
:标准出错(默认对应屏幕),无缓冲(错误信息需立即输出)。
核心标准 IO 函数
文件打开:fopen
、freopen
、fdopen
fopen
(最常用,打开指定路径文件)
#include <stdio.h>
/*** 以指定模式打开文件,返回文件流指针* @param pathname 待打开文件的路径(如"demo.txt"、"/home/user/test.dat")* @param mode 打开模式(字符串形式,决定读写权限和文件处理方式):* - "r":只读,文件必须存在,不存在则打开失败* - "r+":读写,文件必须存在,不存在则打开失败* - "w":只写,文件不存在则创建,存在则清空内容* - "w+":读写,文件不存在则创建,存在则清空内容* - "a":追加写,文件不存在则创建,写入位置始终在文件末尾* - "a+":追加读写,文件不存在则创建,写入在末尾,读取位置默认在开头(Linux下)* @return 成功:返回指向FILE结构体的指针(文件流指针);失败:返回NULL(需判断,避免野指针)* @note 打开成功后,内核从堆内存分配FILE结构体空间;不可反复调用fclose关闭同一文件(会导致double free错误)*/
FILE *fopen(const char *pathname, const char *mode);
freopen
(重定向流,如将 stdout 重定向到文件)
#include <stdio.h>
/*** 重定向文件流(将指定流关联到新文件)* @param pathname 新文件路径(如"log.txt")* @param mode 打开模式(同fopen的mode,如"w"、"a")* @param stream 待重定向的流(如stdout、stdin)* @return 成功:返回重定向后的stream;失败:返回NULL* @note 常用于将标准输出(stdout)重定向到文件,实现“日志写入”*/
FILE *freopen(const char *pathname, const char *mode, FILE *stream);
fdopen
(将文件描述符转换为文件流指针)
#include <stdio.h>
/*** 将内核的文件描述符(fd)转换为FILE*流(适配标准IO函数)* @param fd 已打开的文件描述符(如open函数返回的fd)* @param mode 打开模式(同fopen,但需与fd的权限匹配,如fd是只读的,mode不能用"w")* @return 成功:返回FILE*指针;失败:返回NULL* @note 用于“系统调用IO”(如open)与“标准IO”(如fread)的衔接*/
FILE *fdopen(int fd, const char *mode);
二进制模式 vs 文本模式
- 二进制模式:使用
"rb"
(二进制只读)、"wb"
(二进制只写)、"r+b"
(二进制读写)作为打开模式,数据按 “原始字节” 读写,系统不对任何字符进行解释或转换。适用场景:图片(如 JPG/PNG)、音频(如 MP3/WAV)、视频、可执行文件(如.exe
/.out
)等二进制文件,以及需要精确保留字节结构的文本文件(如配置文件)。例如,用二进制模式读取图片文件时,每个字节(包括\n
、\r
等特殊字符)都会原样保留,避免文件损坏。 - 文本模式:使用
"r"
(文本只读)、"w"
(文本只写)、"r+"
(文本读写)作为打开模式,不同操作系统对 “换行符” 的处理存在差异:- Windows 系统:写入时会将文本中的
\n
(换行)自动转换为\r\n
(回车 + 换行),读取时再将\r\n
反向转换为\n
; - Linux/macOS 系统:无任何字符转换(
\n
直接表示换行)。适用场景:仅用于跨 Windows/Linux 且无需保留原始字节的纯文本文件(如.txt
文档)。若跨系统处理文件,推荐优先使用二进制模式,避免因换行符转换导致数据错位(如 Linux 下用文本模式读取 Windows 生成的文件,会多出现\r
字符)。
- Windows 系统:写入时会将文本中的
文件读取:按字符、按行、按块
按字符读取:fgetc
、getc
、getchar
fgetc
(从指定流读 1 个字符)
#include <stdio.h>
/*** 从指定文件流读取1个字符,同时移动文件光标(位置指示器)* @param stream 待读取的文件流(如fp、stdin)* @return 成功:返回读取字符的ASCII码(unsigned char转换为int);* 失败/到文件末尾:返回EOF(宏定义,值为-1,定义在<stdio.h>中)* @note 需通过feof()和ferror()区分“到末尾”和“读错误”;每次读取后,光标后移1字节*/
int fgetc(FILE *stream);
getc
与getchar
getc
:功能与fgetc
完全一致,但getc
是宏定义实现,多次计算stream
参数(如getc(fp++)
会出错,避免此类用法)。getchar
:等价于getc(stdin)
,仅从标准输入(键盘)读 1 个字符
/*** getc:同fgetc(宏实现,需注意参数副作用)* @param stream 待读取的流* @return 成功返回字符ASCII码,失败/末尾返回EOF*/
int getc(FILE *stream);/*** getchar:仅从stdin读1个字符(等价于getc(stdin))* @return 成功返回字符ASCII码,失败/末尾返回EOF*/
int getchar(void);
按行读取:fgets
(安全读取一行)
#include <stdio.h>
/*** 从指定流读取一行字符,存储到缓冲区,避免缓冲区溢出* @param s 自定义缓冲区(用于存储读取的字符,需提前分配空间,如char buf[1024])* @param n 缓冲区大小(最大读取n-1个字符,最后1个字节自动存'\0',保证字符串结束)* @param stream 待读取的流(如fp、stdin)* @return 成功:返回缓冲区s的地址;失败/到末尾(且未读任何字符):返回NULL* @note 读取停止条件:读满n-1个字符、遇到'\n'(会保留'\n'在缓冲区)、遇到EOF;* 若返回NULL,需用feof()和ferror()区分原因;3. 避免用gets()(无缓冲区大小限制,会溢出)*/
char *fgets(char *s, int n, FILE *stream);
按块读取:fread
(高效读取二进制 / 大量数据)
#include <stdio.h>
/*** 从流中读取指定数量的“数据块”(适合二进制文件,如图片、音频,或大量文本)* @param ptr 自定义缓冲区指针(存储读取的数据,需提前分配空间)* @param size 每个数据块的字节数(如读int类型,size=sizeof(int))* @param nmemb 要读取的数据块总数(如读10个int,nmemb=10)* @param stream 待读取的流* @return 成功读取的“数据块个数”:* - 等于nmemb:读取完全成功;* - 小于nmemb:可能是到文件末尾,或读错误;* - 0:size/nmemb为0,或读取失败* @note 总读取字节数 = 成功读取的块数 × size;需用feof()和ferror()区分“末尾”和“错误”*/
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
文件写入:按字符、按行、按块
按字符写入:fputc
、putc
、putchar
#include <stdio.h>
/*** 将1个字符写入指定流,同时移动文件光标* @param c 待写入的字符(需传入ASCII码,如'A'或65)* @param stream 待写入的流(如fp、stdout)* @return 成功:返回写入的字符(ASCII码);失败:返回EOF* @note 若流是“追加模式”(a/a+),字符始终写入文件末尾*/
int fputc(int c, FILE *stream);
putc
:功能与fputc
一致,宏定义实现,注意参数副作用。putchar
:等价于putc(c, stdout)
,仅写入标准输出(屏幕)。
按行写入:fputs
#include <stdio.h>
/*** 将字符串写入指定流(不自动添加'\n',需手动加)* @param s 待写入的字符串(以'\0'结尾,'\0'不会被写入)* @param stream 待写入的流* @return 成功:返回非负整数;失败:返回EOF* @note 与puts()区别:puts()会自动在字符串后加'\n',且仅写入stdout*/
int fputs(const char *s, FILE *stream);
按块写入:fwrite
(高效写入二进制 / 大量数据)
#include <stdio.h>
/*** 将指定数量的“数据块”写入流(适合二进制文件或大量数据)* @param ptr 存储待写入数据的缓冲区指针* @param size 每个数据块的字节数(如写int类型,size=sizeof(int))* @param nmemb 要写入的数据块总数* @param stream 待写入的流* @return 成功写入的“数据块个数”:等于nmemb为完全成功,小于则为写入错误* @note 总写入字节数 = 成功写入的块数 × size;二进制写入需用"wb"模式,避免换行符转换*/
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
文件关闭:fclose
#include <stdio.h>
/*** 关闭文件流,释放资源* @param stream 待关闭的文件流(如fp)* @return 成功:返回0;失败:返回EOF(如流已关闭仍调用)* @note 关闭前会自动刷新流的缓冲区(未写入的数据会被写入文件);* 关闭后stream指针变为野指针,不可再使用;* 禁止多次关闭同一流(会导致double free错误,堆内存重复释放)*/
int fclose(FILE *stream);
缓冲区刷新:fflush
标准 IO 的缓冲区需 “刷新” 后,数据才会从缓冲区写入文件 / 设备。fflush
用于手动触发刷新。
#include <stdio.h>
/*** 手动刷新流的缓冲区(将未写入的数据写入文件/设备)* @param stream 待刷新的流(如fp、stdout);若为NULL,刷新所有可刷新的流* @return 成功:返回0;失败(如写错误):返回EOF* @note 行缓冲流(如stdout)遇'\n'自动刷新;全缓冲流(如普通文件)满了自动刷新;* 无缓冲流(如stderr)无需刷新,直接输出;关闭流(fclose)时会自动刷新*/
int fflush(FILE *stream);
文件位置控制:fseek
、ftell
、rewind
文件光标(位置指示器)默认在文件开头,读写时会自动后移。通过以下函数可手动控制光标位置。
fseek
(设置光标位置)
#include <stdio.h>
/*** 设置文件流的光标位置(仅支持二进制流;文本流限制较多,建议用二进制流)* @param stream 待设置的流* @param offset 偏移量(可正可负:正表示向后移,负表示向前移)* @param whence 偏移基准点:* - SEEK_SET:基准点为文件开头(offset必须≥0)* - SEEK_CUR:基准点为当前光标位置* - SEEK_END:基准点为文件末尾(offset负表示向前移,如offset=-10表示末尾前10字节)* @return 成功:返回0;失败:返回非0(如offset超出文件范围)* @note 二进制流可用所有基准点;文本流仅支持offset=0且whence=SEEK_SET/SEEK_END*/
int fseek(FILE *stream, long int offset, int whence);
ftell
(获取当前光标位置)
#include <stdio.h>
/*** 获取文件流当前的光标位置(距离文件开头的字节数)* @param stream 待获取的流* @return 成功:返回光标位置(字节数,long类型);失败:返回-1L(L表示long)* @note 常用于计算文件大小(光标移到末尾,用ftell获取位置即为文件大小)*/
long int ftell(FILE *stream);
rewind
(光标移回文件开头)
#include <stdio.h>
/*** 将文件流的光标移回文件开头(等价于fseek(stream, 0, SEEK_SET))* @param stream 待操作的流* @note 无返回值,无法判断是否成功;若需判断,建议用fseek*/
void rewind(FILE *stream);
错误与末尾判断:feof
、ferror
fgetc
、fread
等函数返回 EOF 时,可能是 “到文件末尾”,也可能是 “读写错误”,需用以下函数区分。
#include <stdio.h>
/*** 判断流是否到达文件末尾* @param stream 待判断的流* @return 是:返回非0值;否:返回0* @note 仅在“读取操作返回EOF后”调用才有意义(提前调用可能误判)*/
int feof(FILE *stream);/*** 判断流是否发生读写错误* @param stream 待判断的流* @return 是:返回非0值;否:返回0* @note 错误标志需用clearerr()清除,否则后续操作仍会认为有错误*/
int ferror(FILE *stream);/*** 清除流的“末尾标志”和“错误标志”* @param stream 待清除的流* @note 清除后,可重新对流转发读写操作*/
void clearerr(FILE *stream);
格式化 IO:fprintf
、fscanf
、sprintf
、snprintf
fprintf
(格式化写入流)
#include <stdio.h>
/*** 按指定格式将数据写入流(如文件、屏幕)* @param stream 待写入的流(如fp、stdout)* @param format 格式控制字符串(如"%d %s",指定后续参数的类型和格式)* @param ... 可变参数(需与format的格式匹配,如int、char*等)* @return 成功:返回写入的字符总数;失败:返回负数* @note 与printf()区别:printf()固定写入stdout,fprintf()可指定流*/
int fprintf(FILE *stream, const char *format, ...);
fscanf
(格式化读取流)
#include <stdio.h>
/*** 按指定格式从流中读取数据(需注意缓冲区残留问题)* @param stream 待读取的流* @param format 格式控制字符串(如"%d %s")* @param ... 可变参数(需传入变量地址,如&int_var、char_var)* @return 成功读取的“数据项个数”;失败/到末尾:返回EOF* @note 读取时会跳过空白字符(空格、换行、制表符);读取字符串时遇空白字符停止*/
int fscanf(FILE *stream, const char *format, ...);
sprintf
与snprintf
(格式化写入缓冲区)
sprintf
:将格式化数据写入字符串缓冲区(无大小限制,可能溢出)。snprintf
:指定缓冲区大小,避免溢出(推荐使用)。
#include <stdio.h>
/*** 将格式化数据写入字符串缓冲区(不安全,可能溢出)* @param s 目标缓冲区(存储格式化后的字符串)* @param format 格式控制字符串* @param ... 可变参数* @return 成功:返回写入的字符数(不含'\0');失败:返回负数*/
int sprintf(char *s, const char *format, ...);/*** 将格式化数据写入字符串缓冲区(安全,指定最大写入字节数)* @param s 目标缓冲区* @param n 缓冲区大小(最大写入n-1个字符,最后加'\0')* @param format 格式控制字符串* @param ... 可变参数* @return 成功:返回应写入的字符数(若n足够);失败:返回负数* @note 若应写入的字符数≥n,仅写入n-1个字符,保证缓冲区安全*/
int snprintf(char *s, size_t n, const char *format, ...);
缓冲区机制(关键)
缓冲区的作用
IO 设备(如硬盘、键盘)速度远慢于 CPU,缓冲区暂存数据,减少 CPU 等待 IO 的时间(比如写文件时,先存缓冲区,满了再一次性写硬盘)。
三种缓冲区类型
缓冲类型 | 适用场景(含典型对象) | 刷新时机(数据写入设备的触发条件) |
---|---|---|
全缓冲 | 普通文件(如文本文件 demo.txt 、二进制文件 data.bin ),非实时交互类数据存储场景 |
- 缓冲区被数据填满(默认大小通常为 4KB/4096 字节); - 调用 fflush 函数手动强制刷新;- 关闭文件流( fclose ),关闭前自动刷新未写入数据;- 程序正常结束(如 return 0 );- 对同一文件流调用读操作(如 fread )或修改缓冲区类型 |
行缓冲 | 标准输出流(stdout ,如 printf 终端输出),需按行实时反馈的交互场景(如终端打印日志) |
- 缓冲区中检测到换行符 \n (核心触发条件,无 \n 则暂存);- 缓冲区被数据填满(默认 4KB); - 调用 fflush 函数手动强制刷新;- 关闭流( fclose )或程序正常结束;- 对关联流调用读操作(如对 stdin 用 fgetc 读键盘输入,会触发 stdout 刷新) |
无缓冲 | 标准错误流(stderr ,如 perror 错误提示),需即时输出紧急信息的场景(如程序报错) |
数据生成后直接写入设备,无需暂存到缓冲区;触发写操作(如 perror("error") )后立即输出,无延迟 |
标准 IO 缓冲区设置函数详解
以下函数均用于自定义标准 IO 流的缓冲区(类型、大小、存储地址),需在流打开后、首次读写操作前调用,否则可能导致缓冲区状态混乱或设置失效,相关功能在 C 标准及文档中均有约束。
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
功能
最灵活的缓冲区设置函数,支持自定义缓冲区地址、类型(全缓冲 / 行缓冲 / 无缓冲)和大小,是后三个函数的 “底层实现依赖”()。
参数解析与返回值
#include <stdio.h>
/*** @param stream 目标文件流(需已打开且未读写)* @param buf 缓冲区地址:* - 非NULL:使用用户提供的缓冲区(大小≥size)* - NULL:使用系统自动分配的缓冲区(大小由size指定)* @param mode 缓冲模式(必须是以下三者之一):* - _IONBF:无缓冲(数据直接输出,类似stderr,)* - _IOLBF:行缓冲(遇'\n'或缓冲区满刷新,类似stdout,)* - _IOFBF:全缓冲(缓冲区满或关闭流时刷新,类似普通文件,)* @param size 缓冲区大小(单位:字节):* - 若mode为_IONBF:size无意义(可传0)* - 若mode为_IOLBF/_IOFBF:size需≥1(系统自动分配时,size为最小缓冲大小)* @return 成功返回0;失败返回非0(如mode无效、流已读写、size过小)* @note 标准C函数(文档中明确列为文件访问函数,);* 关闭流时,系统分配的缓冲区会自动释放(用户提供的buf需手动释放);* 若buf非NULL,其内存需持续有效(全局/静态/堆内存,避免栈内存释放)*/
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
关键特性
- 覆盖所有缓冲需求:例如 “无缓冲 + 用户缓冲区”“行缓冲 + 系统分配缓冲区” 等场景;
- 文档中明确其与
setbuf
共同管理缓冲区关联关系:关闭流时,通过这两个函数设置的缓冲区会与流解除关联(若为系统分配则释放,若为用户主动分配(如用malloc
/calloc
分配内存,再通过setbuf
/setbuffer
/setvbuf
绑定到流)时,标准 IO 库仅使用缓冲区,不负责释放,需用户在缓冲区不再使用后手动释放(free
))。
void setbuf(FILE *stream, char *buf);
功能
为指定文件流stream
设置缓冲区,或禁用缓冲区,是setvbuf
的简化版本(默认缓冲区大小通常为 4KB,与文档中行缓冲 / 全缓冲默认大小一致)。
参数解析
#include <stdio.h>
/*** @param stream 目标文件流(如fp、stdout,需已通过fopen打开)* @param buf 缓冲区地址:* - 若为非NULL:使用用户提供的缓冲区(建议大小≥4096字节,匹配系统默认缓冲大小)* - 若为NULL:禁用该流的缓冲区(等效于无缓冲模式,类似stderr默认特性,)* @return 无返回值* @note 必须在流首次读写前调用;用户提供的buf需为全局/静态变量(避免栈内存释放后失效)*/
void setbuf(FILE *stream, char *buf);
关键特性
- 本质是
setvbuf(stream, buf, (buf==NULL)?_IONBF:_IOFBF, BUFSIZ)
的封装(BUFSIZ
是系统定义的默认缓冲大小,通常为 4096); - 若设置非 NULL 缓冲区,关闭流时该缓冲区会与流解除关联(但不会自动释放,需用户手动管理,)。
void setbuffer(FILE *stream, char *buf, size_t size);
功能
扩展setbuf
的功能,允许自定义缓冲区大小(不再固定为 4KB),更灵活适配不同读写场景(如大文件读写需更大缓冲区)。
参数解析
#include <stdio.h>
/*** @param stream 目标文件流(需已打开且未进行读写)* @param buf 缓冲区地址:* - 非NULL:使用用户提供的缓冲区(大小由size指定)* - NULL:禁用缓冲区(无缓冲模式)* @param size 缓冲区大小(单位:字节):* - 若buf非NULL,size需≥1(建议为2的幂,如1024、8192,提升IO效率)* - 若buf为NULL,size无意义(可传0)* @return 无返回值* @note 非标准C函数(但POSIX系统普遍支持);其他约束同setbuf(调用时机、缓冲区内存属性)*/
void setbuffer(FILE *stream, char *buf, size_t size);
关键特性
- 相比
setbuf
,核心优势是自定义size
,例如处理大文件时可设置 8KB/16KB 缓冲区,减少 IO 次数; - 若
buf
非 NULL 但size
过小(如 < 100 字节),可能导致频繁缓冲刷新,反而降低效率。
void setlinebuf(FILE *stream);
功能
强制将指定流stream
设置为行缓冲模式,等效于 “设置行缓冲 + 默认缓冲区大小”,适配需按行刷新的场景(如模拟 stdout 默认行为)。
参数解析
#include <stdio.h>
/*** @param stream 目标文件流(需已打开且未读写)* @return 无返回值* @note 本质是`setvbuf(stream, NULL, _IOLBF, BUFSIZ)`的封装;* 设置后,缓冲区遇'\n'或满时自动刷新(符合行缓冲特性);* 若流原本为全缓冲/无缓冲,调用后会切换为行缓冲*/
void setlinebuf(FILE *stream);
关键特性
- 典型用途:将普通文件流设为行缓冲(如日志文件,需逐行写入以便实时查看);
- 与 stdout 默认行缓冲的区别:stdout 仅在关联终端时为行缓冲,关联文件时为全缓冲,而
setlinebuf
可强制文件流也为行缓冲。
实战案例
案例 1:计算文件大小(命令行传参)
需求:通过命令行传入文件路径,计算并输出文件大小(单位:字节)。
#include <stdio.h>
#include <stdlib.h> // 包含exit
int main(int argc, char const *argv[]) {// 步骤1:判断命令行参数是否正确(需传入1个文件路径,argc应为2)if (argc != 2) {printf("用法:%s <文件路径>\n", argv[0]); // 提示正确用法,如"./file_size demo.txt"exit(1); // 退出程序,1表示错误退出}// 步骤2:以二进制只读模式打开文件FILE *fp = fopen(argv[1], "rb");if (fp == NULL) {perror("fopen error");exit(1);}// 步骤3:计算文件大小fseek(fp, 0, SEEK_END); // 光标移到末尾long file_size = ftell(fp); // 获取末尾位置(即文件大小)// 步骤4:输出结果printf("文件[%s]大小:%ld 字节\n", argv[1], file_size);// 步骤5:关闭文件fclose(fp);return 0;
}
编译运行:
gcc file_size.c -o file_size
./file_size demo.txt # 输出:文件[demo.txt]大小:123 字节
案例 2:文件拷贝(命令行传参)
需求:通过命令行传入源文件(A)和目标文件(B),将 A 的内容完整拷贝到 B(B 不存在则创建)。
#include <stdio.h>
#include <stdlib.h>
#define BUF_SIZE 1024 // 定义1KB缓冲区(平衡效率和内存)
int main(int argc, char const *argv[]) {// 步骤1:判断参数(需传入源文件和目标文件,argc=3)if (argc != 3) {printf("用法:%s <源文件> <目标文件>\n", argv[0]); // 如"./copy demo.txt demo_copy.txt"exit(1);}// 步骤2:打开源文件(只读)和目标文件(只写,不存在则创建)FILE *src_fp = fopen(argv[1], "rb"); // 源文件:二进制只读FILE *dest_fp = fopen(argv[2], "wb"); // 目标文件:二进制只写if (src_fp == NULL || dest_fp == NULL) {perror("fopen error");// 若已打开一个流,需关闭if (src_fp != NULL) fclose(src_fp);if (dest_fp != NULL) fclose(dest_fp);exit(1);}// 步骤3:循环读取源文件,写入目标文件char buf[BUF_SIZE];size_t read_cnt; // 每次读取的字节数while ((read_cnt = fread(buf, 1, BUF_SIZE, src_fp)) > 0) {// 读取多少,写入多少fwrite(buf, 1, read_cnt, dest_fp);}// 步骤4:判断拷贝是否成功if (ferror(src_fp)) {perror("读取源文件错误");} else if (ferror(dest_fp)) {perror("写入目标文件错误");} else {printf("拷贝成功:%s → %s\n", argv[1], argv[2]);}// 步骤5:关闭文件fclose(src_fp);fclose(dest_fp);return 0;
}
案例 3:定时写入系统时间到日志
需求:每隔 1 秒获取当前系统时间,按 “yy 年 mm 月 dd 日 星期 x hh:mm:ss” 格式写入 log.txt(不存在则创建),无限循环。
#include <stdio.h>
#include <time.h> // 包含时间相关函数
#include <unistd.h> // 包含sleep函数(Linux下)
// 星期转换:tm_wday是0(周日)~6(周六),转换为中文
const char *get_weekday(int tm_wday) {const char *week[] = {"日", "一", "二", "三", "四", "五", "六"};return week[tm_wday];
}int main() {// 以追加模式打开log.txt(不存在则创建,存在则追加)FILE *fp = fopen("log.txt", "a");if (fp == NULL) {perror("fopen error");return -1;}while (1) { // 无限循环// 步骤1:获取当前系统时间(秒数,从1970-01-01 00:00:00 UTC开始)time_t now = time(NULL);if (now == (time_t)-1) {perror("time error");fclose(fp);return -1;}// 步骤2:将秒数转换为本地时间(年、月、日等)struct tm *local_tm = localtime(&now);if (local_tm == NULL) {perror("localtime error");fclose(fp);return -1;}// 步骤3:按指定格式写入log.txt// 格式:yy年mm月dd日 星期x hh:mm:ss(注意:tm_year是年份-1900,tm_mon是0~11)fprintf(fp, "%02d年%02d月%02d日 星期%s %02d:%02d:%02d\n",local_tm->tm_year % 100, // yy(取年份后两位)local_tm->tm_mon + 1, // mm(加1,因为tm_mon是0~11)local_tm->tm_mday, // ddget_weekday(local_tm->tm_wday), // 星期xlocal_tm->tm_hour, // hhlocal_tm->tm_min, // mmlocal_tm->tm_sec); // ss// 步骤4:手动刷新缓冲区(避免日志延迟写入)fflush(fp);// 步骤5:等待1秒sleep(1);}// 注:循环不会退出,实际使用中需按Ctrl+C终止,终止后会自动关闭流fclose(fp);return 0;
}
注意事项
- 避免重复关闭文件:
fclose
多次调用同一流会导致 double free 错误,建议关闭后将指针置为NULL
(如fclose(fp); fp=NULL;
)。 - 二进制与文本模式区分:读写图片、音频等二进制文件必须用
"rb"
/"wb"
,避免系统转换字符导致文件损坏。 - 缓冲区刷新:用 “追加模式” 写日志时,需手动调用
fflush
,否则日志会暂存缓冲区,不及时写入文件。 - 命令行参数判断:接收命令行参数(如
argv[1]
)时,必须先判断argc
是否正确,避免访问空指针。 - 内存释放:用
malloc
分配的缓冲区(如fread
的缓冲区),必须用free
释放,避免内存泄漏。