一、C\C++中的文件操作
首先我们明确文件就是:文件的内容+文件的属性(元数据)
我们未来对于文件的操作无非就两种:
1、对内容进行操作
2、对属性进行操作
那么我们对文件进行操作,那么首先要先将文件打开,那么要先找到该文件,那么就需要路径+文件名。
那么我们有时候发现会有的直接使用的文件名的,这是因为文件的操作是由当前进程来完成的,那么就会使用这个进程的工作目录。
那么我们如何打开一个文件呢?
C语言中提供了一个接口:
1、fopen();
使用其要包含头文件:<stdio.h>
其函数原型如下:
FILE*fopen(const char*filename,const char* mode);
第一个参数:要打开的文件、可以选择带路径
第二个参数:打开文件的方式。
常见的打开方式如下:
"r":read只读打开,前提是文件已经存在
"w":write只写打开,但是其会先将文件中已经存在的内容先清空,然后再写入,如果文件不存在 那么就会先创建文件,然后再写入
"a":append追加写入、在这个文件的末尾开始写入内容,如果文件不存在,那么就会先创建这个 文件然后再进行写入
那么我们对于文件的写入,实际上是将字符串进行写入,那么我们的语言层面的话,字符串的最后一个字符是\0,这个\0可以不写入。

可以看到我们上面是没有任何文件的,下面我使用w的方式打开log.exe文件,然后我们看看当前路径下会不会创建一个文件:


可以看到在文件不存在的时候,读文件就会打开失败。
2、fwrite()
这个函数是对文件进行写入操作的。
其函数原型如下:
size_t fwrite(const void*prt,size_t size,size_t nmemd,FILE*stream);
第一个参数:我们要给文件写入的内容
第二个参数:要写入内容的大小
第三个参数:写入的次数
第四个参数:文件指针
补充:
C语言的程序执行的时候,其会默认打开三个输入输出流:stdin、stdout、stderr。
stdin:标准输入
stdout:标准输出
stderr:标准错误
标准输入,我们就认为是我们的键盘
标准输出、我们就认为是显示器
二、系统调用
在我们的操作系统中,我们也要对文件进行操作,操作系统提供的接口,我们叫做系统调用。
1、open()系统调用
这个接口是用来打开一个或者创建一个文件的、然后我们使用的时候要对文件的权限的信息进行设置。
函数原型如下:
int open(const char*pathname,int flags);
int open(const char*pathname,int flags,mode_t mode);
如果我们打开的是已经存在的文件,那么两个参数的就可以了。
要使用其要包含下面的几个头文件:
<fcntl.h>
<sys/stat.h>
<unistd.h>
第一个头文件是open这个系统调用的声明
第二个头文件是包含了文件权限的宏定义
第三个包含了read和write和close
下面我们来看其几个参数分别是啥:
第一个参数:要打开的或者创建的文件名
第二个参数:打开文件的方式,其主要为下面几个:
1、O_RDONLY:只读打开
2、O_WRONLY:只写打开
3、O_RDWR:可读可写
然后下面这几个就和上面的三种方式通过按位与(|)的方式一起使用。
1、O_CREAT如果文件不存在,就创建
2、O_EXCL:这个要和O_CREAT一起使用,如果文件已经存在,那么会报错,防止把原来的文件 覆盖了
3、O_TRUNC:如果文件已经存在而且文件是以写的方式打开的,那么就会先清空原来文件中的内 容
4、O_APPEND:每次写的操作都是追加到文件的末尾
5、O_NONBLOCK:非阻塞模式
第三个参数:当第二个参数为创建文件的时候,那么第三个参数就必须要写了。
这个是文件的权限掩码。
其是一个八进制数,然后文件实际上得到的权限掩码为mode-umask。
然后其返回值为:
打开成功的话,那么就会返回一个非负整数,一个非负整数就是文件描述符fd,一般都是3开始,因为一开始0、1、2就已经被使用了,然后我们的open返回的fd描述符,也是有规律的,返回那个最小的没被使用的。
打开失败就返回-1,然后会设置一个全局变量errno表示错误的原因。
2、write()系统调用
write是Linux和Unix操作系统下的系统调用,其是用于将内核内存缓冲区的数据写入到磁盘文件中的。
函数原型如下:
size_t write(int fd,const void *buff,size_t count);
其三个参数的详情如下:
第一个参数:文件描述符fd
第二个参数:表示我们要写出的数据的缓冲区指针
第三个参数:要写入的数据的大小,单位字节
其返回值为:
如果写入成功,那么就返回成功写入的字节数。
写入失败就返回-1。
补充:
write的写入其在写入完成后,会更新到写入的最后一个字节的下一个位置。
如果我们要更改其偏移量到文件的开始位置,那么我们可以使用lseek(int fd,int count,SEEK_SET)
三、文件的管理
上面我们讲到的是在语言层面上对文件的操作。
那么我们的操作系统对于文件是如何管理和组织的呢。
1、文件的管理
我们知道文件的创建和操作都是通过进程来完成的,我们的进程都会有一个task_struct结构体对象,在这个结构体对象中有一个FILE*的结构体指针,这个指针就是指向我们的文件描述表的
这个文件描述表其实际上是一个指针数组,存放的是文件的指针,来指向我们打开的文件,然后fd就是我们的这个数组的下标。
然后我们上面提到的三种标准输入输出,其打开就是占用了0、1、2.
0:标准输入
1:标准输出
2:标准错误

四、重定向
我们已经知道每一个进程中都会有一个文件描述表的存在。
文件描述表中,其就是一个数组,然后其存储的是文件指针,所以我们可以改变这个指针指向的地址,那么就改变了这个文件描述表这个位置的文件了。
系统中提供了dup2()这个接口,可以更改文件指针的下标位置。
其函数原型如下:
int dup2(int oldfd,int newfd);
其做的工作就是将oldfd这个下标位置的文件覆盖掉newfd这个位置。
其实就是让newfd这个指针指向oldfd这个位置指向的那个文件。
1、输出重定向
其操作符号:>
作用:将输出写入文件,而且会覆盖掉文件中的内容
其本质就是将我们的标准输入的fd和文件的fd使用dup2进行覆盖,那么我们的文件就在fd1这个位置上了,那么我们进行输入的时候,就变成了对这个文件进行写入。
2、追加重定向
操作符号:>>
作用:将输出信息写入到文件中,而且是在末尾追加
其实现原理就是使用dup2,将文件的fd变成1即可。
3、输入重定向
操作符:<
作用:从文件中读取内容输出
其实现原理也是一样的。通过修改文件的fd到2。
五、Linux下一切皆文件的理解
我们的操作系统要对各自的软件硬件进行管理,比如我们的键盘,显示器,网卡等等,那么我们要是对于每个资源都单独的去弄一个结构进行管理,那么就很冗余了。
所以操作系统会将其使用一个结构体封装起来,就比如将我们的read和wirte封装在一起,然后还有输出数组,输入数组等。
然后我们的各种硬件都会保持这种特性的前提下,然后又去根据自己的特点实现一些自己的属性。
