当前位置: 首页 > news >正文

【C标准库】详解<stdio.h>标准输入输出库

文章目录

  • 常用定义
  • 1. 文件访问函数
    • fopen打开文件
  • 2. 直接输入/输出
    • fread从文件读取
    • fwrite写入文件
  • 3. 无格式输入/输出
  • 4. 有格式输入/输出
    • scanf / fscanf / sscanf —— 格式化输入
    • printf / fprintf / sprintf / snprintf —— 格式化输出
    • 使用va_list可变实参列表输入/输出
  • 5. 文件寻位
    • ftell——返回当前文件位置指示器
    • fseek——移动文件位置指示器到文件中的指定位置
    • fgetpos——获取文件位置
    • fsetpos——设置文件位置
    • rewind——重置文件位置到开头
  • 6. 文件上的操作
    • remove——删除文件
    • rename——重命名或移动文件
  • 7. 错误处理


stdio.h 提供了C风格标准输入输出函数的声明、定义和相关宏定义。

API Reference Doc

常用定义

  • FILE,每个 FILE 对象代表一个 C I/O 流,保有控制 C I/O 流所需的全部信息。
    typedef struct _iobuf
    { void* _Placeholder;
    } FILE;
    
  • fpos_t 完整非数组对象类型,足以唯一指定文件中的位置,包含其多字节剖析状态。
    typedef __int64 fpos_t;
    
  • size_t
    //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打开文件

FILE* fopen( const char* filename, const char* mode );

打开 filename 所指示的文件并返回与该文件关联的流。用 mode 确定文件访问模式。

  • filename - 要关联文件流到的文件名
  • mode - 确定文件访问模式的字符串
  • 返回值 - 若成功,则返回指向控制打开的文件流的对象的指针,并清除文件尾和错误位。流为完全缓冲,除非 filename 指代交互设备。错误时,返回空指针。
文件访问模式字符串含义解释若文件已存在的行动若文件不存在的行动
"r"为读取打开文件从起始读取打开失败
"w"为写入创建文件销毁内容创建新文件
"a"追加追加到文件写入到末尾创建新文件
"r+"扩展读为读取/写入打开文件从起始读取错误
"w+"扩展写为读取/写入创建文件销毁内容创建新文件
"a+"扩展追加为读取/写入打开文件写入到末尾创建新文件

文件访问模式标志 "b" 能可选地指定,以用二进制模式打开文件

在追加文件访问模式上,写入数据到文件尾,忽略文件位置指示器的当前位置,即无法用fseek指定文件位置。


fclose——关闭文件

int fclose( FILE* stream );

关闭给定的文件流。无论操作是否成功,流都不再关联到文件。

  • 返回值:成功时为 ​0​ ,否则为 EOF (-1)。

freopen——以不同名称打开既存流

FILE* freopen( const char* filename, const char* mode, FILE* stream );
  1. 试图关闭与 stream 关联的文件,忽略任何错误。
  2. filename 非空,则试图用 mode 打开 filename 所指定的文件,如同用 fopen ,然后将该文件与 stream 所指向的文件流关联。
  3. filename 为空指针,则函数试图重打开已与 stream 关联的文件。

fflush——将输出流与实际文件同步

int fflush( FILE* stream );
  • 对于输出流(和最近操作为输出的更新流),将来自 stream 缓冲区的未写入数据写入关联的输出设备。
  • 对于输入流(和最近操作为输入的更新流),行为未定义。
  • 若 stream 为空指针,则冲入所有打开的输出流,包含在库包内操作,或其他情况下程序不能直接访问的流。

setbuf——为文件流设置缓冲区

void setbuf( std::FILE* stream, char* buffer );

setvbuf——为文件流设置缓冲区与其大小

int setvbuf( std::FILE* stream, char* buffer, int mode, std::size_t size );

mode 缓冲模式

  • _IOFBF 全缓冲:当缓冲区为空时,从流读入数据。或者当缓冲区满时,向流写入数据。
  • _IOLBF 行缓冲:每次从流中读入一行数据或向流中写入一行数据。
  • _IONBF 无缓冲:直接从流中读入数据或直接向流中写入数据,缓冲设置无效。

2. 直接输入/输出

fread从文件读取

size_t fread( void* buffer, size_t size, size_t count, FILE* stream );

从给定输入流 stream 读取至多 count 个对象到数组 buffer 中。流的文件位置指示器前进读取的字符数。

等同于对每个对象调用 sizestd::fgetc ,并按顺序存储结果到转译为 unsigned char 数组的 buffer 中的相继位置。

  • buffer - 指向要读取的数组中首个对象的指针
  • size - 每个对象的字节大小
  • count - 要读取的对象数
  • stream - 读取来源的输入文件流
  • 返回值
    • 成功读取的对象数,若出现错误或文件尾条件,则可能小于 count
    • sizecount 为零,则 fread 返回零且不进行其他动作。
 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);

读取原始二进制数据(如图像、音频)

#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++ 环境)

