【C语言】详细的文件操作相关知识

1、为什么使用文件

  在之前,我们的数据都是在程序结束后就都销毁了,如果在程序结束后仍然有些数据需要保留,文件这一功能将能很好的解决这个问题。
  使用文件我们可以将数据存在电脑磁盘上,做到数据持久化。

2、什么是文件

文件分为两种

  1. 程序文件:包括源文件(.c),目标文件(.obj),可执行文件(.exe)。
  2. 数据文件:数据文件是程序从磁盘中输入放入内存的文件,或者是将程序数据输出放入磁盘中的文件。

  在之前,我们所表示的输入输出指的是从内存与终端的交互,比如将内存中的数据输出到屏幕,将键盘输入的数据保存在内存中。

  这章,变成了内存与磁盘的交互,我们会将数据输出到磁盘,或是将磁盘数据输入到内存中使用。

3、文件的打开和关闭

3.1、文件指针

  每个被使用的数据文件都在内存中开辟了一块区域,这块区域称为文件信息区,用来储存文件的相关信息。这些信息是保存在一个结构体变量中的,该结构体类型是有系统声明的,取名为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* pf;

这样pf指针就指向了这个数据文件开辟的文件信息区,以便我们后续操作对文件内容进行更改。
在这里插入图片描述

3.2、文件的打开和关闭

C语言规定,文件打开使用fopen函数,文件关闭使用fclose函数。

使用fopen函数打开文件,函数将返回一个指针指向这个文件。
filename 代表着文件路径,如果只填文件名将在和源文件同一目录下查找文件名,如果通过绝对路径,将更准确访问。
mode 代表文件的读写方式。

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

文件打开后必须关闭文件,不然会存在资源占用,并且无法再打开这个文件。

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

打开文件和关闭文件

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//操作文件
	//...
	fclose(pf);
	return 0;
}


文件使用方式

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


4、文件的顺序读写

4.1、什么是流

什么是流?
在这里插入图片描述

在C语言中给我们提供了三种流
stdin(标准输入流 键盘)
stdout (标准输出流 屏幕)
stderr (标准输出错误流 屏幕)


4.2、fputc和fgetc

fputc是字符输出函数,能够将内存中的数据输出到文件或者屏幕上。

它使用于所有输出流int fputc ( int character, FILE * stream );

int main()
{
	//输出到文件就是写入数据到文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}

	char i = 0;
	for (i = 'a'; i <= 'z'; i++)
	{
		fputc(i, pf);//输出到文件中保存。
		fputc(i, stdout); //输出到屏幕显示。
	}

	fclose(pf);
	return 0;
}


fgetc是字符输入函数,能够从所有流中获取数据。
读取成功返回1,读取失败返回EOF(0)

int fgetc ( FILE * stream );

int main()
{
	//通过文件输入就是从文件中读数据
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}

	//读文件
	//int ch = fgetc(pf);
	//printf("%c\n", ch);
	//ch = fgetc(pf);
	//printf("%c\n", ch);
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c ", ch);
	}

	fclose(pf);
	pf = NULL;
	return 0;
}


4.3、fputs和fgets

fputs 将一串字符输出到流中。

适用于所有输出流 int fputs ( const char * str, FILE * stream );

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}

	fputs("hello bit\n", pf);//输出到文件
	fputs("hello bit\n", pf);//输出到文件
	
	fputs("hello bit\n", stdout);//输出到屏幕

	fclose(pf);
	pf = NULL;

	return 0;
}


fgets 是文本行输入函数,在流中将num个字符输入到str中

使用于所有输入流 char * fgets ( char * str, int num, FILE * stream );

返回有两种情况

  1. 如果流中字符数大于num,则从流中输入num-1个字符到str后,第num个字符输入\0,返回str。
  2. 如果流中字符数小于num,则从流中输入所有字符后,返回str。
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno)); //两种 先获得错误码信息再打印
		perror("fopen"); //直接打印错误信息
		return 1;
	}
	char s[10];
	char p[20];
	//4个字符加一个\0
	//返回字符串s
	fgets(s, 5, pf);
	printf("%s\n", s);

	//从文件中读取20个字符或读完,放入p数组中
	//返回字符串p
	fgets(p, 20, pf);
	printf("%s\n", p);

	fclose(pf);
	pf = NULL;

	return 0;
}

