文章目录

  • 一、概述
    • 1. 磁盘文件和设备文件
      • 1.1 磁盘文件
      • 1.2 设备文件
    • 2. 磁盘文件分类
    • 3. 文本文件和二进制文件
      • 3.1 文本文件
      • 3.2 二进制文件
  • 二、打开文件与关闭文件
    • 1.文件指针
    • 2. 文件的打开和关闭
      • 2.1 打开文件函数
      • 2.2 打开的模式
      • 2.3 打开文件失败
      • 2.4 关闭文件
  • 三、文件的顺序读写
    • 1.按照字符读写文件fgetc、fputc
      • 1.1 一次写一个字符到文件
      • 1.2 文件结尾
      • 1.3 一次从文件读一个字符
    • 2.按照行读写文件fgets、fputs
      • 2.1 写文件
        • 注意事项
      • 2.2 读文件
        • 注意事项:
        • (1)遇到换行符 '\n' 就结束本次读取操作。
        • (2)如果遇不到换行符
        • (3)读取结束后会自动加上字符 '\0' 作为字符串结束。
        • (4)fgets() 会将换行符作为字符读取出来
    • 3.按照格式化文件fprintf、fscanf
      • 3.1 写文件
      • 3.2 读文件
    • 4.按照块读写文件fread、fwrite
      • 4.1 写文件
        • 注意size 和 nmemb 参数
        • 4.1.1 测试1
        • 4.1.2 测试2
      • 4.2 读文件
        • 4.2.1 测试1
        • 4.2.2 测试2
      • 4.3 注意读写数据格式一致
    • 5. 再次测试 fputs 和 fread 函数
      • 5.1 fputs 写字符类型外的数据进文件
      • 5.2 将文件的内容读出来
        • (1)fgets() 读e.txt文件
        • (2)fread() 读文件
        • (3)结果分析
  • 四、文件的随机读写
    • 1.移动文件光标的读写位置 fseek()
    • 2.获取文件光标的读写位置 ftell()
    • 3.测试
  • 五、Windows和Linux文本文件区别
  • 六、获取文件状态
  • 七、删除文件、重命名文件名
  • 八、文件缓冲区
    • 1. 文件缓冲区概述
    • 2.磁盘文件的存取
    • 3.更新缓冲区

一、概述

1. 磁盘文件和设备文件

1.1 磁盘文件

  指一组相关数据的有序集合,通常存储在外部介质(如磁盘)上,使用时才调入内存。简单点说就是将数据存放到外部介质上,比如磁盘,U盘啊等等,要使用的时候再导进来。

1.2 设备文件

  在操作系统中把每一个与主机相连的输入、输出设备看作是一个文件,把它们的输入、输出等同于对磁盘文件的读和写。

2. 磁盘文件分类

  计算机的存储在物理上是二进制的,所以物理上所有的磁盘文件本质上都是一样的:以字节为单位进行顺序存储。

  从用户或者操作系统使用的角度(逻辑上)把文件分为:文本文件和二进制文件。

  (2)二进制文件:基于值编码的文件,也就是说,如果要存放一个数字,那么就存放这个数字本身的值,比如说要存 123 ,这是数字的值为 一百二十三,那么就将 123 作为整体化为二进制的一百二十三存放,二进制的一百二十三为:0111 1011

3. 文本文件和二进制文件

3.1 文本文件

   基于字符编码,常见编码有ASCII,UNICODE等。一般可以使用文本编辑器直接打开。也就是说文本文件存放的是字符对应的编码值,如果用 ASCII 编码,那么存放的就是每个字符对应的 ASCII 码值。

   比如数5678的以ASCII存储形式(ASCII码)为:

  00110101 00110110 00110111 00111000。为什么会是这样呢,来分析一下。我的理解是:

  (1)文本文件基于字符编码,就是将数据看成字符串,字符串由一个个字符组成。在存储的时候,就将字符串拆分为一个个字符,然后将每个字符对应的 ASCII 码值存放到内存中,又因为计算机只认识0和1,所以 ASCII 码值也要转换为二进制的形式。

  (2)拿上面的例子 5678 来说明,如果以文本的形式存放,那么就将 5678 看成一个字符串,这个字符串由5,6,7,8 这四个字符组成。这四个字符对应的 ASCII 码值为:

5—》53(ASCII)
6—》54(ASCII)
7—》55(ASCII)
8—》56(ASCII)

  (3)计算机只认识0和1,所以要将它们对应的 ASCII 码值转换为二进制:
5—》53(ASCII)—》00110101
6—》54(ASCII)—》00110110
7—》55(ASCII)—》00110111
8—》56(ASCII)—》00111000

  (4)所以5678以文本形式存放就是存放它们对应的 ASCII 码值的二进制,也就是:

00110101 00110110 00110111 00111000

  (5)可以总结文本文件存放数据的步骤:

1)将数据看成字符串,然后拆分成字符

2)将字符转换 ASCII 码值代替

3)将ASCII 码值转化为二进制代表

