文章目录
- 一
- 1 为什么要使用文件?
- 2 什么是文件?
- 2.1 程序文件
- 2.2 数据文件
- 2.3 关于文件名问题!
- 2.4 扩展
- 3 关于流的思想
- 3.1 标准流
- 4 文件信息区
- 5 fopen函数与fclose函数
- 5.1 fopen函数
- 5.2 fclose函数
- 6 顺序读写函数的介绍
- 6.1 fgetc函数
- 6.2 fputc函数
- 6.3 fgets 函数
- 情况1:
- 情况2:
- 6.4 fputs函数
- 6.5 对比两组函数
- (1)scanf与fscanf函数
- (2)printf函数与fprintf函数
- (3)sscanf函数与sprintf函数
- 6.6 fread与fwrite函数
- (1)fread函数
- (2)fwrite函数
- 7 文件的随机读写
- 7.1 fseek
- 7.2 ftell
- 7.3 rewind
- 8 文件读取结束的判定
- 8.1 判断文件是否读取结束
- 8.2 feof函数
- 9 文件缓冲区
一
1 为什么要使用文件?
如果没有文件,我们使用的数据是储存在电脑的内存中的,内存一回收,
我们的数据即丢失,为了能*持久化*的保存数据,我们采用文件
2 什么是文件?
硬盘(磁盘)上的文件是文件,但在程序设计中,
可分为两种文件—程序文件与数据文件(从文件功能的角度进行分析)
2.1 程序文件
程序文件包括源文件(后缀为.c) 目标文件(windows环境下后缀为 .obj )
可执行文件(windows环境下为.exe)
2.2 数据文件
文件中不一定存放执行的程序,也可以存放程序所需的数据,存放数据的文件
我们称为数据文件
数据文件包括文本文件与二进制文件数据在内存中是以二进制的形式进行存储,如果不加转换地移入外存的文件中,此文件即为二进制文件如果要求在外存上以ASCII码的形式进行存储,则数据在存储前转换格式,此文件则被称为文本文件针对数据:字符在文件中一律以ASCII码值的形式存储,而数值型数据以二进制形式或者ASCII码值的形式存储
举例:
#include<stdio.h>
int main() {//打开文件FILE* p = fopen("test6.txt", "wb");//wb是以二进制的形式写入文件//将数据以二进制形式输入文件中去int a = 10000; //功能是将10000以二进制的形式输入文件fwrite(&a, 4, 1, p);//关闭文件fclose(p);p = NULL;return 0;
}
对于数据输入到文本文件中去的例子,关于读写函数时会涉及到!
2.3 关于文件名问题!
一个文件中只有一个对应的文件标识 - 便于用户引用
文件名由
文件路径 + 文件名主干 + 文件后缀
例如:
C:\Users\26921\Desktop\专业\test.txt
在test之前,是文件路径
test是文件名主干
.txt是文件后缀
2.4 扩展
小扩展:计算机硬件设备是文件得以存储数据的基础计算机的存储分为内存与外部存储器内存是cpu存放中间数据和与外部存储器交换的信息的地方而外部存储器则存放系统程序与用户程序及一些常用的数据!c盘与d盘皆是外部存储器
3 关于流的思想
程序执行的数据的来源可以是键盘,光盘,磁盘,u盘等等
程序输出的设备也可以是各种不同类型的输出设备,抽象掉这些无关紧要的
因素,我们可以看到,数据像一个水流一样流入程序,又流出程序,所以我
们引入了流的思想
3.1 标准流
stdin标准输入流 ——一般从键盘中输入数据,scanf函数就是从标准输入流中获取数据stdout 标准输出流—— 大多数环境输出至显示器页面,printf函数就是将数据输出到标准输出流中stderr 标准错误流——大多数环境将其输出到显示器页面stdin,stdout,stderr的类型均是FILE * 通常称为文件指针,c语言中是通过FILE * 的文件指针,来维护对流的各种操作的
4 文件信息区
对于每一个被打开一个文件时,系统会在内存中开辟一个关于此文件的文件信息区
保存此文件的信息(比如此文件的名字,地址,文件的状态等等)
系统事先设定的结构体名称为 FILE
如图:
我们通过文件信息区,间接地对文件进行操作!
5 fopen函数与fclose函数
当我们要对文件进行操作时,要先打开文件,然后进行操作,操作文件完后,再关闭文件
而打开与关闭文件的功能由fopen 与fclose函数实现
5.1 fopen函数
fopen 函数用于打开文件
fopen 的格式:FILE* fopen(const char* FILEName,const char* Mode)
// 第一个参数是文件的地址 , 第二个参数是对文件操作的类别
// 此函数返回文件信息区的地址,我们通过此地址间接地对文件中的内容
进行操作。
操作类别:
注意:
1 读:是指程序读取文件中的数据,写:是指程序写入数据到文件中
2 要注意当我们将字符串赋给形参时,本质上是将首元素的地址赋给形参,所以在填写操作类别时,
要用" " 而不是' ' ,前者是字符串的格式,后者是字符的格式
举例:
新建一个文本文档:
#include<stdio.h> //文件操作函数包含在stdio文件之中!
int main(){// 打开文件text并返回文件信息区的地址FILE* p1 = fopen("text.txt", "r");//执行只读操作return 0;}
5.2 fclose函数
fclose函数用于关闭文件
int fclose(FILE * Stream);
函数的参数是文件信息区的地址
#include<stdio.h> //文件操作函数包含在stdio文件之中!
int main(){FILE* p1 = fopen("text.txt", "r");fclose(p1);return 0;}
6 顺序读写函数的介绍
在打开文件之后就需要对文件进行读写:下面讲一下这些读写函数:
这些读写函数中有些能用作文件流的操作,有些能用作所有的输入流或输出流,故将这些函数命名为文件操作函数是不合理的!
注意:输入:将文件中的数据输入到程序中输出:通过程序将数据存放到文件中
6.1 fgetc函数
fgetc函数的功能是一个字符一个字符地读取数据。
格式:int fgetc(FILE* Stream)
fgetc的参数是对应的流的地址,返回值是int 类型,当读取成功时返回读取字符的ASCII码值,
当什么都没读取到时,返回EOF ,EOF是-1
举例:
#include<stdio.h>
#include<assert.h>
int main() {FILE* p = fopen("text.txt", "r");//打开文件,只读操作 与fgetc函数相匹配assert(p);int a = fgetc(p);printf("%c\n", a);fclose(p);return 0;
}
出现这种结果的原因是文件名输入错误,对于只读操作,如果打开的文件不存在则报错。
#include<stdio.h>
#include<assert.h>
int main() {FILE* p = fopen("test.txt", "r");//此时将地址改正assert(p);for (int i = 0; i < 3; i++) {int a = fgetc(p); //三次读取printf("%c ", a);
}fclose(p);return 0;
}
结果说明了 :当我们重复引用fgetc函数时,其在文件中的光标不断的往后移动,因此我们可以不断地读取新的字符,其他的函数是否也是同样的原理?
6.2 fputc函数
fputc函数用于从程序中输出单个字符到文件中
格式:int fputc(int Character,FILE*Stream)
第一个形参是要输入的字符,第二个形参是目标文件信息区地址
#include<stdio.h>
#include<assert.h>
int main() {//fputc函数//格式:int fputc(int Character,FILE*Stream)//第一个形参是要输入的字符,第二个形参是目标文件信息区地址FILE* p = fopen("test.txt", "w");//打开文件,只写操作 可以与fputc函数相匹配for (int i = 0; i < 10; i++) {fputc('a', "test.txt");}fclose(p);return 0;
}
6.3 fgets 函数
fgets函数的功能是从文件中获取一串字符
fgets函数的格式:char * fgets(char * str,int num,FILE *Stream)
第一个参数是读取的数据所存放到的空间,第二个参数是最多读取num个字符
(但是最后一个字符会自动添加'\0',所以最多从文件中读取num-1个字符)
第三个参数是文件信息区的地址
如果fgets函数读取失败会返回NULL指针。
情况1:
#include<stdio.h>
int main(){FILE* p = fopen("test.txt", "r");//打开文件,只读操作,与fgets函数相匹配char arr[10] = { "xxxxxxxxxx" };fgets(arr, 10, p);for (int i = 0; i < 10; i++) {printf("%c ", arr[i]);}fclose(p);return 0;
}
结果显示为9个字符
监视:
情况2:
#include<stdio.h>
int main(){FILE* p = fopen("test.txt", "r");//打开文件,只读操作,与fgets函数相匹配char arr[10] = { "xxxxxxxxxx"};fgets(arr, 10, p);for (int i = 0; i < 10; i++) {printf("%c", arr[i]);}fclose(p);return 0;
}
结果显示:
这说明此函数在读取文件数据时,只会一行一行的读取,而不会换行读,即使读取的字符不足num个,打印的数据换行说明读取到了换行符,之后再加上'\0',如果依然不满足个数,则不再读取
6.4 fputs函数
fputs函数的功能在于将一串字符输入到文件中;
格式:int fputs(const char*Buffer , FILE* Stream)
第一个参数是要输入文件的字符串的首地址,第二个参数是文件信息区的地址
#include<stdio.h>
int main() {FILE* p = fopen("test.txt", "w");//打开文件,只写操作,//与fputs函数相匹配fputs("abcdef", p);fclose(p);return 0;
}
6.5 对比两组函数
scanf/fscanf
printf/fprintf
(1)scanf与fscanf函数
对比这两个函数我们发现,在形式上:fscanf 比 scanf多一个参数,在功能上:scanf是能键盘上即标准输入流中读取格式化数据,而fscanf可以在任意流中读取格式化数据,比如文件流。【既然是格式化数据,就需要占位符!】
#include<stdio.h>
#include<assert.h>
int main() {char a = 0;//使用scanf函数,从标准输入流中读取数据scanf("%d", &a);printf("%d\n", a);return 0 ;
#include<stdio.h>
#include<assert.h>
int main() {char a = 0 ;//使用fscanf函数从标准输入流中读取数据fscanf(stdin,"%c", &a);printf("%c\n", a);return 0;
}
#include<stdio.h>
#include<assert.h>
int main() {
//使用fscanf函数从文件流中读取数据FILE* p = fopen("test.txt", "r");assert(p);fscanf(p, "%c", &a);printf("%c\n", a);fclose(p);return 0;
}
// 在数组中的数据能不能算一个流?char a = 0;char arr[10] = { "abcde" };fscanf(arr, "%c", &a);printf("%c\n", a);
结果显示报错!
(2)printf函数与fprintf函数
对比两个函数,在格式上:fprintf函数比printf多一个FILE* stream的参数
在功能上:printf函数是将格式化的数据输出到标准输出流上,
而fprintf函数可以将格式化的数据写到流上!【既然是格式化数据,就需要占位符!】
#include<stdio.h>
#include<assert.h>
int main() {char a = 'a';printf("%c\n", a);return 0;
}
#include<stdio.h>
#include<assert.h>
int main() {//采用fprintf函数向标准输出流输出数据char a = 'b';fprintf(stdout, "%c\n", a);return 0;
}
#include<stdio.h>
#include<assert.h>
int main() {//采用fprintf函数向文件流写入数据char a = 'b';FILE* p = fopen("test.txt", "w");assert(p);fprintf(p, "%c", a);fclose(p);return 0;
}
(3)sscanf函数与sprintf函数
sscanf的功能即从字符数组中得到数据并格式化地赋值到指定的变量中去:
代码:
#include<stdio.h>
#include<assert.h>
struct MyStruct
{char name[10];int age;float i;};int main() {//char arr[100] = { "张三 18 65.5f" };struct MyStruct s1 ;sscanf(arr, "%s %d %f",s1.name,&(s1.age),&(s1.i));printf("1 将数据以格式化的形式打印 :%s %d %f\n", s1.name, s1.age, s1.i);return 0;
}
sprintf函数:
sprintf函数的功能是将得到的数据以格式化的形式储存至字符数组中,本质是将得到的数据以字符串的形式存储起来
代码:
struct MyStruct
{char name[10];int age;float i;};
#include<stdio.h>
int main() {char arr[50] = { 0 };struct MyStruct s2 = { "张三",18,23.6f };sprintf(arr, " %s %d %f", s2.name,s2.age,s2.i);printf("%s\n", arr);return 0;
}
6.6 fread与fwrite函数
(1)fread函数
此函数的几个参数是 void类型的指针 ,元素的大小,元素的个数,可访问到文件流的地址
fread函数的功能是从文件流中读取count个size大小的元素到ptr所指向的空间中去!
fread函数是二进制输入!
#include<stdio.h>
#include<assert.h>
int main() {//fread函数char a[10] = { 0 };FILE* p = fopen("test.txt", "rb");//虽然test.txt是文本文件,而rb是对二进制文件进行读取//,但是依然可以读取文件中的数据,应是以二进制的形式进行读取assert(p);fread(a, 1, 10, p);for (int i = 0; i < 10; i++) {printf("%c\n", a[i]);}fclose(p);return 0;
}
本结果说明,fread函数不会自动为字符串添加’\0’
(2)fwrite函数
本函数的参数与上一个参数大体相同,功能是将从ptr指向空间中,count个size大小的元素写入到文件流中去,是以二进制的形式写入!
#include<stdio.h>
#include<assert.h>
int main() {//fwriteFILE* p = fopen("test.txt", "wb");//以二进制的方式写入文件assert(p);char arr[10] = { "abcdefghi" };fwrite(arr, 1, 10, p);fclose(p);return 0;
}
因为写入的格式是二进制,所以在文本文件中显示为乱码!
7 文件的随机读写
在前面的读写函数中,读与写的操作的位置是从文件的起始地址顺序往后的,
而当我们需要读写数据在文件中特定位置时,该怎么办呢?
7.1 fseek
形参origin是指针的位置:
如图:SEEK_SET是文件的起始地址SEEK_CUR是文件指针当前所处的地址SEEK_END是文件末尾的地址
本函数的功能是使文件指针指向 origin位置的偏移量处
要指向的字符在起始位置的左边,则偏移量>0,在左边,则偏移量<0;
代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
int main() {FILE* p = fopen("test4.txt", "r");assert(p);char c = fgetc(p);printf("%c\n", c);fseek(p, 4, SEEK_CUR); //以当前指针位置为起始位置,做偏移量!c = fgetc(p);printf("%c\n", c);fclose(p);return 0;
}
7.2 ftell
long int ftell(FILE * Stream)
本函数的功能是返回文件指针相对于文件指针原始位置的偏移量
//如果新建一个文本文件则解决了这个问题FILE* p = fopen("test4.txt", "r");assert(p);char c = fgetc(p);printf("%c\n", c);fseek(p, 4, SEEK_CUR);c = fgetc(p);printf("%c\n", c);int a = ftell(p);printf("%d\n", a);fclose(p);
7.3 rewind
本函数的功能:使文件指针返回原始位置
void rewind (FILE * Stream)
#include<stdio.h>
int main(){
FILE* p = fopen("test4.txt", "r");
assert(p);
char c = fgetc(p);
printf("%c\n", c);
fseek(p, 4, SEEK_CUR);
c = fgetc(p);
printf("%c\n", c);
//计算此时文件指针位置对于原始位置的偏移量int a = ftell(p);printf("%d\n", a);
//将文件指针返回原始位置rewind(p);
// 测试:看其是否回到原始位置:c = fgetc(p);printf("原始位置的字符:%c\n", c);
fclose(p);
p = NULL;
return 0;
}
8 文件读取结束的判定
8.1 判断文件是否读取结束
1 在文本文件中判断文件读取结束的标志,看其返回值是否为EOF(fgetc)或者NULL(fgets)
2 在二进制文件中判断文件读取结束的标志是,返回值小于要读取字符的个数:
例:fread函数的返回值小于要读取元素的个数。
8.2 feof函数
文件读取结束的原因有两种:一种是读到文件末尾结束,
另一种是遇到错误停止读取.我们用feof函数判断是否是因为读到文件末尾而结束
用ferror函数判断是否是因为遇到错误而停止读取打开的一个流时,这个流上有两个标记值
一个标记值是是否遇到文件末尾
一个标记值是是否读取遇到错误
遇到哪种情况,哪种情况的标记值则被设置
我们用相应的函数去判断其标记值是否被设置
feof:
代码:
#include<stdio.h>
int main(){FILE* p = fopen("test5.txt", "r");if (p == NULL) {perror("p");return 1;}while ((fgetc(p)) != EOF) {char ch = fgetc(p);}//如果是遇到文件末尾结束, feof判断相应的标记值则返回不等于0的数if (feof(p)) {printf("是因为遇到文件末尾结束!");}//如果是因为读取发生错误,导致文件结束else if (ferror(p)) {printf("是因为读取错误,读取结束!");}fclose(p);p = NULL;return 0;
}
9 文件缓冲区
ANSIC采用文件缓冲系统处理数据文件
文件缓冲系统指在内存中为程序中正在使用的文件开辟一块文件缓冲区,
程序在向硬盘中输入数据时,先将数据存放到缓冲区中,装满缓冲区后,
再送到硬盘上。
硬盘向计算机中读入数据,也是先将数据存放到缓冲区中,然后再从缓冲区移
交至程序。
缓冲区的大小由c编译系统决定!
fclose函数是将缓冲区刷新至硬盘的一个操作!