文件操作详解

目录

​编辑

一、认识文件 

1.文件的作用

2. 文件分类

3.文件名

二、文件的打开与关闭

1.文件信息区

2.文件指针

3.文件的打开与关闭

四、文件的顺序读写

1.流的概念

2.文件输入与输出函数讲解

(1)fgetc/fgets

(2)fputc/fputs

(3)fscanf

(4)fprintf

(5)fwrite

(6)fread

五、文件的随机读写

1.fseek函数

2.ftell函数

3.rewind函数

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

七、文件读取结束的判定

1.被错误使用的feof

2.文本文件读取结束的判定

3. 二进制文件读取结束的判定

八、文件缓冲区


一、认识文件 

1.文件的作用

使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。

2. 文件分类

文件有两种:程序文件、数据文件(从文件功能的角度来分类的)

(1)程序文件

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

(2)数据文件

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

我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。

3.文件名

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

文件名包含3部分:文件路径+文件名主干+文件后缀,例如: c:\code\test.txt

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

二、文件的打开与关闭

1.文件信息区

文件的打开与关闭,最关键的概念是“文件类型指针”,简称“文件指针”,我们为了学习文件指针又需要了解文件信息区。

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.

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

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

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

2.文件指针

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

FILE* pf;这就是一个文件指针变量

定义pf是一个指向FILE类型数据的指针变量,可以使pf指向某个文件的文件信息区。通过该文件信息区中的信息就能够访问该文件。

3.文件的打开与关闭

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

打开文件:

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

const char * filename表示文件名, const char * mode表示操作对应的字符串,返回值为指向文件信息区的文件指针

以下位常见的文件操作的字符串:

“r”(只读)

为了输入数据,打开一个已经存在的文本文件

出错

“w”(只写)

为了输出数据,打开一个文本文件

建立一个新的文件

“a”(追加)

向文本文件尾添加数据

建立一个新的文件

“rb”(只读)

为了输入数据,打开一个二进制文件

出错

“wb”(只写)

为了输出数据,打开一个二进制文件

建立一个新的文件

“rb”(只读)

为了输入数据,打开一个二进制文件

出错

“wb”(只写)

为了输出数据,打开一个二进制文件

建立一个新的文件

“ab”(追加)

向一个二进制文件尾添加数据

出错

“r+”(读写)

为了读和写,打开一个文本文件

出错

“w+”(读写)

为了读和写,建议一个新的文件

建立一个新的文件

“a+”(读写)

打开一个文件,在文件尾进行读写

建立一个新的文件

“rb+”(读写)

为了读和写打开一个二进制文件

出错

“wb+”(读写)

为了读和写,新建一个新的二进制文件

建立一个新的文件

“ab+”(读写)

打开一个二进制文件,在文件尾进行读和写

建立一个新的文件

关闭文件:

int fclose ( FILE * stream );

FILE * stream是文件指针

#include<stdio.h>
int main()
{
    FILE* pf = fopen("c:\\code\\test.txt","r");//这里多加一个\起到转义字符的作用
    //将前面的文件按照只读的方式打开
    if(pf == NULL)
    {
        perror("fopen");
        //perror函数会先打印参数的字符串然后打印:最后再打印错误信息  
        return 1;          
    }
    fclose(pf);
    //关闭文件
    return 0;
}

四、文件的顺序读写

顺序读写就是文件从头一个一个字符向后读取,以下是顺序读写的操作函数:

字符输入函数

fgetc

所有输入流

字符输出函数

fputc

所有输出流

文本行输入函数

fgets

所有输入流

文本行输出函数

fputs

所有输出流

格式化输入函数

fscanf

所有输入流

格式化输出函数

fprintf

所有输出流

二进制输入

fread

文件

二进制输出

fwrite

文件

1.流的概念

流是信息转运的一个重要的概念,简而言之它可以抽象地被理解为数据的中转站。

类似于唱片、磁带和电脑手机中的mp3文件,它们都能存储音乐;播放唱片需要唱片机,播放磁带需要录音机,播放mp3音乐需要播放器软件,总之我们需要用不同的方式来听到音乐。

对于文件也一样,我们把数据写到文件中,对于不同的类型的文件我们的读写方式完全不同。那么程序员就需要了解每一种文件如何去储存数据,但是文件种类太多了,那样对于程序员学习压力太大了。这时我们就引入了流的概念。

对于数据我们的传输方式大概是这样:信息 -> 流 -> 目标位置

程序员只需要把数据放入到这个抽象的流中,C语言的底层逻辑就会帮助程序员将数据存放到指定文件内。

