C语言 day22 | 我的日常分享

C语言 day22

文件

  1. 文件的基本概念
  2. C语言对文件的处理
  3. 文件的基本练习

文件的基本概念

  • 磁盘文件:指一组相关数据的有序集合,通常储存在外部介质(如磁盘),使用时才调入内存。
  • 设备文件:
    • 键盘:标准输入文件
    • 屏幕:标准输出文件
    • 其他设备:打印机、触摸屏、摄像头、音箱等

图片

缓冲区的作用:
1、提高存取效率
2、提高磁盘的使用寿命

磁盘文件的分类

  1. 一个文件通常是磁盘上一段命名的存储区
  2. 计算机的存储在物理上是二进制的,所以物理上所有的磁盘文件本质上都是以字节
    为单位进行顺序存储的
  3. 从用户或者操作系统的使用角度上(逻辑上)可以把文件分为:
    • 文本文件:基于字符编码的文件
    • 二进制文件:基于值编码的文件

文本文件

  1. 基于字符编码,常见编码有ASCII、UNICODE等,一般可以使用文本编辑器直接打开

例如:数5678以ASCII码的存储形式为00110101 00110110 00110111 00111000
歌词文件(lrc):文本文件

二进制文件

  1. 把内存中的数据按其在内存中的存储形式原样输出到磁盘上
  2. 一般需要自己判断或使用特定的软件分析数据格式
    例如:5678的储存形式为 二进制码:00010110 00101110
    音频文件(mp3):二进制文件

文本文件、二进制文件的对比(重要)

  1. 译码:

    • 文本文件编码基于字符定长,译码容易些
    • 二进制文件编码是变长的,译码难一些,不同的二进制文件,需要不同的译码方式,或即要使用不同的软件打开
  2. 空间利用率:

    • 二进制文件用一个比特来代表一个意思(位操作)
    • 文本文件任何一个符号至少需要一个字节
  3. 可读性:

    • 文本文件用通用的记事本就几乎可以浏览所有文本文件
    • 二进制文件需要一个具体的文件解码器,不如读图软件、音乐播放器等。

文件的打开与关闭

  • C语言中不能直接操作文件
    只能采用库函数间接对文件进行操作

  • C语言操作文件的基本流程
    在使用文件前调用打开函数将文件打开

  • 打开文件会得到一个文件指针fp

    1. 调用各种有关函数,利用fp对文件进行具体操作(读或写)
    2. 在文件使用完后,及时电泳关闭函数来关闭文件
    3. C语言中所有的文件操作都围绕文件指针完成
  • 任何文件使用之前必须打开,使用之后必须关闭

文件指针

  • 文件指针的定义
    1
    FILE * 指针变量名
    FILE是系统在头文件stdio.h中使用typedef定义出来的存储有关文件信息的一种结构体类型

图片

  • 注意:实际编程过程中,使用库函数操作文件,无需关心FILE结构体的细节

  • 三个特殊的文件指针(无需定义,直接可以使用)

    1. stdio:标准输入设备 默认为当前终端(键盘)
    2. stdout:标准输出设备 默认为当前终端(屏幕)
    3. stderr:标准出错 默认为当前终端(屏幕)
      当我们程序出错或者使用perror函数时,信息打印在此终端

fopen打开文件

  • 头文件:#include <stdio.h>

  • 函数定义:FILE *fopen(const char *path, const char *mode);

    1. 第一个参数:文件的路径
    2. 第二个参数:打开方式 r w a + b t
  • 返回值:成功返回相应的指针,失败返回NULL

  • 第二个参数的集中形式(打开文件的方式)

    1. r以只读的方式打开文件
      • 文件不存在返回NULL
      • 文件存在,返回文件指针,进行后续的读操作
    2. w以只写的方式打开
      • 文件不存在,以指定文件名创建此文件
      • 文件存在,清空文件内容,进行写操作
      • 文件打不开(权限为只读),返回NULL
    3. a以追加的方式打开文件
      • 文件不存在,以指定文件名创建此文件(同w
      • 文件存在,从文件的结尾处进行写操作
    4. +同时以读写方式打开指定文件
      • 注意与rw的区别
    5. bt
      • b以二进制的方式打开文件
      • t(可省略)以文本方式打开文件
1
2
FILE *fp = NULL;
fp = fopen(文件名,文件使用方式);

打开方式的组合形式

图片

fclose关闭文件

  • 头文件:#include <stdio.h>
  • 函数定义:int fclose(FILE *fp);
  • 返回值:
    • 关闭成功:返回0
    • 关闭失败:返回非0
1
2
3
FILE *fp=NULL;
fp=fopen("a.txt","r");
fclose(fp);

文件的顺序读写

  • 字节读写函数:fgetcfputc
  • 字符串读写函数:fgetsfputs
  • 数据块读写函数:freadfwrite
  • 格式化读写函数:fscanffprintf

字节的读写函数

字节的读操作fgetc

  • 头文件:#include <stdio.h>
  • 函数定义:int fgetc(FILE *stream);
  • 返回值:
    • 读取成功: 返回字符对应的ASCII码
    • 到达文件末尾或发生读错误:返回 EOF。
      1
      ch=fgetc(fp);//读一个字节
  1. 文本文件:读到文件末尾返回EOF(EOF是个宏-1)
  2. 二进制文件:要使用feof判断是否到达文件末尾

字节的读操作fgetc

  • 头文件:#include <stdio.h>
  • 函数定义:int fputc(int c, FILE *stream);
  • 返回值:
    • 输出成功:返回输出的字节
    • 输出失败:返回EOF
      1
      ch=fgetc(fp);//读一个字节

案例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void test1(void) {
FILE* fp = NULL;
char buf[256] = "";
int i = 0;
fp = fopen("C:/Users/11977/Desktop/a.txt", "r");

while (1)
{
buf[i] = fgetc(fp);
if (buf[i] == EOF) {
break;
}
i++;
}
printf("%s\n", buf);
fclose(fp);
return;
}

运行结果:
图片
图片

案例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void test2(void) {
char buf[256] = "";
int i = 0;
FILE* fp = NULL;
fp = fopen("C:/Users/11977/Desktop/b.txt", "w");
printf("请输入要写入的字符串:\n");
fgets(buf, 256, stdin);
buf[strlen(buf) - 1] = '\0';//去掉读取进去的换行符
while (1)
{
fputc(buf[i],fp);
if (buf[i] == '\0') {
break;
}
i++;
}
fclose(fp);
return;
}

运行结果:
图片
图片

练习:从一个文件(文本文件)中读取所有信息,写入到另一个文件中

参考:r+ w+ fgetc fputc EOF

字符串的读写函数

字符串的写操作fputs

  • 头文件:#include <stdio.h>
  • 函数定义:int fputs(const char *s, FILE *stream);
  • 返回值:
    • 成功:返回一个非负值
    • 失败:返回 EOF
  • 注意:字符串的’\0’不会写入到文件中,且遇到’\0’结束

字符串的读操作

  • 头文件:#include <stdio.h>
  • 函数定义:char *fgets(char *s, int size, FILE *stream);
  • 返回值:
    • 成功:返回相同的 str 参数
    • 到达文件末尾或者没有读取到任何字符:返回NULL
    • 发生错误:返回NULL
  • 注意:从stream文件中读取size-1个字符,在读取过程中遇到换行符或EOF,读取结束,并读取换行符,在最后加一个’\0’。获取一行数据。

案例3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void test3(void) {
int i = 0;
int res = 0;
char* buf[] = {
"给你一整首情诗\n",
"关于你温暖名字\n",
"在每个孑然的深夜为你诵读\n",
"字句真诚而坚固\n",
"星辰也为你祝福\n" };
int n = sizeof(buf) / sizeof(buf[0]);
FILE* fp = NULL;
fp = fopen("C:/Users/11977/Desktop/c.txt", "w");
for (i = 0; i < n; i++) {
res = fputs(buf[i], fp);
}

fclose(fp);
return;
}

运行结果:
图片

案例4:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void test4(void) {
char* res=NULL;
char str[256] = "";
FILE* fp = NULL;
fp = fopen("C:/Users/11977/Desktop/c.txt", "r");
while (1)
{
res = fgets(str, sizeof(str), fp);
if (res == NULL) {
break;
}
printf("%s", str);
}
fclose(fp);
return;
}

运行结果:
图片

文件块的读写函数

文件块的写操作fwrite

  • 头文件:#include <stdio.h>
  • 函数定义:size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
  • 函数参数:
    1. 第一个参数:从哪里读取写入
    2. 第二个参数:一次读写的数据块大小
    3. 第三个参数:要读写的数据块数
    4. 第四个参数:要写入文件的指针
  • 返回值:
    • 实际读写的数据块数(不是总数据大小,重要)
  • 注意:fwrite是将内存中的数据原样写入到文件中,记事本打开会出现乱码;但是数据本身是没问题的,不影响读取

文件块的读操作fread

  • 头文件:#include <stdio.h>
  • 函数定义:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 函数参数:
    1. 第一个参数:读到哪里去
    2. 第二个参数:一次读写的数据块大小
    3. 第三个参数:要读写的数据块数
    4. 第四个参数:要写入文件的指针
  • 返回值:
    • 实际读写的数据块数(不是总数据大小,重要)

案例5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void test5(void) {
STU grade[] = {
{100,"xiaochen",98},
{101,"xiaohua",87},
{102,"xiaoming",60}
};
int n = sizeof(grade) / sizeof(grade[0]);

FILE* fp = NULL;
fp = fopen("C:/Users/11977/Desktop/d.txt", "w");
fwrite(grade, sizeof(STU), n, fp);
fclose(fp);
return;
}

运行结果:
图片

案例6:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void test6(void) {
STU grade[4];
int n = sizeof(grade) / sizeof(grade[0]);
memset(grade, 0, sizeof(grade));
int i = 0;

FILE* fp = NULL;
fp = fopen("C:/Users/11977/Desktop/d.txt", "r");
/*for (i = 0; i < 3; i++) {
fread(&grade[i], sizeof(STU), n, fp);
}*/
fread(&grade[i], sizeof(STU), 3, fp);

for (i = 0; i < 3; i++) {
printf("num=%d name=%s score=%f\n", grade[i].num, grade[i].name, grade[i].score);
}
fclose(fp);
return;
}

运行结果:
图片

文件的格式化读写

格式化写操作fprintf

  • 头文件:#include <stdio.h>
  • 函数定义:int fprintf(FILE *stream, const char *format, ...);
  • 函数参数:
    1. 第一个参数:要写入文件的指针
  • 返回值:
    • 成功:返回写入的字符总数
    • 失败:返回一个负数。

格式化读操作fscanf

  • 头文件:#include <stdio.h>
  • 函数定义:int fscanf(FILE *stream, const char *format, ...);
  • 函数参数:
    1. 第一个参数:要读取的文件的指针
  • 返回值:
    • 成功:返回成功匹配和赋值的个数
    • 到达文件末尾或发生读错误:返回EOF。

案例7:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void test7(void) {
STU grade[] = {
{100,"xiaochen",98},
{101,"xiaohua",87},
{102,"xiaoming",60}
};
int n = sizeof(grade) / sizeof(grade[0]);
int i = 0;
FILE* fp = NULL;
fp = fopen("C:/Users/11977/Desktop/e.txt","w");
if (fp == NULL) {
perror("fopen");
return;
}
for (i = 0; i < n; i++) {
fprintf(fp, "num=%d name=%s score=%f\n", grade[i].num, grade[i].name, grade[i].score);
}

fclose(fp);
return;
}

运行结果:
图片

案例8:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void test8(void) {
STU grade[3];
memset(grade, 0, sizeof(grade));
int i = 0;
FILE* fp = NULL;
fp = fopen("C:/Users/11977/Desktop/e.txt", "r");
if (fp == NULL) {
perror("fopen");
return;
}
for (i = 0; i < 3; i++) {
fscanf(fp, "num=%d name=%s score=%f\n", &grade[i].num, grade[i].name, \
&grade[i].score);
}
for (i = 0; i < 3; i++) {
printf("num=%d name=%s score=%f\n", grade[i].num, grade[i].name, grade[i].score);
}
fclose(fp);
return;
}

运行结果:
图片


文件的随机读写

rewind、ftell、fseek函数

引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void test9(void) {
char buf[16] = "";
FILE* fp = NULL;
fp = fopen("C:/Users/11977/Desktop/f.txt", "w+");
if (fp == NULL) {
perror("fopen");
return;
}
fputs("hello file!", fp);

fgets(buf, 16, fp);
printf("%s\n", buf);
fclose(fp);
return;
}

运行结果:
图片

为什么会出现这种现象?

当写入hello file!完成后,流指针到达文件末尾!的位置;当再次读取的时候从!开始读取,由于系统不知道你要写入多少内容,他会给你分配足够的空间,所以后面是屯屯屯,而当读取屯屯屯后流指针停在了屯屯屯的末尾,即分配的足够空间的末尾,此时再关闭文件,屯屯屯也不会消失了。如果没有进行读取操作在写入hello file!后立即关闭文件,系统会自动删除流指针后面的屯屯屯。

解决办法:文件写完后需要关闭文件,然后重新打开文件。让文件流指针指向文件开始的位置。(顺序读取只能这样解决)

rewind

  • 头文件:#include <stdio.h>
  • 函数定义:void rewind(FILE *stream);
  • 返回值:void
  • 功能:将文件流指针返回到文件首位置

ftell

  • 头文件:#include <stdio.h>
  • 函数定义:long ftell(FILE *stream);
  • 返回值:返回流指针距离文件首的字节数
  • 功能:测量当前流指针的位置

fseek

  • 头文件:#include <stdio.h>
  • 函数定义:int fseek(FILE *stream, long offset, int whence);
  • 函数参数:
    • offset:偏移量(正数向右,负数向左)
    • whence:开始偏移的位置
      • SEEK_SET 文件的开头(宏 0)
      • SEEK_CUR 文件指针的当前位置(宏 1)
      • SEEK_END 文件的末尾(宏 2)
  • 返回值:
    • 成功:函数返回零
    • 失败:返回非零值
  • 功能:流指针的移动

案例9:

根据文件大小动态申请空间,一次性读取文件所有内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void test10(void) {
FILE* fp = NULL;
fp = fopen("C:/Users/11977/Desktop/g.txt", "r");
if (fp == NULL) {
perror("fopen");
return;
}
//1、测量文件大小
//a、跳到文件末尾
fseek(fp, 0, SEEK_END);
//b、获取当前位置距文件首的字节数
int size = ftell(fp);
//c、将流指针跳回文件首
rewind(fp);

//2、申请堆区空间
char* buf = (char*)calloc(1,size+1);//加1目的是存放 \0

//3、将文件内容读取到内存
fread(buf,size,1,fp);

//打印读取出的内容
printf("%s\n", buf);

fclose(fp);
return;
}

运行结果:
图片

feof函数

  • 头文件:#include<stdio.h>

  • 函数定义:int feof(FILE *stream);

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

    • 文件结束:返回非0
    • 文件未结束:返回0
  • 功能:判断文件是否到达末尾

  • 注意:

    1. EOF宏 只能用于文本文件
    2. feof函数:文本文件、二进制文件都可以使用

      案例10:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      void test11(void) {
      char ch = '\0';
      FILE* fp = NULL;
      fp = fopen("C:/Users/11977/Desktop/g.txt", "r");
      if (fp == NULL) {
      perror("fopen");
      return;
      }

      while (feof(fp) == 0)
      {
      ch = fgetc(fp);
      printf("%c", ch);
      }

      fclose(fp);
      return;
      }
      运行结果:
      图片

ferror函数

  • 头文件:#include<stdio.h>
  • 函数定义:int ferror(FILE *stream)
  • 返回值:如果设置了与流关联的错误标识符,该函数返回一个非零值,否则返回一个零值。即,
    • 遇到错误:返回非0
    • 无错误:返回0