·汉化新世纪 ·汉化新世纪论坛 ·百家争鸣 ·论坛集萃 ·汉化问答 ·软件介绍
文章首页 >> 汉化教学 >> VB汉化 >> VB 与 UniCode    Creative Commons License,创作共用协议(中文版)  署名 非商业性使用 禁止演绎

VB 与 UniCode

作者: 梁利峰 来源:点睛工作室 时间:2003-08-02 点击:14106


VB 与 UniCode


声明

个人可以自由转载本文,不过应保持原文的完整性,并通知我;商业转载先请和我联系。

本文没有任何明确或不明确地提示说本文完全正确,阅读和使用本文的内容是您自己的选择,本人不负任何责任,但是如果您发现本文有错漏的地方,希望您可以给我指出。

本文假定您已经对 VB 的编程比较熟悉,所以对一些本人认为简单的问题不会做太多的解释,如果有什么问题,可以给我提出。

意见、建议和提出的问题最好写在我的主页 http://llf.126.com 的留言版上。

一 UniCode

UniCode 作为一个名词应该是非常著名的了,特别是在我们这样一个非英语国家里。UniCode 是一种联合的符号内码,因为用两个字节表示,所以可以表示世界上的大多数语言。我在某些书上见到说 UniCode 分两种编码转换格式,UTF-8 和 UTF-16 ,其中的 UTF-8 编码是变长的,长度从一个字节到三个字节不等,它的优势在于英文不用处理就符合此编码,UTF-16 编码和 UniCode 的编码基本相同,可能在文本转换上有应用吧?有兴趣的朋友可以通过 E-mail 或者留言版和我讨论此编码 。本文不讨论这些编码转换格式,而主要讨论 UniCode 的内存处理,长度固定两个字节,因为这种编码是 Windows 系统内定的 Unicode 编码方式,另外,长度固定对于字串的处理也非常有利,个人认为用处更大一些。

我不想在概念上做太多的文章,大家只要记住 UniCode 是两个字节就可以了。作为英文,在 UniCode 里的编码是首字节置零,尾字节就是原来的英文码,比如“A”的十六进制码是“41”,而其 UniCode 的十六进制码是“0041”。不过千万不要认为 GB 码的汉字编码和 UniCode 相同,其实几乎都不相同,所以一般要一个对照表进行转换,不过各语言版本的 Windows 自带不同的编码和 UniCode 之间互相转换的函数,我们就不需要关心细节了。Windows 的可执行文件中经常使用 UniCode 作为存储格式,使用十六进制的编辑器就可以看到,如果是英文,就显示成每个字母前有一个“00”,但是如果是中文而又存成 UniCode 的话,是不能察看到的,以前我曾经说过变量使用中文没有问题,因为在可执行文件里查不到那些中文,但是如果是 UniCode 的话,查不到也不表明没有,所以我又把文件转换成 ANSI 格式,再次查找,仍然没有,也就是说结论没有变化,我们还是可以使用中文变量名的。

因为 Windows 的可执行文件编译有使用 UniCode 的习惯,所以海峡对岸的同志们编写的软件虽然没有专门制作 GB 版,但是像菜单标题之类的东西显示也是正常的,不过既然只是“经常”,而不是“总是”,所以很多对话框的显示却是乱码,也就可以解释了。进一步推想,如果所有的字符串都存储为 UniCode 的话,我们就不需要等待同一个软件的 GB 版和 BIG5 版了,当然,我们看到繁体字习以为常,但是他们看到简体字时,不知道会是什么感想?

另外,像 GB、Big5 之类的编码,都是考虑到和英文的兼容性的,比如 GB 码,就是将首位置“1”,当然,现在的 GBK 内码只是第一个字节首位置“1”,第二个字节作了扩展,也可以是 ASCII 值小于 127 的值了。在简体中文版 Windows 95 的目录下有一个“GBK.TXT”文件,罗列了 GBK 内码所有的字,有兴趣可以看一看。顺便说一下,以前常用的 GB2312 内码只有六千多字,Dos 下的汉字系统都是使用此内码的,所以也只能显示这六千多字,而 GBK 内码有两万九千多字,内含繁体编码,不过字体文件不一定支持 GBK ,我所知的微软的 GBK 字体包括 Windows 自带的“宋体”、“黑体”,Office 自带的“隶书”、“幼园”,其它的字体文件一般都是 GB2312 的,包括 Windows 自带的“楷体_GB2312”、“仿宋_GB2312”,还有其它的像“微软简行楷”、“微软简魏碑”等,其它公司制作的字体文件我至今没有见过支持 GBK 的,实在很遗憾,而且微软好像也没有再制作 GBK 字体,大概是只顾得打官司了吧?!

