• [Linux系统编程]_文件编程(一)


    嵌入式之路,贵在日常点滴

                                                                    ---阿杰在线送代码

    目录

    一、系统调用

    1.1 什么是系统调用

    1.2 什么是库函数

    1.3 将hello写入到文件1.txt流程

    1.4 为什么要有缓冲区(补充) 

    1.5 内核缓冲区和C标准缓冲区的区别 

    二、文件编程常用API

    1、文件打开 open() 

    1.1 函数原型

    1.2 参数描述

    1.3代码举例

    附:关于mode

    创建文件 creat() 

    2、文件写入  write()

    2.1 函数原型

    2.2 参数使用

    2.3  代码举例

    3、读取文件 read()

    3.1 函数原型

    3.2 参数使用

    3.3 代码举例

    4、文件光标移动 lseek()

    4.1 函数原型

    4.2 参数使用

    4.3代码举例

    4.4 三种使用举例 

    5、文件关闭 close()

    5.1 函数原型

    5.2 参数使用

    5.3 代码举例

    三、文件操作小应用

    实现cp指令

    修改配置文件

    四、研究往文件里面写入整型数和结构体(增加认知)

    整型数 

    结构体 

    五、Linux文件操作原理简述

    文件描述符:

    文件操作原理: 

    六、 open与fopen的区别

    1. 来源

    2. 移植性

    3. 适用范围

    4. 文件IO层次

    5. 缓冲

    七、用ANSIC标准中的C库函数进行文件编程 

    fopen

    fread

    fwrite

    fseek

    用fopen、fread、fwrite、fseek来给一个文件写入结构体 

    fputc

    fgetc

    feof 


    一、系统调用

    1.1 什么是系统调用

    系统调用函数属于操作系统的一部分,是为了提供给用户进行操作的接口(API函数),使得用户态运行的进程与硬件设备(如CPU、磁盘、打印机、显示器)等进行交互。

    • 例如常见的系统调用 等等write read open …

    1.2 什么是库函数

    1. 库函数可分为两类,一类是C语言标准库函数,一类是编译器特定的库函数。
    2. 库函数可以理解为是对系统调用函数的一层封装。尽管系统函数执行效率是比较高效而精简的,但有时我们需要对获取的信息进行更复杂的处理,或更人性化的需要,我们把这些处理过程封装成一个函数,再将许多这类的函数放在一个文件(库)一般放在 .lib文件。最后再供程序员使用。
    • #include使用的时候包含头文件就可以使用其中的库函数了
    • 例如常见的库函数printf fwrite fread fopen…等等

    1.3 将hello写入到文件1.txt流程

    1. 首先fopen打开文件 fwrite参数附上要写入的内容
    2. 文本内容来到C标准缓冲区
    3. 如果满足条件就刷新C标准缓冲区,调用系统函数write进行写(补充:满了就会自动刷新)
    4. write却只是把要写入的内容写到内核缓冲区
    5. 如果内核缓冲区满足条件就刷新内核缓冲区,系统调用sys_write将缓冲区内容写入到磁盘(补充:有个进程会定时刷新内核缓冲区)
    6. 此时有进程读取1.txt文件内容,发现内核缓冲区就有这个文件内容,就直接从内核缓冲区

    1.4 为什么要有缓冲区(补充) 

    定义:缓冲区就是内存里的一块区域,把数据先存内存里,然后一次性写入硬盘中的文件,类似于数据库的批量操作。
    好处:减少对硬盘的直接操作,硬盘的执行速度为毫秒级别,内存为纳秒级别。在硬盘直接操作读写效率太低。 

    1.5 内核缓冲区和C标准缓冲区的区别 

    C语言标准库函数fopen()每打开一个文件时候,其都会对应一个单独一个缓冲区而内核缓冲区是公用的。 

    二、文件编程常用API

    1、文件打开 open() 

    1.1 函数原型

    • .int open(const char *pathname, int flags);
    • .int open(const char *pathname, int flags, mode_t mode);

    1.2 参数描述

    • .pathname :文件路径+文件名(若不包含路径,则默认为当前路径)
    • .flats : (1) O_RDONLY 只读打开         O_WRONLY 只写打开         O_RDWR  可读可写打开

        (2)当我们附带了权限后,打开的文件就只能按照这种权限来操作。

        以上这三个常数中应当只指定一 个。下列常数是可选择的:     

            O_CREAT 若文件不存在则创建它。使用此选项时,需要同时说明第三个参数mode,用其说明该新文件的存取许可权限。

            O_EXCL 如果同时指定了OCREAT,而文件已经存在,则出错。       

            O_APPEND 每次写时都加到文件的尾端。

            O_TRUNC 属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或只写成功打开,则将其长度截短为0。

    当然也有组合使用(1.O_RDONLY |O_CREAT 只读如果不存在则创建)(O_WRONLY |O_CREAT 只写如果不存在则创建)(O_WRONLY | O_APPEND 文件存在则追加写入)

    • .mode:一定是在flags中使用了O_CREAT标志,mode记录待创建的文件的访问权限

    • . 返回值: 失败返回-1,成功返回 整形(fd:文件描述符)

            fd>0,文件打开成功且fd为文件标识符 fd<0,文件打开失败*/ 

    1.3代码举例

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. int main()
    6. {
    7. int fd;//file description
    8. fd = open("./file1",O_RDWR);
    9. printf("fd = %d\n",fd);
    10. return 0;
    11. }

    运行结果:

    下面对flags(2)这类使用(|)进行附加使用的参数进行额外说明:

    (1)O_CREAT:文件若不存在则创建

    注意:需要额外说明文件操作权限参数mode 

    1. include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. int main()
    6. {
    7. int fd;//file description
    8. //尝试打开当前路径下的文件file1.c
    9. fd = open("./file1",O_RDWR);
    10. //打开失败了
    11. if(fd == -1){
    12. printf("open file1 failed,fd = %d\n",fd);
    13. //尝试以可读可写的方式创建并打开文件
    14. fd = open("./file1",O_RDWR|O_CREAT,0600);
    15. if(fd > 0){
    16. printf("create file1,fd = %d\n",fd);
    17. }
    18. }
    19. return 0;
    20. }

    运行结果:可以看到已经成功创建了file1.c

    (2)O_EXCL:如果同时指定了OCREAT,而文件已经存在,则出错(返回值为-1)

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. int main()
    6. {
    7. int fd;//file description
    8. fd = open("./file1",O_RDWR|O_CREAT|O_EXCL,0600);
    9. if(fd == -1){
    10. printf("file1 cunzai\n");
    11. }
    12. return 0;
    13. }

    运行结果:

      (3)O_APPEND:每次写时都加到文件的尾端(另起一行)

    如果不使用这个参数,因为文件打开后光标是位于文件头的,写入数据会把原来的数据按长度覆盖。(本质上就是光标的问题)

    原file1内容

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. #include
    6. #include "string.h"
    7. int main()
    8. {
    9. int fd;//file description
    10. char *buf = "a jie hen shuai";
    11. fd = open("./file1",O_RDWR|O_APPEND);
    12. printf("open suscess:fd = %d\n",fd);
    13. int n_write = write(fd,buf,strlen(buf));
    14. if(n_write != -1){
    15. printf("write %d byte to file\n",n_write);
    16. }
    17. close(fd);
    18. return 0;
    19. }

    运行后

    若不加O_APPEND

      

    (4)O_TRUNC清空原内容后写入

    在每次打开文件写入之前,先把原有的内容清空后写入

    原file1内容

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. #include
    6. #include "string.h"
    7. int main()
    8. {
    9. int fd;//file description
    10. char *buf = "a jie hen shuai";
    11. fd = open("./file1",O_RDWR|O_TRUNC);
    12. printf("open suscess:fd = %d\n",fd);
    13. int n_write = write(fd,buf,strlen(buf));
    14. if(n_write != -1){
    15. printf("write %d byte to file\n",n_write);
    16. }
    17. close(fd);
    18. return 0;
    19. }

    运行后:

      

    附:关于mode

    1、可读        r         4

    2、可写        w        2

    3、执行         x        1

    创建文件 creat() 

    创建文件的另一种方法(不可用于打开)

    1. int creat(const char *pathname, mode_t mode);
    2. /** mode **
    3. S_IRUSR 可读 宏:4
    4. S_IWUSR 可写 宏:2
    5. S_IXUSR 可执行 宏:1
    6. S_IRWXU 可读可写可执行 宏:7 */

    简单使用示例:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. //可读可写
    9. int fd = creat("./file1.c",S_IRUSR|S_IWUSR);
    10. if(fd > 0){
    11. printf("文件创建成功\n");
    12. }else{
    13. printf("同名文件已经存在\n");
    14. }
    15. close(fd);
    16. return 0;
    17. }

    2、文件写入  write()

    2.1 函数原型

    • int write(int fd, void *buf, int count);

    2.2 参数使用

    • fd   表示改文件的文件描述符 ,open的返回值
    • buf  写入的文本内容
    • count 写入的数据长度【使用srtlen非sizeof】
    • 返回值  成功返回写入的字节数 失败返回-1 

    2.3  代码举例

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. #include
    6. #include "string.h"
    7. int main()
    8. {
    9. int fd;//file description
    10. char *buf = "a jie hen shuai!";
    11. fd = open("./file1",O_RDWR);
    12. if(fd == -1){
    13. printf("open file1 failed,fd = %d\n",fd);
    14. fd = open("./file1",O_RDWR|O_CREAT,0600);
    15. if(fd > 0){
    16. printf("create file1,fd = %d\n",fd);
    17. }
    18. }
    19. printf("create file1,fd = %d\n",fd);
    20. write(fd,buf,strlen(buf));
    21. close(fd);//关闭文件
    22. return 0;
    23. }

    运行结果:

    3、读取文件 read()

    3.1 函数原型

    • int read(int fd, void *buf, size_t count);

    3.2 参数使用

    • .fd 表示改文件的文件描述符,open的返回值
    • .buf 指缓冲区,读取的数据存放位置
    • .count 传入缓冲区的字节大小【使用sizeof非strlen】
    • .返回值 成功返回读出的字节数 失败返回-1
    • (值得注意的是也可能是以非阻塞的方式读一个设备文件和网络文件,后面网络编程常见)

    3.3 代码举例

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. #include
    6. #include "string.h"
    7. #include
    8. int main()
    9. {
    10. int fd;//file description
    11. char *buf = "a jie hen shuai!";
    12. fd = open("./file1",O_RDWR);
    13. if(fd == -1){
    14. printf("open file1 failed,fd = %d\n",fd);
    15. fd = open("./file1",O_RDWR|O_CREAT,0600);
    16. if(fd > 0){
    17. printf("create file1,fd = %d\n",fd);
    18. }
    19. }
    20. printf("create file1,fd = %d\n",fd);
    21. int n_write = write(fd,buf,strlen(buf));
    22. if(n_write != -1){
    23. printf("write %d byte to file\n",n_write);
    24. }
    25. close(fd);//关闭文件
    26. fd = open("./file1",O_RDWR);//重新打开
    27. char *readBuf;
    28. readBuf = (char *)malloc(sizeof(char)*n_write + 1);
    29. int n_read = read(fd,readBuf,n_write);
    30. printf("read %d,contest:%s\n",n_read,readBuf);
    31. close(fd);
    32. return 0;
    33. }

    运行结果:

      

    read最需要注意的就是光标的位置,尤其是在write操作后,光标已经到达文件尾部,直接read,就会读个寂寞。

    解决办法是重新打开文件(不建议)使光标回到文件头。或者使用后面所提到的lseek操作光标。

    4、文件光标移动 lseek()

    4.1 函数原型

    • .int lseek(int fd, off_t offset, int whence);

    4.2 参数使用

    • .fd   表示改文件的文件描述符,open的返回值
    • .offset  表示偏移量
    • .whence 指出偏移的方式

       whence参数补充说明
      SEEK_SET:偏移到文件头+ 设置的偏移量
      SEEK_CUR:偏移到当前位置+设置的偏移量
      SEEK_END:偏移到文件尾置+设置的偏移量

    4.3代码举例

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. #include
    6. #include "string.h"
    7. #include
    8. int main()
    9. {
    10. int fd;//file description
    11. char *buf = "a jie hen shuai!";
    12. fd = open("./file1",O_RDWR);
    13. if(fd == -1){
    14. printf("open file1 failed,fd = %d\n",fd);
    15. fd = open("./file1",O_RDWR|O_CREAT,0600);
    16. if(fd > 0){
    17. printf("create file1,fd = %d\n",fd);
    18. }
    19. }
    20. printf("create file1,fd = %d\n",fd);
    21. int n_write = write(fd,buf,strlen(buf));
    22. if(n_write != -1){
    23. printf("write %d byte to file\n",n_write);
    24. }
    25. // close(fd);
    26. // fd = open("./file1",O_RDWR);
    27. char *readBuf;
    28. readBuf = (char *)malloc(sizeof(char)*n_write + 1);
    29. //使得光标移动到文件的开始位置:
    30. lseek(fd,0,SEEK_SET);//将光标移动至首
    31. int n_read = read(fd,readBuf,n_write);
    32. printf("read %d,contest:%s\n",n_read,readBuf);
    33. close(fd);
    34. return 0;
    35. }

    运行结果:

    4.4 三种使用举例 

    (1) 返回当前的偏移量 

    1. int fd,ret;
    2. fd=open("hello1.txt",O_RDWR);
    3. ret=lseek(fd,0,SEEK_CUR);
    4. printf("%d\n",ret);

    (2)返回文件大小

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. #include
    6. #include "string.h"
    7. #include
    8. int main()
    9. {
    10. int fd;//file description
    11. char *buf = "a jie hen shuai!";
    12. fd = open("./file1",O_RDWR);
    13. int filesize = lseek(fd,0,SEEK_END);
    14. printf("file's size is :%d\n",filesize);
    15. close(fd);
    16. return 0;
    17. }

    运行结果:

      

    (3)☆扩充文件大小   
    特别注意扩充文件大小后 需要写入内容 否则扩充不生效

    1. #include
    2. #include
    3. #include
    4. #include
    5. int main()
    6. {
    7. int fd,ret;
    8. char a[]="JMU WELCOME";
    9. fd=open("hello1.txt",O_RDWR|O_CREAT,0777);
    10. ret=lseek(fd,1000,SEEK_END);
    11. write(fd,a,strlen(a));
    12. printf("%d\n",ret);
    13. return 0;
    14. }

    5、文件关闭 close()

    5.1 函数原型

    • int close(int fd);

    5.2 参数使用

    • .fd 表示改文件的文件描述符,open的返回值
    • .返回值 成功为0 失败返回-1

    5.3 代码举例

    1. int fd;
    2. fd=open("tmp.txt",O_RDONLY);
    3. close(fd);

    三、文件操作小应用

    实现cp指令

    cp src.c(源文件) des.c(目标文件)

    1、 C语言参数  ./a.out   argc(3个参数)   argv(数组指针-》数组的数组)

    1. #include "stdio.h"
    2. int main(int argc,char **argv)
    3. {
    4. printf("total params:%d\n",argc);
    5. printf("No.1 params :%s\n",argv[0]);
    6. printf("No.2 params :%s\n",argv[1]);
    7. printf("No.3 params :%s\n",argv[2]);
    8. return 0;
    9. }
    10. /** 参数
    11. argc: 参数个数
    12. **argv:二级指针,数组的指针,即这个指针里的每一项都是一个数组(字符串)
    13. **/

    2、编程思路:

    • (1)打开src源文件(要被复制的文件)
    • (2)把源文件的内容读入buf
    • (3)创建目标文件
    • (4)把buf内容写入目标文件
    • (5)关闭源文件与目标文件
    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. #include
    6. #include "string.h"
    7. #include
    8. int main(int argc,char **argv)
    9. {
    10. int fdSrc;//file description
    11. int fdDes;
    12. char *readBuf = NULL;
    13. if(argc != 3){ //判断是否为三个参数
    14. printf("pararm error\n");
    15. exit(-1);
    16. }
    17. //(1)打开src源文件(要被复制的文件)
    18. fdSrc = open(argv[1],O_RDWR); //打开src.c
    19. int size = lseek(fdSrc,0,SEEK_END);//计算存储fdSrc文件的字节大小
    20. lseek(fdSrc,0,SEEK_SET);//将光标移到开头
    21. readBuf = (char *)malloc(sizeof(char)*size + 8);
    22. //(2)把源文件的内容读入buf
    23. int n_read = read(fdSrc,readBuf,size);//读src到buf
    24. //(3)创建目标文件,如果已经存在则覆盖
    25. fdDes = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600);//打开/创建des.c
    26. //(4)把buf内容写入目标文件
    27. int n_write = write(fdDes,readBuf,strlen(readBuf));//将buf写入到des.c
    28. //(5)关闭源文件与目标文件
    29. close(fdSrc);
    30. close(fdDes);
    31. return 0;
    32. }

    运行结果:

    fdDes = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600);//打开/创建des.c 
    你是否有想过这里为什么要加上这两个额外说明吗

    O_CREAT:文件若不存在则创建
    O_TRUNC:在每次打开文件写入之前,先把原有的内容清空后写入(否则,如果new的文件已经存在且字节数比较多时,仅仅是覆盖数据而已,多出来的字节数还是原先new的内容)

    修改配置文件

    现有某配置文件如config,要求利用文件编程把LENG的值修改为5.

    SPEED=3
    LENG=3
    SCORE=9
    LEVEL=5

    简单示例: 

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. #include
    6. #include "string.h"
    7. #include
    8. int main(int argc,char **argv)
    9. {
    10. int fdSrc;//file description
    11. char *readBuf = NULL;
    12. if(argc != 2){
    13. printf("pararm error\n");
    14. exit(-1);
    15. }
    16. fdSrc = open(argv[1],O_RDWR);
    17. int size = lseek(fdSrc,0,SEEK_END);//ji suan chu fdSrcwenjian de zi jie da xiao
    18. lseek(fdSrc,0,SEEK_SET);
    19. readBuf = (char *)malloc(sizeof(char)*size + 8);
    20. int n_read = read(fdSrc,readBuf,size);
    21. char *p = strstr(readBuf,"LENG=");
    22. if(p==NULL)
    23. {
    24. printf("not found\n");
    25. exit(-1);
    26. }
    27. p = p+strlen("LENG=");
    28. *p = '5';
    29. lseek(fdSrc,0,SEEK_SET);
    30. int n_write = write(fdSrc,readBuf,strlen(readBuf));
    31. close(fdSrc);
    32. return 0;
    33. }

    运行结果:

    SPEED=3
    LENG=5
    SCORE=9
    LEVEL=5 

    四、研究往文件里面写入整型数和结构体(增加认知)

    为什么会有这一研究?

    如果按照前面进行编写代码 写入文件API 和 读取文件API 操作的都是字符串,直接写入整型数编译会成功,但结果会起冲突。

    非要写入整型数呢?让我们先来看看两个函数的原型

    ssize_t write(int fd, const void *buf, size_t count); 

    ssize_t read(int fd, void *buf, size_t count); 
    误区:const void *buf 一定代表着字符串,事实上,并非如此,它也可以代表一个指针 即地址

    直接上代码

    整型数 

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. #include
    6. #include "string.h"
    7. #include
    8. int main(int argc,char **argv)
    9. {
    10. int fd;//file description
    11. int data = 100;
    12. int data2 = 0;
    13. fd = open("./file1",O_RDWR);
    14. int n_write = write(fd,&data,sizeof(int));
    15. lseek(fd,0,SEEK_SET);//将光标回到文件内容开头,方便后续读取等操作
    16. int n_read = read(fd,&data2,sizeof(int));
    17. printf("read %d\n",data2);
    18. close(fd);
    19. return 0;
    20. }

    运行结果:

    看完结果,是不是认为这样这样操作也错误了呢

    其实不然,结果并没有错误,并不影响程序的写入和读取操作,只不过人眼看起来有点不舒服,ASCII解锁出来并不是我们想象中的样子。(思想:关键是程序读取没错误)

    结构体 

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. #include
    6. #include "string.h"
    7. #include
    8. struct Test
    9. {
    10. int a;
    11. char c;
    12. };
    13. int main(int argc,char **argv)
    14. {
    15. int fd;//file description
    16. struct Test data = {100,'a'};
    17. struct Test data2;
    18. fd = open("./file1",O_RDWR);
    19. int n_write = write(fd,&data,sizeof(struct Test));
    20. lseek(fd,0,SEEK_SET);
    21. int n_read = read(fd,&data2,sizeof(struct Test));
    22. printf("read %d,%c\n",data2.a,data2.c);
    23. close(fd);
    24. return 0;
    25. }

    甚至结构体数组

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. #include
    6. #include "string.h"
    7. #include
    8. struct Test
    9. {
    10. int a;
    11. char c;
    12. };
    13. int main(int argc,char **argv)
    14. {
    15. int fd;//file description
    16. struct Test data[2] ={{100,'a'},{101,'b'}};
    17. struct Test data2[2];
    18. fd = open("./file1",O_RDWR);
    19. int n_write = write(fd,&data,sizeof(struct Test)*2);
    20. lseek(fd,0,SEEK_SET);
    21. int n_read = read(fd,&data2,sizeof(struct Test)*2);
    22. printf("read %d,%c\n",data2[0].a,data2[0].c);
    23. printf("read %d,%c\n",data2[1].a,data2[1].c);
    24. close(fd);
    25. return 0;
    26. }

    运行结果(结果并没有错误)

    五、Linux文件操作原理简述

    文件描述符:

    1.对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或者创建一个新文件时,内核向进程返回一个文件描述符。当读写一个文件时。用open和creat返回的文件描述符标识该文件,将其作为参数传递给read和write

    按照惯例,UNIX shell使用文件描述符0与进程的标准输入相结合,文件描述符1与标准输出相结合。STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO这几个宏代替了0、1、2这几个数。

    在Linux下,对文件的操作都是通过文件描述符来进行的。linux进程默认会打开三个文件描述符,分别是

    stdin 标准输入 对应设备:键盘

    1 stdout 标准输出 对应设备 :显示器

    2 stderror 标准错误 对应设备:显示器 

    1. #include
    2. #include
    3. #include
    4. #include "stdio.h"
    5. #include
    6. #include "string.h"
    7. #include
    8. int main(int argc,char **argv)
    9. {
    10. int fd;//file description
    11. char readBuf[128];
    12. int n_read = read(0,readBuf,5);
    13. int n_write = write(1,readBuf,strlen(readBuf));
    14. printf("\ndone\n");
    15. return 0;
    16. }

    运行结果:

     

    2.文件描述符,这个数字在一个进程中表示一个特定含义,当我们open一个文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回给应用程序一个数字作为文件描述符,这个数字就和我们内存中维护的这个动态文件的这些数据结构绑定上了,以后我们应用程序如果要操作这个动态文件,只需要用这个文件描述符区分。

    3.文件描述符的作用域就是当前进程,出了这个进程文件描述符就没有意义了。

    open函数打开文件,打开成功返回一个文件描述符,打开失败,返回-1.

    文件操作原理: 

    1.在linux中要操作一个文件,一般是先open打开一个文件,得到文件描述符,然后对文件进行读写操作(或其他操作),最后是close关闭文件即可。

    2.我们对文件进行操作时,一定要先打开文件,打开成功之后才能操作,如果打开失败,就不用进行后边的操作了,最后读写完成后,一定要关闭文件,否则会造成文件损坏

    3.文件平时是存放在块设备中的文件系统文件中的,我们把这种文件叫静态文件,当我们去open打开一个文件时,linux内核做到操作包括:内核在进程中建立一个打开文件的数据结构,记录下我们打开的这个文件;内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内核中特定地址管理存放(叫动态文件)。

    静态文件:存放于磁盘,未被打开的文件

    动态文件:当使用open后,在linux内核会产生一个结构体来记录文件的信息,例如fd,buf,信息节点.此时的read,write都是对动态文件进行操作,当close时,才把缓存区所有的数据写回磁盘中。

    4.打开文件以后,以后对这个文件的读写操作,都是针对内存中的这一份动态文件的,而不是针对静态文件的。当然我们对动态文件进行读写以后,此时内存中动态文件和块设备文件中的静态文件就不同步了,当我们close关闭动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)块设备中的静态文件。

    5.为什么这么设计,不直接对块设备直接操作。

    块设备本身读写非常不灵活,是按块读写的,而内存是按字节单位操作的,而且可以随机操作,很灵活。

    六、 open与fopen的区别

    对于这两个名字很类似的函数,对于很多初学者来说,不容易搞清楚它们有什么不同,只知道按照函数用法使用。如果能很好的区分两者,相信大家对于C语言和UNIX系统(包括LINUX)有更深入的了解。

    在网上查找了一些资料,但是感觉不够全面,一些答案只是从某个角度阐述,所以让人觉得,这个也对,那个也对。但到底谁的表述更正确呢?其实都是对的,只是解释的视角不同罢了。下面结合个人的理解做一些梳理。

     

    1. 来源

    从来源的角度看,两者能很好的区分开,这也是两者最显而易见的区别:

    • open 是UNIX系统调用函数(包括LINUX等),返回的是文件描述符(File Descriptor),它是文件在文件描述符表里的索引。
    • fopen 是ANSIC标准中的C语言库函数,在不同的系统中应该调用不同的内核api。返回的是一个指向文件结构的指针。 
       PS:从来源来看,两者是有千丝万缕的联系的,毕竟C语言的库函数还是需要调用系统API实现的。

    2. 移植性

    这一点从上面的来源就可以推断出来,`fopen`是C标准函数,因此拥有良好的移植性;而`open`是UNIX系统调用,移植性有限。如windows下相似的功能使用API函数`CreateFile`。

    3. 适用范围

    • open 返回文件描述符,而文件描述符是UNIX系统下的一个重要概念,UNIX下的一切设备都是以文件的形式操作。如网络套接字、硬件设备等。当然包括操作普通正规文件(Regular File)。
    • fopen   是用来操纵普通正规文件(Regular File)的。

    4. 文件IO层次

    如果从文件IO的角度来看,前者属于低级IO函数,后者属于高级IO函数。低级和高级的简单区分标准是:谁离系统内核更近。低级文件IO运行在内核态,高级文件IO运行在用户态。

    5. 缓冲

    1. 缓冲文件系统 
      缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用;当执行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”,装满后再从内存“缓冲区”依此读出需要的数据。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”装满后再写入文件。由此可以看出,内存“缓冲区”的大小,影响着实际操作外存的次数,内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件“缓冲区”的大小随机器 而定。fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind等。
    2. 非缓冲文件系统 
      缓冲文件系统是借助文件结构体指针来对文件进行管理,通过文件指针来对文件进行访问,既可以读写字符、字符串、格式化数据,也可以读写二进制数据。非缓冲文件系统依赖于操作系统,通过操作系统的功能对文件进行读写,是系统级的输入输出,它不设文件结构体指针,只能读写二进制文件,但效率高、速度快,由于ANSI标准不再包括非缓冲文件系统,因此建议大家最好不要选择它。open, close, read, write, getc, getchar, putc, putchar等。

    一句话总结一下,就是open无缓冲,fopen有缓冲。前者与readwrite等配合使用, 后者与fread,fwrite等配合使用。

    使用fopen函数,由于在用户态下就有了缓冲,因此进行文件读写操作的时候就减少了用户态和内核态的切换(切换到内核态调用还是需要调用系统调用API:readwrite);而使用open函数,在文件读写时则每次都需要进行内核态和用户态的切换;表现为,如果顺序访问文件,fopen系列的函数要比直接调用open系列的函数快;如果随机访问文件则相反。

    这样一总结梳理,相信大家对于两个函数及系列函数有了一个更全面清晰的认识,也应该知道在什么场合下使用什么样的函数更合适,效率更高。

    七、用ANSIC标准中的C库函数进行文件编程 

    fopen

    使用给定的模式 mode 打开 filename 所指向的文件。

    #include

    FILE *fopen(const char *path, const char *mode);//返回的是文件标识符

    fopen函数用的是标准C语言库,第一个参数是文件路径,第二个参数是文件权限。

    /** 返回值 **/
    打开成功,指向该流的文件指针就会被返回。
    打开失败,则返回 NULL,并把错误代码存在 error 中。

    /** 参数 **/
    mode:    "r"          只读  文件必须存在
                  "w"         只写  文件创建,若存在则清空
                  "a"         只读  打开或创建,在文件末尾追加
                  带有"+"的   可读可写
                  带有"b"的   二进制文件


    r:以只读方式打开文件,该文件必须存在。
    r+:以读/写方式打开文件,该文件必须存在。
    rb+:以读/写方式打开一个二进制文件,只允许读/写数据。
    rt+:以读/写方式打开一个文本文件,允许读和写。
    w:打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
    w+:打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
    a:以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF 符保留)。
    a+:以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)。
    wb:以只写方式打开或新建一个二进制文件,只允许写数据。
    wb+:以读/写方式打开或新建一个二进制文件,允许读和写。
    wt+:以读/写方式打开或新建一个文本文件,允许读和写。
    at+:以读/写方式打开一个文本文件,允许读或在文本末追加数据。
    ab+:以读/写方式打开一个二进制文件,允许读或在文件末追加数据。


    fread

    从给定流 stream 读取数据到 ptr 所指向的数组中。

    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

    /** 参数 **/
    ptr     指向带有最小尺寸  size*nmemb 字节的内存块的指针。(buf缓冲区)
    size    要读取的每个元素的大小,以字节为单位。
    nmemb   元素的个数,每个元素的大小为 size 字节。
    stream  流

    /** 返回值 **/
    成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。
    如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。

    fwrite

    把 ptr 所指向的数组中的数据写入到给定流 stream 中。

    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)

    /** 参数遇返回值和fread一样 **/

    或者换种容易理解点的

    参数一:要往文件写入的内容,是字符串格式
    参数二:一次写入的字节数
    参数三:写多少次
    参数四:目标文件标识符

    fseek

    操作文件指针的位置

    int fseek(FILE *stream, long int offset, int whence)

    /* 参数 */
    offset  相对 whence 的偏移量,以字节为单位。
    whence  文件指针的位置
            SEEK_SET 文件的开头
            SEEK_CUR 文件指针的当前位置
            SEEK_END 文件的末尾

    /* 参数 */
    如果成功,则该函数返回零,否则返回非零值。

    简单示例 

    1. #include "stdio.h"
    2. #include "stdlib.h"
    3. #include "string.h"
    4. int main()
    5. {
    6. //以可读可写的方式创建文件,若存在则清空原文件
    7. FILE* fp = fopen("./fopenTest","w+");
    8. char* str = "chen li chen mei wo shuai";
    9. //向文件流中写入字符串
    10. fwrite(str,sizeof(char),strlen(str),fp);
    11. //文件指针回头才能读取
    12. fseek(fp,0,SEEK_SET);
    13. char* readBuf = (char *)malloc(strlen(str));
    14. memset(readBuf,'\0',strlen(str));
    15. fread(readBuf,sizeof(char),strlen(str),fp);
    16. //打印出读取到的readBuf
    17. printf("read:%s\n",readBuf);
    18. fclose(fp);
    19. return 0;
    20. }

    运行结果:

    等效于

    1. #include "stdio.h"
    2. #include "stdlib.h"
    3. #include "string.h"
    4. int main()
    5. {
    6. FILE* fp = fopen("./fopenTest","w+");
    7. char* str = "chen li chen mei wo shuai";
    8. fwrite(str,sizeof(char)*strlen(str),1,fp);
    9. fseek(fp,0,SEEK_SET);
    10. char* readBuf = (char *)malloc(strlen(str));
    11. memset(readBuf,'\0',strlen(str));
    12. fread(readBuf,sizeof(char)*strlen(str),1,fp);
    13. printf("read:%s\n",readBuf);
    14. fclose(fp);
    15. return 0;
    16. }

    补充,研究一下fread和fwrite返回的值

    1. #include "stdio.h"
    2. #include "stdlib.h"
    3. #include "string.h"
    4. int main()
    5. {
    6. FILE* fp = fopen("./fopenTest","w+");
    7. char* str = "chen li chen mei wo shuai";
    8. int nwrite = fwrite(str,sizeof(char)*strlen(str),1,fp);
    9. fseek(fp,0,SEEK_SET);
    10. char* readBuf = (char *)malloc(strlen(str));
    11. memset(readBuf,'\0',strlen(str));
    12. int nread = fread(readBuf,sizeof(char)*strlen(str),1,fp);
    13. printf("read:%s\n",readBuf);
    14. printf("read=%d,write=%d\n",nread,nwrite);
    15. fclose(fp);
    16. return 0;
    17. }

    运行结果:

     

    得出结论: 

    写返回的值取决于第三个参数

    读返回的值就不一定了

    int nread = fread(readBuf,sizeof(char)*strlen(str),1,fp);

    nread = 1

    int nread = fread(readBuf,sizeof(char)*strlen(str),100,fp);

    nread = 1

    int nread = fread(readBuf,sizeof(char),strlen(str),fp);

    nread = 25

    用fopen、fread、fwrite、fseek来给一个文件写入结构体 

    1. #include
    2. #include
    3. struct data
    4. {
    5. int a;
    6. char b;
    7. };
    8. int main()
    9. {
    10. FILE* fp;
    11. struct data test2 = {1,'q'};
    12. struct data test1;
    13. fp = fopen("./file2","w+");//返回文件标识符
    14. fwrite(&test2,sizeof(struct data),1,fp);//每次写多少数据,写多少次
    15. fseek(fp,0,SEEK_SET);//光标移到文件头
    16. fread(&test1,sizeof(struct data),1,fp);
    17. fclose(fp);
    18. printf("test1.a = %d test1.b = %c\n",test1.a,test1.b);
    19. return 0;
    20. }

    fputc

    描述

    C 库函数 int fputc(int char, FILE *stream) 把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。

    参数

    • char -- 这是要被写入的字符。该字符以其对应的 int 值进行传递。
    • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。

    返回值

    如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。

    1. #include "stdio.h"
    2. int main()
    3. {
    4. FILE *fp;
    5. fp = fopen("./test.txt","w+");
    6. fputc('a',fp);
    7. fclose(fp);
    8. return 0;
    9. }

    运行结果:

     

     

    写多个字符 

    1. #include "stdio.h"
    2. #include "string.h"
    3. int main()
    4. {
    5. FILE *fp;
    6. int i;
    7. char *str = "chenlichen mei wo shuai";
    8. int len = strlen(str);
    9. fp = fopen("./test.txt","w+");
    10. for(i=0;i
    11. fputc(*str,fp);
    12. str++;
    13. }
    14. fclose(fp);
    15. return 0;
    16. }

     

     for(i=0;i
                    fputc(*str,fp);
                    str++;
     }

    这样可以吗

    显然是不可以的,每次str都在变,for里面的条件就一直在变

    fgetc

    描述

    C 库函数 int fgetc(FILE *stream) 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。

    参数

    • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要在上面执行操作的流。

    返回值

    该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。

    feof 

    描述

    C 库函数 int feof(FILE *stream) 测试给定流 stream 的文件结束标识符。

    参数

    • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

    返回值

    当设置了与流关联的文件结束标识符时,该函数返回一个非零值,否则返回零。

    1. #include "stdio.h"
    2. #include "string.h"
    3. int main()
    4. {
    5. FILE *fp;
    6. char c;
    7. fp = fopen("./test.txt","r");
    8. while(!feof(fp)){
    9. c = fgetc(fp);
    10. printf("%c",c);
    11. }
    12. fclose(fp);
    13. return 0;
    14. }

     

     

  • 相关阅读:
    string的应用和练习
    嵌入式开发--CubeMX使用入门教程
    微信小程序 实现CBC 加密/解密
    吴恩达《机器学习》9-1:代价函数
    Pascal面试考试题库和答案(命令式和过程式编程语言学习资料)
    leetcode9. 回文数(C++)
    【4003】基于springboot实现的线上阅读系统
    Linux系统下邮件服务器的搭建(Postfix+Dovecot+SSL)
    内部类概述
    阿里云服务器部署Dubbo框架入门教程(SpringBoot+zookeeper+dubbo)
  • 原文地址:https://blog.csdn.net/weixin_50546241/article/details/125980534