文章目录
- 常用定义
- [1. 文件访问函数](#1. 文件访问函数)
- [2. 直接输入/输出](#2. 直接输入/输出)
- [3. 无格式输入/输出](#3. 无格式输入/输出)
- [4. 有格式输入/输出](#4. 有格式输入/输出)
-
- [scanf / fscanf / sscanf ------ 格式化输入](#scanf / fscanf / sscanf —— 格式化输入)
- [printf / fprintf / sprintf / snprintf ------ 格式化输出](#printf / fprintf / sprintf / snprintf —— 格式化输出)
- 使用va_list可变实参列表输入/输出
- [5. 文件寻位](#5. 文件寻位)
- [6. 文件上的操作](#6. 文件上的操作)
- [7. 错误处理](#7. 错误处理)
stdio.h
提供了C风格标准输入输出函数的声明、定义和相关宏定义。
常用定义
-
FILE
,每个FILE
对象代表一个C
I/O
流,保有控制C
I/O
流所需的全部信息。cpptypedef struct _iobuf { void* _Placeholder; } FILE;
-
fpos_t
完整非数组对象类型,足以唯一指定文件中的位置,包含其多字节剖析状态。cpptypedef __int64 fpos_t;
-
size_t
cpp//64位系统下 typedef unsigned __int64 size_t; //32位系统下 typedef unsigned int size_t;
std::size_t
可以存放下理论上可能存在的对象的最大大小,该对象可以是任何类型,包括数组。大小无法以
std::size_t
表示的类型是非良构 的。 (C++14 起) 在许多平台上(使用分段寻址的系统除外),std::size_t
可以存放下任何非成员的指针,此时可以视作其与std::uintptr_t
同义。
std::size_t
通常被用于数组索引和循环计数。使用其它类型来进行数组索引操作的程序可能会在某些情况下出错,例如在 64 位系统中使用unsigned int 进行索引时,如果索引号超过 UINT_MAX 或者依赖于 32 位取模运算的话,程序就会出错。在对诸如 std::string、std::vector 等 C++ 容器进行索引操作时,正确的类型是该容器的成员 typedef size_type,而该类型通常被定义为与 std::size_t 相同。
1. 文件访问函数
fopen打开文件
cpp
FILE* fopen( const char* filename, const char* mode );
打开 filename
所指示的文件并返回与该文件关联的流。用 mode
确定文件访问模式。
filename
- 要关联文件流到的文件名mode
- 确定文件访问模式的字符串- 返回值 - 若成功,则返回指向控制打开的文件流的对象的指针,并清除文件尾和错误位。流为完全缓冲,除非 filename 指代交互设备。错误时,返回空指针。
文件访问模式字符串 | 含义 | 解释 | 若文件已存在的行动 | 若文件不存在的行动 |
---|---|---|---|---|
"r" |
读 | 为读取打开文件 | 从起始读取 | 打开失败 |
"w" |
写 | 为写入创建文件 | 销毁内容 | 创建新文件 |
"a" |
追加 | 追加到文件 | 写入到末尾 | 创建新文件 |
"r+" |
扩展读 | 为读取/写入打开文件 | 从起始读取 | 错误 |
"w+" |
扩展写 | 为读取/写入创建文件 | 销毁内容 | 创建新文件 |
"a+" |
扩展追加 | 为读取/写入打开文件 | 写入到末尾 | 创建新文件 |
文件访问模式标志 "b"
能可选地指定,以用二进制模式打开文件。
在追加文件访问模式上,写入数据到文件尾,忽略文件位置指示器的当前位置,即无法用
fseek
指定文件位置。
fclose------关闭文件
cpp
int fclose( FILE* stream );
关闭给定的文件流。无论操作是否成功,流都不再关联到文件。
- 返回值:成功时为
0
,否则为EOF
(-1
)。
freopen------以不同名称打开既存流
cpp
FILE* freopen( const char* filename, const char* mode, FILE* stream );
- 试图关闭与
stream
关联的文件,忽略任何错误。 - 若
filename
非空,则试图用mode
打开filename
所指定的文件,如同用fopen
,然后将该文件与stream
所指向的文件流关联。 - 若
filename
为空指针,则函数试图重打开已与stream
关联的文件。
fflush------将输出流与实际文件同步
cpp
int fflush( FILE* stream );
- 对于输出流(和最近操作为输出的更新流),将来自 stream 缓冲区的未写入数据写入关联的输出设备。
- 对于输入流(和最近操作为输入的更新流),行为未定义。
- 若 stream 为空指针,则冲入所有打开的输出流,包含在库包内操作,或其他情况下程序不能直接访问的流。
setbuf------为文件流设置缓冲区
cpp
void setbuf( std::FILE* stream, char* buffer );
setvbuf------为文件流设置缓冲区与其大小
cpp
int setvbuf( std::FILE* stream, char* buffer, int mode, std::size_t size );
mode 缓冲模式
_IOFBF
全缓冲:当缓冲区为空时,从流读入数据。或者当缓冲区满时,向流写入数据。_IOLBF
行缓冲:每次从流中读入一行数据或向流中写入一行数据。_IONBF
无缓冲:直接从流中读入数据或直接向流中写入数据,缓冲设置无效。
2. 直接输入/输出
fread从文件读取
cpp
size_t fread( void* buffer, size_t size, size_t count, FILE* stream );
从给定输入流 stream
读取至多 count
个对象到数组 buffer
中。流的文件位置指示器前进读取的字符数。
等同于对每个对象调用
size
次std::fgetc
,并按顺序存储结果到转译为unsigned char
数组的buffer
中的相继位置。
buffer
- 指向要读取的数组中首个对象的指针size
- 每个对象的字节大小count
- 要读取的对象数stream
- 读取来源的输入文件流- 返回值
- 成功读取的对象数,若出现错误或文件尾条件,则可能小于
count
。 - 若
size
或count
为零,则fread
返回零且不进行其他动作。
- 成功读取的对象数,若出现错误或文件尾条件,则可能小于
cpp
FILE* pFile = fopen("test.txt", "rb");
unsigned short pRawData = new unsigned short[width*height];
fread(pRawData, sizeof(unsigned short), width*height, pFile);
//std::vector<char> buf(4); // char 可平凡复制
//fread(&buf[0], sizeof buf[0], buf.size(), f);
读取原始二进制数据(如图像、音频)
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *pFile = fopen("raw_data.bin", "rb"); // 二进制模式
if (!pFile) {
perror("无法打开文件");
return 1;
}
int width = 640, height = 480;
size_t total_elements = width * height;
// 分配内存存储 unsigned short 数据
unsigned short *pRawData = (unsigned short*)malloc(total_elements * sizeof(unsigned short));
if (!pRawData) {
fclose(pFile);
return 1;
}
// 读取数据
size_t elements_read = fread(pRawData, sizeof(unsigned short), total_elements, pFile);
printf("请求读取 %zu 个元素,实际读取 %zu 个。\n", total_elements, elements_read);
// **重要:检查是否读取完整**
if (elements_read != total_elements) {
if (feof(pFile)) {
printf("警告:文件提前结束,数据可能不完整!\n");
} else if (ferror(pFile)) {
printf("错误:读取文件时发生 I/O 错误!\n");
}
}
// ... 使用 pRawData 中的数据 ...
free(pRawData);
fclose(pFile);
return 0;
}
读取到 std::vector
(C++ 环境)
cpp
#include <vector>
#include <cstdio>
// C++ 示例
FILE* f = fopen("data.bin", "rb");
if (f) {
std::vector<char> buf(1024); // 预分配 1024 字节的缓冲区
size_t bytes_read = fread(buf.data(), sizeof(char), buf.size(), f);
// 或者使用 &buf[0] (C++11 前)
// size_t bytes_read = fread(&buf[0], sizeof(char), buf.size(), f);
buf.resize(bytes_read); // 调整 vector 大小到实际读取的字节数
fclose(f);
}
fwrite写入文件
cpp
size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream);
buffer
- 指向源数据内存缓冲区的指针。size
- 每个数据对象的大小(以字节为单位)。count
- 要写入的对象数量。stream
- 目标输出文件流指针。
写入原始二进制数据
cpp
#include <stdio.h>
int main() {
FILE *pFile = fopen("output.bin", "wb"); // 二进制写入模式
if (!pFile) {
perror("无法创建文件");
return 1;
}
// 准备一些数据
unsigned short data[] = {100, 200, 300, 400, 500};
size_t num_elements = sizeof(data) / sizeof(data[0]);
// 写入数据
size_t elements_written = fwrite(data, sizeof(unsigned short), num_elements, pFile);
printf("请求写入 %zu 个元素,实际写入 %zu 个。\n", num_elements, elements_written);
// **重要:检查写入是否成功**
if (elements_written != num_elements) {
printf("错误:写入文件时发生错误或磁盘空间不足!\n");
// 可能需要调用 ferror(pFile) 进一步诊断
}
fclose(pFile);
return 0;
}
写入 std::vector
内容 (C++ 环境)
cpp
#include <vector>
#include <cstdio>
// C++ 示例
std::vector<double> values = {3.14, 2.71, 1.41, 0.57};
FILE* f = fopen("doubles.bin", "wb");
if (f) {
size_t written = fwrite(values.data(), sizeof(double), values.size(), f);
if (written != values.size()) {
fprintf(stderr, "写入失败!\n");
}
fclose(f);
}
写入结构体数组
cpp
typedef struct {
int id;
char name[50];
float score;
} Student;
Student students[] = {
{1, "Alice", 95.5},
{2, "Bob", 87.0},
{3, "Charlie", 92.3}
};
FILE *fp = fopen("students.dat", "wb");
if (fp) {
size_t count = fwrite(students, sizeof(Student), 3, fp);
if (count != 3) {
printf("写入学生记录失败!\n");
}
fclose(fp);
}
3. 无格式输入/输出
窄字符
宽字符加w后缀
3.1 fgetc
/getc
------从文件流获取字符
cpp
int fgetc(FILE *stream);
int getc(FILE *stream);
- 成功时返回读取的字符(提升为 int 类型)
cpp
#include <stdio.h>
int main() {
FILE *fp = fopen("test.txt", "r");
if (!fp) return 1;
int ch;
while ((ch = fgetc(fp)) != EOF) {
putchar(ch); // 输出到 stdout
}
fclose(fp);
return 0;
}
3.2 fgets
------从文件流获取字符串
cpp
char *fgets(char *str, int n, FILE *stream);
str
:存储读取内容的字符数组(缓冲区)。n
:最多读取 n-1 个字符(保留一个给 \0)。stream
:输入流文件指针。
cpp
#include <stdio.h>
int main() {
char buffer[100];
printf("请输入一行文本:");
if (fgets(buffer, sizeof(buffer), stdin)) {
printf("你输入的是:%s", buffer); // 包含 \n
}
return 0;
}
3.3 fputc
/putc
------写字符到文件流
cpp
int fputc(int ch, FILE *stream);
int putc(int ch, FILE *stream);
ch
:要写入的字符(以 int 形式传递)。
cpp
#include <stdio.h>
int main() {
FILE *fp = fopen("output.txt", "w");
if (!fp) return 1;
for (char c = 'A'; c <= 'Z'; ++c) {
fputc(c, fp);
}
fclose(fp);
return 0;
}
3.4 fputs
------写字符串到文件流
cpp
int fputs(const char *str, FILE *stream);
str
:以\0
结尾的字符串。stream
:目标输出流。
cpp
#include <stdio.h>
int main() {
FILE *fp = fopen("log.txt", "a");
if (!fp) return 1;
fputs("程序启动\n", fp);
fputs("日志记录...\n", fp);
fclose(fp);
return 0;
}
3.5 getchar
------从 stdin
读取字符
cpp
int getchar(void); // 等价于: fgetc(stdin)
cpp
int ch;
while ((ch = getchar()) != EOF && ch != '\n') {
// 处理字符
}
3.6 gets------从 stdin 读取字符串
(C++11 中弃用)
(C++14 中移除)
❌ 严重安全问题:不检查缓冲区边界,极易导致缓冲区溢出。
建议使用 fgets(str, size, stdin) 替代。
3.7 putchar
------写字符到 stdout
cpp
int putchar(int ch); //等价于: fputc(ch, stdout)
cpp
for (int i = 0; i < 10; ++i) {
putchar('*');
}
putchar('\n');
3.8 puts
------写字符串到 stdout
cpp
int puts(const char *str); //自动在末尾添加换行符 \n
cpp
puts("Hello World"); // 等价于 printf("Hello World\n");
3.9 ungetc
------把字符放回文件流
cpp
int ungetc(int ch, FILE *stream);
ch
:要"推回"的字符。stream
:对应的文件流。- 成功返回
ch
。 - 失败返回
EOF
。
cpp
int ch = getchar();
if (ch >= '0' && ch <= '9') {
ungetc(ch, stdin); // 推回,后续用 scanf 读数字
int num;
scanf("%d", &num);
}
4. 有格式输入/输出
窄/多字节字符
scanf / fscanf / sscanf ------ 格式化输入
函数 | 原型 | 作用 |
---|---|---|
scanf |
int scanf(const char *format, ...); |
从 stdin 读取 |
fscanf |
int fscanf(FILE *stream, const char *format, ...); |
从文件流读取 |
sscanf |
int sscanf(const char *str, const char *format, ...); |
从字符串缓冲区读取 |
format
:格式控制字符串(如%d
,%s
,%f
等)。...
:可变参数列表,传入变量地址(如&x
)。
cpp
// scanf
int age;
printf("输入年龄:");
scanf("%d", &age);
// fscanf
FILE *fp = fopen("data.txt", "r");
float price;
fscanf(fp, "%f", &price);
fclose(fp);
// sscanf
char line[] = "张三 25 89.5";
char name[20];
int age;
float score;
sscanf(line, "%s %d %f", name, &age, &score);
printf / fprintf / sprintf / snprintf ------ 格式化输出
函数 | 原型 | 作用 |
---|---|---|
printf |
int printf(const char *format, ...); |
输出到 stdout |
fprintf |
int fprintf(FILE *stream, const char *format, ...); |
输出到文件流 |
sprintf |
int sprintf(char *str, const char *format, ...); |
输出到字符串缓冲区 |
snprintf |
int snprintf(char *str, size_t size, const char *format, ...); |
安全版本,限制写入长度 |
sprintf
不检查缓冲区大小,有溢出风险。
snprintf
更安全,推荐使用。
cpp
// printf
printf("姓名:%s,年龄:%d\n", name, age);
// fprintf
FILE *log = fopen("error.log", "w");
fprintf(log, "错误代码:%d\n", errno);
fclose(log);
// sprintf (谨慎使用)
char buffer[50];
sprintf(buffer, "结果=%d", result); // 若结果很大,可能溢出!
// snprintf (推荐)
snprintf(buffer, sizeof(buffer), "编号:%04d", id); // 安全格式化
使用va_list可变实参列表输入/输出
这些函数用于实现自定义的格式化函数(如日志函数)。
vscanf
------从stdin读取有格式输入(使用可变实参列表)vfscanf
------从文件流读取有格式输入(使用可变实参列表)vsscanf
------从缓冲区读取有格式输入(使用可变实参列表)vprintf
------打印有格式输出到stdout(使用可变实参列表)vfprintf
------打印有格式输出到文件流(使用可变实参列表)vsprintf
------打印有格式输出到缓冲区(使用可变实参列表)vsnprintf
------打印有格式输出到缓冲区(使用可变实参列表)
cpp
#include <stdio.h>
#include <stdarg.h>
void my_log(const char *format, ...) {
va_list args;
va_start(args, format);
fprintf(stderr, "[LOG] ");
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
va_end(args);
}
// 使用
my_log("用户 %s 登录失败,尝试次数:%d", username, count);
cpp
void Log( const char* pFormat, ... )
{
char iBuffer[IBUFFER_LEN];
va_list args;
va_start(args, pFormat);
_vsnprintf_s(iBuffer, IBUFFER_LEN, pFormat, args);
va_end(args);
mEventQueue.push_back(std::string(iBuffer));
}
5. 文件寻位
ftell------返回当前文件位置指示器
cpp
long ftell(FILE *stream);
成功:返回从文件开头到当前位置的字节数(long 类型)。
失败:返回 -1L
用途: 记录当前位置,用于后续恢复。
cpp
FILE *fp = fopen("data.bin", "rb");
if (!fp) return -1;
// 读取一些数据
fseek(fp, 100, SEEK_SET);
long pos = ftell(fp); // pos == 100
printf("当前位置:%ld\n", pos);
fclose(fp);
fseek------移动文件位置指示器到文件中的指定位置
cpp
int fseek(FILE *stream, long offset, int origin);
stream
:文件指针。offset
:偏移量(字节数)。origin
:起始位置,可选值:SEEK_SET
:文件开头(偏移从0开始)。SEEK_CUR
:当前位置。SEEK_END
:文件末尾。
cpp
FILE *fp = fopen("example.txt", "r");
// 移动到文件第50个字节
fseek(fp, 50, SEEK_SET);
// 向前移动10字节
fseek(fp, 10, SEEK_CUR);
// 移动到倒数第5个字节
fseek(fp, -5, SEEK_END);
fclose(fp);
在文本模式下,
fseek
的offset
必须是ftell
返回的值或0
。在二进制模式下,可以使用任意偏移量。
文件大小超过4GB大小会超出long
类型的上限
特性 | fseek |
_fseeki64 |
---|---|---|
标准性 | ISO C 标准函数 | Microsoft 扩展(非标准) |
偏移量类型 | long (通常 32 位) | __int64 (64 位) |
最大文件支持 | ~2GB(有符号)或 4GB(无符号) | ~9EB (Exabytes, 2^63 字节) |
可移植性 | 高(所有 C 编译器) | 低(主要在 Windows/MSVC 环境) |
头文件 | <stdio.h> | <stdio.h> |
fgetpos------获取文件位置
cpp
int fgetpos(FILE *stream, fpos_t *pos);
stream
:文件指针。pos
:指向fpos_t
类型对象的指针,用于存储位置信息。- 成功返回 0。
失败返回非零值。
cpp
FILE *fp = fopen("largefile.dat", "r");
fpos_t position;
fgetpos(fp, &position); // 保存当前位置
// 做一些操作...
fseek(fp, 1024, SEEK_CUR);
// 恢复到之前位置
fsetpos(fp, &position);
fclose(fp);
fsetpos------设置文件位置
cpp
int fsetpos(FILE *stream, const fpos_t *pos);
rewind------重置文件位置到开头
cpp
void rewind(FILE *stream);
等价于
cpp
fseek(stream, 0L, SEEK_SET);
额外作用:清除文件结束标志(EOF)和错误标志。
cpp
FILE *fp = fopen("data.txt", "r");
// ... 读取到文件末尾
rewind(fp); // 回到开头,可重新读取
6. 文件上的操作
remove------删除文件
cpp
int remove(const char *filename);
filename
:要删除的文件路径。
cpp
if (remove("temp.log") == 0) {
printf("文件删除成功\n");
} else {
perror("删除失败");
}
rename------重命名或移动文件
cpp
int rename(const char *old_filename, const char *new_filename);
old_filename
:原文件名。new_filename
:新文件名(可包含路径,实现移动)。
cpp
// 重命名
rename("old.txt", "new.txt");
// 移动并重命名
rename("data.txt", "/backup/archived_data.txt");
如果目标文件已存在,行为未定义(通常会失败或覆盖,取决于系统)。
tmpfile
------创建并打开一个临时、自动移除的文件
cpp
FILE *tmpfile(void);
-
成功返回指向临时文件的
FILE*
指针。失败返回
NULL
。 -
文件以 "w+b" 模式自动打开(二进制读写)。
-
程序正常结束或文件关闭时自动删除。
-
不需要手动调用
remove
。
cpp
FILE *tmp = tmpfile();
if (tmp) {
fprintf(tmp, "临时数据:%d\n", 123);
rewind(tmp);
// 使用临时文件...
fclose(tmp); // 自动删除
}
tmpnam
------返回一个唯一独有的文件名
cpp
char *tmpnam(char *str);
str
:存储文件名的缓冲区(至少L_tmpnam
字节)。- 若为
NULL
,函数返回静态缓冲区的指针(不安全,不推荐多线程)。
- 若为
- 成功返回指向文件名字符串的指针。
失败返回 NULL。
cpp
char temp_name[L_tmpnam];
if (tmpnam(temp_name)) {
printf("临时文件名:%s\n", temp_name);
FILE *fp = fopen(temp_name, "w");
if (fp) {
fprintf(fp, "临时内容\n");
fclose(fp);
remove(temp_name); // 手动清理
}
}
7. 错误处理
clearerr
------清除错误和EOF标志
cpp
void clearerr(FILE *stream);
cpp
FILE *fp = fopen("data.txt", "r");
// ... 读取到EOF
if (feof(fp)) {
printf("到达文件末尾\n");
clearerr(fp); // 清除EOF标志
// 可以尝试重新定位或进行其他操作
}
feof
------检查文件结束标志
cpp
int feof(FILE *stream);
如果文件结束标志被设置,返回非零值。
否则返回 0。
feof
只在尝试读取并发现已到文件末尾后才返回真。不能用于循环条件判断的首选方法。
cpp
// 错误!可能多处理一次
while (!feof(fp)) {
fscanf(fp, "%d", &x);
// 如果读取失败(如格式错误),会无限循环
}
// 正确用法
int x;
while (fscanf(fp, "%d", &x) == 1) {
// 正确处理
}
ferror
------检查文件错误
cpp
int ferror(FILE *stream);
- 如果流的错误标志被设置,返回非零值。
否则返回 0。 - 用途: 区分是文件结束还是I/O错误。
cpp
int c;
while ((c = fgetc(fp)) != EOF) {
putchar(c);
}
if (feof(fp)) {
printf("\n正常到达文件末尾。\n");
} else if (ferror(fp)) {
printf("\n读取过程中发生错误!\n");
}
perror
------打印错误信息
cpp
void perror(const char *s);
s
:用户自定义的错误前缀(如 "读取文件")。
作用: 将s
和当前errno
对应的错误消息输出到stderr
。
cpp
FILE *fp = fopen("nonexistent.txt", "r");
if (!fp) {
perror("打开文件失败");
// 输出类似:打开文件失败: No such file or directory
}