C开发实战-文件操作
文件概述
https://m.toutiao.com/is/JsxRHrP/
文件几乎无处不在,主要分为磁盘文件和设备文件,典型的磁盘文件有文本文件和二进制文件,磁盘文件存储在外部存储介质(例如磁盘,硬盘,U盘等等)需要加载到内存中才能使用。
无论是文本文件还是二进制文件在计算机内部都是以字节为单位,二进制的方式存储。
- 文本文件存储的是字符对应的ASCII码值,例如文本字符的a在计算机内部就是以二进制01100001 即97存储。当使用文本编辑器(例如Visual Studio Code)打开文本文件时,会将文本文件的二进制转换成ASCII编码对应的字符。
- 二进制文件存和取的是二进制数值,常见的二进制文件有图片、音频、视频等等。二进制文件不能使用文本编辑器(例如Visual Studio Code)打开,图片需要使用特定的软件(例如Windows的照片浏览器)打开。
设备文件有标准输入文件(stdin),标准输出文件(stdout),标准错误(stderr)三个文件,设备文件在程序启动时由系统默认打开,程序员无需调用fopen(stdin/stdout/stderr,'r+')函数打开这三个设备文件即可使用。
- stdin: 标准输入,默认为当前终端(键盘),我们使用的scanf、getchar函数默认从此终端获得数据。
- stdout:标准输出,默认为当前终端(屏幕),我们使用的printf、puts函数默认输出信息到此终端。
- stderr:标准出错,默认为当前终端(屏幕),我们使用的perror函数默认输出信息到此终端。
文件流指针
当打开文件时,系统内核会返回一个FILE结构体,该结构体有对文件操作的所有信息。
typedef struct{short level;//缓冲区'满'或者'空'的程度 unsigned flags;//文件状态标志 char fd;//文件描述符unsigned char hold;//如无缓冲区不读取字符short bsize;//缓冲区的大小unsigned char *buffer;//数据缓冲区的位置 unsigned ar; //指针,当前的指向 unsigned istemp;//临时文件,指示器short token;//用于有效性的检查 }FILE;
而当调用fopen()函数时,系统会返回这个FILE结构体的地址,即 FILE * p; ,当使用函数fputc('a',p)往文件写入字符时,只需要传递文件流指针p即可。
文件流指针和之前的指针不同的是无法通过*p获取文件内容。
文件的打开和关闭
文件的打开
C语言提供了fopen()函数用于打开文件,该函数的声明位于stdio.h头文件中,fopen()调用时需要两个参数:路径名和打开的方式,如果是当前路径以./表示,Visual Studio2019 本地Windows调试器运行时源文件file_open_close.c的当前目录是D:\workspace\c\ittimelinedotnet\vs2019\c-core\c-core-advanced,打开方式可以是如下几种方式
- r或rb 以只读方式打开一个文本文件(不创建文件,若文件不存在则报错)
- w或wb 以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
- a或ab 以追加方式打开文件,在末尾添加内容,若文件不存在则创建文件
- r+或rb+ 以可读、可写的方式打开文件(不创建新文件)
- w+或wb+ 以可读、可写的方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
- a+或ab+ 以添加方式打开文件,打开文件并在末尾更改文件,若文件不存在则创建文件
其中w选项如果文件不存在会创建文件,但是如果文件有内容会清空。r选项文件不存在,则不创建文件。而b表示打开二进制文件。
fopen()打开文件成功后返回FILE*p结构体的地址,也标识了那个文件,操作指针就是操作那个文件。打开失败返回NULL。
以只读的方式打开当前路径下的test.txt文件
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/* 以只读方式打开当前目录的test.txt文本文件*/void readonly_open_txt() {FILE* p_readonly = fopen('./test.txt', 'r');if (p_readonly == NULL) {//追加错误提示前缀perror('readonly mode open file');return;}}/*文件的打开和关闭 fopen()函数打开方式说明 r或rb以只读方式打开一个文本文件(不创建文件,若文件不存在则报错) w或wb以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件) a或ab以追加方式打开文件,在末尾添加内容,若文件不存在则创建文件 r+或rb+以可读、可写的方式打开文件(不创建新文件) w+或wb+以可读、可写的方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件) a+或ab+以添加方式打开文件,打开文件并在末尾更改文件,若文件不存在则创建文件@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int file_open_close_main(int argc, char* argv[]){readonly_open_txt();system('pause');return 0;}
因为当前路径下不存在test.txt文件,所以程序运行时会出现错误提示
以只写的方式打开当前目录下的test.txt文本文件,如果文件不存在则会创建文件,打开成功返回FILE *p_write
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/* 以只写的方式打开当前目录下的test.txt文本文件,如果文件不存在则会创建文件,打开成功返回FILE *p_write*/FILE* write_open_txt() { FILE* p_write = fopen('./test.txt', 'w'); if (p_write == NULL) { perror('write mode open file'); return; } else { printf('open test.txt success!\n'); } return p_write;}/*文件的打开和关闭 fopen()函数打开方式说明 r或rb以只读方式打开一个文本文件(不创建文件,若文件不存在则报错) w或wb以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件) a或ab以追加方式打开文件,在末尾添加内容,若文件不存在则创建文件 r+或rb+以可读、可写的方式打开文件(不创建新文件) w+或wb+以可读、可写的方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件) a+或ab+以添加方式打开文件,打开文件并在末尾更改文件,若文件不存在则创建文件@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){FILE* p_write = write_open_txt();system('pause');return 0;}
程序运行结果
以只写方式打开时如果test.txt文件不存在,系统会创建文件
文件的关闭
C语言提供了fclose()函数用于关闭文件,该函数的声明也位于stdio.h头文件中,fclose()函数需要的参数就是打开文件返回的文件流指针,通常情况下文件打开后如果操作完成应该就要关闭。如果文件只打开而不关闭,会给程序造成异常。系统设置了一个应用程序只能打开1024个文件。
以只写的方式打开当前系统路径下的文本文件test.txt 后关闭文件
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*以只写的方式打开当前目录下的test.txt文本文件,如果文件不存在则会创建文件,打开成功返回FILE *p_write*/FILE* write_open_txt() {FILE* p_write = fopen('./test.txt', 'w');if (p_write == NULL) {perror('write mode open file');return;}return p_write;}/*文件的打开和关闭 fopen()函数打开方式说明 r或rb以只读方式打开一个文本文件(不创建文件,若文件不存在则报错) w或wb以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件) a或ab以追加方式打开文件,在末尾添加内容,若文件不存在则创建文件 r+或rb+以可读、可写的方式打开文件(不创建新文件) w+或wb+以可读、可写的方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件) a+或ab+以添加方式打开文件,打开文件并在末尾更改文件,若文件不存在则创建文件@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){FILE *p_write=write_open_txt();//关闭文件fclose(p_write);system('pause');return 0;}
文件的顺序读写
基于字符写文件
C语言提供了fputc()函数实现写入单个字符到文件中,该函数需要两个参数:字符和文件流指针。写入成功会返回写入的字符,如果写入失败则返回-1
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*以字符的方式写入文件*/void write_file_char() {//以写的方式打开当前目录下的test.txt文件FILE* p_write = fopen('./test.txt','w');if (NULL==p_write) {perror('write mode open fail');return;}//将字符a写入当前目录下的test.txt文件char c=fputc('a', p_write);printf('写入test.txt文本文件的字符内容是%c\n',c);}/*基于字符的文件写操作@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){write_file_char();system('pause');return 0;}
程序运行结果
查看当前目录D:\workspace\c\ittimelinedotnet\vs2019\c-core\c-core-advanced 下test.txt文件的内容
需要注意的是fputc()函数在写入字符前,默认会清空文件的内容,如果要是追加文件,将打开文件的打开方式改成追加即可。
//以追加的方式打开当前目录下的test.txt文件FILE* p_append = fopen('./test.txt','a');
而如果想要往文件追加写入字符串,借助循环就可以搞定
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*以追加的方式将字符写入test.txt文本文件中*/void append_file_str() {FILE* p_append = fopen('./test.txt', 'a');if (NULL == p_append) {perror('write mode open fail');return;}//以追加的方式将字符串hello world写入test.txt文本文件中char buf[] = 'hello world';int i = 0;while (buf[i] != 0) {fputc(buf[i], p_append);i++;}}/*基于字符的文件写操作@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){append_file_str();system('pause');return 0;}
写入字符串
基于字符读文件
C语言提供了fgetc()函数实现基于字符从字符流指针中读取文件,该函数的参数只需要传递文件流指针即可,读取成功会返回读取的字符,读取失败返回-1。
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>char buf[128] = '';/*读取指定路径的文件并返回char**/char * read_text_by_char(char *path) {//以只读的方式打开当前路径下的test.txt文件FILE *p_readonly=fopen(path,'r');if (NULL==p_readonly) {perror('read file error');return NULL;}int i= 0;//EOF 表示-1 即当没有读取失败时将读取的值赋值给buf[i ]//fgetc()的返回值是-1表示失败,当读取的文件中有-1时,就会提前结束,因此不能使用EOFwhile ((buf[i++] = fgetc(p_readonly)) != EOF);return &buf;}/*基于字符读取文件@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){char *buffer=read_text_by_char('./test.txt');printf('test.txt content is %s\n', buffer);system('pause');return 0;}
程序运行结果
fgetc()函数在基于字符读取文件时,如果文件内容有-1这种数字,就不能使用EOF作为文件的结尾。
为了解决这个问题,C语言提供了feof()函数用于检测是否读到了文件的结尾。即判断最后一次读操作的内容不是当前位置的内容(上一个内容),该函数的返回值如果是0就表示没有到文件结尾,如果是非0就表示到文件结尾。
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>char buf[128] = '';/*使用feof()函数判断文件是否读取完毕,解决了fgetc()函数读取-1字符的二进制返回-1时读取就停止的问题*/char* read_text(char * path) {//以只读的方式打开当前路径下的test.txt文件FILE* p_readonly = fopen(path, 'r');if (NULL == p_readonly) {perror('read file error');return NULL;}int i = 0;int pos = 0;do {buf[i] = fgetc(p_readonly);i++;pos = feof(p_readonly);// pos ==0 表示没有到文件末尾} while (pos==0);return buf;}/*基于字符读取文件@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){char *buffer=read_text('./test.txt');printf('test.txt content is %s\n', buffer);system('pause');return 0;}
基于字符文件的拷贝
文件的拷贝就是将一个文件创建一个副本。
而文本文件的拷贝的实现过程就是首先读取一个文件,然后将读取的文件内容写入到待拷贝的文件中。
我这里封装了一个函数 copy_text_file(char* source_path,char *target_path) ,只需要传递源文件和目标文件就可以实现文件的拷贝
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*封装一个文本文件的拷贝方法,传递两个文件的路径实现将source_path的文件内容拷贝到target_path*/void copy_text_file(char* source_path,char *target_path) {FILE* source = fopen(source_path,'r');if (NULL == source) {perror('open file error');}FILE* target = fopen(target_path,'w');if (NULL == target) {perror('open file target.txt error');}char ch = 0;while (1) {//读取文件的内容ch=fgetc(source);//读取到函数的末尾if (feof(source)) {break;}//将读取的文件内容写入到target文件指针fputc(ch, target);}//关闭文件fclose(target);fclose(source);}/*实现文件拷贝@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){copy_text_file('./test.txt','./target.txt');system('pause');return 0;}
程序运行结果
查看拷贝的源文件和目标文件的内容
但是copy_text_file(char* source_path,char *target_path)函数只能实现文本文件的拷贝,如果想要实现二进制文件(例如图片)的拷贝,需要稍微改造下fopen()方法
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*二进制文件的复制*/void copy_binary_file(char* source_path, char* target_path) {FILE* source = fopen(source_path, 'rb');if (NULL == source) {perror('open file error');}FILE* target = fopen(target_path, 'wb');if (NULL == target) {perror('open file target.txt error');}char ch = 0;while (1) {//读取文件的内容ch = fgetc(source);//读取到函数的末尾if (feof(source)) {break;}//将读取的文件内容写入到target文件指针fputc(ch, target);}//关闭文件fclose(target);fclose(source);}/*实现文件拷贝@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){copy_binary_file('./20201129.jpg','./20201129_copy.jpg');system('pause');return 0;}
程序运行结果
基于字符的文件查看
将指定的文件内容写入到终端
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>void cat_file(char* file_path) {//以只读方式打开当前目录的text_file_cat.c文件FILE *p=fopen(file_path,'r');if (NULL==p) {perror('file read error');return;}char ch = 0;while (1) {//读取指定的内容ch = fgetc(p);//如果读到文件末尾if (feof(p)) {break;}//将读取的文件内容写入到终端 //stdout表示终端的文件流指针//printf()底层会调用fputc()fputc(ch,stdout);}//关闭文件fclose(p);}/*文本文件查看@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){cat_file('text_file_cat.c');return 0;}
程序运行结果
基于行写文件
C语言提供了fputs(const char*str,FILE *stream)函数用于将字符串写入到指定的文件,其中str是待写入文件的字符串地址,stream表示文件流指针,字符串结束符 '\0' 停止写入,写入成功返回0,写入失败返回-1。
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*fputs()写入字符串到指定的文件@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){//以只写的方式打开当前路径下的test.txtFILE* p = fopen('test.txt','w');if (NULL==p) {perror('open file fail');}char buf[] = 'I will see you again\0again ';//将字符串I will see you again 写入到test.txt文件中//遇到\0停止写入int result = fputs(buf,p);if (result==0) {printf('写入数据成功\n');}else {printf('写入数据失败');}system('pause');return 0;}
程序运行结果
基于行读文件
C语言提供了fgets(char* str, int size ,FILE *stream)函数用于从文件读取字符串,其中str用于保存读取的内容,而size是指定最大读取字符串的长度(size-1),stream表示文件流指针。fgets()函数遇到\n会结束。
读取成功则返回读取的字符串,读取到文件末尾或则失败则返回NULL。需要注意的是 fgets()函数只能操作字符串,不能操作二进制文件。
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*基于fgets()读取指定文件的字符串@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){FILE* p = fopen('./test.txt', 'r');if (NULL == p) {perror('读取文件错误');return;}char buf[1024] = '';fgets(buf, sizeof(buf), p);printf('buf = %s \n', buf);system('pause');return 0;}
程序运行结果
基于fputs()函数和fgets()函数实现的文本文件拷贝
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>#include <string.h>/*基于fputs()和fgets()函数实现的文件拷贝 只能支持文本文件,因为遇到\0会停止读写*/void copy_by_fgets_fputs(char * source_path,char *target_path) {FILE* source = fopen(source_path,'r');if (NULL==source) {perror('open file fail');return;}FILE* target = fopen(target_path,'w');if (NULL == target) {perror('open file fail');return;}char* p = NULL;//循环多少次,buf就可以使用多少次char buf[1024] = '';while (1) {memset(buf, 0, sizeof(buf));p = fgets(buf, sizeof(buf), source);//读取结束if (p==NULL) {break;}//将读取的内容写入到targetfputs(buf, target);}//fclose(target);fclose(source);}/*基于fputs()和fgets()函数实现的文件内容拷贝@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){copy_by_fgets_fputs('./test.txt','./copy_of.txt');system('pause');return 0;}
程序运行结果
基于文件读写实现随机生成表达式并完成四则运算
首先定义一个宏,用于描述生成表达式的数量
//表达式生成的数量#define EXPRESSION_NUM 10
由于生产表达式,需要读写文件,在读写文件之前,需要打开文件,因此封装一个打开文件的函数,该函数需要一个参数:打开方式
而FILE_PATH 也是宏定义的文件路径#define FILE_PATH 'expression_calc.txt',算术运算表达式就是存储在该文件中。
/*按照指定的打开方式打开指定路径的文件*/FILE* open_file(char * mode) {FILE* fp=fopen(FILE_PATH, mode);if (NULL==fp) {perror('open file fail');return;}return fp;}
然后实现生成表达式的函数。以及将生成的表达式写入文件中。
这其中表达式的操作数和运算符都是是随机生成的,需要借助srand()函数和rand()函数。
然后还要使用sprintf()函数组包生成表达式。
/*生成表达式并写入文件*/void generator_expression_write_file() {FILE *fp=open_file('w');//设置随机种子srand(time(NULL));int left = 0;int right = 0;char opertors[4] = {'+','-','*','/'};char operator_index=0;char singleton_expression[128] = '';//生成10个随机表达式字符串for (int i = 0; i < EXPRESSION_NUM;i++) {//产生1-100之间的随机数left= rand()%100+1;right= rand()%100+1;//产生0-3之间的随机数赋值给operator_indexoperator_index =rand() % 4;//将 '%d %c %d = \n',left, opertors[operator_index],right 赋值给expressionsprintf(singleton_expression,'%d%c%d=\n',left, opertors[operator_index],right);fputs(singleton_expression, fp);}fclose(fp);}
然后实现读取文件后计算表达式,并将表达式的结果写入FILE_PATH中。
/*计算表达式的结果并写入文件*/void calc_expression_write_file() {FILE* fp = open_file('r');//第一个操作数int left = 0;//第二个操作数int right = 0;//运算符char operator=0;//运算的结果int result = 0;//读取的单个表达式(未计算)char buf[128] = '';//单个表达式(已经计算结果的)char singleton_expression[128] = '';//所有的表达式char all_expression[100][128] = { 0 };//读取一行表达式的指针char* p = NULL;int i = 0;printf('生成的表达式列表\n');while (1) {//每次读取一行p = fgets(buf, sizeof(buf), fp);if (p == NULL) {break;}//将读取的内容buf 解析到left, operator,right变量中sscanf(buf, '%d%c%d', &left, &operator,&right);//根据运算的符来进行运算并保存运算的结果switch (operator) {case '+':result = left + right;break;case '-':result = left - right;break;case '*':result = left * right;break;case '/':result = left + right;break;}//2+3=5//将表达式以及计算的结果写入all_expression二维数组中sprintf(all_expression[i], '%d%c%d=%d\n', left, operator,right, result);printf('%s \n', all_expression[i]);i++;}//关闭文件fclose(fp);//FILE *n_fp = open_file('w');for (int j = 0; j < i; j++) {int result =fputs(all_expression[j], n_fp);if (result==0) {printf('写入%s到文件%s成功\n', all_expression[j],FILE_PATH);}else {printf('写入%s到文件%s失败\n', all_expression[j], FILE_PATH);}}fclose(n_fp);}
最后在main函数中依次调用这两个函数并运行即可
#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<string.h>#include<stdlib.h>#include <time.h>//表达式生成的数量#define EXPRESSION_NUM 10#define FILE_PATH 'expression_calc.txt'/*基于文件读写实现随机生成表达式并完成四则运算@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){generator_expression_write_file();calc_expression_write_file();system('pause');return 0;}
程序运行结果
程序运行结果
基于指定格式写文件
C语言提供了fprintf(FILE * stream, const char * format, ...)函数用于将数据按照指定的格式写入到指定的文件中,其中stream表示文件流指针,而format就是数据的格式,...表示需要组装的数据列表。
fprintf()函数写入成功则会返回实际写入的字符个数,如果写入失败则返回-1。
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*fprintf()函数按照指定的格式写入数据到文本文件@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){char name[] = '刘光磊';double height = 178.00;int age = 28;FILE* fp = fopen('./info.txt','w');if (NULL==fp) {perror('read file fail');return -1;}fprintf(fp, '我的名字是%s我的身高是%.2lf我的年龄是%d\n', name, height, age);fclose(fp);system('pause');return 0;}
程序运行结果
程序运行结果
基于指定格式读文件
C语言提供了fscanf(FILE * stream, const char * format, ...);函数用于从文件中按照指定的格式来读取数据,读取成功返回成功转换参数的个数,失败返回-1。
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*使用fscanf()从文件中读取指定格式的数据@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){int year = 0;int month = 0;int day = 0;FILE* fp = fopen('./date.txt', 'r');if (NULL == fp) {perror('read file fail');return -1;}//从date.txt文件中读取日期并赋值给year,month,dayfscanf(fp, '%d-%d-%d', &year, &month, &day);printf('year = %d\n',year);printf('month = %d\n', month);printf('day = %d\n', day);system('pause');return 0;}
读取的文件内容
程序运行结果
基于块写文件
在读写大文件时需要基于块来读写文件,C语言提供了fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);函数来基于块写文件,其中ptr表示写入文件数据的地址,size表示写入文件内容的大小,nmemb表示写入文件的块数,写入文件的总大小为szie * nmemb,stream表示已经打开的文件流指针。写入成功则返实际成功写入文件数据的块数目,该值和nmemb相等。
将结构体数组user_array以块的形式写入fwrite.txt文件
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>//定义一个结构体user,别名是userstypedef struct user {int id;char name[16];}users;/*fwrite()函数:基于块写文件@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){//初始化一个结构体数组users user_array[3] = { {1,'tony'},{2,'mengmeng'},{3,'xiaohuihui'} };FILE* fp = fopen('fwrite.txt','w');if (NULL==fp) {perror('open file fail');return -1;}//fwrite()第二个参数写1,返回值刚好是写入文件的字节数int byte_size=fwrite(user_array, 1, sizeof(user_array), fp);printf('写入文件的字节数是%d\n',byte_size);system('pause');return 0;}
基于块读文件
C语言提供了size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);函数用于基于块的方式读取文件内容
其中ptr:存放读取出来数据的内存空间,size: size_t 为 unsigned int类型,此参数指定读取文件内容的块数据大小,nmemb:读取文件的块数,读取文件数据总大小为:size * nmemb,stream:已经打开的文件指针
如果读取成功则返回实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。否则失败返回0
实现读取fwrite.txt文件的内容,由于该文件的内容是一个结构体数组,因此需要在头文件user.h中定义一个通用的结构体,其他的源文件使用该结构体时包含#include 'user.h'即可
#pragma once//定义一个结构体user,别名是userstypedef struct user {int id;char name[16];}users;
然后使用fread()函数读取文件内容并存储到结构体数组中,然后遍历结构体数组的内容,显示在终端上。
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>#include 'user.h'#include <string.h>/*fread()基于块的方式读取文件@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){users user_array[3];//将数组清零memset(user_array, 0, sizeof(user_array));int size = sizeof(user_array) / sizeof(user_array[0]);FILE* fp = fopen('./fwrite.txt','r');//一次性读到user_arrayfread(&user_array, 1, sizeof(user_array), fp);for (int i = 0; i < size;i++) {//从fp读取一个结构体users并赋值给&user_array[i]//fread(&user_array[i], 1, sizeof(users), fp);printf('id = %d name = %s \n',user_array[i].id,user_array[i].name);}system('pause');return 0;}
程序运行效果
文件的随机读写
之前介绍的读写文件函数都是顺序读写的。
C语言提供了fseek(FILE *stream, long offset, int whence) 函数实现移动文件流(文件光标)的读写位置实现随机读写。
- fseek(fp,6,SEEK_SET)表示光标相对于开头向后移动6个字节
- fseek(fp,0,SEEK_SET)表示光标移动至开头
- fseek(fp,4,SEEK_CUR)表示光标相对当后移动4个字节
- fseek(fp,-4,SEEK_CUR)表示光标相对当前移动4个字节
- fseek(fp,4,SEEK_END)表示光标相对末尾往后移动4个字节
除此以外C语言还提供了rewind(fp)函数将光标移到开头,等价于fseek(fp,0,SEEK_SET),ftell(fp)函数可以获取文件流(文件光标)的读写位置。
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>#include <string.h>/*fseek()随机读写@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){FILE* fp = fopen('fseek.txt', 'w');if (NULL == fp) {perror('open file fail');return 0;}//往fseek.txt文件写入this is test content 此时光标默认在末尾fputs('this is test content', fp);//移动光标到首部fseek(fp, 0, SEEK_SET);//写入seek到fseek.txt,此时文件内容是seek is test contentfputs('seek', fp);//移动光标到倒数第五个字节fseek(fp,-5,SEEK_END);//在倒数第五个字节的光标写入again,此时文件内容是seek is test coagainfputs('again', fp);//将光标移到开头//rewind(fp);//将光标移到末尾fseek(fp, 0, SEEK_END); //计算末尾到开头的字节数int byte_size=ftell(fp);printf('byte_size=%d\n',byte_size);int count = ftell(fp);system('pause');return 0;}
程序运行结果
获取文件信息
C语言提供了stat(const char *path, struct stat *buf)函数用于获取文件信息,如果获取成功返回0,获取失败则返回-1。stat()函数可以判断该文件存不存在获取文件状态。
在使用该函数前需要包含两个系统路径下的头文件:sys/types.h和sys/stat.h
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>/*stat()函数获取文件状态@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){//定义stat结构体struct stat file_info;int info= stat('fwrite.txt',&file_info);//文件不存在if (info<0) {//打印输出提示信息printf('file not found \n');}else {printf('获取文件信息: 文件大小是%d字节 \n', file_info.st_size);}system('pause');return 0;}
程序运行结果
文件删除和重命名
文件删除
C语言提供了库函数remove(const char *path)来删除一个文件,参数需要一个文件的路径,删除成功返回0,删除失败返回-1
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*文件删除@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){//删除当前路径下的fwrite.txt文件int flag=remove('fwrite.txt');if (flag==0) {printf('删除成功\n');}else {printf('删除失败');}system('pause');return 0;}
程序运行结果
文件重命名
C语言提供了rename(const char *oldpath, const char *newpath)函数实现文件的重命名,该函数需要两个文件的完整路径
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*rename()函数实现文件的重命名@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){//文件重命名rename('fseek.txt','fseek_rename.txt');system('pause');return 0;}
程序运行结果
Linux和Windows文本文件的区别
- Windows字符串中带\n时存取的是\r\n
- Linux中字符串带\n时存取的是\n
如果把Windows的文本文件传到Linux系统上时使用编辑器打开会多了一个\r。而Linux的文本文件到Windows下就没有换行。
文件缓冲区
缓冲区本质就是内存中的一块临时空间,
当写入文件时,库函数会去调用内核提供的系统调用,例如fwrite()函数最终会调用write()系统调用。如果是大量频繁的写操作,会从用户空间切换到内核空间,上下文的切换会导致程序效率非常低。
因此引入了文件的缓冲区,当缓冲区满了才会一次性写入文件,或者有特殊场景可以调用fflush()强制刷新缓冲区,也就是将缓冲区的内容写入文件,或者程序正常退出(关闭Visual Studio的终端)时也会将缓冲区的文件写入磁盘文件。
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*文件缓冲区@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){FILE* fp = fopen('data.txt','w');if (NULL==fp) {perror('open file fail');return -1;}//当不关闭终端时data.txt文件没有内容fputs('this is test content ',fp);system('pause');return 0;}
缓冲区
刷新缓冲区的两种编码实现
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>#include <string.h>/*文件缓冲区@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/29*/int main(int argc, char* argv[]){FILE* fp = fopen('data.txt','w');if (NULL==fp) {perror('open file fail');return -1;}//当不关闭终端时data.txt文件没有内容fputs('this is test content ',fp);//可以调用fflush()函数实现强制刷新缓冲区的内容到data.txt;fflush(fp);//也可以将缓冲区写满char buf[1024] = '';//写入1M的1for (int i = 0; i < 1024;i++) {memset(buf, '1', sizeof(buf));fwrite(buf, 1, sizeof(buf), fp);}system('pause');return 0;}
而Windows下的stdout 是没有缓冲区的,即使用printf()函数打印数据到终端时不会进入缓冲区,直接写入到终端上。而Linux下的stdout文件是有缓冲区的。
Windows下标准输入(stdin) 不能调用fflush()强制刷新。