C语言学习之路(基础篇)—— 文件操作(上)

说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!

概述

1) 磁盘文件和设备文件

  • 磁盘文件
    指一组相关数据的有序集合,通常存储在外部介质(如磁盘)上,使用时才调入内存。

  • 设备文件
    在操作系统中把每一个与主机相连的输入、输出设备看作是一个文件,把它们的输入、输出等同于对磁盘文件的读和写。

2) 磁盘文件的分类

计算机的存储在物理上是二进制的,所以物理上所有的磁盘文件本质上都是一样的:以字节为单位进行顺序存储。

在这里插入图片描述
从用户或者操作系统使用的角度(逻辑上)把文件分为:

  • 文本文件: 基于字符编码的文件
  • 二进制文件: 基于值编码的文件

3) 文本文件和二进制文件

3.1 文本文件

  • 基于字符编码,常见编码有ASCIIUNICODE
  • 一般可以使用文本编辑器直接打开
  • 5678的以ASCII存储形式(ASCII码)为:(先将5678转为ASCII码值53、54、55、56,再转为二进制)
    00110101 00110110 00110111 00111000

3.2 二进制文件

  • 基于值编码,自己根据具体应用,指定某个值是什么意思
  • 把内存中的数据按其在内存中的存储形式原样输出到磁盘上
  • 5678的存储形式(二进制码)为:
  • 00010110 00101110

文件的打开和关闭

1) 文件指针

C语言中用一个指针变量指向一个文件,这个指针称为文件指针。

typedef struct
{
    
    
	short           level;	//缓冲区"满"或者"空"的程度 
	unsigned        flags;	//文件状态标志 
	char            fd;		//文件描述符
	unsigned char   hold;	//如无缓冲区不读取字符
	short           bsize;	//缓冲区的大小
	unsigned char   *buffer;//数据缓冲区的位置 
	unsigned        ar;	 //指针,当前的指向 
	unsigned        istemp;	//临时文件,指示器
	short           token;	//用于有效性的检查 
}FILE;

FILE是系统使用typedef定义出来的有关文件信息的一种结构体类型,结构中含有文件名、文件状态和文件当前位置等信息。

声明FILE结构体类型的信息包含在头文件"stdio.h"中,一般设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE类型变量。通过文件指针就可对它所指的文件进行各种操作。
在这里插入图片描述
C语言中有三个特殊的文件指针由系统默认打开,用户无需定义即可直接使用:

  • stdin: 标准输入,默认为当前终端(键盘),我们使用的scanfgetchar函数默认从此终端获得数据。
  • stdout: 标准输出,默认为当前终端(屏幕),我们使用的printfputs函数默认输出信息到此终端。
  • stderr: 标准出错,默认为当前终端(屏幕),我们使用的perror函数默认输出信息到此终端。

在这里插入图片描述

2) 文件的打开

任何文件使用之前必须打开:

  • 表头文件:#include <stdio.h>
  • 定义函数:FILE * fopen(const char * filename, const char * mode);
  • 功能:打开文件
  • 参数:
    filename:需要打开的文件名,根据需要加上路径
    mode:打开文件的模式设置
  • 返回值:
    成功:文件指针
    失败:NULL

第一个参数的几种形式:

FILE *fp_passwd = NULL;

//相对路径:
//打开当前目录passdw文件:源文件(源程序)所在目录
FILE *fp_passwd = fopen("passwd.txt", "r");

//打开当前目录(test)下passwd.txt文件
fp_passwd = fopen("./test/passwd.txt", "r");

//打开当前目录上一级目录(相对当前目录)passwd.txt文件
fp_passwd = fopen("../passwd.txt", "r");
	
//绝对路径:
//打开C盘test目录下一个叫passwd.txt文件
fp_passwd = fopen("c:/test/passwd.txt","r");

第二个参数的几种形式(打开文件的方式):

打开模式 含义
rrb 以只读方式打开一个文本文件(不创建文件,若文件不存在则报错)
wwb 以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
aab 以追加方式打开文件,在末尾添加内容,若文件不存在则创建文件
r+rb+ 以可读、可写的方式打开文件(不创建新文件)
w+wb+ 以可读、可写的方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
a+ab+ 以添加方式打开文件,打开文件并在末尾更改文件,若文件不存在则创建文件

