文章目录
对比scanf\fscanf\sscanf,printf\fprintf\sprintf
在上一篇文件操作的介绍中,我向大家介绍了有关fscanf fprintf
的用法,我们说他与scanf printf
这两个函数很像,我们可以对比一下他们的函数声明:
我们可以看出来,他们参数的区别是fprintf fscanf
多了一个文件指针,那么他们到底有什么区别呢?其实他们的作用都是以一定的格式输入输出数据,只是他们的对象不一样。
scanf : 按照一定的格式从键盘输入数据
printf : 按照一定的格式把数据输出到屏幕上
适用于标准输入/输出流的格式化的输入/输出语句
fscanf : 按照一定的格式从输入流输入文件,这个输入流可以是文件中的也可以是键盘。
fprintf : 按照一定的格式向输出流输出数据,可以是屏幕或者文件。
适用于所有的输入、输出流的格式化输入、输出语句。
今天还有一组输入输出的函数sscanf sprintf
,让我们来看看这一组函数的作用是什么,与上面两组函数的区别又在哪里。
好了,我们说fscanf fprintf
比scanf printf
多了一个文件指针参数,那么fscanf fprintf
就可以读取文件中的数据,或者向文件中写入数据,而sscanf sprintf
多了一个字符指针,是不是说明它可以将格式化数据向字符串中输入,或者可以从字符串中按照一定格式读取格式化数据。
struct S {
char name[10];
int age;
char sex[5];
};
int main()
{
char ch[100] = { 0 };
struct S tmp = { 0 };
struct S s = { "abc",10,"nan" };
sprintf(ch,"%s %d %s", s.name, s.age, s.sex);//转换为为字符串输出
printf("%s\n", ch);
sscanf(ch, "%s %d %s", tmp.name, &(tmp.age), tmp.sex);
printf("%s %d %s", tmp.name, tmp.age, tmp.sex);
return 0;
}
根据上面的代码我们就可以总结出sscanf sprintf
的作用
sscanf
: 从字符串中按照一定的格式读取出格式化的数据
sprintf
: 把格式化的数据按照一定的格式转换成字符串
这样函数有什么用呢?在我们以后的使用中,我们在前端页面获取信息时信息可能是一个字符串,传到后端后我们需要一个结构体数据,那么这一组函数是不是就可以完成这样的操作呢。
文件的随机读写
在上一篇文件操作的博客中,我们介绍了很多用于文件读写的函数,但那些库函数实现的都是按顺序读写文件,我们想读取任意位置的数据该如何实现呢?我们知道,文件内部内部的位置指针用来指示文件内部的当前读写位置,每读取一次,该指针向后移动一个位置。所以我们是不是可以改变这个指针位置,来实现读取任意位置的数据。这里我们就要向大家介绍一组库函数。
fseek
这个库函数可以根据文件指针的位置和偏移量来定位文件指针。
他的参数是FILE* stream文件流,long int offset偏移量,int origin从何位置开始偏移。返回值==如果成功,返回0,否则返回一个非零的值,在无法查找的设备上,返回值没有定义。我们在代码上演示一下这个函数。
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fseek(pf, 5, SEEK_SET);
char ch = 0;
ch = fgetc(pf);
printf("%c", ch);
fclose(pf);
pf = NULL;
return 0;
}
我们看见我们实现了打印从文件流开始向后偏移五个位置的数据'f'
。
ftell
当我们想使用fseek
库函数但又不知道当下文件指针的位置的时,我们是不是需要打印一下现在文件指针的位置相较于起始位置的偏移量,我们就可以使用ftell
函数来查看。
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fseek(pf, 5, SEEK_SET);
char ch = 0;
ch = fgetc(pf);
printf("%c\n", ch);
int address = 0;
address = ftell(pf);
printf("%d", address);
fclose(pf);
pf = NULL;
return 0;
}
这样就打印出了现在文件指针相较于起始位置的偏移量,有的同学会有问题了,我们刚才使用的fseek
库函数,不是找到的是相较于起始位置偏移量为5的位置上的数据嘛,为什么此时我们使用ftell
打印出来的偏移量是6呢?原因是,我们使用fseek
之后读取了该位置的数据,所以我们的文件指针也向后移动一个位置,读取前距离起始位置的偏移量是5,读取后加一就变成6了。
rewind
这个库函数的作用是让文件指针的位置回到起始位置。
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fseek(pf, 5, SEEK_SET);
char ch = 0;
ch = fgetc(pf);
printf("%c\n", ch);
int address = 0;
address = ftell(pf);
printf("%d\n", address);
rewind(pf);
address = ftell(pf);
printf("%d\n", address);
fclose(pf);
pf = NULL;
return 0;
}
二进制文件与文本文件
数据是以二进制的形式在内存中存储的,对于文件来说,如果不加转换直接输出到外存的,就是二进制文件,如果要求在外存上以ASCII码的形式存储,则需要在存储前转换,以ASCII字符的形式存储的文件就是文本文件。
例如:我们想要存储10000这个数据,如果以ASCII码的形式输出到磁盘上,那么这个数的每一位都被看作一个字符,存储的是每个字符的ASCII码值,一共占5个字节,如果以二进制的形式输出到磁盘上,那么这个数就被看作一个整形,占4个字节,存储的是他的二进制形式。
我们可以在vs里面测试一下。
int main()
{
int num = 10000;
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fwrite(&num, sizeof(num), 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
我们将10000这个数据以二进制的形式存储到文件中
我们看在文件中我们并不能看懂只是一个什么数据,因为他是以二进制存储的,我们使用我们的编译器来打开这个文件。
我们看到这个文件从一堆乱码,变成了二进制的形式。
文件读取结束的判定
feof
这个库函数被很多人错误使用,用来判断文件是否读取结束,其实他的作用是判断文件读取结束的原因,是读取失败结束,还是遇到文件尾结束。
文件读取结束的原因有两种:
1.读取过程中出现异常
2.读取到文件末尾。
如何判断文件读取结束了呢:
文本文件:
如果使用fgetc
读取,要判断feof()
的返回值是否为EOF。
如果使用fgets
读取,要判断feof
的返回值是否为NULL。
二进制文件:
使用fread()
读取,判断其返回值与指定读取个数的大小,如果小于实际要读的个数,就说明读取发生了异常,如果等于实际要读的个数,说明读到文件末尾结束了。对于读取异常的判断,我们要判断ferror()
函数的返回值。
如果ferror()
为真那么就是读取异常结束。
如果feof()
为真那么就是读取到文件末尾结束。
文本文件的判断
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = 0;
while ((ch = fgetc(pf)) != EOF)//判断文件是否读取结束
{
putchar(ch);
}
// 判断文件为何读取结束
if (ferror(pf))
{
printf("读取文件中发生了错误");
}
if (feof(pf))
{
printf("读取到文件末尾结束");
}
fclose(pf);
pf = NULL;
return 0;
}
二进制文件判断
#define size 5
int main()
{
int arr[size] = { 1,2,3,4,5 };
FILE* pf = fopen("test.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fwrite(arr, sizeof *arr, size, pf);
fclose(pf);
int ret[size];
pf = fopen("test.txt", "rb");
size_t ret_num = fread(ret, sizeof *ret, size, pf);
if (ret_num == size)//判断文件是否读取结束
{
printf("读取全部数据成功\n");
int i = 0;
for (i = 0; i < size; i++)
{
printf("%d", ret[i]);
}
}
else//判断文件为何读取结束
{
if (ferror(pf))
{
printf("在读取中出现错误\n");
}
if (feof(pf))
{
printf("读取到文件末尾结束\n");
}
}
return 0;
}
文件缓冲区
什么是文件缓冲区呢?其实就是一块内存空间,这块内存空间在使用文件时自动开辟,当我们从内存向磁盘输出数据时,数据会先存放在文件缓冲区中,等文件缓冲区满了后,一起输出给磁盘。当我们从磁盘向内存输入数据时,磁盘中的数据也是先读取到文件缓冲区,等到缓冲区充满,在从缓冲区逐个的将数据送到程序区,缓冲区的到小根据C语言编译系统决定,当然我们也可以手动刷新文件缓冲区,实现提前输入输出数据。