31.0、C语言——文件操作 (2)
1. 文件的打开和关闭:
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件;
1. 在编写程序的时候,在打开文件的同时,都会返回一个 FILE* 指针变量指向该文件,也相当于建立了指针和文件的关系;
2. ANSIC 规定使用 fopen() 函数来打开文件;fclose() 函数来关闭文件;
FILE* fopen ( const char* filename , const char* mode );
int fclose ( FILE* stream );
打开方式如下:
" r "(只读) | 为了输入数据,,打开一个已经存在的文本文件 | 出错 |
" w "(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
" a "(追加) | 向文本文件尾添加数据 | 出错 |
" rb "(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
" wb "(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
" ab "(追加) | 向一个二进制文件尾添加数据 | 出错 |
" r+ "(读写) | 为了读和写,打开一个文本文件 | 出错 |
" w+ "(读写) | 为了读和写,建议一个新的文件 | 建立一个新文件 |
" a+ "(只读) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
" rb+ "(只读) | 为了读和写打开一个二进制文件 | 出错 |
" wb+ "(只读) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
" ab+ "(只读) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新文件 |
像以下代码,打开一个文件(只读) ->
FILE* pf = fopen( " test.txt ", " r " );
如果打开成功,则会创建一个 FILE 类型的结构体去管理维护这个打开的文件,然后返回一个指向这块存放文件信息 FILE 结构体内存的指针,如果打开失败则会返回一个 NULL 空指针;
那么打开成功,读取文件之后,我们要主动的关闭文件 ->
fclose( pf );
pf = NULL;
2. 读写文件:
1. 我们可以发现,如果只是 ( 读文件 ) ,打开的时候如果该文件不存在就直接报错;但是如果是 ( 写文件 ) 打开文件,发现文件不存在,则新建一个该文件【其实如果是写的话不管文件是否存在都会去新建一个同名的文件】;
2. 除非是给文件追加内存,否则其他任何改写文件的方式都是新建一个同名文件,然后将原来的文件销毁掉【也就是说只要是改写文件,原来文件的内容就会被销毁,然后新建一个同名的文件】;
文件的顺序读写:
功能 | 函数名 | 适用于 |
---|---|---|
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
上述函数的 剖析 与 使用 ->
1. fgetc ( ) 函数
fgetc ( FILE* stream ); //一次读取一个字符;
2. fputc ( ) 函数
fputc ( FILE* stream , char ch ); //一次写一个字符;
3. fgets ( ) 函数
fgets ( char* string , int n , FILE* stream ); //读取出来的字符放到string中去,最多可以读n个字符,从stream中读取数据;
4. fputs ( ) 函数
int fputs ( const char* string , FILE* stream ); //将要写的字符串内容放到 string 中,然后通过 stream 流写到文件中去;
5. 从键盘标准输入流中读取数据->
int main() { char buf[1024] = {0}; fgets (buf , 1024 , stdin); // stdin就是从标准输入读取 fputs (buf , stdout); // stdout 就是标准输出,写到屏幕中去 // 那么等价于以下这种写法 // gets ( buf ); // puts ( buf ); return 0; }
6. printf、scanf 函数
printf、scanf 默认输出标准输出流和标准输入流,所以不需要指定流;
而 fprintf 需要我们指定一个输出流【注意这里是将 内 容 写 入 文 件 】->
而 fscanf 需要我们指定一个输入流【注意这里是将文件内容读入程序】->struct S { int n; float score; char arr[10]; }; int main() { struct S s = { 100 , 3.14f , "bit" }; FILE* pf = fopen("test.txt","w"); if(pf == NULL) { printf("%s" , strerror(errno)); return 0; } // 格式化的形式写文件 fprintf(pf , "%d %f %s" , s.n,s.score,s.arr); // 格式化的形式读文件也一样 // fscanf(pf , "%d %f %s" , &(s.n),&(s.score),s.arr); fclose(pf); pf = NULL; return 0; }
7. fwrite ( ) 函数
size_t fwrite ( const void* buffer , size_t size , size_t count , FILE* stream );
第一个参数 buffer 指的是 -> 要写进去的数据的地址;
第二个参数 size 指的是 -> 你要写一个元素,这个元素有多大(单位是字节);
第三个参数 count 指的是 -> 要写多少个这样大小的元素;
第四个参数 stream 指的是 -> 写到哪个文件去,也就是流;
下面给大家举个实例 ->
struct S { char name[30]; int age; double score; }; int main() { struct S s = {"澜色海湾",18,100}; FILE* pf = fopen("TEST.txt", "wb"); if (pf == NULL) { return 0; } fwrite(&s,sizeof(struct S),1,pf); fclose(pf); pf = NULL; return 0; }
上述这段代码将 结构体对象s中的数据存到了 TEST.txt 文本文件中,如下所示->
可以看到该结构体数据是以 二进制的形式去存储到 TEST.txt 文本文件中的;有些是乱码,有些是原数据;
8. fread ( ) 函数
size_t fread ( const void* buffer , size_t size , size_t count , FILE* stream );
参数的意义和上面 fwrite() 函数如出一辙就不过多介绍了,这里主要说一下 返回的 size_t 指的是函数真实读到数据的个数;
直接上实例代码,将刚刚以二进制形式存储进TEST.txt文件里去的结构体数据,以二进制的形式读取出来,然后将正常的格式打印出来 ->
struct S { char name[30]; int age; double score; }; int main() { struct S tmp = { 0 }; FILE* pf = fopen("TEST.txt", "rb"); if (pf == NULL) { return 0; } //二进制的形式读文件 fread(&tmp, sizeof(struct S), 1, pf); printf("%s %d %lf\n",tmp.name,tmp.age,tmp.score); fclose(pf); pf = NULL; return 0; }
输出结果如下所示 ->
来看一组函数的对比【这是一道面试题】->
scanf / fscanf / sscanf
printf/ fprintf / sprintf
1. scanf / printf 是针对标准输入 / 输出流的格式化输入 / 输出语句;
2. fscanf / fprintf 是针对所有输入 / 输出流的,格式化输入 / 输出语句;
3.sscanf / sprintf -> sscanf 是从字符串中读取格式化的数据;
-> sprintf 是吧格式化数据输出成字符串;
那么给大家解释一下 sscanf 和 sprintf 函数的用法 ->
struct S { int n; float score; char arr[10]; }; int main() { struct S s = { 100 , 3.14f , "abcdef" }; char buf[1024] = {0}; sprintf( buf , "%d %f %s" , s.n , s.score , s.arr ); printf("%s",buf); return 0; }
那么这里打印出来的结果是 100 3.140000 abcdef ,相当于将这些数据全部转化为 字符串类型 的数据了;
也就是说 sprintf 函数有能力将一个结构体的对象中的 数据 转换成一个 字符串;
既然能够转换成字符串,当然也有能力将转换成字符串的结构体对象数据,还原回原来的数据类型 ->
struct S { int n; float score; char arr[10]; }; int main() { struct S s = { 100 , 3.14f , "abcdef" }; struct S tmp = { 0 }; char buf[1024] = { 0 }; //把格式化的数据转换成字符串存储到 buf sprintf( buf , "%d %f %s" , s.n , s.score , s.arr ); //从 buf 中读取格式化的数据到 tmp 中 sscanf( buf , "%d %f %s" , &(tmp.n) , &(tmp.n) , tmp.arr ); return 0; }
读写打开的文件——代码实例 ->
1. 以写 的 方式打开 TEST.txt 文件【这里 pfWrite 其实也就是输出流~;最后也是将这个输出流关闭】;
然后写入 'h' 'k' 'l' 三个字符到 TEST.txt 文件之中 ->
int main() {
FILE* pfWrite = fopen("TEST.txt","w");
if (pfWrite == NULL) {
printf("%s\n",strerror(errno));
return 0;
}
//写文件
fputc('h',pfWrite);
fputc('k', pfWrite);
fputc('l', pfWrite);
//关闭文件
fclose(pfWrite);
pfWrite = NULL;
return 0;
}
执完程序后,打开文件TEST.txt发现修改成功,内容为 hkl ;
2. 再来以读的方式打开 TEST.txt 文件【这里 pr 其实也就是输入流~;最后也是将这个输入流关闭】;
然后读取文件中的内容;
int main() {
FILE* pr = fopen("TEST.txt","r");
if (pr == NULL) {
printf("%s\n",strerror(errno));
return 0;
}
//从TEST.txt文件中读取内容
printf("%c", fgetc(pr));
printf("%c", fgetc(pr));
printf("%c", fgetc(pr));
//关闭文件
fclose(pr);
pr = NULL;
return 0;
}
执行程序后,成功打印出了 h k l 三个字符;
3. 输出流和输入流:
1. 那我们的 屏幕 和 键盘 也是一种 输出流 和 输入流;
2. 屏幕是标准输出设备 - stdout,键盘是标准输入设备 - stdin,这两个流设备是程序一旦执行就默认打开的两个流设备;
3. 我们可以发现当scanf() 输入 和 printf() 输出的时候,并不需要打开流关闭流就可以正常使用,正是因为他是默认打开的;
三个默认流:
1. stdin FILE*
2. stdout FILE*
3. stderr FILE*
下面这段代码,没有用到scanf 和 printf,而是用了键盘标准输入流 stdin,和屏幕标准输出流 stdout,执行程序后仍然能够实现键盘输入一个值然后将这个值输出出来;
int main() {
int ch = fgetc(stdin);
fputc(ch, stdout);
return 0;
}