文件=内容+属性
如果在磁盘上建立一个为0KB的文件,磁盘上也会存储文件的属性。(因为文件的属性也是数据).
文件的操作
- 1.一种是对于文件内容做操作。
- 2.另外一种是对于文件的属性,比如说更改文件的相关权限等。
注意:
- 对于文件内容做操作也可能会影响文件属性改变,比如说文件的大小发生了变化。
- 由于文件在磁盘(硬件)中存放,我们访问文件,先写代码->编译->链接->生成可执行文件->运行。
注意:
- 访问文件的本质是进程在访问,进程访问文件是需要通过接口(语言层面的接口,C语言、C++)访问的。
- 如果想要向硬件写入,那么只能通过操作系统。如果普通用户也想向硬件写入,那么就必须让操作系统提供相应的系统调用接口(文件类的系统调用接口)。由于文件类的系统调用接口比较复杂,所以一写语言对于这些系统调用接口进行了封装,这就导致了不同语言有不同的语言级别的文件访问接口(都不一样),但是都封装的是系统接口,底层原理相同。
为什么要学习OS层面的文件接口?
- 1.由于选择的操作系统只有一个,这样的接口,在Linux上只有一套,其他OS也是一样的。
- 2.这样的代码具有跨平台性。使用C++的平台都可以使用C++的文件接口,使用Python的平台都可以使用Python的文件接口。
- 如果语言不提供给文件的系统接口的封装,所有访问文件的操作都必须直接使用操作系统的接口。(Windows的系统接口和Linux的系统接口种类,参数等都是不一样的)。
注意:
使用语言的用户,也需要访问文件,但是一旦使用系统接口编写文件代码,这份代码只能在该平台中使用,无法在其他平台上运行,不具有跨平台性。而C语言和C++语言将所有平台中的代码都实现出来并封装好。然后采用条件编译的方式,在编译的时候实现动态编译。
由于显示器也是硬件,那么printf向显示器打印,也是一种写入,和磁盘写入到文件没有本质区别!
在Linux下,硬件设备可以被看成文件,可以进行scanf、fgets、printf、cin、cout。在C/C++程序在编译的时候,将代码加载到你的程序中,这就是默认情况下会打开stdin、stdout、stderr。
- stdin标准输入->默认为键盘
- stdout标准输出->默认为显示器
- stderr标准错误->默认为显示器
对于文件来说,读和写是文件操作的核心。
对于显示器来说,printf/cout本质上也是一种写入(write),对于键盘来说,scanf/cin本质上也是一种读入(read).将输入内容给显示器一份也给程序一份。站在内存角度,程序必须加载到内存,键盘将我们输入的数据交给内存,系统将内存中的数据刷新到显示器或者写入到硬盘中,也就是对应着input和output(也就是I/O的动作)。如果一个普通文件,我们使用fopen/fread去读取它,他就被读取到我们程序(进程)的内部(内存),再使用fwrite将数据写入到文件中,从普通程序读取到进程内部(内存)就是input,从内存中写入文件中就是output。
文件的定义:站在系统角度上来说,能够被input读取,能够被output写出的设备就是文件。
- 狭义上的文件:普通的磁盘文件(.txt .doc文件等等)
- 广义上的文件:显示器,键盘,网卡,声卡,显卡,磁盘,几乎所有的外设都可以称之为文件。(这些设备都具有上面可以被读或者被写的特点)。
- 文本文件:把数据的终端形式的二进制数据输出到磁盘上存放,也就是说存放的是数据的终端形式。
- 二进制文件:把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放,也就是说存放的是数据的原形式。
- 绝对路径:是从目录树的树根"/"目录开始往下直至到达文件所经过的所有节点目录。下级目录接在上级目录后面用“/"隔开。
- 相对路径:相对路径是指目标目录相对于当前目录的位置。
- 注意:绝对路径都是从“/"开始的,所以第一个字符一定是“/"。
文件的打开
fopen():打开文件
FILE *fopen( const char *filename, const char *mode );
文件的关闭
fclose():关闭文件
int fclose( FILE *stream );
文件的读写
fgetc():读取一个字符
fputc():写入一个字符
fgets():读取一个字符串
fputs():写入一个字符串
fprintf():写入格式化数据
fscanf():格式化读取数据
fread():读取数据
fwrite():写入数据
int fgetc( FILE *stream );
int fputc( int ch, FILE *stream );
char *fgets( char *str, int count, FILE *stream );
int fputs( const char *str, FILE *stream );
int fprintf( FILE *stream, const char *format, ... );
int fscanf( FILE *stream, const char *format, ... );
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
int fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
文件状态检查
feof():文件是否结束
ferror():文件读/写是否出错
clearerr():清除文件错误标志
ftell():文件指针的当前位置
int feof ( FILE * stream );
int ferror ( FILE * stream );
void clearerr ( FILE * stream );
long int ftell ( FILE * stream );
文件指针定位
rewind():把文件指针移到开始处
fseek():重定位文件指针
void rewind ( FILE * stream );
int fseek ( FILE * stream, long int offset, int origin );
注意:r+具有读写属性,从文件头开始写,保留原文件中没有被覆盖的内容,w+也具有读写属性,如果原文件存在,会被清空从头开始写。
perror打印错误信息
#include <stdio.h>
#include <stdlib.h>int main()
{//当前目录中不存在这个文件FILE* fp=fopen(","r");//如果打开失败,就以文本形式返回if(fp==NULL){perror("fopen");exit(1);}return 0;
}
在Linux下,如果当前是以写的方式的话,如果写入的文件不存在,会在当前路径下直接创建。
#include <stdio.h>
#include <stdlib.h>int main()
{FILE* fp=fopen(","w");if(fp==NULL){perror("fopen");exit(1);}fclose(fp);while(1) sleep(1);return 0;
}
从上图我们可以发现ext指向的路径就是我们当前进程的工作路径。
cwd:进程的内部属性,就是我们这个进程所指向的工作目录。将cwd所指向的路径,和我们上面传入的文件拼接起来,形成完整的路径名称。当一个进程运行起来,每个进程都会记录当前所处的工作路径,当你打开一个文件的时候,创建的文件就是我们当前路径下,也就是我们进程所处的路径下。由于我们进程内部会直接用这个进程的cwd也就是当前的工作路径,然后将文件名test拼接起来形成我们的exe找到可执行文件。
注意:进程具有确定性,一般我们程序将程序部署到系统中,路径一般不发生改变,比如说你安装了一个程序到D盘中,那么路径就在D盘下。
相关代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>int main()
{FILE* fp=fopen(","w");//如果打开失败了,就将打开失败的原因以文本的形式返回if(fp==NULL){perror("fopen");return 1;}//进行文件的相关操作const char* s1="hello fwriten";//二进制的方式写入fwrite(s1,strlen(s1),1,fp);const char* s2="hello fprintfn";//往特定的文件流中,写入特定的字符串fprintf(fp,"%s",s2);const char* s3="hello fputsn";fputs(s3,fp);//关闭文件fclose(fp);return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <string.h>int main()
{FILE * fp=fopen(","w");if(fp==NULL){perror("fopen");return 1;}const char* s1="hello worldn";fwrite(s1,strlen(s1),1,fp);fclose(fp);return 0;
}
由于是在w模式下,先将文件打开的时候,将文件中的内容进行清空处理,然后才写入内容!
这里我们通过echo指令对进行写入内容,首先将文件中的全部内容被清空。这与先清空再写入的原理相同!
#include <stdio.h>
#include <unistd.h>
#include <string.h>int main()
{FILE *fp=fopen(","a");if(fp==NULL){perror("fopen");return 1;}const char*s1="hello worldn";fwrite(s1,strlen(s1),1,fp);fclose(fp);return 0;
}
上图产生的原因是因为打开了文件,而不是将文件的内容进行清空的操作而是将文件后面的内容进行追加处理。
#include <stdio.h>
#include <unistd.h>
#include <string.h>int main()
{FILE* fp=fopen(","r");if(fp==NULL){perror("fopen");return 1;}char line[64];while(fgets(line,sizeof(line),fp)!=NULL){//将读到行的内容显示出来,同时也可以通过printf的方式打印出来printf("%sn",line);//也可以通过标准输出的方式打印出来fprintf(stdout,"%s",line);}fclose(fp);return 0;
}
cat命令用来打印文件中的内容。
#include <stdio.h>
#include <unistd.h>
#include <string.h>int main(int argc,char* argv[])
{if(argc!=2){printf("argc error!n");return 1;}//argv[1]就是我们传入的参数FILE* fp=fopen(argv[1],"r");//如果打开失败的话,则将失败的原因以文本的方式进行返回if(fp==NULL){perror("fopen");return 2;}//按行读取char line[64];while(fgets(line,sizeof(line),fp)!=NULL){//将读取的行显示出来printf("%s",line);//往显示器上写,stdout就是标准输出fprintf(stdout,"%s",line);}//关闭文件fclose(fp);return 0;
}
这里我们通过给argv[1]中传入文件名,我们可以发现我们可以打印其文件中的内容。
注意:open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限(mode),否则,使用两个参数的open。
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- int open(const char *pathname, int flags);
- int open(const char *pathname, int flags, mode_t mode);
- pathname: 要打开或创建的目标文件
- flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
- 参数:
- O_RDONLY: 只读打开
- O_WRONLY: 只写打开
- O_RDWR : 读,写打开
- 这三个常量,必须指定一个且只能指定一个
- O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
- O_APPEND: 追加写
- 返回值:
- 成功:新打开的文件描述符
- 失败:-1
如何给函数传递标志位?
在宏定义中利用大写字母和_来表示一个宏,上面的flags只能传递一种状态,但是如果在C语言中传入大量的选项呢,这时候我们可以利用标记位来表示该状态是否的意思,例如一个int是32字节,那么一个标记位就是一个比特位,那么就可以表示32个状态了。这种表示方式就是一种数据结构--位图
测试代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>#define ONE 0x1
#define TWO 0x2
#define THREE 0x3void show(int flags)
{if(flags&ONE) printf("hello onen");if(flags&TWO) printf("hello twon");if(flags&THREE) printf("hello threen");
}int main()
{show(ONE);printf("--------------------------------n");show(TWO);printf("--------------------------------n");show(ONE|TWO);printf("--------------------------------n");show(ONE|TWO|THREE);return 0;
}
测试代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd=open(",O_CREAT);if(fd<0){perror("open");return 1;}printf("open success fd:%dn",fd);return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd=open(",O_WRONLY);if(fd<0){perror("open");return 1;}printf("open success fd:%dn",fd);return 0;
}
这里我们发现,使用O_WRONLY不会自动创建文件,我们需要添加O_CREAT选项来解决。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd=open(",O_WRONLY|O_CREAT);if(fd<0){perror("open");return 1;}printf("open success fd:%dn",fd);return 0;
}
传入标记位
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd=open(",O_WRONLY|O_CREAT,0666);if(fd<0){perror("open");return 1;}printf("open success fd:%dn",fd);return 0;
}
这里我们通过给文件设置相关的权限为0666(即-rw-rw-rw-),而实际上不是,是由于系统中有创建时默认的权限umask权限(默认是0002)
如果想要得到我们想要设置的权限,那么我们只需要将此时umask设置为0即可解决问题。
关闭文件
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{//设置文件掩码为0umask(0);int fd=open(",O_WRONLY|O_CREAT);if(fd<0){perror("open");return 1;}printf("open success fd:%dn",fd);//关闭文件close(fd);return 0;
}
向文件中写入
测试代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{umask(0);int fd=open(",O_WRONLY|O_CREAT,0666);if(fd<0){perror("open");return 1;}const char* s="hello writen";write(fd,s,strlen(s));printf("open success fd:%dn",fd);close(fd);return 0;
}
这里我们只将上述代码写入字符串s改变为“HeLLoWRITE”,那么会写入到文件中是什么内容呢?
这里我们发现此时在中是进行覆盖式的写入,而不是先清空再进行写入的方式!
先清空文件再向文件中写入
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{umask(0);int fd=open(",O_WRONLY|O_CREAT|O_TRUNC,0666);if(fd<0){perror("open");return 1;}const char* s="hellon";write(fd,s,strlen(s));printf("open success fd:%dn",fd);close(fd);return 0;
}
这里我们发现此时文件首先进行了清空处理然后重新写入了hello。
在文件后面进行追加方式。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{umask(0);int fd=open(",O_WRONLY|O_CREAT|O_APPEND,0666);if(fd<0){perror("open");return 1;}const char* s="hello writen";write(fd,s,strlen(s));printf("open success fd:%dn",fd);close(fd);return 0;
}
对文件进行读取处理
以只读的方式打开
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{umask(0);int fd=open(",O_RDONLY,0666);if(fd<0){perror("open");return 1;}char buffer[64];memset(buffer,'