• 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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

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

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

    在这里插入图片描述

    2) 文件的打开

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

    • 表头文件:#include
    • 定义函数: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");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    打开模式含义
    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 
    
    
    int main()
    {	
    	//打开一个文件,成功返回FILE结构体地址,失败返回NULL
    	// 返回的文件流指针标识了打开的那个文件
    	FILE* fp = fopen("hello.txt", "r"); // 只读,不创建文件,若文件不存在则报错
    	if (NULL == fp)
    	{
    		perror("open error");
    		return;
    	}
    
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    输出结果
    open error: No such file or directory
    
    • 1
    • 2

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

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

    在这里插入图片描述

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

    3) 文件的关闭

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

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

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

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

    • 表头文件:#include

    • 定义函数: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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    文件的顺序读写

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

    1.1 写文件

    • 表头文件:#include
    • 定义函数: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);
    
    • 1
    • 2
    • 3

    示例2:追加写入

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

    示例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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    1.2 读文件

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

    示例:读取文件中的内容

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    
    
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    1.3 文件结尾

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

    #define EOF     (-1)
    
    • 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

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

    • 表头文件:#include
    • 定义函数: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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

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

    案例1:拷贝文本文件

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    在这里插入图片描述

    案例2:拷贝图片文件

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    在这里插入图片描述

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

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

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

    2.1 写文件

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

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

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    
    
    int main()
    {
    	// 打开文件
    	FILE *fpread = fopen("a.txt", "w");
    	if (NULL == fpread)
    	{	
    		perror("open error");
    		return -1;
    	}
    	// 写入字符串
    	char buf[] = "hellocdtaogang";
    	fputs(buf, fpread);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    示例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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    2.2 读文件

    • 表头文件:#include
    • 定义函数: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 
    
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    示例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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

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

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

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

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    在这里插入图片描述

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

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108

    在这里插入图片描述

    第三步:如果直接从第二步去写入结果数据会导致原本的表达式数据被覆盖,比如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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    在这里插入图片描述

    也可以将新组包后的结果数据写入到指针数组中,只需要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++;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

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

    3.1 写文件

    • 表头文件:#include
    • 定义函数: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)
    
    • 1
    • 2
    • 3
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    	// 组包
    	//sprintf(buf, "%04d:%02d:%02d", year, month, day);
    	// 写入文件
    	//fputs(buf, fp);
    	//使用fprintf格式化写入文件
    	fprintf(fp, "%04d:%02d:%02d", year, month, day);
    	// 关闭文件
    	fclose(fp);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    3.2 读文件

    • 表头文件:#include
    • 定义函数: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);
    
    • 1
    • 2
    • 3
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    3.3 强化训练:文件版排序

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

    在这里插入图片描述

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    在这里插入图片描述

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

    4.1 写文件

    • 表头文件:#include
    • 定义函数: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 
    #include 
    
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

    4.2 读文件

    • 表头文件:#include
    • 定义函数: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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    输出结果
    cont=20
    20 cdtaogang
    cont=20
    21 laoli
    cont=20
    22 laozhao
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    示例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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    输出结果
    20 cdtaogang
    21 laoli
    22 laozhao
    
    • 1
    • 2
    • 3
    • 4

    4.3 强化训练:大文件拷贝

    实现二进制大文件的拷贝

    在这里插入图片描述

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    在这里插入图片描述

  • 相关阅读:
    处理火狐浏览器地址栏点击出现 百度/58同城/爱淘宝 链接
    E056-web安全应用-File Inclusion文件包含漏洞进阶
    垂起固定翼无人机基础知识,垂起固定翼无人机应用前景,垂起固定翼无人机优缺点分析
    Redis缓存雪崩、缓存穿透、缓存击穿
    对C语言符号的一些冷门知识运用的剖析和总结
    时光邮局|来写一封未来的信试试吧!一个我的新项目,Java+Vue
    负载姜黄素的葡聚糖修饰的钆掺杂的空心介孔二氧化硅纳米材料(科研级)
    Python之哈希表-封装和解构
    opcua pubsub 消息的wireshark解码
    java springboot 通过ConfigurationProperties给第三方bean注入属性
  • 原文地址:https://blog.csdn.net/qq_41782425/article/details/128071618