C语言——文件操作

 数据持久化的方法:

1.把数据存放到磁盘文件 (使用文件可以将数据直接存放在电脑的硬盘上,做到数据的持久化)

2.存放到数据库 

什么是文件呢?磁盘上的文件就是文件。

在程序设计中,一般谈及的文件有两种,从文件功能的角度来分类,有:

1.程序文件:如  源程序文件(.h)      目标文件(windows环境后缀为.obj)   可执行程序(windows环境后缀为.exe)

2.数据文件:文件的内容不一定是程序,也可以是程序运行时读写的数据,如某个程序运行时需要从中读取数据的文件,或是输出内容的文件。

本篇博客主要讨论数据文件

        在开始的C语言学习时,我们所处理数据的输入输出都是以终端为对象的,如从终端的键盘输入数据,运行结果显示到显示器上。                      如图所示。

#include<stdio.h>
 
int main()
{
   int a=0;
   scanf("%d",&a);
   printf("%d",a);
   return 0;
}

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

文件名:一个文件要有一个唯一的文件标识,以便用户识别和引用。方便起见,这个文件标识常被称为文件名。      如: C:\code\test.txt

文件名包括3部分: 文件路径  +  文件名主干  +文件后缀

                        如: C:\code\         test                .txt

注意:同一路径下不会出现同名文件。

文件的打开和关闭

文件指针

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

       每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字、文件状态、文件当前的位置等)。这些信息是保存在一个结构体变量中的,该结构体类型是由系统声明的,取名为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类型包含的内容不完全相同,但大同小异。

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

       我们通常是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。

打算读写(操作)一个文件时,会分为以下步骤:

1.打开文件

2.被打开的文件会维护一个文件信息区  

