C语言文件操作 - 基本操作|二进制文件|读取结束判定|文件缓冲区

1.为什么使用文件

在C语言中,文件操作是非常重要的,因为它允许程序与外部文件进行交互。

文件是储存在硬盘中的,也就是说即使程序终止,数据仍然存在。

通过文件操作,可以实现读取文件中的数据,将数据写入文件,创建新文件,删除文件,以及在文件中移动位置等等。

文件为同时程序提供了灵活性和功能性,它使程序可以处理各种类型的数据,如文本文件、图像文件、音频文件等。这些文件也可以供多个程序可以共享。例如,多个程序可以读取同一个配置文件,也可以将数据写入同一个日志文件。

2.文件是什么

文件是计算机系统中存储数据的基本单元。

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

程序文件:

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

数据文件:

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

这篇博客主要讨论的是数据文件,后面将介绍用C语言对数据文件的一些操作。

每个文件都有一个唯一的文件名用来标识它,

文件名通常由文件名前缀和文件扩展名组成。文件名前缀是文件名的主体部分,可以包含任何字符,但通常由字母、数字和下划线组成。文件扩展名是文件名后缀,通常由一个或多个点号后跟一个或多个字符组成,用于指示文件类型。例如,myfile.txt 是一个文本文件,image.jpg 是一个图像文件。

要注意,同一路径下文件名是唯一的,这是我们后面根据路径精准访问文件的基础。

3.文件的打开与关闭

3.1文件指针

C语言的文件指针是一种特殊的指针,它指向文件中的某个位置,并且可以在文件中移动,以便对文件进行各种操作,例如打开、关闭、读取、写入、定位等。

在C语言中,文件指针是通过 FILE 结构体类型来表示的。每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态和文件当前的位置等)。

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。例如,这是Visual Studio中对文件类型的声明:

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* pf;//文件指针变量

 对于同时打开多个文件的情况,一般建议使用多个文件指针来维护多个文件信息区。

3.2文件的打开与关闭

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

//打开文件
FILE* fopen(const char* filename, const char* mode);
//关闭文件
int fclose(FILE* stream);

其中,对于fopen中的mode,意思是文件的打开方式,大致有以下几种:

 

(cplusplus.com) 

文件打开模式介绍
文件打开方式含义若指定文件不存在
"r"只读为了输入数据,打开一个已经存在的文本文件ERROR
"w"只写为了输出数据,打开一个文本文件建立一个新的文件
"a"追加向文本文件尾添加数据建立一个新的文件
"rb"只读为了输入数据,打开一个二进制文件ERROR
"wb"只写为了输出数据,打开一个二进制文件建立一个新的文件
"ad"追加向一个二进制文件尾添加数据建立一个新的文件
"r+"读写为了读和写,打开一个文本文件ERROR
"w+"读写为了读和写,建一个新的文件建立一个新的文件
"a+"读写打开一个文件,在文件尾进行读写建立一个新的文件
"rb+"读写为了读和写打开一个二进制文件ERROR
"wb+"读写为了读和写,新建一个新的二进制文件建立一个新的文件
"ab+"读写打开一个二进制文件,在文件尾进行读和写建立一个新的文件

读写文件时,需要:

1.打开文件

2.读写文件

3.关闭文件

那为什么我们在使用scanf,printf的时候似乎不用自己打开文件就能进行操作呢?

对于一个运行中的C语言程序,会默认打开3个流:

1.标准输入流  stdin

2.标准输出流  stdout

3.标准错误流  stderr

它们的类型都是 FILE*

当使用 scanf 函数时,它会从标准输入流读取数据。当使用 printf 函数时,它会将数据写入标准输出流。这些函数是C语言标准库提供的常用函数,简化了程序员对输入输出的操作。底层实现中,它们已经处理了与标准输入输出流相关的文件打开、读取和写入等操作,使得程序员在使用时无需关心文件的具体操作细节。

所以不是在用scanf,printf的时候没有打开文件,而是系统为我们自动打开了,我们没有发觉。

但是,如果我们要从文件读取数据或将数据写入文件,就需要显式打开文件。

4.文件的顺序读写

文件顺序读是指从文件开头开始,依次读取文件中的每个字节;文件顺序写是指从文件开头开始,依次写入文件中的每个字节。

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

4.1函数原型

fgetc

int fgetc ( FILE * stream );

fputc

int fputc ( int character, FILE * stream );

fgets

char * fgets ( char * str, int num, FILE * stream );

需要注意,该函数当读到num-1个字符时停止读取,或提前读到换行符也不再读取了

fputs

int fputs ( const char * str, FILE * stream );

fscanf

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

fprintf

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

