C读写配置文件

        在项目开发中,经常需要读取应用配置文件的初始化参数,用于应用在启动前进行一些初始化配置。比如:Eclipse,参数项包含主题、字体大小、颜色、Jdk安装位置、自动提示等。Eclispe配置的文件格式是以键值对的方式存储的,即:key=value的形式,下面是Eclipse部份设置参数:

/instance/org.eclipse.jdt.ui/useQuickDiffPrefPage=true
/instance/org.eclipse.jdt.ui/content_assist_proposals_foreground=0,0,0
/instance/org.eclipse.egit.core/GitRepositoriesView.GitDirectories=/Users/yangxin/Downloads/kakaolink-android/.git\:/Users/yangxin/Documents/workspace_web/tfyj/.git\:
/instance/org.eclipse.wst.jsdt.ui/fontPropagated=true
/instance/org.eclipse.debug.core/org.eclipse.debug.core.USE_STEP_FILTERS=true
/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
/instance/org.eclipse.ui.workbench/org.eclipse.jface.textfont=1|Monaco|14.0|0|COCOA|1|Monaco;
@org.eclipse.jdt.ui=3.8.2.v20130107-165834
/instance/org.eclipse.cdt.ui/spelling_locale_initialized=true
从Eclipse配置文件中可以看出,都是以xxxx=xxxxx如最后一个配置项,key为/instance/org.eclipse.cdt.ui/spelling_locale_initialized,值为:true。在项目开发当中的也经常采用这种方式,笔者参考了Java的java.util.Properties类,设计了一个C的配置文件读写接口,供大家学习和使用。

1、定义接口头文件(Properties.h)

//
//  Properties.h
//  读写配置文件
//
//  Created by 杨信 on 14-4-24.
//  Copyright (c) 2014年 yangxin. All rights reserved.
//

#ifndef _______Properties_h
#define _______Properties_h

#ifdef _cplusplus
extern "C" {
#endif
    
    // 初始化环境,成功返回0,失败返回非0值
    int init(const char *filepath,void **handle);
    
    // 根据KEY获取值,找到返回0,如果未找到返回非0值
    int getValue(void *handle, const char *key, char *value);
    
    // 修改key对应的属性值,修改成功返回0,失败返回非0值
    int setValue(void *handle, const char *key, const char *value);
    
    // 添加一个属性,添加成功返回0,失败返回非0值
    int add(void *handle, const char *key, const char *value);
    
    // 删除一个属性,删除成功返回0,失败返回非0值
    int del(void *handle, const char *key);
    
    // 获取属性文件中所有的key,获取成功返回0,失败返回非0值
    int getKeys(void *handle, char ***keys, int *keyscount);
    
    // 释放所有key的内存空间,成功返回0,失败返回非0值
    int free_keys(char ***keys,int *keyscount);
    
    // 获取属性文件中所有的值,成功返回0,失败返回非0值
    int getValues(void *handle, char ***values, int *valuescount);
    
    // 释放所有value的内存空间,成功返回0,失败返回非0值
    int free_values(char ***values, int *valuescount);
    
    // 获取属性数量,成功返回0,失败返回非0值
    int getCount(void *handle, int *count);
    
    // 释放环境资源,成功返回0,失败返回非0值
    int release(void **handle);
    
    
#ifdef _cplusplus
}
#endif

#endif

2、实现头文件的接口(Properteis.c)

//
//  Properties.c
//  读写配置文件
//
//  Created by 杨信 on 14-4-24.
//  Copyright (c) 2014年 yangxin. All rights reserved.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "Properties.h"

#define KEY_SIZE        128 // key缓冲区大小
#define VALUE_SIZE      128 // value缓冲区大小

#define LINE_BUF_SIZE   256 // 读取配置文件中每一行的缓冲区大小

typedef struct Properties {
    char *key;
    char *value;
    struct Properties *pNext;
}Properties;

