c语言----文件操作

正文

   文件操作涉及到的内容还是相当的多的,当然相比看完这篇文件操作也会对文件有着更深层次的理解,大致可分为三个问题:是什么? 怎么用?要注意什么会围绕着这个三个问题的衍生出很多问题,其中怎么用是内容最丰富的版块,要学习多个函数的用法相当也比较简单,让我们直接进入正题吧。 

 关于文件

什么是文件?

也便是存储在磁盘上的电子文件。就是在我们电脑中存储那个 数字、照片、文件等都属于电子文件,只要硬盘没坏,那么这些数据随时随地都能找到,而且方便探索。在程序设计中,我们还将文件分为俩种:程序文件数据文件(从文件功能的角度分类),本篇还是主要介绍的数据文件

程序文件

  程序文件包括源程序文件,比如我们的.c 文件;目标文件,经过预编译、编译、汇编后生成的目标文件,后缀为.obj,对其进行链接后,就能生成可执行程序;当然最后一种就是可执行程序文件,后缀名为.exe

数据文件

数据文件主要存储的是各种数据信息,但内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件,这些数据是能够持久化存储的

文件有什么用?

                                           电脑C盘中存储的各种信息

 文件可以保存数,使数据能做到持久化存储。文件可以使用我们的操作更加合理,比如现在我们所写的博客。本质就是一个文件,不过是只是存储在服务器上的文件(数据)。电子文件的族弟啊特点就是易于探索了,着也正是电脑与别的电子设备优点之一。至于c语言中的文件可以用于保存程序运行所产生的数据,比如通讯录系统,可以将联系人信息保存在一个文件夹中,现在的程序设计数据一般都是存储在数据库中,毕竟本地文件夹安全系数还是比较低的。

文件的格式是什么?

所有文件都有唯一的标识符,标识符可以分为三个部分:文件路径 + 文件名主干+文件后缀,比如存储在我电脑中的VS文件标识为:

  为了方便称呼,我们一般将其称为文件名,比如deven.exe 就是一个典型的程序文件

文本文件与二进制文件

文本文件

文本文件指以ASCll码(文本方式)存储的数据,原始数据机器能直接看懂,将内存中的数据对应ASCll码解码存储后,我们人类也能看懂,换句话来说,就是在记事本中写的文件吗,看懂就是文本文件

二进制文件 

二进制文件是将数据编译后转成二进制形式,然后直接存储的文件,这种文件机器能懂,读取效果很高(因为不需要转译),但二进制一般人是看不懂的,部分二进制数据也无法通过ASCll码解码为正确的数据,因此强行输出二进制文件,既有可能会得到乱码。比如将上面的那段话通过二进制形式写入文件中,可以看到出字符类型数外,其他类型的数据变成了乱码。(看不懂代码没关系哈,待会都会更加详细介绍,现在只要注意记事本的状态就好了)

 可以看到数据成乱码,可这时我们又该怎么查看呢?接下来教大家一个方法:将这个文件拉到VS二进制来打开

 注意

  • 如果待读取的文件中存储的时二进制数据,就需要使用 二进制读取 "rb" 的形式读取数据;反之如果想写入二进制数据,就需要用 二进制写入"wb" ,无论是二进制还是普通文本,计算机都能读懂,只是我们看不得罢了。小技巧:可以使用二进制存储重要数据,这样外行人一时班会也理解不了,好比跟看摩斯密码一样。(后续会继续介绍更多的函数)

文件的打开和关闭

流和标准流

1.流

我们程序的数据需要输出待各种外部设备,也需要从外部获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着各种各样的字符的河流,伸手便能拿到你想要的字符

C程序针对文件、画面、键盘等的数据输入输出操作都是通过流操作的。

一般情况下,我们要想向流里写数据,或者从流中读取数据,都是打开流,然后操作。

2.标准流

那为什么我们从键盘输入数据,向屏幕上输出数据,并没有打开流呢?

那是因为我们C语言在启动程序时,已经提前打开了,意思就是给你铺好路了,只要会走路就好啦。可想而知,C语言程序有多贴心。

  • stdin - 标准输入流,在大多数环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
  • stdout - 标准输出流,大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中。
  • stderr - 标准错误流,大多数环境中输出到显示器界面。

这是默认打开了这三个流,我们使用scanf、printf等函数就可以直接进行输入输出操作的。