注意:

  • b是二进制模式的意思,b只是在Windows有效,在Linuxrrb的结果是一样的
  • UnixLinux下所有的文本文件行都是\n结尾,而Windows所有的文本文件行都是\r\n结尾
  • Windows平台下,以 文本 方式打开文件,不加b
  • 当读取文件的时候,系统会将所有的 “\r\n” 转换成 “\n
  • 当写入文件的时候,系统会将 “\n” 转换成 “\r\n” 写入
  • 二进制 方式打开文件,则读写都不会进行这样的转换
  • Unix/Linux平台下, 文本 二进制 模式没有区别,“\r\n” 作为两个字符原样输入输出

示例1: 以只读方式打开一个文本文件(不创建文件,若文件不存在则报错)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>


int main()
{
    
    	
	//打开一个文件,成功返回FILE结构体地址,失败返回NULL
	// 返回的文件流指针标识了打开的那个文件
	FILE* fp = fopen("hello.txt", "r"); // 只读,不创建文件,若文件不存在则报错
	if (NULL == fp)
	{
    
    
		perror("open error");
		return;
	}


	return 0;
}
输出结果
open error: No such file or directory

示例2: 以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)

FILE* fp = fopen("hello.txt", "w"); // 以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
if (NULL == fp)
{
    
    
	perror("open error");
	return;
}

在这里插入图片描述

编辑"hello.txt"文件,并填写数据后保存,如果再次执行代码,文件内容将被清空

3) 文件的关闭

任何文件在使用后应该关闭:

  • 打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存

  • 一个进程同时打开的文件数是有限制的,超过最大同时打开文件数,再次调用fopen打开文件会失败

  • 如果没有明确的调用fclose关闭打开的文件,那么程序在退出的时候,操作系统会统一关闭。

  • 表头文件:#include <stdio.h>

  • 定义函数:int fclose(FILE * stream);

  • 功能:关闭先前fopen()打开的文件。此动作让缓冲区的数据写入文件中,并释放系统所提供的文件资源。

  • 参数:
    stream:文件指针

  • 返回值:
    成功:0
    失败:-1

FILE* fp = fopen("hello.txt", "w"); // 以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
if (NULL == fp)
{
    
    
	perror("open error");
	return -1;
}
fclose(fp);

文件的顺序读写

1) 按照字符读写文件fgetc、fputc

1.1 写文件

  • 表头文件:#include <stdio.h>
  • 定义函数:int fputc(int ch, FILE * stream);
  • 功能:将ch转换为unsigned char后写入stream指定的文件中
  • 参数:
    ch:需要写入文件的字符
    stream:文件指针
  • 返回值:
    成功:成功写入文件的字符
    失败:返回-1

示例1:清空写入

FILE* fp = fopen("hello.txt", "w"); 
fputc('a', fp);
fclose(fp);

示例2:追加写入

FILE* fp = fopen("hello.txt", "a"); 
fputc('b', fp);
fclose(fp);

示例3:清空循环写入

FILE* fp = fopen("hello.txt", "w"); 
char buf[] = "this is a test for fputc";
int i = 0;
int n = strlen(buf);
for (i = 0; i < n; i++)
{
    
    
	//往文件fp写入字符buf[i]
	int ch = fputc(buf[i], fp);
	printf("ch = %c\n", ch);
}
fclose(fp);

在这里插入图片描述

1.2 读文件

  • 表头文件:#include <stdio.h>
  • 定义函数:int fgetc(FILE * stream);
    功能:从stream指定的文件中读取一个字符
    参数:
    stream:文件指针
    返回值:
    成功:返回读取到的字符
    失败:-1

示例:读取文件中的内容

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>


int main()
{
    
    
	FILE* fp = fopen("hello.txt", "r");
	char buf[128] = "";
	int i = 0;
	while ((buf[i++] = fgetc(fp)) != -1);
	printf("%s\n", buf);
	return 0;
}

在这里插入图片描述

