找回密码
 注册
搜索
查看: 4679|回复: 0

Visual C++.NET中的字符串转换方法

[复制链接]
发表于 2003-10-19 13:10:41 | 显示全部楼层 |阅读模式
2002-12-05· ·丁有和··yesky0 g$ \) P, n8 ?+ |; N, [; l& m. X3 I
, R' T4 H3 u7 I, Y4 L, G: u/ e* k
8 S  G  a/ u/ ^6 i' O' K5 a" L2 N" ?
  Visual C++.NET涉及到ATL/ATL Server、MFC和托管C++等多种编程方式,不仅功能强大而且应用广泛。在编程中,我们常常会遇到ANSI、Unicode以及BSTR不同编码类型的字符串转换操作。本文先介绍基本字符串类型,然后说明相关的类,如CComBSTR、_bstr_t、CStringT等,最后讨论它们的转换方法,其中还包括使用最新ATL7.0的转换类和宏,如CA2CT、CA2TEX等。8 t7 T( ?* W8 ^* E. z

: U5 t& Q3 _# a, ]# v5 D: i  一、BSTR、LPSTR和LPWSTR
: G# `9 q% E9 f& |
- `# S* n# ]7 v  在Visual C++.NET的所有编程方式中,我们常常要用到这样的一些基本字符串类型,如BSTR、LPSTR和LPWSTR等。之所以出现类似上述的这些数据类型,是因为不同编程语言之间的数据交换以及对ANSI、Unicode和多字节字符集(MBCS)的支持。
+ t4 p. c/ t, A
! H$ S/ T' K6 C  那么什么是BSTR、LPSTR以及LPWSTR呢?
% \& u' E, [9 v$ ^4 d
3 b# |  Y  c6 N  BSTR(Basic STRing,Basic字符串)是一个OLECHAR*类型的Unicode字符串。它被描述成一个与自动化相兼容的类型。由于操作系统提供相应的API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,但它却在自动化技术以外的多种场合下得到广泛使用。图1描述了BSTR的结构,其中DWORD值是字符串中实际所占用的字节数,且它的值是字符串中Unicode字符的两倍。! Q( z. ~+ K2 I$ {0 h: w- `; E9 @8 H3 Z

8 n0 ^  Y" }% D' N- P' y  LPSTR和LPWSTR是Win32和VC++所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(‘\0’)结尾的8位ANSI字符数组指针,而LPWSTR是一个指向以NULL结尾的16位双字节字符数组指针。在VC++中,还有类似的字符串类型,如LPTSTR、LPCTSTR等,它们的含义如图2所示。! P6 f/ b0 `3 u! W" G/ L
7 X1 M$ s# o- W5 |
  例如,LPCTSTR是指“long pointer to a constant generic string”,表示“一个指向一般字符串常量的长指针类型”,与C/C++的const char*相映射,而LPTSTR映射为 char*。
2 l+ f1 L- V9 R3 Z* A: v
& F- q; `6 y1 N* `- C. T5 C' b  一般地,还有下列类型定义:
7 H! V3 U) z2 c0 M9 Y0 n$ F
& }- K& s( v' s#ifdef UNICODE
' D9 Y0 ~1 W9 t5 Z2 P5 V typedef LPWSTR LPTSTR;+ I/ d$ E" Z  j/ G/ y
 typedef LPCWSTR LPCTSTR;
  L& s( g5 ~, E1 I% l: `+ z#else 4 |) e8 G7 @1 t; A, s$ q9 r
 typedef LPSTR LPTSTR; 2 A. `3 Q" {, o8 ~! h* F
 typedef LPCSTR LPCTSTR;
/ T  p/ H; |2 \#endif  
0 Q& _2 {" E9 y+ n! h8 W% G9 Z" {8 G
  二、CString、CStringA 和 CStringW
5 i$ ]9 d+ U6 P- T7 k/ ^6 K0 d. W& r
  Visual C++.NET中将CStringT作为ATL和MFC的共享的“一般”字符串类,它有CString、CStringA和CStringW三种形式,分别操作不同字符类型的字符串。这些字符类型是TCHAR、char和wchar_t。TCHAR在Unicode平台中等同于WCHAR(16位Unicode字符),在ANSI中等价于char。wchar_t通常定义为unsigned short。由于CString在MFC应用程序中经常用到,这里不再重复。
* A9 D; E/ c: N
0 e5 O! V- _3 z2 r; |3 o  三、VARIANT、COleVariant 和_variant_t
- ]0 s) n& Q8 d: [" a; m5 l3 p8 z/ I0 @& Y( g
  在OLE、ActiveX和COM中,VARIANT数据类型提供了一种非常有效的机制,由于它既包含了数据本身,也包含了数据的类型,因而它可以实现各种不同的自动化数据的传输。下面让我们来看看OAIDL.H文件中VARIANT定义的一个简化版:* E) [# s3 t" O1 }: G

# E+ x! k' Z0 B( o; `struct tagVARIANT {$ S! i) _; c' d& _' j! Y; {4 ?
 VARTYPE vt;
) }  ]- C$ y& l4 g union {# t. I% H4 Z8 J! N: b, {" C" s
  short iVal; // VT_I2.. [7 W4 a. j1 b6 l' |7 O: a8 v8 ^( T
  long lVal; // VT_I4.9 n! m% o6 W: W7 d3 Z# f
  float fltVal; // VT_R4.
+ W  |1 s6 f3 m0 R/ R/ \1 I  double dblVal; // VT_R8.
8 N1 D! v9 W6 y9 e' P7 a# K! ?  DATE date; // VT_DATE.
. d( u* }. J4 D. m  BSTR bstrVal; // VT_BSTR.
7 k& f4 l3 X% |; b" ]! ]  …* t8 d3 t% e$ {- G: F, J
  short * piVal; // VT_BYREF|VT_I2.2 N9 Z; [& Q% V& r
  long * plVal; // VT_BYREF|VT_I4.
. e( d+ h: N2 z5 x  float * pfltVal; // VT_BYREF|VT_R4.5 T. A6 o- P7 \$ z7 y) m  n
  double * pdblVal; // VT_BYREF|VT_R8.
' X1 O9 N- |$ [2 _  DATE * pdate; // VT_BYREF|VT_DATE.
; w5 a: A% `9 R+ w6 ]  BSTR * pbstrVal; // VT_BYREF|VT_BSTR.# q  }0 l1 Z/ e; S* K8 J' V
 };
0 s3 r& w( a# Y$ o( h};
/ n" j4 k' k" _9 ~, j: \% i! t% H( L, Y# l- Q) Z
  显然,VARIANT类型是一个C结构,它包含了一个类型成员vt、一些保留字节以及一个大的union类型。例如,如果vt为VT_I2,那么我们可以从iVal中读出VARIANT的值。同样,当给一个VARIANT变量赋值时,也要先指明其类型。例如:; d0 x4 k. S0 w) z
: X. G/ c/ N8 Q! C/ P" H3 G2 ~
VARIANT va;) e  C) i' M/ b+ C' ~
:: VariantInit(&va); // 初始化
% K, i+ s; r& Mint a = 2002;4 h% U8 a3 K$ Y
va.vt = VT_I4; // 指明long数据类型
3 `7 J! m" A2 C! V5 o: g5 |) i: sva.lVal = a; // 赋值 & @9 O0 E! `6 d6 W6 I5 z/ M8 I' h

" R3 v' X8 P, H2 l' Q' {9 N  为了方便处理VARIANT类型的变量,Windows还提供了这样一些非常有用的函数:
5 R" x( o2 z3 P! {- s$ a" E7 g& L. i, L  y
  VariantInit —— 将变量初始化为VT_EMPTY;
6 }% N+ F3 r; j2 A& G( o( U5 v3 m& S9 K5 z5 `( n
  VariantClear —— 消除并初始化VARIANT;
8 g1 V  J/ b5 R8 r. ]+ Y* c/ ?2 |& M3 ]+ s
  VariantChangeType —— 改变VARIANT的类型;  R# l, g! ?, ^- B" z- w

6 R3 i* h# D; y+ r0 s2 M# @  VariantCopy —— 释放与目标VARIANT相连的内存并复制源VARIANT。! J, e0 s) Q. I
8 p( _, ^( g3 j) g0 Z
  COleVariant类是对VARIANT结构的封装。它的构造函数具有极为强大大的功能,当对象构造时首先调用VariantInit进行初始化,然后根据参数中的标准类型调用相应的构造函数,并使用VariantCopy进行转换赋值操作,当VARIANT对象不在有效范围时,它的析构函数就会被自动调用,由于析构函数调用了VariantClear,因而相应的内存就会被自动清除。除此之外,COleVariant的赋值操作符在与VARIANT类型转换中为我们提供极大的方便。例如下面的代码:: `* U* j/ b% g% |0 Q6 A) S
1 g! t- y" p; p( Y
COleVariant v1("This is a test"); // 直接构造
7 ]. O- k0 ^. U. Q  u4 ?$ P) H3 F& _4 mCOleVariant v2 = "This is a test";
. Z0 f- }2 C4 x- p. s  P! t// 结果是VT_BSTR类型,值为"This is a test"
& Q, p3 G0 K! B. E/ QCOleVariant v3((long)2002);0 j2 L) T( }' A- o% S: S
COleVariant v4 = (long)2002;
3 x; e/ F7 e* D// 结果是VT_I4类型,值为2002
4 @- B$ D8 T, o9 V1 N6 }3 Z  D9 d( K: f  ^/ X' t9 H1 |; P; @+ s) o, w
  _variant_t是一个用于COM的VARIANT类,它的功能与COleVariant相似。不过在Visual C++.NET的MFC应用程序中使用时需要在代码文件前面添加下列两句:, R+ E* Q- U) R& Y8 I3 F7 P# B
