【c语言】文件操作 万字详解

这篇具有很好参考价值的文章主要介绍了【c语言】文件操作 万字详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一,为什么使用文件

二,什么是文件

        1,程序文件

        2,数据文件

        3,文件名

三,文件的打开和关闭

        1,文件指针

        2,文件的打开和关闭

四, 文件的顺序读写

        1,顺序读写函数介绍

        2,字符输出函数fputc

        3,字符输入函数fgetc

        4,文本行输出函数fputs

        5,文本行输入函数fgets

        6,格式化输出函数fprintf

        7,格式化输入函数fscanf

        8,二进制输出fwrite

        9,二进制输入 fread

        10,​​​​​​sprintf

        11,sscanf

五, 文件的随机读写

        1,fseek

        2,ftell

        3,rewind

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

七,文件读取结束的判定

        1,被错误使用的fefo

八,文件缓冲区


一,为什么使用文件

我们前面学习结构体时,写通讯录的程序,当通讯录运行起来的时候,可以给通讯录中增加、删除数据,此时数据是存放在内存中;

当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受;

我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在;

这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式;

使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。

 

二,什么是文件

磁盘上的文件是文件。 但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。

        1,程序文件

包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。

        2,数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件, 或者输出内容的文件

本章讨论的是数据文件。 在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。

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

         3,文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。 文件名包含3部分:文件路径+文件名主干+文件后缀 例如: c:\code\test.txt 为了方便起见,文件标识常被称为文件名。

 

三,文件的打开和关闭

        1,文件指针

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

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名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结构的变量,这样使用起来更加方便;

下面我们可以创建一个FILE*的指针变量:

FILE* pf;//文件指针变量

定义pf是一个指向FILE类型数据的指针变量;

可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。

比如:

【c语言】文件操作 万字详解,c语言,开发语言

         2,文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件

在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系;

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

//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream );

打开方式如下:

文件使用方式                                     含义 如果指定文件不存在
"r"(只读) 为了输入数据,打开一个已经存在过的文本文件 出错
"w"(只写) 为了输出数据,打开一个文本文件 建立一个新的文件
"a"(追加) 向文本文件尾添加数据 建立一个新的文件
"rb"(只读) 为了输入数据,打开一个二进制文件 出错
"wb"(只写) 为了输出数据,打开一个二进制文件 建立一个新的文件
"ab"(追加) 向一个二进制文件尾添加数据 建立一个新的文件
"r+"(读写) 为了读和写,打开一个文本文件 出错
"w+"(读写) 为了读和写,建立一个新的文件 建立一个新的文件
"a+"(读写) 打开一个文件,在文件尾进行读写 建立一个新的文件
"rb+"(读写) 为了读和写打开一个二进制文件 出错
"wb+"(读写) 为了读和写,新建一个新的二进制文件 建立一个新的文件
"ab+"(读写) 打开一个二进制文件,在文件尾进行读和写 建立一个新的文件

 实例代码:

#include <stdio.h>
int main()
{
	FILE * pFile;
	//打开文件
	pFile = fopen("myfile.txt", "w");
	//文件操作
	if (pFile != NULL)
	{
		fputs("fopen example", pFile);
		//关闭文件
		fclose(pFile);
	}
	return 0;
}

此文件原先是没有的,程序运行后新建了在 test.c 目录底下;

【c语言】文件操作 万字详解,c语言,开发语言

 内容也输出进去了;

【c语言】文件操作 万字详解,c语言,开发语言

 

四, 文件的顺序读写

        1,顺序读写函数介绍

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

          2,字符输出函数fputc

int fputc ( int charcter , FILE * stream); 

作用:把字符串常量character的值写到文件指针stream所指向的文件中(不包括’\0’),若写入成功,返回值为输出的字符,否则返回EOF。