4.4、fscanf和fprintf

fprintf 是格式化输出函数,它把格式化的数据输出到流中。

使用于所有流 int fprintf(FILE* stream, const char* format);

struct S
{
	char arr[10];
	int age;
	float score;
};

int main()
{
	struct S s = { "zhangshan",25,50.5f};

	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写 (内存中的数据输出到磁盘里)
	//文件存储数据:zhangshan 25 50.5f
	fprintf(pf,"%s %d %f",s.arr,s.age,s.score);
	
	//屏幕输出:zhangshan 25 50.5f
	fprintf(stdout,"%s %d %f",s.arr,s.age,s.score);


	fclose(pf);
	pf = NULL;
	return 0;
}


fscanf 是格式化输入函数,将流中的数据格式化输入。

使用于所有流 int fscanf(FILE* stream, const char* format);

struct S
{
	char arr[10];
	int age;
	float score;
};

int main()
{
	struct S s = { 0 };

	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读 (磁盘中的数据输入到内存中)
	//将pf中数据格式化输入到结构体中
	fscanf(pf, "%s %d %f", &(s.arr), &(s.age), &(s.score));
	
	//两种打印到屏幕的方式
	/*printf("%s %d %f\n", s.arr, s.age, s.score);*/
	fprintf(stdout, "%s %d %f", s.arr, s.age, s.score);
	


	fclose(pf);
	pf = NULL;
	return 0;
}

4.5、fread和fwrite

前面的文件顺序读写函数都是适用于所有流,
而fread和fwrite只适用于文件,并且是都是通过二进制读写。

fwrite 是通过二进制输出,将内存中数据以二进制方式写入文件

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

  • ptr 代表输出到文件的被ptr所指向的数据
  • size 代表一次输出多少字节到文件
  • count 代表总共输出多少次
  • stream 代表文件流
  • 函数返回 成功输出多少次到文件。
struct S
{
	char arr[10];
	int age;
	float score;
};

int main()
{
	struct S s = { "zhangsan", 25, 50.5f };

	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//将结构体&s处的数据,以每次sizeof(struct S)字节,总共1次,输出到pf中
	fwrite(&s, sizeof(struct S), 1, pf);

	fclose(pf);
	pf = NULL;
	return 0;
}



fread 是通过二进制输入,将文件的二进制数据输入到内存中。

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

  • ptr 代表文件数据输入到ptr所指向的内存空间处。
  • size 代表每次输入多少字节。
  • count 代表一共输入多少次。
  • stream 代表文件流。
  • 函数返回一共成功读取了多少次。
struct S
{
	char arr[10];
	int age;
	float score;
};

