COM组件设计与应用(三)____数据类型[转]
2008-06-12 14:33
225 查看
一、前言
上回书介绍了GUID、CLSID、IID和接口的概念。本回的重点是介绍COM中的数据类型。咋还不介绍组件程序的设计步骤呀?咳......别着急,别着急!孔子曰:“饭要一口一口地吃”;老子语:“心急吃不了热豆腐”,孙子云:“走一步看一步吧”......先掌握必要的知识,将来写起程序来才会得心应手也:-)
走入正题之前,请大家牢牢记住一条原则:COM组件是运行在分布式环境中的。比如,你写了一个组件程序(DLL或EXE),那么使用者可能是在本机的某个进程内加载组件(INPROC_SERVER);也可能是从另一个进程中调用组件的进程(LOCAL_SERVER);也可能是在这台计算机上调用地球那边计算机上的组件(REMOTE_SERVER)。所以在理解和设计的时候,要时时刻刻想起这句话。快!拿出小本本,记下来!
二、HRESULT函数返回值
每个人在做程序设计的时候,都有他们各自的哲学思想。拿函数返回值来说,就有好多种形式。
如此纷繁复杂的返回值,如此含义多变的返回值,使得大家在学习和使用的过程中,增加了额外的困难。好了,COM的设计规范终于对他们进行了统一。组件API及接口指针中,除了IUnknown::AddRef()和IUnknown::Release()两个函数外,其它所有的函数,都以HRESULT作为返回值。大家想象一个组件的接口函数比如叫Add(),完成2个整数的加法运算,在C语言中,我们可以如下定义:
还记得刚才我们说的原则吗?COM组件是运行在分布式环境中的。也就是说,这个函数可能运行在“地球另一边”的计算机上,既然运行在那么遥远的地方,就有可能出现服务器关机、网络掉线、运行超时、对方不在服务区......等异常。于是,这个加法函数,除了需要返回运算结果以外,还应该返回一个值------函数是否被正常执行了。
如果函数正常执行,则返回S_OK,同时真正的函数运行结果则通过参数指针返回。如果遇到了异常情况,则COM系统经过判断,会返回相应的错误值。常见的返回值有:
图一、HRESULT的结构
HRESULT其实是一个双字节的值,其最高位(bit)如果是0表示成功,1表示错误。具体参见MSDN之"StructureofCOMErrorCodes"说明。我们在程序中如果需要判断返回值,则可以使用比较运算符号;switch开关语句;也可以使用VC提供的宏:
三、UNICODE
计算机发明后,为了在计算机中表示字符,人们制定了一种编码,叫ASCII码。ASCII码由一个字节中的7位(bit)表示,范围是0x00-0x7F共128个字符。他们以为这128个数字就足够表示abcd....ABCD....1234这些字符了。
咳......说英语的人就是“笨”!后来他们突然发现,如果需要按照表格方式打印这些字符的时候,缺少了“制表符”。于是又扩展了ASCII的定义,使用一个字节的全部8位(bit)来表示字符了,这就叫扩展ASCII码。范围是0x00-0xFF共256个字符。
咳......说中文的人就是聪明!中国人利用连续2个扩展ASCII码的扩展区域(0xA0以后)来表示一个汉字,该方法的标准叫GB-2312。后来,日文、韩文、阿拉伯文、台湾繁体(BIG-5)......都使用类似的方法扩展了本地字符集的定义,现在统一称为MBCS字符集(多字节字符集)。这个方法是有缺陷的,因为各个国家地区定义的字符集有交集,因此使用GB-2312的软件,就不能在BIG-5的环境下运行(显示乱码),反之亦然。
咳......说英语的人终于变“聪明”一些了。为了把全世界人民所有的所有的文字符号都统一进行编码,于是制定了UNICODE标准字符集。UNICODE使用2个字节表示一个字符(unsignedshorint、WCHAR、_wchar_t、OLECHAR)。这下终于好啦,全世界任何一个地区的软件,可以不用修改地就能在另一个地区运行了。虽然我用IE浏览日本网站,显示出我不认识的日文文字,但至少不会是乱码了。UNICODE的范围是0x0000-0xFFFF共6万多个字符,其中光汉字就占用了4万多个。嘿嘿,中国人赚大发了:0)
在程序中使用各种字符集的方法:
在上面的例子中,T是非常有意思的一个符号(TCHAR、LPCTSTR、LPTSTR、_T()、_TEXT()...),它表示使用一种中间类型,既不明确表示使用MBCS,也不明确表示使用UNICODE。那到底使用哪种字符集那?嘿嘿......编译的时候决定吧。设置条件编译的方式是:VC6中,"Project/Settings.../C/C++卡片Preprocessordefinitions"中添加或修改_MBCS、_UNICODE;VC.NET中,"项目/属性/配置属性/常规/字符集"然后用组合窗进行选择。使用T类型,是非常好的习惯,严重推荐!
四、BSTR
COM中除了使用一些简单标准的数据类型外(注2),字符串类型需要特别重点地说明一下。还记得原则吗?COM组件是运行在分布式环境中的。通俗地说,你不能直接把一个内存指针直接作为参数传递给COM函数。你想想,系统需要把这块内存的内容传递到“地球另一边”的计算机上,因此,我至少需要知道你这块内存的尺寸吧?不然让我如何传递呀?传递多少字节呀?!而字符串又是非常常用的一种类型,因此COM设计者引入了BASIC中字符串类型的表示方式---BSTR。BSTR其实是一个指针类型,它的内存结构是:(输入程序片段BSTRp=::SysAllocString(L"Hello,你好");断点执行,然后观察p的内存)
图二、BSTR内存结构
BSTR是一个指向UNICODE字符串的指针,且BSTR向前的4个字节中,使用DWORD保存着这个字符串的字节长度(没有含字符串的结束符)。因此系统就能够正确处理并传送这个字符串到“地球另一边”了。特别需要注意的是,由于BSTR的指针就是指向UNICODE串,因此BSTR和LPOLESTR可以在一定程度上混用,但一定要注意:
有函数fun(LPCOLESTRlp),则你调用BSTRp=...;fun(p);正确
有函数fun(constBSTRbstr),则你调用LPCOLESTRp=...;fun(p);错误!!!
有关BSTR的处理函数:
五、各种字符串类型之间的转换
1、函数WideCharToMultiByte(),转换UNICODE到MBCS。使用范例:
2、函数MultiByteToWideChar(),转换MBCS到UNICODE。使用范例:
3、使用ATL提供的转换宏。
上表中的宏函数,其实非常容易记忆:
使用范例:
使用ATL转换宏,由于不用释放临时空间,所以使用起来非常方便。但是考虑到栈空间的尺寸(VC默认2M),使用时要注意几点:
1、只适合于进行短字符串的转换;
2、不要试图在一个次数比较多的循环体内进行转换;
3、不要试图对字符型文件内容进行转换,因为文件尺寸一般情况下是比较大的;
4、对情况2和3,要使用MultiByteToWideChar()和WideCharToMultiByte();
六、VARIANT
C++、BASIC、Java、Pascal、Script......计算机语言多种多样,而它们各自又都有自己的数据类型,COM产生目的,其中之一就是要跨语言(注3)。而VARIANT数据类型就具有跨语言的特性,同时它可以表示(存储)任意类型的数据。从C语言的角度来讲,VARIANT其实是一个结构,结构中用一个域(vt)表示------该变量到底表示的是什么类型数据,同时真正的数据则存贮在union空间中。结构的定义太长了(虽然长,但其实很简单)大家去看MSDN的描述吧,这里给出如何使用的简单示例:
学生:我想用VARIANT表示一个4字节长的整数,如何做?
老师:VARIANTv;v.vt=VT_I4;v.lVal=100;
学生:我想用VARIANT表示布尔值“真”,如何做?
老师:VARIANTv;v.vt=VT_BOOL;v.boolVal=VARIANT_TRUE;
学生:这么麻烦?我能不能v.boolVal=true;这样写?
老师:不可以!因为
所以如果你v.boolVal=true这样赋值,那么将来if(VARIANT_TRUE==v.boolVal)的时候会出问题(-1!=1)。但是你注意观察,任何布尔类型的“假”都是0,因此作为一个好习惯,在做布尔判断的时候,不要和“真值”相比较,而要与“假值”做比较。
学生:谢谢老师,你太牛了。我对老师的敬仰如滔滔江水,连绵不绝......
学生:我想用VARIANT保存字符串,如何做?
老师:VARIANTv;v.vt=VT_BSTR;v.bstrVal=SysAllocString(L"Hello,你好");
学生:哦......我明白了。可是这么操作真够麻烦的,有没有简单一些的方法?
老师:有呀,你可以使用现成的包装类CComVariant、COleVariant、_variant_t。比如上面三个问题就可以这样书写:CComVariantv1(100),v2(true),v3("Hello,你好");简单了吧?!(注4)
学生:老师,我再问最后一个问题,我如何用VARIANT保存一个数组?
老师:这个问题很复杂,我现在不能告诉你,我现在告诉你怕你印象不深......(注5)
学生:~!@#$%^&*()......晕!
七、小结
以上所介绍的内容,是基本功,必须熟练掌握。先到这里吧,休息一会儿......更多精彩内容,敬请关注《COM组件设计与应用(四)》
注1:在后续的ISupportErrorInfo接口中介绍。
注2:常见的数据类型,请参考IDL文件的说明。(别着急,还没写那......嘿嘿)
注3:跨语言就是各种语言中都能使用COM组件。但啥时候能跨平台呢?
注4:CComVariant/COlevariant/_variant_t请参看MSDN。
注5:关于安全数组SafeArray的使用,在后续的文章中讨论。
走入正题之前,请大家牢牢记住一条原则:COM组件是运行在分布式环境中的。比如,你写了一个组件程序(DLL或EXE),那么使用者可能是在本机的某个进程内加载组件(INPROC_SERVER);也可能是从另一个进程中调用组件的进程(LOCAL_SERVER);也可能是在这台计算机上调用地球那边计算机上的组件(REMOTE_SERVER)。所以在理解和设计的时候,要时时刻刻想起这句话。快!拿出小本本,记下来!
二、HRESULT函数返回值
每个人在做程序设计的时候,都有他们各自的哲学思想。拿函数返回值来说,就有好多种形式。
函数 | 返回值 | 返回值信息 |
doublesin(double) | 浮点数值 | 计算正玄值 |
BOOLDeleteFile(LPCTSTR) | 布尔值 | 文件删除是否成功。如失败,需要GetLastError()才能取得失败原因 |
void*malloc(size_t) | 内存指针 | 内存申请,如果失败,返回空指针NULL |
LONGRegDeleteKey(HKEY,LPCTSTR) | 整数 | 删除注册表项。0表示成功,非0失败,同时这个值就反映了失败的原因 |
UINTDragQueryFile(HDROP,UINT,LPTSTR,UINT) | 整数 | 取得拖放文件信息。以不同的参数调用,则返回不同的含义: 一会儿表示文件个数,一会儿表示文件名长度,一会儿表示字符长度 |
............ | ... | ............ |
longAdd(longn1,longn2) { returnn1+n2; }
还记得刚才我们说的原则吗?COM组件是运行在分布式环境中的。也就是说,这个函数可能运行在“地球另一边”的计算机上,既然运行在那么遥远的地方,就有可能出现服务器关机、网络掉线、运行超时、对方不在服务区......等异常。于是,这个加法函数,除了需要返回运算结果以外,还应该返回一个值------函数是否被正常执行了。
HRESULTAdd(longn1,longn2,long*pSum) { *pSum=n1+n2; returnS_OK; }
如果函数正常执行,则返回S_OK,同时真正的函数运行结果则通过参数指针返回。如果遇到了异常情况,则COM系统经过判断,会返回相应的错误值。常见的返回值有:
HRESULT | 值 | 含义 |
S_OK | 0x00000000 | 成功 |
S_FALSE | 0x00000001 | 函数成功执行完成,但返回时出现错误 |
E_INVALIDARG | 0x80070057 | 参数有错误 |
E_OUTOFMEMORY | 0x8007000E | 内存申请错误 |
E_UNEXPECTED | 0x8000FFFF | 未知的异常 |
E_NOTIMPL | 0x80004001 | 未实现功能 |
E_FAIL | 0x80004005 | 没有详细说明的错误。一般需要取得RichError错误信息(注1) |
E_POINTER | 0x80004003 | 无效的指针 |
E_HANDLE | 0x80070006 | 无效的句柄 |
E_ABORT | 0x80004004 | 终止操作 |
E_ACCESSDENIED | 0x80070005 | 访问被拒绝 |
E_NOINTERFACE | 0x80004002 | 不支持接口 |
图一、HRESULT的结构
HRESULT其实是一个双字节的值,其最高位(bit)如果是0表示成功,1表示错误。具体参见MSDN之"StructureofCOMErrorCodes"说明。我们在程序中如果需要判断返回值,则可以使用比较运算符号;switch开关语句;也可以使用VC提供的宏:
HRESULThr=调用组件函数; if(SUCCEEDED(hr)){...}//如果成功 ...... if(FAILED(hr)){...}//如果失败 ......
三、UNICODE
计算机发明后,为了在计算机中表示字符,人们制定了一种编码,叫ASCII码。ASCII码由一个字节中的7位(bit)表示,范围是0x00-0x7F共128个字符。他们以为这128个数字就足够表示abcd....ABCD....1234这些字符了。
咳......说英语的人就是“笨”!后来他们突然发现,如果需要按照表格方式打印这些字符的时候,缺少了“制表符”。于是又扩展了ASCII的定义,使用一个字节的全部8位(bit)来表示字符了,这就叫扩展ASCII码。范围是0x00-0xFF共256个字符。
咳......说中文的人就是聪明!中国人利用连续2个扩展ASCII码的扩展区域(0xA0以后)来表示一个汉字,该方法的标准叫GB-2312。后来,日文、韩文、阿拉伯文、台湾繁体(BIG-5)......都使用类似的方法扩展了本地字符集的定义,现在统一称为MBCS字符集(多字节字符集)。这个方法是有缺陷的,因为各个国家地区定义的字符集有交集,因此使用GB-2312的软件,就不能在BIG-5的环境下运行(显示乱码),反之亦然。
咳......说英语的人终于变“聪明”一些了。为了把全世界人民所有的所有的文字符号都统一进行编码,于是制定了UNICODE标准字符集。UNICODE使用2个字节表示一个字符(unsignedshorint、WCHAR、_wchar_t、OLECHAR)。这下终于好啦,全世界任何一个地区的软件,可以不用修改地就能在另一个地区运行了。虽然我用IE浏览日本网站,显示出我不认识的日文文字,但至少不会是乱码了。UNICODE的范围是0x0000-0xFFFF共6万多个字符,其中光汉字就占用了4万多个。嘿嘿,中国人赚大发了:0)
在程序中使用各种字符集的方法:
constchar*p="Hello";//使用ASCII字符集 constchar*p="你好";//使用MBCS字符集,由于MBCS完全兼容ASCII,多数情况下,我们并不严格区分他们 LPCSTRp="Hello,你好";//意义同上 constWCHAR*p=L"Hello,你好";//使用UNICODE字符集 LPCOLESTRp=L"Hello,你好";//意义同上 //如果预定义了_UNICODE,则表示使用UNICODE字符集;如果定义了_MBCS,则表示使用MBCS constTCHAR*p=_T("Hello,你好"); LPCTSTRp=_T("Hello,你好");//意义同上
在上面的例子中,T是非常有意思的一个符号(TCHAR、LPCTSTR、LPTSTR、_T()、_TEXT()...),它表示使用一种中间类型,既不明确表示使用MBCS,也不明确表示使用UNICODE。那到底使用哪种字符集那?嘿嘿......编译的时候决定吧。设置条件编译的方式是:VC6中,"Project/Settings.../C/C++卡片Preprocessordefinitions"中添加或修改_MBCS、_UNICODE;VC.NET中,"项目/属性/配置属性/常规/字符集"然后用组合窗进行选择。使用T类型,是非常好的习惯,严重推荐!
四、BSTR
COM中除了使用一些简单标准的数据类型外(注2),字符串类型需要特别重点地说明一下。还记得原则吗?COM组件是运行在分布式环境中的。通俗地说,你不能直接把一个内存指针直接作为参数传递给COM函数。你想想,系统需要把这块内存的内容传递到“地球另一边”的计算机上,因此,我至少需要知道你这块内存的尺寸吧?不然让我如何传递呀?传递多少字节呀?!而字符串又是非常常用的一种类型,因此COM设计者引入了BASIC中字符串类型的表示方式---BSTR。BSTR其实是一个指针类型,它的内存结构是:(输入程序片段BSTRp=::SysAllocString(L"Hello,你好");断点执行,然后观察p的内存)
图二、BSTR内存结构
BSTR是一个指向UNICODE字符串的指针,且BSTR向前的4个字节中,使用DWORD保存着这个字符串的字节长度(没有含字符串的结束符)。因此系统就能够正确处理并传送这个字符串到“地球另一边”了。特别需要注意的是,由于BSTR的指针就是指向UNICODE串,因此BSTR和LPOLESTR可以在一定程度上混用,但一定要注意:
有函数fun(LPCOLESTRlp),则你调用BSTRp=...;fun(p);正确
有函数fun(constBSTRbstr),则你调用LPCOLESTRp=...;fun(p);错误!!!
有关BSTR的处理函数:
API函数 | 说明 |
SysAllocString() | 申请一个BSTR指针,并初始化为一个字符串 |
SysFreeString() | 释放BSTR内存 |
SysAllocStringLen() | 申请一个指定字符长度的BSTR指针,并初始化为一个字符串 |
SysAllocStringByteLen() | 申请一个指定字节长度的BSTR指针,并初始化为一个字符串 |
SysReAllocStringLen() | 重新申请BSTR指针 |
CString函数 | 说明 |
AllocSysString() | 从CString得到BSTR |
SetSysString() | 重新申请BSTR指针,并复制到CString中 |
CComBSTR函数 ATL的BSTR包装类。在atlbase.h中定义 | |
Append()、AppendBSTR()、AppendBytes()、ArrayToBSTR()、BSTRToArray()、AssignBSTR()、Attach()、Detach()、Copy()、CopyTo()、Empty()、Length()、ByteLength()、ReadFromStream()、WriteToStream()、LoadString()、ToLower()、ToUpper() 运算符重载:!,!=,==,<,>,&,+=,+,=,BSTR | 太多了,但从函数名称不能看出其基本功能。详细资料,查看MSDN吧。另外,左侧函数,有很多是ATL7.0提供的,VC6.0下所带的ATL3.0不支持。 由于我们将来主要用ATL开发组件程序,因此使用ATL的CComBSTR为主。VC也提供了其它的包装类_bstr_t。 |
1、函数WideCharToMultiByte(),转换UNICODE到MBCS。使用范例:
LPCOLESTRlpw=L"Hello,你好"; size_twLen=wcslen(lpw)+1;//宽字符字符长度,+1表示包含字符串结束符 intaLen=WideCharToMultiByte(//第一次调用,计算所需MBCS字符串字节长度 CP_ACP, 0, lpw,//宽字符串指针 wLen,//字符长度 NULL, 0,//参数0表示计算转换后的字符空间 NULL, NULL); LPSTRlpa=newchar[aLen]; WideCharToMultiByte( CP_ACP, 0, lpw, wLen, lpa,//转换后的字符串指针 aLen,//给出空间大小 NULL, NULL); //此时,lpa中保存着转换后的MBCS字符串 ............ delete[]lpa;
2、函数MultiByteToWideChar(),转换MBCS到UNICODE。使用范例:
LPCSTRlpa="Hello,你好"; size_taLen=strlen(lpa)+1; intwLen=MultiByteToWideChar( CP_ACP, 0, lpa, aLen, NULL, 0); LPOLESTRlpw=newWCHAR[wLen]; MultiByteToWideChar( CP_ACP, 0, lpa, aLen, lpw, wLen); ............ delete[]lpw;
3、使用ATL提供的转换宏。
A2BSTR | OLE2A | T2A | W2A |
A2COLE | OLE2BSTR | T2BSTR | W2BSTR |
A2CT | OLE2CA | T2CA | W2CA |
A2CW | OLE2CT | T2COLE | W2COLE |
A2OLE | OLE2CW | T2CW | W2CT |
A2T | OLE2T | T2OLE | W2OLE |
A2W | OLE2W | T2W | W2T |
2 | 好搞笑的缩写,to的发音和2一样,所以借用来表示“转换为、转换到”的含义。 |
A | ANSI字符串,也就是MBCS。 |
W、OLE | 宽字符串,也就是UNICODE。 |
T | 中间类型T。如果定义了_UNICODE,则T表示W;如果定义了_MBCS,则T表示A |
C | const的缩写 |
#include<atlconv.h> voidfun() { USES_CONVERSION;//只需要调用一次,就可以在函数中进行多次转换 LPCTSTRlp=OLE2CT(L"Hello,你好")); ............ //不用显式释放lp的内存,因为 //由于ATL转换宏使用栈作为临时空间,函数结束后会自动释放栈空间。 }
使用ATL转换宏,由于不用释放临时空间,所以使用起来非常方便。但是考虑到栈空间的尺寸(VC默认2M),使用时要注意几点:
1、只适合于进行短字符串的转换;
2、不要试图在一个次数比较多的循环体内进行转换;
3、不要试图对字符型文件内容进行转换,因为文件尺寸一般情况下是比较大的;
4、对情况2和3,要使用MultiByteToWideChar()和WideCharToMultiByte();
六、VARIANT
C++、BASIC、Java、Pascal、Script......计算机语言多种多样,而它们各自又都有自己的数据类型,COM产生目的,其中之一就是要跨语言(注3)。而VARIANT数据类型就具有跨语言的特性,同时它可以表示(存储)任意类型的数据。从C语言的角度来讲,VARIANT其实是一个结构,结构中用一个域(vt)表示------该变量到底表示的是什么类型数据,同时真正的数据则存贮在union空间中。结构的定义太长了(虽然长,但其实很简单)大家去看MSDN的描述吧,这里给出如何使用的简单示例:
学生:我想用VARIANT表示一个4字节长的整数,如何做?
老师:VARIANTv;v.vt=VT_I4;v.lVal=100;
学生:我想用VARIANT表示布尔值“真”,如何做?
老师:VARIANTv;v.vt=VT_BOOL;v.boolVal=VARIANT_TRUE;
学生:这么麻烦?我能不能v.boolVal=true;这样写?
老师:不可以!因为
类型 | 字节长度 | 假值 | 真值 |
bool | 1(char) | 0(false) | 1(true) |
BOOL | 4(int) | 0(FALSE) | 1(TRUE) |
VT_BOOL | 2(shortint) | 0(VARIANT_FALSE) | -1(VARIANT_TRUE) |
学生:谢谢老师,你太牛了。我对老师的敬仰如滔滔江水,连绵不绝......
学生:我想用VARIANT保存字符串,如何做?
老师:VARIANTv;v.vt=VT_BSTR;v.bstrVal=SysAllocString(L"Hello,你好");
学生:哦......我明白了。可是这么操作真够麻烦的,有没有简单一些的方法?
老师:有呀,你可以使用现成的包装类CComVariant、COleVariant、_variant_t。比如上面三个问题就可以这样书写:CComVariantv1(100),v2(true),v3("Hello,你好");简单了吧?!(注4)
学生:老师,我再问最后一个问题,我如何用VARIANT保存一个数组?
老师:这个问题很复杂,我现在不能告诉你,我现在告诉你怕你印象不深......(注5)
学生:~!@#$%^&*()......晕!
七、小结
以上所介绍的内容,是基本功,必须熟练掌握。先到这里吧,休息一会儿......更多精彩内容,敬请关注《COM组件设计与应用(四)》
注1:在后续的ISupportErrorInfo接口中介绍。
注2:常见的数据类型,请参考IDL文件的说明。(别着急,还没写那......嘿嘿)
注3:跨语言就是各种语言中都能使用COM组件。但啥时候能跨平台呢?
注4:CComVariant/COlevariant/_variant_t请参看MSDN。
注5:关于安全数组SafeArray的使用,在后续的文章中讨论。
相关文章推荐
- 【转载】COM组件设计与应用(三)——数据类型
- COM组件设计与应用(三)--数据类型
- COM组件设计与应用3 - 数据类型
- COM组件设计与应用(三)————数据类型
- COM组件设计与应用(三)数据类型
- COM组件设计与应用(三)数据类型
- COM组件设计与应用(三)数据类型
- 【转】COM组件设计与应用(三) 数据类型
- COM组件设计与应用之数据编码类型
- COM组件设计与应用(三)——数据类型
- COM组件设计与应用(三)——数据类型
- COM组件设计与应用(三)---数据类型
- COM组件设计与应用(三)数据类型
- COM组件设计与应用(三) 数据类型
- COM 组件设计与应用之数据类型
- COM 组件设计与应用(三)――数据类型
- COM 组件设计与应用(三)——数据类型
- COM 组件设计与应用(三)——数据类型
- COM 组件设计与应用(三)——数据类型
- JAVA进阶之旅(一)——增强for循环,基本数据类型的自动拆箱与装箱,享元设计模式,枚举的概述,枚举的应用,枚举的构造方法,枚举的抽象方法