全网最详细的Linux文件IO:从概念到实战
·
一、Linux文件IO的说明
(一)、文件的概念(Linux下一切皆文件)
- 狭义:指普通的文本文件,或二进制文件,包括日常所见的txt文档、源代码、word文档、压缩包、图片、视频、音频文件等

- 广义:除了狭义上的文件外,几乎所有可操作的设备或结构都可视为文件。包括键盘、鼠标、硬盘、串口、显示屏、触摸屏等,也包括网络通讯端口(多机通信要用到的文件)、进程间通讯管道(单机通信)等抽象概念

(二)、Linux中文件的分类
普通文件(符号:-)(popular): 存在于外部存储器中,用于存储普通数据 --- 标准IO、文件属性与目录操作(*****)
目录文件(符号:d)(directory): 用于存放目录项,是文件系统管理的重要文件类型
管道文件(符号:p)(pipeline): 一种用于进程间通信的特殊文件,也被称为命名管道FIFO --- 系统编程
套接字文件(符号:s)(socket): 一种用于网络间通信的特殊文件 --- 网络编程
链接文件(符号:l)(link): 用于间接访问另外一个目标文件,相当于windows系统快捷方式
字符设备文件(符号:c)(character):字符设备在应用层的访问接口(以字符为单位,跟系统进行数据交换的设备,比如:键盘、鼠标、触摸屏等) --- 系统IO、设备操作(*****)
块设备文件(符号:b)(block): 块设备在应用层的访问接口 (以块为单位(256、512、1024字节为一块)),跟系统进行数据交换的设备,比如:U盘、内存、硬盘等
- 使用ls -l命令,即可列出文件的详细信息,并从其第一个字符看出是什么文件

(三)、系统IO与标准IO说明
- 说明:对文件的操作,基本上就是输入输出,因此也一般称为IO接口
- 在操作系统层面上:这一组专门针对文件的IO接口就被称为系统IO --- 偏向于底层(设备文件)
- 在标准库的层面上:这一组专门针对文件的IO接口就被称为标准IO --- 偏向于上层(软件程序文件)
- 图解:
- IO控制文件的方式(系统IO控制):

- 标准IO:

- 解析:
- 系统IO:是众多系统调用中专用于文件操作的一部分接口
- 标准IO:是众多标准函数当中专用于文件操作的一部分接口
- 总结:
- 标准IO实际上是对系统IO的封装,系统IO是更接近底层的接口,如果把系统IO比喻为菜市场,提供各式肉蛋奶菜,那么标准IO就是对这些基本原理的进一步封装,是品类和功能更加丰富的酒庄饭店
(四)、如何选择系统IO与标准IO
- 系统IO:
- 由操作系统直接提供的函数接口,特点是简洁,功能单一
- 没有提供缓冲区,因此对海量数据的操作效率较低
- 套接字socket、设备文件的访问只能使用系统IO
- 标准IO:
- 由标准C库提供的函数接口,特点是功能丰富
- 由提供缓冲区,因此对海量数据的操作效率高
- 编程开发中尽量选择标准IO,但许多场合只能用系统IO
- 总结:这两则函数接口在实际编程开发中都经常会用到,都是基本开发技能
二、系统IO的基本API
(一)、文件的打开

