Proc *C/C++入门之动态SQL

7 篇文章 1 订阅
3 篇文章 0 订阅

基本概念
在有些情况下, 在编码时 SQL 语句还不能完整地写出来, 而是在程序执行时才能
构造出来,这种在程序执行临时生成的 SQL 语句叫动态 SQL 语句. 利用动态 SQL 来
编写 Pro*C 程序的方法叫动态 SQL 技术!

目的:加强应用程序的功能和灵活
静态SQL —- 在编写应用程序时,使用EXEC SQL关键字直接嵌入的SQL语句;在proc编译应用程序生成c语言的时,都已经确定
动态SQL —- 在运行应用程序时,由用户动态输入的SQL语句。
在下列情况之一不知道时, 使用动态 SQL 技术:

SQL 语句的文本.
宿主变量的个数。
宿主变量的数据类型
引用的数据库对象, 如列, 索引, 序列, 表, 用户名和视图.
Oracle 中动态 SQL 可用以下两种方法实现:一个是 Oracle 自己的方法, 一个是 ANSI 的方法. 一般建议使用 Oracle 的方法,
但对于较复杂的应用, 可以使用 ANSI 的方法, 因为这样可以使自己的程序简化。

动态SQL1
语法:
EXEC SQL EXECUTE IMMEDIATE :host_string
host_string 字符串

限制
不能执行select语言,仅适用于非select语句
在sqlplus上运行的命令,都可以拿过来来执行
语句中不包含输入宿主变量–一个宿主指针变量指向一块内存空间(存有用户输入的SQL语句)
常用于仅执行一次的动态语句
对重复执行多次的动态SQL语句,降低执行效率

nt main01()
{
    int     ret = 0;    
    int     i = 0;
    char    choosechar;

    memset(mySql, 0, sizeof(mySql));
    pSql = NULL;

    EXEC SQL WHENEVER SQLERROR DO sqlerr02();
    connet();
    pSql = mySql;
    //循环处理sql语言
    for(;;)
    {
        printf("\nplease enter sql(not select ): ");
        gets(mySql);
        //scanf("%s", mySql); --空格截断
        printf("mysql:%s\n", mySql);
        printf("任意键继续....\n");
        getchar();
        EXEC SQL EXECUTE IMMEDIATE :pSql;

        EXEC SQL COMMIT;
        printf("继续执行吗?\n");
        scanf("%c", &choosechar);
        fflush(stdin);

        if (choosechar=='n' || choosechar=='N')
        {
            break;
        }
    }

    EXEC SQL COMMIT WORK RELEASE;
    printf("return ok...\n");

    return ret ;
}

动态SQL2
使用PREPARE命令准备SQL语句
EXEC SQL PREPARE statement_name FROM :host_string;

statement_name: 标识符,
host_string:含SQL语句的字符串

使用EXECUTE命令执行SQL语句
EXEC SQL EXECUTE statement_name [USING :host_variable]

如果SQL语句要通过宿主变量赋值,输入SQL语句时要用占位符仅适用于非select语句
可包含虚拟输入宿主变量和指示器变量,其个数和类型在预编译时已知

int main02()
{
    int     ret = 0;    
    int     i = 0;
    char    choosechar;

    memset(mySql, 0, sizeof(mySql));
    pSql = NULL;

    EXEC SQL WHENEVER SQLERROR DO sqlerr02();
    connet();

    pSql = mySql;
    //循环处理sql语言
    for(;;)
    {
        printf("\n请输入要更新部门编号 ");
        scanf("%d", &deptno);

        printf("\n请输入要新loc值 ");
        scanf("%s", loc);

        //准备动态sql
        EXEC SQL PREPARE my_pre_sql FROM 'update dept set loc = :a where deptno = :b';
        //执行动态sql
        EXEC SQL EXECUTE my_pre_sql USING :loc, :deptno;

        EXEC SQL COMMIT;

        printf("\n 按任意键继续? ");
        getchar();
        printf("\n退出键入n, 其他继续? ");
        scanf("%c", &choosechar);
        fflush(stdin);

        if (choosechar=='n' || choosechar=='N')
        {
            break;
        }
    }

    EXEC SQL COMMIT WORK RELEASE;
    printf("return ok...\n");

    return ret ;
}