例如:

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	FILE* pf = fopen("text.txt", "w");
	if (pf == NULL)
	{
		printf("%s", strerror(errno));
		return 0;
	}
	//写文件
	fputc('a', pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

此时test.c 目录底下生成test.txt文件;

【c语言】文件操作 万字详解,c语言,开发语言

【c语言】文件操作 万字详解,c语言,开发语言

 文件里存放字符‘a'

       3,字符输入函数fgetc

int fgetc ( FILE * stream ); 

作用:从stream所指向文件中读取一个字符,赋予变量character,如果读取文件失败返回EOF。
返回类型是int,返回的是字符的ASCII码值。

例如:

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	FILE* pf = fopen("text.txt", "r");
	if (pf == NULL)
	{
		printf("%s", strerror(errno));
		return 0;
	}
	//读文件
	int ch = fgetc(pf);
	if (ch != EOF)
	printf("%c", ch);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

 【c语言】文件操作 万字详解,c语言,开发语言

        4,文本行输出函数fputs

int fputs ( const char * str , FILE * stream )

作用:将srt所表示的字符串内容(不包括字符串最后的’\0’)输出到stream所指向的文件中去,若成功,返回一个非负数,否则返回EOF。

 例如:

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	FILE* pf = fopen("text.txt", "w");
	if (pf == NULL)
	{
		printf("%s", strerror(errno));
		return 0;
	}
	//读文件
	fputs("hello world", pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

【c语言】文件操作 万字详解,c语言,开发语言

         5,文本行输入函数fgets

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

作用:从stream所指向的文件中读取(n-1)个字符(第n个字符是给’\0’准备的);

放到str为其实地址的储存空间(str可以是一个字符数组的数组名),若在n-1个字符前,遇到回车关键换行符或者文件结束标志,则读取操作结束,并在读入的字符串最后加上一个’\0’字符;

如操作成功,返回str的起始地址,否则返回NULL

例如:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	FILE* pf = fopen("text.txt", "r");
	if (pf == NULL)
	{
		printf("%s", strerror(errno));
		return 0;
	}
	//读文件
	char arr[30];
	fgets(arr, 5, pf);
	printf("%s", arr);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

【c语言】文件操作 万字详解,c语言,开发语言

第五个字符是'\0'

        6,格式化输出函数fprintf

int fprintf ( FILE * stream , const char * format ) ; 

作用:按format所给的输出格式,将……的值写入到stream所指向的文件中去;


这里我们和print对标一下,发现他和printf的参数只有一个之差,就是多了一个文件名

例如:

#include <stdio.h>
#include <string.h>
#include <errno.h>
struct student
{
	char name[20];
	int age;
	float weight;
};
int main()
{
	struct student s = { "changsan",20,50.0 };
	FILE* pf = fopen("text.txt", "w");
	if (pf == NULL)
	{
		printf("%s", strerror(errno));
		return 0;
	}
	//写文件
	fprintf(pf, "%s %d %f", s.name, s.age, s.weight);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

 【c语言】文件操作 万字详解,c语言,开发语言

          7,格式化输入函数fscanf

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

作用:按照format所给出的输入控制符,把从stream中读取到的内容,分别赋值给变元……;


这里同样和scanf对标一下,fscanf也是只是多了一个文件参数。

例如:

#include <stdio.h>
#include <string.h>
#include <errno.h>
struct student
{
	char name[20];
	int age;
	float weight;
};
int main()
{
	struct student s = {0};
	FILE* pf = fopen("text.txt", "r");
	if (pf == NULL)
	{
		printf("%s", strerror(errno));
		return 0;
	}
	//读文件
	fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.weight));
	printf("%s %d %f", s.name, s.age, s.weight);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

【c语言】文件操作 万字详解,c语言,开发语言

          8,二进制输出fwrite

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

ptr : 数据的地址

size :每个数据的大小

count:多少个数据

stream:文件名

例如:

#include <stdio.h>
#include <string.h>
#include <errno.h>
struct student
{
	char name[20];
	int age;
	float weight;
};
int main()
{
	struct student s = { "changsan",20,50.0 };
	FILE* pf = fopen("text.txt", "wb");
	if (pf == NULL)
	{
		printf("%s", strerror(errno));
		return 0;
	}
	//二进制的方式写文件
	fwrite(&s, sizeof(struct student), 1, pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

 【c语言】文件操作 万字详解,c语言,开发语言

 此时存放的就是二进制的数据;

         9,二进制输入 fread

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

这里我们发现fread的参数和fwrite的参数是一样的;


fwrite是把大小为size个数为count的数据从ptr中写入stream;


而fread恰好是反过来的,把大小为size个数为count的数据从stream中读到prt中;

例如:

#include <stdio.h>
#include <string.h>
#include <errno.h>
struct student
{
	char name[20];
	int age;
	float weight;
};
int main()
{
	struct student s = { 0};
	FILE* pf = fopen("text.txt", "rb");
	if (pf == NULL)
	{
		printf("%s", strerror(errno));
		return 0;
	}
	//二进制的形式读文件
	fread(&s, sizeof(struct student), 1, pf);
	printf("%s %d %f", s.name, s.age, s.weight);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

【c语言】文件操作 万字详解,c语言,开发语言

         10,​​​​​​​​​​​​​sprintf

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

把一个格式化的数据写到字符串中;

实际上就是把一个格式化的据写成字符串;

例如:

#include <stdio.h>
#include <string.h>
#include <errno.h>
struct student
{
	char name[20];
	int age;
	float weight;
};
int main()
{
	struct student s = { "changsan",20,50.0 };

	char buf[100] = { 0 };
	sprintf(buf, "%s %d %f", s.name, s.age, s.weight);
	printf("%s", buf);
	return 0;
}

【c语言】文件操作 万字详解,c语言,开发语言

         11,sscanf

int sscanf ( sonst char * s , const char * format , ... ) ; 

把一个字符串中转换成一个格式化的数据;

例如:

#include <stdio.h>
#include <string.h>
#include <errno.h>
struct student
{
	char name[20];
	int age;
	float weight;
};
int main()
{
	struct student s = { "changsan",20,50.0 };
	struct student tmp = { 0 };

	char buf[100] = { 0 };
	//把s中的格式化数据转换成字符串放到buf中
	sprintf(buf, "%s %d %f", s.name, s.age, s.weight);
	printf("%s\n", buf);
	//把buf中的字符串转换成格式化数据放到tmp中
	sscanf(buf, "%s %d %f", tmp.name, &(tmp.age), &(tmp.weight));
	printf("%s %d %f", tmp.name, tmp.age, tmp.weight);
	return 0;
}

【c语言】文件操作 万字详解,c语言,开发语言

五, 文件的随机读写

        1,fseek

根据文件指针的位置和偏移量来定位文件指针。

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

 举个例子:

#include<stdio.h>
int main()
{
	FILE * pFile;
	pFile = fopen("example.txt", "wb");
	fputs("This is an apple.", pFile);
	fseek(pFile, 9, SEEK_SET);
	fputs("sam", pFile);
	fclose(pFile);
	return 0;
}

先创建自动新建一个 example.txt 文件,本应输出 This is an apple

【c语言】文件操作 万字详解,c语言,开发语言

 【c语言】文件操作 万字详解,c语言,开发语言

 实际输出 This is asampple. 

在偏移了初始位置 9 的位置开始输出了 sam ;

         2,ftell

返回文件指针相对于起始位置的偏移量

long int ftell ( FILE * stream );

 举个例子:

#include <stdio.h>
int main()
{
	FILE * pFile;
	long size;
	pFile = fopen("myfile.txt", "rb");
	if (pFile == NULL) perror("Error opening file");
	else
	{
		fseek(pFile, 0, SEEK_END);   // non-portable
		size = ftell(pFile);
		fclose(pFile);
		printf("Size of myfile.txt: %ld bytes.\n", size);
	}
	return 0;
}

【c语言】文件操作 万字详解,c语言,开发语言

此时偏移量为 9 ;

        3,rewind

让文件指针的位置回到文件的起始位置

void rewind ( FILE * stream );

 

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

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

数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件;

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

一个数据在内存中是怎么存储的呢? 字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储;

如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。

【c语言】文件操作 万字详解,c语言,开发语言

 例如:

#include<stdio.h>
int main()
{
	int a = 10000;
	FILE * pf = fopen("test.txt", "wb");
	fwrite(&a, 4, 1, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

【c语言】文件操作 万字详解,c语言,开发语言

【c语言】文件操作 万字详解,c语言,开发语言

 

 

七,文件读取结束的判定

        1,被错误使用的fefo

牢记:在文件读取过程中,不能用feof函数的返回值直接来判断文件的是否结束;

feof 的作用是:当文件读取结束的时候,判断是读取结束的原因是否是:遇到文件尾结束。

1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )

例如:

fgetc 判断是否为 EOF 

fgets 判断返回值是否为 NULL

2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数;

例如:

fread判断返回值是否小于实际要读的个数。

八,文件缓冲区

ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”;

从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上;

如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等;

缓冲区的大小根据C编译系统决定的。

【c语言】文件操作 万字详解,c语言,开发语言

列如:

include <stdio.h>
#include <windows.h>
//VS2013 WIN10环境测试
int main()
{
 FILE*pf = fopen("test.txt", "w");
 fputs("abcdef", pf);//先将代码放在输出缓冲区
 printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
 Sleep(10000);
 printf("刷新缓冲区\n");
 fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
 //注:fflush 在高版本的VS上不能使用了
 printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
 Sleep(10000);
 fclose(pf);
 //注:fclose在关闭文件的时候,也会刷新缓冲区
 pf = NULL;
 return 0;
}

 【c语言】文件操作 万字详解,c语言,开发语言

这里可以得出一个结论: 因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件;

如果不做,可能导致读写文件的问题

​​​

如有不足之处欢迎来补充交流!

完结。。。文章来源地址https://www.toymoban.com/news/detail-679340.html


到了这里,关于【c语言】文件操作 万字详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C语言中的操作符(万字详解)

    🎈个人主页:库库的里昂 🎐CSDN新晋作者 🎉欢迎 👍点赞✍评论⭐收藏 ✨系列专栏C语言初阶、代码小游戏 🤝希望作者的文章能对你有所帮助,有不足的地方请在评论区留言指正,大家一起学习交流!🤗 【前言】 操作符的种类比较多,也是我们在敲代码过程中很常用的知

    2024年02月14日
    浏览(34)
  • 万字长文·通俗易懂·一篇包掌握——输入/输出·文件操作(c语言超详细系列)(二)

    前言:Hello,大家好😘,我是心跳sy,上一节我们主要学习了格式化输入输出的基本内容,这一节我们对格式化进行更加深入的了解,对文件概念进行介绍,并且对输入、输出与文件读写的基本概念进行学习,本节主要对printf,scanf深入了解,并介绍文件处理函数,如fprintf,

    2024年02月13日
    浏览(55)
  • 【C语言】——动态内存管理与文件操作,后面加一个通讯录福利,万字解读,看完你会有一个全新认识

    目录 一.动态内存管理 1.为什么有动态内存管理 2.malloc和free  2.calloc和realloc 3.柔性数组 二.文件操作 1.为什么使用文件  2.二进制文件和文本文件  3.文件的打开和关闭 4.文件的顺序读写 5.文件缓冲区 三.通讯录  1.预处理  2.基本框架 3.初始化函数 4.增加联系人  5.显示联系人

    2024年03月13日
    浏览(29)
  • 【C语言】详解文件与文件操作

    文件是记录在外存上的相关信息的命名组合,但是在程序设计中,我们⼀般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。 程序文件包括: 源程序文件 (后缀为.c) 目标文件 (windows环境后缀为.obj) 可执行程序 (windows环境后缀为.exe) 文件的内容

    2024年01月22日
    浏览(29)
  • (C语言)文件操作-----详解

    目录 1. 为什么使用文件 2. 什么是文件 程序文件 数据文件  文件名  3. 文件的打开和关闭 文件指针  文件的打开和关闭 🙉🙉打开文件(fopen函数)  🙈🙈关闭文件(fclose函数)  补充 : 相对路径 绝对路径 4. 文件的顺序读写  顺序读写函数  字符输入函数 —— fputc函数

    2023年04月15日
    浏览(38)
  • 【C语言】文件操作详解

    hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注🥰🥰收藏🌹🌹🌹 💥 个人主页:大耳朵土土垚的博客 💥 所属专栏:C语言学习笔记 💥对于C语言学习疑问的都可以在上面的专栏进行学习哦~ 有问题可以写在评论区或者私信我哦~ 如果没有⽂件,我们写的

    2024年03月12日
    浏览(31)
  • 【C语言】文件操作 -- 详解

    磁盘上的文件是文件。 1、为什么要使用文件         举个例子,当我们想实现一个 “通讯录” 程序时,在通讯录中新建联系人、删除联系人等一系列操作,此时的数据存储于内存中,程序退出后所有数据都会随之消失。为了让通讯录中的信息得以保存,也就是想让 数据

    2024年02月11日
    浏览(23)
  • 详解C语言—文件操作

    目录 1. 为什么使用文件 2. 什么是文件 3. 文件的使用 文件指针 文件的打开和关闭 三个标准的输入/输出流:  4. 文件的顺序读写 对字符操作:  fputc:  fgetc:   练习复制整个文件: 对字符串操作: fputs:  fgets: fprintf:  fscanf: 辨析sprintfsscanf: 对二进制操作:  fwrit

    2024年02月07日
    浏览(29)
  • 【c语言】超详解文件操作

           学习过C语言的伙伴,对程序运行结果往往出现在一个黑框里一定不陌生,一旦关闭黑框程序就结束,为数据分配的内存空间也会随之退还。如果你想使用C语言写一个通讯录的话,一定不想黑框关闭存入的数据就丢失,那么此时我们就需要学会使用文件操作。 为了使

    2024年04月16日
    浏览(19)
  • 【c语言】详解文件操作(二)

    fgetc 为字符输入函数, fputc 为字符输出函数,适用所以输入流和输出流 函数原型: 该函数从 stream 指向的输入流中读取 unsigned char 型的下一个字符的值,并将其转换为 int 型,并返回。若在流中 检查到文件末尾 ,则设置该流的文件结束指示符并返回 EOF ;如果 发生读取错误

    2024年02月08日
    浏览(23)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包