* D* f+ x# r% {4 ?<IMG src="http://www.vckbase.com/document/image/paragraph.gif"> <B>正确的遍历和索引字符串</B> + J* ?+ z/ d+ K/ j. z : r: ]3 I+ L- M7 t 因为我们中大多数人都是用着SBCS字符串成长的,所以我们在遍历字符串时,常常使用指针的++-和-操作。我们也使用数组下标的表示形式来操作字符串中的字符。这两种方式是用于SBCS和Unicode字符串,因为它们中的字符有着相同的宽度,编译器能正确的返回我们需要的字符。- u$ e7 _2 N) z k, A
然而,当碰到DBCS字符串时,我们必须抛弃这些习惯。这里有使用指针遍历DBCS字符串时的两条规则。违背了这两条规则,你的程序就会存在DBCS有关的bugs。</P>' O2 r% N% N! k: ?1 F5 E
<DIR> " e$ H5 d. f! b! H: c9 Y o8 ` \# o<LI>1.在前向遍历时,不要使用++操作,除非你每次都检查lead byte; 8 o7 }8 ] |: v _; N9 X
<LI>2.永远不要使用-操作进行后向遍历。 </LI></DIR># b7 f3 u7 c5 N* n' D8 V
<> 我们先来阐述规则2,因为找到一个违背它的真实的实例代码是很容易的。假设你有一个程序在你自己的目录里保存了一个设置文件,你把安装目录保存在注册表中。在运行时,你从注册表中读取安装目录,然后合成配置文件名,接着读取该文件。假设,你的安装目录是C:\Program Files\MyCoolApp,那么你合成的文件名应该是C:\Program Files\MyCoolApp\config.bin。当你进行测试时,你发现程序运行正常。 / D; N# k+ `& Y7 i: D6 x. ^ 现在,想象你合成文件名的代码可能是这样的:</P><RE>bool GetConfigFileName ( char* pszName, size_t nBuffSize ) 2 f, G9 Z6 q7 Z2 [( H' Y9 S{( a5 |6 F2 ]) t- B: a* s: x7 s( u
char szConfigFilename[MAX_PATH]; 0 o2 t& y: d1 J5 V 4 p1 p2 Y1 B. y6 q' L7 w; u7 o/ W // Read install dir from registry... we''ll assume it succeeds. O; e3 B0 j% W. J$ C" j
/ B$ V; d+ {6 X) l0 _' u& g/ ?+ z
// Add on a backslash if it wasn''t present in the registry value. 3 H' C0 ~! y' C // First, get a pointer to the terminating zero. - H' t1 C7 a/ a" u. X char* pLastChar = strchr ( szConfigFilename, ''\0'' ); " U. M0 K8 S/ O4 ^5 G, ~ ( ?) p& H0 y+ W8 ` w; ?
// Now move it back one character.1 Z3 z' w2 s; }! I: }7 W
pLastChar--; , w) _/ q e2 G7 ]( {6 N
& I* a M) f/ Z$ F. y% }5 p if ( *pLastChar != ''\\'' )/ }6 |2 v( i5 l9 B% V7 h+ z
strcat ( szConfigFilename, "\\" );+ \1 h) K: g/ V+ [! F; n) g v
+ F3 M4 s+ _! R# r- X% }, a // Add on the name of the config file. 1 _' j8 @6 `0 f8 G0 l/ p7 _/ X! O& w strcat ( szConfigFilename, "config.bin" );7 Z7 n+ D* W9 a. y; S( Z
" n( s* U3 J% h T // If the caller''s buffer is big enough, return the filename.( L+ @5 v% s" E3 q
if ( strlen ( szConfigFilename ) >= nBuffSize ) * [1 j4 H7 c- o( s& |; \9 A return false; % y$ \" o& g/ J: {; c J! f% \# e else . D$ E3 p7 g" a8 X" `& W {6 \& v! k3 [, g! K- x: p
strcpy ( pszName, szConfigFilename ); * Z8 v" u# V6 S8 m% } return true;, F- c- ]3 J+ W5 U( ?
}7 `& t/ G% _' k
} </PRE> 这是一段很健壮的代码,然而在遇到 DBCS 字符时它将会出错。让我们来看看为什么。假设一个日本用户使用了你的程序,把它安装在 C:\<IMG src="http://www.vckbase.com/document/journal/vckbase30/images/youkoso.gif" border=0>。下面是这个名字在内存中的存储形式:' x( R: g# |7 e- m
" k9 V9 t3 i3 {- \
<TABLE>1 M+ |; a) Q) F2 n8 H
* \" R& _8 u( [# z3 f/ H5 {- o! k
<TR> 3 d% ?3 i4 R; U5 \, o+ }" y9 E/ @& M2 ?<TD align=middle width="12%">43</TD>: F, i. r% q5 _0 N6 y7 K* U& e$ z
<TD align=middle width="12%"><FONT color=#990000>3A</FONT></TD>+ h: x- y- I0 l9 o' C. ?
<TD align=middle width="12%"><FONT color=#0000ff>5C</FONT></TD> 4 P( T: x7 P- y6 c, V% P8 a<TD align=middle width="12%">83 88</TD> 9 w1 u0 j8 f$ O$ o# s<TD align=middle width="13%">83 45</TD> ) f9 r9 j# j8 D0 R5 ?( ]! O9 y) T. Z<TD align=middle width="13%">83 52</TD>& }2 ?0 `5 \" t, R- Z
<TD align=middle width="13%">83 <FONT color=#0000ff>5C</FONT></TD> 8 Y$ a7 H3 `# T' O( K; w<TD align=middle width="13%">00</TD></TR>- @/ d1 D$ z k, Z. V* J% H
<TR> $ s" A! P. C' v! K<TD align=middle width="12%"> </TD>1 r8 b) o R' g3 r+ B {* W
<TD align=middle width="12%"> </TD>: f- R7 i3 ^% G
<TD align=middle width="12%"> </TD>. W! s0 ~8 m: k Q- U* T$ s5 `
<TD align=middle width="12%"><FONT color=#990000>LB TB </FONT></TD> 9 L( f9 V& F9 B8 r5 t<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD>: f2 B# K9 q! y9 n
<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD>$ K0 }- @8 b- G2 a4 w
<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD> 7 g0 t5 m: K' g<TD align=middle width="13%"> </TD></TR>' L2 \$ C* H+ a) h: U" ~% i0 h
<TR> # Y/ O% _% z1 y- e! Q( ~<TD align=middle width="12%">C</TD>2 a& q3 T. w2 t5 F8 ~8 y5 _
<TD align=middle width="12%">:</TD> # `; \2 y5 Z! p<TD align=middle width="12%">\</TD>" u6 ^8 S" h# b" o5 s
<TD align=middle width="12%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/yo.gif" border=0></TD>. V" y" C R9 F1 k+ \4 t
<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/u.gif" border=0></TD> : o, b# S2 y0 F8 _- @<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/ko.gif" border=0></TD> 5 i- Z8 x1 F- G( k; F<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/so.gif" border=0></TD>" x: m1 [5 K5 }; I
<TD align=middle width="13%">EOS</TD></TR></TABLE>& e* g. G: q1 y1 L8 @0 x: M$ d$ L t
<> 当使用 GetConfigFileName() 检查尾部的''\\''时,它寻找安装目录名中最后的非0字节,看它是等于''\\''的,所以没有重新增加一个''\\''。结果是代码返回了错误的文件名。 , P; _: Y7 m5 c 哪里出错了呢?看看上面两个被用蓝色高量显示的字节。斜杠''\\''的值是0x5c。'' ''的值是83 5c。上面的代码错误的读取了一个 trail byte,把它当作了一个字符。; d2 ^% R% }5 _& P- F
正确的后向遍历方法是使用能够识别DBCS字符的函数,使指针移动正确的字节数。下面是正确的代码。(指针移动的地方用红色标明) </P><RE>bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )+ h& J7 }. o: v7 A/ T
{! u2 d# t3 p" z+ |2 X
char szConfigFilename[MAX_PATH];4 ~0 W# e' W- D! ^' a1 M& Q8 o
- T. ]) C$ Y/ |' p' P2 |
// Read install dir from registry... we''ll assume it succeeds.2 U, j6 l! b& \& S
; q/ X% \# b- F% ]' D // Add on a backslash if it wasn''t present in the registry value. - r, U( Z3 q4 }7 f" J // First, get a pointer to the terminating zero. y! @: e% V. K1 U( m- ^3 D+ [6 h
char* pLastChar = _mbschr ( szConfigFilename, ''\0'' );+ E. r, E. C, e
' s3 [- @4 Q, f) }9 e4 e/ d // Now move it back one double-byte character. & O( x* E$ N* V4 B <FONT color=#ff0000> pLastChar = CharPrev ( szConfigFilename, pLastChar );</FONT> & l/ s3 w$ y! H9 j& { q7 L % T. i- s( i' { a( i/ t
if ( *pLastChar != ''\\'' )& f- n0 x, Y+ f6 b9 c
_mbscat ( szConfigFilename, "\\" );, W( N( ]2 o t# {, |
. C( S+ u+ |9 Z7 ?* Y+ T& u
// Add on the name of the config file.. ^1 J4 U! N, J( x
_mbscat ( szConfigFilename, "config.bin" );* L) l' z1 |. x7 V0 z5 b* d
2 L# ?: l3 w, C6 P |
// If the caller''s buffer is big enough, return the filename.' t, o: C+ N1 t( Y/ w
if ( _mbslen ( szInstallDir ) >= nBuffSize ) + N7 |4 K7 a: S Z0 G6 {1 R( F8 a, ` return false; * p, R# g) @. G6 a o5 h else 1 R3 l6 I5 y& U( I- X& [1 H- X) ~1 K { 4 p6 L8 U1 u! T- n _mbscpy ( pszName, szConfigFilename );) s! Z3 ?* y" i! b0 B9 F
return true; v( p+ _* l# n# S& j7 Z3 ]. b P } 8 V8 e- X+ ] v! m1 u1 B& I}' W. |1 }. `4 l
</PRE> 上面的函数使用CharPrev() API使pLastChar向后移动一个字符,这个字符可能是两个字节长。在这个版本里,if条件正常工作,因为lead byte永远不会等于0x5c。 7 v8 a9 P- b2 t/ f" O+ y 让我们来想象一个违背规则1的场合。例如,你可能要检测一个用户输入的文件名是否多次出现了'':''。如果,你使用++操作来遍历字符串,而不是使用CharNext(),你可能会发出不正确的错误警告如果恰巧有一个trail byte它的值的等于'':''的值。 T0 b) c9 U+ t* h
与规则2相关的关于字符串索引的规则:<RE>2a. 永远不要使用减法去得到一个字符串的索引。</PRE> 7 z+ Z. A" y4 {<>违背这条规则的代码和违背规则2的代码很相似。例如,</P><RE>char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];</PRE>$ l6 y. K7 c7 y: x. e1 W: c0 T
<>这和向后移动一个指针是同样的效果。& I# ^. N1 W) R' {) P$ x0 Z
! o) R% b7 B6 ]' z1 \<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 回到关于str***()和_mbs***()的区别</B> + r; ~3 Z3 \0 ^ 1 E3 ^& M! M" {( G4 e 现在,我们应该很清楚为什么_mbs***()函数是必需的。Str***()函数根本不考虑DBCS字符,而_mbs***()考虑。如果,你调用strrchr("C:\\ ", ''\\''),返回结果可能是错误的,然而_mbsrchr()将会认出最后的双字节字符,返回一个指向真的''\\''的指针。* t! {4 d( c- M
关于字符串函数的最后一点:str***()和_mbs***()函数认为字符串的长度都是以char来计算的。所以,如果一个字符串包含3个双字节字符,_mbslen()将会返回6。Unicode函数返回的长度是按wchar_t来计算的。例如,wcslen(L"Bob")返回3。$ W/ M0 {5 _# u2 d4 F- A
8 i0 w, {: W& \ O. F5 J
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> Win32 API中的MBCS和Unicode</B>% @0 d6 ?) s# ^% x8 m) _3 s