每一个C程序运行时都会默认打开三个流:标准输入流(standard input stream),接受从键盘读取的信息;标准输出流(standard output stream),将信息输出到键盘上;标准错误流(standard error stream)。

2.文件输入与输出函数讲解

(1)fgetc/fgets

int fgetc( FILE* stream );

  • 从文件中获取一个字符,放到内存中
  • FILE* stream为文件指针

char* fgets(char* string,int n,FILE* stream);

  • 从文件中获取一个字符串,放到内存中
  • char* string为字符串放入的起始地址,int n表示需要在文件中读取几个字符,FILE* stream为文件指针
#include<stdio.h>
int main()
{
	FILE* pf = fopen("c:\\code\\test.txt", "r");
	
	//字符输入函数   所有输入流
	char a = getc(pf);//读取文件第一个字符a
	printf("%c ", a);
	a = getc(pf);//读取文件第二个字符b
	printf("%c ", a);

	//文本行输入函数  所有输入流
	char arr[20] = { 0 };
	fgets(arr, 6, pf);//文件共有6个字符但此时读写应从c开始,会直接读取后面的四个字符和'\0'
	printf("%s ",arr);

	fclose(pf);
	return 0;
}
//操作前文件内的内容:abcdef
//结果:a b cdef

(2)fputc/fputs