fread

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

fwrite

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

使用样例:

写文件:

#include<stdio.h>

int main()
{
	FILE* pf = fopen("data.txt", "w");
	{
		if (pf == NULL)
		{
			perror("fopen");
			return 1;
		}
	}

	for (int i = 'a'; i < 'z'; i++)
	{
		fputc(i, pf);
	}



	fclose(pf);
	pf = NULL;

	return 0;
}

读文件 :

#include<stdio.h>

int main()
{
	FILE* pf = fopen("data.txt", "r");
	{
		if (pf == NULL)
		{
			perror("fopen");
			return 1;
		}
	}

	for (int i = 'a'; i < 'z'; i++)
	{
		int ch = fgetc(pf);
		printf("%c ", ch);
	}



	fclose(pf);
	pf = NULL;

	return 0;
}

4.2几组函数对比

1. `scanf` & `printf`
     `scanf`: 从标准输入流读取格式化的数据
     `printf`: 向标准输出流写格式化的数据
2. `fscanf` & `fprintf`
     `fscanf`: 适用于所有输入流的格式化输入函数
     `fprintf`: 适用于所有输出流的格式化输出函数
3. `sscanf` & `sprintf`
     `sscanf`: 从字符串中读取格式化的数据
     `sprintf`: 将格式化的数据转化为字符串

5.文件的随机读写

随机读写文件通常通过fseek()ftell()函数来实现。这些函数允许你在文件中移动指针,从而实现随机读写的功能。

5.1fseek介绍

函数原型:

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

 其中,stream是文件指针,offset是偏移量,origin是offset的参考,一共有三种:

SEEK_SET : 文件的起始位置
SEEK_CUR : 文件指针的当前的位置
SEEK_END : 文件的结束位置

 示例:

假设在项目路径下有data.txt的文本文档,内容如下:

如果我们要让文件指针中指向文档内容的指针移动到 'f' 的位置,可以用fseek来实现:

#include<stdio.h>

int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//定位文件指针到'f'
	fseek(pf, 5, SEEK_SET); //从文件开始的位置,定位到'f',偏移量是5
	char c = fgetc(pf);
	printf("%d\n", c);

	fclose(pf);
	pf = NULL;

	return 0;
}

5.2ftell介绍

每次读完文档中的某个数据时,文件指针中指向文档内容的指针都会移到下一个位置,为了知道这个位置的偏移量,进行其他随机读写,我们可以用ftell函数来获得偏移量

函数原型:

long int ftell ( FILE * stream );

5.3rewind介绍

void rewind ( FILE * stream );

作用:将文件指针中指向文档内容的指针指向文件开始的位置(偏移量为0) 

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

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

数据在内存中以二进制形式储存,如果不加转换地输出到外存,就是二进制文件

如果要求在外存上以ASCII码的形式输出到键盘的,则需要在储存前加以转换。以ASCII码形式储存的文件就是文本文件

如果有整数10000,如果以ASCII的形式输出到磁盘,则在磁盘中占用5个字节,而二进制输出,只占4个字节。

7.文件读取结束的判断

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

7.1文本文件示例

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int c;
    FILE* fp = fopen("test.txt", "r");
    if(fp == NULL) 
    {
        perror("fopen");
        return 1;
    }

    //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
    while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
   { 
       putchar(c);
   }

    //判断是什么原因结束的
    if (ferror(fp))
        puts("I/O error when reading");
    else if (feof(fp))
        puts("End of file reached successfully");
    fclose(fp);
    fp = NULL;
    
    return 0;
}

7.2二进制文件示例 

#include<stdio.h>

enum { SIZE = 5 };

int main(void)
{
    double a[SIZE] = {1.0,2.0,3.0,4.0,5.0};
    double b = 0.0;
    size_t ret_code = 0;
    FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式
    fwrite(a, sizeof(*a), SIZE, fp); // 写 double 的数组
    fclose(fp);
    fp = fopen("test.bin","rb");
    // 读 double 的数组
    while((ret_code = fread(&b, sizeof(double), 1, fp))>=1)
   {
        printf("%lf\n",b);
   }
    if (feof(fp))
        printf("Error reading test.bin: unexpected end of file\n");
    else if (ferror(fp)) {
        perror("Error reading test.bin");
   }
    fclose(fp);
    fp = NULL;
}

8.文件缓冲区 

ANSIC标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。文件缓冲区通常分为全缓冲、行缓冲和无缓冲三种类型。

文件缓冲区的意义:

文件缓冲区有助于提高文件I/O操作的效率,减少了频繁的磁盘访问,从而提高了程序的性能。 


参考链接:https://zh.cppreference.com/w/c

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值