【C语言初阶】[图文] 二进制文件怎么看?C语言能对文件进行哪些操作?今天我来教你

文件操作

C语言中又可以直接操作文件的函数,你知道都有哪些吗?


一. 文件的分类

文件按照功能,区分为两类:程序文件、数据文件

两种文件类型的区分是相对的,并不是绝对的

  1. 程序文件

    比如:C语言的源程序文件( .c 为后缀的文件 )

    ​ 目标文件( 在Windows环境中 以 .obj 为后缀 )

    ​ 可执行程序文件( 在Windows环境中 以 .exe 为后缀 )

    ​ 等等

  2. 数据文件

    数据文件的内容,不一定是程序。可以是程序运行时所需要读取、改变的数据。

为什么说两种文件类型的区分是相对的?

比如:

存在两个源文件 test1.c test2.c

如果 test1.c 文件可以对 test2.c 文件中的数据进行读取等操作,那么test1.c 就是程序文件,test2.c 就是数据文件。


PS:以下讨论的均为数据文件


二. 文件的操作

2.1 文件指针

在学习文件指针之前,首先来了解一个概念:文件信息区

文件信息区:

每次打开一个文件,计算机都会在内存中开辟一块区域来存放该文件的各种信息(比如文件名、文件的状态、文件的地址、文件的大小等)。

这些信息都存放在一个结构体变量中,此结构体变量的类型默认被系统声明为 FILE。所以,被使用文件的文件信息区,本质上就是一个FILE 类型的结构体变量。

每当一个文件打开后,计算机会自动根据文件的状态、情况自动生成一个FILE类型的结构体变量,并存入该文件的各种信息。

FILE 类型的具体成员,内容。在不同的编译器中是不完全相同的,但是差别不大

我们使用 FILE 类型定义的结构体指针变量,就是一个文件指针变量

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

定义 pf 是一个指向 FILE 类型数据的指针变量,可以指向某个文件的文件信息区,通过文件信息区中存放的信息可以进一步访问该文件。所以,通过文件指针变量能够找到与其相关联的文件。

2.2 文件的打开与关闭

文件的打开操作及关闭操作,需要使用两个函数 fopen(文件打开函数)fclose(文件关闭函数)

fopen 文件打开
FILE* fopen( const char *filename, const char *mode );

第一个参数 filename,应该传入 需打开文件的文件名

尽量详细需要打开的文件名,如:C:\\Program Files\\TEST.c

若只传入 TEST.c,只会默认打开(创建).c 文件所在路径的 TEST.c 文件

第二个参数 mode,应该传入 表示文件打开模式(方式)的字符串

具体的模式有:

表示读写权限的:

字符串权限说明
"r"只读只允许读取,不允许写入。文件必须存在,否则打开失败。
"w"写入若文件不存在,则创建一个新文件;若文件存在,则清空文件内容
"a"追加若文件不存在,则创建一个新文件;若文件存在,则将写入的数据追加到文件的末尾
"r+"读写既可以读取也可以写入。文件必须存在,否则打开失败
"w+"写入既可以读取也可以写入。若文件不存在,则创建一个新文件;若文件存在,则清空文件内容
"a+"追加既可以读取也可以写入。若文件不存在,则创建一个新文件;若文件存在,则将写入的数据追加到文件的末尾

表示读写方式的:

字符串说明
"t"以文本文件方式读写。
"b"以二进制文件方式读写。

其实,文件打开方式由 r、w、a、t、b、+ 六个字符拼成,各字符的含义是:

  • r(read):读取
  • w(write):写入
  • a(append):追加
  • t(text):文本文件
  • b(binary):二进制文件
  • +:读取和写入

第二个参数 mode ,传参时,其实 读写权限和读写方式 是结合使用的(但必须将 读写方式 放在 读写权限 的中间或者尾部),不过 读写方式可以忽略不写(忽略的情况下,默认为 "t",即默认以文本文件的方式进行读写)

读写权限 及 读写方式 的结合使用,例:

读写方式放在读写权限的尾部 "rt""rb""r+t""r+b""wt""w+b""at"等等

读写方式放在读写权限的中间 "rt+""rb+""wt+""wb+"等等

fopen 函数的返回值 是 FILE* 类型的,返回的是所打开的文件的文件信息区的首地址,所以需要用 FILE* 类型的指针变量接收,然后可以通过此指针变量操作此文件信息。

