VC中使用ADO访问数据库技术程序实现方法

VC中使用ADO访问数据库技术程序实现方法

440人阅读 评论(1) 收藏 举报
  
一,数据库访问技术
目前数据库接口问技术主要有DAO,RDO,ODBC,OLE DB和ADO
ODBC,开发数据库互连。为数据库开发提供统一的接口,可以与任何具有ODBC驱动程序的数据库通信。和其他数据库访问技术相比,属于比较底层的数据库接口。只能和关心数据库进行通信,不能访问非关系数据库。
DAO,数据访问对象。基于COM的面向对象数据库编程模型。是一组基于Mircrosoft Access Jet引擎的COM自动化接口。直接与Access/Jet数据库通信,通过Jet引擎也可以与其他数据库通信。效率不高。最初是作为ODBC API的抽象,为Visual Basic程序员提供的编程对象。使用ODBC API对数据源进行操作。不需要经过Jet引擎,比DAO效率高。
OLE DB,对象链接嵌入数据库。未来数据库访问的发展模式。提供COM接口,与其他数据库访问技术相比,具有更好的健壮性和灵活性,更高的容错能力。属于底层访问技术。可以与关系和非关系型数据库进行通信。
ADO,ActiveX Data Object。对OLE DB的高层次封装。简化了OLE DB,属于高层的数据库接口。另外同OLE DB相比,能够使用ADO的编程语言更多。ADO提供一个自动化接口,使VBScript和JavaScript等脚本语言可以使用ADO。
二,ADO访问基础
1.引入ADO类型库

    在Stdafx.h中使用如下指令引入类型库:
    #import "c:/program files/common files/system/ado/msado15.dll" no_namespace rename("EOF","adoEOF")
    根据操作系统的不同,msado*.dll的版本不同,目前最高版本是2.1吧。rename("EOF","adoEOF")是为了防止和别的结束标示重名。
    在编译过程中不用理会下面的编译警告:
    warning: unary minus operator applied to unsigned type, result still unsigned
    如果不想此警告出现,可以在 StdAfx.h 文件中加入这样一行代码以禁止此警告:     #pragma warning(disable:4146)
对于指定ADO版本
//如果使用 ADO 2.0 加入下面代码
#import "C:/Program Files/Common Files/System/Ole DB/msdasc.dll" no_namespace
//如果使用 ADO 2.1 加入下面代码
#import "C:/Program Files/Common Files/System/Ole DB/oledb32.dll" no_namespace
注:ADO2.0 或 ADO2.1 中,如果其中一个编译不成功,则用另一个
2.初始化COM库
    一般中应用程序类初始化函数中初始化COM库,当然也可以在别的地方。只要是在使用之前初始化就可以。
AfxOleInit();    // 初始化COM环境
上面是MFC环境下注册COM的方法,如果非MFC环境使用方法如下:
 CoInitialize(NULL);
 CoUnInitialize();
3.创建Connection对象并连接数据库
 针对链接数据库的方法主要有两种,静态链接和动态链接。