#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写入文件

size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream);
  • buffer - 指向源数据内存缓冲区的指针。
  • size - 每个数据对象的大小(以字节为单位)。
  • count - 要写入的对象数量。
  • stream - 目标输出文件流指针。

写入原始二进制数据

#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++ 环境)

#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);
}

写入结构体数组

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——从文件流获取字符

int fgetc(FILE *stream);
int getc(FILE *stream);
  • 成功时返回读取的字符(提升为 int 类型)
#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——从文件流获取字符串

char *fgets(char *str, int n, FILE *stream);
  • str:存储读取内容的字符数组(缓冲区)。
  • n:最多读取 n-1 个字符(保留一个给 \0)。
  • stream:输入流文件指针。
#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——写字符到文件流

int fputc(int ch, FILE *stream);
int putc(int ch, FILE *stream);
  • ch:要写入的字符(以 int 形式传递)。
#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——写字符串到文件流

int fputs(const char *str, FILE *stream);
  • str:以 \0 结尾的字符串。
  • stream:目标输出流。
#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 读取字符

int getchar(void);  // 等价于: fgetc(stdin)
int ch;
while ((ch = getchar()) != EOF && ch != '\n') {// 处理字符
}

3.6 gets——从 stdin 读取字符串

(C++11 中弃用)
(C++14 中移除)
❌ 严重安全问题:不检查缓冲区边界,极易导致缓冲区溢出。
建议使用 fgets(str, size, stdin) 替代。

3.7 putchar——写字符到 stdout

int putchar(int ch);  //等价于: fputc(ch, stdout)
for (int i = 0; i < 10; ++i) {putchar('*');
}
putchar('\n');

3.8 puts——写字符串到 stdout

int puts(const char *str);	//自动在末尾添加换行符 \n
puts("Hello World");  // 等价于 printf("Hello World\n");

3.9 ungetc——把字符放回文件流

int ungetc(int ch, FILE *stream);
  • ch:要“推回”的字符。
  • stream:对应的文件流。
  • 成功返回 ch
  • 失败返回 EOF
int ch = getchar();
if (ch >= '0' && ch <= '9') {ungetc(ch, stdin);  // 推回,后续用 scanf 读数字int num;scanf("%d", &num);
}

4. 有格式输入/输出

窄/多字节字符

scanf / fscanf / sscanf —— 格式化输入

函数原型作用
scanfint scanf(const char *format, ...);从 stdin 读取
fscanfint fscanf(FILE *stream, const char *format, ...);从文件流读取
sscanfint sscanf(const char *str, const char *format, ...);从字符串缓冲区读取
  • format:格式控制字符串(如 %d, %s, %f 等)。
  • ...:可变参数列表,传入变量地址(如 &x)。
// 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 —— 格式化输出

函数原型作用
printfint printf(const char *format, ...);输出到 stdout
fprintfint fprintf(FILE *stream, const char *format, ...);输出到文件流
sprintfint sprintf(char *str, const char *format, ...);输出到字符串缓冲区
snprintf int snprintf(char *str, size_t size, const char *format, ...);安全版本,限制写入长度

sprintf 不检查缓冲区大小,有溢出风险。
snprintf 更安全,推荐使用。

// 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——打印有格式输出到缓冲区(使用可变实参列表)
#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);
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——返回当前文件位置指示器

long ftell(FILE *stream);

成功:返回从文件开头到当前位置的字节数(long 类型)。
失败:返回 -1L
用途: 记录当前位置,用于后续恢复。

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——移动文件位置指示器到文件中的指定位置

int fseek(FILE *stream, long offset, int origin);
  • stream:文件指针。
  • offset:偏移量(字节数)。
  • origin:起始位置,可选值:
    • SEEK_SET:文件开头(偏移从0开始)。
    • SEEK_CUR:当前位置。
    • SEEK_END:文件末尾。
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);

在文本模式下,fseekoffset 必须是 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——获取文件位置

int fgetpos(FILE *stream, fpos_t *pos);
  • stream:文件指针。
  • pos:指向 fpos_t 类型对象的指针,用于存储位置信息。
  • 成功返回 0。
    失败返回非零值。
FILE *fp = fopen("largefile.dat", "r");
fpos_t position;fgetpos(fp, &position);  // 保存当前位置// 做一些操作...
fseek(fp, 1024, SEEK_CUR);// 恢复到之前位置
fsetpos(fp, &position);fclose(fp);

fsetpos——设置文件位置

int fsetpos(FILE *stream, const fpos_t *pos);

rewind——重置文件位置到开头

void rewind(FILE *stream);

等价于

fseek(stream, 0L, SEEK_SET);

额外作用:清除文件结束标志(EOF)和错误标志。

FILE *fp = fopen("data.txt", "r");
// ... 读取到文件末尾rewind(fp);  // 回到开头,可重新读取

6. 文件上的操作

remove——删除文件

int remove(const char *filename);
  • filename:要删除的文件路径。