二 VB 和 UniCode 的关系

在 C 语言中,内部的字符串是 ANSI 格式,也就是以字节为单位,但是在 VB 中字符串是 UniCode 格式,也就是说以字为单位,为了和类 C 语言相区别,我把以 UniCode 表示的字符串称之为字串。

既然 VB 中字串是 UniCode 格式,我们就知道 Len("ABC测试") 等于 5 , LenB("ABC测试") 等于 10 ,当然,因为 Windows 9x 系统内部并不使用 UniCode ,所以我们在和 API 接口时就会出现问题了。我们经常可以见到 API 的声明函数最后由一个“A”,比如“SetWindowTextA”“GetPrivateProfileStringA”等等,这是表明此函数使用 ANSI 字符串格式;相对的,也有使用 UniCode 格式字串的相同功能的函数,后缀为“U”,比如“SetWindowTextU”“GetPrivateProfileStringU”等等,不过这些 UniCode 格式的函数一般用于 Windows NT ,Windows 9x 上很少有,另外,因为 Windows NT 也支持 ANSI 格式的函数,所以平时我们调用的仍然是 ANSI 格式的函数,也所以 VB 在调用 API 时,都会把字串转换成字符串,以便和 ANSI 函数相兼容。

不过 Windows 9x 中也并不是没有 UniCode 函数,比如 OLE 自动化函数就全部是 UniCode 函数,如上,VB 会把自己的 UniCode 字串转换成 ANSI 字符串,所以调用这些 OLE 自动化函数一般不能用“Declare”语句定义,当然,事实上 VB 和 OLE 自动化关系十分密切,VB 中的很多功能都是建立在 OLE 自动化的基础上的(比如读取 JPG、GIF 等图形文件就是使用的 OLE 自动化函数),所以 VB 在内部调用 OLE 自动化函数,这样就不需要作转换了,也因此,VB 内部调用 OLE 自动化函数的速度比 VC 调用 OLE 自动化函数的速度要快,因为 VC 要先做 ANSI 字符串到 UniCode 的转换,不过这种优势并不太明显,就像 VB 调用 ANSI 格式函数时速度比 VC 慢的劣势也不明显一样。(需要注意,VB 使用和 OLE 自动化同样的变体类型的变量,这也是 VB 调用 OLE 自动化函数速度快的一个原因,使用其它语言调用 OLE 自动化函数可能需要自己做普通变量到变体变量的转换)

因为 VB 内部使用 UniCode ,很多人在使用 VB 编程处理字串的时候都或多或少地遇到了问题,因此很多人在介绍在 VB 中处理字串的技巧的时候总会带出一些对 VB 的不满,因为不能用老的对字符串的认识来处理字串了,不过我认为这种认识是不对的,正是因为我们是在一个非英语国家,VB 的这一特性才更有用处,当然,如果能多了解关于 UniCode 的知识会更有用。

在 VB 中还有一种处理字串的特殊方法,用来解决我们遇到需要对字串按字节方式访问的问题,看一下以下的代码,大家应该可以明白的:

Option Explicit

Private Sub Form_Load()
    Dim 字节数组() As Byte
    Dim 字串 As String
    字串 = "测试"
    字节数组 = 字串 & "成功"
    字串 = 字节数组
    MsgBox 字节数组, vbOKOnly, 字串
End Sub

我们看到,在以上的程序中,字节数组可以和字串互相赋值,而且字节数组在取得字串时会自动调整大小以适应字串,这一功能非常有用,而且非常方便,不过因为 VB 内部是 UniCode 的,所以我们处理时必须以两个字节为单位,否则会造成处理错误,当然,因为总是以两个字节为单位,所以也是很方便的,比起老的字符串方式处理中文可是方便的太多了。VB 中的 Integer 是十六位的,也就是两个 Byte ,那么使用 Integer 数组处理字串岂不是更好?好的,VB 支持 Integer 数组和字串的互相赋值吗?不支持!

