目录
4.2对比一组函数(scanf,fscanf,sscanf——printf,fprintf,sprintf)
1.有必要使用文件吗?
当程序运行之后,此时数据保存在内存中,当退出运行程序的时候,产生的数据就会在内存上消失,为了保存运行时的要储存的数据,以便于下一次运行程序的时候能够调用上一次运行程序时产生的数据,我们采用了文件操作。
2.什么是文件?
磁盘上的文件就是文件,文件操作就是去生成或者调用这些文件,这些文件一般可以分为,程序文件和数据文件。
2.1程序文件
程序文件包括:
2.2数据文件
2.3文件名
一个文件要有 唯一的一个文件标识(文件名),以便用户识别和引用
文件名包含三个部分:
1.文件路径:C:\code
2.文件名主干:test
3.文件后缀:.txt
例如:C:\code\test.txt
3.文件的打开和关闭
3.1文件指针
存中开辟了一个相应的 文件信息区 ,用来存放文件的相关信息(如文件的名字,文件状态及文件当
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就是一个文件指针类型的变量

3.2文件打开和关闭的具体方法
去访问一个文件的时候,首先要打开这个文件(fopen),其次要对这些文件进行读或者写的操作,最后会试着去关闭这个文件(fclose)
接下来详细探讨一下如何对文件进行打开、读写和关闭的操作
1.fopen
FILE * fopen ( const char * filename, const char * mode );
函数的含义:
filename:fopen打开的文件名字
mode:fopen打开的文件的方式(可读还是可写)
返回值:如果打开成功返回的是该文件的文件指针,如果打开失败返回NULL指针
mode分为以下几种:
“r” (只读)
|
为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w” (只写)
|
为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a” (追加)
|
向文本文件尾添加数据
|
建立一个新的文件
|
“rb” (只读)
|
为了输入数据,打开一个二进制文件
|
出错
|
“wb” (只写)
|
为了输出数据,打开一个二进制文件
|
建立一个新的文件
|
“ab” (追加)
|
向一个二进制文件尾添加数据
|
出错
|
“r+” (读写)
|
为了读和写,打开一个文本文件
|
出错
|
“w+” (读写)
|
为了读和写,建议一个新的文件
|
建立一个新的文件
|
“a+” (读写)
|
打开一个文件,在文件尾进行读写
|
建立一个新的文件
|
“rb+” (读写)
|
为了读和写打开一个二进制文件
|
出错 |
“wb+” (读写)
|
为了读和写,新建一个新的二进制文件
|
建立一个新的文件 |
“ab+” (读写)
|
打开一个二进制文件,在文件尾进行读和写
|
建立一个新的文件 |
2.fclose
int fclose ( FILE * stream )
函数含义:关闭一个文件
stream:想要关闭的文件的指针类型
返回值:如果关闭文件成功返回0,如果关闭文件失败返回EOF;
注意:用fopen打开的文件一定要用fclose关闭,不然会出现很多意料之外的情况,比如,文件信息没有存在磁盘上,或者其他进程想要读写此文件出现读写冲突。
看代码:
int main()
{
//打开文件
//文件的两种路径方式:
//绝对路径
FILE* ff = fopen("D:\\C_C++_CODE\\test_-c\\10_21\\myfile.txt", "w");
//相对路径
FILE* ff = fopen("myfile.txt", "w");
if (ff != NULL)
{
//进行我想要的操作
}
//关闭文件:
fclose(ff);
return 0;
}
当以绝对路径打开的文件:
以相对路径打开的文件:
4.文件的读写
之前在程序设计的时候,我们用的输入和输出一般是scantf和printf,分别是在键盘上输入(读),在频幕上打印(写)出来,那没有没有办法,我想把我程序产生的数据存在文件上呢?并且能够进行文件的读写操作呢?其实是可以的。
看图:
4.1顺序读写
顺序读写的一组函数:
函数功能 | 函数名 | 适用于 |
字符输入函数(读)
|
fgetc
|
所有输入流
|
字符输出函数(写)
|
fputc
|
所有输出流
|
文本行输入函数(读)
|
fgets
|
所有输入流
|
文本行输出函数(写)
|
fputs
|
所有输出流
|
格式化输入函数(读)
|
fscanf
|
所有输入流
|
格式化输出函数(写)
|
fprintf
|
所有输出流
|
二进制输入(读)
|
fread
|
文件
|
二进制输出(写)
|
fwrite
|
文件
|
探究顺序读写的一些函数:
1.fgetc
int fgetc ( FILE * stream );
函数功能:
fgetc 的含义是是file get char(从文件中获取一个字符)(一次读一个字符)
我们从输入流(stream——想读文件的文件指针)中获取下一个字符,并将文件指针加1。
返回值:如果读取成功,返回所得到的字符的ASIIC码值;若读取失败,返回EOF。
看代码:
int main()
{
FILE* fp = fopen("myfile.txt", "r");
if (fp != NULL)
{
printf("%c\n", fgetc(fp));
printf("%c\n", fgetc(fp));
printf("%c\n", fgetc(fp));
printf("%c\n", fgetc(fp));
}
//关闭文件:
fclose(fp);
return 0;
}
之前文件里面存的是:abcdefghi
输出结果是:
说明每读一次,文件指针就向后挪动一个字符。
2.fputc
int fputc ( int character, FILE * stream );
函数含义:写一个字符(character)到到文件中,并且文件指针向后加1,如果写入成功,则返回被写入的字符。如果写入失败,则返回 EOF。(一次写一个字符)
看代码:
int main()
{
FILE* fp = fopen("myfile.txt", "w+");
if (fp != NULL)
{
/*fputc('a', fp);
fputc('b', fp);
fputc('c', fp);*/
int i = 0;
for (i = 0; i < 26; i++)
{
fputc('a' + i, fp);
}
}
//关闭文件:
fclose(fp);
return 0;
}
输出:
3.fgets
char * fgets ( char * str, int size, FILE * stream );
函数含义:str为一个字符数组,用来保存读取到的字符。
size为要读取的字符的个数。如果该行字符数大于size-1,则读到 size-1 个字符时结束,并在最后补充' \0';
如果在stream里面中读5个字符,实际上只读了4个,第5个放了一个\0.
如果该行字符数小于等于 size-1,则读取所有字符,并在最后补充 '\0'。即,每次最多读取 size-1 个字符。
stream为文件流指针。(要读取的文件的文件指针)
如果文件的第一行读取结束了,那么会接着读下一行,也就是按照一行行的读取。
返回值:
读取成功,返回读取到的字符串,即string;
失败或读到文件结尾返回NULL。
因此我们不能直接通过fgets()的返回值来判断函数是否是出错而终止的,应该借助feof()函数或者ferror()函数来判断。
看代码:
int main()
{
FILE* fp = fopen("myfile.txt", "r");
if (fp != NULL)
{
char arr[30];
printf("%s", fgets(arr, 3, fp));
printf("%s", fgets(arr, 20, fp));
}
//关闭文件:
fclose(fp);
return 0;
}
文件的内容:
输出的结果:
4.fputs
int fputs ( const char * str, FILE * stream )
函数含义:把字符串写入到指定的流 stream 中,但不包括空字符。如果写入成功,返回一个非0 的值,失败返回EOF.
如果fputs写完一次之后,再用fputs的话,就会另外启用一行,再写。也就是说,两次fputs写了两行。
看代码:
int main()
{
FILE* fp = fopen("myfile.txt", "w");
if (fp != NULL)
{
fputs("woshishuaige\n", fp);
fputs("efghi\n", fp);
fputs("xyz\n", fp);
}
//关闭文件:
fclose(fp);
return 0;
}
输出:
5.fscanf
int fscanf ( FILE * stream, const char * format, ... );
函数含义:格式化的读取文件中的数据,用法和scanf类似,只不过scanf读取的是键盘上的数据,而fscanf读取的是文件中数据。
文件里面存的数据是:
程序:
int main()
{
FILE* fp = fopen("myfile.txt", "r");
if (fp != NULL)
{
int a = 0;
while (fscanf(fp, "%d", &a) != EOF)
{
printf("%d\n", a);
}
}
//关闭文件:
fclose(fp);
return 0;
}
输出的结果:
6.fprintf
int fprintf ( FILE * stream, const char * format, ... );
函数含义:用法和printf相似,stream是文件指针,指向一个文件。fprintf将数据写入到stream指向的文件流当中。
看代码:
struct A
{
char name[20];
int age;
char sex[20];
};
int main()
{
FILE* fp = fopen("myfile.txt", "w");
if (fp != NULL)
{
struct A s = {"zhsngsan",10,"nan"};
fprintf(fp, "%s %d %s", s.name, s.age, s.sex);
}
//关闭文件:
fclose(fp);
return 0;
}
输出结果:
补充:
对于任意的一个C程序而言,只要运行起来都会默认打开三个流,这三个流的默认类型都是FILE*.
三个流分别是:
1.stdin——标准输入流——键盘
2.stdout——标准输出流——屏幕
3.stderr——标准错误流——键盘
举例:
int main() { int ch = fgetc(stdin); fputc(ch,stdout); return 0; }
由此可知,
scanf(......)等价于fscanf(stdin, ......)
printf(......)等价于fprintf(stdout, ......)
7.fread
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
函数含义:
fread函数每次从stream中最多读取count个单元,每个单元大小为size个字节,将读取的数据放到buffer;文件流的指针后移size*count字节。
函数参数:
buffer为接收数据的地址,对于fread来书是要读出数据的地址,即数据保存的地址
size是要读出内容的单字节数。
count是要进行读出size字节的数据项的个数。
stream为目标文件指针。
返回值:
返回实际读取的单元个数,
如果小于count,则可能文件结束或者读取出错;
可以用ferror()检测是否读取出错,
用feof()函数,检测是否达到文件结尾。如果size或count为0,则返回0.
看代码:
文件的内容
代码:
int main()
{
FILE* fp = fopen("myfile.txt", "rb");
if (fp != NULL)
{
char arr[50];
fread(arr, sizeof(arr), 1, fp);
printf(arr);
}
//关闭文件:
fclose(fp);
return 0;
}
输出结果
8.fwrite
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
函数含义:
把ptr所指向的数组中的数据写入到给定流stream中。(以二进制形式在文件中写入数据)
参数:
ptr —— 指向要被写入的元素数组的指针。(被写的数组)
size —— 要被写入的每个元素的大小,以字节为单位。
count —— 元素的个数,每个元素的大小为 size 字节。
stream —— 指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。(写入的文件)
返回值:
返回实际写入的数据数目。
如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。
如果该数字与 count 参数不同,则会显示一个错误。
看代码:
int main()
{
FILE* fp = fopen("myfile.txt", "wb");
if (fp != NULL)
{
char arr[50]="abcdefg";
fwrite(arr, sizeof(arr[0]), 5, fp);
}
//关闭文件:
fclose(fp);
return 0;
}
输出:
4.2对比一组函数(scanf,fscanf,sscanf——printf,fprintf,sprintf)
1.sprintf
int sprintf ( char * str, const char * format, ... );
函数含义:将格式化的数据打印到一个字符串srt中去
直接看代码:
int main()
{
char arr[50];
int a = 100;
sprintf(arr, "hello world");
//sprintf(arr,"%d",a);
printf(arr);
return 0;
}
结果:
2.sscanf
int sscanf ( const char * s, const char * format, ...)
函数含义:从一个字符串s中格式化输入数据,输入的源头是字符串。
看代码:
int main()
{
char arr1[50] = "helloworld";
char arr2[50];
sscanf(arr1, "%s", arr2);
printf(arr2);
return 0;
}
结果:
总结:
scanf:按照一定的格式从键盘中输入数据
printf:按照一定的格式把数据打印(输出)到屏幕上
——适用于标准输入/输出流的格式化输入/输出语句
fscanf:按照一定的格式从输入流(文件/stdin)输入数据
fprintf:按照一定的格式向输出流(文件/stdout)输出数据
——适用于所有输入/输出流的格式化输入/输出语句
sscanf:从字符串中按照一定的格式读取出格式化的数据
sprintf:把格式化的数据按照一定的格式转换成字符串
4.3随机读写
1.fseek
int fseek ( FILE * stream, long int offset, int origin )
函数含义:
函数设置文件指针stream的位置。(修改刚开始文件指针的位置)
如果执行成功,stream将指向以origin为基准,偏移offset个字 节的位置。
如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置。
返回值: 成功,返回0,否则返回其他值。
注意:
第一个参数stre am为文件指针
第二个参数offset为偏移量,正数表示正向偏移,负数表示负向偏移
第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET
SEEK_SET: 文件开头
SEEK_CUR: 当前位置
SEEK_END: 文件结尾
其中SEEK_SET,SEEK_CUR和SEEK_END和依次为 0,1和2.
简言之:
fseek(fp,100L,0);把fp指针移动到离文件开头100字节处;
fseek(fp,100L,1);把fp指针移动到离文件当前位置100字节处;
ffseek(fp,-100L,2);把fp指针退回到离文件结尾100字节处
看代码:
int main()
{
FILE* fp = fopen("myfile.txt", "r");
if (fp == NULL)
{
perror("fopen()");
return 1;
}
//读文件
fseek(fp, 2, SEEK_SET);//从起始位置向右偏移两个字节读取一个字符
int ch = fgetc(fp);
printf("%c", ch);
//关闭文件:
fclose(fp);
//防止fp为野指针
fp = NULL;
return 0;
}
元文件的内容:
输出结果:
2.ftell
long int ftell ( FILE * stream );
函数含义:
返回当前文件指针位置。这个位置是当前文件指针相对于文件开头的位移量。
返回值:返回文件指针的位置,若出错则返回-1L
看代码:
int main()
{
FILE* fp = fopen("myfile.txt", "r");
if (fp == NULL)
{
perror("fopen()");
return 1;
}
//读文件
int ch = 0;
printf("%d ", ftell(fp));//0
ch = fgetc(fp);
printf("%c ", ch);
//查看文件指针相对于起始位置的偏移量
printf("%d ", ftell(fp));//1
ch = fgetc(fp);
printf("%c ", ch);
printf("%d ", ftell(fp));//2
ch = fgetc(fp);
printf("%c ", ch);
printf("%d ", ftell(fp));//3
ch = fgetc(fp);
printf("%c ", ch);
printf("%d ", ftell(fp));//4
//关闭文件:
fclose(fp);
//防止fp为野指针
fp = NULL;
return 0;
}
结果:
3.rewind(重绕)(重置)
void rewind ( FILE * stream );
函数含义:
将文件内部的位置 指针重新指向一个流( 数据流/文件)的开头
注意:不是 文件指针而是文件内部的位置指针,随着对文件的读写文件的位置指针(指向当前读写字节)向后移动。而文件指针是指向整个文件,如果不重新赋值文件指针不会改变。
返回值:没有
看代码;
int main()
{
FILE* fp = fopen("myfile.txt", "r");
if (fp == NULL)
{
perror("fopen()");
return 1;
}
//读文件
int ch = 0;
printf("%d ", ftell(fp));//0
ch = fgetc(fp);
printf("%c ", ch);
//查看文件指针相对于起始位置的偏移量
printf("%d ", ftell(fp));//1
ch = fgetc(fp);
printf("%c ", ch);
printf("%d ", ftell(fp));//2
//充值文件指针到起始位置
rewind(fp);
printf("%d ", ftell(fp));//0
//关闭文件:
fclose(fp);
//防止fp为野指针
fp = NULL;
return 0;
}
结果:
5.文本文件和二级制文件
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
如有整数10000,如果以ASCII码的形式输出到磁盘,
则磁盘中占用5个字节(每个字符一个字节),而 二进制形式输出,则在磁盘上只占4个字节
看图:
6.文件读取结束的判定
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
int feof ( FILE * stream )
当返回值是0 的时候,说明读取失败而结束,当返回值是非0 的时候,说明是遇到文件尾结束的。
举例说明:
#include <stdlib.h>
int main(void)
{
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("myfile.txt", "r");
if (!fp)
{
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
}
7.文件缓冲区

但是, 万一我要在内存中读取的数据很少,不能够充满缓冲区的话,难道需要等到充满了再输出吗?其实不是的,当遇到\n或者fflush,都会把输出缓冲区的内容放到硬盘中,并不是要放慢缓冲区再放到硬盘中,特殊情况会特殊处理。
举个例子:
#include <stdio.h>
#include <windows.h>
int main()
{
FILE*pf = fopen("test.txt", "w");
fputs("abcdef", pf);//先将代码放在输出缓冲区
printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
//注:fflush 在高版本的VS上不能使用了
printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
Sleep(10000);
fclose(pf);
//注:fclose在关闭文件的时候,也会刷新缓冲区
pf = NULL;
return 0;
}
总结: