API学习笔记:4.3文件和目录管理


本章所需要学习:
□ 删除、 复制、 重命名、 移动文件 。
□ 创建、 打开、 读写文件。
□ 创建、 打开目录。
□ 获取当前目录、 获取程序所在的目录、 获取模块路径。
□ 査找文件、 遍历目录下的文件和子目录。
□ 递归遍历目录树。
□ 获取、 设置文件属性和时间。

4.3.1 删除、 复制、 重命名、 移动文件

关键API:

API说明
DeleteFile删除文件, 以文件路径作为输入参数, 指向需要删除的文件路径字符串。
CopyFile复制文件 。 通过参数输入复制文件和源路径和目的路径, 路径可以是绝对路径也可以是相对路径 ,还可以通过参数指明如果目的路径己经存在文件, 是否覆盖。
MoveFile移动、 重命名文件和目录。 通过参数输入源路径和目的路径, 路径可以是绝对路径也可以是相对路径, 如果目的路径的文件或目录已经存在, 则返回失败。
代码示例:
#include<Windows.h>
#include<stdio.h>


int main(int argc,  PCHAR argv[])
{
	//-d参数,删除文件:
	if (lstrcmpA("-d", argv[1])==0  && argc == 3)
	{
		if (!DeleteFileA(argv[2]))
		{
			printf("删除文件错误:%x\n", GetLastError());
		}
		else
		{
			printf("删除成功!");
		}
	}
	//-c参数,复制文件
	else if(lstrcmpA("-c", argv[1])==0 && argc==4)
	{
		//复制,不覆盖已经存在的文件
		if (!CopyFileA(argv[2], argv[3], TRUE))
		{
			//LastError==0x50,文件存在
			if (GetLastError() == 0x50)
			{
				printf("文件%s以及存在,是否覆盖?y/n:", argv[3]);
				{
					if ('y' == getchar())
					{
						//复制,且覆盖文件
						if (!CopyFileA(argv[2], argv[3], FALSE))
							printf("复制文件错误, % d\n", GetLastError());
						else printf("复制成功!\n");
					}
					else printf("取消复制!\n"); return 0;
				}
			}
		}
		else printf("复制成功!\n");		
	}
	//-m参数,移动,重命名文件
	else if (lstrcmpA("-m", argv[1]) && argc == 4)
	{
		if (!MoveFileA(argv[2], argv[3]))	 printf("移动文件错误:%d\n", GetLastError());
		else printf("移动文件成功!\n");
	}
	else printf("参数错误!\n");


	return 0;
}

编译后再同目录下生成.exe文件,需要再CMD下执行相应命令实现相应功能。

因为vs2022我默认的是Unicode,但是cmd主要是以GBK编码为主,也懒得去修改,所以这里需要用到A版的命令来配合cmd的编码以防止字符串输入问题和乱码

4.3.2 创建、 打开、 读写文件 , 获取文件大小

创建和打开文件都是使用 API 函数 CreateFile, CreateFile 通过指定不同的参数来表示是新建一个文件, 打开已经存在的文件, 还是重新建立文件等。 读写文件最为直接的方式是使用 ReadFile 和 WriteFile 函数, 也可以使用文件镜像。 获取文件大小一般使用 GetFileSize函数。读写文件、 获取文件大小之前都需要使用 CreateFile 创建或打开的文件, 获得文件句柄。
在文件操作中, 文件句柄是一个关键的概念, 文件句柄唯一,标识了一个文件, ReadFile 、WriteFile、 GetFileSize 等函数是使用文件句柄作为参数来表示, 用户需要读、 写、 获取大小的文件是哪—个文件,在对文件进行操作前, 都必须要使用 CreatdFile 获得文件句柄。

关键API:

API说明
CreateFile文件操作中最主要的一个函数。 几乎所有的文件操作都需要使用到文件句柄。 而CreateFile 函数为这些操作建立文件句柄。
ReadFile从文件中读出数据。
WriteFile将数据写入到文件中, 写入到文件指针所在的位置, 写入操作完成后,文件指针会移动到写入的数据之后。
GetFileSize、 GetFileSizeEx功能是一致的, 都是获取文件大小。

代码示例:

#include<Windows.h>
#include<stdio.h>
#include<iostream>
using namespace std;
//功能:读取文件的大小,并以十六进制打印出来

DWORD ReadFileContent(LPTSTR szFilePath)
{	
	HANDLE hFileRead = 0;						//文件句柄
	LARGE_INTEGER liFileSize = { 0 };			//保存文件大小
	DWORD dwReadedSize;							//成功读取的文件数据大小
	LONGLONG liTotalRead = 0;					//累加计算依据读取数据的大小
	BYTE lpFileDataBuffer[32] = { 0 };			//文件数据缓存

	//打开依据存在的文件,读取内容:
	hFileRead = CreateFile(szFilePath,		//要打开的文件名
		GENERIC_READ,						//以读方式打开
		FILE_SHARE_READ,					//共享读
		NULL,								//默认安全设置
		OPEN_EXISTING,						//只打开已经存在的文件
		FILE_ATTRIBUTE_NORMAL,				//常规文件属性
		NULL);								//无模板

	//打开是否成功
	if (hFileRead == INVALID_HANDLE_VALUE)
	{
		cout << "打开文件失败:" <<GetLastError() << endl;
	}
	else if(!GetFileSizeEx(hFileRead,&liFileSize))
	{
		cout << "获取文件大小失败:" << GetLastError() << endl;
	}
	else
	{
		cout << "文件大小为:" << liFileSize.QuadPart << endl;
	}
	DWORD i=0;
	while (true)
	{
		if (!ReadFile(hFileRead,	//读取文件的句柄
			lpFileDataBuffer,		//存储读取文件内同
			32,						//读的字节	
			&dwReadedSize,			//实际读取的大小
			NULL))					//不使用结构体
		{
			cout << "读取文件错误:" << GetLastError() << endl;
			break;
		}
		cout << "读取了" << dwReadedSize << "字节,文件内容:";
		for (i = 0; i < dwReadedSize; i++)
		{
			/*cout << "0x" << hex<<(int)lpFileDataBuffer[i]<<"   ";*/
			printf("0x%x      ", lpFileDataBuffer[i]);
		}
		cout << endl;
		liTotalRead += dwReadedSize;
		if (liTotalRead == liFileSize.QuadPart)
		{
			cout << "文件读取结束" << endl;
			break;
		}
	}
	CloseHandle(hFileRead);
	return 0;	
}

//将数据存储到文件末尾
DWORD SaveDataToFile(LPTSTR szFilePath, LPVOID lpData, DWORD dwDataSize)
{
	//文件句柄
	HANDLE hFileWrite = 0;
	//成功写入的数据大小
	DWORD dwWritedDataSize = 0;

	//打开已经存在的文件,读取内容:
	hFileWrite = CreateFile(szFilePath,	//要打开的文件名
		GENERIC_WRITE,					//写的方式打开
		0,								//可共享读
		NULL,							//默认安全设置
		OPEN_ALWAYS,					//打开已经存在的文件,没有则创建
		FILE_ATTRIBUTE_NORMAL,			//常规文件属性
		NULL);							//无模板

	if (hFileWrite == INVALID_HANDLE_VALUE)
	{
		cout << "打开文件失败:" << GetLastError() << endl;
	}
	SetFilePointer(hFileWrite, 0, 0, FILE_END);//设置文件从尾部写入
	if (!WriteFile(hFileWrite, lpData, dwDataSize, &dwWritedDataSize, NULL))
		cout << "写入文件失败" << dwWritedDataSize << endl;
	else cout << "写入文件成功,写入" << dwWritedDataSize << "字节" << endl;
	CloseHandle(hFileWrite);
	return 0;
}


int main()
{
	TCHAR szFileData[] = TEXT("这是一个例子");
	SaveDataToFile((LPTSTR) (TEXT("g:\\test\\show.txt")), &szFileData, lstrlen(szFileData)*2);
	ReadFileContent((LPTSTR)(TEXT("g:\\test\\show.txt")));
	return 0;
}

4.3.3 创建目录