三 VB 中快速处理 UniCode

VB 中处理 UniCode 是有一些问题需要解决的。

首先,我们遇到的问题是如何取得中英文混合字串在 ANSI 格式时的长度?VB 当然是可以测量其长度的,不过需要用到字符串转换函数:LenB(StrConv("ABC测试",vbFromUnicode)) 。好的,我们来看一下,首先我们把字串转换成字符串 StrConv("ABC测试",vbFromUnicode) ,然后用 LenB 函数取得字符串的长度,显然,在这里我们做了一些的无用功——字串转换,这是很费时间的,但是因为 VB 内部是使用 UniCode 格式的,所以倒也不可避免,不过在某些情况下可以将这一次转换和其它的转换合并成一次以减少时间消耗。

在了解转换合并之前,我们先来了解一下 VB 调用 API 时的字串参数的处理方法。来看一个简单的 API 函数:

Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" _
    (ByVal hwnd As Long, ByVal lpString As String) As Long

在这个函数中有两个参数,一个是窗口句柄 hwnd ,一个是要设置的窗口标题指针 lpString 。我们知道在 VB 里使用 ByVal 关键字传递字串参数,当然,在其中并不是真的按值传递,VB 在这时作了一次 UniCode 到 ANSI 的转换,然后把转换后的 ANSI 字符串的指针传递给了 API 函数,因为有时候 API 不止利用字符串指针传入字符串,也会传出字符串,所以 VB 在 API 函数调用结束时又作了一次 ANSI 到 UniCode 的转换,把转换的结果传回到原来的字串里,在这个函数里是 lpString 。假设在你的程序里要调用 SetWindowText ,而且又要得到字符串的长度,那么程序一般是这样的:

Public Function 设置标题(标题 As String) As Long
    设置标题 = LenB(StrConv(标题, vbFromUnicode))
    Call SetWindowText(Me.hWnd, 标题)
End Function

程序可以说是非常简单,不过这里所作的额外操作还是很多的,如前所述,调用 SetWindowText 时会有一次 UniCode 到 ANSI 的转换,还有一次 ANSI 到 UniCode 的转换,另外,为了取得字符串的长度,程序本身还做了一次 UniCode 到 ANSI 的转换,而且这些 VB 在内部进行的转换都另外申请了内存以便进行操作,其实这都是可以避免的,不过需要修改一下 API 的定义,另外需要用到字符串和 Byte 数组之间能互相赋值的特性,而且还要用到一个未公开的函数 VarPtr 用来取得变量的地址:

Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" _
    (ByVal hwnd As Long, ByVal lpString As Long) As Long

Public Function 设置标题(标题 As String) As Long
    Dim 标题字符串() As Byte
    标题字符串 = StrConv(标题, vbFromUnicode)
    设置标题 = UBound(标题字符串) + 1
    Call SetWindowText(Me.hWnd, VarPtr(设置标题(0)))
End Function

这样,我们在调用此 API 函数时一共只分配了一次内存,作了一次 UniCode 到 ANSI 的转换就完成了和上一个函数同样的任务,速度大为提高,内存的需求量也有所降低。

当然,标题不可能很长,所以这个函数所得到的速度提升和性能改善并不能在程序中体现出来,而且 SetWindowText 这个函数还会刷新窗口标题,会用到极其缓慢的 GDI 函数,怕是即使调用很多次,速度提升也会淹没在缓慢的 GDI 海洋里,在这里也只是作为一个例子来说明此种处理的优势,取这个函数只是为了容易理解罢了,为了测试用这种方法究竟能得到多少的优势,应该选一个只作简单内存处理的 API (最好是自己做一个 DLL ,写一个什么也不做的函数)调用,不过我没有测试过,如果各位谁作了这种测试,可不要忘了把结果告诉我啊。 :)