if (remove("temp.log") == 0) {printf("文件删除成功\n");
} else {perror("删除失败");
}

rename——重命名或移动文件

int rename(const char *old_filename, const char *new_filename);
  • old_filename:原文件名。
  • new_filename:新文件名(可包含路径,实现移动)。
// 重命名
rename("old.txt", "new.txt");// 移动并重命名
rename("data.txt", "/backup/archived_data.txt");

如果目标文件已存在,行为未定义(通常会失败或覆盖,取决于系统)。


tmpfile——创建并打开一个临时、自动移除的文件

FILE *tmpfile(void);
  • 成功返回指向临时文件的 FILE* 指针。
    失败返回 NULL

  • 文件以 “w+b” 模式自动打开(二进制读写)。

  • 程序正常结束或文件关闭时自动删除。

  • 不需要手动调用 remove

FILE *tmp = tmpfile();
if (tmp) {fprintf(tmp, "临时数据:%d\n", 123);rewind(tmp);// 使用临时文件...fclose(tmp);  // 自动删除
}

tmpnam——返回一个唯一独有的文件名

char *tmpnam(char *str);
  • str:存储文件名的缓冲区(至少 L_tmpnam 字节)。
    • 若为 NULL,函数返回静态缓冲区的指针(不安全,不推荐多线程)。
  • 成功返回指向文件名字符串的指针。
    失败返回 NULL。
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标志

void clearerr(FILE *stream);
FILE *fp = fopen("data.txt", "r");
// ... 读取到EOFif (feof(fp)) {printf("到达文件末尾\n");clearerr(fp);  // 清除EOF标志// 可以尝试重新定位或进行其他操作
}

feof——检查文件结束标志

int feof(FILE *stream);

如果文件结束标志被设置,返回非零值。
否则返回 0。

feof 只在尝试读取并发现已到文件末尾后才返回真。不能用于循环条件判断的首选方法。

// 错误!可能多处理一次
while (!feof(fp)) {fscanf(fp, "%d", &x);// 如果读取失败(如格式错误),会无限循环
}// 正确用法
int x;
while (fscanf(fp, "%d", &x) == 1) {// 正确处理
}

ferror——检查文件错误

int ferror(FILE *stream);
  • 如果流的错误标志被设置,返回非零值。
    否则返回 0。
  • 用途: 区分是文件结束还是I/O错误。
int c;
while ((c = fgetc(fp)) != EOF) {putchar(c);
}if (feof(fp)) {printf("\n正常到达文件末尾。\n");
} else if (ferror(fp)) {printf("\n读取过程中发生错误!\n");
}

perror——打印错误信息

void perror(const char *s);
  • s:用户自定义的错误前缀(如 “读取文件”)。
    作用: 将 s 和当前 errno 对应的错误消息输出到 stderr
FILE *fp = fopen("nonexistent.txt", "r");
if (!fp) {perror("打开文件失败"); // 输出类似:打开文件失败: No such file or directory
}
http://www.xdnf.cn/news/1355203.html

相关文章:

  • CUDA和torch的安装
  • 什么是多元线性回归,系数、自变量、因变量是什么,多元线性回归中的线性是什么
  • 多光谱相机检测石油石化行业的跑冒滴漏的可行性分析
  • 【yocto】Yocto Project 配置层(.conf)文件语法详解
  • calchash.exe和chckhash.exe计算pe文件hash值的两个实用小工具
  • 智慧零售漏扫率↓79%!陌讯多模态融合算法在智能收银与货架管理的实战解析
  • 双目密集匹配(stereo dense matching)
  • stack,queue以及deque的介绍
  • 深度学习中主流激活函数的数学原理与PyTorch实现综述
  • 【字母异位分组】
  • 随机森林1
  • 【机器学习深度学习】多模态学习
  • 【GaussDB】使用MySQL客户端连接到GaussDB的M-Compatibility数据库
  • 【85页PPT】数字化转型LIMS大型企业智能制造之LIMS实验室管理系统产品解决方案(附下载方式)
  • MVC模式在个人博客系统中的应用
  • 简单介绍计算机的工作过程
  • 激光雷达工作原理
  • 算法训练营day59 图论⑨ dijkstra(堆优化版)精讲、Bellman_ford 算法精讲
  • C++初阶(2)C++入门基础1
  • 第1篇:走进日志框架的世界 - 从HelloWorld到企业级应用
  • 为什么在WHERE子句里使用函数,会让索引失效
  • 复杂工业场景误报率↓85%!陌讯多模态火焰识别算法实战解析
  • Codeforces Round 1043 (Div. 3)(A-E)
  • 历史数据分析——半导体
  • 【科研绘图系列】浮游植物的溶解性有机碳与初级生产力的关系
  • 【Game】Powerful——Punch and Kick(12.2)
  • ComfyUI Portrait Master肖像大师中文版
  • 【51单片机】【protues仿真】基于51单片机宠物投食器系统
  • Redis 持久化策略
  • 如何创建自己的 Minecraft 世界