1.3 文件结尾

C语言中,EOF表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束标志。

#define EOF     (-1)

示例:使用EOF作为结束符,存在的问题

// 写入-1
FILE* fp = fopen("hello.txt", "w");
if (NULL == fp)
{
    
    
	perror("open error");
	return -1;
}
char buf[10] = {
    
    97,-1,-2,98,99};
int i = 0;
while (buf[i] != 0)
{
    
    
	fputc(buf[i], fp);
	i++;
}
fclose(fp);

在这里插入图片描述

int main()
{
    
    	
	FILE* fp = fopen("hello.txt", "r");
	char buf[128] = "";
	int i = 0;
	while ((buf[i++] = fgetc(fp)) != EOF);
	printf("%s\n", buf);
	return 0;
}

在这里插入图片描述

当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为二进制文件的结束标志。为解决这一个问题,ANSI C提供一个feof函数,用来判断文件是否结束。feof函数既可用以判断二进制文件又可用以判断文本文件。

  • 表头文件:#include <stdio.h>
  • 定义函数:int feof(FILE * stream);
  • 功能:检测是否读取到了文件结尾。判断的是最后一次 读操作的内容 ,不是当前位置内容(上一个内容)。
  • 参数:
    stream:文件指针
  • 返回值:
    0值:已经到文件结尾
    0:没有到文件结尾

示例:使用feof函数来判断文件是否结束

int main()
{
    
    	
	FILE* fp = fopen("hello.txt", "r");
	char buf[128] = "";
	int i = 0;
	do
	{
    
    
		buf[i++] = fgetc(fp);
	} while (!feof(fp));
	
	printf("%s\n", buf);
	return 0;
}

在这里插入图片描述

1.4 强化训练:实现cp、cat命令

案例1:拷贝文本文件

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>


void copyFile(char srcFileName[128], char dstFileName[128])
{
    
    
	// 打开src文件 创建dst文件
	FILE* fpread = fopen(srcFileName, "r");
	FILE* fpwrite = fopen(dstFileName, "w");
	if (NULL == fpread || NULL == fpwrite)
	{
    
    
		perror("open error");
		return -1;
	}
	while (1)
	{
    
    	
		int ch;
		//  读取src一个字符
		ch = fgetc(fpread);
		if (feof(fpread))
			break;
		// 写入到dst文件
		fputc(ch, fpwrite);

	}
	//关闭
	fclose(fpread);
	fclose(fpwrite);
}

int main()
{
    
    	
	char srcFileName[128] = "hello.txt";
	char dstFileName[128] = "hello2.txt";
	copyFile(srcFileName, dstFileName);

	return 0;
}

在这里插入图片描述

案例2:拷贝图片文件

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>


void copyFile(char srcFileName[128], char dstFileName[128])
{
    
    
	// 打开src文件 创建dst文件
	FILE* fpread = fopen(srcFileName, "rb"); 
	FILE* fpwrite = fopen(dstFileName, "wb");
	if (NULL == fpread || NULL == fpwrite)
	{
    
    
		perror("open error");
		return -1;
	}
	while (1)
	{
    
    	
		int ch;
		//  读取src文件
		ch = fgetc(fpread);
		if (feof(fpread))
			break;
		// 写入到dst文件
		fputc(ch, fpwrite);

	}
	//关闭
	fclose(fpread);
	fclose(fpwrite);
}

int main()
{
    
    	
	char srcFileName[128] = "csdn_cdtaogang_blog.png";
	char dstFileName[128] = "my_csdn_blog.png"; 
	copyFile(srcFileName, dstFileName);

	return 0;
}

在这里插入图片描述

案例3:实现cat命令,把文件内容输出到终端

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>


int main()
{
    
    
	// 打开文件
	FILE *fpread = fopen("04拷贝案例.c", "r");
	if (NULL == fpread)
	{
    
    
		perror("open error");
		return -1;
	}
	// 读取文件
	int ch;
	while (1)
	{
    
    
		ch = fgetc(fpread);
		if (feof(fpread))
			break;
		fputc(ch, stdout);  //输出到终端
		
	}
	fclose(fpread);

	return 0;
}

