win32核心编程00:文件操作

目录

0x00.文件基本操作

0x01写入:

0x02获取文件大小:

0x03设置文件内容指针:

0x04拷贝文件:

0x05移动文件:

0x06关闭文件句柄:

0x07.文件夹操作

windows操作系统的文件系统:每个文件夹下都有两个默认隐藏的文件夹:

0x08实现文件夹遍历:

0x09.文件映射



0x00.文件基本操作

  •   C文件操作:FILE* fopen fwrite fread fseek ftell fclose

  •    C++文件操作:fstream文件流对象 file.open file.wirte file.read file.seekg file.close

  •   windows文件操作:不太常用,因为用C和C++的文件操作就可以了,前两者是跨平台的,在Linux上也可以使用。

    • 用HANDLE句柄来标识文件

    • 创建打开文件用 CreateFile

    • 写文件 WriteFile

    • 读文件 ReadFile

    • 设置文件内容指针 SetFilePointer

    • 拷贝文件 CopyFile()

    • 获取文件大小 GetFileSize

    • 移动文件 MoveFile

    • 删除文件 DeleteFile

    • 关闭文件 CloseHandle

    • 当我们在写操作系统内核的代码 需要进行文件操作时,没得C和C++编译器供我们使用,只能用这些函数,所以必须掌握。

当一个函数你不知道怎么用的时候,vs编译器按F1或者F12查阅相关文档即可。

HANDLE CreateFileA(//返回创建或者打开后的文件句柄,如果失败,返回-1
  LPCSTR                lpFileName,//文件名
  DWORD                 dwDesiredAccess,//访问方式 GENERIC_ALL 可读可写,GENERIC。。 只读 ,GENERIC。。只写
  DWORD                 dwShareMode,//共享方式,指文件对于操作系统的很多用户的共享方式
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,//安全属性 SECURITY_开头
  DWORD                 dwCreationDisposition,//创建(CREATE_AlWAYS) 还是 打开(OPEN_ALWAYS) 
  DWORD                 dwFlagsAndAttributes,//方式和属性
  HANDLE                hTemplateFile//模板文件句柄
);

CreateFile不仅仅可以用来打开或者创建一个文件,它可以用来打开或者创建一个设备(凡是可以和windows通信的都叫设备,包括文件(读写)、文件夹、逻辑磁盘驱动器(读写),物理磁盘驱动器(读写)、控制台(输入输出)等等)

windows为CreateFile打开的文件 创建一个文件内核对象,用这个内核对象来管理这个文件。多次调用CreateFile打开同一个文件,虽然是同一个文件,但是windows每次都会创建一个新的内核对象来管理它。每个内核对象中都有一个文件内容指针。纸箱应该读写的地址。

而所谓的文件句柄,不过是文件内核对象在进程句柄表的中的索引。

windows文件操作的好处:

1.可以创建隐藏文件:dwFlagsAndAttributes中如果选择FILE_ATTRIBUTE_HIDDEN可创建一个隐藏文件,这是C语言的文件操作所不能办到的。

2.windows文件操作是“线程安全的”,C或C++的文件操作则不是。所谓线程安全,就是当一个文件被当前进程所打开或者创建时,该文件就被该进程的该线程所占有,其他进程的线程无法读写该文件,直到该线程closehandle(即将对应文件的内核对象从该进程的句柄表中删除)或者该线程结束或者该进程结束。

3.很多其他操作:

  1. 获取文件大小
  2. 拷贝文件
  3. 移动文件

 

// win32wenjian.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <windows.h>
int main()
{  
	//1.使用该函数需要添加头文件 windows.h
	//2.该函数的作用:
(1)如果同文件中没有该文件,创建该文件。如果有该文件,打开该文件(文件名处为路径名)。
(2)返回文件句柄给一个变量,方便操作文件。
	HANDLE hFile=CreateFile("file.txt",GENERIC_ALL,
		FILE_SHARE_READ|FILE_SHARE_WRITE,/*共享方式 有 共享读,共享写,共享删除,全部以FILE_SHARE_开头*/
		NULL,
		CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,//普通文件
		NULL); 
	if (INVALID_HANDLE_VALUE == hFile)//如果hFile == -1 即创建文件失败
	{
		printf("创建文件失败!\n");
		return -1;
	}
	printf("创建文件成功!\n");
	

	while (1);
	return 0;
    
}


