使用c语言实现INI文件的读写

INI文件是Windows上独有的一种存储结构,这只是Windows下发明的存储结构,也有很多类似的,比如xml!

就说一下TXT文本

是按行读取,这就是它的格式!

而xml是类似二叉树的带层次结构的存储方式

INI则是以键值的方式将数据对应起来,类似数据库一样!

在Windows上我们可以使用一些自带API实现对INI文件的读取写入,但是在其它系统平台上就不可以了,这里是博主在开发跨平台SDT工具包时,写的一款对INI文件操作的函数

在开始之前我们需要编写一些辅助函数,因为这些存储结构,都是对字符进行处理,我们先读入内存,然后开始字符流处理,这里值得注意的是!

 所以需要编写一些CharString处理字符类,这里是从CharString库里copy出来的,这个库是我开源的,你可以在我的博客SDT分类里找到这个库的介绍以及源码!

//获取文本所在行
int GetStrTxTIndx(char* m_str,const char* Str){
	if (m_str == NULL || Str == NULL){
		return 0;
	}
	int StrSize = strlen(Str);
	if (StrSize <= 0){
		return 0;
	}
	int Str_m_str = strlen(m_str);
	int m_str_index = 0;/*二次循环坐标*/
	int y = 0;
	for (int i = 0; i <= Str_m_str - 1; i++/*加一是因为i从0开始,下方循环是size+1是为了将坐标移动到下个字符*/){
		y = 0;
		m_str_index = i;
		for (int j = 0; j <= StrSize - 1; ++j, ++m_str_index){
			if (Str[j] == m_str[m_str_index]){
				y += 1;
			}
			if (y == StrSize){
				return i + 1;
			}
			if (Str[j] != m_str[m_str_index]){
				break;

			}
		}
	}
	return 0;
}
//获取一行文本
char* GetLineTxT(char* m_str,int LineIndex = 1) {
	if (LineIndex == 0){
		return 0;
	}
	if (m_str == NULL) {
		return 0;
	}
	int l = 0;
	char *yun = NULL;
	int j = 0;
	int u = 0;
	for (int i = 0; i <= strlen(m_str) + 1/*包含\0*/; ++i) {
		if (m_str[i] == '\n') {
			++l;
			if (l != LineIndex)
				u = i + 1;//+1跳过\n 这里递增1后面已经忽略0因为已经不是第一行坐标
		}
		if (l == LineIndex) {
			//判断此行多长用于分配内存
			for (j = u;; ++j) {
				if (m_str[j] == '\n' || m_str[j] == '\0') {
					int size = 0;
					if (u == 0){//防止从0下标导致计算实际长度少1,因为额外的1要用来\0
						size = j + 1;	//+1是因为分配内存不是从0做下标所以需要判断是否为0
					}
					else{
						size = j;
					}
					yun = (char*)malloc(size + 1);	//多分配额外字节,\0
					memset(yun, 0, size + 1);
					break;
				}
			}
			int in = 0;
			for (int n = u; n <= j; ++n, ++in) {
				yun[in] = m_str[n];
			}
			return yun;
		}
	}
}
//获取文本多少行
int GetLineNum(char* m_str){
	if (m_str == NULL){
		return 0;
	}
	int Size = 0;
	for (int i = 0; i <= strlen(m_str)-1; ++i){
		if (m_str[i] == '\n'){
			++Size;
		}
		if (m_str[i] == '\0'){
			if (m_str[i - 1] != '\n'){
				Size++;
			}
			return Size;
		}
	}
	return Size;
}
//判断文本是否存在
bool JumpTxTExits(char* m_str,char *Str){
	if (GetStrTxTIndx(m_str,Str) == 0){
		return false;
	}
	return true;
}
//判断文本在第几行
int GetTxTLineNum(char* m_str,char *Str){
	if (Str == NULL || m_str == NULL){
		return NULL;
	}
	char *str1 = NULL;
	int i = 1;
	int u = GetLineNum(m_str);
	while (1){//查找这个文本格式在第几行里
		str1 = GetLineTxT(m_str,i);
		if (str1 == NULL){
			return NULL;
		}
		if (JumpTxTExits(str1, Str) == true){
			break;
		}
		if (i > u){
			return 0;
		}
		++i;
	}
	return i;
}

 

