在学习C语言时,我们就学习过文件相关的内容,但是由于知识储备尚且不足,无法深入的了解文件,下面我们就要重新认识一下文件。
<1> 简单介绍(铺垫)
1.前面我们说过,文件 = 内容 + 属性,所以我们对文件的操作也就分为对属性和内容的操作。
2.通常我们在访问文件之前,都得先打开文件,打开文件这个操作就必须通过执行代码的方式来完成修改。 而修改这个动作是由cpu来执行的,cpu只能通过访问内存的方式来执行代码修改文件, 所以文件必需被加载到内存中,方便cpu对其进行修改。
3. 是谁打开的文件呢?我们执行修改文件代码前,这段代码会先变成进程,然后再由cpu调度进程打开文件。所以实际上打开文件的是进程。
4.一个进程可以打开多少文件?进程是可以打开多个文件
总结一下上面四点,在一定的时间段内,系统中存在多个进程,同时可能存在更多的被打开的文件,操作系统肯定是要管理这些文件,根据先描述,再组织的原则,内核中一定会有描述被打开文件的数据结构(这里假定是struct),并且用其定义对象。后面要研究的问题就变成了PCB和struct XXX的关系。当然,上述的文件都是被打开的文件,也叫内存文件。至于没有打开文件,一般就在磁盘上。
<2>重温一下C语言的文件接口 -对比一下重定向
下面主要重新介绍一下文件的打开,关闭和读写函数。由于文件操作函数在C语言阶段已经有所涉猎,所以这里重点介绍与重定向的关系。
在介绍它们的关系之前,我们先回顾一下文件函数的相关参数。
pathname表示路径名称,但实际上我们只要输入文件名即可,默认是在当前路径下查找(原因后面介绍)。mode主要是打开文件的模式,这里主要介绍一下w,r,a。其他的模式大同小异,大家可自行查阅复习。
其中W就是表示以写方式打开文件,如果文件不存在,就自动创建该文件。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define filename "file.txt"
int main()
{
FILE* p = fopen(filename,"w");
if(p == NULL)
{
perror("fopen");
}
fclose(p);
}
运行结果:
下面我们再用文件读写函数对文件进行写入,这里为了方便显示,我们使用fputs函数
这个函数的参数就是把s这个字符串打印到stream这个文件流里面,这个文件流就是其实就可以看成是文件。
演示代码
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define filename "file.txt"
int main()
{
FILE* p = fopen(filename,"w");
if(p == NULL)
{
perror("fopen");
}
const char* str = "hello world\n";
fputs(str,p);
fclose(p);
}
上面这段代码其实很简单,我们把写入文件的操作去掉,再次运行代码,就会发现文件内容被清空,这是因为文件以W方式打开,会以覆盖的方式写入(就是在打开文件时会清空文件)。下面我们如果我们用echo命令重定向一下,我们再看一下文件的内容。
我们可以发现,重定向的操作其实和文件以读方式打开文件效果上是一样的,所以我们可以推出输出重定向时就是文件以(w)读的方式打开。
同理可得,我们可以推出,当我们使用追加重定向和输出重定向时,就是以a(append) 和 r(read)的方式打开文件。
文件操作在默认在当前路径的原因
我们在执行文件操作时,一定是由进程来实施操作。而我们之前说过/proc目录下,存在关于进程的大量信息,其中里面包含了进程当前的所处路径cwd。
下面复用上文的代码演示一下
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define filename "file.txt"
int main()
{
FILE* p = fopen(filename,"w");
if(p == NULL)
{
perror("fopen");
}
const char* str = "hello world\n";
printf("pid: %d\n",getpid());
while(1)
{
fputs(str,p);
sleep(2);
}
fclose(p);
return 0;
}
进程的信息和相关结果
如果我们需要修改文件路径,我们就可以使用chdir函数,修改进程的工作路径,此时我们再运行程序,文件就会在修改后的路径里面被创建。
程序默认打开的文件流
一般来说,我们写的程序都是向显示器这个文件打印,但是我们自己却从来没有打开过显示器文件,而我们默认就能使用该文件进行打印,说明系统在打印之前就帮我们打开了文件。其中这里的文件包含了好几个
stdin输入流文件,叫做标准输入,默认是键盘设备;stdout输出流文件,叫做标准输出,默认是显示器设备;stderr 叫做标准错误,默认是显示设备。stdout可以从stdin里面读取信息,然后将其打印到显示屏上。而这些文件流是可以直接使用。
下面用一些接口来演示一下stdout这个输出流
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define filename "file.txt"
int main()
{
printf("hello world\n");
fprintf(stdout,"hello world\n");
fputs("hello\n",stdout);
}
运行结果
stdin输入流和stdout输出流类似,只不过一个是做输入一个做输出,我们读取数据时可以从stdin里面进行读取,而读取操作就可以使用scanf,fscanf等函数,下面就不具体演示了。而stderr先简单了解即可,后面再叙述。
<3>文件操作的系统调用接口
我们所使用的文件大多在磁盘进行存储,而磁盘属于底层的硬件,我们作为用户是无法直接修改底层硬件的数据,只有操作系统才有权力修改。虽然我们能够使用一些C语言的文件操作接口对文件进行修改,但这些接口最后都是封装的系统调用,下面我们就来聊一聊文件操作相关的系统调用。
<1>open
该接口用于打开文件,返回值为一个整数,如果打开成功,就会返回一个整数,这个整数叫做文件描述符,而文件如果打开失败,就返回-1。
这里的pathname字符串记录着要打开文件的名称,flags这个整数表示打开文件的方式, 我们一般用宏来表示这些方式。这些宏一般是用位图的方式来区分的,简单点说就是在32个比特位上的0变为1,不同的宏变化的比特位是错位的。这样我们就能使用按位或的操作实现用一个整数表示多个文件打开方式(按位或:两个比特位均为零才是零,其余皆是1)。
下面的宏代表了文件打开的各种模式
O_RDONLY
: 以只读方式打开文件。O_WRONLY
: 以只写方式打开文件。O_RDWR
: 以读写方式打开文件。O_APPEND
: 追加数据到文件末尾。O_CREAT
: 如果文件不存在,则创建它。O_EXCL
: 与O_CREAT
一起使用,如果文件已存在,则返回错误。O_TRUNC
: 如果文件已存在,则截断它到零长度。(清空文件内容 )O_NONBLOCK
: 非阻塞模式打开文件。O_SYNC
: 同步写入,数据立即写入磁盘。O_DSYNC
: 同步写入,数据写入磁盘后再返回。O_RSYNC
: 同步读取,读取操作等待数据写入磁盘完成后进行。O_NOCTTY
: 防止文件成为控制终端。O_CLOEXEC
: 在执行exec
函数族后自动关闭文件描述符。
如果我们要实现fopen中的w功能,我们就可以给flag参数传 O_RDONLY | O_CREAT。
如果我们使用第二个open系统调用接口,mode就表示文件的权限,这个带mode参数的系统open接口是在文件不存在时调用,mode参数就表示在创建文件时的文件权限。如果是正常的普通文件,我们输入0666即可。需要注意的是,文件的权限还受权限掩码的约束,所以我们输入的文件权限并不是最终的文件权限。当然,如果不想受权限掩码的约束,我们直接调用umask的系统调用接口,并且直接将权限掩码设置为零即可
<2>close
当我们对文件的操作结束时,我们就需要关闭文件,此时我们就可以使用close接口来关闭文件。这个接口的使用非常简单只需要将open接口的返回值传入close即可。
<3>write
write接口就是向文件里面写入内容,其操作和fwrite差不多。
fd就是文件描述符,buf就是要写入文件的内容的头指针,count表示写入文件字节总数。
下面演示一下,用w方式打开文件,并写入一些内容
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#define filename "file.txt"
int main()
{
int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd == -1)
{
perror("open fail");
return 1;
}
const char* str = "hello world\n";
write(fd,str,strlen(str));
close(fd);
return 0;
}
结果
如果我们要追加,就可以把O_TRUNC修改成O_APPEND即可,通过灵活运用各种宏的组合,我们就可以实现C语言中文件操作的各种特性。
以上就是所有内容,文中如有不当之处,还望各位大佬指正,谢谢!!!