重新回到字串和数组的问题上。我们知道,VB 中处理字符串有很多函数,比如 MidLeftRight 等,如果我们使用这些函数来取得字串的子串,会经过一次函数调用,一些对输入值的判断和转换,重新分配内存,返回。而这只是我们察看时的代价,如果要修改一个字串中间的一个字,我们就不得不取得此字前的字串,加上修改后的字,再加上此字后的字串,浪费就更大了。在这种时候,数组就能显出其优势了,因为对数组的操作事实上是指针操作,速度非常快,而且如果要修改其中的字,因为是指针操作,所以不需要提取字串的操作,只要简单的对要改的字操作就可以了:

Option Explicit

Private Sub Command1_Click()
    字串方式 "不好吗?"
End Sub

Private Sub Command2_Click()
    数组方式 "不好吗?"
End Sub

Private Sub 字串方式(字串 As String)
    Dim i As Integer, 新字串 As String, 字 As String
    For i = 1 To Len(字串)
        字 = Mid(字串, i, 1)
        If 字 = "好" Then
            新字串 = 新字串 & "坏"
        Else
            新字串 = 新字串 & 字
        End If
    Next
    MsgBox 新字串
End Sub

Private Sub 数组方式(字串 As String)
    Dim i As Integer, 新字串() As Byte, 字() As Byte
    字 = "好"
    新字串 = 字串
    For i = LBound(新字串) To UBound(新字串) Step 2
        If 新字串(i) = 字(0) And 新字串(i + 1) = 字(1) Then
            字 = "坏"
            新字串(i) = 字(0)
            新字串(i + 1) = 字(1)
            'Exit For '只替换第一个字的时候加上这一句
        End If
    Next
    MsgBox 新字串
End Sub

在实际的编程中间大概不会有第一个函数那样的操作方法,因为可以用 InStr 函数得到某字的位置,但是取出前后字串的操作还是需要的,所以仍然会比第二个函数的速度慢,当然,这也是在大量数据操作的时候,如果数据量较小,还是使用 Mid 等函数的好,毕竟简单才是 Basic 的精华!

不过我们也见到了,在第二个函数中处理字的时候仍然很麻烦,因为要判断两次才能确定一个字,很不方便,仍然是上面的老问题:VB 支持 Integer 数组和字串的互相赋值吗?答案当然也仍然是:不支持!不过我们可以自己设计这样的函数来实现这样的功能。

为了实现这一功能,首先我们要得到字串的地址,不过我试了很多办法,就是没有办法直接得到它的地址,很不幸的仍然需要使用 Byte 数组做桥梁,下面是我写的两个函数,一个实现从字符数组到字数组的转换,一个实现从字数组到字符数组的转换:

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
	(lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)

Public Sub 字符数组到字数组(字符数组() As Byte, 字数组() As Integer)
    Dim 长度 As Long
    长度 = UBound(字符数组) - LBound(字符数组) + 1
    If 长度 >= 2 Then
        Redim 字数组(1 To 长度 \ 2)
        CopyMemory 字数组(1), 字符数组(0), 长度
    End If
End Sub

Public Sub 字数组到字符数组(字数组() As Integer, 字符数组() As Byte)
    Dim 字长度 As Long, 字符长度 As Long
    字长度 = UBound(字数组) - LBound(字数组) + 1
    字符长度 = 字长度 * 2
    If 字长度 >= 1 Then
        If UBound(字符数组) - LBound(字符数组) + 1 <> 字符长度 Then
            Redim 字符数组(1 To 字符长度)
        End If
        CopyMemory 字符数组(0), 字数组(1), 字符长度
    End If
End Sub

所以用过程(Sub)也是迫不得已,只有这样我才能确定 VB 使用的是快速的指针方式,而不是又创建了一个副本。现在,我们就可以使用以上的两个函数改写刚才的程序:

Option Explicit

Private Sub Command1_Click()
    数组方式 "不好吗?"
End Sub

Private Sub 数组方式(字串 As String)
    Dim i As Integer, 新字串() As Byte, 字() As Byte
    Dim 中间字串() As Integer, 中间字() As Integer
    字 = "好坏"
    新字串 = 字串
    字符数组到字数组 新字串, 中间字串
    字符数组到字数组 字, 中间字
    
    For i = LBound(中间字串) To UBound(中间字串)
        If 中间字串(i) = 中间字(1) Then
            中间字串(i) = 中间字(2)
            Exit For
        End If
    Next
    '也可以不用以下的一句,因为大多数的 API 需要的只是地址
    '所以如果和 API 接口的话,需要做的是把 UniCode 转换成
    'ANSI,然后取得地址传递过去,如果和 UniCode 函数接口的
    '话,更可以直接把地址传过去。把 UniCode 转换成 ANSI 的
    '话,需要使用 API 函数 WideCharToMultiByte 。
    字数组到字符数组 中间字串, 新字串
    MsgBox 新字串