int fputc( int c, FILE* stream );

  • 从内存中获取一个字符,放到文件中
  • (int c为字符的ASCII码值,FILE* stream为文件指针

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

  • 从内存中获取一个字符串,放到文件中
  • (const char* string为字符串的起始地址,FILE* stream为文件指针FILE* stream为文件指针
#include<stdio.h>
int main()
{
	FILE* pf = fopen("c:\\code\\test.txt", "w");
	//字符输出函数  所有输出流
	char a = 'a';
	fputc(a, pf);

	//文本行输出函数  所有输出流
	char arr[20] = "abcdefgh";
	fputs(arr, pf);

	fclose(pf);
	return 0;
}
//建立一个新文件,先输出a,再输出abcdefgh
//操作后文件的内容:aabcdefgh

(3)fscanf

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

  • 从文件中获取格式化的数据,放到内存中
  • FILE* stream为文件指针,const char* format表示有格式的数据输入方式
  • 可以简单理解为scanf函数的文件版本
struct person
{
	char name[20];
	int age;
	char sex[10];
	char address[20];
};
#include<stdio.h>
int main()
{
	FILE* pf = fopen("c:\\code\\test.txt", "r");
	struct person s1 = { 0 };
	//格式化输入函数  所有输入流
	fscanf(pf, "%s %d %s %s", s1.name, &s1.age, s1.sex, s1.address);
	printf("%s %d %s %s", s1.name, s1.age, s1.sex, s1.address);

	fclose(pf);
	return 0;
}
//文件中的内容:zhangsan 19 male Tianjin
//结果:zhangsan 19 male Tianjin

(4)fprintf

int fprintf( FILE* stream, const char* format);

  • 从内存中获取格式化的数据,放到文件中
  • FILE* stream为文件指针,const char* format表示有格式的数据输出方式
  • 可以简单理解为printf函数的文件版本
struct person
{
	char name[20];
	int age;
	char sex[10];
	char address[20];
};
#include<stdio.h>
int main()
{
	FILE* pf = fopen("c:\\code\\test.txt", "wb");
	//格式化输出函数  所有输出流
	struct person s = { "zhangsan",19,"male","天津市" };
	fprintf(pf, "%s %d %s %s", s.name, s.age, s.sex, s.address);
	fclose(pf);
	return 0;
}
//处理后文件中的内容:zhangsan 19 male 天津市

(5)fwrite

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

  • 从内存的变量中获取二进制数据,放到文件中
  • const void* buffer表示获取数据的位置,size_t size一个变量类型的大小,size_t count表示读多少个这样类型的数据,FILE* stream为文件指针
struct person
{
	char name[20];
	int age;
	char sex[10];
	char address[20];
};
#include<stdio.h>
int main()
{
	FILE* pf = fopen("c:\\code\\test.txt", "w");
	struct person s = { "zhangsan",19,"male","天津市" };
	//二进制输出  所有输出流
	fwrite(&s, sizeof(struct person), 1, pf);
	fclose(pf);
	return 0;
}
//文件中的内容:zhangsan 19 male 天津市

(6)fread

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

  • 从文件中获取二进制数据,放到内存的变量中
  • const void* buffer表示获取数据的位置,size_t size一个变量类型的大小,size_t count表示读多少个这样类型的数据,FILE* stream为文件指针
struct person
{
	char name[20];
	int age;
	char sex[10];
	char address[20];
};
#include<stdio.h>
int main()
{
	FILE* pf = fopen("c:\\code\\test.txt", "r");
	struct person s1 = { 0 };
	//二进制输入函数  所有输入流
	fread(&s1, sizeof(struct person), 1, pf);
	printf("%s %d %s %s", s1.name, s1.age, s1.sex, s1.address);
	fclose(pf);
	return 0;
}
//结果:打印zhangsan 19 male Tianjin

五、文件的随机读写

1.fseek函数

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

  • 根据文件指针的位置和偏移量来定位文件指针
  • SEEK_SET表示文件开头,SEEK_END表示文件结尾
  • FILE * stream表示文件指针, long int offset表示偏移量, int origin表示相对偏移量为0的位置
#include<stdio.h>
int main()
{
	FILE* pf = fopen("c:\\code\\test.txt", "wb");
	fputs("This is an apple.", pf);
	fseek(pf, 9, SEEK_SET);
	//SEEK_SET表示文件开头,9表示相对偏移量为9
	//这里文件指针被定位到以开头为0偏移量,偏移量为9的地方
	fputs(" sam", pf);
	//从an的n开始替换对应的字符
	fclose(pf);
	return 0;
}
//文件内数据:This is a sample.

2.ftell函数

long int ftell(FILE * stream);

  • 返回文件指针相对于起始位置的偏移量
#include <stdio.h>
int main()
{
	FILE* pf = fopen("c:\\code\\test.txt", "r");
	if (pf == NULL) 
		perror("Error opening file");
	else
	{
		fseek(pf, 0, SEEK_END); 
		long int size = ftell(pf);
		fclose(pf);
		printf("the size of this document are %ld bytes.\n", size);
	}
	return 0;
}
//文件内容:Tianjin is a good choice as a travel destination.
//结果:49

3.rewind函数

void rewind(FILE* stream);

  • 让文件指针的位置回到文件的起始位置
#include <stdio.h>
int main()
{
	int n= 0 ;
	FILE* pf = fopen("c:\\code\\test.txt", "w+");;
	for (n = 'A'; n <= 'Z'; n++)
	{
	    fputc(n, pf);
	}
	rewind(pf);//复位文件指针到初始位置
	char a = getc(pf);
	putchar(a);
	fclose(pf);
	return 0;
}
//文件内容:ABCDEFGHIJKLMNOPQRSTUVWXYZ
//结果:A

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

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

数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。二进制文件计算机看得懂,我们看不懂。

如有整数10000,如果以ASCII码的形式输出到磁盘,则10000被分为五个数字字符,占用5个字节(每个字符一个字节);如果以二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。

文本文件按字符储存

00110001 00110000 00110000 00110000 00110000(字符0的ASCII码值为48)

10000在二进制文件中按整形数据储存

0000 0000 0000 0000 0010 0111 0001 0000

   0      0        0       0       2       7      1        0

#include <stdio.h>
int main()
{
	int a = 10000;
	FILE* pf = fopen("test.txt", "wb");
	fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
	fclose(pf);
	pf = NULL;
	return 0;
}

七、文件读取结束的判定

1.被错误使用的feof

在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。这个函数应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。它只能判断文件的读取是否因为错误而停止。

2.文本文件读取结束的判定

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

3. 二进制文件读取结束的判定

  • 判断返回值是否小于实际要读的个数
  • fread判断返回值是否小于实际要读的个数
#include <stdio.h>
#include <stdlib.h>
int main(void) 
{
    int c; // 注意:int,非char,要求处理EOF
    FILE* fp = fopen("test.txt", "r");
    if(!fp)
    {
        perror("File opening failed");
        return EXIT_FAILURE;
    }
   //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);
}

八、文件缓冲区

  • ANSIC标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。
  • 从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。
  • 我们在关闭文件后,缓冲区内的数据也会传递到对应位置并清空缓冲区。
  • 缓冲区的大小根据C编译系统决定的。

我们在向磁盘输出数据时,需要向操作系统申请,如果我们有一点数据就向操作系统申请,那操作系统也不用干其他的活儿了。此时设置一个内存缓冲区临时存储数据,充满缓冲区时再向操作系统申请,这样就能减少对操作系统的影响。

输入:磁盘 -> 输入缓冲区 -> 程序数据区

输出:程序数据区 -> 输出缓冲区 -> 磁盘

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值