- 关键点:
- open函数有两个版本,一个有两个参数,一个有三个参数
- 当打开一个已存在的文件时,指定两个参数即可(使用两个参数的那个open即可)
- 当创建一个新文件,需要用第三个参数指定新文件的权限,否则新文件的权限是随机值(系统会帮你配置)
- 模式flags,可以使用位或的方式,来同时指定多个模式
- 模式flags,O_NOCTTY主要用在后台精灵进程,阻止这些精灵进程拥有控制终端。 --- 系统编程的时候才讲的,现在不必看
- linux创建的文件权限:
- 注意:umask值和open函数设置得文件权限值
- 第一个数:表示八进制的意思
- 第二个数:文件所有者的权限
- 第三个数:文件所属组的权限
- 第四个数:其它用户的权限
- 注意:umask值和open函数设置得文件权限值
- 示例代码:
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd = 0; // 文件描述符
// 一、以下三种打开方式,都要求文件已存在,否则失败返回 (知道文件存在)
fd = open("1.txt", O_RDWR); // 以可读可写方式打开文件
fd = open("1.txt", O_RDONLY); // 以只读方式打开文件
fd = open("1.txt", O_WRONLY); // 以只写方式打开文件
// 二、以下三种打开方式,如果文件不存在,则创建文件,并设置其权限为0644。 存在则返回错误信息 (你想要确保文件存在)
fd = open("1.txt", O_RDWR|O_CREAT|O_EXCL, 0644); // 以可读可写方式打开文件, 若文件不存在,就创建该文件,若文件存在则返回错误信息
fd = open("1.txt", O_RDONLY|O_CREAT|O_EXCL, 0644); // 以只读方式打开文件, 若文件不存在,就创建该文件,若文件存在则返回错误信息
fd = open("1.txt", O_WRONLY|O_CREAT|O_EXCL, 0644); // 以只写方式打开文件, 若文件不存在,就创建该文件,若文件存在则返回错误信息
// 三、以下三种打开方式,如果文件不存在,则创建文件,并清空里面的信息,并设置其权限为0644 (你想要确保文件存在,并且里面是没有东西的)
fd = open("1.txt", O_RDWR|O_CREAT|O_TRUNC, 0644); // 以可读可写方式打开文件, 若文件不存在,就创建该文件,并清空里面的信息
fd = open("1.txt", O_RDONLY|O_CREAT|O_TRUNC, 0644); // 以只读方式打开文件, 若文件不存在,就创建该文件,并清空里面的信息
fd = open("1.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644); // 以只写方式打开文件, 若文件不存在,就创建该文件,并清空里面的信息
// 四、以下两种打开方式,都要求文件已存在,否则失败返回,并追加文件内容 (知道文件存在,并且想要追加数据到里面去)
fd = open("1.txt", O_RDWR|O_APPEND); // 以可读可写方式打开文件, 并追加文件内容
fd = open("1.txt", O_WRONLY|O_APPEND); // 以只写方式打开文件,并追加文件内容
return 0;
}
(二)、标准库函数的错误处理
- 说明:在所有的库函数中,如果调用过程中出错了,那么该函数除了会返回一个特定的数据来告诉用户调用失效之外,还都会去修改一个大家共同的全局错误码errno,我们可以通过这个错误码,来进一步确认究竟是什么错误
- 关键点:
- 如果库函数,系统调用出错了,全局错误码errno会随之改变
- 如果库函数,系统调用没出错,全局错误码errno不会改变
- 一个库函数,系统调用出错后,若未及时处理错误码,则错误码可能会被随后的其它函数修改
(三)、文件描述符的本质
- 说明:函数open()的返回值,是一个整型int数据,这个整型数据,实际上是内核中的一个称为fd_array的数组的下标
- 图解:

- 解释:打开文件时,内核产生一个指向file{}的指针,并将该指针放入一个位于file_struct{}的数组fd_array[]中,而该指针所在的数组的下标,就被open()函数返回给用户,用户把这个数组下标称为文件描述符(fd)
- 结论:
- 文件描述符从0开始,每打开一个文件,就产生一个新的文件描述符
- 可以重复打开同一个文件,每次打开文件都会使用内核产生系列结构体,并得到不同的文件描述符
- 由于系统在每一个进程开始运行时,都默认打开了一次键盘、两次屏幕,因此0、1、2描述符分别代表标准输入、标准输出和标准出错

(四)、文件的读取、写入

