文件操作详解

目录

一、什么是文件?

1.程序文件

2.文本文件

3.文件名

二、文件的打开和关闭

1.文件指针

2.文件的打开和关闭

三、文件的顺序读写

1.字符输出函数 fputc

2.字符输入函数 fgetc

3.文本行输出函数 fputs

4.文本行输入函数 fgets

5.格式化输出函数 fprintf

6.格式化输入函数 fscanf

7.二进制输出函数 fwrite

8.二进制输入函数 fread

四、文件的随机读写

1.fseek 函数

2.ftell 函数

3.rewind 函数

五、文本文件和二进制文件

 六、文件读取结束的判定

1.feof 函数

2.文件读取结束判断

七、文件缓冲区


本片文章主要介绍C语言中文件操作的相关内容以及文件操作中常使用的函数。下文介绍的函数均需要引用头文件<stdio.h>

一、什么是文件?

简单的说:磁盘上的文件是文件。

但是在程序设计中,我们一般谈的文件有两种:程序文件数据文件(从文件功能的角度来分类的)。

1.程序文件

包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境 后缀为.exe)。

2.文本文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件, 或者输出内容的文件。

本文讨论的是数据文件。 在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。

其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。

3.文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。

文件名包含3部分:文件路径+文件名主干+文件后缀

例如:

C : 2023 \ code \ test . txt

(文件路径 文件名主干 文件后缀)

为了方便起见,文件标识常被称为文件名

二、文件的打开和关闭

1.文件指针

缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE

例如,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型声明:

struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
       };
typedef struct _iobuf FILE;

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。

每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息, 使用者不必关心细节。

一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。

下面我们可以创建一个FILE*的指针变量:

FILE* pf;//文件指针变量

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。

也就是说,通过文件指针变量能够找到与它关联的文件。

2.文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。

在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。

ANSIC 规定使用fopen函数来打开文件,fclose函数来关闭文件。

 fopen:

参数filename为文件名,mode为文件的打开方式

打开方式如下:

文件使用方式含义如果指定文件不存在
“r”(只读)为了输入数据,打开一个已经存在的文本文件出错
“w”(只写)为了输出数据,打开一个文本文件建立一个新的文件
“a”(追加)向文本文件尾添加数据建立一个新的文件
“rb”(只读)为了输入数据,打开一个二进制文件出错
“wb”(只写)为了输出数据,打开一个二进制文件建立一个新的文件
“ab”(追加)向一个二进制文件尾添加数据出错
“r+”(读写)为了读和写,打开一个文本文件出错
“w+”(读写)为了读和写,建议一个新的文件建立一个新的文件
“a+”(读写)打开一个文件,在文件尾进行读写建立一个新的文件
“rb+”(读写)为了读和写打开一个二进制文件出错
“wb+”(读写)为了读和写,新建一个新的二进制文件建立一个新的文件
“ab+”(读写)打开一个二进制文件,在文件尾进行读和写建立一个新的文件

代码演示:

#include <stdio.h>
int main()
{
    FILE* pFile;
    //打开文件
    pFile = fopen("myfile.txt", "r");//读的方式打开,没有该文件会报错

    if (pFile == NULL)
    {
        perror("fopen:");//打印错误信息
        return 1;
    }
    //关闭文件
    fclose(pFile);
    pFile = NULL;
    return 0;
}

输出结果:fopen:: No such file or directory

#include <stdio.h>
int main()
{
    FILE* pFile;
    //打开文件
    pFile = fopen("myfile.txt", "w");//写的方式打开,没有该文件会创建一个
    
    if (pFile == NULL)
    {
        perror("fopen:");//打印错误信息
        return 1;
    }
    //关闭文件
    fclose(pFile);
    pFile = NULL;
    return 0;
}

三、文件的顺序读写

1.字符输出函数 fputc

fputc函数把参数 Char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。

参数:

1.Char是要被写入的字符。该字符以其对应的 int 值进行传递。

2.stream是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。

返回值:

如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。

代码演示:

#include <stdio.h>

int main()
{
    char ch;
    //文件不存在是创建文件
    FILE* fp = fopen("text.txt", "w");
    //判断是否打开成功
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    //向文件中写入字符
    for (ch = 'a'; ch <= 'z'; ch++)
    {
        fputc(ch, fp);
    }
    //关闭文件
    fclose(fp);
    fp = NULL;
    return(0);
}

结果:

注意:当使用"w"(写)的方式打开文件时,文件原来的内容会被覆盖

例:

#include <stdio.h>

int main()
{
    char ch;
    //文件不存在是创建文件
    FILE* fp = fopen("text.txt", "w");
    //判断是否打开成功
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    //关闭文件
    fclose(fp);
    fp = NULL;
    return(0);
}

当再次以"w"的方式打开且不写入数据时,文件内容被清空。

2.字符输入函数 fgetc

fgetc函数从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。