stdin、stdout、stderr 三个流的类型是:FILE*  通常称为 文件指针(马上就会讲到)

c语言中,就是通过FILE* 文件指针来维护流的各种操作的。

使用文件

文件指针

文件是一个庞大的集合体,类似于结构体,不过更为复杂,因此在C语言中有一个专门的指针 文件类型指针,简称为 文件指针 用来指向文件首地址。系统会将为文件规范化,当使用我呢见时,系统会在内存中开辟一个对应的文件信息区,这个信息区中包括了文件的各种信息(文件名、文件状态、文件位置等),如果对应信息缺失,系统会自动补齐。前面说过,文件类似于结构体,因此整个文件信息是保存在一个庞大的结构体中的,为了与传统结构体分开,专门创建了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类型包含的内容不完全相同,但是大同小异,但上面这个在VS2013编译器下是解释的最清晰的不过,大家就看看长长眼,看看就行,使用者不必关心细节。但知道 FILE 这个东西本质是个结构体就行了。

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

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

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够间接找到与他关联的文件。 

文件的打开和关闭

  文件的先打开,才能关闭,最好跟我们之前学习动态内存管理一样,有申请就要有释放,成对出现更为安全。

文件打开

文件打开用的是 fopen 这个函数,fopen 的作用是从一个文件中以某种方式打开文件,返回类型是

FILE* 即打开文件的起始地址,因此我们需要用一个 FILE* 类型的指针来接收。 

文件在读写之前应该先 打开文件,然后便时 写入文件,紧接着就是在使用结束之后应该 关闭文件

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

   注意:文件打开后,要对文件指针进行判断,如果指针为空,说明文件打开失败,此时要报错,并中止后续操作

//列如:
int main()
{
	//打开文件
	 FILE* pf = fopen("daat.txt", "w"); // "w"表示只写
	 if (pf == NULL)
	 {
		 perror("fopen"); //假如为空指针就会报错相应的错误
		 return 1; //结束程序
	 }  
	 // 写文件 (很快就会解释到) ...//

	 //关闭文件(很快就会解释到) ...//
	return 0;
}
目标文件 

有两种形式,一种时绝对地址,另一种是相对地址

即唯一路径,使用绝对地址访问绝对地址文件时,文件可以在电脑种的任何位置,前提是地址要合法。绝对位置的文件标识符必须全,即文件路径+文件明主干+文件后缀名

列如:可以看到刚刚我们讲述的IDE的绝对地址

F:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE

注意:

使用绝对路径时,需要在每个\前面再额外加一个\ 原因也很简单,单个\与其他字符结合,可能会变成一个转义字符,因此需要俩个\\来标识一个\

如果时Mac就不需要担心这个问题的,因为它用的是/

// 绝对,指此地址是唯一,能通过这个地址找到唯一的文件
FILE *pf = fopen("F:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE");
相对地址 

此时的路径是固定的,一般和当前源文件处于同一个位置,相对吗,就是相对于当前程序文件。相对位置只需要文件名主干+文件后缀就行了。

比如 devenv.exe,此时存储的位置相对于上面的绝对地址,位于同以目录下

//相对,指在当前工程文件内的文件
FILE* fp = fopen("data.txt", "w");
打开方式

文件打开方式有很多种,比如只读、只写、读+、二进制写等...

值得注意的是当我们通过 读 的方式打开文件时,如果目标文件不存在,那么打开就会失败;当如果是通过 写 的方式打开文件时,如果文件不存在,会自动创建一个目标文件。

    接下来就是演示写的方式打开文件,然后文件不存在,自动创建文件的情况:

注意:这种文件的标准使用方式,既先打开,然后判断是否打开失败,如果失败就报错,否则就可以使用文件,最好再关闭文件

//文件创建,通过程序创建
int main()
{
	//再利用只读的特性,没有就会自己创建
	// 这里时相对路径
	
	//打开文件
	 FILE* pf = fopen("data.txt", "w"); // "w"表示只写 没有即会自己创建
	 if (pf == NULL)
	 {
		 perror("fopen"); //假如为空指针就会报错相应的错误
		 return 1; //结束程序
	 }  
	 // 进行文件操作 

	 //关闭文件
	 fclose(pf); //关闭
	 pf = NULL; // 手动置空

	return 0;
}

 注意: 

  • 这个特点很好用,但也很致命,因为每次写文件,都相当于覆盖文件,假如我们想要对原文的进行追加,就需要创建原来的数据,再创建新数据,然后一起写入文件中。其实面对这种场景,C语言还提供了另外一种文件打开方式追加 "a"  ,下面就是各种打开指令的集合表。