- 关键点:
- 参数count是读写字节数的愿望值,实际读写成功的字节数由返回值决定
- 读取普通文件时,如果当读到了文件末尾,read()会返回0
- 示例代码1:不断地读取文件里面的数据,并打印出来
#include <stdio.h>
// open函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// read函数、close
#include <unistd.h>
// 全局错误码声明所在的文件
#include <errno.h>
// strerror函数
#include <string.h>
// 主函数
int main(int argc, char const *argv[])
{
// 0、判断命令行参数
if (argc != 2)
printf("命令行参数错误,格式为(./可执行文件 要打开文件)!\n");
// 1、打开文件(以只读的方式打开)
int fd = open(argv[1], O_RDONLY);
if ( -1 == fd )
{
printf("错误信息:打开%s文件失败, 错误原因:%s, 错误位置:%d行\n", argv[1], strerror(errno), __LINE__);
return -1;
}
// 2、读取文件里面的信息,并打印出来
char buf[128] = {0};
int ret = 0;
while (1)
{
// 清空buf里面的数据,防止对下一次的读取数据造成干扰
bzero(buf, sizeof(buf));
// 每次最多读取128个字节
ret = read(fd, buf, sizeof(buf));
// 如果返回0,则证明文件已被读取完毕
if (ret == 0)
{
printf("啊,我读完了!\n");
break;
}
// 打印读取的数据
printf("%s", buf);
}
// 3、关闭文件
close(fd);
return 0;
}
- 示例代码2:向文件写入数据
#include <stdio.h>
// open函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// write函数、close函数
#include <unistd.h>
// 全局错误码声明所在的文件
#include <errno.h>
// strerror函数
#include <string.h>
int main(int argc, char const *argv[])
{
// 0、判断命令行参数
if (argc != 2)
printf("命令行参数错误,格式为(./可执行文件 要打开文件)!\n");
// 1、打开文件(以只写的方式打开,如果没有这个文件就创建,并清空文件)
int fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0644);
if (-1 == fd)
{
printf("错误信息:打开%s文件失败, 错误原因:%s, 错误位置:%d行\n", argv[1], strerror(errno), __LINE__);
return -1;
}
// 2、将数据写入
char buf[1024] = {0};
// 清空buf里面的数据,防止对下一次的写入数据造成干扰
bzero(buf, sizeof(buf));
// 从键盘输入数据
printf("作者,请继续您的作品,读者催更中!:\n");
scanf("%s", buf);
while(getchar()!='\n');
// 向文件写入数据
write(fd, buf, strlen(buf));
// 3、关闭文件
close(fd);
return 0;
}
- 注意:当我们对文件读写操作时,系统会为我们记录操作的位置,以便于下次继续进行读写操作的时候吗,从适当的地方开始
- 每当打开一个文件,系统就会维护一套包括文件操作位置在内的相关
- 对同一个文件描述符进行读写操作时,使用的同一套文件信息,影响的是同一个位置参数
- 对同一个文件的多个文件描述符进行读写操作时,使用的是不同的文件信息,影响的是不同的位置参数,彼此互相之间独立,这往往会导致文件信息的错乱
- 示例代码3:读写作用于一个读写位置
#include <stdio.h>
// open函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// read函数、 write函数、close函数
#include <unistd.h>
// 全局错误码声明所在的文件
#include <errno.h>
// strerror函数
#include <string.h>
int main(int argc, char const *argv[])
{
// 0、判断命令行参数
if (argc != 2)
printf("命令行参数错误,格式为(./可执行文件 要打开文件)!\n");
// 1、打开文件(以可读可写的方式打开)
int fd = open(argv[1], O_RDWR);
if ( -1 == fd )
{
printf("错误信息:打开%s文件失败, 错误原因:%s, 错误位置:%d行\n", argv[1], strerror(errno), __LINE__);
return -1;
}
// 2、读取文件里面的信息,并打印出来
char buf[128] = {0};
int ret = 0;
while (1)
{
// 清空buf里面的数据,防止对下一次的读取数据造成干扰
bzero(buf, sizeof(buf));
// 每次最多读取128个字节
ret = read(fd, buf, sizeof(buf));
// 如果返回0,则证明文件已被读取完毕
if (ret == 0)
{
printf("啊,我读完了!\n");
break;
}
// 打印读取的数据
printf("%s", buf);
}
// 2、将数据写入文件中
// 清空buf里面的数据,防止对下一次的写入数据造成干扰
bzero(buf, sizeof(buf));
// 从键盘输入数据
printf("作者,请继续您的作品,读者催更中!:\n");
scanf("%[^\n]", buf); // 直到遇到'\n'才退出
while(getchar()!='\n');
// 向文件写入数据
write(fd, buf, strlen(buf));
// 4、关闭文件
close(fd);
return 0;
}
(五)、文件的读写位置的设置