INI读取:

//ini
char* iniGet(char *FileName, char* Primary_Key, char* Key,bool Speck = true){
	//判断是否为空
	if (Primary_Key == NULL || Key == NULL||FileName == NULL){
		return NULL;
	}
	//格式合成
	char *FormatStr = (char*)malloc(strlen(Primary_Key) + 3);	//[]\0
	memset(FormatStr, 0, strlen(Primary_Key) + 3);
	sprintf(FormatStr, "[%s]", Primary_Key); 
	//读入文件
	FILE *fp = fopen(FileName, "r");
	if (fp == NULL){
		return NULL;
	}
	fseek(fp, 0L, SEEK_END);
	signed long long int size = ftell(fp);
	fseek(fp, 0L, SEEK_SET);
	char *txt = (char*)malloc(size + 1);
	memset(txt, 0, size+1);
	fread(txt, size, 1, fp);
	fclose(fp);
	//获取主键值的位置
	int y = GetStrTxTIndx(txt, FormatStr);
	if (y == 0){
		return NULL;
	}
	//循环读取当前键值下的所有值,除遇得下一个键值
	for (int i = y; i <= size-1; ++i){
		if (txt[i] == '\n'){
			for (int j = i; j <= size - 1; ++j){
				if (txt[j] == '['){	//找到了第二个键值
					for (int h = j; h <= size - 1; ++h){
						txt[h] = '\0';
					}
					goto t;	//读取完成后不要继续读取了,直接跳入下一个处理环境,因为我们已经得到当前主键值下的所有内容了
				}
			}
		}
	}
t:
	//判断文本在第几行
	int Lin = GetTxTLineNum(txt, Key);
	if (Lin == 0){
		return NULL;
	}
	//获取所在行文本
	char* st = GetLineTxT(txt, Lin);
	char* stcopy = (char*)malloc(strlen(st) + 1);
	memset(stcopy, 0, strlen(st) + 1);
	strcpy(stcopy, st);
	free(st);
	st = NULL;
	int yunsi = 0;
	//开始处理
	for (int i = 0; i <= strlen(stcopy) - 1; ++i){
		if (stcopy[i] == '='){
			if (Speck){
				if (stcopy[i + 1] == ' '){	//忽略开头空格
					i = i + 1;
					for (; i <= strlen(stcopy) - 1; ++i){
						if (stcopy[i] != ' ')break;

					}
					i = i - 1;
				}
			}
			for (int j = i + 1; j<=strlen(stcopy)-1; ++j){
				++yunsi;
			}
			char* data = (char*)malloc(yunsi + 1);
			if (data == NULL){
				return NULL;
			}
			memset(data, 0, yunsi + 1);
			int hk = 0;
			for (int j = i + 1; j <= strlen(stcopy) - 1; ++j){
				if (stcopy[j] == '\n'){//如果遇到文件尾一样退出
					return data;
				}
				data[hk++] = stcopy[j];
			}
			return data;
		}
	}
	return 0;


}

示列:

ini:

[key1]
Test = test文本
test=这里是不忽略空格的文本
[key2]
Test=test

代码:

iniGet("file.txt","key1","Test");

输出

test文本

//ini写入