关键API

API说明
CreateDirectory创建文件夹,可以是相对路径和绝对路径

使用 CreateDirectoiy 函数在程序当前目录下创建一个“sub_dir”子目录, 在 指定路径下创建"example_dir"代码示例:

#include<iostream>
#include<stdio.h>
#include<Windows.h>
using namespace std;

int main()
{
	//在程序当前目录下创建目录
	TCHAR szDirPath[] = TEXT("text_dir");
	if (!CreateDirectory(szDirPath, NULL))
	{
		cout << "创建目录错误" << endl;
		return 1;
	}
	//指定路径创建文件夹
	TCHAR szDirPath1[] = TEXT("G:\\test\\example_dir");
	if (!CreateDirectory(szDirPath1, NULL))
	{
		cout << "创建目录错误" << endl;
		return 1;
	}
	cout << "创建成功" << endl;
	return 0;
}

4.3.4 获取程序所在的目录、 程序模块路径, 获取和设置当前目录

关键API:

API说明
GetCurrentDirectory获取进程的当前目录
SetCurrentDirectory设置进程的当前目录
GetModuleFileName获取模块文件名, 当第一个参数为 NULL 时获取当前模块路径

获取、设置进程或模块路径:
本实例首先获取并打印出程序的当前路径, 如果程序的当前路径没有经过设置, 默认情况下将是程序运行时所在的目录。 然后将当前目录设置设置完成后, 使用相对路径创建的目录,目的是验证相对路径是相对于进程的当前路径的, 而不是可执行文件所在的路径。 然后使用GetModuleFileName 获取了本模块和 kernel32.exe 的路径。
代码示例:

#include<iostream>
#include<stdio.h>
#include<Windows.h>
using namespace std;


//演示获取当前路径,模块路径
int main()
{
	wcout.imbue(locale(""));
	//TCHAR nBufferLength[MAX_PATH] = { 0 };
	//DWORD ret= GetCurrentDirectory(MAX_PATH, nBufferLength);
	//wcout << "当前进程目录" << nBufferLength << endl;
	//SetCurrentDirectory(TEXT("G:\\text\\"));
	//ret = GetCurrentDirectory(MAX_PATH, nBufferLength);
	//wcout << "设置进程目录" << nBufferLength << endl;
	//ret= GetModuleFileName(NULL, nBufferLength, MAX_PATH);
	//wcout << "模块当前路径"  << nBufferLength << endl;


	wcout.imbue(locale(""));  //用于输出宽字符串
	//用于存储当前路径
	TCHAR szCurrentDirectory[MAX_PATH]={0};
	//用于存储模块路径
	TCHAR szMoudlePath[MAX_PATH]={0};
	//Kernel32 文件名与句柄
	const TCHAR* szKernel32 =L"kernel32.dll";
	HMODULE hKernel32 =0;
	//当前路径的长度,也用于判断获取是否成功
	DWORD dwCurDirPathLen = 0;

	//获取继承当前目录
	dwCurDirPathLen = GetCurrentDirectory(MAX_PATH, szCurrentDirectory);
	if (dwCurDirPathLen == 0)
	{
		//printf("获取当前目录错误。\n");
		cout << "获取当前目录错误。" << endl;
		return 0;
	}
	//wprintf(L"进程当前目录为:%s\n ", szCurrentDirectory);
	wcout << L"进程当前目录为:" << szCurrentDirectory << endl;;

	//将进程当前目录设置为g:\test
	lstrcpy(szCurrentDirectory, L"G:\\test");
	if (!SetCurrentDirectory(szCurrentDirectory))
	{
		cout << "设置当前目录错误" <<endl;
		return 0;
	}
	wcout << L"已经设置当前目录为" << szCurrentDirectory << endl;

	//在当前目录下创建子目录"current_dir"
	//运行完成后 g:\test\ 出现"current_dir"
	CreateDirectory(L"current_dir", NULL);

	//再次获取系统当前目录
	dwCurDirPathLen = GetCurrentDirectory(MAX_PATH, szCurrentDirectory);
	if (dwCurDirPathLen == 0)
	{
		cout << "获取当前目录错误。" << endl;
		return 0;
	}
	wcout << L"进程当前目录为:" << szCurrentDirectory << endl;;

	//使用null参数,获取本地模块路径
	if (!GetModuleFileName(NULL, szMoudlePath, MAX_PATH))
	{
		cout << "获取模块路径错误\n" << endl;
	}
	wcout << L"本地模块路径" << szMoudlePath << endl;

	//获取Kernel32.dll的模块句柄
	hKernel32 = LoadLibrary(szKernel32);

	//使用Kernel32.dll的模块句柄,获取其路径
	if (!GetModuleFileName(hKernel32, szMoudlePath, MAX_PATH))
	{
		cout << "获取模块路径错误" << endl;
	}
	wcout << L"Kernle32模块路径:" << szMoudlePath << endl;

	return 0;
}