fclose 文件关闭
int fclose( FILE* stream );

参数的类型是 FILE* 的指针变量,此指针变量 需指向 已打开文件的文件信息区的地址

例如

#include <stdio.h>
int main ()
{
 //打开文件
	FILE * pf = fopen ("test.txt","w");
	if (pf != NULL)
	{
	//文件操作
     //…………
	//关闭文件
		fclose (pf);
    	pf = NULL;
	}
	return 0;
}

2.3 文件的顺序读写

2.3.1 文件读写函数
功能函数名函数适用于
字符输入函数fgetcint fgetc(FILE* stream);所有输入流
字符输出函数fputcint fputc(int c, FILE* stream);所有输出流
文本行输入函数fgetschar *fgets(char* string, int n, FILE* stream);所有输入流
文本行输出函数fputsint fputs(const char* string, FILE* stream);所有输出流
格式化输入函数fscanfint fscanf(FILE* stream, const char* format [, argument ]...);所有输入流
格式化输出函数fprintfint fprintf(FILE* stream, const char* format [, argument ]...);所有输出流
二进制输入freadsize_t fread(void* buffer, size_t size, size_t count, FILE* stream );文件
二进制输出fwritesize_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream );文件

上面是对 打开的文件进行顺序读写时,可用到的函数。


2.3.2 单个字符读写

使用上边的函数,尝试向文件中写入字符:

fputc 输出字符函数
#include <stdio.h>

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		printf("打开文件失败\n");
		return 0;
	}
	fputc('c', pf);
    fputc('s', pf);
    fputc('b', pf);
    fputc('i', pf);
    fputc('t', pf);

	fclose(pf);
	pf = NULL;

	return 0;
}

我们使用fputc 函数成功在文件中写入了内容

fputc_FILE

不过这时候肯定会有疑惑,比如:fputc不是字符输出函数吗?为什么能往文件中输入字符?

需要知道为什么,就需要学习在文件操作中的以下两个概念:

  • 输入

    在一般的认知中,用键盘打字,就算是输入了。

    但在文件操作中,输入,指 从键盘获取的内容 存入 内存中;也可以指 文件中的内容 存入 内存中。输入的终点,是内存,而不是文件

    input

  • 输出

    与输入相反,在文件操作中,把 内存中的数据 输出显示到 屏幕上,或是 输出到 文件中。这就是输出操作。

    所以 用 fputc 字符输出函数,往文件中输入字符。

    output

我们用 fputc 函数,成功向文件中写入了字符,那么如何向屏幕上输出字符呢?需不需要先类似打开文件的操作呢?很显然不需要先打开屏幕什么的。为什么呢?

这里又需要引入三个概念:

在C语言程序运行时,会默认打开三个流:
stdin:标准输入流

stdout:标准输出流

stderr:标准错误流

三个标准流,都是 FILE* 类型的

当我们需要用fputc 函数,向屏幕输出字符的时候,只需要把目标文件地址改为 标准输出流 就可以了:

#include <stdio.h>

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		printf("打开文件失败\n");
		return 0;
	}
	fputc('a', stdout);
    fputc('b', stdout);
    fputc('c', stdout);
    fputc('d', stdout);

	fclose(pf);
	pf = NULL;

	return 0;
}

fputc_STDOUT

上面测试了fputc 输出字符函数,那么怎么样使用输入字符函数将文件内的数据,输入至内存中呢?

fgetc 输入字符函数

首先在 .c 源文件的路径下创建 test2.txt 文件,并输入相应的内容

#include <stdio.h>

int main()
{
    //以只读方式打开文件,需要先创建文件
	FILE* pf = fopen("test2.txt", "r");
	if (pf == NULL)
	{
		printf("打开文件失败\n");
		return 0;
	}
	int ch;
    //将 fgetc 的返回值存入 ch,再将 ch 内容输出
	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	fclose(pf);
	pf = NULL;

	return 0;
}

以上代码的运行结果如下(test2.txt 文件 在程序中被打开前 内容就为:abcdefg):

fgetc_FILE

我们将fgetc的返回值存入 变量ch 并输出,是因为fgetc读取成功的返回值就是读取的内容,屏幕上也输出了 abcd

!!! 这也说明了,如果读取成功,fgetc 函数的返回值就是 读取到的字符的 ASCII 值。

但是ch 为什么不用 char 类型呢?读取的内容不是字符吗?用 char 类型的变量来接收也可以吗?