//ini
int iniSet(char *FileName, char* Primary_Key, char* Key, char* Data,bool Speck = true){
	if (Primary_Key == NULL || Key == NULL || FileName == NULL){
		return NULL;
	}
	char *FormatStr = (char*)malloc(strlen(Primary_Key) + 3);	//[]\0
	memset(FormatStr, 0, strlen(Primary_Key) + 3);
	sprintf(FormatStr, "[%s]", Primary_Key);
	FILE *fp = fopen(FileName, "r");
	char* flun = NULL;	//用于保存后数据
	if (fp == NULL){
		return NULL;
	}
	fseek(fp, 0L, SEEK_END);
	signed long long int size = ftell(fp);
	fseek(fp, 0L, SEEK_SET);
	char *txt = (char*)malloc(size + 1);
	memset(txt, 0, size + 1);
	flun = (char*)malloc(size + 1);
	memset(flun, 0, size + 1);
	char* jnmhand = (char*)malloc(size + 1+strlen(Data));	//保存头
	memset(jnmhand, 0, size + 1 + strlen(Data));
	fread(txt, size, 1, fp);
	int y = GetStrTxTIndx(txt, FormatStr);
	if (y == 0){
		return NULL;
	}
	int yi = 0;
	if (y != 1){
		for (int i = 0; i <= y - 1; ++i){
			jnmhand[i] = txt[i];
		}
	}
	for (int i = y; i <= size - 1; ++i){
		flun[yi++] = txt[i-1];
		if (txt[i] == '\n'){
			for (int j = i; j <= size - 1; ++j){
				flun[yi++] = txt[j];
				if (txt[j] == '['){	//找到了第二个键值
					for (int h = j; h <= size - 1; ++h){
						//flun[yi++] = txt[h];
						txt[h] = '\0';
					}
					goto t;
				}
			}
		}
	}
t:
	int Lin = GetTxTLineNum(flun, Key);
	if (Lin == 0){
		return NULL;
	}
	char* setdata = (char*)malloc(size + strlen(Data) + 1);
	memset(setdata, 0,size + strlen(Data) + 1);
	int nn = 0;
	int gh = 0;
	bool kj = false;	//防止修改数据时再次遇到=
	for (int i = 0; i <= strlen(flun) - 1; ++i){
		if (flun[i] == '\n'){
			++nn;
		}
		if (nn == Lin - 1){	//代表已经到要修改数据行的头,因为\n在尾
			++nn;
			for (int j = i; j <= strlen(flun) - 1; ++j){	//寻找=
				setdata[gh++] = flun[j];
				if (kj == false){
					//kj = true;
					if (flun[j] == '='){
						//寻找有效数据
						if (Speck){
							j = j + 1;
							for (; j <= strlen(flun); ++j){	//寻找有效数据
								if (flun[j] != ' '){
									break;
								}
							}
							j = j - 1;
							i = j;
						}
						int n = 0;
						for (; n <= strlen(Data) - 1; ++n){
							setdata[gh++] = Data[n];
						}
						for (;;){
							++i;
							if (flun[i] == '\n' || flun[i] == '\0'){
								break;
							}
						}
						
						//i = i + n;
						break;
					}
				}
			}
		}
		setdata[gh++] = flun[i];
		
	}
	strcat(jnmhand, setdata);
	//内存释放
	free(txt);
	free(setdata);
	//数据写入
	fp = fopen(FileName, "w");
	if (fp == NULL){
		return 0;
	}
	fwrite(jnmhand, strlen(jnmhand), 1, fp);
	fclose(fp);
	return 0;
}

用法

iniSet("C:\\Users\\ZZH\\Desktop\\test.ini", "uu", "kk"," k");

