C语言:文件操作详解

一、什么是文件

磁盘上的文件是文件
但是在程序设计中,我们谈到的文件分为两种 : 1.程序文件 2.数据文件(从文件功能的角度进行分类)
1.程序文件:
包括源程序文件(后缀为.c),目标文件(windows环境下后缀为 .obj),可执行文件( .exe)
obj(object的缩写,目标文件是我们的程序在编译的时候产生的临时文件)
2.数据文件:
文件的内容不一的是程序,当文件内容是程序运行时读写的数据时,这种文件称为数据文件。

我们这次主要讲的是数据文件

二、文件名

定义:一个文件有一个唯一的文件标识,以便用户识别和引用
文件名包含3部分:文件路径+文件名主干+文件后缀
eg:c:\Study\20221209,txt

三、文件类型

数据文件一般分为文本文件和二进制文件。

3.1二进制文件:

数据在内存中是以二进制存储的,不加转换输出的外存的文件就是二进制文件。
二进制数据是字节序列,数字123的二进制表示是01111011,如果用二进制格式形式存储,char、short、int、long都可以存储123
存储方式分别如下:
char 存储

01111011

short 存储

00000000 01111011

int 存储

00000000 00000000 00000000 01111011

long 存储

00000000 00000000 00000000 00000000 00000000 00000000 00000000 01111011

3.2文本文件:

如果在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
文本数据由字符串组成,存放了每个字符的 ASCII 码值,每个字符占一个字节,每个字节存放一个字符
比如数字 123,如果用文本格式存放,数据内容是’1’、‘2’、‘3’ 三个字符,占三个字节
表格形式:
字符 ‘1’ ‘2’ ‘3’
ASCII(十进制) 49 50 51
ASCII(二进制) 00110001 00110010 00110011

以ASCII字符型形式存储就指的是将数据看成字符,然后以字符的ASCII码值存储。

四、文件类型的指针

数据文件的处理是采用“缓冲文件系统”来处理数据文件的。系统会自动给程序中正在使用的数据文件在内存上开辟一个缓冲区,程序输出时将数据输入缓冲区,缓冲区满后输出到磁盘;程序输入时先从磁盘上读取一批数据放入内存,然后再根据需要一个个输入到程序数据中。这样做可以提高数据读写效率,因为内存上的数据读写速度更快。
缓冲文件系统中,每个正在使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息,这些信息存放在一个结构体(FILE)中,在这里我们不用过分关注这些信息都是什么,我们的重点是如何进行文件读写。总结就是:我们进行操作的文件是FILE类型,我们在操作文件的时候创建一个FILE*指针来操作。

操作文件的时候,C语言为文件分配一个信息区,该信息区包含文件描述信息、缓冲区位置、缓冲区大小、文件读写到的位置等基本信息,这些信息用一个结构体来存放(struct _IO_FILE),FILE结构体和对文件操作的库函数在 stdio.h 头文件中声明的
打开文件的时候,fopen 函数中会动态分配一个FILE结构体大小的内存空间,并把FILE结构体内存的地址作为函数的返回值,程序中用FILE结构体指针存放这个地址
关闭文件的时候,fclose 函数除了关闭文件,还会释放FILE结构体占用的内存空间
FILE结构体指针习惯称为文件指针

关于VS2013编译环境提供的stdio.h头文件中有以下的文件类型申明

typedef struct _iobuf {
	char* _ptr;         //文件输出的下一个位置
	int _cut;			//当前缓冲区的相对位置
	char* _base;		//指基础位置,文件的起始位置
	int _flag;			//文件标志
	int _file;			//文件有效性检查
	int _charbuf;		//检查缓冲区状况,如果无缓冲区则不读取
	int _bufsiz;		//文件的大小
	char* _tmpfname;	//临时文件名
}FILE;

五、文件操作

5.1 文件打开

文件的打开函数:

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

参数:filename是文件名,mode是打开模式
返回值:FILE*类型的指针。文件打开或者创建之后,返回指向文件的指针,相当于建立了与该文件的联系;如果返回空指针说明出错。
作用:打开或者建立一个文件
下表是mode参数的选择:
在这里插入图片描述
不用去死记硬背 read简写r、write简写w、append简写a罢了
有的说打开文本文件的方式要用"rt"、“wt”、“at”、“rt+”、“wt+”、“at+”,"t"是text的简写,“t"可以省略不写
有的说打开二进制文件的方式要用"rb”、“wb”、“ab”、“rb+”、“wb+”、“ab+”,"b"是binary的简写

5.2 文件关闭

文件关闭函数

int fclose( FILE *stream );