0x01写入:

//写入
	char buff[256] = "像我这么优秀的人,本该灿烂过一生\n";//16*2=32
	DWORD writeOff = 0;
	bool is = WriteFile(hFile, buff, strlen(buff),//总共写入多少个字符
		&writeOff//成功写入了多少字节,一般我们会创建一个DWORD类型的变量,将变量的地址写进去
		, NULL);//是否有其他操作,NULL表示没有
		//之所以要创建一个wirteOff变量,是因为WriteFile函数只有一个返回值,返回写入成功与否,具体成功写入了多少字符,需要用我们创建的变量writeOff来返回。
	if (is)
			printf("写入%d字节\n", writeOff);//如果写入的文字变成了乱码,需要将当前项目属性改为多字节字符集
	else
			printf("写入失败!\n");
	

0x02获取文件大小:

DWORD 就是unsigned long 4个字节,2^32-1B=4 294 967 295B=4GB,即如果仅仅用返回值表示size的话,最大只能表示4GB,但是一个文件可能比4GB大。所以专门设置了一个参数:文件大小的高字节fileSizeHigh,将文件大小分成两个变量来存。低字节存在size中,高字节部分存在另一个变量中。

注意:打开文件和创建文件的 参数不同,CREATE_ALWAYS OPEN_ALWAYS

    //得到文件
    HANDLE hMkv = CreateFile("123.mkv", GENERIC_ALL, FILE_SHARE_READ, NULL, OPEN_ALWAYS,                                                                                                                                           FILE_ATTRIBUTE_NORMAL, NULL);
	if (hMkv == INVALID_HANDLE_VALUE) {
		cout << "打开失败!" << endl;
		return 0;
	}
	DWORD fileSizeHigh;
	DWORD size = GetFileSize(hMkv,&fileSizeHigh);
        long long totalSize = size | (long long)fileSizeHigh << 32;
	printf("文件大小为%ld字节\n", totalSize);

0x03设置文件内容指针:

//设置文件的内容指针
	LONG high = 0;
	SetFilePointer(hFile,
		33,//移动多少个字节
		&high,
		FILE_BEGIN//从什么地方开始移动
		);
	DWORD num;
	WriteFile(hFile, "奈何20多年到头来,还在人海里浮沉",
		strlen("奈何20多年到头来,还在人海里浮沉"),
		&num, NULL);

0x04拷贝文件:

//**拷贝文件**
	bool ret = CopyFile("file.txt",//已经存在的文件名
		"Song.txt",//新的文件名
		true);
	if (ret)
		printf("拷贝成功!\n");
	else
		printf("拷贝失败!\n");
	//用这个函数可以写一个U盘偷猎者

0x05移动文件:

//**移动文件**
	MoveFile("file.txt",//已经存在的文件名 
		"..//dustbin.txt");//移动到上层文件夹中的。。
	DeleteFile("song.txt");

0x06关闭文件句柄:

//关闭文件句柄
	CloseHandle(hFile);
	//只要不关闭文件句柄,现在的进程就一直占据这个文件
	//即拥有该文件的操作权限,禁止其他程序来去操作这个文件
	//直到关闭文件句柄。实现原理:线程锁

0x07.文件夹操作

windows操作系统的文件系统:每个文件夹下都有两个默认隐藏的文件夹:

名称为.的文件夹:表示当前文件夹

名称为..的文件夹:表示上层文件夹