typedef struct PROPS_HANDLE {
    Properties *pHead;  // 属性链表头节点
    char *filepath;     // 属性文件路径
}PROPS_HANDLE;

static int createPropsNode(Properties **props);         // 创建一个节点
static int trimeSpace(const char *src,char *dest);      // 去空格
static int saveConfig(const char *filepath,Properties *head);   // 将修改或保存后的配置项保存到文件

// 初始化环境,成功返回0,失败返回非0值
int init(const char *filepath,void **handle)
{
    int ret = 0;
    FILE *fp = NULL;
    Properties *pHead = NULL,*pCurrent = NULL, *pMalloc = NULL;
    PROPS_HANDLE *ph = NULL;
    char line[LINE_BUF_SIZE];               // 存放读取每一行的缓冲区
    char keybuff[KEY_SIZE] = { 0 };         // 存放key的缓冲区
    char valuebuff[VALUE_SIZE] = { 0 };     // 存放value的缓冲区
    char *pLine = NULL;                     // 每行缓冲区数据的指针
    
    if(filepath == NULL || handle == NULL)
    {
        ret = -1;
        printf("fun init error:%d from (filepath == NULL || handler == NULL)\n",ret);
        return ret;
    }
    
    ph = (PROPS_HANDLE *)malloc(sizeof(PROPS_HANDLE));
    if (ph == NULL) {
        ret = -2;
        printf("fun init malloc handle error:%d",ret);
        return ret;
    }
    memset(ph, 0, sizeof(PROPS_HANDLE));
    
    // 打开文件
    fp = fopen(filepath, "r");
    if (!fp) {
        ret = -3;
        printf("fun init open file error:%d from %s\n",ret,filepath);
        return ret;
    }
    
    // 创建头节点
    ret = createPropsNode(&pHead);
    if (ret != 0) {
        fclose(fp);  // 关闭文件
        printf("fun init create head node error:%d\n",ret);
        return ret;
    }
    memset(pHead, 0, sizeof(Properties));
    
    // 保存链表头节点和文件路径到handle中
    ph->pHead = pHead;
    ph->filepath = (char *)malloc(strlen(filepath) + 1);
    strcpy(ph->filepath, filepath);
    
    pCurrent = pHead;

    // 读取配置文件中的所有数据
    while (!feof(fp)) {
        if(fgets(line, LINE_BUF_SIZE, fp) == NULL)
        {
            break;
        }
        
        // 找等号
        if ((pLine = strstr(line, "=")) == NULL) {   // 没有等号,继续读取下一行
            continue;
        }
        
        // 循环创建节点
        ret = createPropsNode(&pMalloc);
        if (ret != 0) {
            fclose(fp);  // 关闭文件
            release((void **)&ph);  // 创建节点失败,释放所有资源
            printf("create new node error:%d\n",ret);
            return ret;
        }

        // 设置Key
        memcpy(keybuff, line, pLine-line);
        trimeSpace(keybuff, pMalloc->key);    // 将keybuff去空格后放到pMallock.key中
    
        // 设置Value
        pLine += 1;
        trimeSpace(pLine, valuebuff);
        strcpy(pMalloc->value, valuebuff);
        
        // 将新节点入链表
        pMalloc->pNext = NULL;
        pCurrent->pNext = pMalloc;
        pCurrent = pMalloc; // 当前节点下移
        
        // 重置key,value
        memset(keybuff, 0, KEY_SIZE);
        memset(valuebuff, 0, VALUE_SIZE);
    }
    
    // 设置环境句柄给调用者
    *handle = ph;
    
    // 关闭文件
    fclose(fp);
    
    return ret;
}