3.2 二进制文件

  基于值编码,自己根据具体应用,指定某个值是什么意思。把内存中的数据按其在内存中的存储形式原样输出到磁盘上

   比如数5678的存储形式(二进制码)为:

   00010110 00101110

  为什么二进制文件是这存放呢?我的理解是:5678 这个数字对应的值是五千六百七十八,那么就将五千六百七十八化为二进制存储。

  5678(五千六百七十八)-——》00010110 00101110。

  从这里可以看出来,以二进制文件的方式存储时比较省空间的,但是不利于查看。二进制文件的方式就是直接存放整个数字本身的值。

二、打开文件与关闭文件

  在目录一里,主要介绍了文本文件和二进制文件,那么我们如何在程序中打开文件呢?下面就来简单地介绍一下。

1.文件指针

  想要知道在程序中如何打开文件,先来了解一下文件指针。如果将文件比作房子,那么文件指针可以简单地比作房卡,房卡也是钥匙,是用来打开和关闭房子的。

  在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。文件指针是一种结构体类型变量:

typedef struct
{short           level; //缓冲区"满"或者"空"的程度 unsigned        flags; //文件状态标志 char            fd;        //文件描述符unsigned char   hold;    //如无缓冲区不读取字符short           bsize;  //缓冲区的大小unsigned char   *buffer;//数据缓冲区的位置 unsigned        ar;      //指针,当前的指向 unsigned        istemp;   //临时文件,指示器short           token; //用于有效性的检查
}FILE;

  FILE是系统使用typedef定义出来的有关文件信息的一种结构体类型,结构中含有文件名、文件状态和文件当前位置等信息。

  声明FILE结构体类型的信息包含在头文件“stdio.h” 中,一般设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE类型变量。通过文件指针就可对它所指的文件进行各种操作。

2. 文件的打开和关闭

  在程序中想要使用文件,就先要打开文件。就像是想要使用房间,就先打开房间。

2.1 打开文件函数

#include <stdio.h>
FILE * fopen(const char * filename, const char * mode);

(1)功能:打开文件

(2)参数:

  filename:需要打开的文件名,根据需要加上路径。这个就像是要打开房间,要知道房间在哪。

   mode:打开文件的模式设置,打开文件的模式指的是打开了文件要做什么,是将数据读出来,还是将数据写进去。

(3)返回值:
   成功:文件指针
   失败:NULL

(4)如何理解:FILE * fp = fopen(const char * filename, const char * mode);

  之前在定义普通的变量时,比如说 int a=5;这句话的意思是请给我分配 4 个字节的大小的内存,我用来存放整型数据5,这块内存的名字就叫做a。

  那么int a=4,FILE *fp = fopen(const char * filename, const char * mode);这句代码的意思可以理解为:我想要打开一个文件,名字是xxx,将数据写进去或者读出来;请把这个文件的钥匙fp给我,我用来操作文件。

2.2 打开的模式

打开模式 含义
r或rb 以只读方式打开一个文本文件(不创建文件,若文件不存在则报错)。 读文件,如果文件没有,你不能凭空捏造一个文件出来读。
w或wb 以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
a或ab a是 append 的简写。以追加方式打开文件,在末尾添加内容,不覆盖(删除)文件之前存在的内容。若文件不存在则创建文件,写东西,如果这个文件不存在,那么你可以自己创建一个。
r+或rb+ 以可读、可写的方式打开文件(不创建新文件)
w+或wb+ 以可读、可写的方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
a+或ab+ 以添加方式打开可读、可写的文件。若文件不存在则创建文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留。

2.3 打开文件失败

  打开一个文件失败的原因大致有三个:
  (1)找不到文件
  (2)没有权限
  (3)打开的文件数量超出上限

2.4 关闭文件

  任何文件在使用后应该关闭:

  打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存。一个进程同时打开的文件数是有限制的,超过最大同时打开文件数,再次调用fopen打开文件会失败。

  如果没有明确的调用fclose关闭打开的文件,那么程序在退出的时候,操作系统会统一关闭。

#include <stdio.h>
int fclose(FILE * stream);功能:关闭先前fopen()打开的文件。此动作让缓冲区的数据写入文件中,并释放系统所提供的文件资源。参数:stream:文件指针返回值:成功:0失败:-1
 FILE * fp = NULL;fp = fopen("abc.txt", "r");fclose(fp);

三、文件的顺序读写

1.按照字符读写文件fgetc、fputc

1.1 一次写一个字符到文件

#include <stdio.h>
int fputc(int ch, FILE * stream);f:file 文件
put:放
c:字符fputc:文件放字符进去,就是每次将一个字符写入文件功能:将ch转换为unsigned char后写入stream指定的文件中
参数:ch:需要写入文件的字符stream:文件指针
返回值:成功:成功写入文件的字符失败:返回-1

测试代码:

char buf[] = "this is a test for fputc";
int i = 0;
int n = strlen(buf);
for (i = 0; i < n; i++)
{//往文件fp写入字符buf[i]int ch = fputc(buf[i], fp);printf("ch = %c\n", ch);
}

1.2 文件结尾

  在C语言中,EOF表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束标志。

#define EOF    (-1)

  当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为二进制文件的结束标志。为解决这一个问题,ANSI C提供一个feof函数,用来判断文件是否结束。feof函数既可用以判断二进制文件又可用以判断文本文件。

