--程序的数据存储在电脑的内存中,若程序退出,内存回收,数据会丢失,再次运行程序什么也没有。所以,数据存储在文件中,就可以持久保存。
--什么是文件?:磁盘(硬盘)上的文件就是文件。在程序设计中,文件一般分为两种:程序文件、数据文件(文件功能不同)。
包括源程序文件(后缀为.c), 目标文件(windows环境后缀为.obj), 可执行程序(windows环境后缀为.exe);
文件的内容不一定是程序,而是程序运行时读写的数据。比如程序运行需要从中读取数据的文件,或者输出内容的文件。
--主要介绍数据文件,前面学习所处理数据的输入输出是以终端为对象,即从终端键盘输入数据,屏幕显示结果。 --有时会将信息输出到硬盘上,需要时再读取到内存中使用,这里处理的是磁盘上的文件。

--文件有自己的标识,便于进行识别、利用。
文件名包含3部分:文件路径+文件名主干+文件后缀
如:C:\code\test.txt --C:\code\ ->文件路径;test ->文件名主干;.txt ->文件后缀;
--由数据的组织形式,数据文件被分为文本文件和二进制文件。
(外存:硬盘、U盘...)
--数据在文件中怎样存储?

--代码演示:(相关操作会在后面介绍)
int main()
{
int a = 10000;
FILE* pf = fopen("data.txt", "wb");//打开文件的操作
if (pf == NULL)
{
perror("fopen");
return 1;
}
fwrite(&a, 4, 1, pf);//将10000以二进制形式写到文件中
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}--想要观察二进制,在vs上打开:
--运行结束会创建一个文件->

--在vs上添加test文本文件、打开:


--程序数据需要输出到外部设备,也要获取数据,不同外部设备的输入输出操作不同,为方便进行操作,抽象出了->"流"的概念,将”流“想成流淌字符的河。 --C语言对文件、画面、键盘等数据的输入输出操作都是由”流“完成的。一般,向”流“里写数据或读数据,都要打开”流“,在操作。

--为什么从键盘输入、屏幕输出,我们没有打开”流“? --C语言在程序启动时默认打开3个”流“:
--3个流的类型是 -> FILE * ,通常称为文件指针。 C语言中,就是通过 FILE* 的文件指针来维护流的各种操作的。
--缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
--每个被使用的文件都在内存中开辟相应文件信息区,存放文件相关信息(文件名、文件状态、文件当前位置...)。这些信息保存在结构体变量中,类型由系统声明的取名为:FILE。
--vs2013中提供的 stdio.h 头文件中有以下的文件类型申明:
struct _iobuf
{
char* _ptr;
int _cnt;
char* _base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char* _tmpfname;
};
typedef struct _iobuf FILE;--不同编译器FILE类型包含内容不完全相同,每次打开文件,系统会自动创建FILE结构的变量,并填充信息,不需要我们关心。
--一般是通过一个FILE指针来维护结构变量:
FILE* pf; //文件指针变量--定义pf是⼀个指向FILE类型数据的指针变量。使pf指向某个文件的文件信息区(结构体变量)。通过该文件信息区中的信息能够访问文件。即—>通过文件指针变量能够间接找到与它关联的文件。