在这里插入图片描述

2) 按照行读写文件fgets、fputs

2.1 写文件

  • 表头文件:#include <stdio.h>
  • 定义函数:int fputs(const char * str, FILE * stream);
    功能:将str所指定的字符串写入到stream指定的文件中,字符串结束符 '\0' 不写入文件。
  • 参数:
    str:字符串
    stream:文件指针
  • 返回值:
    成功:0
    失败:-1

示例1:将一字符串写入到文件

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>


int main()
{
    
    
	// 打开文件
	FILE *fpread = fopen("a.txt", "w");
	if (NULL == fpread)
	{
    
    	
		perror("open error");
		return -1;
	}
	// 写入字符串
	char buf[] = "hellocdtaogang";
	fputs(buf, fpread);

	return 0;
}

在这里插入图片描述

示例2:按照行向文件写入数据,遇到\0结束写入,遇到\n就换行

int main()
{
    
    
	// 打开文件
	FILE *fpread = fopen("a.txt", "w");
	if (NULL == fpread)
	{
    
    	
		perror("open error");
		return -1;
	}
	// 写入字符串,遇到\0就结束
	char buf[] = "hello\0cdtaogang";
	fputs(buf, fpread);

	return 0;
}

在这里插入图片描述

int main()
{
    
    
	// 打开文件
	FILE *fpread = fopen("a.txt", "w");
	if (NULL == fpread)
	{
    
    	
		perror("open error");
		return -1;
	}
	// 写入字符串,遇到\0就结束,遇到\n就换行
	//char buf[] = "hello\0cdtaogang";
	char buf[] = "hello\ncdtaogang";
	fputs(buf, fpread);

	return 0;
}

在这里插入图片描述