文件打开指令                                含义如果目标文件不在
" r"       只读打开一个文件,只能对它这个文件进行读取操作       打开失败,报错
" w"      只写打开一个文件,只能对它这个文件进行写入操作       建立目标文件
 "a"      追加向文件末尾处添加数据建立目标文件
" rb"      只读打开二进制文件,只能对其进行二进制读取打开失败,报错
" wb"     只写打开二进制文件,只能对其进行二进制写入建立目标文件
" ab"      追加向二进制文件末尾处添加数据追加失败,报错
" r+"       读写打开一个文件,可以进行读取和写入操作打开失败,报错
" w+"     读写打开一个文件,可以进行读取和写入操作建立目标文件
" a+"      读写对文件末尾处进行读取和写入操作建立目标文件
" rb+"     读写打开二进制文件,可以进行二进制读取和写入打开失败,报错
" wb+"   读写打开二进制文件,可以进行二进制读取和写入建立目标文件
" ab+"     读写对二进制文件末尾处进行读取和写入操作建立目标文件
文件关闭

文件关闭用到的就是刚刚我们用的很陌生函数, fclose 这个函数,当然也相对简单,只需要一个参数-文件指针(FILE*),然后就能关闭这个文件指针所指向的文件,感觉有点像 free  , 功能强大,使用方便。同 free 一样,fclose 关闭文件后,也需要将指针(文件指针)手动置空,避免出现野指针

//列如:
int main()
{
	//打开文件
	 FILE* pf = fopen("daat.txt", "w"); // "w"表示只写
	 if (pf == NULL)
	 {
		 perror("fopen"); //假如为空指针就会报错相应的错误
		 return 1; //结束程序
	 }  
	 // 写文件 (很快就会解释到) .....//

	 //关闭文件
     fclose(fp); //关闭
     fp = NULL; //手动置空
	return 0;
}

顺序读写

接下来对文件进行操作各种输入输出函数集合表:

                     功能           函数明       适用于(目标流)
    进行单字符的输入       fgetc                      所有输入流
    进行单字符的输入       fputc            所有输出流
文本行输入函数(读取一行数据)       fgets            所有输入流
文本行输出函数(读取一行数据)       fputs            所有输出流
    格式化输入函数       fscanf            所有输入流
    格式化输出函数       fprintf            所有输出流
    二进制输入函数       fread            文件输入流
    二进制输出函数       fwrite            文件输出流

注意:为了方便函数的介绍,接下来我会介绍写入(也就是输出),再介绍读取(输入)函数

fputc 与 fgetc

fputc 对文件进行单字符的写入fgetc 读取文件中的单字符

fputc

int main()
{
	//文件打开
	 FILE* pf = fopen("data.txt", "w");
	 if (pf == NULL)
	 {
		 perror("fopen");
		 return 1;
	 }
	 //进行文件操作
	 char* pc = "abcdef";
	 //逐字符写入
	 while (*pc)
	 {
		 fputc(*pc, pf); // 逐字符放入
		 pc++;
	 }

	 // 文件关闭
	 fclose(pf);
	 pf = NULL; //手动置空
	return 0;
}

代码也在这,大家要想的验证也可以直接复制粘贴上去,进到相对地址找到文件打开它,但有的时候需要注意字体预防报错。(反正就是有报错把打印的字体也重新输入再尝试尝试)。

fgetc 

//文件读写之逐字符读
int main()
{
	//文件打开
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//逐字符读取
	int ch = 0; // 需要用整形,因为EOF是 -1
	while ((ch = fgetc(pf)) != EOF)
	{
		//逐字符读取后,赋给字符变量ch,然后打印
		printf("%c ", ch);
	}

	// 文件关闭
	fclose(pf);
	pf = NULL; //手动置空
	return 0;
}
注意:
  • 在读取或者写入字符串时,可以通过特定的条件读写。比如写入:可以通过字符串自带的结束标志 \0 结束写入 读取:可以通过fgetc 的返回值进行判读,如果返回-1(EOF)就说明数据已经读取完了。
  • 单纯写文本数据时,要使用指令“w” ; 单纯读数据时,要使用指令“r”,指令与操作一定要匹配上,不然就会发生意想不到的错误

