[c语言]文件操作

一.为什么使用文件

我们在本地IDE(集成开发工具,如vs)中的写的程序中的数据只有在程序运行时才会在存储到内存中,运行结束时数据就不复存在了。如果我们想程序结束时,数据仍然存在,那么就要涉及数据持久化问题。数据持久化的方法有:把数据放到磁盘文件中,存放到数据库等方式。

二.什么是文件

文件是计算机管理数据的基本单位,同时也是应用程序保存和读取数据的一个重要场所,文件是存储在外部介质(如磁盘)上的以文件名标识 的数据的集合。存储在磁盘上的文件称为磁盘文件, 与计算机相连的设备称为设备文件。这些文件都不在 计算机内,统称为外部文件。在程序设计中,将磁盘文件按照其分类按照文件的功能分为:程序文件(后缀为.c/.obj/.exe)和数据文件(程序运行时读写的数据);而数据文件按照其存储的内容又分为二进制文件和文本文件,我们着眼于磁盘文件
[c语言]文件操作_第1张图片

文件名:

为了区分不同的文件,必须给每个文件命名。
文件名由三部分组成:文件路径+文件名主干+后缀。例:c:\code\test.txt

  • 后缀决定了系统默认用哪种方式打开文件
  • 文件名可以是汉字、数字、英文字母(不区分大小写)、特殊符号$#&@()- [ ] ^ ~等及几者的任意组合。
  • 文件名中允许使用空格,但不允许使用下列字符 / \ | :" * ?

常见文件的扩展名有.doc(Word文档)、.xls(Excel电子表格)、.ppt(Powerpoint演示文稿)、.txt(文本文档)、.rar(压缩文件)、.mp3(音乐)、.wma(音乐)、.wav(音乐)、.lrc(歌词文件)、.rmvb(视频)、.rm(视频)、.mp4(视频)、.3gp(视频)、.swf(flash动画文件)、.jpg(图片)、.gif(图片)、.bmp(图片)、.exe(程序,可执行文件)。

三.文件的打开与关闭

类比喝水:1.先打开瓶盖。2.喝水或者灌水。3.盖上瓶盖-----文件的操作也是如此:打开文件,读/写文件,关闭文件

文件指针:

类似整形指针,文件指针存放的是文件。
当你使用一个文件时,会在内存开辟一块空间(文件信息区),用来保存文件的信息(文件名,文件所在位置等等),这些信息是存放在一个由系统创建的FILE类型的结构体变量中。fopen函数会返回这个变量的地址,通常用一个FILE类型的结构体指针来指向该变量,以便使用者可以操作文件。

//例如:vs2013中对该结构体有如下定义:
struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
       };
typedef struct _iobuf FILE;

//文件指针的定义
FILE* pf;      //通过文件指针可以找到打开的文件

文件的打开:

ANSIC规定用fopen函数打开文件。
:::
FILE*_ fopen (const char_ * filename, const char * mode);
:::
const char * filename:有两种写法:
1.绝对路径:包括文件路径。如:“c:\code\test.txt”
2. 相对路径:不包括文件路径,默认路径为当前目录。如:“test.txt”
注意:

  • 在绝对路径中用\会被当做转义序列,解决办法如下:
    1. 用\替换\。
    2. 用/替换\,因为Windows会把/当作目录分隔符。

const char* mode:文件的打开方式

mode 功能 文件不存在
“r” 以只读方式打开文本文件 打开失败
“w” 以只写方式打开文本文件 创建一个新文件
“a” 以追加的方式打开文本文件 创建一个新文件
“r+” 为了读写,打开文本文件 打开失败
“w+” 为了读写,打开文本文件 创建一个新文件
“a+” 打开一个文本文件在文件末尾读写 创建一个新文件
“rb” 以读的方式打开一个二进制文件 创建一个新文件
“wb” 以写的方式打开一个二进制文件 创建一个新文件
“ab” 向一个二进制文件末尾追加数据 打开失败
“rb+” 为了读和写打开二进制文件 打开失败
“wb+” 为了读和写,建立一个二进制文件 创建一个新文件
“ab+” 打开二进制文件,在文件尾进行读和写 创建一个新文件

