文件(c语言文件流)

前言

什么是文件,文件就是存储在磁盘(硬盘)的文件。在程序设计中,我们一般谈的文件有两种:程序文件、数据文件 (文件功能的角度来分类)

程序文件包括源程序文件(后缀为.c),目标文件(windows下后缀为.obj),可执行程序(windows下为.exe)。而数据文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。(本文主要介绍数据文件)

根据数据的组织形式,数据文件被分为文本文件和二进制文件。

数据在内存中以二进制形式存储,如果不加转换的输出到外存的文件中,就是二进制文件,如果要求在外存上以ASCll码的形式存储,则需要在存储前转换。以ASCll字符的形式存储的文件就是文本文件。

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

字符一律以ASCll形式存储,数值型数据既可以用ASCll形式存储,也可以使用二进制形式存储。如有整数10000,如果以ASCll码的形式存储到磁盘上,则此盘占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上值占4个字节。(具体用ASCll码形式还是用二进制形式并没有严格规定)

如上图,以vs举例,通过以二进制形式打开文件并写入了一个10000的数据,4个字节,通过二进制编译器打开他的16进制(32位小端字节序)本来是00 00 27 10 ,反过来就是10 27 00 00。

文件的打开和关闭

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据。但是不同的外部设备输入和输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把它比喻成一个流淌字符的小河。

C程序针对文件、画面、键盘等的数据输入输出操作都是通过流操作的,一般情况下,我们要想象向流里写数据,从流里读取数据,都是要打开流,然后操作。

标准流

什么是标准流,一般就是指键盘,屏幕这些。那我们为什么在写一般的c程序时并没有打开标准流的操作呢,就只是展开头文件呢?

那是因为c语言程序在启动的时候,默认打开了3个流:

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

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

stderr - 标准错流,大多数环境中输出到哦显示器界面

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

stdin、stdout、stderr三个流的类型是:FILE*,通常称为文件指针。C语言中,就是通过FILE*的文件指针来维护流的各种操作的。

读和写介绍,我们一般把从外部设备(文件)读取数据并存储到内存中叫读。反之,把从内存中读取数据并存储到文件中叫写。

文件指针

文件指针是一个指向文件的抽象指针,用于标识文件中的当前位置。在C语言中,文件指针的类型是FILE*,他是一个结构体指针,包含了文件的相关信息(如文件描述符、缓冲区、当前位置等)。它描述一个文件信息区,也就是说每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息,也就是这个结构体指针FILE*。但是,不同的编译器FILE类型包含的内容不完全相同,也就是说定义有一定的差异。但也只是大同小异。每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不用很关心细节。一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更家方便。

文件的打开和关闭

文件在读写之前应该先打卡文件,在使用结束后应该关闭文件。在打开文件的时候,会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。

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

mode表示文件的打开模式,如下

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

注意文件打开的前提是,在你的目录中由文件,如果没有,不同的打开方式处理方式不同,有的会报错,有的则会直接给你新建一个文件。

文件的顺序读写函数

|-------------|-------------|-----------|
| 函数名 | 功能 | 适用于 |
| fgetc | 字符输入函数 | 所有输出流 |
| fputc | 字符输出函数 | 所有输出流 |
| fgets | 文本行输入函数 | 所有输出流 |
| fputs | 文本行输出函数 | 所有输出流 |
| fscanf | 格式化输入函数 | 所有输出流 |
| fprintf | 格式化输出函数 | 所有输出流 |
| fread | 二进制输入 | 文件输出流 |
| fwrite | 二进制输出 | 文件输出流 |

上面说的适用于所有输入流一般指适用于标准输入流和其他输出流(如文件输入流);所有输出流也是一样的。

具体代码

#include <stdio.h>
int main()
{
	int a = 10000;
	FILE* pf = fopen("text.txt", "wb");
	fwrite(&a, 4, 1, pf);
	fclose(pf);
	pf = NULL;
}

文件的随机读取函数

1.fseek:根据文件指针的位置和偏移量来定位文件指针(文件内容的光标)

#include <stdio.h>
int main()
{
	int a = 10000;
	FILE* pf = fopen("text.txt", "wb");
	fputs("This is an apple.", pf);
	fseek(pf, 9, SEEK_SET);
	fputs(" sam", pf);
	fclose(pf);
	pf = NULL;
}

2.ftell:返回文件指针相对于起始位置的偏移量

