C语言中的文件(一)

系列文章目录

C语言中的文件(二)-CSDN博客


前言

        在使用电脑写程序时,你知道数据是储存在哪的吗?我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失 了,等再次运⾏程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们可以使用文件。


一、文件介绍

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

1.1 程序文件

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

1.2 数据文件

        文件的内容不⼀定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输⼊数据,运行结果显示到显⽰器上。 其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使⽤,这里处理的就是磁盘上⽂件。我们重点介绍数据文件。

1.3 文件名

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

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

例如: c:\code\test.txt

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

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

        根据数据的组织形式,数据⽂件被称为文本文件或者二进制文件。 数据在内存中以⼆进制的形式存储,如果不加转换的输出到外存,就是二进制文件

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

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

         字符⼀律以ASCII形式存储数值型数据既可以⽤ASCII形式存储,也可以使⽤⼆进制形式存储。 如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占⽤5个字节(每个字符⼀个字节),而二进制形式输出,则在磁盘上只占4个字节(VS2019测试)。        

三、打开文件和关闭文件

3.1 流的概念

        针对文件、画面、键盘等的数据的输入输出操作,都是通过流进行的。我们可以想象成流淌着字符的河。我们所用到的printf函数或者scanf函数都用到了

下图printf函数将字符A、B、C输出到连接显示器的流而从键盘输入的字符会进入流中,scanf 函数会将它们取出来,并将它们的值保存至变量x。

3.1.1 标准流

        我们之所以能够如此简单方便地执行使用了流的输入输出操作,是因为c语言程序在启动时已经将标准流准备好了。

        标准流有三种。

  • stdin--标准输出流

       在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。

  • stdout--标准输入流

       大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中。

  • stderr--标准错误流

        用于写出的错误流,大多数环境中输出到显⽰器界面。

这是默认打开了这三个流,我们使⽤scanf、printf等函数就可以直接进行输⼊输出操作的。

stdin、stdout、stderr 三个流的类型是: FILE* ,通常称为文件指针

C语⾔中,就是通过 FILE* 的⽂件指针来维护流的各种操作的。  

3.2 文件指针FILE*

        缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。 每个被使用的文件都在内存中开辟了⼀个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明的,取名FILE。

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

       

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

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

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

⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量,这样使⽤起来更加方便。 下⾯我们可以创建⼀个FILE*的指针变量:

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

        定义pf是⼀个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是⼀个结构体变 量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够间接找到与它关联的文件。

        

3.3 打开文件和关闭文件

        在使用纸质笔记本时通常时先打开,然后再阅读或者在适当的地方书写。程序中的文件处理过程也同样如此。首先打开文件并定位到文件开头,然后找到要读取或写入的目标位置进行读写操作,最后将文件关闭

        打开文件的操作称为open。函数库中的fopen函数用于打开文件。

该函数需要俩个参数

第一个参数是要打开的文件名,第二个参数是文件类型及打开模式。

FILE打开成功返回一个指向对象的指针,打开失败则返回空指针。

关闭文件使用的是fclose();在程序最后记得关闭同时给文件指针的值置为NULL;这样能有效避免野指针。        

下面我们使用''r''模式打开文件"abc.txt"。(文件类型有俩种,文本文件和二进制文件,这里我们先讲文本文件)。

#include <stdio.h>
int main()
{
	FILE* fp = fopen("abc.txt", "w");//打开文件
    
	if (fp != NULL)
	{
		fputs("fopen example", fp);
		fclose(fp);关闭文件
        fp = NULL;//避免野指针的出现
	}
	return 0;
}

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

        通过具体例子,介绍一下''r'' 、 ''w''  、 ''rb''  、 ''wb''  这四个模式。

''w''模式,如果没有要找的文件,他会在text.c所在的目录下建立一个新的文件

#include <stdio.h>
int main()
{
	FILE* fp = fopen("abc.txt", "w");//打开文件
    
	if (fp != NULL)
	{
		fputs("fopen example", fp);
		fclose(fp);关闭文件
        fp = NULL;//避免野指针的出现
	}
	return 0;
}

fopen中的路劲也可以是绝对路径,但是在写入代码是要注意转义,所以将\变成俩个\\,我们所写的是相对路径(相对与text.c( 我们程序的文件目录))

fputs是向fp所指向的文件中输出数据。我们输入的内容就会再文本文件中显示。(如果存在文件abc.txt,并且里面有所保存的内容,会将内容删除,输入代码所包含的数据)。

如果要在text.c所在目录上一级或者上上一级目录里建立。我们可以通过下面的代码来实现

.表示当前目录    ..表示上一级目录

perror函数是一个打印错误信息的函数,用法简单,一般放在每一个可能出现错误的代码后面,perror()空格里面不写直接是错误信息,因此加上"fopen"可以明确表示是我们上面的fopen出错了。