注意:

  • 当文件用于读和写时,不能读和写同时操作,需要使用文件定位函数,和fflush(刷新缓冲区)函数。

FILE :fopen返回值*

  1. 打开成功则返回一个文件指针,程序可以把此指针存储在一个变量中。
    1. 常见使用方式:FILE* pf = fopen(“test.txt”, “r”);
  2. 打开失败则返回一个空指针。可能是因为没有访问权限、文件不存在、文件位置不对。

注意:

  • 打开文件后一定要检测是否打开成功, 以确保不是空指针。方法如下:
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
    perror("fopen::");
    return;
}

文件的关闭:

通常用fclose函数来关闭不在使用的文件
:::
int fclose(FILE* stream);
:::
int 返回值:

  • 成功关闭文件会返回0
  • 关闭失败会返回EOF

FILE *stream:

  • 对应的文件指针
//打开文件
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
    perror("fopen::");
    return;
}

//关闭文件
fclose(pf);
pf = NULL;        

四.文件的顺序读写

流和文件:

在c语言中,流表示任意输入的源或者任意输出的目的地。流常常表示存储在不同介质(如硬盘驱动器,CD,DVD和闪存)上的文件,但也很容易和不存储文件的设备(如网络端口,打印机等)相关联。
在学习文件的读写函数时,应该与scanf函数和printf函数对照着来。键盘,屏幕就是一种标准输入/输出流(设备文件)
[c语言]文件操作_第2张图片
由于中提供了三个默认流:标准输入流(默认键盘),标准输出流(默认屏幕),标准错误流(默认屏幕),这三个流可以直接使用,不需要对其进行声明,也不用打开或者关闭它们。所以scanf,printf可以直接从键盘输入,从屏幕输出,而下面这几组函数需要打开对应的文件。

文件顺序读写函数:

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

fgetc:
:::
int fgetc(FILE* stream);
:::
返回值:

  • 读取成功返回读到的字符ASCII码值
  • 读取失败返回EOF
#include
int main(void)
{
	FILE* pf = fopen("test.txt", "r");
	if (NULL == pf)
	{
		perror("fopen::");
		return 1;
	}

	int a = 0;
	while ((a = fgetc(pf)) != EOF)
	{
		printf("%c", a);
	}

	fclose(pf);
	pf = NULL;

	return 0;
}

fputc:
:::
int fputc(int character, FILE* stream);
:::
返回值:

  • 写入成功将返回写入的字符
  • 写入失败则返回EOF