0x08实现文件夹遍历:

 思路:写一个函数 TravelDirectory(传入文件夹名),函数功能就是遍历一个文件夹。怎么遍历呢?当然是用递归来实现,最简单的情形的就是文件夹中没有文件,直接返回。那么如何缩小问题规模呢?当然是采用分治的思想来分布解决这个问题了。遍历一个文件夹可以分为两步,第一步、找到第一个文件,判断是文件还是文件夹,如果找到的是一个文件夹就调用TravelDirectory遍历。如果是文件,就输出文件名。第二步、不断找下一个文件。直到没有下一个文件就返回。

是不是感觉这个思路 和 深度寻路算法很相似呢?其实问题的本质就是深度寻路算法的问题。只不过,遍历文件夹,问题更简单,因为windows的所有文件其实就是一个森林,每一个盘符都是一个树。所以我们不需要像深度遍历图一样,准备一个辅助数组来记录每个点是不是已经走过。

下面我们来详细讨论每一步具体怎么做:

1.通配符号:

%dint
\n换行
\\\
%%%
*对应任意个任意字符
对应一个任意字符

2.找指定文件(从头开始找):

注意:并不是像函数名说的找第一个文件。这个函数就是找指定的文件。

如果能找到指定文件,创建对应的文件对象,返回该文件对象在当前进程的句柄。如果找不到指定名字的文件,返回值为INVALID_HANDLE_VALUE.

HANDLE FindFirstFileA(
    LPCSTR lpFileName, //你要找的文件的名字
    LPWIN32_FIND_DATAA lpFindFileData //你找到的文件的相关信息,根据属性可以判断是文件还是文件夹
 );

使用实例:

        WIN32_FIND_DATA fileInfo;
	HANDLE hFile = FindFirstFile("123.mkv", &fileInfo);
	if (INVALID_HANDLE_VALUE == hFile) {
		cout << "找不到" << endl;
		return 0;
	}
	cout << "找到了" << endl;

我们用一个结构体来存该文件的相关信息,WIN32_FIND_DATA结构体定义如下:

typedef struct _WIN32_FIND_DATAW {
    DWORD dwFileAttributes;//文件属性
    FILETIME ftCreationTime;//创建时间
    FILETIME ftLastAccessTime;//最后一次访问时间
    FILETIME ftLastWriteTime;//最后一次写入时间
    DWORD nFileSizeHigh;//文件大小
    DWORD nFileSizeLow;//文件大小
    DWORD dwReserved0;//保留位,更新迭代之用
    DWORD dwReserved1;//保留位,更新迭代之用
    _Field_z_ WCHAR  cFileName[ MAX_PATH ];//文件名
    _Field_z_ WCHAR  cAlternateFileName[ 14 ];
} WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW;

其中文件属性包括:

#define FILE_ATTRIBUTE_READONLY             0x00000001  
#define FILE_ATTRIBUTE_HIDDEN               0x00000002  
#define FILE_ATTRIBUTE_SYSTEM               0x00000004  
#define FILE_ATTRIBUTE_DIRECTORY            0x00000010  
#define FILE_ATTRIBUTE_ARCHIVE              0x00000020  
#define FILE_ATTRIBUTE_DEVICE               0x00000040  
#define FILE_ATTRIBUTE_NORMAL               0x00000080  
#define FILE_ATTRIBUTE_TEMPORARY            0x00000100  
#define FILE_ATTRIBUTE_SPARSE_FILE          0x00000200  
#define FILE_ATTRIBUTE_REPARSE_POINT        0x00000400  
#define FILE_ATTRIBUTE_COMPRESSED           0x00000800  
#define FILE_ATTRIBUTE_OFFLINE              0x00001000  
#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED  0x00002000  
#define FILE_ATTRIBUTE_ENCRYPTED            0x00004000  
#define FILE_ATTRIBUTE_INTEGRITY_STREAM     0x00008000  
#define FILE_ATTRIBUTE_VIRTUAL              0x00010000  
#define FILE_ATTRIBUTE_NO_SCRUB_DATA        0x00020000  
#define FILE_ATTRIBUTE_EA                   0x00040000  
#define FILE_ATTRIBUTE_PINNED               0x00080000  
#define FILE_ATTRIBUTE_UNPINNED             0x00100000  
#define FILE_ATTRIBUTE_RECALL_ON_OPEN       0x00040000  
#define FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS 0x00400000 