int main()
{
	struct S s = { 0 };

	FILE* pf = fopen("test.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//二进制读
	fread(&s, sizeof(struct S), 1, pf);
	fprintf(stdout, "%s %d %f", s.arr, s.age, s.score);

	fclose(pf);
	pf = NULL;
	return 0;
}

4.6、sscanf和sprintf

sscanf和sprintf 和流就没有了关系,它们是字符串和格式化数据的关系。

sscanf 从字符串s中获得格式化数据。

int sscanf(const char* s, const char* format);

struct S
{
	char arr[10];
	int age;
	float score;
};

int main()
{
	struct S s = { 0 };
	char buf[100] = { "zhangsan 20 40.5f" };
	sscanf(buf, "%s %d %f", s.arr, &(s.age), &(s.score));

	printf("%s %d %f", s.arr, s.age, s.score);
	return 0;
}


sprintf 将格式化数据放入字符串中。

int sprintf(char* str,const char* format);

struct S
{
	char arr[10];
	int age;
	float score;
};

int main()
{
	struct S s = { "zhangsan", 20 ,40.5f };
	char buf[100] = { 0 };
	sprintf(buf, "%s %d %f", s.arr, s.age, s.score);

	printf("%s\n", buf);
	return 0;
}

5、文件的随机读写

文件随机读写的几个函数非常简单。

5.1、fseek

fseek 是定位文件指针的函数,能够通过文件指针的起始地址,偏移量定位指针。

int fseek ( FILE * stream, long int offset, int origin );

  • stream 是文件指针
  • offset 代表偏移量
  • origin 代表起始地址 起始地址有 SEEK_SET(0位置开始) SEEK_CUR(文件指针当前位置) SEEK_END(文件末尾位置)。
#include <stdio.h>
int main ()
{
  FILE * pFile;
  pFile = fopen ( "example.txt" , "wb" );
  fputs ( "abcdef" , pFile );
  fseek ( pFile , 3 , SEEK_SET );
  fgets (pFile); //d
  fclose ( pFile );
  return 0;
}

5.2、ftell和rewind

ftell 函数能告诉你文件指针现在处在的位置,从0开始算。

long int ftell ( FILE * stream );

rewind 函数能让文件指针返回到起始位置。

void rewind ( FILE * stream );

值得注意的是:fgetc()函数会使得函数指针向后偏移一位

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//文件显示abcdefgh
	
	fseek(pf, 3, SEEK_SET);
	char ch = fgetc(pf);//注意fetc(pf)使得pf往后移了一位
	printf("%c ", ch); //d
	printf("%d ", ftell(pf)); //4

	fseek(pf, 2, SEEK_CUR);
	ch = fgetc(pf);
	printf("%c ", ch);//g 因为fgetc使得pf再次向后移了一位

	fseek(pf, -3, SEEK_END);
	ch = fgetc(pf);
	printf("%c ", ch);//f


	//ftell 返回文件指针相对于起始位置的偏移量
	//long int ftell(FILE* stream);
	printf("%d ", ftell(pf));//6

	//rewind 让文件指针的位置回到文件的起始位置
	rewind(pf);
	printf("%d", ftell(pf));//0
	fclose(pf);
	pf = NULL;
	return 0;
}

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

通过文件顺序读写的函数,我们可以发现数据文件有两种,文本文件二进制文件

这就值得注意,如果我们想要存储一个10000的数据放入文件。
如果用文本文件,那么就需要存储5个字符(1个1,4个0),那么就会消耗5个字节,

而如果用二进制方式存储(fwrite),那么就只需要4个字节了,

所以不同存储方式,会使得文件大小不同。

7、文件读取结束的判定

7.1、feof

feof 函数是用来在文件读取结束后,判断文件结束的原因是否是由于到达文件尾结束。
又对于不同文件有着不同的判断方式,所以

  1. 对于文本文件,如果返回值是EOF(fgetc)或者NULL(fgets),那么就不是到达文件尾结束的,而是输入失败。
  2. 对于二进制文件,判断返回值是否小于实际要读的次数。如果小于那么就不是因为到达文件尾结束的,而是读取失败。如果==,那么就是到达文件尾结束的。
#include <stdio.h>

int main ()
{
  FILE * pFile;
  int n = 0;
  pFile = fopen ("test.txt","rb");
  if (pFile==NULL) 
  {
  	perror ("Error opening file");
  }
  else
  {
    while (fgetc(pFile) != EOF) 
    {
      ++n;
  	}
  	//读取结束后再判断,如果为真代表是到达末尾结束的。
    if (feof(pFile)) 
    {
      puts ("End-of-File reached.");
      printf ("Total number of bytes read: %d\n", n);
    }
    else puts ("End-of-File was not reached.");
    fclose (pFile);
  }
  return 0;
}

8、文件缓冲区

  在C语言标准,存在文件缓冲系统,文件缓冲系统是指系统自动为正在使用的文件创建一块文件缓冲区,从内存向磁盘输出的数据先送到内存中的输出缓冲区,待输出缓冲区存满后再一同放入磁盘中。如果从磁盘输入数据到内存中,也是先送到内存中的输入缓冲区,存满再送到程序数据区域。

在这里插入图片描述
本章完

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值