(如图所示,我们通常通过一个FILE的指针来维护这个FILE结构的变量

3.对文件进行读写等操作

4.关闭文件

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

fopen函数

filename:文件名       mode:打开模式

文件使用方式含义如果指定文件不存在
“r”(只读)为了输入数据,打开一个已经存在的文本文件出错

“w”(只写)

为了输出数据,打开一个文本文件建立一个新的文件
“a”(追加)向文本文件尾添加数据建立一个新的文件
“rb”(只读)为了输入数据,打开一个二进制文件出错

“wb”(只写)

为了输出数据,打开一个二进制文件建立一个新的文件
“ab”(追加)向一个二进制文件尾添加数据建立一个新的文件
“r+”(读写)为了读和写,打开一个文本文件出错

“w+”(读写)

为了读和写,建立一个新的文件建立一个新的文件
“a+”(读写)打开一个文件,在文件尾进行读写建立一个新的文件
“rb+”(读写)为了读和写打开一个二进制文件出错

“wb+”(读写)

为了读和写,建立一个新的二进制文件建立一个新的文件
“ab+”(读写)打开一个二进制文件,在文件尾进行读和写建立一个新的文件

例如,

FILE *pf=fopen("data.txt","w");

"w" 双引号括住 意味着传字符串,即传首地址。

       这段代码意思为,为了输出数据,以只写的方式打开一个名为“data.txt”的文本文件,如果该文件不存在,则会建立一个名为“data.txt”的新文件。

相对路径绝对路径

写文件名时分为相对路径绝对路径

一、相对路径:指在当前程序路径下

例如,

FILE* pf = fopen("data.txt", "w");

       如果当前路径下没有名为“data.txt”的文件,该代码执行时,会在当前路径下建立一个名为“data.txt”的新文件。

补充

1.              . \\  代表指向当前目录    (如上图所示)   (这里把  这个符号加粗方便辨认)

 如:     FILE* pf = fopen(".\\x64\\data.txt", "w");          .\\  指当前目录

该代码意思为,在当前目录的名为x64文件中,以只写的方式打开一个名为“data.txt”的文本文件。   
2.              .. \\  代表指向上一级目录    (如图所示)

 如:     FILE* pf = fopen("..\\练习\\data.txt", "w");                     ..\\  指上一级目录
该代码意思为,在当前目录上一级目录中的名为练习文件中,以只写的方式打开一个名为“data.txt”的文本文件。 

3.              同理,..\\..\\   代表指向上一级目录的上一级目录    

如:     FILE* pf = fopen("..\\..\\Contact\\data.txt", "w");//                    ..\\..\\指上一级目录的上一级

该代码意思为,在当前目录上一级目录上一级目录中的名为Contact文件中,以只写的方式打开一个名为“data.txt”的文本文件。 

二、绝对路径:指定其他位置路径

打开文件时,我们也可以打开其他位置路径的文件

例如:我们在桌面上创建一个data.txt,查看它的属性

  C:\Users\ASUS\Desktop\data.txt 就是它的文件名

FILE* pf = fopen("C:\\Users\\ASUS\\Desktop\\data.txt", "w");

该代码意思为,以只写的方式在桌面上打开一个名为“data.txt”的文本文件。 

(写   ‘\\’  是因为存在转义字符)  

fclose函数

#include<stdio.h>

int main()
{
    //打开文件
	FILE* pf = fopen("data.txt", "w");

	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//关闭文件
	fclose(pf);
	pf = NULL;//置空
	return 0;
}

注意:打开文件后一定要记得关闭文件,同时把FILE类型的指针置空。

读写文件

       

读写文件分为   顺序读写    和   随机读写 (即指定位置读写)

文件的顺序读写

       首先思考一个问题,在读写文件时,首先要打开文件,再进行读写文件操作,那为什么使用scanfprintf时,我们没有打开键盘打开屏幕这些操作?

         因为C语言程序运行起来后,默认会打开3个流,也因此使用scanfprintf时无需考虑打开的问题。

1.标准输入流(stdin)      打开了这个流,才可以进行scanf、getchar等操作

2.标准输出流(stdout)    打开了这个流,才可以进行printf、putchar等操作

3.标准错误流(stderr)

三个流 的 类型皆为 FILE*             

       stdio.h是头文件的叫法,称之为标准输入输出,它针对的是键盘上输入把数据打印到屏幕上这些函数的总和。

       流是一个很抽象的概念,就如日常生活的水流,我们可以认为写程序处理的是数据流,在读数据和写数据时,计算机所涉及的外部设备是非常多的,就如输入时可以从键盘上输入、打开文件输入、使用摄像头输入数据,输出时可以打印到电脑屏幕上、可以打开文件输出、发送到网络上,我们会从不同的外部设备上面读数据,然后把数据输出到不同的外部设备上,而我们程序员不可能去学习各种各样外部设备的读写方式,因此流的概念应运而生,把数据流淌的过程抽象为流,要写数据时把数据写入流中,要读数据时把数据从流中读出,至于流如何与各种外部设备进行交互,我们无需去关心。

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

fputc函数

fputc函数:写字符到流中    

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

	fputc('a', pf);   //'a'会传字符的ASCII码值
    fputc('b', pf);
    fputc('c', pf);
    int i = 0;
    for (i = 0; i < 10; i++)
    {
		fputc('a' + i, pf);
		fputc('a' + i, stdout);//写成stderr结果一样,出现在屏幕上
    }

	fclose(pf);
	pf = NULL;//置空
	return 0;
}

fgetc函数

fgetc函数:从流中获取字符
读取到字符,会返回字符的ASCII码值,读取失败或者遇到文件末尾时会返回EOF

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

	int ch = fgetc(pf);
	printf("%c\n", ch);
    //文件指针默认打开文件时指向起始位置,读取完指针会默认往后走
	ch = fgetc(pf);
	printf("%c\n", ch);
	
	printf("%c\n", fgetc(stdin));//从键盘上读

	fclose(pf);
	pf = NULL;//置空

	return 0;
}

fputs函数

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


    fputs("hello bit ", pf);
	fputs("hehehe\n", pf);//要换行需要自行添加\n
	fputs("hehehe", pf);

	fputs("hello wxcb", stdout);
	
	fclose(pf);
	pf = NULL;//置空
	return 0;
}

fgets函数

fgets函数:从流中读取最多num-1个(或者遇到换行就不读了)  最后一个为'\0'  
//  将stream指向的存到str指向的

//读取成功,则返回str的地址;读取失败,则返回NULL

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

	
    char arr[10] = { 0 };
    fgets(arr, 10, pf);
    printf("%s\n", arr);
    
	fgets(arr, 10, pf);
    printf("%s", arr);

	fclose(pf);
	pf = NULL;//置空
	return 0;
}