动态SQL3
使用PREPARE命令准备SQL语句
EXEC SQL PREPARE statement_name FROM :host_string;

statement_name: 标识符,
host_string:含SQL语句的字符串

使用DECLARE命令定义游标,可以结合游标一块使用
 EXEC SQL DECLARE cursor_name CURSOR FOR statement_name; 
 EXEC SQL OPEN cursor_name [using host_variable_list]
 EXEC SQL FETCH cursor_name INTO host_variable_list
 EXEC SQL CLOSE cursor_name 

查询部门号大于10的所有部门信息
动态sql3 处理选择列表项(select查询出来的结果列数固定) 和 输入宿主变量个数一定
本质:
输入宿主变量个数固定 查询条件固定
输出宿主变量个数固定 返回结果固定

//可以结合游标一块使用

int main()
{
    int     ret = 0;    
    int     i = 0;
    char    choosechar;

    memset(mySql, 0, sizeof(mySql));
    pSql = NULL;

    EXEC SQL WHENEVER SQLERROR DO sqlerr02();
    connet();
    //EXEC SQL WHENEVER NOT FOUND DO nodata();

    //循环处理sql语言
    for(;;)
    {
        printf("\n请输入部门编号 ");
        scanf("%d", &deptno);

        //准备动态sql
        EXEC SQL PREPARE my_pre_sql3 FROM 'select deptno, dname, loc from dept where deptno > :a';

        //定义游标
        EXEC SQL DECLARE c1 CURSOR FOR my_pre_sql3;

        //打开游标
        EXEC SQL OPEN c1 USING :deptno;

        //提取数据
        EXEC SQL WHENEVER NOT FOUND DO break;

        for (;;)
        {
            EXEC SQL FETCH c1 INTO :deptno, :dname,:loc:loc_ind;
            printf("%d\t %s\t %s \n", deptno, dname, loc); 

        }
        EXEC SQL CLOSE c1;


        EXEC SQL COMMIT;

        printf("\n 按任意键继续? ");
        getchar();
        printf("\n键入 n 退出, 其他继续? ");
        scanf("%c", &choosechar);
        fflush(stdin);

        if (choosechar=='n' || choosechar=='N')
        {
            break;
        }
    }

    EXEC SQL COMMIT WORK RELEASE;
    printf("return ok...\n");

    return ret ;
}   



动态SQL4
既适用于SELECT语句,也适用于非SELECT语句、
工程中主要使用该模式
与前面的方法相比有两个突出的不同点:

不但包含选择表项或虚拟输入宿主变量,而且它们的个数或数据类型在编译时还不知道
在其它方法中,ORACLE和C之间的数据类型转换是自动实现的。而在方法4中,由于动态语句中的宿主变量个数和类型在编译时还不知道,因此不能实现自动转换,必须由程序来控制数据类型之间的转换
主要信息:

选择表项和实输入宿主变量的个数
每一个选择表项和实输入宿主变量的成年高度
每一个选择表项和实输入宿主变量的数据类型
每一个输出宿主变量和实输入宿主变量的内存单元地址
ANSI模式

/* 包含C头文件 */  
#include <stdio.h>  
#include <string.h>  

/* 包含SQLCA头文件 */  
#include "sqlca.h"  

/* 定义绑定变量值和选择列表项值的最大长度 */  
#define MAX_VAR_LEN     30 

/* 定义选择列表项名的最大长度 */  
#define MAX_NAME_LEN    31  


/* 定义宿主变量 */  
exec sql begin declare section; 
    char    *usrname = "scott";
    char    *passwd = "tiger";
    char    *serverid = "orcl"; 
    char sql_stat[100];  
    char current_date[20];  
exec sql end declare section;  

void sql_error();  
void connet();
void process_input();  
void process_output();  