- 关键点:
- lseek函数可以将文件位置调整到任意的位置,可以是已有数据的地方,也可以是未有数据的地方,假设调整到文件末尾之后的某个地方,那么文件将会形成所谓的"空洞"
- lseek函数只能对普通文件调整文件位置,不能对管道文件调整
- lseek函数的返回值是调整后的文件位置距离文件开头的偏移量,单位是字节
- 示例代码1(拓展):形成一个空洞文件
#include <stdio.h>
// open函数、lseek函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// lseek函数
#include <unistd.h>
// 全局错误码声明所在的文件
#include <errno.h>
// strerror函数
#include <string.h>
// 主函数
int main(int argc, char const *argv[])
{
// 0、判断命令行参数
if (argc != 2)
printf("命令行参数错误,格式为(./可执行文件 要打开文件)!\n");
// 1、打开文件(以可读可写的形式打开,如果没有这个文件就创建,并清空文件)
int fd = open(argv[1], O_RDWR|O_CREAT|O_TRUNC, 0644);
if (-1 == fd)
{
printf("错误信息:打开%s文件失败, 错误原因:%s, 错误位置:%d行\n", argv[1], strerror(errno), __LINE__);
return -1;
}
// 2、在空洞前,写入数据(头信息数据)
char w_buf1[128] = "文件的大小+文件的名字+文件的格式";
write(fd, w_buf1, strlen(w_buf1));
// 3、利用lseek形成一个空洞文件
lseek(fd, 10*1024*1024, SEEK_CUR);
// 4、在空洞后,写入数据(尾信息数据)
char w_buf2[128] = "文件的末尾";
write(fd, w_buf2, strlen(w_buf2));
// 5、关闭文件
close(fd);
return 0;
}
- 示例代码(拓展):获取文件的大小
#include <stdio.h>
// open函数、lseek函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// lseek函数
#include <unistd.h>
// 全局错误码声明所在的文件
#include <errno.h>
// strerror函数
#include <string.h>
/**
* @brief: 获取文件的大小
* @note: None
* @param: pathname:要计算大小的文件路径
* @retval: 成功:返回计算的文件的大小
* 失败:返回-1
*/
off_t GetFileSize(const char *pathname)
{
// 1、打开文件(以读写的权限打开文件)
int fd = open(pathname, O_RDWR);
if (-1 == fd)
{
printf("错误信息:打开%s文件失败, 错误原因:%s, 错误位置:%d行\n", pathname, strerror(errno), __LINE__);
return -1;
}
// 2、获取文件的大小
off_t ret_len = lseek(fd, 0, SEEK_END); // 将文件位置偏移到文件末尾,并只移动0字节
// 3、关闭文件
close(fd);
// 4、返回文件的大小
return ret_len;
}
// 主函数
int main(int argc, char const *argv[])
{
// 0、判断命令行参数
if (argc != 2)
printf("命令行参数错误,格式为(./可执行文件 要打开文件)!\n");
// 1、打印文件的大小
printf("%s文件的大小为: %ld字节\n", argv[1], GetFileSize(argv[1]));
return 0;
}
(六)、文件的关闭