// 获取属性数量,成功返回0,失败返回非0值
int getCount(void *handle, int *count)
{
    int ret = 0,cn = 0;
    PROPS_HANDLE *ph = NULL;
    Properties *pCurrent = NULL;
    if (handle == NULL || count == NULL) {
        ret = -1;
        printf("fun getCount error:%d from (handle == NULL || count == NULL)\n",ret);
        return ret;
    }
    ph = (PROPS_HANDLE *)handle;
    pCurrent = ph->pHead->pNext;
    while (pCurrent != NULL) {
        cn++;
        pCurrent = pCurrent->pNext;
    }
    
    *count = cn;
    
    return ret;
}

// 根据KEY获取值,找到返回0,如果未找到返回非0值
int getValue(void *handle, const char *key, char *value)
{
    int ret = 0;
    PROPS_HANDLE *ph = NULL;
    Properties *pCurrent = NULL;
    if (handle == NULL || key == NULL || value == NULL) {
        ret = -1;
        printf("getValue error:%d from (handle == NULL || key == NULL || value == NULL)\n",ret);
        return ret;
    }
    
    ph = (PROPS_HANDLE *)handle;
    pCurrent = ph->pHead->pNext;
    while (pCurrent != NULL) {
        if (strcmp(pCurrent->key,key) == 0) {
            break;
        }
        pCurrent = pCurrent->pNext;
    }
    
    if (pCurrent == NULL) {
        ret = -2;
        printf("fun getValue warning: not found the key:%s\n",key);
        return ret;
    }
    
    strcpy(value, pCurrent->value);
    
    return ret;
}

// 修改key对应的属性值,修改成功返回0,失败返回非0值
int setValue(void *handle, const char *key, const char *value)
{
    int ret = 0;
    PROPS_HANDLE *ph = NULL;
    Properties *pCurrent = NULL;
    if (handle == NULL || key == NULL || value == NULL) {
        ret = -1;
        printf("fun setValue error:%d from (handle == NULL || key == NULL || value == NULL)\n",ret);
        return ret;
    }
    
    // 获得环境句柄
    ph = (PROPS_HANDLE *)handle;
    
    // 从环境句柄中获取头节点
    pCurrent = ph->pHead->pNext;
    while (pCurrent != NULL) {
        if (strcmp(pCurrent->key, key) == 0) {  // 找到
            break;
        }
        pCurrent = pCurrent->pNext;
    }
    
    if (pCurrent == NULL) { // 未找到key
        ret = -2;
        printf("fun setValue warning: not found the key:%s\n",key);
        return ret;
    }
    
    // 修改key的value
    strcpy(pCurrent->value, value);
    if (strchr(value, '\n') == NULL) {  // 加一个换行符
        strcat(pCurrent->value, "\n");
    }
    
    // 将修改的配置项写入到文件
    ret = saveConfig(ph->filepath, ph->pHead);
  
    return ret;
}

// 添加一个属性,添加成功返回0,失败返回非0值
int add(void *handle, const char *key, const char *value)
{
    int ret = 0;
    PROPS_HANDLE *ph = NULL;
    Properties *pCurrent = NULL;
    if (handle == NULL || key == NULL || value == NULL) {
        ret = -1;
        printf("fun add error:%d from (handle == NULL || key == NULL || value == NULL)\n",ret);
        return ret;
    }
    
    ph = (PROPS_HANDLE *)handle;
    
    //-----------如果key存在链表中,则直接修改,否则添加到链表中-----------//
    pCurrent = ph->pHead;
    while (pCurrent->pNext != NULL) {
        if (strcmp(pCurrent->pNext->key,key) == 0) {
            break;
        }
        pCurrent = pCurrent->pNext;
    }
    
    if (pCurrent->pNext != NULL) {
        return setValue(handle, key, value);
    }
    
    //-----------key不存在,创建一个新的配置项,添加到链表中-----------//
    Properties *pMalloc;
    ret = createPropsNode(&pMalloc);
    if (ret != 0) {
        printf("fun add error:%d from malloc new node.",ret);
        return ret;
    }
    
    strcpy(pMalloc->key, key);
    if (strchr(pCurrent->value,'\n') == NULL) {
        strcat(pCurrent->value, "\n");
    }
    strcpy(pMalloc->value, value);
    if (strchr(value, '\n') == NULL) {  // 加一个换行符
        strcat(pMalloc->value, "\n");
    }
    pCurrent->pNext = pMalloc;  // 新配置项入链表
    
    // 将新配置项写入到文件
    ret = saveConfig(ph->filepath, ph->pHead);
    
    return ret;
}