通过这个函数,我们只要将文件名设置为“传入的文件夹名\\*”,就可以找到文件夹中的第一个任意文件。

3.找下一个文件:给定一个文件对象句柄,找到文件夹中该文件的下一个文件。如果能找到下一个文件,返回值为真,如果找不到下一个文件,返回值为假。我们遍历文件夹就是一个循环,我们可以用这个函数来作为循环结束的条件。

BOOL
WINAPI
FindNextFileA(
    _In_ HANDLE hFindFile,
    _Out_ LPWIN32_FIND_DATAA lpFindFileData
 );

源代码:无注释版。这个代码经过修改,可以查找磁盘中特定后缀的文件。并进行特定操作。

// 20-windows文件操作.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <Windows.h>
using namespace std;
void travelDirectory(char* pathName) {
	TCHAR fileName[MAX_PATH] = { 0 };
	sprintf(fileName, "%s\\*", pathName);
	WIN32_FIND_DATA fileInfo;
	HANDLE hFile = FindFirstFile(fileName, &fileInfo);
	if (INVALID_HANDLE_VALUE == hFile) {
		//cout << "空文件夹" << endl;
		return;
	}
	int ret = 1;
	TCHAR tempName[MAX_PATH] = { 0 };
	while (ret) {
		if (fileInfo.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY) {
			if (fileInfo.cFileName[0] != '.') {//跳过".文件夹和..文件夹"
				memset(tempName, 0, sizeof(TCHAR)*MAX_PATH);
				sprintf(tempName, "%s\\%s", pathName, fileInfo.cFileName);
				//printf("文件夹:%s\n", tempName);
				travelDirectory(tempName);
			}	
		}
#if 0
		else if (fileInfo.dwFileAttributes == FILE_ATTRIBUTE_NORMAL) {
			printf("文件:%s\n", fileInfo.cFileName);
		}
#endif
		else if(fileInfo.dwFileAttributes == FILE_ATTRIBUTE_HIDDEN){
			printf("隐藏文件:%s\n", fileInfo.cFileName);
		}
#if 1	
		else {
			printf("其他文件:%s\n", fileInfo.cFileName);
		}
#endif
		ret = FindNextFile(hFile, &fileInfo);
	}
	return;
}
int main()
{
	travelDirectory((char*)"F:");
	cout << "遍历完成" << endl;
	return 0;
}

注释版:

// win32文件夹操作.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <windows.h>//用windows的宏都要加这个头文件

int count = 0;
void findFile(char* fileName) {
	//1.设置文件名 fileName//*.*
	char findFileName[MAX_PATH] = { 0 }; //define MAX_PATH 260 在windows操作系统上,一个文件的名字,最大260个字节
	sprintf(findFileName, "%s\\*.*", fileName);//  反斜杠需要转义 *.*也可以修改为*
	/*
	Windows中常用的通配符是
 * 可以代替所有的字母/中文
 ? 可以代替一个字母/中文
	*/

	//2.循环找文件
	WIN32_FIND_DATA findData;//结构体,用来专门装文件的属性
	HANDLE hFile = FindFirstFile(findFileName, &findData);//
	if (INVALID_HANDLE_VALUE == hFile) {//如果没有找到第一个文件
		return;
	}
	int ret = 1;
	char temp[MAX_PATH];
	while (ret) {//找不到为止
		//2.1判断是文件夹还是文件
		if (FILE_ATTRIBUTE_DIRECTORY == findData.dwFileAttributes) {
			//2.1.1文件夹
			if (findData.cFileName[0] != '.')//防止陷入 .文件夹 和 ..文件夹的死循环里面
			{
				//2.1.1.1设置文件名
				memset(temp, 0, MAX_PATH);
				sprintf(temp, "%s\\%s", fileName, findData.cFileName);
				printf("%d:%s\n", count++, temp);
				Sleep(1000);
				//2.1.1.2遍历文件夹
				findFile(temp);
			}

		}
		else {
			//2.1.2文件
			//打印文件名 或者do someting else
			memset(temp, 0, MAX_PATH);
			sprintf(temp, "%s\\%s", fileName, findData.cFileName);
			printf("%d:%s\n", count++, temp);
			Sleep(1000);
		}
		//2.2找下一个文件
		ret = FindNextFile(hFile, &findData);//如果找到了下一个文件,返回1,没找到返回0
	}
}