End Sub

再重申一次,这样做的原因是有大量数据需要处理,如果没有大量数据需要处理的话,一来不需要这么麻烦,二来可能速度还会减慢,大家可以分析一下,应该是可以明白的。那么什么时候是有大量数据,而且需要有这种方便的 Integer 数组的呢?我遇到的是我做的内码转换器,使用这种方法的速度提升占总提升的 1/3 (另外的 2/3 在文件的读和写上,参见我写的《文件处理速度》),以下给出其中使用此方法的部分核心代码以供参考:

Private Function 字数组方式转换(字数组() As Integer) As Boolean
    Dim i As Long, 不使用多线程 As Boolean, n As Integer
    不使用多线程 = Not 使用多线程
    If 不使用多线程 Then DoEvents
    For i = LBound(字数组) To UBound(字数组)
        字数组(i) = 内码对照表(字数组(i))
        If 不使用多线程 Then
            n = n + 1
            If n > 1000 Then
                n = 0
                DoEvents
            End If
        End If
        If 中止 Then
            中止 = False
            字数组方式转换 = True
            Exit For
        End If
    Next
End Function

可以看到,这一段代码里也演示了在不使用多线程的时候怎么加快程序的执行速度,不过和本题无关,就不说了。

上面的注释中说道把 Integer 数组做 UniCode 到 ANSI 的转换需要使用 API 函数,这是因为 StrConv 也不认识 Integer 数组,下面把我写的关于文本文件操作的两个函数附上,其中利用了 API 函数 MultiByteToWideChar 和 WideCharToMultiByte ,不过使用的是 Bruce McKinney 提供的类型库的函数说明:

Public Function 文本文件读入(文件名 As String, 内容() As Integer) As Boolean
    Dim 文件句柄 As Long, 文件长度 As Long, 总长度 As Long, 临时() As Byte
    文件名 = Trim$(文件名)
    文件句柄 = CreateFile(文件名, GENERIC_READ, FILE_SHARE_READ, _
        0&, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0&)
    If 文件句柄 <> 0 Then
        文件长度 = GetFileSize(文件句柄, 0&)
        Redim 临时(1 To 文件长度)
        If ReadFile(文件句柄, 临时(1), 文件长度, 文件长度, ByVal 0&) <> 0 Then
            Redim 内容(1 To 文件长度)
            总长度 = MultiByteToWideCharPtrs(CP_OEMCP, 0&, VarPtr(临时(1)), _
                文件长度, VarPtr(内容(1)), 文件长度 * 2)
            Redim Preserve 内容(1 To 总长度)
            文本文件读入 = True
        End If
        CloseHandle 文件句柄
    End If
End Function

Public Function 文本文件写入(文件名 As String, 内容() As Integer) As Boolean
    Dim 文件句柄 As Long, 文件长度 As Long, 总长度 As Long, 临时() As Byte
    文件名 = Trim$(文件名)
    文件句柄 = CreateFile(文件名, GENERIC_WRITE, 0&, 0&, _
        CREATE_NEW, FILE_FLAG_SEQUENTIAL_SCAN, 0&)
    If 文件句柄 <> 0 Then
        总长度 = UBound(内容) - LBound(内容) + 1
        Redim 临时(1 To 总长度 * 2)
        文件长度 = WideCharToMultiBytePtrs(CP_OEMCP, 0&, VarPtr(内容(1)), _
            总长度, VarPtr(临时(1)), 总长度 * 2, 0&, 0&)
        Redim Preserve 临时(1 To 文件长度)
        If WriteFile(文件句柄, 临时(1), 文件长度, 文件长度, ByVal 0&) <> 0 Then
            文本文件写入 = True
        End If
        CloseHandle 文件句柄
    End If
End Function

