C语言篇 +文件操作(营养鸡汤期末不挂科)

Posted IT莫扎特

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言篇 +文件操作(营养鸡汤期末不挂科)相关的知识,希望对你有一定的参考价值。


什么是文件

  • 什么是文件 磁盘上的文件是文件。

  • 但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件

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

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

本章只讨论数据文件,数据文件总结一下:就是你可以用程序去向文件中(输出信息 / 写)又或者从文件中(输入信息 /读)到你的程序中去,而在这里需要站在内存的角度想清楚文件的读和写

  • 在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。
  • 其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件,所以我们今天面对的输入输出对象就是文件

文件名

  • 文件名包含3部分:文件路径+文件名主干+文件后缀
  • 例如: c:\\code\\test.txt
  • 为了方便起见,文件标识常被称为文件名

文件类型

文件缓冲区

文件指针

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

  • 每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及
    文件当前的位置等)。

  • 这些信息是保存在一个结构体变量中的。

  • 该结构体类型是有系统声明的,取名FILE. 例如,VS2008编译环境提供的stdio.h 头文件中有以下的文件类型申明:

总结:

当文件被打开的时候会自动创建一个文件信息区,文件信息区是强关联该文件的,他会记录文件信息实际上他就是一个结构体变量,struct _iobuf 是文件的本质类型,FILE是由typedef定义出来的文件类型,他也是一个文件类型,
而文件信息区的类型是一个struct FILE类型

补充:

  • 不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
    每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关 心细节。
    一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便

  • 定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。

  • 通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件

//以只读的方式打开data文件,并返回该文件信息区的起始地址
FILE *pf1 = fopen("data1.txt,"r"");
FILE *pf2 = fopen("data2.txt,"r"");
FILE *pf3 = fopen("data3.txt,"r"");

如图所示是文件指针与文件信息区对应的关系

总结:文件信息区实际上是一个FILE类型的结构体变量,关于他是怎么创建的,其实是在文件被打开的时候就会自动生成,并且是关联这个文件的,最后再返回文件信息区的起始地址,有了文件信息区的起始地址就能找到文件

文件的打开和关闭

注意事项

  • 1、文件在读写之前应该先打开文件,
  • 2、在使用结束之后应该关闭文件,
  • 3、文件在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系
  • 4、ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件

文件打开函数的原型

FILE * fopen ( const char * filename, const char * mode );

filename:是打开文件名
mode 是以什么模式打开

文件关闭函数的原型

int fclose ( FILE * stream );
stream :传入一个文件指针

打开方式如下:

//函数功能打开一个文件
void filetest() 
{
	//以只读的方式“r”打开文件返回文件信息区的起始地址
	FILE *pf = fopen("data.txt","r");
	//如果打开失败返回NULL,就打印错误信息并退出程序
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//读文件
	//关闭文件,有点类似于free函数的写法
	flcose(pf);
	//指针置空防止成为野指针
	pf = NULL
}

int main() 
{
	filetest();
	return 0;
}

以只读的方式“r”打开 由于此时在我的工程中并没有data这个数据文件,所以会打印错误信息提示我没有此文件,说明我们的文件打开失败了

既然没有这个文件那么就创建一个文件

当我们的工程目录下多出了一个data.txt会是什么样呢

这个时候程序正常,并没有出错,
总结一下:
当以不指定路径的方式打开文件,默认先访问工程目录下,如果工程下找不到这个文件又以只读的方式 “r” 打开文件那么就会出错,

那么如何打开桌面上的文件呢?

其实桌面上的文件和从磁盘上打开文件并没有多大的区别,因为fopen的第一个参数只需要接受一个文件名(文件路径+文件名主干+文件后缀),和使用打开方式就行

这时在我的桌面上新建一个文本文件(数据文件),我们在程序中将他打开,先来观察这个文件所处的路径

有了文件的路径就只需要将文件路径传给fopen函数就行,而在这需要注意的是 \\ 遇到字符会变成转义字符,而为了防止他成为转义字符就需要用双斜杠将 / 转义成普通的斜杠,这样才是一个完整的路径