// 删除一个属性,删除成功返回0,失败返回非0值
int del(void *handle, const char *key)
{
    int ret = 0;
    PROPS_HANDLE *ph = NULL;
    Properties *pCurrent = NULL, *pPrev = NULL;
    if (handle == NULL || key == NULL) {
        ret = -1;
        printf("fun del error:%d from (handle == NULL || key == NULL)\n",ret);
        return ret;
    }
    
    ph = (PROPS_HANDLE *)handle;
    pPrev = ph->pHead;
    pCurrent = ph->pHead->pNext;
    
    while (pCurrent != NULL) {
        if (strcmp(pCurrent->key, key) == 0) {
            break;
        }
        pPrev = pCurrent;           // 上一个节点下移
        pCurrent = pCurrent->pNext; // 当前节点下移
    }
    
    if (pCurrent == NULL) { // 没有找到
        ret = -2;
        printf("fun del warning:not found the key:%s\n",key);
        return  ret;
    }
    
    pPrev->pNext = pCurrent->pNext; // 从链表中删除
    free(pCurrent); // 释放内存
    pCurrent = NULL;
    
    // 保存到文件
    ret = saveConfig(ph->filepath, ph->pHead);
    
    return ret;
}

// 获取属性文件中所有的key,获取成功返回0,失败返回非0值
int getKeys(void *handle, char ***keys, int *keyscount)
{
    int ret = 0, count = 0, index = 0;
    PROPS_HANDLE *ph = NULL;
    Properties *pCurrent = NULL;
    char **pKeys = NULL;
    if (handle == NULL || keys == NULL || keyscount == NULL) {
        ret = -1;
        printf("fun getKeys error:%d from (handle == NULL || keys == NULL || keyscount == NULL) \n",ret);
        return ret;
    }
    
    // 获取配置项数量
    ret = getCount(handle, &count);
    if (ret != 0) {
        printf("fun getKeys error:%d from getCount \n",ret);
        return ret;
    }
    
    ph = (PROPS_HANDLE *)handle;
    pCurrent = ph->pHead->pNext;
    
    // 根据链表长度,申请内存空间
    pKeys = (char **)malloc(sizeof(char *) * count);
    if (pKeys == NULL) {
        ret = -2;
        printf("fun getKeys error:%d from malloc keys\n",ret);
        return ret;
    }
    
    pCurrent = ph->pHead->pNext;
    while (pCurrent != NULL) {
        pKeys[index] = pCurrent->key;
        pCurrent = pCurrent->pNext;
        index++;
    }
    
    *keys = pKeys;
    *keyscount = count;
    
    return ret;
}

// 释放所有key的内存空间,成功返回0,失败返回非0值
int free_keys(char ***keys,int *keyscount)
{
    int ret = 0;
    if (keys == NULL || keyscount == NULL) {
        ret = -1;
        printf("fun free_keys error:%d from (keys == NULL || keyscount == NULL) \n",ret);
        return ret;
    }
    
    free(*keys);
    *keys = NULL;
    *keyscount = 0;
    
    return ret;
}