2.2 读文件

  • 表头文件:#include <stdio.h>
  • 定义函数:char * fgets(char * str, int size, FILE * stream);
  • 功能:从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束。
  • 参数:
    str:字符串
    size:指定最大读取字符串的长度(size - 1
    stream:文件指针
  • 返回值:
    成功:成功读取的字符串
    读到文件尾或出错: NULL

示例1:从文件中读取一字符串

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>


int main()
{
    
    
	// 打开文件
	FILE* fpread = fopen("a.txt", "r");
	if (NULL == fpread)
	{
    
    
		perror("open error");
		return -1;
	}
	char buf[1024] = "";
	// 读取文件
	fgets(buf, sizeof(buf), fpread);
	printf("%s", buf);

	fclose(fpread);
	return 0;
}

在这里插入图片描述

示例2:从文件中读取一字符串,遇到\n就结束

在这里插入图片描述

示例3:使用fgetsfputs完成文本文件的拷贝(二进制文件图片读取无法使用,因为字符串二进制文件有很多0,fgets遇到0就读取结束了,同理fputs写入文件也是一样,所以它们只能操作文本文件)

int main()
{
    
    
	// 打开a文件 创建b文件
	FILE* fpread = fopen("a.txt", "r");
	FILE* fpwrite = fopen("b.txt", "w");
	if (NULL == fpread || NULL == fpwrite)
	{
    
    
		perror("open error");
		return -1;
	}
	char buf[128] = "";
	char* p = NULL;
	while (1)
	{
    
    
		//  读取a文件
		p = fgets(buf, sizeof(buf), fpread);
		if (NULL == p)
			break;
		// 写入到b文件
		fputs(buf, fpwrite);
	}
	//关闭
	fclose(fpread);
	fclose(fpwrite);

	return 0;
}

在这里插入图片描述

2.3 强化训练:文件版四则运算

有个文件大小不确定,每行内容都是一个四则运算表达式,还没有算出结果,写一个程序,自动算出其结果后修改文件。
在这里插入图片描述

第一步:随机生成10个四则运算表达式,并写入到文件中。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define CALC_NUM 10  // 要生成四则运算表达式的个数


// 获取10个四则运算表达式并写入到文件中
void write_data()
{
    
    	
	// 生成并打开calc.txt文件
	FILE* fp = fopen("calc.txt", "w");
	if (NULL == fp)
	{
    
    
		perror("open error");
		return -1;
	}
	// 设置随机种子
	srand(time(NULL));
	// 定义基本运算符数组
	char ysf[] = {
    
     '+', '-', '*', '/' };
	int a, b = 0;
	char c = 0;
	// 定义一个buf数组来保存四则运算表达式
	char buf[128] = "";
	for (int i = 0; i < CALC_NUM; i++)
	{
    
    
		// 产生随机数1~100
		int a = rand() % 100 + 1;
		int b = rand() % 100 + 1;
		// 随机产生0~3的数
		int c = rand() % 4;  // 0,1,2,3  对应运算符数组下标
		// 组包
		sprintf(buf, "%d%c%d=\n", a, ysf[c], b);
		printf(buf);
		// 写入到calc.txt文件中
		fputs(buf, fp);
	}
	// 关闭文件
	fclose(fp);
}

int main()
{
    
    	
	// 调用
	write_data();

	return 0;
}

在这里插入图片描述

第二步:读取calc.txt文件中的内容一行一行的读取,读取一次就进行解包计算结果,再将结果组包到表达式中

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define CALC_NUM 10  // 要生成四则运算表达式的个数
#define FILE_PATH "calc.txt"  // 文件路径


// 封装打开文件方法
FILE* open_file(char* str)
{
    
    
	
	FILE* fp = fopen(FILE_PATH, str);
	if (NULL == fp)
	{
    
    
		perror("open error");
		return -1;
	}
	return fp;
}

// 封装关闭文件方法
void close_file(FILE* fp)
{
    
    
	fclose(fp);
	return;
}

// 获取10个四则运算表达式并写入到文件中
void write_data()
{
    
    	
	// 生成并打开calc.txt文件
	FILE* fp = open_file("w");
	// 设置随机种子
	srand(time(NULL));
	// 定义基本运算符数组
	char ysf[] = {
    
     '+', '-', '*', '/' };
	int a, b = 0;
	char c = 0;
	// 定义一个buf数组来保存四则运算表达式
	char buf[128] = "";
	for (int i = 0; i < CALC_NUM; i++)
	{
    
    
		// 产生随机数1~100
		int a = rand() % 100 + 1;
		int b = rand() % 100 + 1;
		// 随机产生0~3的数
		int c = rand() % 4;  // 0,1,2,3  对应运算符数组下标
		// 组包
		sprintf(buf, "%d%c%d=\n", a, ysf[c], b);
		printf(buf);
		// 写入到calc.txt文件中
		fputs(buf, fp);
	}
	// 关闭文件
	close_file(fp);
}

void read_data()
{
    
    	
	// 读取文件
	FILE* fp = open_file("r");
	int a, b = 0;
	char c = 0;
	char* p = NULL;
	char buf[128] = "";
	char new_buf[128] = "";
	int res = 0;
	while (1)
	{
    
    
		p = fgets(buf, sizeof(buf), fp); //读一行的数据72*65=\n
		if (NULL == p)
		{
    
    
			break;
		}
		// 拆包
		sscanf(buf, "%d%c%d", &a, &c, &b); // 72*65
		// switch判断运算符
		switch (c)
		{
    
    
		case '+':
			res = a + b;
			break;
		case '-':
			res = a - b;
			break;
		case '*':
			res = a * b;
			break;
		case '/':
			res = a / b;
			break;
		}
		// 再组包,将计算结果组进去
		sprintf(new_buf, "%d%c%d=%d\n", a, c, b, res); // 72*65=4680\n
		printf("%s", new_buf);
	}
}

int main()
{
    
    	
	// 写入
	write_data();
	printf("\n");
	// 读取
	read_data();

	return 0;
}

在这里插入图片描述

第三步:如果直接从第二步去写入结果数据会导致原本的表达式数据被覆盖,比如calc.txt文件13+15=28\n34-21=13\n...在读取第一个\n后写入会直接将\n后面的数据覆盖掉,那么就读取不到后面的数据了,解决方法则是将每行组包数据保存到二维数组中即可

void read_data()
{
    
    	
	// 读取文件
	FILE* fp = open_file("r");
	int a, b = 0;
	char c = 0;
	char* p = NULL;
	char buf[128] = "";
	char new_buf[128] = "";
	int res = 0;
	// 定义二维数组保存每行组包结果数据
	char new_buff[10][128] = {
    
     0 };
	int i= 0;
	while (1)
	{
    
    
		p = fgets(buf, sizeof(buf), fp); //读一行的数据72*65=\n
		if (NULL == p)
		{
    
    
			break;
		}
		// 拆包
		sscanf(buf, "%d%c%d", &a, &c, &b); // 72*65
		// switch判断运算符
		switch (c)
		{
    
    
		case '+':
			res = a + b;
			break;
		case '-':
			res = a - b;
			break;
		case '*':
			res = a * b;
			break;
		case '/':
			res = a / b;
			break;
		}
		// 再组包,将计算结果组进去, 
		//sprintf(new_buf[i], "%d%c%d=%d\n", a, c, b, res); // 72*65=4680\n
		//printf("%s", new_buf);
		sprintf(new_buff[i], "%d%c%d=%d\n", a, c, b, res); // 72*65=4680\n
		i++;
		
	}
	// 关闭文件
	close_file(fp);
	// 再次打开calc.txt文件,写入含结果的四则运算表达式
	fp = open_file("w");
	for (int j = 0; j < i; j++)
	{
    
    
		fputs(new_buff[j], fp);
	}
	// 关闭文件
	close_file(fp);
}

在这里插入图片描述

也可以将新组包后的结果数据写入到指针数组中,只需要malloc申请空间即可保存组包数据。

char* new_buff[10] = {
    
     NULL };
int i= 0;
while (1)
{
    
    
	p = fgets(buf, sizeof(buf), fp); //读一行的数据72*65=\n
	if (NULL == p)
	{
    
    
		break;
	}
	// 拆包
	sscanf(buf, "%d%c%d", &a, &c, &b); // 72*65
	// switch判断运算符
	switch (c)
	{
    
    
	case '+':
		res = a + b;
		break;
	case '-':
		res = a - b;
		break;
	case '*':
		res = a * b;
		break;
	case '/':
		res = a / b;
		break;
	}
	// 再组包,将计算结果组进去, 
	//sprintf(new_buf[i], "%d%c%d=%d\n", a, c, b, res); // 72*65=4680\n
	//printf("%s", new_buf);
	new_buff[i] = (char*)malloc(128);
	sprintf(new_buff[i], "%d%c%d=%d\n", a, c, b, res); // 72*65=4680\n
	i++;
}

3) 按照格式化文件fprintf、fscanf