#include <stdio.h>
int feof(FILE * stream);
功能:检测是否读取到了文件结尾。判断的是最后一次“读操作的内容”,不是当前位置内容(上一个内容)。
参数:stream:文件指针
返回值:非0值:已经到文件结尾0:没有到文件结尾

1.3 一次从文件读一个字符

#include <stdio.h>
int fgetc(FILE * stream);功能:从stream指定的文件中读取一个字符
参数:stream:文件指针
返回值:成功:返回读取到的字符失败:-1

测试代码:

char ch;
#if 0
while ((ch = fgetc(fp)) != EOF)
{printf("%c", ch);
}
printf("\n");
#endifwhile (!feof(fp)) //文件没有结束,则执行循环
{ch = fgetc(fp);printf("%c", ch);
}
printf("\n");

2.按照行读写文件fgets、fputs

2.1 写文件

#include <stdio.h>
int fputs(const char * str, FILE * stream);S:string,是字符串的意思功能:将str所指定的字符串写入到stream指定的文件中,字符串结束符 '\0'  不写入文件。
参数:str:字符串stream:文件指针
返回值:成功:0失败:-1

注意事项

  (1) fputs() 函数 不会将字符串结束符 ‘\0’ 写入文件但是会将 ‘\n’ 写入文件,在文件中就会换行。也就是说 fputs() 函数遇到了 ‘\0’ 就会停止本次的写入。

  测试:将helloword 用两个等号夹起来," == helloword\0 == ";

#include <stdio.h>
#include <string.h>int main()
{       // 1.以追加的方式打开一个文件FILE *fp = fopen("/oracle/heima/CBasics/day10/test.txt","a");// 2.定义一个字符串char *str  = "==helloword\0==";char *str1 = "ilike\nyou";// 3.将字符串写入文件fputs(str,fp);fputs(str1,fp);fclose(fp);return 0;
}

测试结果:这一部分(\0 ==)并没有写入文件,也就是fputs() 遇到了 ‘\0’ 就停止本次的写入了,进行第二次的写入,将 ilikeyou 写进去。我们还会发现 you 这部分字符串换行了,所以说明了 fputs() 函数可以将换行符写入文件。

  (2) fputs() 函数每次写入一个字符串,不会自动加上换行符(\n)。如果没有换行符,那么第二次写入的字符串就会紧接在第一次写入的字符串后面,并不是每次写入一个字符串就作为新的一行。因为写入一个字符串,文件内光标就会移动到那个字符串的最后一个字符的位置。

  测试:先写入字符串str,然后在写入字符串str1。

#include <stdio.h>
#include <string.h>int main()
{       // 1.以追加的方式打开一个文件FILE *fp = fopen("/oracle/heima/CBasics/day10/test.txt","a");// 2.定义一个字符串char *str  = "helloword";char *str1 = "ilikeyou";// 3.将字符串写入文件fputs(str,fp);fputs(str1,fp);fclose(fp);return 0;
}

  查看结果:查看写入的文件的内容,str1 紧接着 str 后面,并没有另一起一行。

2.2 读文件

#include <stdio.h>
char * fgets(char * str, int size, FILE * stream);功能:从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束。
参数:str:字符串size:指定最大读取字符串的长度(size - 1)stream:文件指针
返回值:成功:成功读取的字符串读到文件尾或出错: NULL

注意事项:

  fgets() 函数是从文件中读取字符(内容),那么一次读取操作什么时候结束呢 ?

(1)遇到换行符 ‘\n’ 就结束本次读取操作。

测试:
  1)先看要读取的文件中的内容:一共有三行,至少前面两行都有换行符,如果进行一次读取操作,那么读取到的将会是第一行,因为有换行符。

  2)测试代码:

#include <stdio.h>
#include <string.h>int main()
{       // 1.以的方式打开一个文件FILE *fp = fopen("/oracle/heima/CBasics/day10/test.txt","r");// 2.定义一个字符串char strBuffer[1024];memset(strBuffer,0,sizeof(strBuffer));// 3.将文件数据读取出来放到字符串fgets(strBuffer,sizeof(strBuffer),fp);printf("strBuffer= %s\n",strBuffer);printf("strlen(strBuffer) = %d\n",strlen(strBuffer));fclose(fp);return 0;
}

  3)测试结果:只读取了一行。

(2)如果遇不到换行符

  就一直读取下去,直到文件的末尾或者是读取了 size-1 个字符。

  1)测试读取到文件的末尾:先将读取的文件的内容修改,去掉换行符,也就是将三行内容放到同一行。

  代码与2.2 中的(2)的测试代码一样。执行代码查看结果:

  一次将文件的全部内容读取出来,因为没有遇到换行符,也没有读取到 size-1 个字符。

  2)测试读取到了 size-1 个字符的情况:

  修改代码,将 size 参数改为5:

 // 3.将文件数据读取出来放到字符串fgets(strBuffer,5,fp);printf("strBuffer = %s\n",strBuffer);

  执行程序,查看结果,读取了4个字符,也就是5-1,同时我们也将读取出来的字符串长度打了出来。

(3)读取结束后会自动加上字符 ‘\0’ 作为字符串结束。

  这也是为什么读取 size-1 个字符的原因,要留一个位置给 ‘\0’ 。如果读取出来的字符串没有结束符,那么在操作字符串的时候就会出现意想不到的情况。