// 获取属性文件中所有的值,成功返回0,失败返回非0值
int getValues(void *handle, char ***values, int *valuescount)
{
    int ret = 0, count = 0, index = 0;
    PROPS_HANDLE *ph = NULL;
    Properties *pCurrent = NULL;
    char **pValues = NULL;
    if (handle == NULL || values == NULL || valuescount == NULL) {
        ret = -1;
        printf("fun getValues error:%d from (handle == NULL || values == NULL || valuescount == NULL)\n",ret);
        return ret;
    }
    
    // 获取配置项数量
    ret = getCount(handle, &count);
    if (ret != 0) {
        printf("fun getValues error:%d from getCount \n",ret);
        return ret;
    }
    
    // 申请内存空间,存放所有的value
    pValues = (char **)malloc(sizeof(char *) * count);
    if (pValues == NULL) {
        ret = -2;
        printf("fun getValues error:%d from malloc values\n",ret);
        return ret;
    }
    
    ph = (PROPS_HANDLE *)handle;
    pCurrent = ph->pHead->pNext;
    while (pCurrent != NULL) {
        pValues[index] = pCurrent->value;
        pCurrent = pCurrent->pNext;
        index++;
    }
    
    *values = pValues;
    *valuescount = count;
    
    return ret;
}

// 释放所有value的内存空间,成功返回0,失败返回非0值
int free_values(char ***values, int *valuescount)
{
    int ret = 0;
    if (values == NULL || valuescount == NULL) {
        ret = -1;
        printf("fun free_values error:%d from (values == NULL || valuescount == NULL) \n",ret);
        return ret;
    }
    
    free(*values);
    *values = NULL;
    *valuescount = 0;
    
    return ret;
}

// 释放环境资源,成功返回0,失败返回非0值
int release(void **handle)
{
    int ret = 0;
    PROPS_HANDLE *ph = NULL;
    if(handle == NULL)
    {
        ret = -1;
        printf("release error:%d from (handler == NULL)\n",ret);
        return ret;
    }
    
    ph = (PROPS_HANDLE *)*handle;
    
    // 释放链表内存资源
    Properties *pCurr = ph->pHead;
    Properties *pTemp = NULL;
    
    while (pCurr != NULL) {
        if (pCurr->key != NULL) {
            free(pCurr->key);
            pCurr->key = NULL;
        }
        
        if (pCurr->value != NULL) {
            free(pCurr->value);
            pCurr->value = NULL;
        }
        
        pTemp = pCurr->pNext;
        
        free(pCurr);
        
        pCurr = pTemp;
    }
    
    // 释放存放配置文件路径分配的内存空间
    if(ph->filepath != NULL)
    {
        free(ph->filepath);
        ph->filepath = NULL;
    }
    
    // 释放环境句柄本身
    free(ph);
    *handle = NULL;    // 避免野指针
        
    return ret;
}

// 去空格
static int trimeSpace(const char *src,char *dest)
{
    int ret = 0;
    if (src == NULL || dest == NULL) {
        ret = -1;
        printf("trimeSpace error:%d from (src == NULL || dest == NULL)\n",ret);
        return ret;
    }
    
    const char *psrc = src;
    unsigned long i = 0,j = strlen(psrc) - 1,len;
    while (psrc[i] == ' ')
    {
        i++;
    }
    
    while (psrc[j] == ' ') {
        j--;
    }
    
    len = j - i + 1;
    
    memcpy(dest,psrc+i,len);
    *(dest+len) = '\0';
    
    return ret;
}

// 创建一个节点
static int createPropsNode(Properties **props)
{
    int ret = 0;
    Properties *p = NULL;
    if (props == NULL) {
        ret = -100;
        printf("createProps error:%d from (props == NULL)\n",ret);
        return ret;
    }
    
    p = (Properties *)malloc(sizeof(Properties));
    if (p == NULL) {
        ret = -200;
        printf("createProps malloc %ld bytes error:%d\n",sizeof(Properties),ret);
        return ret;
    }
    p->key = (char *)malloc(KEY_SIZE);
    p->value = (char *)malloc(VALUE_SIZE);
    p->pNext = NULL;
    
    *props = p;
    
    return ret;
}