int main()  
{  
    /* 安装错误处理句柄 */  
    exec sql whenever sqlerror do sql_error();  

    /* 连接到数据库 */  
    connet();  

    /* 分配输入描述区和输出描述区 */  
    exec sql allocate descriptor 'input_descriptor';  
    exec sql allocate descriptor 'output_descriptor';  

    for( ; ; )  
    {  
        printf("\n请输入动态SQL语句(EXIT:退出):\n");  
        gets(sql_stat);  

        /* EXIT(exit)->退出 */  
        if(0 == strncmp(sql_stat , "EXIT" , 4) || 0 == strncmp(sql_stat , "exit" , 4))  
            break;  

        /* 准备动态SQL语句 */  
        exec sql prepare s from :sql_stat;  

        /* 定义游标 */  
        exec sql declare c cursor for s;  

        /* 处理绑定变量 */  
        process_input();  

        /* 打开游标 
         * select语句:处理查询结果 
         * 其他SQL语句:执行 
        */  
        exec sql open c using descriptor 'input_descriptor';  
        if(0 == strncmp(sql_stat , "SELECT" , 6) , 0 == strncmp(sql_stat , "select" , 6))  
        {  
            process_output();
        }  
        /* 关闭游标 */  
        exec sql close c;  
    }  

    /* 释放输入描述区和输出描述区 */  
    exec sql deallocate descriptor 'input_descriptor';  
    exec sql deallocate descriptor 'output_descriptor';  

    /* 提交事务,断开连接 */  
    exec sql commit work release;  
    puts("谢谢使用ANSI动态SQL!\n");  

    return 0;  
}  

void sql_error()  
{  
    /* 显示SQL错误 */  
    printf("%.*s\n" , sqlca.sqlerrm.sqlerrml , sqlca.sqlerrm.sqlerrmc);  
    exit(0);
}  

void process_input()  
{  
    int i;  

    /* 定义宿主变量 */  
    exec sql begin declare section;  
        int input_count;  
        int input_type ;  
        int input_len;  
        char input_buffer[MAX_VAR_LEN];  
        char name[MAX_NAME_LEN];  
        int occurs;  
    exec sql end declare section;  

    /* 绑定变量->输入描述区 */  
    exec sql describe input s using descriptor 'input_descriptor';  

    /* 取得绑定变量个数 */  
    exec sql get descriptor 'input_descriptor' :input_count = count;  

    /* 循环处理绑定变量名 */  
    for(i = 0 ; i != input_count ; ++i)  
    {  
        occurs = i + 1;  

        /* 取得绑定变量名 */  
        exec sql get descriptor 'input_descriptor' value :occurs :name = name;  
        printf("请输入%s的值:" , name);  
        gets(input_buffer);  

        /* 以NULL结尾 */  
        input_len = strlen(input_buffer);  
        input_buffer[input_len] = '\0';  

        /* 设置绑定变量类型、长度和值 */  
        input_type = 1;  
        exec sql set descriptor 'input_descriptor' value :occurs   
            type = :input_type , length = :input_len , data = :input_buffer;  
    }  
}  

void process_output()  
{  
    int i;  

    // 定义宿主变量  
    EXEC SQL BEGIN DECLARE SECTION ;  

        int output_count;  
        int output_type;  
        int output_len;  
        char output_buffer[MAX_VAR_LEN];
        short  output_indicator;  
        char name[MAX_NAME_LEN];  
        int occurs;  
    EXEC SQL END DECLARE SECTION ;  


    // 选择列表项, 输出描述区 
    exec sql describe output s using descriptor 'output_descriptor';  

    //取得选择列表项个数  
    exec sql get descriptor 'output_descriptor' :output_count = count;  


    //循环处理选择列表项 

    output_type = 12;  //note //设置类型为变长字符串
    //output_type = 1;  //note
    for(i = 0 ; i != output_count ; ++i)  
    {  
        occurs = i + 1;  

        output_len = MAX_VAR_LEN;  

      //printf("22222222222222:%d \n", i);
        // 设置选择列表项的类型和长度 
        exec sql set descriptor 'output_descriptor' value :occurs   
            type = :output_type , length = :output_len;  

        //取得选择列表项的名称并输出 
        exec sql get descriptor 'output_descriptor' value :occurs :name = name;  

        //显示选择列表项名称
        printf("\t%s" , name);  
    }  
    printf("\n");  

    // 提取数据完毕->退出循环  
    exec sql whenever not found do break;  

    // 循环处理选择列表项数据  
    for( ; ; )  
    {  
        // 行数据->输出描述区  
        exec sql fetch c into descriptor 'output_descriptor';  

        // 循环处理每列数据  
        for(i = 0 ; i < output_count ; ++i)  
        {  
            occurs = i +1;  

            // 取得列数据和指示变量值  
            exec sql get descriptor 'output_descriptor' VALUE :occurs  
                :output_buffer = DATA , :output_indicator = INDICATOR;  

            //输出列数据   
            if(-1 == output_indicator)  
               printf("\t%s", "   ");         
            else  
               printf("\t%s" , output_buffer);        
        }  

        printf("\n"); 
    }  

}