静态链接:
_ConnectionPtr m_pConnection;
    在初始化函数中初始化它。
    try
   {
          hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
          if(SUCCEEDED(hr))
          {
              hr = m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库
      ///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为            :Provider=Microsoft.Jet.OLEDB.3.51;
          }
  }
  catch(_com_error e)///捕捉异常
  {
      CString errormessage;
      errormessage.Format("连接数据库失败!/r/n错误信息:%s",e.ErrorMessage());
      AfxMessageBox(errormessage);///显示错误信息
  }
 
  //假设数据库名称test.mdb;数据源为test,下面使用不同的方法连接数据库
  使用ADO连接ACCESS2000数据库
  m_pConnection->Open("Provider=Mircrosoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);
  使用ADO连接ACCESS97数据库
  m_pConnection->Open("Provider=Mircrosoft.Jet.OLEDB.3.51;Data Source=test.mdb","","",adModeUnKnown);
  使用DSN数据源对任何支持ODBC的数据库进行连接
  m_pConnection->Open("Data Source=test;UID=sa;PWD=","","",adModeUnknown);
  不通过数据源对SQL SERVER数据库进行连接
  m_pConnection->Open("driver={SQL SERVER};Server=127.0.0.1;DATABASE=TEST;UID=sa;PWD=","","",adModeUnknown);
 
  设置数据库连接超时,需要在Open之前调用
  m_pConnection->ConnectionTimeOut = 30;//秒
动态链接:
HRESULT hr;
IDataSourceLocatorPtr dlPrompt=NULL;
_RecordsetPtr rs=NULL;
try
{
  // 初始化DataLinks对象
  hr=dlPrompt.CreateInstance(__uuidof(DataLinks));
  if(FAILED(hr))
 throw(_com_error(hr,NULL));

  // 建立连接
  pConn=dlPrompt->PromptNew();

  // 如果 conn 为 NULL
  if(pConn==NULL)
 return;

  // 打开连接
  pConn->Open(pConn->ConnectionString,L"",L"",-1);

  // 清除列表框
  while(m_tblList.GetCount()>0)
 m_tblList.DeleteString(0);

  // 获取数据库中表集
  rs=pConn->OpenSchema(adSchemaTables);
  while(!rs->adoEOF)
  {
 m_tblList.AddString((char*)(_bstr_t)rs->Fields->Item[L"TABLE_NAME"]->Value);
 rs->MoveNext();
  }
  rs=NULL;
  dlPrompt.Release();
}
catch (_com_error &e)
{
 AfxMessageBox(e.ErrorMessage());
}
需要注意的是,在建立工程时,要选上Automation选项。(具体情况看)

三,ADO的数据操作
1.Recordset对象操作数据

 Recordset对象是操作数据最简单的方法,也比较直接,特别可以直接用SQL语句来进行操作。我本人也最常用Recordset对象。
 _RecordsetPtr m_pRecordset;
m_pRecordset.CreateInstance("ADODB.Recordset");
或者直接写成:
_RecordsetPtr  pRecordset ("ADODB.Recordset");
也可以这样写:
_RecordsetPtr  pRecordset;
pRecordset.CreateInstance(__uuidof(_Recordset));
注意,如果变量是一个类的实例则用"."操作符,若是一个指向实例的指针则应使用"->"操作符。
一个变量能通过两种方式被使用。因为"->"操作符被重载,允许一个对象实例类似一个接口指针那样被使用;"->"操作符返回该指针;而由这个返回的指针访问_Recordset对象的成员。
    如下代码,获得表中所有记录到Recordset对象
    CString strSQL = "select * from student";
    m_pRecordset->Open(strSQL,_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
    第一个参数是要执行的SQL字符串,第二个是活动连接
    第三个参数是光标类型。可以取如下值
    adOpenUnspecified = -1,///不作特别指定
    adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark, RecordCount,AbsolutePosition,AbsolutePage都不能使用
    adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
    adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
    adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你记录集来说是不可见的。
    第四个参数,锁定类型。
    adLockUnspecified = -1,///未指定
    adLockReadOnly = 1,///只读记录集
    adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制
    adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
    adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
    第五个参数。取如下值之一:
    adCmdText:表明CommandText是文本命令
    adCmdTable:表明CommandText是一个表名
    adCmdProc:表明CommandText是一个存储过程
    adCmdUnknown:未知
   
    取得某条记录上某字段的值
    _variant_t vValue = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
    vValue = m_pRecordset->GetCollect("FieldName");///取得username字段的值
    删除满足条件的记录。移动光标到要删除的记录。如:
    m_pRecordset->MoveFirst();///移到首条记录
    m_pRecordset->Delete(adAffectCurrent);///删除当前记录
    添加记录
    m_pRecordset->AddNew();
    m_pRecordset->PutCollect("ID", _variant_t("value"));
    m_pRecordset->Updata();
    修改记录
    m_pRecordset->PutCollect("ID", _variant_t("value"));
    m_pRecordset->Updata();
   
2.属性
对于类中的每个操作(或称方法、属性调用),都有一个声明以保证能直接调用它(或称作操作的源形式),以及另一个声明来调用这个源操作并在操作失败时抛出一个COM错误。如果操作是一个属性,那么编译指示符可以为该操作创建一个可交互的类似VB的语法形式。
返回/设置属性的操作有对应的形式化的名字—GetProperty/PutPropert,而设置一个指向某个ADO对象的指针型属性值时则是PutRefProperty。你将使用如下的形式读写属性的值:
variable = objectPtr->GetProperty(); // 读取属性的值
objectPtr->PutProperty(value);       // 设置属性的值
objectPtr->PutRefProperty(&value);   // 设置一个指针型的属性的值
直接使用属性
__declspec(property...)编译指示符是微软定义的一个针对C语言的扩展,使一个函数象一个属性那样被使用。这样你就可以采用如下的语法形式象在使用VB一样读写一个属性的值: objectPtr->property = value;        // 设置属性的值
variable = objectPtr->property;     // 读取属性的值
__declspec(property...)编译指示符只能针对属性的读写函数使用,并根据属性是否可供读写自动生成对应的调用形式。每个属性可能有 GetProperty, PutProperty,PutRefProperty三个函数,但这个编译符只能生成其中的两种交互形式。比如,Command对象的 ActiveConnection属性有GetActiveConnection和PutRefActiveConnection这两个读写函数。而 PutRef-的形式在实践中是个好的选择,你可以将一个活动的Connection对象的指针保存在这个属性中。另一方面,Recordset对象则有 Get-, Put-, and PutRefActiveConnection操作,但却没有可交互的语法形式。

Collections,GetItem方法和Item属性
ADO定义了几种集合Collection,包括Fields,Parameters,Properties,和Errors。在Visual C++中,GetItem(index)方法返回Collection中的某个成员。Index是一个Variant型的参数,内容可以是一个该成员对应的序数,也可以是一个包括其名称的字符串。
__declspec(property...)编译指示符为Item属性生成对应于GetItem()方法的直接使用形式(上文提到的可交互的语法形式)。这种形式类似于引用数组元素时使用[]的语法形式:
collectionPtr->GetItem(index);
collectionPtr->Item[index];
举例说明,要给一个Recordset对象rs中的某个字段赋值,而这个Recordset对象派生于pubs数据库中的authors表。使用Item ()属性访问这个Recordset的Fields集合中的第三个字段(集合总是从0开始编号,假设第三个字段名为au_fname)。然后调用 Value()方法为该字段赋一个字符串值。
rs->Fields->GetItem(2)->PutValue("value");
rs->Fields->GetItem("au_fname")->PutValue("value");
或者:
rs->Fields->Item[2]->Value = "value";
rs->Fields->Item["au_fname"]->Value = "value";

3. COM特定的数据类型
 专属于COM使用的数据类型则有:Variant, BSTR, and SafeArray。
Variant
Variant是一个结构化的数据类型,包含了一个成员值及其数据类型的表示。Variant可以表示相当多的数据类型,甚至另一个Variant, BSTR, Boolean, Idispatch或Iunknown指针,货币,日期等等。同时COM也提供了许多方法使数据类型间的转换更简单化。
_variant_t类封装并管理Variant这一数据类型。
当ADO的一个方法或属性要使用一个参数时,通常意味着需要一个_variant_t类型的参数。作为例外的是,有时则会要求操作数是一个标准的数据类型,比如Long或Byte, 或者一个枚举值。另一个例外是要求操作数是一个字符串String。

BSTR
BSTR (Basic STRing)也是一个结构化的数据类型,包括了串及串的长度。COM提供了方法进行串的空间分配、操作、释放。
_bstr_t类封装并管理BSTR这一数据类型。
一个方法或属性要使用一个字符串参数时,通常意味着需要一个类_bstr_t型的参数。

_variant_t和_bstr_t类的强制类型转换
通常当传递一个_variant_t或_bstr_t参数给一个操作时并不需要显式的类型转换代码。如果_variant_t或_bstr_t类提供了对应于该参数类型的构造函数,那么编译器将会自动生成适当的_variant_t或_bstr_t值。

然而,当参数模棱两可时,即对应了多个构造函数时,你就必须显式地调用正确的构造函数以获得正确的参数。比如,Recordset::Open方法的函数声明如下:
    HRESULT Open (
        const _variant_t & Source,
        const _variant_t & ActiveConnection,
        enum CursorTypeEnum CursorType,
        enum LockTypeEnum LockType,
        long Options );
其中参数ActiveConnection就是针对一个variant_t型变量的引用,它可以是一个连接串或者一个指向已打开的Connection对象的指针。
正确的_variant_t型参数会被构造,无论你传递的是一个类似"DSN=pubs;uid=sa;pwd=;"这样的字符串,或者是一个类似" (IDispatch *) pConn"的指针。或者你还可以显式的编写"_variant_t((IDispatch *) pConn, true)"这样的代码来传递一个包含指针的_variant_t变量。这里的强制类型转换(IDispatch *)避免了可能调用IUnknown接口构造函数的模棱两可性。虽然很少提及但特别重要的是,ADO总是一个IDispatch接口。任何被传递的被包含在Variant中的指针都必须被转换为一个IDispatch接口指针。
最后需要说明的是构造函数的第二个逻辑参数是可选择的,它的缺省值是True。这个参数将决定Variant的构造函数是否调用内嵌的AddRef()方法,并在完成ADO的方法或属性调用后是否自动调用_variant_t::Release()方法

SafeArray
SafeArray也是一种结构化的数据类型,包含了一个由其它数据类型的数据元素组成的数组。之所以称之为安全的数组是因为它包含了每一维的边界信息,并限制在边界内进行数组元素的访问。
一个方法或属性要使用或者返回一个数组时,通常意味着是一个SafeArray数组,而非一个本地化的C/C++数组。
比如,Connection对象的OpenSchema方法的第二个参数需要一个由Variant值组成的数组。这些Variant值必须作为一个SafeArray数组的元素进行传递。而这个SafeArray数组本身又被作为一个Variant进行传递。
更进一步的,Find方法的第一个参数是一个指向一维SafeArray数组的Variant;AddNew方法的可选的第一与第二个参数也是一个一维的SafeArray数组;GetRows方法的返回值则是一个包含二维SafeArray数组的Variant。

以下的代码演示了如何通过一个_variant_t使用一个SafeArray数组。注意注释对应了编码的步骤。
1.再一次的,TESTHR()内置函数被定义以利用预存的错误处理机制。
2.如果你只需要一个一维数组,你可以使用SafeArrayCreateVector,而非SAFEARRAYBOUND声明与SafeArrayCreate函数。下面的代码使用了SafeArrayCreate:
   SAFEARRAYBOUND   sabound[1];
   sabound[0].lLbound = 0;
   sabound[0].cElements = 4;
   pSa = SafeArrayCreate(VT_VARIANT, 1, sabound);
3.枚举常量adSchemaColumns定义的模式,决定了与TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME和COLUMN_NAME四列相联系。为此,一个有四个Variant元素的数组被创建。而对应于第三列TABLE_NAME的值被设置。
由若干列组成的返回的Recordset只是对应的所有列的一个子集,并且每一行的值保持了一一对应。
4.熟悉SafeArrays的人也许会对退出前没有调用SafeArrayDestroy()感到惊奇。实际上,在这种情况下调用 SafeArrayDestroy()会导致一个运行时的异常发生。这是因为vtCriteria的析构函数会在_variant_t超出使用范围时调用 VariantClear(),从而释放SafeArray。只调用SafeArrayDestroy,而没有手动清除_variant_t,将会导致析构函数试图去清除一个无效的SafeArray指针。如果要调用SafeArrayDestroy(),那么代码应该象这样:
   TESTHR(SafeArrayDestroy(pSa));
   vtCriteria.vt = VT_EMPTY;
   vtCriteria.parray = NULL;
实际更像是让_variant_t管理SafeArray。

完整的代码如下:
#import "c:/Program Files/Common Files/System/ADO/msado15.dll" /
   no_namespace rename("EOF", "EndOfFile")
#include

// Note 1
inline void TESTHR( HRESULT _hr )
   { if FAILED(_hr) _com_issue_error(_hr); }

void main(void)
{
   CoInitialize(NULL);
   try
   {
   _RecordsetPtr   pRs("ADODB.Recordset");
   _ConnectionPtr  pCn("ADODB.Connection");
   _variant_t      vtTableName("authors"),
                   vtCriteria;
   long            ix[1];
   SAFEARRAY       *pSa = NULL;

   pCn->Open("DSN=pubs;User ID=sa;pwd=;Provider=MSDASQL;", "", "",
               adConnectUnspecified);
// Note 2, Note 3
   pSa = SafeArrayCreateVector(VT_VARIANT, 1, 4);
   if (!pSa) _com_issue_error(E_OUTOFMEMORY);

// 为第三个元素赋值TABLE_NAME(索引值2).
   ix[0] = 2;     
   TESTHR(SafeArrayPutElement(pSa, ix, &vtTableName));

// 由于Variant没有SafeArray的构造函数,所以手工设置Variant的数据类型和值。
   vtCriteria.vt = VT_ARRAY | VT_VARIANT;
   vtCriteria.parray = pSa;

   pRs = pCn->OpenSchema(adSchemaColumns, vtCriteria, vtMissing);

   long limit = pRs->GetFields()->Count;
   for (long x = 0; x < limit; x++)
      printf("%d: %s/n", x+1,
         ((char*) pRs->GetFields()->Item[x]->Name));
// Note 4
   pRs->Close();
   pCn->Close();
   }
   catch (_com_error &e)
   {
   printf("Error:/n");
   printf("Code = %08lx/n", e.Error());
   printf("Code meaning = %s/n", (char*) e.ErrorMessage());
   printf("Source = %s/n", (char*) e.Source());
   printf("Description = %s/n", (char*) e.Description());
   }
   CoUninitialize();
}

利用(IDispatch *)转换ADO对象的指针类型
1.在一个Variant中显式地封装一个活动的Connection对象,然后用(IDispatch *)进行类型转换确保正确的构造函数被调用。同时明确地设置第二个参数为缺省的true,使该对象的引用计数在Recordset::Open操作完成后仍得到正确的维护。
2.表达式(_bstr_t)不是一个类型转换,而是一个_variant_t的操作符,用以从中提取一个_bstr_t字符串。
表达式(char*)也不是一个类型转换,而是一个_bstr_t的操作符,用以从中提取封装在_bstr_t中的字符串的指针。
下面这些代码演示了_variant_t和_bstr_t的一些常见操作。
#import "c:/Program Files/Common Files/System/ADO/msado15.dll" /
no_namespace rename("EOF", "EndOfFile")

#include

void main(void)
{
   CoInitialize(NULL);
   try
   {
      _ConnectionPtr pConn("ADODB.Connection");
      _RecordsetPtr  pRst("ADODB.Recordset");

      pConn->Open("Provider=sqloledb;Data Source=a-tima10;"
         "Initial Catalog=pubs;User Id=sa;Password=;",
         "", "", adConnectUnspecified);
// Note 1
      pRst->Open(
         "authors",
         _variant_t((IDispatch *) pConn, true),
         adOpenStatic,
         adLockReadOnly,
         adCmdTable);
      pRst->MoveLast();
// Note 2
      printf("Last name is '%s %s'/n",
            (char*) ((_bstr_t) pRst->GetFields()->GetItem("au_fname")->GetValue()),
            (char*) ((_bstr_t) pRst->Fields->Item["au_lname"]->Value));

      pRst->Close();
      pConn->Close();
   }
   catch (_com_error &e)
   {
      printf("Description = '%s'/n", (char*) e.Description());
   }  
::CoUninitialize();
}


vc++数据库开发资源包 都是过时的技术。 我自己学习用的。 ------------------------- ADO270.CHI ADO270.CHM ADOSQL.CHI ADOSQL.CHM COM.CHM DBLIBC.CHI DBLIBC.CHM DBLIBC.chw ODBCSQL.CHM ODECORE.CHI OLEDB.CHI OLEDB.CHM OLEDBSQL.CHI OLEDBSQL.CHM ─INCLUDE DB2CONST.H DBMCONST.H DTMCONST.H DTSCONST.H DTSFFILE.H DTSPKG.H DTSPUMP.H EQMCONST.H GENCONST.H IFXCONST.H MDSCONST.H MSDADC.H MSDAGUID.H MSDASC.H MSDASQL.H OCLCONST.H ODBCINST.H ODBCOR_G.BAS ODBCSS.H ODBEXT_G.BAS OLEDB.H OLEDBERR.H OLPCONST.H REPAPI.H REPAUTO.H REPENG.BAS REPERR.H REPLDIST.H REPLDSTX.C REPLERRI.C REPLERRX.H REPLINIX.C REPLMRGX.C REPTIM.H REPTIM2.H REPTIM3.H SIMCONST.H SQL.H SQLCA.H SQLCONST.H SQLDA.H SQLDB.H SQLDISTX.H SQLDMO.H SQLDMOID.H SQLEXT.H SQLFRONT.H SQLINITX.H SQLMERGX.H SQLNSDEF.H SQLNSX.H SQLOLEDB.H SQLRES.H SQLRES.IDL SQLRESID.H SQLTYPES.H SQLUCODE.H SRV.H SRVAPI.H SRVCONST.H SRVDBTYP.H SRVMISC.H SRVSTRUC.H SRVTOK.H SRVTYPES.H TFMCONST.H TRANSACT.H TXCOORD.H TXDTC.H UMLCONST.H UMXCONST.H VDI.H VDIERROR.H VDIGUID.H WN95SCM.H XACTOMSG.H XOLEHLP.H ─SAMPLES ├─ADO │ UNZIP_AD │ ├─BACKUP │ UNZIP_BA │ ├─DBLIB │ UNZIP_DB │ ├─DESKTOP │ SAMPLE.M │ SAMPLEUP │ UNZIP_DE │ ├─DTS │ UNZIP_DT │ ├─ESQLC │ UNZIP_ES │ ├─MISC │ UNZIP_MI │ ├─MSDTC │ UNZIP_MS │ ├─ODBC │ UNZIP_OD │ ├─ODS │ UNZIP_OD │ ├─OLEAUTO │ UNZIP_OL │ ├─SQLDMO │ UNZIP_SQ │ ├─SQLNS │ UNZIP_SQ │ ├─SQLREPL │ UNZIP_SQ │ ├─UTILS │ UNZIP_UT │ └─XML UNZIP_XM ─SDK MDACSDKLITE. XMLSDK.MSI ─X86LIB ADME.LIB CAW32.LIB DACDLL.LIB DTCCFG.LIB DTCCM.LIB DTCHELP.LIB DTCTRACE.LIB DTCUIC.LIB DTCUIS.LIB DTCUTIL.LIB LOGMGR.LIB MSDASC.LIB MSDTC.LIB MSDTCPRX.LIB MSDTCTM.LIB NTWDBLIB.LIB ODBC32.LIB ODBCBCP.LIB ODBCCP32.LIB OLEDB.LIB OLEDBD.LIB OPENDS60.LIB SAMPDTCR.LIB SQLAKW32.LIB SVCSRVL.LIB UTIL.LIB W95SCM.LIB XOLEHLP.LIB
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值