答案是不行。为什么?

fgetc_RETURN

在这句话中我们可以看出,fgetc 将读取到的字符以 int 类型返回 或者 返回 EOF,表示读取错误 或 文件结尾。

说明 fgetc 的返回值,并不一定全都是 字符,也有可能是 EOF。所以我们要用 int 类型的变量接收。

fgetc 函数,传参传入的是 需要被存放到内存中的 文件的数据 的地址。并且,在程序执行时 屏幕上输出的内容是不同的,意味着 fgetc 函数读到的数据是不同的,但传入的参数均为 变量pf, 这就说明,fgetc 函数会将传入的地址向后移动一位(移动到下一次需要读取的数据的地址)

然后我们回过头来发现,上边我们使用fputc 字符输出函数的时候,每次传入的参数也是同一个变量,但是输出的字符位置却不一样,所以 fputc 函数的使用,也会将传入的地址向后移动一位,以便下一次输出不覆盖之前的输出。


2.3.3 整行字符读写

对文件的内容一行一行的读写,就需要用到这两个函数

fputs 文本行输出函数

fputs 函数 与 fputc 函数的使用方法类似,只不过本函数是输出一行,而另一个是输出单个字符

以下示例:

#include <stdio.h>

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		printf("打开文件失败\n");
		return 0;
	}
	fputs("Hello Bit\n", pf);
	fputs("Great\n", pf);

	fclose(pf);
	pf = NULL;

	return 0;
}

fputs_FILE

同样的,会改变传入的地址,会将传入的地址 向后移动输出的字符串位数 位

fgets 文本行输入函数

fgets 函数的使用方法,就与 fgetc 函数有很大的不同了。

char* fgets(char* string, int n, FILE* stream);

三个参数分别代表:

  • string:需要输入的字符串地址
  • n:需要输入到第几位
  • stream:读取的文件的文件指针

使用方法如下:

#include <stdio.h>

int main()
{
    // 程序运行前需要创建好 test2.txt,并输入相应的内容
	FILE* pf = fopen("test2.txt", "r");
	if (pf == NULL)
	{
		printf("打开文件失败\n");
		return 0;
	}
	char ch[100] = { 0 };
	fgets(ch, 3, pf);
	printf("%s", ch);

	fgets(ch, 3, pf);
	printf("%s", ch);

	fclose(pf);
	pf = NULL;

	return 0;
}

fgets_FILE

fgets 函数可以 自定义每次输入的字符长度 ,即 第二个参数 减 1

并且,每次输入到内存中,如果传参不变,会将已经输入到内存中的数据覆盖

fgets_MEMORY

若,传参大于文件中数据的长度,则输入完整

fgets(ch, 3, pf); >>>>>> fgets(ch, 100, pf);

fgets_FLIE_LONGTH


2.3.4 格式化数据读写

这里的这个格式化,不是格式化清空的意思。而是 有一定格式的数据,就是格式化数据。比如,结构体等自定义类型。

格式化的读写,需要用到这两个函数 fscanf 格式化输入函数fprintf 格式化输出函数

这两个函数,与 scanfprintf 长得很像。其实不仅长得像,用法也很类似:

fprintf 格式化输出函数

用结构体来举例:

#include <stdio.h>

struct student{
    char name[20];
    int age;
    char sex[10];
};

int main()
{
	struct student xxs = {"July", 20, "male"};
    FILE* pf = fopen("test.txt", "w");
    if(pf == NULL)
    {
        printf("文件打开失败\n");
        return 0;
	}
    fprintf(pf, "%s %d %s", xxs.name, xxs.age, xxs.sex);
    
    fclose(pf);
    pf = NULL;

	return 0;
}

程序运行结果如下:

fprintf_FILE

同样的,可以将文件指针改为 标准输出流 将内存中的数据输出到 屏幕上,这里就不演示了

fscanf 格式化输入函数

还是用结构体来举例,不过这次是将文件中的数据存入内存中:

#include <stdio.h>

struct student{
   char name[20];
   int age;
   char sex[10];
};

int main()
{
	struct student xxs = { 0 };
   FILE* pf = fopen("test2.txt", "r");
   if(pf == NULL)
   {
       printf("文件打开失败\n");
       return 0;
	}
   fscanf(pf, "%s %d %s", xxs.name, &(xxs.age), xxs.sex);
   printf("%s %d %s", xxs.name, xxs.age, xxs.sex);
   
   fclose(pf);
   pf = NULL;

	return 0;
}