完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//获取文本所在行
int GetStrTxTIndx(char* m_str,const char* Str){
	if (m_str == NULL || Str == NULL){
		return 0;
	}
	int StrSize = strlen(Str);
	if (StrSize <= 0){
		return 0;
	}
	int Str_m_str = strlen(m_str);
	int m_str_index = 0;/*二次循环坐标*/
	int y = 0;
	for (int i = 0; i <= Str_m_str - 1; i++/*加一是因为i从0开始,下方循环是size+1是为了将坐标移动到下个字符*/){
		y = 0;
		m_str_index = i;
		for (int j = 0; j <= StrSize - 1; ++j, ++m_str_index){
			if (Str[j] == m_str[m_str_index]){
				y += 1;
			}
			if (y == StrSize){
				return i + 1;
			}
			if (Str[j] != m_str[m_str_index]){
				break;

			}
		}
	}
	return 0;
}
//获取一行文本
char* GetLineTxT(char* m_str,int LineIndex = 1) {
	if (LineIndex == 0){
		return 0;
	}
	if (m_str == NULL) {
		return 0;
	}
	int l = 0;
	char *yun = NULL;
	int j = 0;
	int u = 0;
	for (int i = 0; i <= strlen(m_str) + 1/*包含\0*/; ++i) {
		if (m_str[i] == '\n') {
			++l;
			if (l != LineIndex)
				u = i + 1;//+1跳过\n 这里递增1后面已经忽略0因为已经不是第一行坐标
		}
		if (l == LineIndex) {
			//判断此行多长用于分配内存
			for (j = u;; ++j) {
				if (m_str[j] == '\n' || m_str[j] == '\0') {
					int size = 0;
					if (u == 0){//防止从0下标导致计算实际长度少1,因为额外的1要用来\0
						size = j + 1;	//+1是因为分配内存不是从0做下标所以需要判断是否为0
					}
					else{
						size = j;
					}
					yun = (char*)malloc(size + 1);	//多分配额外字节,\0
					memset(yun, 0, size + 1);
					break;
				}
			}
			int in = 0;
			for (int n = u; n <= j; ++n, ++in) {
				yun[in] = m_str[n];
			}
			return yun;
		}
	}
}
//获取文本多少行
int GetLineNum(char* m_str){
	if (m_str == NULL){
		return 0;
	}
	int Size = 0;
	for (int i = 0; i <= strlen(m_str)-1; ++i){
		if (m_str[i] == '\n'){
			++Size;
		}
		if (m_str[i] == '\0'){
			if (m_str[i - 1] != '\n'){
				Size++;
			}
			return Size;
		}
	}
	return Size;
}
//判断文本是否存在
bool JumpTxTExits(char* m_str,char *Str){
	if (GetStrTxTIndx(m_str,Str) == 0){
		return false;
	}
	return true;
}
//判断文本在第几行
int GetTxTLineNum(char* m_str,char *Str){
	if (Str == NULL || m_str == NULL){
		return NULL;
	}
	char *str1 = NULL;
	int i = 1;
	int u = GetLineNum(m_str);
	while (1){//查找这个文本格式在第几行里
		str1 = GetLineTxT(m_str,i);
		if (str1 == NULL){
			return NULL;
		}
		if (JumpTxTExits(str1, Str) == true){
			break;
		}
		if (i > u){
			return 0;
		}
		++i;
	}
	return i;
}
//ini
char* iniGet(char *FileName, char* Primary_Key, char* Key,bool Speck = true){
	//判断是否为空
	if (Primary_Key == NULL || Key == NULL||FileName == NULL){
		return NULL;
	}
	//格式合成
	char *FormatStr = (char*)malloc(strlen(Primary_Key) + 3);	//[]\0
	memset(FormatStr, 0, strlen(Primary_Key) + 3);
	sprintf(FormatStr, "[%s]", Primary_Key); 
	//读入文件
	FILE *fp = fopen(FileName, "r");
	if (fp == NULL){
		return NULL;
	}
	fseek(fp, 0L, SEEK_END);
	signed long long int size = ftell(fp);
	fseek(fp, 0L, SEEK_SET);
	char *txt = (char*)malloc(size + 1);
	memset(txt, 0, size+1);
	fread(txt, size, 1, fp);
	fclose(fp);
	//获取主键值的位置
	int y = GetStrTxTIndx(txt, FormatStr);
	if (y == 0){
		return NULL;
	}
	//循环读取当前键值下的所有值,除遇得下一个键值
	for (int i = y; i <= size-1; ++i){
		if (txt[i] == '\n'){
			for (int j = i; j <= size - 1; ++j){
				if (txt[j] == '['){	//找到了第二个键值
					for (int h = j; h <= size - 1; ++h){
						txt[h] = '\0';
					}
					goto t;	//读取完成后不要继续读取了,直接跳入下一个处理环境,因为我们已经得到当前主键值下的所有内容了
				}
			}
		}
	}
t:
	//判断文本在第几行
	int Lin = GetTxTLineNum(txt, Key);
	if (Lin == 0){
		return NULL;
	}
	//获取所在行文本
	char* st = GetLineTxT(txt, Lin);
	char* stcopy = (char*)malloc(strlen(st) + 1);
	memset(stcopy, 0, strlen(st) + 1);
	strcpy(stcopy, st);
	free(st);
	st = NULL;
	int yunsi = 0;
	//开始处理
	for (int i = 0; i <= strlen(stcopy) - 1; ++i){
		if (stcopy[i] == '='){
			if (Speck){
				if (stcopy[i + 1] == ' '){	//忽略开头空格
					i = i + 1;
					for (; i <= strlen(stcopy) - 1; ++i){
						if (stcopy[i] != ' ')break;

					}
					i = i - 1;
				}
			}
			for (int j = i + 1; j<=strlen(stcopy)-1; ++j){
				++yunsi;
			}
			char* data = (char*)malloc(yunsi + 1);
			if (data == NULL){
				return NULL;
			}
			memset(data, 0, yunsi + 1);
			int hk = 0;
			for (int j = i + 1; j <= strlen(stcopy) - 1; ++j){
				if (stcopy[j] == '\n'){//如果遇到文件尾一样退出
					return data;
				}
				data[hk++] = stcopy[j];
			}
			return data;
		}
	}
	return 0;


}
//ini
int iniSet(char *FileName, char* Primary_Key, char* Key, char* Data,bool Speck = true){
	if (Primary_Key == NULL || Key == NULL || FileName == NULL){
		return NULL;
	}
	char *FormatStr = (char*)malloc(strlen(Primary_Key) + 3);	//[]\0
	memset(FormatStr, 0, strlen(Primary_Key) + 3);
	sprintf(FormatStr, "[%s]", Primary_Key);
	FILE *fp = fopen(FileName, "r");
	char* flun = NULL;	//用于保存后数据
	if (fp == NULL){
		return NULL;
	}
	fseek(fp, 0L, SEEK_END);
	signed long long int size = ftell(fp);
	fseek(fp, 0L, SEEK_SET);
	char *txt = (char*)malloc(size + 1);
	memset(txt, 0, size + 1);
	flun = (char*)malloc(size + 1);
	memset(flun, 0, size + 1);
	char* jnmhand = (char*)malloc(size + 1+strlen(Data));	//保存头
	memset(jnmhand, 0, size + 1 + strlen(Data));
	fread(txt, size, 1, fp);
	int y = GetStrTxTIndx(txt, FormatStr);
	if (y == 0){
		return NULL;
	}
	int yi = 0;
	if (y != 1){
		for (int i = 0; i <= y - 1; ++i){
			jnmhand[i] = txt[i];
		}
	}
	for (int i = y; i <= size - 1; ++i){
		flun[yi++] = txt[i-1];
		if (txt[i] == '\n'){
			for (int j = i; j <= size - 1; ++j){
				flun[yi++] = txt[j];
				if (txt[j] == '['){	//找到了第二个键值
					for (int h = j; h <= size - 1; ++h){
						//flun[yi++] = txt[h];
						txt[h] = '\0';
					}
					goto t;
				}
			}
		}
	}
t:
	int Lin = GetTxTLineNum(flun, Key);
	if (Lin == 0){
		return NULL;
	}
	char* setdata = (char*)malloc(size + strlen(Data) + 1);
	memset(setdata, 0,size + strlen(Data) + 1);
	int nn = 0;
	int gh = 0;
	bool kj = false;	//防止修改数据时再次遇到=
	for (int i = 0; i <= strlen(flun) - 1; ++i){
		if (flun[i] == '\n'){
			++nn;
		}
		if (nn == Lin - 1){	//代表已经到要修改数据行的头,因为\n在尾
			++nn;
			for (int j = i; j <= strlen(flun) - 1; ++j){	//寻找=
				setdata[gh++] = flun[j];
				if (kj == false){
					//kj = true;
					if (flun[j] == '='){
						//寻找有效数据
						if (Speck){
							j = j + 1;
							for (; j <= strlen(flun); ++j){	//寻找有效数据
								if (flun[j] != ' '){
									break;
								}
							}
							j = j - 1;
							i = j;
						}
						int n = 0;
						for (; n <= strlen(Data) - 1; ++n){
							setdata[gh++] = Data[n];
						}
						for (;;){
							++i;
							if (flun[i] == '\n' || flun[i] == '\0'){
								break;
							}
						}
						
						//i = i + n;
						break;
					}
				}
			}
		}
		setdata[gh++] = flun[i];
		
	}
	strcat(jnmhand, setdata);
	//内存释放
	free(txt);
	free(setdata);
	//数据写入
	fp = fopen(FileName, "w");
	if (fp == NULL){
		return 0;
	}
	fwrite(jnmhand, strlen(jnmhand), 1, fp);
	fclose(fp);
	return 0;
}
int main()
{
	 iniSet("C:\\Users\\ZZH\\Desktop\\test.ini", "uu", "kk"," k");
	
	getchar();
	return 0;
}

 

