目录
引入:
一、文件的打开和关闭
1.文件名
2.文件指针
3.文件的打开和关闭
二、对文件如何操作和流
1.对文件操作相关功能:
2.流
三、文件操作函数(顺序读写)
1."w"输出流:
2."r" 输入流
3."wb"与"rb"二进制输出输入
4.sscanf与sprintf函数
四、fseek相关函数介绍(随机读写)
1.fseek函数
2.ftell
3.rewind
五、文件缓冲区
磁盘上的文件都是文件。
对于日常生活中,我们在使用电子设备的时候,用到某个软件或者应用的时候,总是发现他们会在本地占用空间,即文件,是不是打开软件后,先前保留的数据都不见了呢?不是的,这些数据是被存放在文件里面的。
之前我们制作的动态储存版的通讯录(一个小项目,数据只能储存到内存中),程序是不是只要一关闭,保存的数据下次打开就消失了。这是因为没有将其数据保存在文件中,而是内存中,结束程序内存的空间也自然被释放,从而无法保留。
那么如果想要改进这个通讯录自然要加上文件操作的函数让其数据保留在我们的硬盘上:
而在程序设计中:文件一般包括两种,一种就是我们写程序所得到的文件(程序文件,比如:.cc语言源文件 .obj(windows环境下)目标文件,.exe可执行程序)第二种就是通过程序来操作输入输出的文件(数据文件:从某个文件通过程序写入或者拿取数据的文件)。
所以,接下来我们一起来了解文件操作的相关函数的知识吧!
首先了解一下文件名:
文件名 = 文件路径 + 文件名主干 + 文件后缀。在Windows下要注意的是:
(1)文件名最长可以使用255个字符。
(2)可以使用扩展名,扩展名用来表示文件类型,也可以使用多间隔符的扩展名。如win.ini.txt是一个合法的文件名,但其文件类型由最后一个扩展名决定。
(3)文件名中允许使用空格,但不允许使用下列字符(英文输入法状态):< > / \ | : " * ?
(4)windows系统对文件名中字母的大小写在显示时有不同,但在使用时不区分大小写。
(上面参考百度百科)
那么,当文件被创建或者使用时,电脑又是如何感知到它的存在的呢?
那么这里就要涉及到指针相关的知识了。
要操作一个文件,被使用的文件(无论读或者写)就会创建一个文件信息区(结构体对象,FILE类型,在内存里面创建,强关联,用来描述文件基本信息的)
文件基本信息可以参考一下截图:
就是类型,位置,大小等等信息了。所以每次操作文件的时候,内存内总会出现文件信息区与之关联,好让我们操作。
通常,在c中,将文件指针变量如此定义:
FILE* pr //文件指针变量,通过该指针便就可以操作相关文件。
现在重头戏来啦:我们已经了解了文件指针,那么要如何在程序中通过该指针对文件进行打开和关闭操作呢?
(1).打开(open) :
FILE* pr = fopen("C:\\Users\\HP\\Desktop HELLO WORLD", "r");
上面的操作就是对我们之前创建的文件进行打开,并且*只读操作。
*部分不懂先不急,后续会补充。
该函数fopen即为文件打开函数:FILE* fopen(const char* name, const char* mod);返回文件信息区的地址,第一个是文件名,第二个是打开操作,如果打开失败,返回NULL。
(2).关闭(close)
fclose(pr);
上面的操作就是对刚刚打开的文件信息区进行关闭,即关闭文件函数。
fclose即为文件关闭函数,int fclose( FILE *stream );文件关闭。如果*流被成功关闭,fclose返回0。函数返回EOF表示错误。
一般关闭文件后,需要给文件指针给NULL,防止成为野指针。
看下面的打开与关闭文件代码:
FILE* or = fopen("test.txt", "r");//只是文件名加扩展名的话只在源文件目录下进行寻找,如果打开失败,那么就返回NULL
if (or == NULL) {
perror("fopen");
return 1;
}
fclose(or);//关闭此文件。关闭成功返回0,否则返回EOF即发生错误。
or = NULL;
return 0;
在了解了文件的打开与关闭后,我们就来了解一下对文件进行怎样操作的相关功能和流的介绍:
fopen第二个参数 (使用方式) 含义
"r" 打开,为了只读 。如果打开文件不存在,报错。
"w" 为了只写 如果文件存在,会将原来内容销毁,然后在写,没有就会在文件名下开辟
"a" 为了追加,不会将内容摧毁的条件下写。
"rb" 二进制只读,如果打开文件不存在,报错。
"wb" 二进制只写,文件存在,会将原来内容销毁,然后在写,没有就会在文件名下开辟
"wa" 二进制追加,不会将内容摧毁的条件下写。
目前只对上述操作进行介绍,并且在介绍其功能时,也一边将文件操作函数带入:
针对r和w的含义,特别的:
//在w只写的条件下,对文件输出字符。如果找不到该文件,则会创建一个,如果找到了,则会销毁里面的内容。
FILE* pr = fopen("test.txt", "w");
if (pr == NULL)
{
perror("fopen");
return 1;
}
int a = fputc('x', pr);
fclose(pr);
pr = NULL;
printf("%c\n", a);
return 0;
请多加留意一下文件里的内容哦!
流可以形容成:水流
外部设备封装个流
电脑上有各种的外部设备(键盘、屏幕、u盘、硬盘、网卡】.......)
把数据写入流,读取数据从流里面读。
流就有:文件流(文件指针)标准输入(stdin键盘)、输出(stdout屏幕)、错误流(stderr屏幕)(只要c程序运行起来,这三个流默认打开,类型也是文件指针 FILE*))
前提,"w"表明在打开文件的时候,是用的 FILE* pr = fopen("test.txt", "w");方式打开,以只写的方式,即输出,向流输出,即输出流。
(1).fputc 字符操作
函数原型:int fputc(int c, FILE* ar); c为要写入的字符变量,存入文件之中。返回的是写入的字符的ASCII码值,出现错误就返回EOF
//使用字符存入利用循环对文件进行存入字符串
FILE* pr = fopen("test.txt", "w");
if (pr == NULL)
{
perror("fopen");
return 1;
}
int a = 'A';
for (int i = 0; i < 26; i++)
{
fputc(a++, pr); //往文件里面存入ABCDEFG........
}
fclose(pr);
pr = NULL;
(2).fputs 文本行(字符串)操作
函数原型:int fputs(const char *string, FILE* ar);将字符串(传入的是该字符串或者文本首元素地址),写入文件。如果成功,每个函数都会返回一个非负值。当发生错误时,fputs返回EOF
//直接用字符串存入函数即可;
FILE* pr = fopen("test.txt", "w");
if (pr == NULL)
{
perror("fopen");
return 1;
}
fputs("ABCDEF\n", pr);//改完后文件里面存ABCDEF
fclose(pr);
pr = NULL;
(3).fprintf 针对于所有输出流的格式化输出函数
函数原型:int fprintf( FILE *stream, const char *format [, argument ]...);格式化输出,所有输出流,后面和printf一致,针对于w只写
因为具有格式化进行输出了,那么就要有对于的格式化操作符,下面例子使用结构体方便举例:
struct student
{
char name[20];
char xingbie;
int age;
};
struct student c = { "小刘", 'L', 13 };
FILE* arr = fopen("test3.txt", "w");
if (arr == NULL)
{
perror("fopen");
return 1;
}
fprintf(arr, "%s %c %d", c.name, c.xingbie, c.age);//上述代码会在源代码根目下创建一个新的test3文件,并且写入结构体内存入的数据。
fclose(arr);
arr = NULL;
其实就是和原来的只针对标准流输出的printf差了第一个文件指针而已,可以对比记忆哦!
前提,"r"表明在打开文件的时候,是用的 FILE* pr = fopen("test.txt", "r");方式打开,以只读的方式,即输入,从流输入程序,即输入流。
(1)fgetc 字符输入
函数原型:int fgetc(FILE* ar);将文件中的字符输入程序中,返回的是读到的字符的ASCII码值 ,遇到错误或者结束就返回EOF。
FILE* pr = fopen("E:\\学习资料\\C\\xuexi\\text_2022\\wenjiantest_4_6\\test2.txt", "r");
if (pr == NULL)
{
perror("fopen");
return 1;
}
//利用字符操作循环输入
//printf("%c\n", fgetc(pr));
//printf("%c\n", fgetc(pr));
//printf("%c\n", fgetc(pr));
int a = 0;
while ((a = fgetc(pr)) != EOF)
{
printf("%c", a);
}fclose(pr);
arr = NULL;
上述是两种输入方法;另外:(fgetc(stdin)键盘里读)也是读取键盘里的字符。
(2).fgets 文本行输入
函数原型:char *fgets( char *string, int n, FILE *stream );三个参数分别是:读文件所放位置,最大的个数(读多少个字符,最多读255个字符)读的时候实际上读n - 1个。最后一个要给\0 函数返回字符串,如果读完或者没有,返回NULL
char arr[255] = { 0 };
fgets(arr, 255, pr);
printf("%s\n", arr);
自己对比记忆也可哦!
(3).fscanf 针对于所有输入流的格式化输入函数
函数原型:int fscanf( FILE *stream, const char *format [, argument ]... );格式化输入,所有输入流,后面和scanf一致。针对于r只读 。
同理,也是用结构体来举例:
struct student
{
char name[20];
char xingbie;
int age;
};
struct student c = { 0 };
FILE* pr = fopen("test3.txt", "r");
if (pr == NULL)
{
perror("fopen");
return 1;
}
fscanf(pr, "%s %c %d", c.name, &(c.xingbie), &(c.age));
printf("%s %c %d", c.name, c.xingbie, c.age);
fclose(pr);
pr = NULL;
记忆与printf相似,多了一个文件指针。
上面的wb和rb只与w和r差别一个b,表现了使用二进制交换的,和之前的文本不同:
FILE* pr = fopen("test.txt", "wb");//FILE* pr = fopen("test.txt", "rb");
与上面文件打开方式对应的文件操作函数是:fwrite和fread
(1)fwrite (在"wb"条件下) 输出操作
函数原型size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
指针指向要写的数据 写多大 最多写几个数据 写入哪个流(单位字节)fwrite返回实际写入的完整项数,如果发生错误,这个数可能小于count。此外,如果发生错误,则无法确定文件位置指示器。
struct student
{
char name[20];
char xingbie;
int age;
};
struct student c = { "张三", 'L', 19 };
FILE* pr = fopen("test4.txt", "wb");
if (pr == NULL)
{
perror("fopen");
return 1;
}
fwrite(&c, sizeof(struct student), 1, pr);
打开文件后,会发现有乱码,这是因为是用二进制写入的。
(2). fread(在"rb"条件下) 输入操作
函数原型: size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
指针指向要存的数据地址 读多大 最多读几个数据 取出哪个流(单位字节) 返回实际读到的元素
struct student
{
char name[20];
char xingbie;
int age;
}; struct student d = { 0 };
FILE* pr = fopen("test4.txt", "rb");
if (pr == NULL)
{
perror("fopen");
return 1;
}
fread(&d, sizeof(struct student), 2, pr);
printf("%s %c %d", d.name, d.xingbie, d.age);
fclose(pr);
pr = NULL;
下面是两个函数的原型和解析:
sscanf - int sscanf( const char *buffer, const char *format [, argument ] ... );从一个字符串里面读取一个格式化的数据。后面和scanf一致,第一个要提取字符串的地址
sprintf - int sprintf( char *buffer, const char *format [, argument] ... );把格式化的数据转化为字符串,后面和printf一致,前面是储存字符串的地址。
struct student
{
char name[20];
char xingbie;
int age;
};
struct student c = { 0 };
sscanf("小明 N 14", "%s %c %d", c.name, &(c.xingbie), &(c.age));
printf("%s %c %d", c.name, c.xingbie, c.age);
struct student
{
char name[20];
char xingbie;
int age;
};
struct student c = { "张三", 'L', 19 };
char arr[256] = { 0 };
sprintf(arr, "%s %c %d", c.name, c.xingbie, c.age);
printf("%s\n", arr);
上面介绍的全是关于文件里面顺序读写的。
那么,我们能否自由指定位置呢?当然是可以实现的。
在上面代码实现fgetc函数的时候,你会发现读取一个然后就会读取下一个,不会重复读取的存在,而写自然也是一样,这说明了文件指针也是会随着读写发生变化的,证明了我们是能够操控这个位置的,下面我们首先向文件导入一串字符串:
int main()
{
//文件的随机读写,我要往哪里去读写 文件里面一开始有记录读取文件的位置的,默认是起始位置。
FILE* pr = fopen("test.txt", "w");//以只写的方式打开
if (pr == NULL)
{
perror("fopen");
return 1;
}
fputs("nihaoshijie", pr);//先写入进去;
fclose(pr);
pr = NULL;
return 0;
}
存好之后,我们会发现文本文件里面存在
下面介绍一下fseek函数的使用:
函数原型:int fseek( FILE *stream, long offset, int origin );三个参数:origin:SEEK_CUR -文件当前 SEEK_END 末尾 SEEK_SET 开始 offset:偏移量 表现了通过起始坐标的不同,我们便就可以操作目前读取或者写入的位置了,下面以读取为例子:
int main()
{
//文件的随机读写,我要往哪里去读写 文件里面一开始有记录读取文件的位置的,默认是起始位置。
FILE* pr = fopen("test.txt", "r");//以只读的方式打开
if (pr == NULL)
{
perror("fopen");
return 1;
}
printf("%c", fgetc(pr));//正常按顺序读
printf("%c", fgetc(pr));//每次读取你会发现不会读取重复的,即文件指针也随着读取(或者写入)在变化
//此时使用文件的随机读,所谓随机,即我们自己去控制
//使用函数int fseek( FILE *stream, long offset, int origin );
//origin:SEEK_CUR -文件当前 SEEK_END 末尾 SEEK_SET 开始 offset:偏移量
//此时文件指针应该指向的是 h的位置,我们让它指向o
fseek(pr, 2, SEEK_CUR);//为了让其指向o,自然从文件指针当前指向位置向后偏移两个单位即可
printf("%c", fgetc(pr));//此时读取的就是o了,字符串和写入也是如此
fclose(pr);
pr = NULL;
return 0;
}
下面还有几个方便使用的函数:
long ftell( FILE *stream );
返回当前位置与起始位置的偏移量
让文件回归到起始位置 void rewind(FILE *stream) 文件指针的位置回到起始位置。
以上就是随机读写的全部知识啦,自己写的不是很好,嘿嘿,谢谢指正说明啦~
在c语言中会自动为程序中使用的文件开辟缓冲区 是在内存里面
而读取的时候 先将硬盘中数据放入输入缓冲区,放满,在放入程序里面,反之同理。
一下代码只是提供测试,在刷新缓存区,或者关闭文件操作都可以是直接将缓存区的数据给文件或者程序的。
#include
#include
//VS2013 WIN10环境测试int main()
{
FILE* pf = fopen("test.txt", "w");
fputs("abcdef", pf);//先将代码放在输出缓冲区printf("睡眠20秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
Sleep(20000);//睡眠10秒printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
//注:fflush 在高版本的VS上不能使用了
printf("再睡眠20秒-此时,再次打开test.txt文件,文件有内容了\n");
Sleep(20000);fclose(pf);
//注:fclose在关闭文件的时候,也会刷新缓冲区
pf = NULL;
return 0;
}
所以得到结论:想让数据瞬间到文件里面去,刷新数据或者关闭文件即可,如果不关闭,可能造成数据丢失。