(4)fgets() 会将换行符作为字符读取出来

  fgets() 会将换行符作为字符读取出来,如果将读取出来的字符串打印,也会将换行符打出来,也就是换行。但是不要错误地认为 fgets() 函数会自动加上换行符。下面就来测试一下:

  1)先修改要读取的文件的内容:分成了四行。
  2)测试代码:

 // 1.以的方式打开一个文件FILE *fp = fopen("/oracle/heima/CBasics/day10/test.txt","r");// 2.定义一个字符串char strBuffer[1024];char strBuffer_1[1024];memset(strBuffer,0,sizeof(strBuffer));memset(strBuffer_1,0,sizeof(strBuffer_1));// 3.将文件数据读取出来放到字符串fgets(strBuffer,6,fp);fgets(strBuffer_1,6,fp);printf("strBuffer = %s\n",strBuffer);printf("strBuffer = %s\n",strBuffer_1);printf("strlen(strBuffer) = %d\n",strlen(strBuffer));printf("strlen(strBuffer_1) = %d\n",strlen(strBuffer_1));fclose(fp);return 0;
}

  3)查看结果:设置的 size 参数为5,那么应该是读取4个字符的,如果没有遇到换行符。那么我们看到 strBuffer 的长度是4,但是只显示了 hel 三个字符,还有一个跑哪去了,还有一个是换行符。我们也可以看到打印的两个行之间有空行,就是因为读取的字符串中本身就有一个换行符,然后在打印的时候我们又加上了一个换行符,所以就出现空白行了。

  4)那么再测试一次,我们打印的时候不加换行符了,看看会不会换行。修改代码:

         printf("strBuffer = %s",strBuffer);printf("strBuffer = %s",strBuffer_1);

  执行查看结果:strBuffer 和 strBuffer_1 依旧会换行。这也说明了会将换行符作为字符读取出来。因为我们在打印的时候没有加换行符,但是它们却换行了,说明字符串中有换行符。

字符串是这样的:“hel\n\0”,"lo\n\0"

3.按照格式化文件fprintf、fscanf

3.1 写文件

  这个函数是将字符串以指定的格式写到文件中。

#include <stdio.h>
int fprintf(FILE * stream, const char * format, ...);功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 '\0'  为止。
参数:stream:已经打开的文件format:字符串格式,用法和printf()一样
返回值:成功:实际写入文件的字符个数失败:-1

测试代码:这里的格式就是 a*b=c,a,b,c 是变量。

#include <stdio.h>
#include <string.h>int main()
{// 1.以追加的方式打开文件FILE *fp = fopen("/oracle/heima/CBasics/day10/fprintf.txt","a");if( fp == NULL ){printf("打开文件失败.\n");return -1;}int a,b,c;a=b=c=0;int i=10;while(i--){a = i;b = i+3;c = a*b;fprintf(fp,"%d*%d=%d\n",a,b,c);}fclose(fp);return 0;
}

测试结果:符合 a*b=c 的格式。

3.2 读文件

#include <stdio.h>
int fscanf(FILE * stream, const char * format, ...);
功能:从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据。
参数:stream:已经打开的文件format:字符串格式,用法和scanf()一样
返回值:成功:参数数目,成功转换的值的个数失败: - 1
int a = 0;
int b = 0;
int c = 0;
fscanf(fp, "%d %d %d\n", &a, &b, &c);
printf("a = %d, b = %d, c = %d\n", a, b, c);

4.按照块读写文件fread、fwrite

  前面的 fgetc() ,fputc(),fgets(),fputs() 函数,可以简单而又不完全正确(这只是我的理解)地说,这几个函数都是操作文本文件的。在将数据写入文本文件之前,数据类型是多种多样的,但是将数据写入了文本文件,就都变成了字符型或者字符串类型。

  或者说我要将一些数据写入文本文件,那必须先转换为字符或者字符串类型。使用 sprintf 字符串格式化函数,可以将不同的数据类型的数据,写到字符串里面。

  在前面我们也说过,文件大致分为两种文本文件和二进制文件。有时候我不想将数据转换为文本文件,也就是说数据在计算机内存中是什么样子,我就将这个计算机内存中的数值存到文件里面,而不是转换为ASCII码(字符类型)。或者这样说,我就想存放数字本身的数值,怎么办。

举个前面的例子:

  数5678的存储形式(二进制码)为:00010110 00101110

  如果以ASCII存储形式(文本文件的形式)为:00110101 00110110 00110111 00111000

  现在我想将数5678的值(00010110 00101110),写到文件中,也就是二进制文件;而不是以文本文件的形式(00110101 00110110 00110111 00111000)写到文件中,怎么办。

  C语言就推出了下面的函数:

4.1 写文件

  这个写文件的函数,也可以这样说:将内存中存放的二进制数值存放到指定的文件中。

#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);功能:以数据块的方式给文件写入内容。
将内存中存放的二进制数存放到指定的文件中。参数:ptr:准备写入文件数据的地址,这个地址就是要找到内存,然后取出内存中存放的二进制数。size: size_t 为 unsigned int类型,此参数指定写入文件内容的块数据大小,单位是字节。我们知道内存是分块的,最小的内存单位就是一个字节,就是说可以一个字节为一块。那么我也可以4个字节为单位,4个字节为一块内存。这个通常可以写你要写的数据类型,或者填1.nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb。这个就是一次写的操作要写多少块的内存进文件。stream:已经打开的文件指针
返回值:成功:实际成功写入文件数据的块数目,此值和 nmemb 相等失败:0

注意size 和 nmemb 参数

  其实size 和 nmemb 参数是共同决定一次写操作要将多少字节的数据,分成多少次写入文件而已。

  就比如说有100块砖头,要一天搬完。有的人一筐就装了50块,分两次就搬完了。有的人一筐装20块,要分5次才能搬完。不管怎么搬,他们只要一天之内搬完就行了。

  回到 fwrite() 函数,ptr 是数据存放的地址,你找到了数据在哪, 一次搬多少(size),搬多少次( nmemb ),由你决定,最终能搬完就行,总数目对就行。

4.1.1 测试1

typedef struct Stu
{char name[50];int id;
}Stu;Stu s[3];
int i = 0;
for (i = 0; i < 3; i++)
{sprintf(s[i].name, "stu%d%d%d", i, i, i);s[i].id = i + 1;
}int ret = fwrite(s, sizeof(Stu), 3, fp);
printf("ret = %d\n", ret);

4.1.2 测试2

  (1)测试代码:

#include <stdio.h>
#include <string.h>struct stu
{char name[31];int  age;
};
int main()
{struct stu s1;strcpy(s1.name,"伊丽莎白");s1.age = 19;// 以写的方式打开文件FILE *fp = fopen("/oracle/heima/CBasics/day10/fw.txt","w");fwrite(&s1,sizeof(struct stu),1,fp);fclose(fp);return 0;
}

  (2)测试结果:用 vi 编辑器打开 fw.txt 文件。

  可以看到很多乱码,其实并不是文件的内容乱,而是vi无法识别文件的格式,把内容当成ASCII码显示,如果内容刚好是ASCII码(名字),就能正确显示,如果不是ASCII码(如年龄),就无法正常显示了。

4.2 读文件

  这个函数就是,从文件中搬数据出来。搬到指定位置,不管你怎么搬,最后搬完指定的数目就行。搬到了指定的地址之后,别人就可以使用这个数据了。

  还有个注意的地方就是,地址一般是连续的。

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式从文件中读取内容
参数:ptr:存放读取出来数据的内存空间size: size_t 为 unsigned int类型,此参数指定读取文件内容的块数据大小nmemb:读取文件的块数,读取文件数据总大小为:size * nmembstream:已经打开的文件指针
返回值:成功:实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。失败:00: 表示读到文件结尾。(feof())

4.2.1 测试1

typedef struct Stu
{char name[50];int id;
}Stu;Stu s[3];
int ret = fread(s, sizeof(Stu), 3, fp);
printf("ret = %d\n", ret);int i = 0;
for (i = 0; i < 3; i++)
{printf("s = %s, %d\n", s[i].name, s[i].id);
}

4.2.2 测试2

struct stu
{char name[31];int age;
};int main()
{struct stu s1;// 以读的方式打开文件FILE *fp = fopen("/oracle/heima/CBasics/day10/fw.txt","r");// 将数据从文件中读出来fread(&s1,sizeof(struct stu),1,fp);printf("s1.name=%s,s1.age=%d\n",s1.name,s1.age);return 0;
}

测试结果:

4.3 注意读写数据格式一致

  要注意我们将什么样格式的数据写进二进制文件,那么读取出来时,为了减少不必要的麻烦,在将数据读取出来时,就应用什么格式的数据接收。

  比如4.1 是将下面这样格式的数据写入文件

typedef struct Stu
{char name[50];int id;
}Stu;

  那么 4.2 将数据读取了出来,就用这样格式的数据接收。

typedef struct Stu
{char name[50];int id;
}Stu;

5. 再次测试 fputs 和 fread 函数

  先来回顾一下 fputs 函数,可以看到这个函数的第一个参数是将要写入文件的字符串的地址。从第一个参数来看,这个函数一般是用来将字符串写入文件的。现在来重新想一下,为什么叫做文本文件?因为写入文件的一般是字符串或者字符,字符类型的数据,本质上就是ASCII码,在内存中存放的就是ASCII码,所以文件里面本质上是 ASCII 码(如果采用ASCII 编码)

int fputs(const char * str, FILE * stream);

  fputs() 函数要从参数1传进来的地址,去找到那片内存空间,然后将内存中的数写进文件里。其实 fputs() 函数并不知道内存中的数是ASCII码还是数值本身的二进制数,它只需要从内存中拿出来,然后写到文件里面就行了。也就是说,fputs() 只是负责将数据从内存中取出来,然后放到文件里,它不知道是文本文件还是二进制文件,它只是个搬运工。文本文件和二进制文件只是我们人类给他们取的名字,在计算机眼里只有1和0。

  根据上面的说法,fputs() 函数只是负责搬运,那么我将其他类型的数据写入文件也是可以的,使用fputs() 函数,不只是字符类型的数据。

  那么用这个 fputs() 来将其他类型的数据写入文件有什么区别呢?还是回到这个函数的第一个参数:char * str,这是字符型的指针,指针移动每次移动一个字节,也就是说在一次写操作里面,每次写到缓冲区中的数据是一个字节。我们知道,如果是 int * ,整型的指针,指针每次移动4个字节。

  但是对于 fputs() 来说无论是什么类型的指针,都当做 char * 字符型的指针来处理,在写数据时每次就移动一个字节,那么最后也是可以将数据写进去的。下面就来测试一下:

5.1 fputs 写字符类型外的数据进文件

(1)测试代码:

struct stu
{       char name[31];int  age;
};int main()
{// 1.以追加的方式打开文件FILE *fp = fopen("/oracle/heima/CBasics/day10/e.txt","a");if( fp == NULL ){printf("打开文件失败。\n");return -1;}// 2.测试用其他类型的数据指针传进去int a=1234567;int *p_a = &a;if( fputs(p_a,fp) == -1 ){printf("写入文件失败。\n");}double b = 3.14;double *p_b = &b;if( fputs(p_b,fp) == -1 ){printf("写入文件失败。\n");}float c = 6.111;float *p_c = &c;if( fputs(p_c,fp) == -1 ){printf("写入文件失败。\n");}// 结构体类型struct stu s;strcpy(s.name,"伊利斯");s.age = 19;struct str *p_s = &s;if( fputs(p_s,fp) == -1 ){printf("写入文件失败。\n");}fclose(fp);return 0;
}

  (2)执行程序,就会有警告,但不是错误,就是因为我们传的指针不是字符类型的指针。

  (3)查看文件中的内容,测试打开的文件是e.txt,是乱码。因为 vi 编辑器将写进去的二进制数当成ASCII码来显示了。在前面的内容说过:可以看到很多乱码,其实并不是文件的内容乱,而是vi无法识别文件的格式,把内容当成ASCII码显示,如果内容刚好是ASCII码,就能正确显示,如果不是ASCII码(如年龄和身高是整数),就无法正常显示了。

5.2 将文件的内容读出来

  在 5.1 中,我们使用 fputs() 函数将其他类型的数据写进去了,但文件里面并不是 ASCII 码值。现在我们来将文件里面的数据读取出来,那么我们应该用什么函数去读,fgets() 还是 fread() ?

  先来看看这两个函数的声明:

char * fgets(char * str, int size, FILE * stream);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

  会发现这两个函数的第一个参数都是指针,不过一个要求传字符型指针,一个是泛型指针。我们知道 fread() 函数肯定可以将数据读取出来,一是前面测试过,可以将多种数据读取出来,二是这文件里面的内容本身就是二进制数,也就是二进制文件。下面就来测试一下:

(1)fgets() 读e.txt文件

测试代码:

struct stu
{char name[31];int  age;
};int main()
{// 1.以读的方式打开文件FILE *fp = fopen("/oracle/heima/CBasics/day10/e.txt","r");if( fp == NULL ){       printf("打开文件失败。\n");return -1;}// 2.将数据读取出来int a=0;int *p_a = &a;if( fgets(p_a,sizeof(int),fp) == -1 ){       printf("读文件失败。\n");}printf("a=%d\n",a);double b = 0;double *p_b = &b;if( fgets(p_b,sizeof(double),fp) == -1 ){printf("读文件失败。\n");}printf("b=%d\n",b);float c = 0;float *p_c = &c;if( fgets(p_c,sizeof(float),fp) == -1 ){printf("读文件失败。\n");}printf("c=%d\n",c);// 结构体类型struct stu s;memset(&s,0,sizeof(struct stu));struct stu *p_s = &s;if( fgets(p_s,sizeof(struct stu),fp) == -1 ){printf("读文件失败。\n");}printf("s.name=%s,s.age=%d\n",s.name,s.age);fclose(fp);return 0;
}

测试结果:

  写的代码有问题,fgets() 读取数据的时候,是读取 size-1 个字符,在代码中至少要size+1;还有fgets() 会在后面补一个结束符 ‘\0’ 。所以说我们写进去的内容,用 fgets() 的话,会被加上结束符 ‘\0’ 导致数据出错。所以说还是不要混着用了,容易出错,不好控制。

(2)fread() 读文件

测试代码:

struct stu
{char name[31];int  age;
};int main()
{// 1.以读的方式打开文件FILE *fp = fopen("/oracle/heima/CBasics/day10/e.txt","r");if( fp == NULL ){       printf("打开文件失败。\n");return -1;}// 2.将数据读取出来int a=0;int *p_a = &a;if( fread(p_a,sizeof(int),1,fp) == -1 ){       printf("读文件失败。\n");}printf("a=%d\n",a);double b = 0;double *p_b = &b;if( fread(p_b,sizeof(double),1,fp) == -1 ){printf("读文件失败。\n");}printf("b=%d\n",b);float c = 0;float *p_c = &c;if( fread(p_c,sizeof(float),1,fp) == -1 ){printf("读文件失败。\n");}printf("c=%d\n",c);// 结构体类型struct stu s;memset(&s,0,sizeof(struct stu));struct stu *p_s = &s;if( fread(p_s,sizeof(struct stu),1,fp) == -1 ){printf("读文件失败。\n");}printf("s.name=%s,s.age=%d\n",s.name,s.age);fclose(fp);return 0;
}