纯c读写ini配置文件 用c/c++读写ini配置文件有不少第三方的开源库,如iniparser、libini、rwini、UltraLightINIParser等,但都不理想,往往代码较大、功能较弱、 接口使用不方便。尤其在大小写处理、前后空格、各种注释、跨平台换行符支持、带引号字符串处理、无section操作、原格式保持等方面存在问题。 现将本人精心制作的ini读写程序源码奉献给大家,纯c编写,简洁好用。支持windows和linux。 主要特点: 1、支持;和#注释符号,支持行尾注释。 2、支持带引号'或"成对匹配的字符串,提取时自动去引号。引号中可带其它引号或;#注释符。 3、支持无section或空section(名称为空)。 4、支持10、16、8进制数,0x开头为16进制数,0开头为8进制。 5、支持section、key或=号前后带空格。 6、支持\n、\r、\r\n或\n\r换行格式。 7、不区分section、key大小写,但写入时以新串为准,并保持其大小写。 8、新增数据时,若section存在则在该节最后一个有效数据后添加,否则在文件尾部添加。 9、支持指定key所在整行删除,即删除该键值,包括注释。 10、可自动跳过格式错误行,修改时仍然保留。 11、修改时保留原注释:包括整行注释、行尾注释(包括前面空格)。 12、修改时保留原空行。以上三点主要是尽量保留原格式。 不足之处: 1、不支持单key多value(逗号分割),只能一次性提取后自行处理。 2、不支持同名重复section和key。(重复section可视为错误,重复key则可能造成分歧) 3、不能提取所有section或key名称。 使用只需两个文件inirw.h、inirw.c,另有测试程序和工程文件,支持windows和linux。
C语言可以通过使用文件操作函数来读写INI文件INI文件是一种以文本形式存储配置信息的文件,包含一系列的节和键值对。下面是一个简单的示例: 首先,我们需要创建一个INI文件,并写入一些配置信息。 #include <stdio.h> int main() { // 打开INI文件 FILE *file = fopen("config.ini", "w"); if (file == NULL) { printf("无法打开INI文件。\n"); return 1; } // 写入配置信息 fprintf(file, "[System]\n"); fprintf(file, "Version=1.0\n"); fprintf(file, "Author=John Doe\n\n"); fprintf(file, "[Database]\n"); fprintf(file, "Host=localhost\n"); fprintf(file, "Port=3306\n"); fprintf(file, "Username=root\n"); fprintf(file, "Password=123456\n"); // 关闭INI文件 fclose(file); return 0; } 上述代码通过fopen函数创建一个名为config.iniINI文件,并使用fprintf函数写入了一些配置信息,每个节和键值对都以特定的格式写入。写入完成后,可以使用fclose函数关闭文件。 接下来,我们可以通过打开已有的INI文件来读取其中的配置信息。 #include <stdio.h> #include <stdlib.h> int main() { // 打开INI文件 FILE *file = fopen("config.ini", "r"); if (file == NULL) { printf("无法打开INI文件。\n"); return 1; } char line[100]; char section[100]; char key[100]; char value[100]; // 逐行读取INI文件 while (fgets(line, sizeof(line), file) != NULL) { // 解析节 if (line[0] == '[' && line[strlen(line) - 2] == ']') { strncpy(section, line + 1, strlen(line) - 3); section[strlen(line) - 3] = '\0'; } // 解析键和值 else if (sscanf(line, "%[^=]=%[^\n]", key, value) == 2) { printf("节: %s, 键: %s, 值: %s\n", section, key, value); } } // 关闭INI文件 fclose(file); return 0; } 上述代码通过fopen函数打开了名为config.iniINI文件,并使用fgets函数以行为单位读取文件内容。然后,代码解析了每一行的内容,并将解析的结果输出。 这只是一个简单的示例,实际的INI文件读写可能会更加复杂。C语言提供了多种文件操作函数,可以进行更高级的INI文件读写操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

17岁boy想当攻城狮

感谢打赏

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

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

打赏作者

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

抵扣说明:

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

余额充值