程序运行结果如下:

fscanf_FILE

通过两个例子可以看出,fprintffscanf 两个函数,可以对内存或者文件中的 格式化的数据 进行读写的操作。并且呢,两个函数的的使用方法与 printf scanf 两个函数的使用方法 十分的相似。


2.3.5 二进制读写

二进制的读取和写入,顾名思义,就是将数据从内存以二进制的形式输出到文件(写入文件),或者将二进制的数据从文件中写入到内存(读取文件)

fwrite 二进制输出
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream );

fwrite 函数,数据从内存以二进制的形式输出到文件(写入文件)

此函数的参数表示的是:

  • const void* buffer :需要输出到文件的数据
  • size_t size :需要写入的数据的类型(大小)
  • size_t count :需要写入的数据的个数
  • FILE* stream :文件流

二进制输出示例:

#include <stdio.h>

struct Stu
{
	char name[20];
	int age;
	char sex[10];
};

int main()
{
	struct Stu stu[3] = { {"CSDN", 15, "Not"}, {"July", 19, "Male"}, {"Three", 20, "Male"} };
	FILE* pf = fopen("data.txt", "wb");		//以二进制输出形式打开文件(写入文件的形式)
	if (pf == NULL)
	{
		printf("打开文件失败\n");
		return 0;
	}
	fwrite(&stu, sizeof(struct Stu), 3, pf);

	fclose(pf);
	pf = NULL;

	return 0;
}

fwrite_FILE

文件以记事本打开,发现数据存在乱码,那么究竟是不是二进制数据呢?

fwrite_READ

以二进制编辑器打开,可以发现确实是二进制数据

fread 二进制输入
size_t fread(void* buffer, size_t size, size_t count, FILE* stream );

二进制输入与二进制输出相反,可以将文件中的二进制数据,输入到内存中(读取文件中的二进制数据)

fread 函数的参数表示的是:

  • void* buffer :需要输入内存的地址
  • size_t size :读取的文件中的数据的类型的大小
  • size_t count :读取的数据的个数
  • FILE* stream :需要读取的文件流

二进制输入(读取二进制数据)的示例:

#include <stdio.h>

struct Stu
{
	char name[20];
	int age;
	char sex[10];
};

int main()
{
	struct Stu stu[3] = {0};
	FILE* pf = fopen("data.txt", "rb");		//以二进制输入形式打开文件(读取文件的形式)
 // 打开的文件就是 上边的示例文件
	if (pf == NULL)
	{
		printf("打开文件失败\n");
		return 0;
	}
	fread(&stu, sizeof(struct Stu), 3, pf);

	printf("%s %d %s\n", stu[0].name, stu[0].age, stu[0].sex);
	printf("%s %d %s\n", stu[1].name, stu[1].age, stu[1].sex);
	printf("%s %d %s\n", stu[2].name, stu[2].age, stu[2].sex);

	fclose(pf);
	pf = NULL;

	return 0;
}

fread_FILE



介绍过这些对文件顺序读写操作函数后,我们发现,这些函数每执行一次,文件指针 就会自然而然地按照排列顺序移动至下一个读写对象。这也就是为什么被称为顺序读写的原因。

2.4 文件的随机读写

上面介绍的都是对文件进行顺序读写操作时,能使用到的函数。被称为顺序读写操作,是因为以上函数的每一次执行,文件指针就会 按顺序移动至下一个需要读写对象。那么,随机读写又是因为什么呢?

这里的随机并不是不可定的意思,而是不用按照顺序的顺序,具体作何解释,就介绍完随机读写函数在做总结吧。

2.4.1 定位(指定)文件指针
fseek
int fseek( FILE *stream, long offset, int origin );

fseek 函数的功能是,根据文件指针 的位置和偏移量 来定位文件指针(或 通过给定文件指针 的位置和偏移量 来指定文件指针的位置)

本函数的参数含义是:

  • FILE *stream :文件流

  • long offset :偏移量

    就是需要指定 文件指针 从初始位置偏移的位数

  • int origin :文件指针 开始偏移的初始位置

    此参数 C语言 给定了三个宏:

    SEEK_CUR

    文件指针当前在文件流内容中的位置;(即 不改变文件指针的位置,使文件指针 从当前位置 开始偏移)

    SEEK_END

    此文件流内容的末尾;(即 将文件指针指向文件流内容的末字符之后,使文件指针 从从文件流内容的末位 开始偏移)

    SEEK_SET

    此文件流内容的开始;(即 将文件指针指向文件流内容的首位,使文件指针 从文件流内容的首位 开始偏移)