// 保存到文件
static int saveConfig(const char *filepath,Properties *head)
{
    int ret = 0,writeLen = 0;
    FILE *fp = NULL;
    Properties *pCurrent = NULL;
    if (filepath == NULL || head == NULL) {
        ret = -100;
        printf("fun saveConfig error:%d from (filepath == NULL || head == NULL)\n",ret);
        return ret;
    }
    
    fp = fopen(filepath,"w");
    if (fp == NULL) {
        ret = -200;
        printf("fun saveConfig:open file error:%d from %s\n",ret,filepath);
        return ret;
    }
    
    pCurrent = head->pNext;
    while (pCurrent != NULL) {
        writeLen = fprintf(fp, "%s=%s",pCurrent->key,pCurrent->value);    // 返回写入的字节数,出现错误返回一个负值
        if (writeLen < 0) {  //TODO 如果写入失败,如何将写入的数据回退???
            ret = -300;
            printf("fun saveConfig err:%d from (%s=%s)\n",ret,pCurrent->key,pCurrent->value);
            break;
        }
        pCurrent = pCurrent->pNext;
    }

    fclose(fp); // 关闭文件
    
    return ret;
}

3、测试代码(需要在项目根目录创建props.txt)

//
//  main.c
//  读写配置文件
//
//  Created by 杨信 on 14-4-24.
//  Copyright (c) 2014年 yangxin. All rights reserved.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Properties.h"

int main(int argc, const char * argv[])
{
    int ret;
    void *handle;
    const char *filepath = "/Users/yangxin/Desktop/props.txt";
    // 初始化
    ret = init(filepath, &handle);
    if (ret != 0) {
        printf("env init error:%d\n",ret);
        return 0;
    }
    
    char valuebuf[128];
    // 测试获取配置项
    ret = getValue(handle, "host", valuebuf);
    if (ret == 0) {
        printf("value=%s\n",valuebuf);
    }
    else {
        printf("获取值host的值失败\n");
    }
    
    // 测试修改配置项
    ret = setValue(handle, "version", "1.2.3");
    if (ret == 0) {
        printf("修改成功!\n");
    }
    else{
        printf("修改失败\n");
    }
    
    // 测试添加配置项
    ret = add(handle, "pool_connection_countxx", "2000");
    if (ret == 0) {
        printf("添加成功!\n");
    }
    else{
        printf("添加失败\n");
    }
    
    // 测试删除配置项
    ret = del(handle, "connectionMax");
    if (ret == 0) {
        printf("删除成功!\n");
    }
    else{
        printf("删除失败\n");
    }
    
    // 测试获取所有配置项的key
    char **keys = NULL;
    int keyscount;
    ret = getKeys(handle, &keys, &keyscount);
    if (ret == 0) {
        printf("一共有%d个Key\n",keyscount);
        for (int i =0; i<keyscount; i++) {
            printf("%s\n",keys[i]);
        }
    }
    // 释放内存
    ret = free_keys(&keys, &keyscount);
    if (ret == 0) {
        printf("keys释放内存成功!\n");
    }
    
    // 测试获取所有配置项的value
    char **values = NULL;
    int valuescount;
    ret = getValues(handle, &values, &valuescount);
    if (ret == 0) {
        printf("一共有%d个Value\n",valuescount);
        for (int i = 0; i < valuescount; i++) {
            printf("%s",values[i]);
        }
    }
    // 释放内存
    ret = free_values(&values, &valuescount);
    if (ret == 0) {
        printf("values释放内存成功!\n");
    }
    
    // 释放资源
    ret = release(&handle);
    if (ret != 0) {
        printf("env release error:%d\n",ret);
    }
    
    return 0;
}

测试配置文件:

username=root
password=root123456
host=192.168.1.100
port=9090
connectionMax=200
version=1.0

测试结果:

源码下载地址:git@github.com:xyang0917/RWAppProperites.git

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
纯cini配置文件 用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。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

xyang0917

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

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

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

打赏作者

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

抵扣说明:

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

余额充值