int main()
{
	//FILE* fp = fopen("./abc.txt", "w");//当前目录写与不写都行

    FILE* fp = fopen("./../abc.txt", "w");//上一级目录
    //FILE* fp = fopen("./../../abc.txt", "w");//上上一级目录
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputs("fopen example", fp);
	fclose(fp);
	fp == NULL;
	return 0;
}

可见在文件名前加上./../就是在上一级目录建立。改成./../../就是在上上级目录里建立。

''r''模式:

 同样的代码,删除''abc.txt''文件之后,因为''r''模式没有找到文件(打开失败),fopen会返回一个空指针,此时fp == NULL

如果存在该文件,则会读取该文件内容,然后再进行操作。

接下来我们介绍一下顺序读写,再下面介绍''wb'',''rb''.

四、文件的顺序读写

        4.1 顺序读写函数的介绍

          函数名功能               适⽤于
          fgetc字符输⼊函数          所有输⼊流
          fputc字符输出函数          所有输出流
          fgets文本行输⼊函数          所有输⼊流
           fputs文本行输出函数          所有输出流
           fscanf格式化输⼊函数          所有输⼊流
           fprintf格式化输出函数                  所有输出流        
           fread⼆进制输⼊                文件
           fwrite⼆进制输出                文件

在没了解这些之前,我们使用的printf,scanf都是标准输出,输入流(stdout、stdin),都是在终端(及屏幕上)输入输出。如下图

而我们顺序读写函数(除了fwrite、fread)适用于所有输出、输入流。那么它们也可以在屏幕上显示。

4.1.1 fgetc与fputc 

介绍前俩个函数。

fgetc的参数表示输入到哪fputc的是将第一个参数输出到第二个参数(输出到哪)。俩个读取失败都是返回EOF。fgetc读取失败或者读取到文件末尾就是返回的是EOF.

在向文本文件输入输出时是通过ASCII值来进行的。

#include <stdio.h>
int main()
{
	char a = fgetc(stdin);//从屏幕上输入
	fputc(a,stdout);//从屏幕上输出
	return 0;
}

记下来我在''w''、''r''模式下使用这俩个函数。首先介绍''w''中写文件,fputc

#include <stdio.h>

int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fputc('a', pf);
	fputc('b', pf);
	fputc('c', pf);
	fputc('d', pf);

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

在改善一下把26个字母全写进去。再加上换行。

#include <stdio.h>