void connet()
{
    int ret = 0;
    //连接数据库
    EXEC SQL CONNECT:usrname IDENTIFIED BY:passwd USING:serverid ;
    if (sqlca.sqlcode != 0)
    {
        ret = sqlca.sqlcode;
        printf("sqlca.sqlcode: err:%d \n", sqlca.sqlcode);
        return ;
    }
    else
    {
        printf("connect ok...\n");
    }
}



ORACLE模式
关于SQLDA
为了保存执行动态SQL语句所需要的信息,系统提供一个称之为SQL描述区的程序数据结构。把ORACLE所需要的全部有关选择表项或虚拟输入宿主变量的信息,除了值和名字外,都存储在SQLDA中

SQLDA中所包含的信息主要通过以下三种方法写入:

sqlald()函数:在分配描述区和缓冲区的同时,还把SLI或P的名字在缓冲区中的地址和长度写入SQLDA中
应用程序:通过程序把SLI或BV值的缓冲区地址、长度既数据类型写入SQLDA
DESCRIBE语句:检查每一个选择表项,确定它的名字、数据类型、约束、长度、定标和精度,然后把这方面的信息存储在选择SQLDA和输出缓冲区中,以供用户使用


说明的方式有如下三种:

直接把商标中所示的代码编写到程序中
用INCLUDE语句:EXEC SQL INCLUDE sqlda;
使用指针:EXEC SQL INCLUDE sqlda; sqlda *bind_dp;sqlda *select_dp;bind_dp=sqlald(…);select_dp=sqlald(…);
sqlald(max_vars,max_name,max_ind_name)为SQLDA分配控件,并分配相应的缓冲区,并把缓冲区的地址等填入SQLDA中


/* 包含C头文件 */  
#include <stdio.h>  
#include <string.h>  
#include <stdlib.h>  
#include <setjmp.h>  
#include <sqlcpr.h>  

/* 包含SQLDA和SQLCA结构 */  
#include <sqlda.h>  
#include <sqlca.h>  

/* 定义绑定变量和选择列表项的最大个数 */  
#define MAX_ITEMS       40  

/* 定义绑定变量和选择列表项名称的最大长度 */  
#define MAX_VNAME_LEN   30  

/* 定义指示变量名称的最大长度 */  
#define MAX_INAME_LEN   30  

void connect();  
void sql_error();  
void alloc_descriptors(int , int , int);  
void dealloc_descriptors();  
void set_bind_variables();  
void process_select_list();  

/* 定义绑定描述区和选择描述区 */  
SQLDA* bind_dp;  
SQLDA* select_dp;  

/* 定义输入宿主变量:存放动态SQL语句 */  
char sql_stat[100];   
char current_date[20];     

int main()  
{  
    /* 安装错误处理句柄 */  
    exec sql whenever sqlerror do sql_error();  

    /* 连接到数据库 */  
    connect2();  

    /* 分配绑定描述区和选择描述区 */  
    alloc_descriptors(MAX_ITEMS , MAX_VNAME_LEN , MAX_INAME_LEN);  

    for( ; ; )  
    {  
        printf("请输入动态SQL语句(exit:退出):");  
        gets(sql_stat);  

        /* EXIT(exit):退出 */  
        if(0 == strncmp(sql_stat , "exit" , 4) || 0 == strncmp(sql_stat , "EXIT" , 4))  
            break;  

        /* 准备动态SQL语句 */  
        exec sql prepare s from :sql_stat;  

        /* 定义游标 */  
        exec sql declare c cursor for s;  

        /* 出错,继续下一循环 */  
        if(0 != sqlca.sqlcode)  
            continue;  

        /* 设置绑定变量 */  
        set_bind_variables();  

        /* 
         * 打开游标 
         * select语句:生成结果集 
         * 其他SQL语句:执行语句 
         */  
        exec sql open c using descriptor bind_dp;  

        /* 
         * select语句 
         */  
        if(0 == strncmp(sql_stat , "select" , 6) || 0 == strncmp(sql_stat , "SELECT" , 6)) 
        {  
            process_select_list();   
        }  
        /* 关闭游标 */  
        exec sql close c;  
    }  

    /* 释放选择描述区和选择描述区 */  
    dealloc_descriptors();  

    /* 提交事务,断开连接 */  
    exec sql commit work release;  
    puts("谢谢使用Oracle动态SQL方法四!\n");  

    return 0;  
}  