测试结果:

(3)结果分析

  哎呦,我去,竟然都读失败了。为啥呢,想法错了吗,将这几个函数混合用,有空再来分析吧。看来还是不要混合用了,容易出错,读写文本文件尽量用 fputs() 和 fgets();读二进制文件就尽量用 fwrite() 和 fread();不要混合着用了,容易出错在写程序的时候。

四、文件的随机读写

1.移动文件光标的读写位置 fseek()

#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
功能:移动文件流(文件光标)的读写位置。
参数:stream:已经打开的文件指针offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。whence:其取值如下:SEEK_SET:从文件开头移动offset个字节SEEK_CUR:从当前位置移动offset个字节SEEK_END:从文件末尾移动offset个字节
返回值:成功:0失败:-1

2.获取文件光标的读写位置 ftell()

#include <stdio.h>
long ftell(FILE *stream);
功能:获取文件流(文件光标)的读写位置。
参数:stream:已经打开的文件指针
返回值:成功:当前文件流(文件光标)的读写位置失败:-1#include <stdio.h>
void rewind(FILE *stream);
功能:把文件流(文件光标)的读写位置移动到文件开头。
参数:stream:已经打开的文件指针
返回值:无返回值

3.测试

typedef struct Stu
{char name[50];int id;
}Stu;//假如已经往文件写入3个结构体
//fwrite(s, sizeof(Stu), 3, fp);Stu s[3];
Stu tmp;
int ret = 0;//文件光标读写位置从开头往右移动2个结构体的位置
fseek(fp, 2 * sizeof(Stu), SEEK_SET);//读第3个结构体
ret = fread(&tmp, sizeof(Stu), 1, fp);
if (ret == 1)
{printf("[tmp]%s, %d\n", tmp.name, tmp.id);
}//把文件光标移动到文件开头
//fseek(fp, 0, SEEK_SET);
rewind(fp);ret = fread(s, sizeof(Stu), 3, fp);
printf("ret = %d\n", ret);int i = 0;
for (i = 0; i < 3; i++)
{printf("s === %s, %d\n", s[i].name, s[i].id);
}

五、Windows和Linux文本文件区别

   b是二进制模式的意思,b只是在Windows有效,在Linux用r和rb的结果是一样的。

   Unix和Linux下所有的文本文件行都是\n结尾,而Windows所有的文本文件行都是\r\n结尾

   在Windows平台下,以“文本”方式打开文件,不加b:

   当读取文件的时候,系统会将所有的 “\r\n” 转换成 “\n”

  当写入文件的时候,系统会将 “\n” 转换成 “\r\n” 写入

   以"二进制"方式打开文件,则读\写都不会进行这样的转换

  在Unix/Linux平台下,“文本”与“二进制”模式没有区别,"\r\n" 作为两个字符原样输入输出。

测试:判断文本文件是Linux格式还是Windows格式

#include<stdio.h>int main(int argc, char **args)
{if (argc < 2)return 0;FILE *p = fopen(args[1], "rb");if (!p)return 0;char a[1024] = { 0 };fgets(a, sizeof(a), p);int len = 0;while (a[len]){if (a[len] == '\n'){if (a[len - 1] == '\r'){printf("windows file\n");}else{printf("linux file\n");}}len++;}fclose(p);return 0;
}

六、获取文件状态

#include <sys/types.h>
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
功能:获取文件状态信息
参数:
path:文件名
buf:保存文件信息的结构体
返回值:
成功:0
失败-1
struct stat {dev_t         st_dev;         //文件的设备编号ino_t         st_ino;          //节点mode_t        st_mode;   //文件的类型和存取的权限nlink_t       st_nlink;     //连到该文件的硬连接数目,刚建立的文件值为1uid_t         st_uid;         //用户IDgid_t         st_gid;         //组IDdev_t         st_rdev;      //(设备类型)若此文件为设备文件,则为其设备编号off_t         st_size;        //文件字节数(文件大小)unsigned long st_blksize;   //块大小(文件系统的I/O 缓冲区大小)unsigned long st_blocks;    //块数time_t        st_atime;     //最后一次访问时间time_t        st_mtime;    //最后一次修改时间time_t        st_ctime;     //最后一次改变时间(指属性)
};
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>int main(int argc, char **args)
{if (argc < 2)return 0;struct stat st = { 0 };stat(args[1], &st);int size = st.st_size;//得到结构体中的成员变量printf("%d\n", size);return 0;
}

七、删除文件、重命名文件名

#include <stdio.h>
int remove(const char *pathname);
功能:删除文件
参数:pathname:文件名
返回值:成功:0失败:-1#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
功能:把oldpath的文件名改为newpath
参数:
oldpath:旧文件名
newpath:新文件名
返回值:
成功:0
失败: - 1

八、文件缓冲区

1. 文件缓冲区概述

  ANSI C标准采用“缓冲文件系统”处理数据文件。

  所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。

  如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量) 。