fprintf函数

fprintf函数:将格式化的数据输出到文件中         //  ... 为可变参数

struct S
{
	int a;
	float s;
};

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


	struct S s = { 100,3.14f };//   3.14默认是double类型  3.14f是float类型
	fprintf(pf, "%d  %f", s.a, s.s);

	fclose(pf);
	pf = NULL;//置空
	return 0;
}

fscanf函数

fscanf函数:从文件中读取格式化的数据到其他位置

struct S
{
	int a;
	float s;
};

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


	struct S s = {0};
    fscanf(pf, "%d  %f", &(s.a), &(s.s));
    printf("%d  %f\n", s.a, s.s);

    fscanf(stdin, "%d  %f", &(s.a), &(s.s));//此时就相当于  scanf
    printf("%d  %f", s.a, s.s);
	
	fclose(pf);
	pf = NULL;//置空
	return 0;
}

补充:sprintf与sscanf函数

sprintf函数:把格式化数据转换成字符串

sscanf函数:从s字符串提取格式化数据放到后面 可变参数处

struct S
{
	int a;
	float s;
	char str[10];
};

int main()
{
  
	struct S s = { 100,3.14f,"hehe" };
	char arr[30] = { 0 };
	printf("%d %f %s\n", s.a, s.s, s.str);
	
	sprintf(arr, "%d %f %s", s.a, s.s, s.str);
	printf("%d %f %s\n", s.a, s.s, s.str);
	printf("%s\n", arr);
	printf("\n");
	
	struct S tmp = {0};
	sscanf(arr, "%d %f %s", &(tmp.a), &(tmp.s), &(tmp.str));
	printf("%s\n", arr);
	printf("%d %f %s\n", tmp.a, tmp.s, tmp.str);
	
	return 0;
}
scanf从标准输入流读取格式化的数据
printf向标准输出流写格式化的数据
fscanf适用于所有输入流的格式化输入函数
fprintf适用于所有输出流的格式化输出函数
sscanf从字符串中读取格式化的数据
sprintf将格式化的数据转换成字符串

fwrite函数

fwrite函数:从ptr指向的位置找 count 个大小为 size 的数据写到流中

count:元素个数    size:每个元素的字节数(byte)

struct S
{
	int a;
	float s;
	char str[10];
};

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


	struct S s = { 99,6.18f,"bit" };
	fwrite(&s, sizeof(struct S), 1, pf);

	fclose(pf);
	pf = NULL;//置空
	return 0;
}

fread函数

fread函数:从流里读数据放到ptr所指的位置空间
size_t:返回的值为实际读到的数据的个数

struct S
{
	int a;
	float s;
	char str[10];
};

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


	struct S s1 = { 0 };
	fread(&s1, sizeof(struct S), 1, pf);
	printf("%d %f %s", s1.a, s1.s, s1.str);

	fclose(pf);
	pf = NULL;//置空
	return 0;
}

文件的随机读写

fseek函数

fseek函数:根据文件指针的位置和偏移量来定位文件指针

stream:要定义的文件流    offset:偏移量   origin:起始位置

    如图所示为 文件“data.txt”

当打开文件时,文件指针默认指向文件的起始位置

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

	//要定位文件指针到f
	//fseek(pf, 5, SEEK_SET);//从起始位置开始读   正数代表向右偏移
	//int ch = fgetc(pf);
	//printf("%c\n", ch);

	//fseek(pf, -4, SEEK_END);//从结束位置开始读    负数代表向右偏移
	//ch = fgetc(pf);
	//printf("%c\n", ch);

	int ch = fgetc(pf);
	printf("%c\n", ch);//a

	ch = fgetc(pf);
	printf("%c\n", ch);//b

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

	fseek(pf, 2, SEEK_CUR);//从当前位置开始读
	ch = fgetc(pf);
	printf("%c\n", ch);


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

ftell函数

ftell函数:返回文件指针相对起始位置的偏移量

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



	int ch = fgetc(pf);
	printf("%c\n", ch);//a

	ch = fgetc(pf);
	printf("%c\n", ch);//b

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

	printf("%d\n", ftell(pf));//此时的偏移量


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

rewind函数