- 关键点:
- 当不再使用一个文件时,应当关闭文件,防止系统资源浪费
- 对同一文件重复执行关闭操作(或其它操作)会失败返回,不会由其它副作用
三、标准IO的基本API
(一)、文件的打开与关闭
- 说明:不管用系统IO函数还是标准IO函数,操作文件的第一步,都是打开文件(open/fopen)
- 注意:
- 系统IO:打开文件得到的是一个整数,称为文件描述符
- 标准IO:打开文件得到的是一个指针,称为文件指针
- 图解:文件指针指向结构体FILE,该结构体内部包含了文件描述符

1、文件的打开:

- 注意:总共6种打开模式,不能自创别的模式,比如"rw"是非法的
2、文件的关闭

- 注意:fclose函数涉及内存释放,不可对同一个文件多次关闭
3、示例代码
#include <stdio.h>
// 全局错误码声明所在的文件
#include <errno.h>
// strerror函数
#include <string.h>
int main(int argc, char const *argv[])
{
FILE *fp = NULL;
// 一、打开文件
// (1)、打开文件(方式1:要求文件必须存在)
// 1、以只读方式打开
fp = fopen("./1.txt", "r");
if (fp == NULL)
{
printf("错误信息:打开1.txt文件失败, 错误原因:%s, 错误位置:%d行\n", strerror(errno), __LINE__);
return -1;
}
else
{
printf("使用fopen函数,打开文件成功!\n");
}
// 2、以读写方式打开
fp = fopen("./1.txt", "r+");
// (2)、打开文件(方式2:文件可以不存在,如果文件不存在就创建文件,并清空文件)
// 1、以只写方式打开
fp = fopen("./1.txt", "w");
// 2、以读写方式打开
fp = fopen("./1.txt", "w+");
// (3)、打开文件(方式3:文件可以不存在,如果文件不存在就创建文件,并清空文件,以追加的方式打开)
// 1、以只写方式打开
fp = fopen("./1.txt", "a");
// 2、以读写方式打开
fp = fopen("./1.txt", "a+");
// 二、关闭文件
int ret = fclose(fp); // 关闭一般不判断返回值
if (ret == EOF)
{
printf("错误信息:打开1.txt文件失败, 错误原因:%s, 错误位置:%d行\n", strerror(errno), __LINE__);
return -2;
}
else
{
printf("使用fclose函数,关闭文件成功!\n");
}
return 0;
}
(二)、文件位置的获取与设置
- 说明:与系统IO接口类似,标准IO也可以设置文件的读写位置,获取读写位置,只不过对于标准IO而言,这两个功能是分开实现的

- 关键点:设置或获取的文件位置都是long类型;可以将文件位置设置到已有数据范围之外,形成"空洞"文件
- 示例代码
#include <stdio.h>
// 全局错误码声明所在的文件
#include <errno.h>
// strerror函数
#include <string.h>
// fseek函数
#include <sys/ioctl.h>
int main(int argc, char const *argv[])
{
// 0、判断命令行参数
if (argc != 2)
printf("命令行参数错误,格式为(./可执行文件 要打开文件)!\n");
// 1、打开文件(文件可以不存在,如果文件不存在就创建文件,并清空文件,以读写权限)
FILE *fp = fopen(argv[1], "w+");
if (fp == NULL)
{
printf("错误信息:打开%s文件失败, 错误原因:%s, 错误位置:%d行\n", argv[1], strerror(errno), __LINE__);
return -1;
}
// 2、在空洞前,写入数据(头信息数据)
char w_buf1[128] = "文件的大小+文件的名字+文件的格式";
fwrite(w_buf1, 1, strlen(w_buf1), fp);
// 3、利用lseek形成一个空洞文件
fseek(fp, 10*1024*1024, SEEK_CUR);
// 4、在空洞后,写入数据(尾信息数据)
char w_buf2[128] = "文件的末尾";
fwrite(w_buf2, 1, strlen(w_buf2), fp);
// 5、关闭文件
fclose(fp);
return 0;
}
(三)、文件内容的读写操作
- 说明1:标准IO和系统IO不同的地方在于,标准IO的读写操作提供了很多不同的函数接口,以满足不同的需求:
- 说明2:对于读操作而言,返回EOF意味着操作失败,这有两种情况
- 如果feof(fp)为真,此时意味着读到了文件末尾,没有数据可读了
- 如果ferror(fp)为真,此时意味着遇到了错误
1、按字节读写文本文件