--对于文件,读写前要打开,使用结束要关闭:使用 fopen 函数打开文件, fclose 关闭文件。
--编写程序的时,打开文件同时,会返回⼀个FILE*的指针变量指向该文件,相当于建立了指针和文件的关系。
--头文件:<stdio.h>
FILE* fopen (const char* filename, const char* mode);功能: 用来打开参数 filename 所指定文件,同时将其和一个流进行关联,后续对流的操作是通关 fopen 函数返回的指针来维护的。具体对流的操作是通过参数mode指定的; 参数: --filename:要打开的文件的路径和名称。可以是相对路径(如
"data.txt"),也可以是绝对路径(如"C:\\Users\\data.txt"); (注意:在字符串中使用'\'时,需要用转义字符,写两个'\\'。) --mode:文件的打开模式。是一个字符串,指定文件是用于读、写、追加,以及是以文本模式还是二进制模式打开。这是 fopen 的核心参数,决定了能对文件进行何种操作; 返回值:一个FILE*类型(文件指针)。
FILE 结构的指针。可以用于后续操作中标识对应的流;
--mode--文件操作方式:
文件使用方式 | 含义 | 若指定文件不存在 |
|---|---|---|
"r"(只读) | 为了输入数据,打开已经存在的文本文件 | 出错 |
"w"(只写) | 为了输出数据,打开文本文件 | 建立新的文件 |
“a”(追加) | 向文本文件尾添加数据 | 建立新的文件 |
“rb”(只读) | 为了输入数据,打开二进制文件 | 出错 |
"wb"(只写) | 为了输出数据,打开二进制文件 | 建立新的文件 |
“ab”(追加) | 向二进制文件尾添加数据 | 建立新的文件 |
“r+”(读写) | 为了读和写,打开文本文件 | 出错 |
“w+”(读写) | 为了读和写 建立新的文件 | 建立新的文件 |
“a+”(读写) | 打开文件,在文件尾进行读写 | 建立新的文件 |
“rb+”(读写) | 为了读和写打开二进制文件 | 出错 |
“wb+”(读 写) | 为了读和写,新建新的二进制文件 | 建立新的文件 |
“ab+”(读 写) | 打开二进制文件,在文件尾进行读和写 | 建立新的文件 |
--演示几种方式,体会函数使用:
int main()
{
//打开文件,"w"--不存在,创建文件
FILE* pf = fopen("data.txt", "w");
//这里的路径为相对路径,表示在当前工程目录下的data.txt文件
if (pf == NULL)
{
perror("fopen");
return 1;
}
//后续读取文件操作
//……
//关闭文件
//……
return 0;
}
./ -- 表示当前目录; ../ --表示当前目录的上一级目录
int main()
{
//打开文件,"r"--不存在,出错
FILE* pf = fopen("./../data.txt", "r");//只读打开,看是否存在
//这里的路径为相对路径,表示在当前工程目录上一级目录下的data.txt文件
//已经将文件剪切到上一级目录中
if (pf == NULL)
{
perror("fopen");
return 1;
}
//后续读取文件操作
//……
//关闭文件
//……
return 0;
}
--结果正常运行int main()
{
//打开文件,"r"--不存在,出错
FILE* pf = fopen("./../../data.txt", "r");//只读打开,看是否存在
//这里的路径为相对路径,表示在当前工目录上一级目录的上一级目录下的data.txt文件
//已经将文件剪切到相应目录中
if (pf == NULL)
{
perror("fopen");
return 1;
}
//后续读取文件操作
//……
//关闭文件
//……
return 0;
}
--结果正常运行int main()
{
//打开文件,"r"--不存在,出错
FILE* pf = fopen("./../../test/test2/data.txt", "r");//只读打开,看是否存在
//这里的路径为相对路径,表示在当前工目录上二级目录下的test文件下test2文件的data.txt文件
//已经将文件剪切到相应目录中
if (pf == NULL)
{
perror("fopen");
return 1;
}
//后续读取文件操作
//……
//关闭文件
//……
return 0;
}int main()
{
//打开文件,"r"--不存在,出错
FILE* pf = fopen("C:\\Users\\Tian\\Desktop\\data.txt", "r");//只读打开,看是否存在
//这里的路径为绝对路径,表示在桌面上的data.txt文件
//已经将文件剪切到相应目录中
if (pf == NULL)
{
perror("fopen");
return 1;
}
//后续读取文件操作
//……
//关闭文件
//……
return 0;
}int fclose(FILE* stream);功能: --关闭参数 stream 关联的文件,取消关联关系。与该流关联的所有内部缓冲区均会解除关联并刷新:任何未写入的输出缓冲区内容将被写入,任何未读取的输入缓冲区内容将被丢弃; 参数: --stream: 这是一个指向
FILE对象的指针,该对象标识要被关闭的流。指针通常由fopen等函数成功调用后返回的; 返回值:成功--返回0、失败--返回EOF;
--注意:
--必须检查返回值; --一个指针关一次:确保每个由
fopen打开的FILE*指针都有且仅有一次对应的fclose调用。关闭一个已经关闭的指针或一个空指针会导致未定义行为(程序崩溃); --作用域问题: 确保指针失效(如局部变量离开作用域)之前关闭文件。否则永远失去关闭机会,导致资源泄漏(类似于动态内存管理函数的释放、置空)。
--代码演示函数功能:
nt main()
{
//打开文件,"w"--不存在,创建文件
FILE* pf = fopen("data.txt", "w");
//这里的路径为相对路径,表示在当前工程目录下的data.txt文件
if (pf == NULL)
{
perror("fopen");
return 1;
}
//后续读取文件操作
//……
//关闭文件
fclose(pf);
pf == NULL;
return 0;
}--文件读写会涉及到以下函数:
函数名 | 功能 | 适用于 |
|---|---|---|
fgetc | 从输入流读取一个字符 | 所有输入流 |
fputc | 向输出流写入一个字符 | 所有输出流 |
fgets | 从输出流读取一个字符 | 所有输入流 |
fputs | 向输出流写入一个字符 | 所有输出流 |
fscanf | 从输入流读取带有格式的数据 | 所有输入流 |
fprintf | 向输出流写入带有格式的数据据 | 所有输出流 |
fread | 从输入流读取一块数据 | 文件输入流 |
fwrite | 从输出流写入一块数据 | 所有文件流 |
int fputc(int characer, FILE* stream);功能:将参数 character 指定的字符写入 stream 指向的输出流,通常用于向文件或标准输出流写入。写入字符之后,会调整指示器。字符会被写入流内部位置指示器当前指向的位置,随后该指示器自动向前移动⼀个位置; 参数: --character: 要写入的字符。虽然参数类型是 int ,但函数内部会将其转换为
unsigned char后写入; --stream : 是⼀个FILE*类型的指针,指向了输出流(通常是文件流或stdout); 返回值:--成功: 返回写入的字符(以int形式返回);--失败/错误: 返回EOF(End Of File,通常是 -1)。
int main()
{
//打开文件
FILE* ps = fopen("data.txt", "w");
//判断返回值
if (ps == NULL)
{
perror("fopen");
return 1;
}
//写数据--文件流
fputc('a', ps);
fputc('b', ps);
fputc('c', ps);
//关闭文件
fclose(ps);
ps = NULL;
return 0;
}
//循环输出一系列字符
int main()
{
//打开文件
FILE* ps = fopen("data.txt", "w");
if (ps == NULL)
{
perror("fopen");
return 1;
}
for (int i = 'a'; i <= 'z'; i++)
{
fputc(i, ps);
}
fclose(ps);
ps = NULL;
return 0;
}int main()
{
//写数据--标准输出流_屏幕
fputc('a', stdout);
fputc('b', stdout);
fputc('c', stdout);
return 0;
}--打开相应文件进行查看输出:

int fgetc(FILE* stream);功能:从参数 stream 指向的流中读取一个字符。函数返回的是文件指示器当前指向的字符,读取这个字符之后,文件指示器自动前进道下⼀个字符; 参数: --stream: FILE* 类型的文件指针,可以是 stdin、其他输入流的指针。是 stdin 就从标准输入流读取数据。是文件流指针,就从文件读取数据; 返回值: --成功: 返回读取到的字符(以
unsigned char转换后的int形式返回)。 --失败/到达文件末尾: 返回EOF(设置错误指示器( ferror )/文件结束指示器( feof ))。
int main()
{
//打开文件
FILE* ps = fopen("data.txt", "r");
//检查返回值
if (ps == NULL)
{
perror("fgets");
return 1;
}
//在前面fputc函数操作的基础上,对文件内容进行读取
//读取数据--文件流
for (int i = 0; i < 10; i++)
{
//读取
int c = fgetc(ps);
//将读取的数据打印在屏幕
fputc(c, stdout);
}
//关闭文件
fclose(ps);
ps = NULL;
return 0;
}
输出:abcdefghijint main()
{
for (int i = 0; i < 4;i++)
{
//读数据--标准输入流
int c = fgetc(stdin);
fputc(c, stdout);
}
return 0;
}--这里建议一次性输入完数据后,再回车输出!
int feof(FILE *stream); // 检查是否到达文件末尾
int ferror(FILE *stream); // 检查是否发生文件错误--测试feof函数
int main()
{
FILE* ps = fopen("data.txt", "r");
if (ps == NULL)
{
perror("fopen");
return 1;
}
//读文件-文件:abcdef
int i = 0;
for (i = 0;i < 10;i++)
{
int c = fgetc(ps);
if (c == EOF)
{
if (feof(ps))
{
printf("遇到文件末尾了\n");//到f就没有了
}
else if (ferror(ps))
{
printf("读取发生错误\n");
}
}
else
{
fputc(c, stdout);
}
}
fclose(ps);
ps = NULL;
return 0;
}int fputs(const char* str, FILE* stream);功能:将参数 str 指向的字符串写入到参数 stream 指定的流中(不含结尾空字符 \0 ),用于文件流或标准输出(stdout); 参数: --str : 指针,指向要写入的字符串(必须以 \0 结尾); --stream : FILE* 的指针,指向要写入字符串的流; 返回值:--成功,返回非负整数;--失败,返回 EOF设置流的错误指示器,用 ferror() 检查原因;
int main()
{
//打开文件
FILE* ps = fopen("data.txt", "w");
//判断
if (ps == NULL)
{
perror("fopen");
return 1;
}
//写数据
fputs("abcd", ps);
//关闭文件
fclose(ps);
ps = NULL;
return 0;
}char* fgets(char* str, int num, FILE* stream);功能:从 stream 指定输入流中读取字符串,读取到换行符、文件末尾(EOF)或到指定字符数(含结尾空字符 \0 ),然后将读取到的字符串存储到str指向的空间; 参数: --str :指向字符数组的指针,指向的空间存储读取的字符串。 --num :最大读取字符数(包含结尾的 \0 ,实际最多读取 num-1 个字符)。 --stream :输入流的文件指针(文件流或 stdin )。 返回值: --成功,返回 str 指针; --在读取字符时遇到文件末尾,设置文件结束指示器,返回 NULL ,通过 feof()检测; --发生读取错误,设置流错误指示器,返回 NULL,通过 ferror() 检测。
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char arr[20] = "xxxxxxxxxxxxxxxx";
fgets(arr, 10, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
相关系列回顾:
#C语言——学习攻略:攻克 动态内存分配、柔性数组,根本不在话下!
结语:本篇文章到此结束,对于C语言相关知识大家要多次回顾,如果这篇文章对你的学习有帮助的话,欢迎一起讨论学习,你这么帅、这么美给个三连吧~~~