参数:

 stream是指向 FILE 对象的指针,该 FILE 对象标识了要在上面执行操作的流。

返回值:

该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF

EOF为文件结束标志,通常值为-1

代码演示:

首先在文件中写入要读取的数据,如:

#include <stdio.h>

int main()
{
    int c;
    FILE* fp = fopen("text.txt", "r");//以读的方式打开
    //判断是否打开成功
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    //读取字符
    while ((c = fgetc(fp)) != EOF)//直到遇到文件结束标志
    {
        printf("%c ", c);
    }
    //关闭文件
    fclose(fp);
    fp = NULL;
    return(0);
}

 输出结果:a b c

3.文本行输出函数 fputs

 fputs函数把字符串写入到指定的流 stream 中,但不包括空字符。

参数:

str是一个数组,包含了要写入的以空字符终止的字符序列。

stream是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流。

返回值:

该函数返回一个非负值,如果发生错误则返回 EOF

代码演示:

#include <stdio.h>
int main()
{
	//以写的方式打开
	FILE* fp = fopen("text.txt", "w");
	//判断是否打开成功
	if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }

	fputs("abcdefg", fp);
	fputs("123456", fp);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return(0);
}

结果:

如果要换行,则需要在字符串后方加上 '\n'

4.文本行输入函数 fgets

fgets函数从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。

当读取 (n-1) 个字符时(第n个字符放'\0'),或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

参数:

str是指向一个字符数组的指针,该数组存储了要读取的字符串。

n是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。

stream是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。

返回值:

如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。

如果发生错误,返回一个空指针。

代码演示:

#include <stdio.h>
int main()
{
	char arr[20] = { 0 };
	//以读的方式打开
	FILE* fp = fopen("text.txt", "r");
	//判断是否打开成功
	if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }

	fgets(arr, 10, fp);
	printf("%s", arr);
	fgets(arr, 10, fp);
	printf("%s", arr);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return(0);
}

输出结果:abcdefg
                  123456


 

5.格式化输出函数 fprintf

 fprintf函数发送格式化输出到流 stream 中。

参数:

stream是指向 FILE 对象的指针,该 FILE 对象标识了流。

format C 字符串,包含了要被写入到流 stream 中的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。

返回值:

如果成功,则返回写入的字符总数,否则返回一个负数。

代码演示:

#include<stdio.h>
struct stu
{
	char name[20];
	int age;
	double num;
};