- 关键点1:
- fgetc()函数与getc()函数完全一样,区别是fgetc是函数,而getc是宏
- fputc()函数与putc()函数完全一样,区别是fputc是函数,而putc是宏
- getchar()函数与putchar()函数只能针对键盘输入和屏幕输出,不能指定别的文件
- 读操作函数接口的返回值是int,而不是char,原因是当读操作失败是返回EOF的数值是-1,而char型数据可能无法表达-1(而且char型数据本来就是单字节的int型)
- 示例代码:
#include <stdio.h>
// 全局错误码声明所在的文件
#include <errno.h>
// strerror函数
#include <string.h>
int main(int argc, char const *argv[])
{
// 0、判断命令行参数
if (argc != 2)
printf("命令行参数错误,格式为(./可执行文件 要复制的文件路径 要粘贴的文件路径)!\n");
// 1、打开文件(文件可以不存在,如果文件不存在就创建文件,并清空文件,以读写权限)
FILE *fp = fopen(argv[1], "w+");
if (fp == NULL)
{
printf("错误信息:打开%s文件失败, 错误原因:%s, 错误位置:%d行\n", argv[1], strerror(errno), __LINE__);
return -1;
}
// 2、写
// a、fputc: 函数(推荐)
// b、putc: 宏
// c、putchar: 只针对标准输出(屏幕)
char w_buf[128] = "世界那么大,我想去浪浪!";
int i = 0;
while (w_buf[i] != '\0') // 没有数据写入也退出
{
int ret = fputc(w_buf[i], fp);
if (ret == EOF ) // 写入出错就退出
break;
i++;
}
// 获取当前位置的偏移量,重新移位至文件开头处
long ret_t = ftell(fp);
printf("ret_t == %ld\n", ret_t);
fseek(fp, 0, SEEK_SET);
// 3、读
// a、fgetc: 函数(推荐)
// b、getc: 宏
// c、getchar: 只针对标注输入(键盘)
char r_buf[128] = {0};
int len = sizeof(r_buf)-1;
int j = 0;
while (j <= len) // 防止溢出
{
int ret = fgetc(fp);
if (ret == EOF) // 已经读到文件末尾,或者出错就退出
break;
r_buf[j] = ret;
j++;
}
printf("r_buf == %s\n", r_buf);
// 4、关闭文件
fclose(fp);
return 0;
}
2、按行读写文本文件

