环境:centos7.6,腾讯云服务器 Linux文章都放在了专栏:【Linux】欢迎支持订阅 相关文章推荐: 【Linux】冯.诺依曼体系结构与操作系统 【C/进阶】如何对文件进行读写(含二进制)操作?
在C语言阶段,我们学习过相关文件操作,但是,我们真的了解文件操作吗?
是否只有我们的C/C++才可以对文件进行操作呢?
如何看待文件?
文件是怎么打开的?由谁打开?
FILE *fopen(const char *path, const char *mode);
对于该函数path表示打开或创建的目标文件(默认会在当前路径下创建/打开),mode表示文件的打开方式。对于mode来说,这里就简单介绍以下几种(更多的在前文:点击跳转):
打开方式 | 含义 | 假如文件不存在 |
---|---|---|
"w"(只写) | 为了输出数据,打开一个文本文件 | 自动创建该文件,并且在写入前会清空原文件 |
"r"(只读) | 为了输入数据,打开一个已经存在的文件 | 打开失败 |
"a"(追加) | 向文本文件尾添加数据 | 自动创建该文件,追加前不会清空原文件 |
FILE* fp1=fopen("./TEST/log.txt", "w");//只写,文件不存在在,在./TEST/下创建该文件
FILE* fp2=fopen("log.txt","a");//追加,文件不存在,在当前路径下创建该文件
FILE* fp3=fopen("log.txt","r");//只读,同一个文件可以被打开多次
int fclose(FILE *fp);//对已打开的文件进行关闭。
对于一个被打开的文件,最后一定要关闭该文件。这就好比我们malloc出来的空间一定要free一样。fclose与fopen配套使用。
fclose(fp1);
fclose(fp2);
fclose(fp3);
C语言提供的写函数一共有以下几种:
函数 | 功能 | 适用于 |
---|---|---|
fputc | 以一个字符为单位进行写入 | 所有输出流 |
fputs | 以一行为单位进行写入 | 所有输出流 |
fwrite | 二进制写入 | 文件 |
fprintf | 格式化写入 | 所有输出流 |
snprintf | 格式化写入 | 所有输出流 |
对于上面的大多数函数,这里就不做过多讲解,可以自行前去观看,这里讲一下snprintf。snprintf是fprintf的优化版本,相较于fprintf,可以对写入数据进行长度控制,会更加安全。
int snprintf(char *str, size_t size, const char *format, ...);
演示代码如下:
#include<stdio.h>
#include<stdlib.h>
#define SIZE 128
int main()
{
FILE* fp=fopen("log.txt","w");//写
//打开失败返回空
if(fp==NULL)
{
perror("fopen fail\n");
exit(-1);
}
char buffer[SIZE];
int cnt=10;
const char* st="hello world!";
while(cnt)
{
//格式化写入到buffer数组
snprintf(buffer,sizeof(buffer),"%d:%s\n",cnt,st);
//再将数组内容写到文件
fputs(buffer,fp);
--cnt;
}
//关闭文件
fclose(fp);
return 0;
}
对于文件的读取,C提供的函数与上面一一对应,用法也是如此
文件读取演示:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define SIZE 128
//文件的读取
int main()
{
FILE* fp=fopen("log.txt","r");//读方式打开
if(fp == NULL)
{
perror("fopen fail\n");
exit(-1);
}
//按行来读取文件
while(1)
{
//从文件流中按行读取内容,读到tmp中
char tmp[SIZE];
if(fgets(tmp,sizeof(tmp),fp)==NULL)
{
break;
}
//打印tmp
printf("%s",tmp);
}
//关闭文件
fclose(fp);
return 0;
}
以上都是语言级别的文件相关操作。至此往下将讲解系统级别的相关文件操作。
对于文件的打开,我们采用open系统调用函数。如下:
//头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//系统调用函数open
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
对于该函数,有如下理解:
对于多参数选项flags:
参数选项 | 含义 |
---|---|
O_RDONLY | 只读 |
O_WRONLY | 只写 |
O_CREAT | 若文件不存在则创建该文件,与open的第三个参数mode(设置权限)配套使用 |
O_APPEND | 追加 |
O_TRUNC | 打开文件前清空原文件 |
多参数传递实际上采用的就是位图的方式来实现,一个整形一共32个比特位,每一个比特位都可以用来表示一个参数,用|运算符则可以实现一个整形传递多个参数选项。
我们可以利用此特点来自己实现一个小demo,如下图所示:
参数flags便是利用此特点来实现一个整形多个参数选项。与我们的C语言对应关系如下;
通过系统调用函数close来关闭一个文件,该函数如下:
#include <unistd.h>
int close(int fd);
其中这里的fd就是文件描述符,也就是open函数返回的那个整形。具体含义将在下一篇详细讲解。
通过函数write来实现文件的写入操作
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
对于该函数:
具体案例操作:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define SIZE 128
int main()
{
umask(0);//设置umask为0
int fd=open("log.txt",O_CREAT|O_WRONLY|O_TRUNC,0666);//以写的方式打开文件,文件不存在则创建,打开之前会清空原文件,创建时文件权限为0666
//打开失败
if(fd == -1)
{
perror("open fail\n");
exit(-1);
}
char buffer[SIZE];//充当缓冲区的buffer数组
const char* str="hello world";
int cnt=10;
//利用snprintf进行格式化写入到缓冲区,再写入到文件
while(cnt)
{
snprintf(buffer,sizeof(buffer),"%d:%s\n",cnt,str);
--cnt;
ssize_t n=write(fd,buffer,strlen(buffer));//这里我们不需要将/0计算在内,因为/0是C语言规定的字符串结尾,此时我们处于系统级别
printf("成功写入%lu个字节内容\n",n);//%lu:无符号整型
//间隔1秒写入一次
sleep(1);
}
//关闭文件
close(fd);
return 0;
}
运行结果如下:
对于文件的读取,提供了函数read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
对于该函数:
具体操作如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define SIZE 128
//文件读取
int main()
{
int fd=open("log.txt",O_RDONLY);//读方式打开文件
if(fd== -1)
{
perror("open fail\n");
exit(-1);
}
char buffer[SIZE];//读到的内容放进buffer数组
//注意处理\0
ssize_t n=read(fd,buffer,sizeof(buffer)-1);
//读取成功
if(n>0)
{
buffer[n]='\0';
printf("%s共读取%lu字节的内容\n",buffer,n);//字符串结尾的标志\0(这里我们用printf打印,属于C语言级别,所以得考虑\0的存在)
}
//关闭文件
close(fd);
return 0;
}
运行结果如下:
实际上,我们所有的语言级别的文件操作都是对系统调用的封装,比如我们使用的fclose,fopen等函数,他们的底层实际上都会调用对应的系统级别的函数,比如close、open等。也就是说,语言级别的文件操作的底层,并不会直接跳过OS,而是一层一层往下进行。