void connect2()  
{  
    /* 定义宿主变量 */  
    char username[20] , password[20] , server[20];  

    /* 输入用户名、口令和网络服务名 */  
    printf("输入用户名:");  
    gets(username);  

    printf("输入口令:");  
    gets(password);  

    printf("输入网络服务名:");  
    gets(server);  

    /* 连接到数据库 */  
    EXEC SQL CONNECT :username identified by :password using :server;  
}  

void sql_error()  
{  
    /* 显示SQL错误信息 */  
    printf("%.*s\n" , sqlca.sqlerrm.sqlerrml , sqlca.sqlerrm.sqlerrmc);  
}  

void alloc_descriptors(int size , int max_vname_len , int max_iname_len)  
{  

    int i;  

    /* 分配绑定描述区和选择描述区 */  
    bind_dp = SQLSQLDAAlloc(0 , size , MAX_VNAME_LEN , MAX_INAME_LEN);  
    select_dp = SQLSQLDAAlloc(0 , size , MAX_VNAME_LEN , MAX_INAME_LEN);  


    /* 为指示变量、绑定变量和选择列表项分配内存 */  
    for(i = 0 ; i != MAX_ITEMS ; ++i)  
    {  
        bind_dp->I[i] = (short*)malloc(sizeof(short));  
        select_dp->I[i] = (short*)malloc(sizeof(short));  

        bind_dp->V[i] = (char*)malloc(1);  
        select_dp->V[i] = (char*)malloc(1);  
    }  
}  

void dealloc_descriptors()  
{  
    int i;  

    /* 释放指示变量、绑定变量和选择列表项占用的内存 */  
    for(i = 0 ; i != MAX_ITEMS ; ++i)  
    {  
        if(bind_dp->V[i] != (char*)0)  
            free(bind_dp->V[i]);  
        free(bind_dp->I[i]);  

        if(select_dp->V[i] != (char*)0)  
            free(select_dp->V[i]);  
        free(select_dp->I[i]);  
    }  

    /* 释放绑定描述区和选择描述区 */  
    SQLSQLDAFree(0 , bind_dp);  
    SQLSQLDAFree(0 , select_dp);  
}  

void set_bind_variables()  
{  
    int i;  
    char bind_var[64];  

    /* 设置绑定变量最大个数 */  
    bind_dp->N = MAX_ITEMS;  

    /* 绑定变量名称: 绑定描述区 */  
    exec sql describe bind variables for s into bind_dp;  

    /* 设置绑定变量实际个数 */  
    bind_dp->N = bind_dp->F;  

    /* 循环处理绑定变量 */  
    for(i = 0 ; i != bind_dp->F ; ++i)  
    {  
        /* 显示绑定变量名 */  
        printf("请输入绑定变量%.*s的值:" , (int)bind_dp->C[i] , bind_dp->S[i]);  

        /* 输入绑定变量的值 */  
        gets(bind_var);  

        /* 设置绑定变量的长度成员 */  
        bind_dp->L[i] = strlen(bind_var);  

        /* 为绑定变量数据缓冲区重新分配内存(多一位,留给'\0') */  
        bind_dp->V[i] = (char*)realloc(bind_dp->V[i] , bind_dp->L[i] + 1);  

        /* 绑定变量数据: 数据缓冲区 */  
        strcpy(bind_dp->V[i] , bind_var);  

        /* 设置指示变量,处理NULL */  
        if(0 == strncmp(bind_var , "NULL" , 4) || 0 == strncmp(bind_var , "null" , 4))  
            *bind_dp->I[i] = -1;  
        else  
            *bind_dp->I[i] = 0;  

        /* 设置数据缓冲区数据类型代码->char */  
        bind_dp->T[i] = 1;  
    }  
}  