#include <stdio.h>
int main()
{
	FILE* pf = fopen("text.txt", "rb");
	long size;
	if (pf == NULL)
		perror("open file");
	else
	{
		fseek(pf, 0, SEEK_SET);
		size = ftell(pf);
		fclose(pf);
		pf = NULL;
		printf("%ld", size);
	}

	return 0;
}

3.rewind:让文件指针的位置回到文件的起始位置

#include <stdio.h>
int main()
{
	int x;
	FILE* pf;
	char buffer[27];

	pf = fopen("myfile_txt", "w+");
	for (x = 'A'; x <= 'Z'; x++)
		fputc(x, pf);
	rewind(pf);

	fread(buffer, 1, 26, pf);
	fclose(pf);
	pf = NULL;

	buffer[26] = '\0';
	printf("%s", buffer);

	return 0;
}

文件读取结束的判定

被错误使用的feof函数

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

feof函数的作用是:当文件读取结束的时候,判断读取结束的原因是否:遇到文件尾结束。

文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgets)

例如:

1. fgetc 判断是否为EOF

2. fgets 判断返回值是否为NULL

#include <stdio.h>
int main()
{
	int c;  //一定要int ,fgetc的返回值类型是int,非char,要处理EOF,
	FILE* pf = fopen("text.txt", "r");
	if (!pf)
	{
		perror("File opening failed");
		return 1;
	}
	//fgetc当读取失败或读取遇到文件结束的时候,都会返回EOF
	while ((c = fgetc(pf)) != EOF)
	{
		putchar(c);
	}
	//判断什么原因结束
	if (ferror(pf))
	{
		puts("error when reading");
	}
	else if(feof(pf))
	{
		puts("End of file reached successfully");
	}


	return 0;
}

二进制文件的读取结束判断,判断返回值是否小于实际要读的个数

例如:

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

#include <stdio.h>

int main()
{
	enum
	{
		SIZE = 5
	};

	double a[SIZE] = { 1.,2.,3.,4.,5. };
	FILE* pf = fopen("text.txt", "wb");
	fwrite(a, sizeof* a, SIZE, pf);
	fclose(pf);
	pf = NULL;
	double b[SIZE];
	pf = fopen("text.txt", "rb");
	size_t ret_code = fread(b, sizeof *b, SIZE, pf);
	if (ret_code == SIZE)
	{
		puts("Array read successfuly");
		for (int x = 0; x < SIZE; x++)
		{
			printf("%f ", b[x]);
		}
		putchar('\n');
	}
	else if (feof(pf))
	{
		printf("Error reading text.txt:unexpected end of file\n");
	}
	else if (ferror(pf))
	{
		perror("Error reading text.txt");
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

文件缓存区

ANSI C标准采用缓冲文件系统处理数据文件,所谓的缓冲文件系统是指系统自动地在内存中位程序中每一个正在使用的文件开辟一块文件缓冲区。从内存向磁盘输出数据会先送到内存中的缓存区,装满缓冲区才一起送到磁盘上。如果从磁盘像计算及读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定。

为什么存在文件缓存区

提高读写速度,简单来说缓存区是属于内存上的一块区域,当需要从磁盘上读取数据时,先读取到缓存中,从缓存中读取到CPU上是很快的,这是因为磁盘和CPU是两个单独设备,这两个不同设备从对方中读取是比较慢的,而且很多时候需要重复读取数据,这时候缓冲区的存在就大大减少了重复读写的开销,要注意缓冲区属于内存上的一块区域,它并不属于一块独立的外部设备。这一点对于计算机的爱好者应该是了如指掌。但是,对于初入计算机的学习者需要加强一下了解。

相关推荐
Nebula嵌入式23 分钟前
【C语言】main函数解析
c语言
SomeB1oody25 分钟前
【Rust自学】17.3. 实现面向对象的设计模式
开发语言·设计模式·rust
itclanCoder1 小时前
在php中怎么打开OpenSSL
开发语言·php
慕璃嫣1 小时前
Haskell语言的安全开发
开发语言·后端·golang
迂幵myself1 小时前
14-6-2C++STL的list
开发语言·c++·list
crossoverpptx1 小时前
C++的类Class
开发语言·c++
SY师弟2 小时前
团体程序设计天梯赛-练习集——L1-025 正整数A+B
c语言·数据结构·c++·算法·c#·图论·gplt
qq_447663059 小时前
java-----多线程
java·开发语言
a辰龙a9 小时前
【Java报错解决】警告: 源发行版 11 需要目标发行版 11
java·开发语言