fputs 与 fgets

fputs 对文件的进行一行数据的写入,fgets 是读取文件中的一行数据

fputs

 

//文件读写之行写
int main()
{
	//打开
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//文件进行操作
	char* pc = "这是有标准输入输出写入的数据";
	fputs(pc, pf); //行写入

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

//文件读写之行读
int main()
{
	//打开
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//文件进行操作
	char tmp[30] = "0";
	fgets(tmp, sizeof(tmp), pf); //行 读取
	printf("%s\n", tmp);

	//文件关闭
	fclose(pf);
	pf = NULL;
	return 0;
}
注意
  • 行写入时,要确保写入的字符串数据,传参数时要传地址;行读取时,需要设定待读取数据的数量,一般时跟待存储空间大小相匹配。如果行读取结束,有俩种情况:1、因无法读取数据而结   2、因读取到文件末尾而结束
  • 单纯文本数据时,要使用指令 “w”;单纯数据时,要使用指令 “r”

fprintf 与 fscanf

fprintf 是对文本进行格式化数据的写入(可以对各种格式写入)scanf 是将文本中的数据进行格式化读取(可以对各种格式写入)

fprintf

//按找文件流格式化写入

struct S
{
	char name[20];
	int age;
	float score;
}a = {"zhansan",20,78.8f};

	int main()
	{
		//文件打开
		FILE* pf = fopen("data.txt", "w");
		if (pf == NULL)
		{
			perror("fopen");
			return 1;
		}
		
		//文件操作
		
		//适合与所有流格式化输入输出函数
		fprintf(pf, "%s %d %.2f", a.name, a.age, a.score); 

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

//按找文件流格式化读取

struct S
{
	char name[20];
	int age;
	float score;
}a;

int main()
{
	//文件打开
	FILE* pf = fopen("data.txt", "r"); //记得改只读
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//文件操作

	//适合与所有流格式化输入输出函数
	fscanf(pf, "%s %d %f", a.name, &(a.age), &(a.score));
	printf( "%s %d %.2f", a.name, a.age, a.score);

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

	return 0;
}

延申:sprintf 和 scanf

  除了 fprintf / fscanfprintf / scanf 这俩组格式化输入输出外,还存在另一组格式化输入输出函数数:sprintf / sscanf 

简单介绍一下,sprintf 是把格式化的数据按照的一定的格式妆化为字符串,相反的,sscanf 就是从字符串中按照一个格式读取出格式化的数据

sprintfsscanf 可以把结构体中的数据打包成一个字符串,也可以对某个字符串进行拆分。

这个东西在我们生活中有应用,比如当我们登录账号时,会把账号、密码这个结构体打包成一串字符串,交给后端处理,当然有个更高级的名词:序列化与反序列化

注意:
  • printf 输出家族返回的是实际写入(输出)的字符总数(包括转义字符),而scanf 输入家族返回的是实际读取(输入)的元素个数。举例一下,字符串abc,输出返回3,输入返回1,因为此时的字符串视为一个元素。
  • 单纯文本数据时,要使用指令 "w" ;单纯数据时,要使用指令 "r" 

fwrite 与 fread

fwrite 是对文件进行二进制数据的写入,fread是以二进制的形式读取文件中的数据

fwrite

//文件读写指二进制写入

struct S
{
	char name[20];
	int age;
	float score;
}a = {"zhangsan",20,65.7f};

int main()
{
	//文件打开
	FILE* pf = fopen("data.txt", "wb"); //将用二进制写入
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//文件操作

	//二进制的写文件
	fwrite(&a, sizeof(a), 1, pf);

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

	return 0;
}
fread 

//文件读写指二进制读取

struct S
{
	char name[20];
	int age;
	float score;
}a ;

int main()
{
	//文件打开
	FILE* pf = fopen("data.txt", "rb"); //将用二进制读取
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//文件操作  把文件中的数据读取到a中
	fread(&a, sizeof(a), 1, pf);
	printf("%s %d %.1f", a.name, a.age, a.score);

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

	return 0;
}
注意: 
  • 当我们使用二进制写入数据到文件时,如果是以文本的方式打开,只能看懂字符串部分,数字部分是看不懂的,我们可以通过VS中的二进制编码器,来观察其中的数据。
  • 单纯二进制数据时,要使用指令 “wb” ;单纯二进制数据时,要使用指令 “rb"

随机读写 

随机读写函数的,需要配合上面的输入输出函数使用,所谓的随机读写,是指通过改变文件指针的偏移量,来写入或者读取数据。介绍三个和随机读取有关的函数:fseek 改变文件指针偏移量ftell 查看当前我呢见指针的偏移量rewind 使文件指针复原至起始位置

fseek 

//fseek ,文件指针偏移量
int main()
{
	//打开
	FILE *pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	// 进行操作
	char* pc = "abc";
	fseek(pf, 5, SEEK_SET); //起点往后偏移
	fputs(pc, pf);

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

//ftell,返回当前文件指针偏移信息
int main()
{
	//打开
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	// 进行操作
	printf("当前文件指针偏移量为:%d\n", ftell(pf));
	fseek(pf, 5, SEEK_SET); //起点往后偏移 5个
	printf("经过fseek设置后的文件指针偏移量为:%d\n", ftell(pf));


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

//rewind,使文件指针恢复至当前位置
int main()
{
	//打开
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	// 进行操作
	fseek(pf, 5, SEEK_SET); //先让文件指针向后偏移5
	printf("当前文件指针偏移量为:%d\n", ftell(pf));
	rewind(pf);
	printf("经过fseek设置后的文件指针偏移量为:%d\n", ftell(pf));
	//关闭
	fclose(pf);
	pf = NULL;
	return 0;
}
注意:
  • 每进行一个我呢见输入输出操作,文件指针都会向后移动一位,比如读到字符'b'后,文件指针向后移动一位,指向字符‘c’ ,此时只需要把文件指针向后偏移一位,就能愉快的读取到字符‘d’了     

文件使用注意是事项 

 被错误使用的feof

很多小伙伴会把 feof 是用来判断文件是否读取结束,这个是错误的用法,因为 feof 的作为u是判断当前文件读取结束原因,如果是因为读取到了末尾而结束,feof(fp) 就为真;除了这个以外,还有另一个文件读取结算原因判断函数,ferror  ferror(fp) 为真时,说明此时发生了读取异常,并非正常结束,我们可以通过这两个报错函数来判断文件读取结束的真正原因。

牢记:在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的是否结束。
feof 的作⽤是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束。
⽂本⽂件读取是否结束,判断返回值是否为 EOF fgetc ),或者 NULL fgets
例如:
  • fgetc 判断是否为 EOF 
  •  fgets 判断返回值是否为 NULL 
  •  ⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数
  • fread判断返回值是否⼩于实际要读的个数。

 ⽂件缓冲区

ANSIC 标准采⽤“缓冲⽂件系统”处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为
程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓
冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输
⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓
冲区的⼤⼩根据C编译系统决定的。
验证文件缓冲区是否存在

 我们可以利用睡眠函数 Sleep 来使程序暂停,此时数据还没有被写入文件中,仍然位于缓冲区;之后再手动刷新缓冲区,数据此时会被推送至文件中

//文件缓冲区
#include<windows.h>
int main()
{
	//打开文件
	FILE* fp = fopen("test.txt", "w");
	if (NULL == fp)
	{
		perror("fp");
		return 1;
	}
	char* ps = "测试文件缓冲区";
	fputs(ps, fp);//先将数据写到缓冲区中
	printf("数据现在已经在缓冲区里面了,但还没有推送到文件中\n");
	printf("程序睡眠10秒,10秒后刷新缓冲区\n");
	Sleep(10000);//睡眠函数,单位是毫秒
	fflush(fp);
	printf("现在缓冲区已经刷新,数据已经写入文件中了\n");
	Sleep(10000);

	//关闭文件,当文件关闭时,缓冲区也会被刷新
	fclose(fp);
	fp = NULL;
	return 0;
}

注意:

  • fclose 关闭文件后,会自动刷新缓冲区,数据能够推送至文件
  • 当程序运行结束后,缓冲区也会被自动刷新
  • scanf 遇到 \n 也会触发缓冲区刷新,另外如果其在读取字符型数据时,遇到空白字符(空格、TAB键)也会触发缓冲区的刷新

总结 

 以上就是C语言文件操作的所有内容了,从文件的打开到文件的关闭,中间可以进行多种操作,构造出巧妙的数据。学到这里相必对文件操作有着一定的理解和收获。

每一篇都在很用新的写,如果觉得不错的话,可以用你发财的小手点点赞!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值