int main()
{
	//获取当前文件夹名
	char currentDirName[MAX_PATH] = { 0 };
	GetCurrentDirectory(MAX_PATH,currentDirName);//获取当前文件夹名称,储存在一个字符数组中
	findFile(currentDirName);
	
}

//作业:
//代码行数查看器。
//找到一个文件夹下所有的.c .cpp文件统计一共写了多少行代码

0x09.文件映射

3.1虚拟内存 和 物理内存

物理内存:内存条。

虚拟内存:物理内存映射而来。

3.2 文件映射虚拟内存:

io操作:常规的文件操作方式,需要从磁盘中读取或者写入磁盘

内存操作:CPU直接读写内存,效率更高

可以像操作内存来操作内存,因为文件io(读写)很慢。

3.3.文件映射编程模型:

  1. 打开文件         CreateFile
  2. 创建文件映射 CreateFileMapping
  3. 加载文件映射  MapViewOfFile:把文件映射为内存
  4. 使用映射内存
  5. 卸载文件映射  UnmapViewOfFile
  6. 关闭文件映射  CloseHandle
  7. 关闭文件  CloseHandle
// 文件映射.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <windows.h>

int main()
{
	//step1:创建文件
	HANDLE hFile = CreateFile("American.txt", GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS/*只读*/, FILE_ATTRIBUTE_NORMAL, NULL);
	if (INVALID_HANDLE_VALUE == hFile) {
		printf("创建文件失败!\n");
		return -1;
	}
	printf("创建文件成功!\n");
	//step2:创建文件映射
	HANDLE hFileMapping = CreateFileMapping(hFile,
		NULL,//文件映射的属性:NULL表示默认的属性
		PAGE_READWRITE,//保护方式:PAGE_开头表示文件映射以 内存页(4K)为单位
		0,//高位文件大小
		40,//低位文件大小 文件多大 映射多大 40字节
		"fileMapping1"//映射的名字
	);
	//step3:加载文件映射:即我们可以像使用内存一样去使用这个文件了
	void* p = MapViewOfFile(hFileMapping,//文件映射的句柄
		FILE_MAP_ALL_ACCESS,//访问方式:任何方式,可读可写可操作
		0,     //开始的地方
		0,     //开始的地方
		40 //总共的长度
	);//返回一个指针,这个指针就是映射出来的内存段首地址。
//step4:使用文件映射出来的内存,给内存中赋值 0-9
	int* pp = (int*)p;
	for (int i = 0; i < 10; i++) {
		printf("%d", *pp);
		pp++;
	}
	//step5:卸载文件映射
	UnmapViewOfFile(p);
	//step6:删除文件映射
	CloseHandle(hFileMapping);
	//step7:关闭文件
	CloseHandle(hFile);
}

选做:创建一个保存有4G个电话号码的文件,查找某个电话号码。

方式1.直接读文件

方式2.映射文件为虚拟内存,内存操作。

作业:代码行数统计器

// 代码行数统计者.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <Windows.h>
#include<string.h>
int judge(char* str);

int counter = 0;//统计代码行数
int num=0;//统计C文件数量
int dirs = 0;//统计文件夹数量