参数: stream指的是指针变量
返回值: 关闭成功返回0,失败返回EOF
作用:关闭文件,将文件数据写入磁盘,可以防止文件一直被独占打开,防止数据丢失。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    FILE* fp = 0; //定义文件指针fp
    // 以只读的方式打开文件    
    if ((fp = fopen("/study/20221209/20221209练习.c", "r")) == 0)
    {
        printf("文件打开失败\n");
        return -1;
    }
    fclose(fp);
    return 0;
}

结果
在这里插入图片描述
注意事项:
调用 fopen 打开文件的时候,一定要判断返回值,如果文件不存在、或没有权限、或磁盘空间满了,都有可能造成打开文件失败
文件指针是调用 fopen 的时候,系统动态分配了内存空间,函数返回或程序退出之前,必须用 fclose 关闭文件指针,释放内存,否则后果严重
如果文件指针是空指针或野指针,用 fclose 关闭它相当于操作空指针或野指针,后果严重

六、文本文件的读写

在这里插入图片描述
关于上述函数的命名需要解释一下:这块的输入指的是从磁盘到内存的输入(或者是从键盘到内存的输入),输出指的是从内存到磁盘的输出(或者是从内存到终端的输出)。举例来说,文件中的数据是放在磁盘上的,程序产生的数据在内存上,最终我们要将数据输出到磁盘中,要是想得到数据,那么就要从磁盘输入到内存中。
总结:输入输出的对象是内存。输入是往内存中输入,输出是从内存中输出。

6.1 fputc()

函数原型:

int fputc( int c, FILE *stream );

返回值:int型的字符(字符的ASCII值),获取失败了返回EOF
作用:在文件中写入一个字符。(从内存往文件输出一个字符)

#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "w"); // 以只写的方式打开文件test.txt,如果文件未定义,那么创建该文件再写入
	if (pf == NULL)  // 判断是否打开成功
	{
		perror("fopen");
		return -1;
	}
	// 写文件
	fputc('a', pf);
	fputc('b', pf);
	fputc('c', pf);
	fputc('d', pf);
	fputc('e', pf);
	fputs("\nHello world!\n", pf);
	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

结果
在这里插入图片描述

6.2 fgetc()

函数原型:

int fgetc( FILE *stream );

参数: stream是指向要操作的文件的指针变量
返回值:int型的字符(字符的ASCII值),获取失败了返回EOF
作用:读取文件中的一个字符。(从文件往内存中输入一个字符)

举例:对数据进行顺序读取
还是对上面创建的test.txt文件进行操作

#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}

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

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

	return 0;
}

结果
在这里插入图片描述
上述代码是对test.txt文件中的内容进行顺序读取,是从文件中的第一个字符开始一个一个往后读取。

6.3 fputs()

函数原型:

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

参数:string是要写入到文件中的字符串的指针变量;stream是指向要操作的文件的指针变量
返回值:成功返回一个非负整数;出错返回EOF
作用:在文件中写入一行字符。(从内存往文件中输出一行字符)

#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}
	//写入数据时,需要将fopen第二个参数为"w"
	fputs("hello, world\n", pf); // 写一行
	fputs("hello, world\n", pf);
	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

结果
在这里插入图片描述

6.4 fgets()

函数原型:

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

参数:与fputs的区别是多了一个参数n,这参数是要读取的最大字符数
返回值:返回指向读取到的字符串的指针;返回NULL以指示错误或遇到文件结束条件
作用:从文件中读取n个字符。(文件中的数据输入到内存中)

#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}

	char str[20] = { 0 };

	fgets(str, 13, pf);
	printf("%s\n", str);

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

	return 0;
}

结果
在这里插入图片描述
在上面的代码中,hello, world总共12个字符,因为读取结果是以字符串形式返回,所以最后面需要加’\0’,因此我们要读取所有字符的话fgets()函数的第二个参数应该写13。

6.5 fprintf()

fprintf函数的声明如下:

int fprintf(FILE *fp, const char *format, ...);

f参数:stream是指向要写入的文件的指针;format是格式控制字符串;argument选择性参数
返回值:成功返回写入的字节数;失败返回负值

#include <stdio.h>
struct Stu
{
	char name[20];
	int age;
	float score;
};

int main()
{
	struct Stu s = { "zhangsan", 20, 66.5f };
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	// 格式化写入文件
	fprintf(pf, "%s %d %f", s.name, s.age, s.score);

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

	return 0;
}

结果
在这里插入图片描述

6.6 fscanf()

往内存中格式化输入
函数原型:

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

参数:stream是指向要读取的文件的指针;format是格式控制字符串;argument选择性参数
返回值:返回成功转换和分配的字段数;返回值不包括已读取但未分配的字段。返回值0表示未分配任何字段。如果发生错误或者到达文件流的末尾,则返回EOF

#include <stdio.h>
struct Stu
{
	char name[20];
	int age;
	float score;
};

int main()
{
	struct Stu s = { 0 };
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	// 格式化输入内存
	fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));
	// 打印
	printf("%s %d %f\n", s.name, s.age, s.score);

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

	return 0;
}