- fgets()函数和gets()函数的区别:
- fgets()函数可以读取指定的任意文件,而gets()函数只能从键盘读取
- fgets()函数有内存边界判断,而gets()函数没有,因此后者是不安全的,不建议使用
- fgets()函数在任何情形下都按原样读取数据,但gets()函数会自动去除数据末尾的'\n'
- fputs()函数和puts()函数的区别:
- fputs()函数可以写入指定的任意文件,而ptus()函数只能将数据输出到屏幕
- fputs()函数在任何情形下都按原样写入数据,但puts()函数会自动给写入数据末尾加上'\n'
- 示例代码
#include <stdio.h>
// 全局错误码声明所在的文件
#include <errno.h>
// strerror函数
#include <string.h>
int main(int argc, char const *argv[])
{
// 0、判断命令行参数
if (argc != 2)
printf("命令行参数错误,格式为(./可执行文件 要复制的文件路径 要粘贴的文件路径)!\n");
// 1、打开文件(文件可以不存在,如果文件不存在就创建文件,并清空文件,以读写权限)
FILE *fp = fopen(argv[1], "w+");
if (fp == NULL)
{
printf("错误信息:打开%s文件失败, 错误原因:%s, 错误位置:%d行\n", argv[1], strerror(errno), __LINE__);
return -1;
}
// 2、写
// a、fputs:函数(推荐)
// b、puts: 宏
char w_buf[128] = {0};
char w_buf_tmp[256] = {0};
while (1)
{
// 1、从键盘输入要写入的数据
printf("请输入要写入的字符串:\n");
scanf("%[^\n]", w_buf);
while(getchar()!='\n');
if (strcmp(w_buf, "exit") == 0)
break;
// 2、将w_buf数据写入到文本中
sprintf(w_buf_tmp, "%s\n", w_buf);
fputs(w_buf_tmp, fp);
// 3、清空w_buf数据
bzero(w_buf, sizeof(w_buf));
bzero(w_buf_tmp, sizeof(w_buf_tmp));
}
// 3、获取当前位置的偏移量,重新移至文件开头处
long ret_t = ftell(fp);
printf("ret_t == %ld\n", ret_t);
fseek(fp, 0, SEEK_SET);
// 4、读
// a、fgets:函数(推荐)
// b、gets: 宏、
char r_buf[256] = {0};
while (1)
{
fgets(r_buf, sizeof(r_buf), fp);
printf("r_buf == %s\n", r_buf);
// 判断文件是否读取完毕
if (feof(fp))
{
printf("文件内容已读完!\n");
break;
}
// 判断文件是否读取有误
if (ferror(fp))
{
printf("读操作遇到错误!\n");
break;
}
}
// 4、关闭文件
fclose(fp);
return 0;
}
3、按指定格式读写文本

- 注意:
- fprintf()函数不仅可以像printf()函数一样向标准输出设备输出信息,也可以像由stream指定的任何有相应权限的文件写入数据
- sprintf()函数和snprintf()函数都是向一块自定义缓冲区写入数据,不同的是后者第二个参数提供了这块缓冲区的大小,避免缓冲区溢出,因此应尽量使用后者,放弃使用前者
- fscanf()函数不仅可以像从由stream指定的任何由相应权限的文件读入数据,也可以从标准输入中读入数据
- scanf()函数一样从标准输入设备读入信息,也可以从一块由指定的自定义缓冲区中读入数据
- 这些函数的读写带个是,格式由下表决定:

- 示例代码:
#include <stdio.h>
// 全局错误码声明所在的文件
#include <errno.h>
// strerror函数
#include <string.h>
int main(int argc, char const *argv[])
{
// 0、判断命令行参数
if (argc != 2)
printf("命令行参数错误,格式为(./可执行文件 要复制的文件路径 要粘贴的文件路径)!\n");
// 1、打开文件(文件可以不存在,如果文件不存在就创建文件,并清空文件,以读写权限)
FILE *fp = fopen(argv[1], "w+");
if (fp == NULL)
{
printf("错误信息:打开%s文件失败, 错误原因:%s, 错误位置:%d行\n", argv[1], strerror(errno), __LINE__);
return -1;
}
// 2、写
// a、fprintf: 不仅可以像printf()函数一样向标准输出设备输出信息,也可以像由stream指定的任何有相应权限的文件写入数据(推荐)
// b、printf: 只能将数据输出到输出设备上(屏幕)
// c、sprintf: 处理有格式的字符串数据
// d、snprintf:处理有格式的字符串数据(有规定范围的)
int num = 0;
printf("请输入用户名:\n");
char account_buf[128] = {0};
scanf("%s", account_buf);
fprintf(fp, "[server_account]\n");
fprintf(fp, "account[%d]=%s\n", num, account_buf);
fprintf(fp, "[un_server_account]\n");
printf("请输入密码:\n");
char passwd_buf[128] = {0};
scanf("%s", passwd_buf);
fprintf(fp, "\n");
fprintf(fp, "[server_passwd]\n");
fprintf(fp, "passwd[%d]=%s\n", num, passwd_buf);
fprintf(fp, "[un_server_passwd]\n");
// 3、获取当前位置的偏移量,重新移位至文件开头处
long ret_t = ftell(fp);
printf("ret_t == %ld\n", ret_t);
fseek(fp, 0, SEEK_SET);
// 4、读
// a、fscanf:不仅可以像从由stream指定的任何由相应权限的文件读入数据,也可以从标准输入中读入数据(推荐)
// b、scanf: 只能从标准输入设备上(键盘)获取数据
// c、sscanf:处理有格式的字符串数据
int num_tmp = -1;
char account_buf_tmp[128] = {0};
char passwd_buf_tmp[128] = {0};
fscanf(fp, "[server_account]\n");
fscanf(fp, "account[%d]=%s\n", &num_tmp, account_buf_tmp);
fscanf(fp, "[un_server_account]\n");
fscanf(fp, "\n");
fscanf(fp, "[server_passwd]\n");
fscanf(fp, "passwd[%d]=%s\n", &num_tmp, passwd_buf_tmp);
fscanf(fp, "[un_server_passwd]\n");
printf("num_tmp == %d\n", num_tmp);
printf("account_buf_tmp == %s\n", account_buf_tmp);
printf("passwd_buf_tmp == %s\n", passwd_buf_tmp);
// 5、关闭文件
fclose(fp);
return 0;
}
4、按数据块读写文本

- 注意:
- 以上两个函数既能处理文本,也能处理二进制文本
- 返回值均是成功读写的"块"数,而非字节数
- 示例代码:
#include <stdio.h>
// 全局错误码声明所在的文件
#include <errno.h>
// strerror函数
#include <string.h>
int main(int argc, char const *argv[])
{
// 0、判断命令行参数
if (argc != 2)
printf("命令行参数错误,格式为(./可执行文件 要复制的文件路径 要粘贴的文件路径)!\n");
// 1、打开文件(文件可以不存在,如果文件不存在就创建文件,并清空文件,以读写权限)
FILE *fp = fopen(argv[1], "w+");
if (fp == NULL)
{
printf("错误信息:打开%s文件失败, 错误原因:%s, 错误位置:%d行\n", argv[1], strerror(errno), __LINE__);
return -1;
}
// 2、写
char w_buf[128] = "三十年河东,三十年河西,莫欺少年穷";
size_t ret_w = fwrite(w_buf, 1, strlen(w_buf), fp); // 等价于:系统IO函数的write(fd, w_buf, strlen(w_buf));
if (ret_w < strlen(w_buf) || ret_w == 0)
{
printf("写入数据失败!\n");
return -2;
}
// 3、获取当前位置的偏移量,重新移位至文件开头处
long ret_t = ftell(fp);
printf("ret_t == %ld\n", ret_t);
fseek(fp, 0, SEEK_SET);
// 4、读
char r_buf[128] = {0};
size_t ret_r = fread(r_buf, 1, ret_w, fp); // 等价于:系统IO函数的write(fd, w_buf, strlen(w_buf));
if (ret_r < ret_w || ret_r == 0)
{
printf("读取到了文件末尾或读取文件失败!\n");
return -3;
}
printf("r_buf == %s\n", r_buf);
/*
// 判断文件是否读取完毕
if (feof(fp))
{
printf("文件内容已读完!\n");
break;
}
// 判断文件是否读取有误
if (ferror(fp))
{
printf("读操作遇到错误!\n");
break;
}
*/
// 5、关闭文件
fclose(fp);
return 0;
}
欢迎在评论区分享你的实践心得或提出疑问。典型问题将汇总至下期内容统一回复,复杂问题可能通过单独教程深入讲解。留言时注明使用环境和预期目标,有助于更快获得针对性建议
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)