3.1 写文件

  • 表头文件:#include <stdio.h>
  • 定义函数:int fprintf(FILE * stream, const char * format, ...);
  • 功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 '\0' 为止。
  • 参数:
    stream:已经打开的文件
    format:字符串格式,用法和printf()一样
  • 返回值:
    成功:实际写入文件的字符个数
    失败:-1

示例:使用fprintf对比sprint组包后fputs写入

printf("%04d:%02d:%02d", year, month, day);
sprintf(buf, "%04d:%02d:%02d", year, month, day)
fprintf(fp, "%04d:%02d:%02d", year, month, day)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>


int main()
{
    
    
	int year = 2022;
	int month = 12;
	int day = 2;
	char buf[128] = "";

	FILE* fp = NULL;
	fp = fopen("fprintf.txt", "w");
	if (!fp)
	{
    
    
		perror("open error");
		return -1;
	}
	// 组包
	sprintf(buf, "%04d:%02d:%02d", year, month, day);
	// 写入文件
	fputs(buf, fp);

	return 0;
}
	// 组包
	//sprintf(buf, "%04d:%02d:%02d", year, month, day);
	// 写入文件
	//fputs(buf, fp);
	//使用fprintf格式化写入文件
	fprintf(fp, "%04d:%02d:%02d", year, month, day);
	// 关闭文件
	fclose(fp);

在这里插入图片描述