int main()
{
	struct stu a = { "zhangsan",30,50.5 };
	//以写的方式打开
	FILE* fp = fopen("test.txt", "w");
	//判断是否打开成功
	if (fp == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//写入格式化数据
	fwrite(&a, sizeof(struct stu), 1, fp);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

结果:

6.格式化输入函数 fscanf

 fscanf函数从流 stream 读取格式化输入。

参数:fprintf函数

返回值:

如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF

代码演示:(文件内容见fprintf函数)

#include<stdio.h>
struct stu
{
	char name[20];
	int age;
	double num;
};

int main()
{
	struct stu a = {0};
	//以读的方式打开
	FILE* fp = fopen("text.txt", "r");
	//判断是否打开成功
	if (fp == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//读取格式化数据
	fscanf(fp, "%s %d %lf", a.name, &a.age, &a.num);
	printf("%s %d %lf", a.name, a.age, a.num);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

输出结果: zhangsan 30 50.500000

7.二进制输出函数 fwrite

  fwrite函数把 ptr 所指向的数组中的数据写入到给定流 stream 中。

参数:

ptr是指向要被写入的元素数组的指针。

size是要被写入的每个元素的大小,以字节为单位。

nmemb是元素的个数,每个元素的大小为 size 字节。

stream是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。

返回值:

如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。

代码演示:

#include<stdio.h>
struct stu
{
	char name[20];
	int age;
	double num;
};

int main()
{
	struct stu a = { "zhangsan",30,50.5 };
	//以写的方式打开
	FILE* fp = fopen("text.txt", "wb");
	//判断是否打开成功
	if (fp == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//写入二进制数据
	fwrite(&a, sizeof(struct stu), 1, fp);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

结果:

 根据写入文件的数据可用发现,除了字符串"zhangsan"外,其余数据是以乱码的形式呈现的。

这是因为记事本为文本编辑器,具体讲解见  “五、文本文件和二进制文件”

8.二进制输入函数 fread

fread函数从给定流 stream 读取数据到 ptr 所指向的数组中。

参数:

ptr是指向带有最小尺寸 size*nmemb 字节的内存块的指针。

size是要读取的每个元素的大小,以字节为单位。

nmemb 是元素的个数,每个元素的大小为 size 字节。

stream这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。

返回值:

成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。

代码演示:(文件内容见fwrite函数)

#include<stdio.h>
struct stu
{
	char name[20];
	int age;
	double num;
};

int main()
{
	struct stu a = { 0 };
	//以读的方式打开
	FILE* fp = fopen("test.txt", "rb");
	//判断是否打开成功
	if (fp == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//读取二进制数据
	fread(&a, sizeof(struct stu), 1, fp);
	printf("%s %d %lf", a.name, a.age, a.num);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

输出结果:zhangsan 30 50.500000

四、文件的随机读写

1.fseek 函数

fseek函数设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给的 whence 位置查找的字节数。

参数:

stream是指向 FILE 对象的指针,该 FILE 对象标识了流。

offset是相对 whence 的偏移量,以字节为单位。

whence是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:

常量描述
SEEK_SET文件的开头
SEEK_CUR文件指针的当前位置
SEEK_END文件的末尾

 返回值:

如果成功,则该函数返回零,否则返回非零值。

代码演示:

#include<stdio.h>
int main()
{
	FILE* fp = fopen("test.txt", "w");
	//判断是否打开成功
	if (fp == NULL)
	{
		perror("fopen:");
		return 1;
	}
	fputc('a', fp);
	fputc('b', fp);
	fputc('c', fp);
	fputc('d', fp);
	fseek(fp, -2, SEEK_CUR);
	fputc('e', fp);
    fputc('f', fp);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

结果:(依次输入字符abcd,此时指针指向d的后方,fseek函数将指针从当前位置向前移动2个字节,此时指针指向c处,再输入字符ef后,则覆盖字符cd)

2.ftell 函数

ftell函数返回给定流 stream 的当前文件位置。

参数:

stream是指向 FILE 对象的指针,该 FILE 对象标识了流。

返回值:

该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。

代码演示:

#include<stdio.h>
int main()
{
	FILE* fp = fopen("test.txt", "w");
	//判断是否打开成功
	if (fp == NULL)
	{
		perror("fopen:");
		return 1;
	}
	fputc('a', fp);
	fputc('b', fp);
	fputc('c', fp);
	fputc('d', fp);

	long ret = ftell(fp);
	printf("%d\n", ret);

	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

文件内容:

输出结果:4

3.rewind 函数

 rewind函数设置文件位置为给定流 stream 的文件的开头。

 参数:

stream是指向 FILE 对象的指针,该 FILE 对象标识了流。

代码演示:

#include<stdio.h>
int main()
{
	FILE* fp = fopen("test.txt", "w");
	//判断是否打开成功
	if (fp == NULL)
	{
		perror("fopen:");
		return 1;
	}
	fputc('a', fp);
	fputc('b', fp);
	fputc('c', fp);
	fputc('d', fp);

	long ret = ftell(fp);
	printf("%d\n", ret);

	rewind(fp);

	ret = ftell(fp);
	printf("%d\n", ret);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

输出结果:4  0

五、文本文件和二进制文件

根据数据的组织形式,数据文件被称为文本文件或者二进制文件

数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件

如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件

一个数据在内存中是怎么存储的呢?

字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而 二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。

测试代码:

#include <stdio.h>
int main()
{
 int a = 10000;
 FILE* pf = fopen("test.txt", "wb");
 fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
 fclose(pf);
 pf = NULL;
 return 0;
}

运行成功后:

文本方式打开:(乱码)

 

 二进制编辑器打开:

 转换过程如下:

 六、文件读取结束的判定

1.feof 函数

feof 函数测试给定流 stream 的文件结束标识符。

返回值:

当设置了与流关联的文件结束标识符时,该函数返回一个非零值,否则返回零。

简单来说:即当文件读取结束时,遇到文件结尾返回非零值,读取失败结束返回零。

牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束

代码演示:(文本文件)

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    int c; // 注意:int,非char,要求处理EOF
    FILE* fp = fopen("test.txt", "r");
    if (!fp)
    {
        perror("File opening failed");
        return EXIT_FAILURE;
    }
    //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
    while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
    {
        putchar(c);
    }
    //判断是什么原因结束的
    if (ferror(fp))
    {
        puts("I/O (读取时出错)error when reading");
    }
    else if (feof(fp))
    {
        puts("(成功到达文件结尾)End of file reached successfully");
    }
    fclose(fp);
    fp = NULL;
    return 0;
}

文件内容:

 输出结果:abcdefg(成功到达文件结尾)End of file reached successfully

2.文件读取结束判断

1.文本文件:判断返回值是否为 EOF或者 NULL。例如:

 fgetc 判断是否为 EOF

 fgets 判断返回值是否为 NULL

2.二进制文件:判断返回值是否小于实际要读的个数。

fread判断返回值是否小于实际要读的个数。

七、文件缓冲区

ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序 中每一个正在使用的文件开辟一块“文件缓冲区”。

从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。

从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓 冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。

缓冲区的大小根据C编译系统决定的。

 代码演示:

fflush函数强迫将缓冲区内的数据写回参数 stream 指定的文件中。

#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语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。 如果不做,可能导致读写文件的问题。

(本文中出现的函数描述、声明、参数、返回值等信息均摘自菜鸟教程)

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值