5 对比一组函数
scanf/fscanf/sscanf
printf/fprintf/sprintf
首先我们先了解sscanf和sprintf是什么
sprintf
将一个格式化的数据写入字符串(把一个格式化的数据转换成字符串)format的数据放进str里转换成字符串
int sprintf ( char * str, const char * format, … );
参数说明:
1.str:指向存储生成的c字串的缓冲区的指针。
缓冲区应该足够大,以包含生成的字符串,2.format: 该参数类似于printf一样使用
返回值
- 如果成功,则返回写入的总字符数。
如果失败,返回负数
代码实例
- fprintf (把数据转换成字符串放进一个数组里)
#include<stdio.h> #include<stdlib.h> struct S { int age; float f; char adders[20]; }; //sprintf 把数据转换成字符串放进一个数组里 int main() { struct S s = { 200,3.14,"guangzhou"}; char buf[200] = { 0 }; //把数据转换成字符串放进buf里 sprintf(buf,"%d %f %s", s.age, s.f, s.adders); printf("%s\n", buf); system("pause"); return 0; }
最终输出结果:
10 3.140000 guangzhou
sscanf
把一个字符串转换成对应的格式化数据
int sscanf ( const char * s, const char * format, …);
参数说明:
- s:这是 C 字符串,是函数检索数据的源。
- format : 该参数跟scanf使用方法类似.
返回值:
- 如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返
回 EOF。
代码实例:
#include<stdio.h> #include<stdlib.h> struct S { int age; float f; char adders[20]; }; //sscanf 把一个字符串转换成对应的格式化数据 int main() { struct S s = { 200,3.14,"guangzhou"}; char buf[200] = { 0 }; //把数据转换成字符串放进buf里 sprintf(buf,"%d %f %s", s.age, s.f, s.adders); printf("字符串的数据: %s\n", buf); //从字符串中读取格式化数据 struct S tmp = { 0 }; sscanf(buf, "%d %f %s", &(tmp.age), &(tmp.f), tmp.adders); printf("格式化的数据: %d %f %s", tmp.age, tmp.f, tmp.adders); system("pause"); return 0; }
最终输出结果:
字符串的数据: 200 3.140000 guangzhou
格式化的数据: 200 3.140000 guangzhou
sscanf 和 sprintf 这两个函数用的不多,一般是序列化和反序列化的时候使用的。
那我们一起来对比下这几组函数吧:
scanf/fscanf/sscanf
printf/fprintf/sprintf
- scanf 对于标准输入流(stdin)的格式化输入函数
- printf 对于标准输出流(stdout)的格式化输出函数
- fscanf 对于所有输入流(文件流/stdin)的格式化输入函数
- fprintf 对于所有输出流(文件流/stdin)的格式化输出函数
- scanf 将一个字符串转换成格式化的数据
- sprintf 将格式化的数据转换成字符串
5 文件的随机读写
fseek
根据文件指针的位置和偏移量来定位文件指针。
重新定位流位置指示器
int fseek ( FILE * stream, long int offset, int origin );
参数说明:
1.stream:这是指向 FILE 对象的指针,该 FILE 对象标识了流
2.offest: 这是相对 origin 的偏移量,以字节为单位。
3.origin: 起始位置选择,该参数选择有三种
常量 | 描述 |
SEEK_SET | 文件的开头位置 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件的末尾 |
- 我们画个图举个列子来理解该函数
返回值:
- 如果成功,则该函数返回零,否则返回非零值。
代码实例:
- fseek (代码讲解请看注释!)
此时我们test.txt 文件存储的数据是
#include<stdio.h> #include<stdlib.h> int main() { FILE* pf = fopen("test.txt", "r"); //判断是否为空 if (pf == NULL) { perror("fopen:"); } int ch = fgetc(pf); printf("%c\n", ch); //A ch = fgetc(pf); printf("%c\n", ch); //B ch = fgetc(pf); printf("%c\n", ch); //C //此时test.txt文件指针已经指向了D了 //可我们这次想输出A应该怎么做 这时我们就使用fseek了 //SEEK_CUR是文件当前指针 fseek 把pf文件当前指针向后偏移3个位置指向A fseek(pf, -3, SEEK_CUR); ch = fgetc(pf); printf("Fseek指针偏移后的结果 %c\n", ch); //A //当然我们也可以使用SEEK_SE 文件开头位置 或者SEEK_END 文件结尾位置来偏移 //关闭文件 fclose(pf); pf = NULL; system("pause"); return 0; }
最终输出结果:
A
B
C
Fseek指针偏移后的结果 A
ftell
返回文件指针相对于起始位置的偏移量
long int ftell(FILE *stream)
参数说明:
- stream: 指向 FILE 对象的指针,该 FILE 对象标识了流
返回值:
- 该函数返回位置标识符的当前值。如果发生错误,则返回 -1,全局变量 errno 被设置
为一个正值。
代码实例:
- ftell (文件指针从起始位置偏移到哪了)
-此时我们test.txt 文件存储的数据是
#include<stdio.h> #include<stdlib.h> //ftell 告诉我们文件指针从起始位置偏移到哪了 int main() { FILE* pf = fopen("test.txt", "r"); //判断是否为空 if (pf == NULL) { perror("fopen:"); } int ch = fgetc(pf);//A ch = fgetc(pf);//B ch = fgetc(pf);//C //此时的文件指针指向了C long int offest = ftell(pf); printf("文件指针从起始位置偏移了->%d\n", offest); //关闭文件 fclose(pf); pf = NULL; system("pause"); return 0; }
最终输出结果:
文件指针从起始位置偏移了->3
rewind
让文件指针的位置回到文件的起始位置
void rewind ( FILE * stream );
参数说明
stream: 指向 FILE 对象的指针,该 FILE 对象标识了流
返回值:
- 无
代码实例
- rewind
-此时我们test.txt 文件存储的数据是
#include<stdio.h> #include<stdlib.h> //rewind 让文件指针回到起始位置 int main() { FILE* pf = fopen("test.txt", "r"); //判断是否为空 if (pf == NULL) { perror("fopen:"); } int ch = fgetc(pf);//A ch = fgetc(pf);//B ch = fgetc(pf);//C //此时的文件指针指向了C rewind(pf); ch = fgetc(pf); //A printf("rewind后文件指针回到起始位置-> %c\n", ch); //关闭文件 fclose(pf); pf = NULL; system("pause"); return 0; }
最终输出结果:
rewind后文件指针回到起始位置-> A
6 文本文件和二进制文件
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
- 数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
- 如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存
储的文件就是文本文件
- 一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储.
如有整数10000,如果以ASCII码的形式输出到磁盘,则以ASCII码的形式(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。
- 那是不是以二进制存储更加节省空间呢?
并非是,如果存储1呢?以ASCII码的形式存储磁盘就占一个字节,如果二进制形式存储,磁盘就占4个字节,所以没有决定性的因素说哪个更加节省空间
- 我们用一张图来感受一下他们的存储方式
代码实例
- 把一万以二进制方式存储到我们的文件里.
#include<stdio.h> #include<stdlib.h> //rewind 让文件指针回到起始位置 int main() { FILE* pf = fopen("test.txt", "w"); //判断是否为空 if (pf == NULL) { perror("fopen:"); } int n = 10000; fwrite(&n, 4, 1, pf); //关闭文件 fclose(pf); pf = NULL; system("pause"); return 0; }
文件的存储结果
以二进制的形式存储,我们看不懂,我们可以用二进制的方式打开,那如何打开呢
存储的内容是16进制显示的
前面几个0没有意义,不用在意
7 文件读取结束的判定
feof
测试给定流 stream 的文件结束标识符,判断是否是遇到文件末尾结束还是遇见读取失败结束的。
int feof(FILE *stream)
被错误使用的feof
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
返回值:
- 当设置了与流关联的文件结束标识符时,该函数返回一个非零值,否则返回零。
- 那该如何怎么判断文件结束呢?
1.文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL (fgets)
例如:
1.fgetc 判断是否为 EOF .
fgetc函数返回值的分析:1.遇到文件末尾,返回EOF,同时设置一个状态,遇到文件未尾了,使用feof来检测 这个状态。
2.遇到错误,返回EOF,同时也设置一个状态,遇到了错误,使用ferror来检测这个 状态
2.fgets 判断返回值是否为 NULL .
gets函数返回值分析:
- 遇到文件末尾,则设置 feof检测这个状态。并返回的是空指针
- 如果发生读错误,则设置 ferror检测这个状态 ,并返回的是空指针.
2.二进制文件的读取结束判断,判断返回值是否小于实际要读的个数
例如:
- fread判断返回值是否小于实际要读的个数。
ps:比如实际要读5个的,但只读了3个
正确的使用方式:
- 文本文件的例子:
此时文件存储的数据是
#include<stdio.h> #include<stdlib.h> int main() { FILE* pf = fopen("test.txt", "r"); //判断是否为空 if (pf == NULL) { perror("fopen:"); } //读取文件 int ch = 0; while ((ch = fgetc(pf)) != EOF) { printf("%c ", ch); } //检测是遇见错误还是遇见文件末尾结束 //ferror检测读取错误这个状态 if (ferror(pf)) { printf("\n遇到错误结束\n"); } //feof检测文件遇见末尾这个状态 else if (feof(pf)) { printf("\n遇见文件末尾结束\n"); } //关闭文件 fclose(pf); pf = NULL; system("pause"); return 0; }
最终输出结果:
A B C D E F
遇见文件末尾结束
- 二进制文件的例子:
#include<stdio.h> #include<stdlib.h> #define size 5 int main() { int arr[size] = { 1,2,3,4,5 }; //二进制的写文件 wb FILE* pf = fopen("test.txt", "wb"); //判断是否为空 if (pf == NULL) { perror("fopen:"); } //写文件 fwrite(arr, sizeof(arr[0]), size, pf); //把arr数组内容写进去 fclose(pf); pf = NULL; //二进制读文件 pf = fopen("test.txt", "rb"); // 判断是否为空 if (pf == NULL) { perror("fopen:"); } //读文件 int str[size] = { 0 }; int count = fread(str, sizeof(str[0]), size, pf); //检测读取是否成功 if (count == size) { printf("数组读取成功内容\n"); } //读取异常 else { //检测是遇见错误还是遇见文件末尾结束 //是否遇见文件末尾 if (feof(pf)) printf("遇见文件末尾结束\n"); //是否遇见异常 else if (ferror(pf)) { printf("读取错误\n"); } system("pause"); return 0; }
最终输出结果
数组读取成功内容
8 文件缓冲区
ANSIC(标准C) 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
当我们从程序进写入到硬盘上,并非是直接传送到硬盘上,而先要把数据放进输出缓冲区内,到放满时候才会把数据放到硬盘上。硬盘文件把数据放进数据区也是一样.
#include <stdio.h> #include <windows.h> //VS2013 WIN10环境测试 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; }
结论:
因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。
如果不做,可能导致读写文件的问题。
知识点扩展:
像C语言文件函数并非是直接能操作文件的,他们中间经过操作系统API(接口),系统调用来完成操作的。