1.为什么要用到文件?怎样数据才能持久化?
保存在内存中的数不安全(一次断电,忘记保存,不用了还给系统)
持久化:保存在硬盘上(放在文件中)
什么是文件?文件分为程序文件和数据文件
程序文件是什么呢?
在windows环境下。.c源程序文件 / .obj目标文件 / .exe执行文件,这些就是程序文件
数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
如图:
编辑.c源程序文件(test.c)可以对A文件(数据文件)进行读写操作进行
2.文件名
一个文件有唯一的文件标识,以便于用户的识别和引用
文件名包含三部分:文件路径 + 文件主干名 + 文件后缀
例: c:\code\heyerous\test.txt
3.文件的打开 or 关闭
->1. 文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称:文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如:文件的名字,文件的状态,文件当前的位置等)。这些信息是保存在一个结构体变量中的,该结构体类型是由系统声明的,取名:FILE
如图:
注: 每打开一个文件就会产生一个文件信息区,每一个文件信息区都需要一个文件指针来指向它。不同的C编译器的FILE类型包含的内容不完全相同,但大同小异
一般都是通过FILE的指针来维护这个FILE结构的变量,这样更方便
创建一个FILE指针的变量:
FILE* pf //文件指针变量
->2. 文件的打开和关闭
文件在读写之前先打开文件,在使用结束之后应该关闭文件
ANSIC(标准C)规定使用fopen函数(打开成功放回文件地址,失败则返回NULL)来打开文件,fclose函数(打开成功返回0,打开失败返回EOF(-1))来关闭文件
fopen和fclose的使用示例:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("C:\\code\\heyerous\\test.txt", "r");
//判断文件是否打开成功
if(pf == NULL)
{
perror("fopen");
return 1;
}
//读写文件
//....
//关闭文件
fclose(pf);
pf = NULL;
}
运行结果:
打开成功说明该路径存在这个文件
上面所示代码是用的绝对路径打开的文件 C:\code\heyerous\test.txt
绝对路径
文件的准确位置
相对路径
相对路径的理解,可以认为是程序和文件的相对位置,比如说和当前.c文件在同文件夹中,或者文件在.c文件的上级路径中 (以.c文件为例)
就可以看到该文本文件(test.txt)了
将文件放在上级目录程序依旧可以运行
除了这两个操作以外还其他的操作:
如:“../test.txt” 上级目录 “./text.txt” 当前目录 "././test,txt" 上一级目录的上一级目录
注: 创建文件的时候看看后缀名(扩展名)
4.怎么读写?
1.文件的顺序读写
fputc
->1. fputc函数 //向文件中写一个字符
int fputc(int charactor, FILE* stream);
(1). FILE* stream 需要写入的文件的指针
(2). int charactor 需要向文件中写的字符
->2. fputc函数的使用
puts按顺序向文件中写内容
#include <stdio.h>
int main()
{
//用"w"会将我们上次写入的内容全部覆盖掉
FILE* pf = fopen("C:\\code\\heyerous\\test.txt", "w");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//方法1
/*fputc('z', pf);
fputc('y', pf);
fputc('x', pf);
fputc('6', pf);*/
//方法2:使用for循环
for (char ch = 'A'; ch <= 'Z'; ch++)
{
fputc(ch, pf);
}
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
向文件写一个字符,不是字符串!!!
fgetc
->1. fgetc函数 //向文件中读一个字符
int fgetc(FILE* stream);
(1). 按顺序向文件读字符
(2). FILE* stream 需要读文件的指针
->2. fgetc函数的使用
#include <stdio.h>
int main()
{
FILE* pf = fopen("C:\\code\\heyerous\\test.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
int ch = fgetc(pf);
printf("%c", ch);
ch = fgetc(pf);
printf("%c", ch);
ch = fgetc(pf);
printf("%c", ch);
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
fputs
->1. fputs函数 //向文件中写入一行内容
int fputs( const char *string, FILE *stream );
(1). const char *string 需要写入文件的内容
(2). FILE *stream 目标文件
->2. fputs函数的使用
这里我们换用相对路径
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
if(NULL == pf)
{
perror("fopen");
return 1;
}
fputs("hello world", pf);
fclose(pf);
pf ==NULL;
return 0;
}
运行结果:
fgets
->1. fgets函数 //向文件读一行内容
char *fgets( char *string, int n, FILE *stream );
(1). char *string 将读到的内容放到另一个空间中
(2). int n 个数限制
(3). FILE *stream 需要读的文件的指针
->2. fgets函数的使用
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
char buf[25] = { 0 };
fgets(buf, 12, pf);
printf(buf);
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
注: fgets读文件时会留一个空间存放\0,所以我们想要将hello world全部都读出来得限制12个个数(包括\0)
如果buf空间比文件中一行大,即:fgets只读一行的内容
1.buf > num, 直接读到末尾结束,第二行不管,只管第一行得内容
2.buf < num,只读num数量的字符
fprintf
->1. fprintf函数 //将格式化的内容写到文件中
int fprintf(FILE* stream, const char*format, ...);
(1). FILE* stream 需要写入的文件的指针
(2). const char*format 数据流:格式化字符串等各种信息
(3). ... 可变参数列表可接收多个参数
->2. fprintf函数的使用
#include <stdio.h>
typedef struct S
{
char name[20];
int age;
float score;
}S;
int main()
{
FILE* pf = fopen("test.txt", "w");
if(FILE == NULL)
{
perror("fopen");
reutrn 1;
}
s stu = {"zhangsan", 20, 89.5};
fprintf(pf, "%s %d %f", stu.name, stu.age, stu.score);
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
fscanf
->1. fscanf函数 //将格式化的内容写到文件中
int fscanf(FILE* stream, const char*format, ...);
(1). FILE* stream 需要写入的文件的指针
(2). const char*format 数据流:格式化字符串等各种信息
(3). ... 可变参数列表可接收多个参数
->2. fprintf函数的使用
#include <stdio.h>
typedef struct S
{
char name[20];
int age;
float score;
}S;
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
S stu = { 0 };
fscanf(pf, "%s %d %f", stu.name, &(stu.age), &(stu.score));
printf("%s %d %f", stu.name, stu.age, stu.score);
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
以上函数操作时写入文件都看得懂,因为是文本信息(适用于所有输入输出流)
我们将数据传给输出设备如果这些设备的读写的都不相同,程序要使用的话都需要了解,那对一个程序来说太复杂了,所以为了解决这个问题C语言引入了流(stream)
将数据输出到流上,然后通过流输出到设备上
任何一个C语言程序运行的时候:默认打开3个流
1.stdin - 标准输出(键盘)
2.stdout - 标准输出(屏幕)
3.stderr - 标准错误(屏幕)
这三个流的类型都是FILE*
例1:
int ch = fgets(stdin)
fputc(ch, stdout)
从键盘上读一个字符然后写到屏幕上
例2:
#include <stdio.h>
typedef struct stu
{
char name[20];
int age;
float score;
}stu;
int main()
{
stu p = { 0 };
fscanf(stdin, "%s %d %f", p.name, &(p.age), &(p.score));
fprintf(stdout, "%s %d %f", p.name, p.age, p.score);
return 0;
}
运行结果:
fwrite
->1. fwrite函数 //以二进制的方式向文件中写入
size_t fwrite(const void *ptr, size_t size, size_t count, FILE* stream)
(1). const void *ptr 需要写入数据的地址
(2). size_t size 每个元素的大小
(3). size_t count 需要写入文件的元素个数
(4). FILE *stream 该文件的指针
->2. fwrite函数的使用
#include <stdio.h>
typedef struct stu
{
char name[20];
int age;
float score;
}stu;
int main()
{
FILE *pf = fopen("test.txt", "wb"); //wb以二进制的方式写
if(NULL ==pf)
{
perror("fopen");
return 1;
}
stu p = {"zhangsan", 20, 85.5};
fwrite(&p, sizeof(stu), 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
从运行结果来看,确实是以二进制的形式写进去的
fread
->1. fread函数 //以二进制的方式读文件
size_t fwrite(void *ptr, size_t size, size_t count, FILE* stream)
(1). void *ptr 存放向文件读到的数据
(2). size_t size 每个元素的大小
(3). size_t count 需要写入文件的元素个数
(4). FILE *stream 该文件的指针
->2. fread函数的使用
#include <stdio.h>
typedef struct stu
{
char name[20];
int age;
float score;
}stu;
int main()
{
FILE *pf = fopen("test.txt", "rb"); //以二进制的形式读
if(NULL == pf)
{
perror("fopen");
return 1;
}
stu s = { 0 };
fread(&s, sizeof(stu), 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
可以看到fread将文件的内容放入了结构体变量s上
sprintf
->1. sprintf函数 //把格式化的数据,以字符串的格式打印存放在一个字符串中
int sprintf(const char* s, const char* format, ...);
(1). const char* s 存放数据的地址
(2). const* format 数据流:格式化字符串等各种信息
(3). ... 可变参数列表,可以放多个参数
->2. sprintf函数的使用
#incldue <stdio.h>
typedef struct tmp
{
char name[20];
int age;
float score;
}tmp;
int main()
{
tmp s = {"zhangsan", 20, 86.5};
char buf[30] = { 0 };
sprintf(buf, "%s%d%f", s.name, s.age, s.score);
printf(buf);
return 0;
}
运行结果:
sscanf
->1. sscanf函数 //从一个字符串中,还原出一个格式化的数据
int sscanf(const char* s, const char* format, ...);
(1). const char* s 存放数据的地址
(2). const* format 数据流:格式化字符串等各种信息
(3). ... 可变参数列表,可以放多个参数
->2. sscanf函数的使用
#include <stdio.h>
typedef struct tmp
{
char name[20];
int age;
float score;
}tmp;
int main()
{
tmp s = { 0 };
char buf[30] = { "zhangsan 20 86.5" };
sscanf(buf, "%s%d%f", s.name, &(s.age), &(s.score));
printf("%s %d %f", s.name, s.age, s.score);
return 0;
}
运行结果:
scanf - 从键盘(stdin)上读取格式化的数据
printf - 把数据输出到屏幕(stdout)上
fprintf - 针对所有输入流的格式化的输入函数:stdin,打开的文件
fscanf - 针对所有的输出流的格式化的输出函数:stdout,打开的文件
5.文件的随机读写
fseek
->1. fseek函数 //调整光标位置
int fseek(FILE* stream, long int offset, int orgin);
(1). FILE* stream 目标文件
(2). long int offset 偏移量(往哪偏移)
(3). int orgin 从哪开始调整
SEEK_SET 文件开始的位置
SEEK_CUR 光标现指向的位置
SEEK_END 文件末尾位置
->2. fseek函数的使用
例:
文本文件中的内容
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
int ch = fgetc(pf);
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
//此时光标在字符c的后面d的前面,如果继续往下读肯定回到d,但是我们调整一下去读b
fseek(pf, 1, SEEK_SET);// 或者fseek(pf, -2, SEEK_SUR);
//这时往下读就会读到b
ch = fgetc(pf);
printf("%c", ch);
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
ftell
->1. ftell函数 //返回文件指针相对于起始位置的偏移量
long int fseek(FILE* stream);
(1). FILE* stream 目标文件
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
int ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
int pos = ftell(pf);
printf("%d", pos);
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
->2. rewind函数的使用
#include <stdio.h>
int main()
{
FILE *pf = fopen("test.txt", "r");
if(NULL == pf)
{
perror("fopen");
return 1;
}
int ch = fgetc(pf);
printf("%c\n", ch);
rewind(pf);
ch = fgetc(pf);
printf("%c", ch);
fclose(pf);
pf = NULL;
return 0;
}
rewind
->1. rewind函数 //使指针回到初始位置
void rewind( FILE *stream );
(1). FILE *stream 需要调整文件的指针
->2. rewind函数的使用
#include <stdio.h>
int main()
{
FILE *pf = fopen("test.txt", "r");
if(NULL == pf)
{
perror("fopen");
return 1;
}
int ch = fgetc(pf);
printf("%c\n", ch);
rewind(pf);
ch = fgetc(pf);
printf("%c", ch);
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
6.文本文件和二进制文件
数据文件被称为文本文件或者二进制文件
数据在内存中以二进制的形式存储,如果不加转换的输出到外存(文件),就是二进制文件
在外存上以ASCII码的形式储存,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件
测试代码:
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "wb");
if (NULL == pf)
{
perror("fopen");
return 1;
}
int a = 10000;
fwrite(&a, 4, 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
可以看到是二进制文件
我们来看看它是否是如我们所说的二进制一样
然后添加这个文件
添加进来之后右击点击这个文件,选择打开方式,点击二进制编辑器
可以看到是以十六进制的方式写的,因为这样更加方便表达展现,而且字节存储方式是小端
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
if (NULL == pf)
{
perror("fopen");
return 1;
}
int a = 10000;
fprintf(pf, "%d", a);
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
文本文件
7.文件读取结束的判定
feof 和 ferror
int ferror( FILE *stream );
int feof( FILE *stream );
(1). FILE *stream - 需要判断的文件
feof - 文件正常读取遇到了结束标志而结束就返回真
ferror - 返回真,说明文件在读取过程中出错了而结束
用这两个函数的前提是文件已经读取结束了才可以去使用这两个函数
用于文件读取结束的时候,判断是读取失败,还是到文件末尾结束
1.文本文件读取是否结束,判断返回值是否为EOF(fgetc),NULL(fgets)
例如:
- fgetc判断是否为EOF
- fgets判断是否为NULL
文本文件的例子:
#include <stdio.h>
#define EXIT_FAILURE 1
int main()
{
FILE *pf = fopen("test,txt", "r");
if(NULL == pf)
{
perror("file opening failed");
return EXIT_FAILURE;
}
int ch;
while((ch = fgetc(pf)) != EOF) //fgetc的返回值是int类型
{
putchar(ch);
}
//判断是什么原因结束的
//判断文件是否是读取时遇到错误结束
if(ferror(pf))
puts("I/O error when reading"); //I/O读取文件
else if(feof(pf))
puts("End of file reached successfully");
fclose(pf);
pf = NULL;
return 0;
}
2.二进制文件的读取结束判断,判断返回值是否小于实际要读的个数
例如:
- fread 判断返回值是否小于实际要读的个数
二进制文件的例子:
#include <stdio.h>
enum //没有枚举名作用和define差不多,文本替换
{
size = 5,
};
int main()
{
FILE* pf = fopen("test.txt", "wb");
if (NULL == pf)
{
perror("FILE fopening failed");
return 1;
}
int a[size] = { 1, 2, 3, 4, 5, };
fwrite(a, sizeof * a, size, pf);
fclose(pf);
pf = NULL;
return 0;
}
//以上代码是将数组a中的容写进文件test.txt
#include <stdio.h>
enum //没有枚举名作用和define差不多,文本替换
{
size = 5,
};
int main()
{
FILE* pf = fopen("test.txt", "rb");
int a[size] = { 0 };
size_t ret_code = fread(a, sizeof *a, size, pf);
if (ret_code == size)
{
perror("FILE fopening failed");
return 1;
}
else if(feof(pf))
{
puts("Error reading test.txt: unexpected end of file\n");
}
else if(ferror(pf))
{
puts("Error reading test.txt");
}
fclose(pf);
pf = NULL;
return 0;
}
8.文件缓冲区
我们将数据冲数据区输出/输入到磁盘(文件)的时候不是直接输入/输出过去而是先经过缓冲区,通过缓冲区进入磁盘的,但是我们放到缓冲区的数据不会直接进入到磁盘,要怎么才能将数据放到磁盘了
将数据放入磁盘的条件:
(1). 缓冲区放满的时候,缓冲区会自动将数据放入磁盘
(2). 主动刷新缓冲区的时候,缓冲区会将数放入磁盘
(3). 使用fclose关闭文件时,也会主动刷新缓冲区的将数据写到硬盘中然后关闭文件
注:
因为有缓冲区的存在,C语言在操作文件的时候。需要做刷新缓冲区或者在文件操作结束的时候关闭文件 很重要!!!(保存信息)