void filetest() 
{
	FILE *pf = fopen("C:\\\\Users\\\\26961\\\\Desktop\\\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	fclose(pf);
	pf = NULL;
}
int main() 
{
	filetest();
	return 0;
}

那么关于以只读 “r” 的方式打开文件的举例已经够了,再来看看以“w"(只写)的方式打开文件会是怎么样的呢,这里需要注意细节

可以看到如果桌面上没有这个文件,那么以 “ w ” 只写的方式打开文件会创建一个文件,我们再来看如果文件已经存在了再以“ w ”只读的方式打开会怎么样

很明显可以看出之前的信息明显是保存了,可是再次打开这个文件中却什么都没有,看到这里其实我们也应该直到这个fopen函数的脾气了,当文件不存在就会创建一个文件,而当文件存在就会销毁该文件的内容

文件的顺序读写

当我们的程序需要写入一个数据到文件当中去的时候对应的动作是(输出/写)这里用fputc表示,当我们从文件当中读取一个数据到内存中去的时候对应的动作是(输入/读),这里用fgetc表示,

文件读写的函数

字符输出函数fputc

int fputc( int c, FILE *stream );
c:要写入到文件的字符
stream :FILE结构的指针

int fputc( int c, FILE *stream )函数的使用,会将内存中的数据写入到文件中去,

void filetest() 
{
	FILE *pf = fopen("C:\\\\Users\\\\26961\\\\Desktop\\\\data.txt","w");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//顺序写入文件,将字符写到pf关联的文件中
	fputc('a',pf);
	fputc('b', pf);
	fputc('c', pf);
	fputc('d', pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

当程序成功执行起来的时候,可以看到文本文件已经存放了abcd四个字符

那么有没有读者会比较好奇fputc是怎么将字符写入到文件中的?
其实pf这个信息区里面存放了一个指针用来记录存取字符的位置,当存入字符后,指针会往后偏移,这样子一点一点地就将数据存放完了,

流的概念

fputc他是适用于所有输出流的(文件 / 标准输出),在我们c语言中有一个流的概念,流相当于水流,他是一种高度抽象的概念,而一些外部设备比如:文件、屏幕、网络、光盘、软盘,我可以从一些外部设备中读写一些数据,而不同外部设备的读写方式肯定不一样,而作为程序员不需要关心怎么向外部设备读写数据,就在这些外部设备该需要采用如何读写的方式这个问题交给谁的时候,c语言封装了流,程序员只需要关心如何往流里面输入数据,并不需要关心数据给谁,数据给谁都是流来考虑

  • c语言的程序只要一运行起来就会默认打开3个流:
  • 标准输出流 - stdout
  • 标准输入流 - stdin
  • 标准错误流 - stderr
  • 这3个流的类型都是FILE类型

fputc适用于所有输出流,这里再举例标准输出流(屏幕)

void fpunc() 
{	//将字符顺序输出到屏幕
	fputc('a',stdout);
	fputc('b', stdout);
	fputc('c', stdout);
	fputc('d', stdout);
}

打印结果

字符输入函数fgetc

// 适用于所有输入流
int fgetc( FILE *stream );
// stream:FILE结构的指针

fgetc适用于所有输入流,这个函数功能是可以从一个流里面读取数据然后返回,当然这里是从文件流里面读取数据

void filetest() 
{
	
	FILE *pf = fopen("C:\\\\Users\\\\26961\\\\Desktop\\\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//从文件流中读取数据返回存放到变量ch中
	int ch = fgetc(pf);
	printf("%c",ch);

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

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

	ch = fgetc(pf);
	printf("%c", ch);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

打印结果

其实pf这个信息区里面存放了一个指针用来记录存取字符的位置,当取出一个字符后,指针会往后偏移,这样子一点一点地就将数据依次取出来了

fgetc函数读取标准输入流(键盘)

void pfunc() 
{
	//从标准输入流读取字符存放到变量ch中
	int ch = fgetc(stdin);
	printf("%c\\n",ch);

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

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

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

打印结果

总结:

  • 1、fgetc()是适用于所有的输入流 ,它可以用于读入所有输入流
  • 2、fputc()函数是适用于所有的输入流,它可以用于输出所有输出流

文本行输出函数 fputs

功能:将一个字符串输出到文件中
函数原型:

int fputs( const char *string, FILE *stream );
string : 想要输出的字符串
stream : FILE*类型的结构体指针 
void filetest() 
{
	
	FILE *pf = fopen("C:\\\\Users\\\\26961\\\\Desktop\\\\data.txt","w");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//写文件,将字符串写到pf关联的文件中
	fputs("男人喜欢吃米饭\\n",pf);
	fputs("女人喜欢吃面食\\n", pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

程序运行后的结果:
可以看到字符串成功地写入进了文件中去,这个函数注意的地方是如果你想让他换行就必须再字符串中加入‘\\n’

文本行输入函数 fgets

这个函数的功能是从流中读取一个字符存放到一个字符指针中去,前提是字符指针指向的空间够大

char *fgets( char *string, int n, FILE *stream );
string : 数据存储位置
n :可读的最大字符数
stream :FILE结构的指针

文本行输入函数 fgets的使用以及注意事项

void filetest() 
{
	FILE *pf = fopen("C:\\\\Users\\\\26961\\\\Desktop\\\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//写文件,将字符串写到pf关联的文件中
	char arr[20] = { 0 };
	fgets(arr,5,pf);
	printf("%s", arr);
	fgets( arr,5,pf);

	printf("%s",arr);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

从程序运行的结果不妨猜测一下为什么,结果会是这样子

其实fgets这个函数的参数n可读的最大字符数虽然是5,以为可以读5个字符,实际上只能读到n - 1个字符,也就是4个字符,并且末尾会补‘\\0’保证有5个字符

那么如果是读取n = 20个他会全部读完吗?

void filetest() 
{
	
	FILE *pf = fopen("C:\\\\Users\\\\26961\\\\Desktop\\\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//已将读取的长度改为20
	char arr[20] = { 0 };
	fgets(arr,20,pf);
	printf("%s\\n", arr);
	fgets( arr,20,pf);

	printf("%s\\n",arr);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

从打印的结果可以看出不管n指定有多大,fgets只会读取一行数据,并不会为了填充这20个大小的目标而去读下一行字符串,

格式化输出函数 fprintf

函数功能:以指定的格式将数据输出到流里面去
fprintf函数的原型:

int fprintf( FILE *stream, const char *format [, argument ]...);
stream:FILE结构的指针

printf函数的原型和fprintf函数的原型对比:

我们可以看出fprintf和printf的函数原型好像基本是一样的,那么它们的使用是不是也基本一样呢?下面来试试看

void filetest() 
{
	FILE *pf = fopen("C:\\\\Users\\\\26961\\\\Desktop\\\\data.txt","w");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//写文件,将数据写到pf关联的文件中
	int arr[10] = { 0 };
	for (int i = 0; i < 10; i++) 
	{
		fprintf(pf, "%d\\n", arr[i]++);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
}

程序执行完的结果:

我们可以看出好像fprintf和printf的使用上没太大的区别,只是一个是输出到屏幕上去的,而另外一个是输出到文件上去的

格式化输入函数 fscanf

函数功能:以指定的格式读取出流里面的数据放到内存中去
fscanf函数的原型:

int fscanf( FILE *stream, const char *format [, argument ]... );

可以看出fscanf和scanf函数原型上其实也差不多,在这里就直接对标了哈哈哈

代码:

void filetest() 
{
	FILE *pf = fopen("C:\\\\Users\\\\26961\\\\Desktop\\\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//读文件,从文件流中拿去数据存放到a和b变量中
	int a = 0, b = 0;
	fscanf(pf,"%d %d", &a, &b);
	printf("%d %d", a, b);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

原本就存放在文件中的数据:

打印的结果:

二进制输出 fwrite

功能:以二进制的方式将程序中的数据输出到文件中

size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );

buffer:指向要写入数据的指针	
size:元素大小(以字节为单位)
count:要写的元素的最大个数
stream :FILE结构的指针

代码

struct s 
{
	int a;
	int b;
	char name[20];
};
void filetest() 
{
	//"wb":为了输出数据,打开一个二进制文件
	FILE *pf = fopen("C:\\\\Users\\\\26961\\\\Desktop\\\\data.txt", "wb");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//写文件,将数据写到pf关联的文件中
	struct s s = { 0,40 ,"zhangsan"};
	fwrite(&s,sizeof(struct s),1,pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

程序运行的实际效果,这里我们可以看到这样zhangsan的拼音是我们看的懂得,因为字符串本身就是以文本形式存放,而剩下得数据我们就看不懂了,因为是以二进制的形式写进文件里去的,二进制的形式写进去的跟以文本写进去的会有很大区别

既然以看不懂二进制的表示方式,那我们就用程序将这个文本文件的信息读取出来,“rb”(只读) 为了输入数据,打开一个二进制文件,二进制输入,fread登场

二进制输入 fread

功能:从一个流里面读取count个大小为size(单位字节)的数据存放到buffer里去

size_t fread( void *buffer, size_t size, size_t count, FILE *stream );

buffer:数据存储位置
size:元素大小(以字节为单位)
count:要读取的最大的元素个数
stream :FILE结构的指针

Fread返回实际读取的完整项的数量,如果发生错误或在达到count之前遇到文件结束,则可能小于count。使用feof或ferror函数来区分读错误和文件结束条件。如果size或count为0,fread返回0并且缓冲区内容不变。

对标一下我们可以发现,其实它们之间的差别就是一个加了const和没加const的区别,可实际上却并不是这点差别

以上是关于C语言篇 +文件操作(营养鸡汤期末不挂科)的主要内容,如果未能解决你的问题,请参考以下文章

自考数据结构第三章,栈队列数组,期末不挂科指南,第3篇

自考数据结构第六章查找,期末不挂科指南,第10篇

自考数据结构第三章,数组,期末不挂科指南,第5篇

自考大学本科那个数据结构怎么学,期末不挂科指南,第1篇

自考数据结构第五章图,期末不挂科指南,第9篇

自考数据结构中的线性表,期末不挂科指南,第2篇