2.磁盘文件的存取


  磁盘文件,一般保存在硬盘、U盘等掉电不丢失的磁盘设备中,在需要时调入内存

   在内存中对文件进行编辑处理后,保存到磁盘中

  程序与磁盘之间交互,不是立即完成,系统或程序可根据需要设置缓冲区,以提高存取效率

3.更新缓冲区

#include <stdio.h>
int fflush(FILE *stream);
功能:更新缓冲区,让缓冲区的数据立马写到文件中。
参数:
stream:文件指针
返回值:
成功:0
失败:-1

day10 文件操作相关推荐

  1. Python菜鸟入门:day11文件操作

    写在前面: 此博客仅用于记录个人学习进度,学识浅薄,若有错误观点欢迎评论区指出.欢迎各位前来交流.(部分材料来源网络,若有侵权,立即删除) 传送门: day01基础知识 day02知识分类 day03 ...

  2. C语言从入门到精通——文件操作

    共用体和联合体: union test {char ch;short sh;int var; };联合体,内部所有成员变量地址一致.等同于整个联合体的地址.联合体的大小,是内部成员变量中,最大的那个成 ...

  3. python基础之文件操作 内置模块(13)

    文件操作: 打开文件,读写文件,操作文件内容 写入文件操作:(把大象装进冰箱) 1.打开文件 open() #1.打开冰箱门 2.向文件中添加内容 write() #2.把大象塞进去 3.关闭文件 c ...

  4. C++核心编程(四)--文件操作

    5 文件操作 程序运行时产生的数据都属于临时数据,程序一点运行结束,就会被释放 通过文件可以将数据持久化 C++中对文件操作需要包含头文件:fstream 文件类型分为两种: 文本文件:文件以文本的A ...

  5. python codecs.open()及文件操作-文本处理 with open

    20210810 以二进制并追加的形式写入文件 20210731 f.write('\n') 换行写入 20210625 https://blog.csdn.net/a543402496/articl ...

  6. day07-字符编码、文件操作

    目录: 字符编码 文件处理 一.字符编码: #1. 什么是字符编码:将人识别的字符转换计算机能识别的01,转换的规则就是字符编码表 #2. 常用的编码表:ascii.unicode.GBK.Shift ...

  7. HTML5 本地文件操作之FileSystemAPI整理(二)

    一.文件目录操作 1.DirectoryEntry对象 属性: 1.isFile: 操作对象的是否为文件,DirectoryEntry对象固定其值为false 2.isDirectory: 操作对象是 ...

  8. python3 转码的函数_python基础3之文件操作、字符编码解码、函数介绍

    内容概要: 一.文件操作 二.字符编码解码 三.函数介绍 一.文件操作 文件操作流程: 打开文件,得到文件句柄并赋值给一个变量 通过句柄对文件进行操作 关闭文件 基本操作: 1 #/usr/bin/e ...

  9. linux文件编辑操作,Linux下文本编辑及其文件操作

    文本编辑及其文件操作 Vim 命令模式: dd 删除当前行 yy 2yy nyy 复制 从光标算起,复制n行 p 粘贴 默认粘贴在光标的下一行 u 撤销 末行模式: :wq 保存退出 :wq! 强制保 ...

最新文章

  1. [转载] 大道至简:软件工程实践者的思想——第四章 流于形式的沟通
  2. 使用pytorch创建神经网络并解决线性拟合和分类问题
  3. 数据中心暖通设计若干思考
  4. ASP.NET aspx页面中 写C#脚本; ASP.NET 指令(%@%);
  5. 十五个实用的mysql语句分享_分享MySQL中实用的几种SQL语句
  6. 云原生 DevOps,模型化应用交付能力很重要
  7. 生成路径 vs 设置_Simulink代码生成之模型配置
  8. PHP根据指定url生成二维码图片
  9. CSS中class优先级问题
  10. LeetCode 646. 最长数对链
  11. C++虚调用及其调用的具体形式
  12. 7.8 一阶常系数线性方程
  13. [Matlab]切比雪夫Ⅱ型滤波器设计:低通、高通、带通和带阻
  14. Java设计模式-工厂模式
  15. Python 基础学习Chapter6
  16. 基于arm嵌入式linux毕业设计,本科毕业论文--基于arm的嵌入式系统设计.doc
  17. 设计模式(博客园精化集)
  18. day17.什么是堆栈
  19. flyingsaucer转换多个html,如何在将PDF转换为HTML的同时在Flying-Saucer中设置基本URL?...
  20. 新神魔大陆服务器维护,《新神魔大陆》手游8月20日合服维护公告

热门文章

  1. 通达信捉妖改良CCI指标公式,简洁巧妙
  2. nacos配置mysql8
  3. 一次性掌握机器学习基础知识脉络 | 百万人学AI
  4. 复制 GROUP 会导致 AutoCAD 中出现未分组的项目
  5. javascript模拟点击事件--实现视频自动播放
  6. mkdir命令-创建目录的小技巧
  7. 【python-docx】文本操作(段落、run、标题、首行缩进、段前段后、多倍行距、对齐方式)
  8. mysql 复合索引_Mysql复合索引的顺序和必要值
  9. 外部表不是预期的格式怎么解决_干货分享|同学,你的论文格式有点不太对啊!...
  10. java计算机毕业设计美食推荐管理系统源码+系统+mysql数据库+lw文档