void process_select_list()  
{  
    int i , null_ok , precision , scale;  
    char title[MAX_VNAME_LEN];  

    /* 设置选择列表项的最大个数 */  
    select_dp->N = MAX_ITEMS;  

    /* 选择列表项: 选择描述区 */  
    exec sql describe select list for s into select_dp;  

    /* 设置选择列表项的实际个数 */  
    select_dp->N = select_dp->F;  

    /* 循环处理选择列表项 */  
    for(i = 0 ; i != select_dp->F ; ++i)  
    {  
        /* 清除select_dp->T[i]的高位->null */  
        SQLColumnNullCheck(0 , (unsigned short*)&select_dp->T[i]  
            , (unsigned short*)&select_dp->T[i] , &null_ok);  

        /* 根据内部数据类型确定外部类型数据长度(显示长度) */  
        switch(select_dp->T[i])  
        {  
        case 2:  
            /* number类型,取得精度与标度 */  
            //SQLNumberPrecV6(0 , (unsigned short*)&select_dp->T[i] , &precision , &scale); 
            SQLNumberPrecV6(0 , (unsigned long *)&select_dp->L[i] , &precision , &scale);  //wangbaoming modify 201409

            if(scale > 0)  
                /* 实数: 显示长度:float  */  
                select_dp->L[i] = sizeof(float);  
            else  
                /* 整数: 显示长度 int */  
                select_dp->L[i] = sizeof(int);  
            break;  
        case 12:  
            /* DATA数据类型(DD-MON-YY) */  
            select_dp->L[i] = 9;  
            break;  
        }  

        /* 根据变量长度,重新为选择列表项数据缓冲区分配内存 */  
        if(2 != select_dp->T[i])  
            /* 其他类型 */  
            select_dp->V[i] = (char*)realloc(select_dp->V[i] , select_dp->L[i] + 1);  
        else  
            /* number类型 */  
            select_dp->V[i] = (char*)realloc(select_dp->V[i] , select_dp->L[i]);  

        /* 初始化title */  
        memset(title , ' ' , MAX_VNAME_LEN);  

        /* 选择列表项名称: title */  
        strncpy(title , select_dp->S[i] , select_dp->C[i]);  

        /* 显示列名 */  
        if(2 == select_dp->T[i])  
            if(scale > 0)  
                printf("\t%.*s" , select_dp->L[i] + 3, title);  
            else  
                printf("\t%.*s" , select_dp->L[i] , title);  
        else  
            printf("\t%-.*s" , select_dp->L[i] , title);  

        /* 根据Oracle内部类型确定外部数据类型(显示类型) */  
        if( 2 == select_dp->T[i])  
        {  
            /* number 类型*/  
            if(scale > 0)  
                /* float */  
                select_dp->T[i] = 4;  
            else  
                /* int */  
                select_dp->T[i] = 3;  
        }  
        else  
            /* char */  
            select_dp->T[i] = 1;  
    }  

    printf("\n");  

    /* 提取数据完毕->结束循环 */  
    exec sql whenever not found do break;  

    /* 循环处理选择列表数据 */  
    for( ; ; )  
    {  
        /* 数据->选择描述区 */  
        exec sql fetch c using descriptor select_dp;  

        /* 显示数据 */  
        for( i = 0 ; i != select_dp->F ; ++i)  
        {  
            if(*select_dp->I[i] < 0){  
                /* 处理NULL */  
                printf("\tNULL");  
            }else{  
                if(3 == select_dp->T[i]){  
                    /* int */  
                    printf("\t%d" , *(int*)select_dp->V[i]);  
                }else if(4 == select_dp->T[i]){  
                    /* float */  
                    printf("\t%8.2f" , *(float*)select_dp->V[i]);  
                }else{  
                    /* char */  
                    printf("\t%.*s" , select_dp->L[i] , select_dp->V[i]);  
                }  
                }  
        }  
        printf("\n");  
    }  
}  


————————————————
转至
原文链接:https://blog.csdn.net/lzjsqn/article/details/54352861

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值