#include
int main(void)
{
	
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (NULL == pf)
	{
		return 1;
	}

	//写文件
	int i = 0;
	for (i = 0; i < 26; i++)
	{
		fputc('a'+i, pf);
	}

	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

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

  • num:可以读取到的最大字符个数。实际上读取到的字符个数为num-1,因为还要放’\0’;遇到\n会读取\n并且在后面放\0同时终止读操作
  • char* str:存放数据的地方

返回值:

  • 读取成功返回str
  • 读取失败返回NULL
#include
int main(void)
{
	
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (NULL == pf)
	{
		return 1;
	}

	//读文件-一行一行读
	char arr[20] = "#########";
	fgets(arr, 20, pf);
	printf("%s", arr);
	
	fgets(arr, 20, pf);
	printf("%s", arr);

	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

fputs:
:::
int fputs ( const char * str, FILE * stream );
:::
返回值:

  • 成功则返回非负值
  • 失败则返回EOF
#include
int main(void)
{
	
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (NULL == NULL)
	{
		perror("fopen");
		return 1;
	}

	//写入数据
	fputs("hello\n", pf);

	fputs("world\n", pf);
    
	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

fscanf:
:::
int fscanf ( FILE * stream, const char * format, … );
:::
fscanf与scanf的差别只是参数多了一个FILE* stream,
返回值:

  • 读取成功,返回格式串中指定的数据个数
  • 读取失败,则返回值小于读取成功的返回值
struct str
{
	char name[20];
	int age;
	int score;
};


int main(void)
{
	struct str stu = { 0 };

	FILE* pf = fopen("test.txt", "r");
	if (NULL == pf)
	{
		return 1;
	}

	
	fscanf(pf, "%s %d %d", stu.name, &(stu.age), &(stu.score));

	printf("%s %d %d", stu.name, stu.age, stu.score);
    
	fclose(pf);
	pf = NULL;
	return 0;
}

fprintf:
:::
int fprintf ( FILE * stream, const char * format, … );
:::
fprintf与printf的差别也只是参数多一个FILE* stream,

struct str
{
	char name[20];
	int age;
	int score;
};


int main(void)
{
	struct str stu = { "zhaowu", 14, 55};

	FILE* pf = fopen("test.txt", "w");
	if (NULL == pf)
	{
		return 1;
	}

	fprintf(pf, "%s %d %d", stu.name, stu.age, stu.score);

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

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

  • void* ptr :从文件中读取出的数据存放的地方
  • size_t size:每个数据所占字节大小
  • size_t count:总共读的数据个数
  • FILE* stream: 打开的文件

返回值:

  • 读取成功返回count
  • 读取失败:返回值
int main(void)
{
	FILE* pf = fopen("test.txt", "rb");
	if (NULL == pf)
	{
		return 1;
	}

	int a = 0;

	fread(&a, 4, 1, pf);
	
	printf("%d", a);

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

fwrite:
:::
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
:::
fwrite与fread相似的参数,
fread:是从stream中读取count个大小为size字节的数据到ptr中
fwrite:是将ptr中count个大小为size字节的数据写到stream中

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

	int a = 35;
	fwrite(&a, 4, 1, pf);
	
	fclose(pf);
	pf = NULL;
	return 0;
}

三组函数的对比

:::
printf/fprintf/sprintf
scanf/fscanf/sscanf
:::
sprintf函数:
函数原型

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

功能:

  • 该函数的功能与fprintf大致相同,只是将fprintf中的目的地替换为字符串
  • 字符串序列化与反序列化的相关功能

用法:

struct str
{
	char name[20];
	int age;
	int score;
};
int main(void)
{
	struct str s = { "zhansa", 14, 99 };

	char str[100] = { 0 };

	//将"%s %d %d"这个字符串给str
	sprintf(str, "%s %d %d", s.name, s.age, s.score);

	printf("%s", str);  //zhansa 14 99

	return 0;
}

sscanf函数:
函数原型:

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

功能:

  • 从字符串中按照一定的格式读取格式化的数据
  • 字符串的序列化与反序列化

用法:

struct str
{
	char name[20];
	int age;
	int score;
};
int main(void)
{
	struct str s = { 0 };

	char str[100] = "zhansa 14 99";

	sscanf(str, "%s %d %d", s.name, &(s.age), &(s.score));

	printf("%s %d %d", s.name, s.age, s.score);  //zhansa 14 99

	return 0;
}

总结比较:

printf/scanf:适用于标准输入/输出流的格式化输入/输出语句
fprintf/fscanf:适用于所有输入/输出流的格式化输入/输出语句
sprintf:把格式化的数据按照一定的格式转换为字符串
sscanf:从字符串中按照一定的格式读取出格式化的数据

五.文件的随机读写

由于打开文件其文件指针指向最开始,所以使用上述的读写函数是按照顺序的;所谓的文件随机读写就是利用一些函数来改变文件指针的位置然后利用上述的读写函数进行随机读写。

改变文件指针位置的函数:fseek

函数原型:

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

函数参数:

  • FILE* stream:对应的文件
  • origin:有三个参数可以填
    • SEEK_SET:文件起始位置
    • SEEK_CUR:文件指针的当前位置
    • SEEK_END:文件的末尾
  • long int offset :相对于origin三个参数的偏移量,如果为SEEK_END时,offset应负数

用法:

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

	//写数据
	char str[] = "abcdefg";
	fprintf(pf, "%s", str);

	//用fseek改变文件指针的位置
	fseek(pf, 3, SEEK_SET);

    //写数据
	fprintf(pf, "%s", "123");

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

找到文件指针的位置函数:ftell

函数原型:

  • long int ftell ( FILE * stream );

用法:

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

	//写数据
	char str[] = "abcdefg";
	fprintf(pf, "%s", str);

	//用fseek改变文件指针的位置
	fseek(pf, 3, SEEK_SET);
	fprintf(pf, "%s", "123");

	printf("%d", ftell(pf));  //此时值为6,因为你移动了三位,又写了三个字符进去

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

使文件指针位置回到起始位置:rewind

函数原型:

  • void rewind ( FILE * stream );

用法:


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

	//写数据
	char str[] = "abcdefg";
	fprintf(pf, "%s", str);

	//用fseek改变文件指针的位置
	fseek(pf, 3, SEEK_SET);
	rewind(pf);
	fprintf(pf, "%s", "123");

	printf("%d", ftell(pf)); //此时结果为3
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

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

由数据的组织形式,数据文件分为文本文件和二进制文件。数据在内存中以二进制形式存储,如果不加转换的输出到文件中那么该文件就是二进制文件;如果要求文件中的数据需要以ASCII的形式存储,则需要在存储前转换,以ASCII形式存储的文件就是文本文件。因为字符在内存中以ASCII码的形式存储,所以不管是哪种存储方式都是一样的,但是数值型数据就不一样了:既可以以ASCII码的形式存储,也可以以二进制的方式存储,如10000,以字符形式存储是占用5个字节,而以二进制方式存储则占用4个字节。
上述顺序输出函数的解读:

  • fputc/fscanf/fputs:不管是字符型数据还是数值型数据都是把每一位看作字符来存储的。
  • fwrite:将数值型数据的二进制存储到文件中,将字符型数据的ASCII码存储到文件中

[c语言]文件操作_第3张图片

七.文件读取结束的判断

被错误使用的函数:feof

函数原型:

  • int feof ( FILE * stream );

返回值:

  • 如果遇到EOF返回非0值
  • 没有遇到EOF返回0

功能:

  • feof是用来判断当文件读取结束时,是否是遇到EOF结束
  • feof并不是用来判断文件是否结束

判断文件是否读取结束用上述读写函数的返回值:

  • fgetc:返回值为EOF读取结束。
  • fgets:返回值为NULL读取结束。
  • fscanf:返回值小于格式串中指定的数据个数读取结束
  • fread:返回值小于count读取结束

feof用法:

int main(void)
{
	FILE* pf = fopen("test.txt", "r");
	if (NULL == pf)
	{
		perror("fopen");
		return 1;
	}
	int a = 0;
	if (fscanf(pf, "%d", &a) == 1)
	{
		printf("%d\n", a);
	}
	else if (ferror(pf))   //判断是否是I/O错误
	{
		perror("I/O");
	}
	else if (feof(pf))    //判断是否是遇到EOF而读取结束
	{
		puts("EOF");
	}

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

八.文件缓冲区

ANSIC标准采用“缓冲文件系统”来管理数据文件,当使用文件时,系统自动的在内存中开辟一块“文件缓冲区”,从内存中输出的数据都要先存放到缓冲区,装满缓冲区后才会写到磁盘中;如果磁盘向内存输入数据,先存放到输入缓冲区中,待缓冲区满后才会将数据逐个地送到程序数据区(变量等)。因为有缓冲区的原因,必须要刷新缓冲区或者关闭缓冲区,否则会导致读写文件的问题。
[c语言]文件操作_第4张图片

你可能感兴趣的:(c语言,c语言)