3.2 读文件

  • 表头文件:#include <stdio.h>
  • 定义函数:int fscanf(FILE * stream, const char * format, ...);
  • 功能:从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据。
  • 参数:
    stream:已经打开的文件
    format:字符串格式,用法和scanf()一样
  • 返回值:
    成功:参数数目,成功转换的值的个数
    失败: - 1

示例:使用fscanf对文件数据进行拆包

scanf("%d:%d:%d", &year, &month, &day);
sscanf(buf, "%d:%d:%d", &year, &month, &day);
fscanf(fp, "%d:%d:%d", &year, &month, &day);
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>


int main()
{
    
    
	FILE* fp = NULL;
	fp = fopen("fprintf.txt", "r");  // 2022:12:02
	if (!fp)
	{
    
    
		perror("open error");
		return -1;
	}
	int year = 0, month = 0, day = 0;
	// 使用fscanf进行数据拆包
	fscanf(fp, "%d:%d:%d", &year, &month, &day);
	printf("%d-%d-%d", year, month, day);
	// 关闭文件
	fclose(fp);

	return 0;
}

在这里插入图片描述

3.3 强化训练:文件版排序

10个随机数写入到abc.txt中,然后将abc.txt文件中的随机数进行排序后写入

在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define CALC_NUM 10  // 要生成1~3位的整数个数
#define FILE_PATH "abc.txt"  // 文件路径


int main()
{
    
    
	// 设置随机种子,并写入数据
	FILE* fp = open_file("w");
	srand(time(NULL));
	for (int i = 0; i < CALC_NUM; i++)
	{
    
    
		// 产生随机数1~300
		int num = rand() % 300 + 1;
		// 格式化后写入
		fprintf(fp, "%d\n", num);
	}
	// 关闭文件
	close_file(fp);
	// 读取文件中写入的随机数,并保存到数组中
	int num = 0;
	int nums[10] = {
    
     0 };
	int n = sizeof(nums) / sizeof(nums[0]);
	fp = open_file("r");
	for (int i = 0; i < n; i++)
	{
    
    
		// 格式化读取字符串
		fscanf(fp, "%d", &num);
		// 将随机数保存到数组中
		nums[i] = num;
	}
	close_file(fp);
	// 对nums数组元素进行排序
	for (int i = 0; i < n - 1; i++) //比较的轮数
	{
    
    	// 因为每次比较的次数都要减1,刚好i每次加1,所以每一轮比较的次数就是n-1-i
		for (int j = 0; j < n - 1 - i; j++) // 每一轮比较的次数
		{
    
    
			if (nums[j] > nums[j + 1])  // 交换位置
			{
    
    
				int temp = nums[j];
				nums[j] = nums[j + 1];
				nums[j + 1] = temp;
			}
		}
	}
	// 再将排好序的nums数组写入到abc.txt文件
	fp = open_file("w");
	for (int i = 0; i < n; i++)
	{
    
    	
		// 将nums每个元素进行组包
		fprintf(fp, "%d\n", nums[i]);
	}
	close_file(fp);
	return 0;
}

在这里插入图片描述

4) 按照块读写文件fread、fwrite

4.1 写文件

  • 表头文件:#include <stdio.h>
  • 定义函数:size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 功能:以数据块的方式给文件写入内容
  • 参数:
    ptr:准备写入文件数据的地址
    size: size_tunsigned int类型,此参数指定写入文件内容的块数据大小
    nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb
    stream:已经打开的文件指针
  • 返回值:
    成功:实际成功写入文件数据的块数目,此值和 nmemb 相等
    失败:0

示例:

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


typedef struct _std
{
    
    
	int age;
	char name[16];
}STD;

int main()
{
    
    	
	int cont = 0;
	STD buf[3] = {
    
     {
    
    20, "cdtaogang"}, {
    
    21, "laoli"}, {
    
    22, "laozhao"} };
	FILE* fp = fopen("fwrite.txt", "w");
	// fwrite 第二个参数写1 ,是为了返回值刚好是写入文件的字节数,这也是个技巧
	cont = fwrite(buf, 1, sizeof(buf), fp);
	// cont = fwrite(buf, sizeof(buf), 1, fp);
	// 验证返回值是否等于字节数
	if (cont == sizeof(buf))
	{
    
    
		printf("cont == sizeof(buf) == %d", cont); // 60 (int:4 + char name[16]:16)*3
	}

	return 0;
}