结果
在这里插入图片描述

6.7 fwrite()

函数原型:

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

参数:buffer是指向要写数据的存储位置的指针;size是写的项目的单位大小(比如说要读取int型的数据,那么一个单位就是4个字节);count是写入项目的最大个数;stream是指向要写入的文件的指针
返回值:fwrite返回实际写入的完整项目数,如果发生错误,该数字可能小于count。此外,如果发生错误,则无法确定文件位置指针。

#include <stdio.h>
struct Stu
{
	char name[20];
	int age;
	float score;
};

// 写文件
int main()
{
	FILE* pf = fopen("test.dat", "wb");
	if (pf == NULL)
	{
		perror("open file for writing");
		return 1;
	}
	struct Stu s = { "张三", 22, 99.5f };
	// 写文件
	fwrite(&s, sizeof(struct Stu), 1, pf);

	fclose(pf);
	pf = NULL;

	return 0;
}

结果
在这里插入图片描述

6.8 fread()

函数原型:

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

参数:buffer是指向要读取数据的存储位置的指针;size是读取的项目的单位大小(比如说要读取int型的数据,那么一个单位就是4个字节);count是读取项目的最大个数;stream是指向要读取的文件的指针
返回值:fread返回实际读取的项目数,如果发生错误或在达到count之前遇到文件结尾,则可能小于count,使用feof或ferror函数区分读取错误和文件结束情况。如果size或count为0,则fread返回0,并且缓冲区内容不变。

#include <stdio.h>
struct Stu
{
	char name[20];
	int age;
	float score;
};

// 读文件
int main()
{
	FILE* pf = fopen("test.dat", "rb");
	if (pf == NULL)
	{
		perror("open file for reading");
		return 1;
	}
	struct Stu s = { 0 };
	// 读文件
	fread(&s, sizeof(struct Stu), 1, pf);
	// 打印出来看一下读取结果是否正确
	printf("%s %d %f", s.name, s.age, s.score);

	fclose(pf);
	pf = NULL;

	return 0;
}

结果
在这里插入图片描述
总结:
scanf 是针对标准输入的格式化输入语句
printf 是针对标准输出的格式化输出语句
fscanf 是针对所有输入流的格式化输入语句
fprintf 是针对所有输出流的格式化输入语句
sscanf 是将字符串转化为格式化数据
sprintf 是将格式化数据转换为字符串

七、文件的随机读写

上面讨论的都是对于文件的顺序读写,程序运行时我们每读取一个数据,文件中的指针就会按照顺序往后移动。因此,要想在文件中的任意位置进行读写,我们需要控制文件中的指针移动。

7.1 fseek()

函数原型:

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

参数:offset是距离origin的字节数;origin是初始位置有三个可选参数:SEEK_CUR(当前位置)、SEEK_END(文件末尾)、SEEK_SET(文件开头)
返回值:如果成功fseek返回0。否则返回非零值。
作用:根据文件指针的位置和偏移量来定位文件指针

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


int main()
{

    FILE* fp = 0; //定义文件指针fp

    // 以只读的方式打开文件
    if ((fp = fopen("test.txt", "w+")) == 0)
    {
        printf("文件打开失败\n");
        return -1;
    }

    fprintf(fp, "hello world\n");

    fseek(fp, 6, SEEK_SET);

    fprintf(fp, "xiaoqiu\n");

    fclose(fp);

    return 0;
}

结果
在这里插入图片描述

7.2 ftell()

函数原型:

long ftell( FILE *stream );

ftell是和上面的fssek函数内容相互照应,它能知道当前位置相对于起始位置的偏移量是多少。

#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		return 1;
	}

	fputs("ABEDEFGHIJKLMNOPQRSTUVWXYZ", pf);
	int ret = ftell(pf); // 返回指针与初始位置的偏移量
	printf("对于文件开头的偏移量为:%d\n", ret);


	fputs("hello", pf);

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

	return 0;
}

结果
在这里插入图片描述

7.3 rewind()

函数原型:

void rewind( FILE *stream );

rewind是用来回到起始位置,当我们已经不知道文件指针的位置时,就可以用这个函数来回到起始的位置。

#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		return 1;
	}

	fputs("ABEDEFGHIJKLMNOPQRSTUVWXYZ", pf);

	// 然后测试rewind,目标是在文件开头插入“hello ”
	rewind(pf);

	fputs("hello", pf);

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

	return 0;
}

结果
在这里插入图片描述

八、文件读取结束的判定

文件结束有两个原因:1.就是文件读取到末尾。 2.就是文件读取错误,失败了从而结束。
feof函数会告诉我们文件读取结束的原因,而不能用feof函数的返回值直接用来判断文件的是否结束。
ferror函数会告诉我们文件读取失败时的原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hello xiǎo lěi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值