rewind函数:让文件指针的位置回到文件的起始位置

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


	int ch = fgetc(pf);
	printf("%c\n", ch);//a

	ch = fgetc(pf);
	printf("%c\n", ch);//b

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

	ch = fgetc(pf);
	printf("%c\n", ch);//d

	rewind(pf);
	ch = fgetc(pf);
	printf("%c\n", ch);//又变回了 a


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

文本文件和二进制文件

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

       数据在内存(程序运行所依赖的环境)中以二进制的形式存储,如果不加转换地输出到外存(即写到文件中去),就是二进制文件

(内存:程序运行所依赖的环境     外存:外部存储,硬盘)

       如果要在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件

一个数据在内存中是怎么存储的?

1.字符一律以ASCII码值形式存储,ASCII码值是整数,就是以整数的二进制形式存储

2.数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

        假设有个整数10000,如果以ASCII码的形式输出到磁盘, 则磁盘中占用5个字节(每个字符是一个字节) ,10000看作 字符1、字符0、字符0、字符0、字符0 五个字符的组合,放到文件中去,存的是每个字符的ASCII码值。这种存储方式是以文本的形式存进文件的,这种文件被称为文本文件

        不加任何转换,以二进制的形式,把10000的每一位当成一个字符,以数字字符的形式存到文件中去,这种文件被称为文本文件。

        而二进制形式输出,则在磁盘上只占4个字节(VS2013环境测试) 。10000 在内存中存储的是二进制的形式,把这个二进制的形式不加任何转换,直接写到文件中去,10000是个整数,占4个字节,这4个字节直接写到文件里去,文件里也只占4个字节。这个文件就被称为二进制文件

文件读取结束的判定

判断文件读取是否结束

1.文本文件

使用fgetc函数判断返回值是否为EOF

使用fgets函数判断返回值是否为NULL

2.二进制文件

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

feof函数

注意:在文件读取过程中,不能用feof函数的返回值 直接来判断文件是否结束   eof(end  of  file)

feof函数:当文件读取结束的时候,判断 是否 是因为 遇到了文件末尾 才 致使读取结束 的,就是已知读取结束了,用来分析具体原因的。

ferror函数

ferror函数:当文件读取结束的时候,判断 是否 是因为 发生了错误 才 致使读取结束 的,也是已知读取结束了,用来分析具体原因的。

文本文件读取的例子

int main()
{
	int ch = 0;//注意是int类型而不是char类型,要处理EOF
	FILE* pf = fopen("test.txt", "r");
	if (!pf)
	{
		perror("FILE opening failed");
		return 1;
	}

	while ((ch = fgetc(pf)) != EOF)//注意括号!!!
	{
		putchar(ch);
	}
	//判断是什么原因结束的
	if (feof(pf))
	{
		puts("End of file reached successfully");
	}
	else if (ferror(pf))//返回非0的值,说明读取遇到了错误
	{
		puts("I/O error when reading");
	}
	fclose(pf);
	pf = NULL;

	return 0;
}

实现文件的拷贝

//拷贝文件
int main()
{
	FILE* pfRead = fopen("data1.txt", "r");
	if (pfRead == NULL)
	{
		perror("open file for read");
		return 1;
	}

	FILE* pfWrite = fopen("data2.txt", "w");
	if (pfWrite == NULL)
	{
		perror("open file for write");
		fclose(pfRead);
		pfRead = NULL;
		return 1;
	}
	//读写文件
	int ch = 0;
	while ((ch = fgetc(pfRead)) != EOF)
	{
		fputc(ch, pfWrite);
	}

	//关闭文件
	fclose(pfRead);
	pfRead = NULL;
	fclose(pfWrite);
	pfWrite = NULL;

	return 0;
}

文件缓冲区

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

#include<stdio.h>
#include <windows.h>

int main()
{
	FILE* pf = fopen("test.txt", "w");
	fputs("abcdef", pf);//先将代码放在输出缓冲区
	printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
	Sleep(10000);
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
	printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
	Sleep(10000);
    //这里再次睡眠是因为fclose函数也会刷新缓冲区,为了证明是fflush刷新缓冲区的而不是fclose
	fclose(pf);
	//注:fclose在关闭文件的时候,也会刷新缓冲区
	pf = NULL;
	return 0;
}
  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值