在这里插入图片描述

4.2 读文件

  • 表头文件:#include <stdio.h>
  • 定义函数:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 功能:以数据块的方式从文件中读取内容
  • 参数:
    ptr:存放读取出来数据的内存空间
    size: size_tunsigned int类型,此参数指定读取文件内容的块数据大小
    nmemb:读取文件的块数,读取文件数据总大小为:size * nmemb
    stream:已经打开的文件指针
  • 返回值:
    成功:实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。
    失败:0

示例1:从结构体数组中,读取一个一个结构体大小

#pragma once
typedef struct _std
{
    
    
	int age;
	char name[16];
}STD;
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include "type.h"


int main()
{
    
    	
	// 定义结构体数组
	STD buf[3];
	// 全部设置为0
	memset(buf, 0, sizeof(buf));
	FILE* fp = NULL;
	fp = fopen("fwrite.txt", "r"); 
	if (!fp)
	{
    
    
		perror("open error");
		return -1;
	}
	int cont = 0;
	// 从结构体数组中,读取一个一个结构体大小
	for (int i = 0; i < 3; i++)
	{
    
    
		cont = fread(&buf[i], 1, sizeof(STD), fp);
		printf("cont=%d\n", cont);
		printf("%d %s\n", buf[i].age, buf[i].name);
	}

	return 0;
}
输出结果
cont=20
20 cdtaogang
cont=20
21 laoli
cont=20
22 laozhao

示例2:一次性读完整个结构体数组大小

int main()
{
    
    	
	// 定义结构体数组
	STD buf[3];
	// 全部设置为0
	memset(buf, 0, sizeof(buf));
	FILE* fp = NULL;
	fp = fopen("fwrite.txt", "r"); 
	if (!fp)
	{
    
    
		perror("open error");
		return -1;
	}
	int cont = 0;
	// 一次性读完整个结构体数组大小
	fread(buf, 1, sizeof(buf), fp);
	for (int i = 0; i < 3; i++)
	{
    
    
		printf("%d %s\n", buf[i].age, buf[i].name);
	}

	return 0;
}
输出结果
20 cdtaogang
21 laoli
22 laozhao

4.3 强化训练:大文件拷贝

实现二进制大文件的拷贝

在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 1024 * 64



int main()
{
    
    
    printf("Start Copy\n");

    // 拷贝的源地址
    char* src_file = "a.mp4";
    // 拷贝的目标地址
    char* dst_file = "b.mp4";

    // 以 可读 + 二进制 方式打开文件
    // r 表示可读打开方式
    // 打开方式后添加 b , 表示以二进制形式打开
    FILE* p_src = fopen(src_file, "rb");
    // 如果打开失败 , 直接返回
    if (p_src == NULL) {
    
    
        printf("src_file open failed");
        return 0;
    }

    // 以 可写 + 二进制 方式打开文件
    // w 表示可写打开方式
    // 打开方式后添加 b , 表示以二进制形式打开
    FILE* p_dst = fopen(dst_file, "wb");
    // 如果打开失败 , 直接返回
    if (NULL == p_dst) {
    
    
        printf("dst_file open failed");
        return 0;
    }
    // 为缓冲区内存申请堆内存
    char* buffer = malloc(BUFFER_SIZE);

    // 判定文件指针是否指向文件末尾
    // 如果指向末尾 , 说明该文件
    while (!feof(p_src)) {
    
    
        // 读取源文件数据到 buffer 缓冲区, 读取 buffer_size 个字节
        // 如果没有那么多字节 , 将读取的字节数返回
        int res = fread(buffer, 1, BUFFER_SIZE, p_src);
        // 将读取到缓冲区中的数据写出到目标文件中
        fwrite(buffer, 1, res, p_dst);
    }

    // 释放缓冲区内存
    free(buffer);
    // 关闭源文件
    fclose(p_src);
    // 关闭目标文件
    fclose(p_dst);

    printf("Copy Success");

	return 0;
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41782425/article/details/128071618
今日推荐