void findFile(char* filename){
	char findFileName[MAX_PATH] = { 0 };
	sprintf(findFileName, "%s\\*.*",filename);

	WIN32_FIND_DATA findFileData;
	HANDLE hFile = FindFirstFile(findFileName, &findFileData);
	if (hFile == INVALID_HANDLE_VALUE) {
		printf("该文件夹中没有.C文件\n");
		return;
	}
	int ret = 1;
	char temp[MAX_PATH];
	while (ret) {
		if (FILE_ATTRIBUTE_DIRECTORY ==findFileData.dwFileAttributes) {//如果是文件夹
			if (findFileData.cFileName[0] != '.') {
				dirs++;
				printf("第%d个文件夹:%s\n",dirs,findFileData.cFileName);
				
				memset(temp, 0, MAX_PATH);
				
                sprintf(temp, "%s\\%s", filename, findFileData.cFileName);
				findFile(temp);
			}
		}
		else {

			if (judge(findFileData.cFileName))//如果是一个.c文件
			{
				HANDLE hCpp = CreateFile(findFileData.cFileName, GENERIC_ALL, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

				if (INVALID_HANDLE_VALUE == hCpp) {
					printf("打开文件失败\n");
				}
				else {
					int lines = 0;
					num++;
					printf("第%d个C文件:%s\n", num, findFileData.cFileName);

					printf("打开第%d个文件成功!开始分析代码行数...\n", num);

					//得到文件大小
					DWORD FileSizeHigh = 0;

					DWORD FileSizeLow = GetFileSize(hCpp, &FileSizeHigh);
					printf("该文件低位大小为:%d B,高位大小为%d B\n ", (int)FileSizeLow, (int)FileSizeHigh);
					Sleep(1000);
					//创建文件映射
					HANDLE hFileMapping = CreateFileMapping(hCpp,
						NULL,//文件映射的属性:NULL表示默认的属性
						PAGE_READWRITE,//保护方式:PAGE_开头表示文件映射以 内存页(4K)为单位
						FileSizeHigh,//高位文件大小
						FileSizeLow,//低位文件大小 文件多大 映射多大 
						"fileMapping1"//映射的名字
					);
					//step3:加载文件映射:即我们可以像使用内存一样去使用这个文件了
					void* p = MapViewOfFile(hFileMapping,//文件映射的句柄
						FILE_MAP_ALL_ACCESS,//访问方式
						0,     //开始的地方
						0,     //开始的地方
						FileSizeLow //总共的长度
					);//返回一个指针,这个指针就是映射出来的内存段首地址
					/*
					MapViewOfFile()函数允许全部或部分映射文件,在映射时,
					需要指定数据文件的偏移地址以及待映射的长度。其中,文
					件的偏移地址由DWORD型的参数dwFileOffsetHigh和dwFileOffsetLow
					组成的64位值来指定,而且必须是操作系统的分配粒度的整数倍,对于Windows操作系统,分配粒度固定为64KB。
	。

					*/
					char * ppBuffer = (char*)p;
					if (ppBuffer == NULL) {
						printf("文件指针为空,退出\n");
						return;
					}
					for (int i = 0; i < (int)FileSizeLow; i++) {
						if (*ppBuffer++ == '\n') {
							counter++;
							lines++;
						}


					}
					printf("该文件代码行数大约为:%d\n", lines);

					//step5:卸载文件映射
					UnmapViewOfFile(p);
					//step6:删除文件映射
					CloseHandle(hFileMapping);
				}
				CloseHandle(hCpp);
				
			}
			

		}
		ret = FindNextFile(hFile, &findFileData);
	}


}
int judge(char* str) {
	int len = strlen(str);

	if ((
		*(str + len - 1) == 'p'&&
		*(str + len - 2) == 'p'&&
		*(str + len - 3) == 'c'&&
		*(str + len - 4) == '.') || ((*(str + len - 1) == 'c') && (*(str + len - 2) == '.')))
		return 1;
	else
		return 0;

}
int main()
{
	char currentDirectoryName[MAX_PATH] = { 0 };
	GetCurrentDirectory(MAX_PATH, currentDirectoryName);
	findFile(currentDirectoryName);
	
	printf("小盆友,到目前为止你一共写了%d行代码!\n", counter);
	
	while (1);

}

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值