int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	/*fputc('a', pf);
	fputc('b', pf);
	fputc('c', pf);
	fputc('d', pf);*/
	int i = 0;
	for (i = 0; i < 26; i++)
	{
		fputc('a' + i, pf);
		fputc('\n', pf);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

当然将for循环里的pf改成stdout则是在屏幕上输出

接下来介绍读文件,fgetc。当然首先我们文件中得有数据,我们在data.txt文本文件中写入abcd。

//fputc这俩步可以和printf("%c\n",ch);是一个效果

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int ch = fgetc(pf);//将文件内容输出到屏幕上
	fputc(ch, stdout);
	fputc('\n', stdout);//fputc这俩步可以和printf("%c\n",ch);是一个效果

	ch = fgetc(pf);
	fputc(ch, stdout);
	fputc('\n', stdout);

	ch = fgetc(pf);
	fputc(ch, stdout);
	fputc('\n', stdout);

	ch = fgetc(pf);
	fputc(ch, stdout);
	fputc('\n', stdout);

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

接下来让我们练习一下,练习将一个文件的内容拷贝到另一个文件中

#include <stdio.h>
int main()
{
	//打开文件,从中读取文件
	FILE* pfread = fopen("data1.txt", "r");
	if (pfread == NULL)
	{
		perror("fopen->data1.txt");
		return 1;
	}
	//打开文件,写入数据。
	FILE* pfwrite = fopen("data2.txt", "w");
	if (pfwrite == NULL)
	{
		fclose(pfread);//当写入文件出错,我们应该关掉data1.txt
		pfread = NULL;
		perror("fopen->data1.txt");
		return 1;
	}
	//数据的读写(拷贝)
	//fgetc读取失败或者读取到文件末尾就是返回的是EOF;
	int ch = 0;
	while ((ch = fgetc(pfread)) != EOF)
	{
		fputc(ch, pfwrite);
	}
	//关闭文件
	fclose(pfread);
	pfread = NULL;
	fclose(pfwrite);
	pfwrite = NULL;
	return 0;
}

运行成功之后。

4.1.2 fgets与fputs

        fputs和fputc,前者是输出字符串,后者是输出单个字符

        fgets和fgetc,也是如此。但是fgets要注意它的参数,它有三个参数

第一个和第三个参数是stream输入到string中。第二个参数是输入多少

在读文件"r"模式下

在文件"data.txt"中写入abcdefghihehe

测试下面的代码看是否从文件中读取到了内容。fgets就是从文件中输入内容到数组arr中

#include <stdio.h>
int main()
{
	// 打开文件
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件
	char arr[20] = "xxxxxxxxxxxxxxxxxxx";
	fgets(arr, 10, pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行之后,文件的内容确实在arr中。发现它会为'\0'预留一个位置。所以实际上它读取的内容是9个。

同样可以从屏幕上输入。

#include <stdio.h>
int main()
{
	// 打开文件
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件
	char arr[20] = "xxxxxxxxxxxxxxxxxxx";
	fgets(arr, 10, stdin);
    fputs(arr,stdout);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

效果是同样的。注意'\0'是空白字符,不会显示到屏幕上

4.1.3 fscanf与fprintf

        fprintf与printf非常相似。  

fprintf只是多了一个参数。后面的省略号叫做可变参数列表。有兴趣可以了解我们只需要知道怎么用。我们给出下面的代码。

struct Stu
{
	char name[20];
	int age;
	float score;
};
#include <stdio.h>
int main()
{
	struct Stu s = { "zhangsan",20,90.5f };
	//打开文件
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fprintf(pf,"%s %d %.1f", s.name, s.age, s.score);

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

结构体的内容是否会被写入文件中?答案是会。

因此在使用fprintf进行输入的时候,第一个参数就是目的地。后面的参数和printf用法一样即可。

上面的代码fprintf中的pf改成stdout就是在屏幕上输出

fscanf与scanf也是如此。

同样是fscanf多了一个参数。在"r"模式下来进行fscanf的运用。在data.txt文件中保存结构体的数据

给出下面的代码:

注意fscanf是需要读取地址的,因为结构体中数组名name本身就是地址,而其他的不是因此需要取地址符号

struct Stu
{
	char name[20];
	int age;
	float score;
};
#include <stdio.h>
int main()
{
	struct Stu s = { 0 };
	//打开文件
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件//fscanf和scanf一样需要的是地址。
	fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));
    
	fprintf(stdout, "%s %d %.1f", s.name, s.age, s.score);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

输出的是后我们浮点数控制到了小数点后一位%.1f。如果是%f默认后面有六位小数

因此fscanf和scanf就只有一个区别。fscanf适用于所有输出流。后面的参数和scanf的使用方法是一样的。fscanf和fprintf可以说包含了scanf和printf的功能。

4.1.4 fread和fwrite

        在上面我们使用fprintf-文本的方式向文件内输出了结构体数据

下面我们来介绍最后俩个函数,使用二进制的方式输入文件data.txt中。

           fread⼆进制输⼊                文件
           fwrite⼆进制输出                文件

同时我们使用下面俩个读写模式

“rb”(只读)为了输⼊数据,打开⼀个⼆进制文件出错
“wb”(只写)为了输出数据,打开⼀个⼆进制文件建立⼀个新的文件

        fwrite的第一个参数时我们要输出的数据的起始位置第二个参数是每一个元素的大小单位是字节。第三个参数是一次要写入元素的个数。第四个参数是我们要输出到的文件位置

struct Stu
{
	char name[20];
	int age;
	float score;
};
#include <stdio.h>
int main()
{
	struct Stu s = { "zhangsan",20,90.5f };
	//打开文件
	FILE* pf = fopen("data.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//二进制的形式写文件
    //将s中的数据写入pf所指向的文件中,每个元素的大小是sizeof(s)
    //写入一个元素。
	fwrite(&s, sizeof(s), 1, pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

        代码输出的结果是

fwrite是以二进制的形式写入。会发现除了zhangsan,后面的数据我们都不能读懂。因为zhangsan的二进制和ASCII码值文本是一一对应的,而整形和浮点型的二进制对应的ASCII码值我们是看不懂的。但是如果是使用fread来输入是可以读取出来的。下面来学习fread

它的参数和fwrite的参数是一样的。fread的第一个参数时我们要输入的数据的起始位置第二个参数是每一个元素的大小单位是字节。第三个参数是一次要写入元素的个数。第四个参数是我们要输入到的文件位置

        注意不要认为将fread中的第一个参数和最后一个参数换位置就可以实现。要写入二进制数据到文件时必须使用fwrite。

        使用fread来读取二进制文件就可以,把文本中二进制的数据读取出来。

struct Stu
{
	char name[20];
	int age;
	float score;
};
#include <stdio.h>
int main()
{
	struct Stu s = { 0 };
	//打开文件
	FILE* pf = fopen("data.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//二进制的形式写文件
	fread(&s, sizeof(s), 1, pf);
	fprintf(stdout, "%s %d %f", s.name, s.age, s.score);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

以上就是顺序读写的内容。我们要在对应的模式下,用相匹配的函数来进行文件的操作。

如果你看完以上内容有所收获,请留下你宝贵的点赞和关注!谢谢你的观看!

  • 34
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值