上面的函数中各位只需注意 UniCode 和 ANSI 互相转换的函数就可以了,其中的“CP_OEMCP”常数指的是当前版本的 Windows 使用的缺省代码页,比如在简体中文版 Windows 上调用 MultiByteToWideCharPtrs 函数时使用 CP_OEMCP 常数,就会把 GBK 内码的文字流转换成 UniCode 内码的文字流,而如果在繁体中文版的 Windows 上,同样的程序就是把 BIG5 内码的文字流转换成 UniCode 内码的文字流;相对的,WideCharToMultiBytePtrs 函数使用 CP_OEMCP 常数就是把 UniCode 文字流转换成当前语言内码的文字流。至于使用 API 函数进行读写操作,正像 Bruce McKinney 所说,是因为和其它 API 函数的兼容性考虑,不过不再说了,不然就离题太远了。

结语 UniCode 随想

回想一下我们处理 UniCode 的方法。首先,我们把文本文件读出,然后把读出的内容使用 API 函数转换成 UniCode ,在 UniCode 的世界中,我们处理各种语言都会得心应手,但是 Windows 的字库是各种方言的字库,比如 GBK 、BIG5 等,如果我们可以使用 UniCode 字库的话,那么一套字库就可以支持所有国家版本的 Windows ,而不会造成每安装一种语言支持就需要安装一套新的字库,这样显示的时候调用的就只是一套字库,操作系统需要提供的只是各国编码和 UniCode 的转换函数罢了。

我们知道 Windows NT 是从底层开始重新设计的操作系统,所以 NT 其实也是从底层支持 UniCode 的,现在 Windows 2000 使用和 NT 同样的构造,就是说它也是从底层开始支持 UniCode 的,事实上 Windows 2000 会出一种所谓的世界版,用户可以选择添加对多种语言的支持,并且选择多种语言的字库,在 Internet 高度发达的今天,这种方便性当然是无可置疑的,我坚决拥护。不过我的推想是如果世界上存储资料的时候都使用 UniCode ,而不是各自的编码方法,则对于我们在各国之间传输资料将会有非常大的帮助,如果真的完全实现了这一点,操作系统连提供各国编码和 UniCode 的转换函数都将成为多余的了!

HTML 现在是网络上流行的的格式,不过 XML 的发展应该会替代今天 HTML 的地位,而 XML 的标准中有一项规定:必须支持 UniCode (同时也可以选择性的支持 GBK、BIG5 等编码)。UniCode 是英语国家制定的标准,XML 也是英语国家制定的标准,这让人有些遗憾 —— 非英语国家为了使用计算机,想出了各种方法来让计算机支持本语种,却没有想到兼容其它国家(除了英语)的问题,最后仍然是英语国家找到了解决方案。而在已经有了解决方案的时候,我们却还懵然不觉,甚至有些不愿意了解,才是更可遗憾的地方。

有集体观的个人才有发展,有国家观的集体才有发展,有世界观的国家才有发展,不是吗?

点睛工作室·梁利锋 结稿于 2000.3.27

汉化新世纪 责任编辑: 乾 .:|:. 标签(Tag): VB UniCode 非标

·上一篇: Delphi 字号修改之二 ·下一篇: 对话框破解两例

· 版权申明: 本文引自《点睛工作室》,如有版权疑问请及时联系本站,以便本站处理。

· 转载申明: 本文引自《点睛工作室》[ 作者: 梁利峰],如需转载请直接联系原始作者,并请注明原始出处。

相关文章                                                                                发表评论 打印此文 关闭窗口

| 设为首页 | 加入收藏 | 联系我们 | 友情链接
Creative Commons License,创作共用协议(中文版)  署名 非商业性使用 禁止演绎
本站内容,除转载或版权特别申明的内容外,皆遵守 创造共用协议中文版之“署名-非商业性使用-禁止演绎 2.5 中国大陆”条款
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 2.5 China License.
本网站内容源自汉化新世纪论坛的摘录和汉化新世纪成员的原创文章。
凡汉化新世纪论坛的文字皆默认为汉化新世纪与原作者共同拥有并授权发布。
如对本站发布文章有所异议请来信告知,我们将及时删除。
凡商业摘录本站文字请先与我们联系,本站将保留非授权商业发布的追究权利。
凡非商业摘录本站文字请明显注明出处和原作者,并不得改动,凡改动必先征求原作者同意。
苏ICP备05002283号