注意事项:
□ 在进程中使用相对路径, 则相对路径的起始点是程序的当前路径而不是可执行文件所在的路径。
□ 进程的当前路径在默认情况下是应用程序可执行文件所在的路径。
□ 模块路径与程序的当前路径是两个概念, 进程的主程序和进程中所加载的所有DLL 都是进程的模块。

4.3.5 查找文件、 遍历指定目录下的文件和子目录

有一组专门的函数和结构, 用于遍历目录。
关键API:

API说明
FindFirstFile査找第一个目录或文件, 获取査找句柄
FindNextFile对文件、 文件夹进行循环査找
关键数据结构:
WIN32_FILE_DATA 结构用于表示找到的文件, 结构中包括文件、 目录的名字, 创建、 最后访问和最后写入时间, 文件大小、 文件属性等。
关键数据结构
typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[MAX PATH];
TCHAR cAlternateFileName[14];
} WIN32_FIND_DATA, *PWIN32 FINDJ3ATA, *LPWIN32_FIND_DATA;

遍历指定目录代码示例:

#include<iostream>
#include<stdio.h>
#include<Windows.h>
using namespace std;

//本实例实现对指定目录中文件和子目录的遍历, 并将遍历得到的文件和其他属性打印到界面上。

DWORD EnumerateFileInDirectory(LPTSTR szPath)
{
	WIN32_FIND_DATA FindFileData = { 0 };
	HANDLE hListFile = 0;
	TCHAR szFilePath[MAX_PATH] = { 0 };

	//构造代表目录和文件夹路径的字符串,使用通配符"*"
	wsprintf(szFilePath, L"%s\\*.*", szPath);
	//查找第一个文件/目录,获得句柄
	hListFile = FindFirstFile(szFilePath, &FindFileData);
	if (hListFile == INVALID_HANDLE_VALUE)
	{
		FindClose(hListFile);
		cout << "错误:" << GetLastError() << endl;
		return 1;
	}
	else
	{
		while (FindNextFile(hListFile,&FindFileData))
		{
			//过滤上级目录"."和".."
			if (lstrcmp(FindFileData.cFileName, TEXT(".")) == 0 ||
				lstrcmp(FindFileData.cFileName, TEXT("..")) == 0) continue;
			//打印文件名或者目录名
			wcout << FindFileData.cFileName;			
			//判断文件属性部分示例:
			if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED)
				cout << "<加密>";
			if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
				cout << "<隐藏>";
			if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				cout << "<文件夹>";
			//如果需要知道其他属性可以自行测试
			cout << endl;
		}
	}
	return 0;
}

int main()
{
	wcout.imbue(locale(""));  //用于输出宽字符串

	//遍历当前文件所在文件夹
	TCHAR szBuffer[MAX_PATH] = { 0 };
	GetCurrentDirectory(MAX_PATH, szBuffer);
	EnumerateFileInDirectory(szBuffer);

	//遍历指定文件夹
	EnumerateFileInDirectory((LPTSTR)TEXT("G:"));
	return 0;
}

4.3.6 递归条用遍历目录树

根据上面的代码稍加修改,进行循环递归调用, 采用树的深度遍历的方法 。 可以遍历指定目录中的所有文件、 包括子目录中的文件。
代码示例:

#include<iostream>
#include<stdio.h>
#include<Windows.h>
using namespace std;

//本实例采用了树的深度遍历算法


//全局变量,记录所有文件和目录数
int dwTotalFileNum = 0;
//遍历所有,打印
int ListAllFileInDirectroy(LPTSTR szPath)
{
	TCHAR szFilePath[MAX_PATH] = { 0 };
	WIN32_FIND_DATA FindFileData = { 0 };
	HANDLE hListFile = 0;
	TCHAR szFullPath[MAX_PATH] = { 0 };
	wsprintf(szFilePath, TEXT("%s\\*"), szPath);

	//查找第一个文件
	hListFile= FindFirstFile(szFilePath, &FindFileData);
	if (hListFile == INVALID_HANDLE_VALUE)
	{
		cout << "错误%d" << GetLastError << endl;
		return 0;
	}
	else
	{
		while (FindNextFile(hListFile, &FindFileData))
		{
			if (lstrcmp(FindFileData.cFileName, TEXT(".")) == 0 || lstrcmp(FindFileData.cFileName, TEXT("..")) == 0)continue;
			wsprintf(szFullPath, TEXT("%s\\%s"), szPath, FindFileData.cFileName);
			dwTotalFileNum++;
			wcout << endl << dwTotalFileNum << "\t" << szFullPath << "\t";
			//重点:如果时目录,递归调用,继续遍历
			if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{
				cout << "文件夹";
				ListAllFileInDirectroy(szFullPath);
			}
		}
	}
	return 0;
}
int main()
{
	wcout.imbue(locale(""));  //用于输出宽字符串
	//查找本程序及本程序下所有子目录文件
	TCHAR szBuffer[MAX_PATH] = { 0 };
	GetCurrentDirectory(MAX_PATH, szBuffer);
	ListAllFileInDirectroy(szBuffer);

	//查找指定文件夹及所有子目录文件
	wsprintf(szBuffer, TEXT("g:/test"));
	ListAllFileInDirectroy(szBuffer);

	return 0;
}

4.3.7 获取、 设置文件属性和时间

提示:大于4G的文件,系统分为了高32位和低32位存储,两个 都需要使用到

关键API

API说明
GetFileAttributes获取文件或目录的属性,要判断文件具体有哪些属性, 需要使用与属性常量进行运算, 如果运行结果为真, 则表示具有这种属性。
GctFileAttributesEx获取文件或目录的属性、时间、 大小, 以 WIN32_FILE_ATTRIBUTE_DATA 结构的形式返回结果
SetFileAttributes设置文件或目录的属性
FileTimeToLocalFileTime把文件时间转换为本地的文件时间
FileTimeToSystemTime将文件时间转换为系统时间( SYSTEMTIME 格式), 便于显示
关键数据结构:
typedef struct _FILETIME {
    DWORD dwLowDateTime;
    DWORD dwHighDateTime;
} FILETIME, *PFILETIME, *LPFILETIME;