6 q/ H' x6 h$ J2 i- h' `
  #include "comutil.h"
1 \: P4 [& g; w7 K2 i* S1 l& M+ j0 |3 q5 T
  #pragma comment( lib, "comsupp.lib" )1 C% c8 D' C: E% P' i! c6 z: K1 e

0 Q) O& o; `1 r$ M9 b6 X* ~  四、CComBSTR和_bstr_t" J( U5 n* Y9 P
: t4 Q8 m$ T7 |/ x6 U/ x& \
  CComBSTR是对BSTR数据类型封装的一个ATL类,它的操作比较方便。例如:3 {' K$ q; u+ z" p/ A8 @

$ _4 ^3 c4 t+ q3 l" L$ h* R% g5 kCComBSTR bstr1; 7 l' v) D" H/ C$ u
bstr1 = "Bye"; // 直接赋值$ S: W9 [1 X$ |5 F! H5 \, a; ?
OLECHAR* str = OLESTR("ta ta"); // 长度为5的宽字符& o6 h% e8 G- m6 C5 P( p
CComBSTR bstr2(wcslen(str)); // 定义长度为5
/ T. P2 `$ u& m% r4 w: G, U. p% Pwcscpy(bstr2.m_str, str); // 将宽字符串复制到BSTR中& \* A) ^( ^4 ~; c" K& j
CComBSTR bstr3(5, OLESTR("Hello World"));
* U* w: O1 R$ T; V0 a6 @3 O/ m6 bCComBSTR bstr4(5, "Hello World"); " @& i1 Q9 }$ V2 _- Y5 s
CComBSTR bstr5(OLESTR("Hey there"));
/ b9 t) K2 c" ~% E2 vCComBSTR bstr6("Hey there"); 9 d$ w7 d$ ~8 T5 d7 V2 p3 y
CComBSTR bstr7(bstr6);
( N8 G' O9 S2 v7 `// 构造时复制,内容为"Hey there"
$ D; _' g1 Q! F' {( @- d8 P* S6 G; E  @' ~+ m( Q  e* p: }6 K
  _bstr_t是是C++对BSTR的封装,它的构造和析构函数分别调用SysAllocString和SysFreeString函数,其他操作是借用BSTR API函数。与_variant_t相似,使用时也要添加comutil.h和comsupp.lib。/ s/ t5 M9 u9 I! P1 w  U: w

$ [- P  g( \( R) V5 m  五、BSTR、char*和CString转换1 I% U+ `2 g% ^1 T3 Q

/ L' Z; r6 z: ?" G- P  (1) char*转换成CString
6 z! f+ v5 u) h1 y+ W, H$ u! C
& \- M2 r+ _; T- D& N7 d8 v  若将char*转换成CString,除了直接赋值外,还可使用CString::Format进行。例如:
5 `& }  j* a% ^! b/ D! S0 y8 I+ t0 f/ C3 `' w
char chArray[] = "This is a test";
3 G; M6 P) U$ s0 b2 B7 lchar * p = "This is a test"; / m- i& _  u4 {

  C6 N& d: b' J$ [  或
" Z+ ]+ q* p( m/ m- V4 ?) B6 K" P! @7 J' a: w0 R0 S( _  S/ ?2 h+ r
LPSTR p = "This is a test"; : M  i* G# B& w6 j" R

$ Z+ v3 E$ L# H, h8 ]3 u& E; N  或在已定义Unicode应的用程序中
. a( \- |. j" o/ [4 @1 |) \
& Z; v. ]% D( z* P  c  Q5 OTCHAR * p = _T("This is a test");
- u9 G8 Q. a# ^7 R! Q5 i2 O! ^8 z0 }3 C" q* q: S9 B
  或) H) X& H; y4 X

3 i( R5 d7 N8 o! f6 o. pLPTSTR p = _T("This is a test");' ^) Q) o4 r2 n" h; a5 z1 |
CString theString = chArray;* R( B! x3 n1 a$ Y6 v
theString.Format(_T("%s"), chArray);
* h( C- ?! E8 k0 q7 ytheString = p;
5 K& G8 F6 I* U( A+ \0 g) p6 i' _: h) ~! y% ^4 v; ~2 A
  (2) CString转换成char*% n) o4 s) }) k- D/ S& o$ `! [

1 ~$ z  ~2 N, w, O: x' w0 y  若将CString类转换成char*(LPSTR)类型,常常使用下列三种方法:
0 D. L" @( U# T  h
! J- t7 m: C* w7 [  方法一,使用强制转换。例如:" q8 D3 L0 _3 @
3 }5 T3 g0 ^; p- X
CString theString( "This is a test" );
: C5 W7 r- V" T8 n$ `! E; p$ rLPTSTR lpsz =(LPTSTR)(LPCTSTR)theString;  
& G, S, L" _- x6 ]. q
3 C' w( D) I0 g8 p0 I! h+ a  方法二,使用strcpy。例如:6 a, J! X' U8 t) u  ]* X6 ]
& t- F7 R  Q7 L2 G, l# }- ]8 k
CString theString( "This is a test" );. g) t5 z7 [3 b* F& [' {: I+ h3 J
LPTSTR lpsz = new TCHAR[theString.GetLength()+1];
( O" D6 C$ s2 T& d0 z. x- U+ U_tcscpy(lpsz, theString);
  R2 h" ~3 N* q% f0 I
  @3 A0 M4 t5 d+ r+ O  需要说明的是,strcpy(或可移值Unicode/MBCS的_tcscpy)的第二个参数是 const wchar_t* (Unicode)或const char* (ANSI),系统编译器将会自动对其进行转换。2 t9 J0 K% e+ `0 i

( U) j+ o) h! C. {  方法三,使用CString::GetBuffer。例如:
# N. }8 N6 }0 b2 M" m: U% n' t# u$ u! m- w. D' D8 c, s
CString s(_T("This is a test "));0 y+ F) {. h* t- F& {- c3 w
LPTSTR p = s.GetBuffer();
1 y# c$ b% ?6 X, u! `// 在这里添加使用p的代码
- C! P% b) V0 @' n$ Lif(p != NULL) *p = _T('\0');
! C' S/ u5 O) p- ls.ReleaseBuffer(); 6 r/ G( U7 K, O( N
// 使用完后及时释放,以便能使用其它的CString成员函数
" o8 q0 p$ p8 z3 _0 R) A- A0 N. ?1 U
  (3) BSTR转换成char*
8 A* n6 V1 c  K. ^
0 v8 L+ i9 o. W. u  方法一,使用ConvertBSTRToString。例如:) W" f0 I6 p2 F5 x
6 i. N6 E  {4 i, T* C3 v# |" m
#include / `1 {* `1 h0 L  ^
#pragma comment(lib, "comsupp.lib")
$ |6 k  g3 w8 i; Y: u1 w# tint _tmain(int argc, _TCHAR* argv[]){: h9 o! b4 D0 X7 U8 w: ?7 S
BSTR bstrText = ::SysAllocString(L"Test");
# a/ f2 B" d) u% N. n% hchar* lpszText2 = _com_util::ConvertBSTRToString(bstrText);- H/ e9 |! V# ?+ P1 y; Y  a5 I, _
SysFreeString(bstrText); // 用完释放
' ~$ h' Z! n3 K5 c( \* g4 j2 }delete[] lpszText2;
( x* w( g5 I. O$ creturn 0;
( m5 I4 ?1 P) J" d' I, j}  4 ]& S9 D6 I' Q: b3 ?9 F  k5 Z
6 m* Z! ~0 U& N" ^$ Z' \! X& @
  方法二,使用_bstr_t的赋值运算符重载。例如:
% {' }: P* h+ K
5 G* x) T$ y1 p* q# d2 T0 i_bstr_t b = bstrText;
& _5 |* A( W' u" b  vchar* lpszText2 = b; 3 ]5 b9 M% X2 w  ~2 P7 {  Z

6 r* I0 h  Z! i  (4) char*转换成BSTR
5 e& z6 Y; n5 z/ c7 q/ T& l1 l) q
  方法一,使用SysAllocString等API函数。例如:
9 j6 @) l8 w3 t. m4 [8 T& G
: O" `; c* ]# K* Q! }BSTR bstrText = ::SysAllocString(L"Test");) [* N! S8 y6 Z0 E
BSTR bstrText = ::SysAllocStringLen(L"Test",4);
* _- x: a& l  ?5 S9 W; W; B/ `! V3 OBSTR bstrText = ::SysAllocStringByteLen("Test",4); ! ~3 s4 f" D6 T( a9 ^
$ W/ G: o, q, r# ^, _$ n
  方法二,使用COleVariant或_variant_t。例如:8 j7 e5 N  J( g# `% T  q
: I0 \8 D! K! f! y" `
//COleVariant strVar("This is a test");$ O$ }  ?$ g6 m2 u% v" ~
_variant_t strVar("This is a test");$ X5 Q  `2 q# I
BSTR bstrText = strVar.bstrVal;   ?9 V( Y* s, [4 I# n: z. P
8 q7 s* ^! ~8 m& ~
  方法三,使用_bstr_t,这是一种最简单的方法。例如:0 W) u7 z/ j* |: }$ w

+ e: J- H- w0 Y0 BBSTR bstrText = _bstr_t("This is a test"); ) u9 X. v( z& [9 v; A7 A7 E) i3 ?
, ?, q: F0 R2 `4 Q! D6 h8 |) u
  方法四,使用CComBSTR。例如:/ b& G4 f# Q) A0 x3 l9 N* r' e% p
3 ~3 g& n0 C" d2 g, @% Q, [, G
BSTR bstrText = CComBSTR("This is a test"); + b5 ^: V: [9 T& K" [  e) H. T# ]
, q7 o; E  G: S) r2 [/ v. f
  或
8 V, k* l0 L6 p, @9 R  `; _2 t/ M1 g+ o% [2 u- [) }1 c  f. m3 A9 z8 w' |
CComBSTR bstr("This is a test");: C+ t2 T* F- W! i, X+ A- X1 ]
BSTR bstrText = bstr.m_str; * W, \7 O( a. t1 Q$ E4 M

: G2 i5 p2 J3 [: b  方法五,使用ConvertStringToBSTR。例如:
8 G. {( g+ O* s2 [7 d) ~7 F4 O# S/ v# h
' g/ v3 b, b: `  u6 Jchar* lpszText = "Test";0 s$ A+ L; {5 v
BSTR bstrText = _com_util::ConvertStringToBSTR(lpszText);
0 f; ?, A& e- A, p$ v1 G3 q: k# t
1 ?2 B: J( Z$ h. d4 I  (5) CString转换成BSTR; g; B4 }* o, c& Z+ r0 R7 _
  T  v6 o$ |8 s: T
  通常是通过使用CStringT::AllocSysString来实现。例如:
+ |8 v6 h* i9 G8 o& L- H6 L0 B- E' f
CString str("This is a test");. W; c* {4 ~# t8 ?( U
BSTR bstrText = str.AllocSysString();% j( f2 X- _% W

, ]. w; g. I  s. y( \# c7 n2 XSysFreeString(bstrText); // 用完释放  
: c3 f$ G( f- z! F8 z
$ j3 P; A) T% E) o) h  (6) BSTR转换成CString
" a! Q+ y0 u* X( S! W# S
! Y8 C1 W" o- Z* i/ X2 p$ O  一般可按下列方法进行:9 U0 N) A3 p: U2 D8 J

/ L" e# ]6 E4 S; `: F5 P* c6 QBSTR bstrText = ::SysAllocString(L"Test");4 a: k8 |* j' `4 U- l& D! x+ a
CStringA str;$ x1 M5 M8 N% w; b4 A/ B6 f& Q
str.Empty();8 p9 M4 V% m0 z. {# K
str = bstrText;  0 t; f1 }! E* L6 `5 V! |- z
" r5 T6 f5 \$ h! v6 T7 ^
  或: {1 K* u. D2 z7 z- m# i( ~6 h7 A4 H

, u4 j; G' e% F! x( N5 ?5 o$ I+ OCStringA str(bstrText);
9 X% ~# w8 `# V1 B5 g
; a( ~5 W/ B( K6 I! J; Q" @# d  (7) ANSI、Unicode和宽字符之间的转换! `/ g  p% c/ @( o$ d& B

! x/ v9 U: p3 j9 k2 u$ b  方法一,使用MultiByteToWideChar将ANSI字符转换成Unicode字符,使用WideCharToMultiByte将Unicode字符转换成ANSI字符。' M& Z6 W+ b! s* h) Q

/ Y6 ~- J  P- [7 Q( f" n! s  方法二,使用“_T”将ANSI转换成“一般”类型字符串,使用“L”将ANSI转换成Unicode,而在托管C++环境中还可使用S将ANSI字符串转换成String*对象。例如:
' R2 w& V/ p+ o3 a" T9 ?9 H, j% Z; m
! H: F9 G$ e2 D! x; w5 M: LTCHAR tstr[] = _T("this is a test");0 a! g  }# C$ {2 O/ f
wchar_t wszStr[] = L"This is a test";
& m" o4 S  k3 t7 |$ ?7 ^; BString* str = S”This is a test”;
7 y% A1 m, L1 ~- k! W4 M# e" @  @  Y) r3 A) A  D
  方法三,使用ATL 7.0的转换宏和类。ATL7.0在原有3.0基础上完善和增加了许多字符串转换宏以及提供相应的类,它具有如图3所示的统一形式:) h& B. T1 K, ~+ K1 Y  a

3 G' g: x( W5 u% V  其中,第一个C表示“类”,以便于ATL 3.0宏相区别,第二个C表示常量,2表示“to”,EX表示要开辟一定大小的缓冲。SourceType和DestinationType可以是A、T、W和OLE,其含义分别是ANSI、Unicode、“一般”类型和OLE字符串。例如,CA2CT就是将ANSI转换成一般类型的字符串常量。下面是一些示例代码:7 b8 ~5 a& |+ m  J* d  C
* D& [( ^1 J& x5 K% O
LPTSTR tstr= CA2TEX<16>("this is a test");
) U) `1 H1 I3 j9 T7 l1 S" vLPCTSTR tcstr= CA2CT("this is a test");$ g9 G" t" @& a# G( N! s0 H
wchar_t wszStr[] = L"This is a test";( h/ H9 n0 ?) l) v6 r: k" z6 a
char* chstr = CW2A(wszStr);  
  j6 B) ]/ O  G0 I8 g6 n/ v/ {
' r' N, b6 j; J  l0 h; S$ F  f% H  六、结语
( w/ E/ T/ e/ j6 V" u/ d
0 e; U( |  h/ j. L+ M7 @  几乎所有的程序都要用到字符串,而Visual C++.NET由于功能强大、应用广泛,因而字符串之间的转换更为频繁。本文几乎涉及到目前的所有转换方法。当然对于.NET框架来说,还可使用Convert和Text类进行不同数据类型以及字符编码之间的相互转换。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|宁德市腾云网络科技有限公司 ( 闽ICP备2022007940号-5|闽公网安备 35092202000206号 )

GMT+8, 2026-8-14 00:07 , Processed in 0.020659 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表