fseek 函数到底如何使用呢?具体作用究竟是什么呢?:

首先,我们先创建一个文件(我这里路径是 D:\TEST.txt ),并输入内容

fseek_TEST

当我们不使用 fseek 函数时,

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
	FILE* pf = fopen("D:\\TEST.txt", "r");
	if (pf == NULL)
	{
		printf("fopen::%s", strerror(errno));
		return 0;
	}
	int ch = 0;
	for (int i = 0; i < 10; i++)
	{// 进行 10 次循环 
		ch = fgetc(pf);
		printf("%c\n", ch);
	}

	fclose(pf);
	pf = NULL;

	return 0;
}

这段代码的运行结果是:

此时,文件指针应该在 文件内容的 k 字符上。如果再使用 ch = fgetc(pf) ,并输出 ch 存入的字符,将输出 k

但是如果这时候我们使用 fseek 函数,就可以将文件指针定位到文件内容的其他地方,时文件指针指向的文件内容改变:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
	FILE* pf = fopen("D:\\TEST.txt", "r");
	if (pf == NULL)
	{
		printf("fopen::%s", strerror(errno));
		return 0;
	}
	int ch = 0;
	for (int i = 0; i < 10; i++)
	{// 进行 10 次循环 
		ch = fgetc(pf);
		printf("%c\n", ch);
	}

	fseek(pf, 10, SEEK_CUR);	//使文件指针,从当前位置向后偏移 10 个字符
	//fseek(pf, 15, SEEK_SET);	//使文件指针,从文件内容的首位,向后偏移 15 个字符
	//fseek(pf, -5, SEEK_END);	//使文件指针,从文件内容的末字符之后,向后偏移 -5 个字符(向前偏移 5 个字符)
   
   ch = fgetc(pf);
   printf("ch = %c\n", ch);

	fclose(pf);
	pf = NULL;

	return 0;
}

fseek 函数,三次使用的运行结果 分别为:

fseek(pf, 10, SEEK_CUR);

文件指针从当前位置向后偏移 10 个字符,到 u

fseek(pf, 15, SEEK_SET);

文件指针从文件内容的首位,想后偏移 15 个字符,到 p

fseek(pf, -5, SEEK_END);

文件字符从文件内容的末字符之后,向 前 偏移 5 个字符,到 v


2.4.2 返回偏移量

因为对文件进行随机读写操作 可能造成操作者不知道 文件指针此时的位置。所以为了能够确定 文件指针此时指向的位置,就可以使用 ftell 函数。

ftell

ftell 函数,可以返回 文件指针相对于文件内容初始位置 的偏移量

long ftell( FILE *stream );

ftell 函数没有什么需要特别注意的地方,了解一下如何使用就足够了:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
	FILE* pf = fopen("D:\\TEST.txt", "r");
	if (pf == NULL)
	{
		printf("fopen::%s", strerror(errno));
		return 0;
	}
	int ch = 0;

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	long ret = ftell(pf);
	printf("%ld\n", ret);

	fclose(pf);
	pf = NULL;

	return 0;
}

上述代码的运行结果:

两次 fget(pf) 之后,计算偏移量 为 2;

2.4.3 返回初始位置
rewind
void rewind( FILE *stream );

rewind 函数可以将 文件指针 重新指向 文件内容的初始位置。

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
	FILE* pf = fopen("D:\\TEST.txt", "r");
	if (pf == NULL)
	{
		printf("fopen::%s", strerror(errno));
		return 0;
	}
	int ch = 0;

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	long ret = ftell(pf);
	printf("%ld\n", ret);

	rewind(pf);
	ret = ftell(pf);
	printf("%ld\n", ret);

	fclose(pf);
	pf = NULL;

	return 0;
}

代码运行结果:


以上就是部分的文件操作函数,本篇的文件操作函数 就只介绍到这里

传统功夫,以点到为止

如果想要了解 学习更多的 文件操作函数,可以参考 Win32 API 或者 Cplusplus 等网站自行学习

  • 14
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七月.cc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值