typedef struct _SYSTEMTIME {
    WORD wYear;
    WORD wMonth;
    WORD wDayOfWeek;
    WORD wDay;
    WORD wHour;
    WORD wMinute;
    WORD wSecond;
    WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;

typedef struct _WIN32_FILE_ATTRIBUTE_DATA {
    DWORD dwFileAttributes;
    FILETIME ftCreationTime;
    FILETIME ftLastAccessTime;
    FILETIME ftLastWriteTime;
    DWORD nFileSizeHigh;
    DWORD nFileSizeLow;
} WIN32_FILE_ATTRIBUTE_DATA, *LPWIN32_FILE_ATTRIBUTE_DATA;

代码示例:

#include<iostream>
#include<stdio.h>
#include<Windows.h>
using namespace std;

//本实例编译完成后是一个可执行文件, 运行时需输入两个参数, 均是文件路径, 程序将第一个
//参数所指向的文件和目录的属性显示出来, 将第二个参数所指向的文件或目录设置为隐藏和只读。


//获取并显示文件属性
int ShowFileTime(PFILETIME lptime)
{
	//本地文件时间结构
	FILETIME ftLocal = { 0 };
	//系统时间结构
	SYSTEMTIME st = { 0 };
	//调整为本地所在时区的时间
	FileTimeToLocalFileTime(lptime, &ftLocal);
	//转换为系统时间格式,便于显示
	FileTimeToSystemTime(&ftLocal, &st);
	//显示时间信息字符串
	cout << st.wYear << "年" << st.wMonth << "月" << st.wDay << "日,"
		<< st.wHour << ":" << st.wMinute << ":" << st.wSecond << endl;
	return 0;
}

//获取文件大小
int ShowFileSize(DWORD dwFileSizeHigh, DWORD dwFileSizeLow)
{
	ULONGLONG liFileSize = dwFileSizeHigh;
	liFileSize <<= sizeof(DWORD) * 8;
	liFileSize += dwFileSizeLow;
	wcout << L"文件大小:" << liFileSize << endl;
	return 0;
}

//打印文件属性
int ShowFileAttrInfo(DWORD dwAttribute)
{
	//依次判断属性,并显示:
	cout << "文件属性:\t";
	if (dwAttribute & FILE_ATTRIBUTE_ARCHIVE)cout << "<ARCHIVE>";
	if (dwAttribute & FILE_ATTRIBUTE_COMPRESSED)cout << "<压缩>";
	if (dwAttribute & FILE_ATTRIBUTE_ENCRYPTED)cout << "<加密>";
	if (dwAttribute & FILE_ATTRIBUTE_HIDDEN)cout << "<隐藏>";
	if (dwAttribute & FILE_ATTRIBUTE_NORMAL)cout << "<NORMAL>";
	if (dwAttribute & FILE_ATTRIBUTE_OFFLINE)cout << "<OFFLINE>";
	if (dwAttribute & FILE_ATTRIBUTE_READONLY)cout << "<只读>";
	if (dwAttribute & FILE_ATTRIBUTE_SPARSE_FILE)cout << "<SPARSE>";
	if (dwAttribute & FILE_ATTRIBUTE_SYSTEM)cout << "<系统文件>";
	if (dwAttribute & FILE_ATTRIBUTE_TEMPORARY)cout << "<临时文件>";
	cout << endl;
	return 0;
}

int setFileHiddenAndReadonly(LPTSTR szFileName)
{
	//获取原来的文件属性
	DWORD dwFileAttributes = GetFileAttributes(szFileName);
	//将只读和隐藏附加
	dwFileAttributes |= FILE_ATTRIBUTE_READONLY;
	dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
	if (SetFileAttributes(szFileName,dwFileAttributes))cout << "设置属性成功" << endl;
	else cout << "设置属性失败" << endl;
	return 0;
}

int ShowFileAttributes(LPTSTR szPath)
{
	//文件属性结构
	WIN32_FILE_ATTRIBUTE_DATA wfad = { 0 };
	wcout << L"文件:" << szPath;
	//获取文件属性
	if (!GetFileAttributesEx(szPath, GetFileExInfoStandard, &wfad))
	{
		cout << "获取文件属性错误:" << GetLastError();
		return 1;
	}
	//显示相关时间
	cout << "创建时间:\t";
	ShowFileTime(&(wfad.ftCreationTime));
	cout << "最后访问时间\t";
	ShowFileTime(&(wfad.ftLastAccessTime));
	cout << "最后修改时间\t";
	ShowFileTime(&(wfad.ftLastWriteTime));
	cout << "文件大小\t";
	ShowFileSize(wfad.nFileSizeHigh, wfad.nFileSizeLow);
	cout << "文件属性\t";
	ShowFileAttrInfo(wfad.dwFileAttributes);
	
	return 0;	
}

int main()
{
	wcout.imbue(locale(""));  //用于输出宽字符串
	//显示一个文件的属性
	TCHAR szBuffer[MAX_PATH] = { 0 };
	wsprintf(szBuffer, TEXT("g:\\test\\123.png"));
	ShowFileAttributes(szBuffer);

	//设置一个文件为只读和隐藏
	setFileHiddenAndReadonly(szBuffer);
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_NxPro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值