文件操作
文件分类
文本文件:基于字符编码的文件(存储时,将字符的ASCII码值存储在磁盘中;读取文件时,将ASCII码值转换为字符)
二进制文件:存储的就是二进制
文件流
文件流指针
当打开文件时,系统会返回一个结构体,结构体中会包含对该文件的所有信息,如下图。当调用fopen()函数时,会返回该结构体的地址,用FILE * fp接收即可
FILE * fp = fopen("a.txt");
文件的打开&关闭
打开文件
文件的打开操作表示将给用户指定的文件在内存分配一个FILE结构区,并将该结构的指针返回给用户程序,以后用户程序就可用此FILE指针来实现对指定文件的存取操作了。当使用打开函数时,必须给出文件名、文件操作方式(读、写或读写)。
FILE * fopen(const char * filename, const char * mode);
/*
功能:打开文件
参数:
filename:需要打开的文件名,根据需要加上路径
mode:打开文件的权限设置
返回值:
成功:文件指针
失败:NULL
*/
注意:(1)只有带‘r’的选项,如果文件不存在,则不会创建;
(2)带W的选项,打开文件时会清空文件;
方式 | 含义 |
---|---|
“r” | 打开,只读,文件必须已经存在。 |
“w” | 只写,如果文件不存在则创建,如果文件已存在则把文件长度截断(Truncate)为0字节。再重新写,也就是替换掉原来的文件内容文件指针指到头。 |
“a” | 只能在文件末尾追加数据,如果文件不存在则创建 |
“rb” | 打开一个二进制文件,只读 |
“wb” | 打开一个二进制文件,只写 |
“ab" | 打开一个二进制文件,追加 |
“r+” | 允许读和写,文件必须已存在 |
“w+” | 允许读和写,如果文件不存在则创建,如果文件已存在则把文件长度截断为0字节再重新写 。 |
“a+” | 允许读和追加数据,如果文件不存在则创建 |
“rb+” | 以读/写方式打开一个二进制文件 |
“wb+” | 以读/写方式建立一个新的二进制文件 |
“ab+” | 以读/写方式打开一个二进制文件进行追加 |
int main()
{
FILE *fp = NULL;
// "\\"这样的路径形式,只能在windows使用
// "/"这样的路径形式,windows和linux平台下都可用,建议使用这种
// 路径可以是相对路径,也可是绝对路径
fp = fopen("../test", "w");
//fp = fopen("..\\test", "w");
if (fp == NULL) //返回空,说明打开失败
{
//perror()是标准出错打印函数,能打印调用库函数出错原因
perror("open");
return -1;
}
}
注意:应该检查fopen的返回值!如何函数失败,它会返回一个NULL值。如果程序不检查错误,这个NULL指针就会传给后续的I/O函数。它们将对这个指针执行间接访问,并将失败
关闭文件
函数原型:
int fclose(FILE * stream);
功能:关闭先前fopen()打开的文件。此动作让缓冲区的数据写入文件中,并释放系统所提供的文件资源。
参数:
stream:文件指针
返回值:
成功:0
失败:-1
它表示该函数将关闭FILE指针对应的文件,并返回一个整数值。若成功地关闭了文件,则返回一个0值,否则返回一个非0值.
标准输入输出文件
C语言程序,运行起来,就默认打开三个流:标准输入流(stdin)也就是键盘,标准输出流(stdout)也就是屏幕,标准错误流(stderr)。所有的输入输出流包括其他的比如文件之类的所有流。
文件读写函数
按照字符读写文件:fgetc(), fputc()
按照行读写文件:fputs(), fgets()
按照块读写文件:fread(), fwirte()
按照格式化读写文件:fprintf(), fscanf()
按照随机位置读写文件:fseek(), ftell(), rewind()
按照字符读写文件:fgetc(), fputc()
fputc() 函数原型
int fputc(int ch, FILE * stream);
功能:将ch转换为unsigned char后写入stream指定的文件中
参数:
ch:需要写入文件的字符
stream:文件指针
返回值:
成功:成功写入文件的字符
失败:返回-1
另外,可是使用“a”,对文件进行追加。
缓冲区写文件
int main()
{
FILE *fp = NULL;
fp = fopen("../test.txt", "w");
if (fp == NULL) //返回空,说明打开失败
{
perror("open");
return -1;
}
char buf[] = "Asdfbsdogwseu";
int i = 0;
while (buf[i]!=0)
{
fputc(buf[i], fp);
i++;
}
fclose(fp);
system("pause");
return 0;
}
fgetc() 函数原型
int fgetc(FILE * stream);
功能:从stream指定的文件中读取一个字符
参数:
stream:文件指针
返回值:
成功:返回读取到的字符
失败:-1
int ReadFile()
{
FILE *fp = fopen("../test.txt", "r");
if (!fp)
{
perror("Open Fail!!!!");
return -1;
}
//读取一个
char ch = fgetc(fp);
printf("%c\n", ch);
//读取多个
char buff[128] = "";
int i = 0;
while ((buff[i++] = fgetc(fp)) != -1);
printf("%s\n", buff);
fclose(fp);
return 0;
}
如果读取的文件不是纯文本,就不能使用EOF作为文件的结尾
feof() 函数原型
int feof(FILE * stream);
功能:检测是否读取到了文件结尾
参数:
stream:文件指针
返回值:
非0值:已经到文件结尾
0:没有到文件结尾
int ReadFile()
{
FILE *fp = fopen("../test.txt", "r");
if (!fp)
{
perror("Open Fail!!!!");
return -1;
}
//读取非文本文件
char buff[128] = "";
int i = 0;
do
{
buff[i] = fgetc(fp);
i++;
} while (!feof(fp));
printf("%s\n", buff);
fclose(fp);
return 0;
}
COPY文件操作
int CopyFile()
{
char ch = 0;
FILE *src = fopen("../test.txt", "r");
FILE *dst = fopen("../my_test.txt", "w");
if (!src || !dst)
{
perror("FAIL!!!!");
return -1;
}
while (1)
{
ch = fgetc(src);
if (feof(src))
{
break;
}
fputc(ch, dst);
}
fclose(src);
fclose(dst);
return 0;
}
cat文件操作–将文件输出到终端显示
int CATFile()
{
char ch = 0;
//1、读取文件
FILE *fp = fopen("../test.txt", "r");
if (!fp)
{
perror("Open Fail!!!");
return -1;
}
while (1)
{
ch = getc(fp);
if (feof(fp)) // feof()读取为非0的时候,读取到文件末尾,需要break
{
break;
}
fputc(ch, stdout);
}
fclose(fp);
return 0;
}
按照行读写文件:fputs(), fgets()
fputs()函数原型
int fputs(const char * str, FILE * stream);
功能:将str所指定的字符串写入到stream指定的文件中, 字符串结束符 ‘\0’ 不写入文件。
参数:
str:字符串
stream:文件指针
返回值:
成功:0
失败:-1
int WriteLine2File()
{
FILE *fp = fopen("../123.txt", "w");
if (!fp)
{
perror("Open Fail!!!");
return -1;
}
char buff[] = " xnvolwseihgxn;lZl;aebnsdcv;lsdf;gnlz.f'awdlcbviy\0VAepqkw[pgsfhnoQ]\W2LK";
fputs(buff, fp); //写入时,遇到'\0'结束
fclose(fp);
return 0;
}
fgets()函数原型
char * fgets(char * str, int size, FILE * stream);
功能:从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 ‘\0’ 作为字符串结束。
参数:
str:字符串
size:指定最大读取字符串的长度(size - 1)
stream:文件指针
返回值:
成功:成功读取的字符串
读到文件尾或出错: NULL
int ReadLine()
{
FILE *fp = fopen("../123.txt", "r");
if (!fp)
{
perror("Open Fail!!!");
return -1;
}
char buff[1024] = " ";
fgets(buff, sizeof(buff), fp);
fputs(buff, stdout); //输出到终端
fclose(fp);
return 0;
}
按照块读写文件:fread(), fwirte()
fwrite()函数原型
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式给文件写入内容
参数:
ptr:准备写入文件数据的地址
size: size_t 为 unsigned int类型,此参数指定写入文件内容的块数据大小
nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb
stream:已经打开的文件指针
返回值:
成功:实际成功写入文件数据的块数,此值和nmemb相等
失败:0
int My_write()
{
FILE *fp = fopen("../Fwrite.txt", "w");
if (!fp) { perror("Fail!!!"); return -1; }
STD num[3] = { {23,"lucy"},{34,"bods"},{13,"xcf"} };
//此处第二个参数为1,目的是为了返回值刚好是写入文件的字节数
int bytes = fwrite(num, 1, sizeof(num), fp);
printf("%d\n", bytes);
fclose(fp);
return 0;
}
fread()函数原型
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
int My_fread()
{
STD readnum[3];
memset(readnum, 0, sizeof(readnum));
char buff[1024] = " ";
int count = 0;
FILE *fp = fopen("../Fwrite.txt", "r");
if (!fp) { perror("Fail!!!"); return -1; }
//一次性读取
fread(readnum, 1, sizeof(readnum), fp);
for (int i = 0; i < 3; i++)
{
printf("%d %s\n", readnum[i].id, readnum[i].name);
}
分别读取
//for (int i = 0; i < 3; i++)
//{
// count = fread(&readnum[i], 1, sizeof(STD), fp);
// printf("count = %d bytes.\n", count);
// printf("%d %s\n", readnum[i].id,readnum[i].name);
//}
fclose(fp);
return 0;
}
按照格式化读写文件:fprintf(), fscanf()
fprintf()函数原型
int fprintf(FILE * stream, const char * format, …);
功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 ‘\0’ 为止。
参数:
stream:已经打开的文件
format:字符串格式,用法和printf()一样
返回值:
成功:实际写入文件的字符个数
失败:-1
int My_fprintf()
{
int year = 2025;
int month = 2;
int day = 16;
char buff[1024] = " ";
FILE *fp = fopen("../fprintf.txt", "w");
if (!fp)
{
perror("Fail");
return -1;
}
//方式一:采用组包函数--将数据组包写入文件中
sprintf(buff, "%04d:%02d:%02d\n", year, month, day);
fputs(buff, fp);
//方式二:使用fprintf,直接将某种格式的数据写入文件
fprintf(fp, "%04d:%02d:%02d\n", year, month, day);
fclose(fp);
return 0;
}
fscanf()函数原型
int fscanf(FILE * stream, const char * format, …);
功能:从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据。
参数:
stream:已经打开的文件
format:字符串格式,用法和scanf()一样
返回值:
成功:实际从文件中读取的字符个数
失败: - 1
注意:fscanf遇到空格和换行时结束
int My_fscanf()
{
int year = 0;
int month = 0;
int day = 0;
char buff[1024] = " ";
FILE *fp = fopen("../fprintf.txt", "r");
if (!fp)
{
perror("Fail");
return -1;
}
//方式一:拆包函数sscanf()
fgets(buff, sizeof(buff), fp);
sscanf(buff, "%d:%d:%d\n",&year, &month, &day);
printf("%d\n", year);
printf("%d\n", month);
printf("%d\n", day);
//方式二 : fscanf()
fscanf(fp, "%d:%d:%d\n", &year, &month, &day);
printf("%d\n", year);
printf("%d\n", month);
printf("%d\n", day);
fclose(fp);
return 0;
}
按照随机位置读写文件:fseek(), ftell(), rewind()
fseek()函数原型
int fseek(FILE *stream, long offset, int whence);
功能:移动文件流(文件光标)的读写位置。
参数:
stream:已经打开的文件指针
offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了 文件末尾,再次写入时将增大文件尺寸。
返回值:
成功:0
失败:-1
whence:其取值如下:
SEEK_SET:从文件开头移动offset个字节
SEEK_CUR:从当前位置移动offset个字节
SEEK_END:从文件末尾移动offset个字节
ftell()函数原型
long ftell(FILE *stream);
功能:获取文件流(文件光标)的读写位置。
参数:
stream:已经打开的文件指针
返回值:
成功:当前文件流(文件光标)的读写位置
失败:-1
rewind()函数原型
void rewind(FILE *stream);
功能:把文件流(文件光标)的读写位置移动到文件开头。
参数:
stream:已经打开的文件指针
返回值:
无返回值
typedef struct _TEACHER {
char name[64];
int age;
}Teacher;
void Test_fseek_ftell_rewind()
{
//写文件
FILE* fp_write = NULL;
//写方式打开文件
fp_write = fopen("./mydata.txt", "wb");
if (fp_write == NULL) {
perror("fopen:");
return;
}
Teacher teachers[4] = {
{ "Obama", 33 },
{ "John", 28 },
{ "Edward", 45 },
{ "Smith", 35 }
};
for (int i = 0; i < 4; i++) {
fwrite(&teachers[i], 1, sizeof(Teacher), fp_write);
}
//关闭文件
fclose(fp_write);
//读文件
FILE* fp_read = NULL;
fp_read = fopen("./mydata.txt", "rb");
if (fp_read == NULL) {
perror("fopen:");
return;
}
Teacher temp;
//读取第三个数组
fseek(fp_read, sizeof(Teacher) * 2, SEEK_SET);
fread(&temp, sizeof(Teacher), 1, fp_read);
printf("Name:%s Age:%d\n", temp.name, temp.age);
memset(&temp, 0, sizeof(Teacher));
fseek(fp_read, -(int)sizeof(Teacher), SEEK_END);
fread(&temp, sizeof(Teacher), 1, fp_read);
printf("Name:%s Age:%d\n", temp.name, temp.age);
rewind(fp_read);
fread(&temp, sizeof(Teacher), 1, fp_read);
printf("Name:%s Age:%d\n", temp.name, temp.age);
fclose(fp_read);
}
文件换行符问题
文件缓冲区问题
ANSI C标准采用“缓冲文件系统”处理数据文件 所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去 如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲 区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量)
那么文件缓冲区有什么作用呢?
如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。