. I! {7 a' Q: l. M<IMG src="http://www.vckbase.com/document/image/paragraph.gif"> <B>正确的遍历和索引字符串</B> 3 C+ t( K/ N# K0 { ) G' o' ^9 @' _1 d 因为我们中大多数人都是用着SBCS字符串成长的,所以我们在遍历字符串时,常常使用指针的++-和-操作。我们也使用数组下标的表示形式来操作字符串中的字符。这两种方式是用于SBCS和Unicode字符串,因为它们中的字符有着相同的宽度,编译器能正确的返回我们需要的字符。 " N: Q9 j3 a( b3 \4 g 然而,当碰到DBCS字符串时,我们必须抛弃这些习惯。这里有使用指针遍历DBCS字符串时的两条规则。违背了这两条规则,你的程序就会存在DBCS有关的bugs。</P> 4 C" X. m' [) A" {$ }+ H3 X; I<DIR> : R$ l8 H. w8 A8 l1 J* R1 d/ i<LI>1.在前向遍历时,不要使用++操作,除非你每次都检查lead byte; % a- T1 {6 b: c9 c<LI>2.永远不要使用-操作进行后向遍历。 </LI></DIR>8 _) h8 i& Q' b( S8 x, y* Y3 v
<> 我们先来阐述规则2,因为找到一个违背它的真实的实例代码是很容易的。假设你有一个程序在你自己的目录里保存了一个设置文件,你把安装目录保存在注册表中。在运行时,你从注册表中读取安装目录,然后合成配置文件名,接着读取该文件。假设,你的安装目录是C:\Program Files\MyCoolApp,那么你合成的文件名应该是C:\Program Files\MyCoolApp\config.bin。当你进行测试时,你发现程序运行正常。3 d% D. w; v0 v. |
现在,想象你合成文件名的代码可能是这样的:</P><RE>bool GetConfigFileName ( char* pszName, size_t nBuffSize ) * E+ r. W; y+ Q/ E- x+ G: p. G{. }+ r# ~9 X. P0 L! g9 V4 ?
char szConfigFilename[MAX_PATH];' A! h5 Q0 s7 D( j( U
# p6 {5 z+ \* i. a9 B) O
// Read install dir from registry... we''ll assume it succeeds.% I) S: G+ a0 \, d1 L
# B" H: ]/ G9 a2 y. K* y1 G% g0 s // Add on a backslash if it wasn''t present in the registry value.( B+ r2 f T3 n0 U. G. L( n5 }
// First, get a pointer to the terminating zero.3 h! y9 H% Y: t6 L$ j
char* pLastChar = strchr ( szConfigFilename, ''\0'' ); 8 g& w" \$ o: f 6 O. H% `& O" I- O% ?/ v3 A! T% c+ M // Now move it back one character.5 ~2 R: y d D) I5 u8 ~! _# r8 p
pLastChar--; * E5 W0 W' r0 Q6 e; T2 z L
- n! W% h" r/ H3 | if ( *pLastChar != ''\\'' ): i( ^2 o7 \1 U1 P* l9 g
strcat ( szConfigFilename, "\\" ); : E7 N# x5 [ o! P " P6 L% X/ m+ ]- H$ S
// Add on the name of the config file.: l+ l+ O/ n- W
strcat ( szConfigFilename, "config.bin" );. `2 G M: v* |5 f9 {% @
' ]' D1 g/ Z5 w4 d- v // If the caller''s buffer is big enough, return the filename. 6 b# \/ T' M9 u" k. B( [4 @$ c+ W! z if ( strlen ( szConfigFilename ) >= nBuffSize ) 8 J, R! E' f# [" e& d return false;5 g. ~; I) o) }2 \! h
else " \( M7 z& F7 D {, e. \, y% l4 w+ W1 P2 f. o$ r. U
strcpy ( pszName, szConfigFilename ); ; W. E! ^& {* a/ N4 N8 K return true; ) ]/ o1 w' S) C } - E8 _: p, w3 X f1 U! H* v} </PRE> 这是一段很健壮的代码,然而在遇到 DBCS 字符时它将会出错。让我们来看看为什么。假设一个日本用户使用了你的程序,把它安装在 C:\<IMG src="http://www.vckbase.com/document/journal/vckbase30/images/youkoso.gif" border=0>。下面是这个名字在内存中的存储形式: ; o2 T# ?9 i: h & @8 i0 _; e. v0 N. P$ }<TABLE> , q5 X+ v: T! g' R, W4 y9 \0 } 3 a. C* y6 Y- u' A$ m<TR> , w# r. t1 w' g% H+ B1 R/ K! q<TD align=middle width="12%">43</TD> 7 n2 E: @+ B4 Q1 z<TD align=middle width="12%"><FONT color=#990000>3A</FONT></TD>5 }. m& l" D+ R0 J2 `# B; C$ m
<TD align=middle width="12%"><FONT color=#0000ff>5C</FONT></TD>, F& x2 E- p F: g- P ^8 H! Q# D
<TD align=middle width="12%">83 88</TD>! D6 n# d; W! V
<TD align=middle width="13%">83 45</TD>$ ?: b/ t* T" {7 O/ x; u+ P4 A, v& \
<TD align=middle width="13%">83 52</TD>; o/ N5 J; @3 B: v$ Y; o
<TD align=middle width="13%">83 <FONT color=#0000ff>5C</FONT></TD>& [! Q+ s, p: m0 c. \5 }' h" H
<TD align=middle width="13%">00</TD></TR> + p3 p6 U0 G- ?2 _+ X& [<TR>, P) o& @* M$ u
<TD align=middle width="12%"> </TD>$ g1 _; U7 c; y# F6 r
<TD align=middle width="12%"> </TD>7 K9 m/ Y' G+ [
<TD align=middle width="12%"> </TD> ( j; s% Q, L, y9 L; F<TD align=middle width="12%"><FONT color=#990000>LB TB </FONT></TD> 3 e* R9 c1 v8 w<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD>/ I1 c; {2 L- W- @
<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD>; C; {' s. ^& |
<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD> # Y9 W- G" P; Z9 w+ l' E% g<TD align=middle width="13%"> </TD></TR>- Q2 |: \% `8 l6 {1 Y) k: D- @
<TR> , z4 t6 r4 T+ H9 ~ J<TD align=middle width="12%">C</TD> 2 F9 m6 v( {0 E* y; C# H! Z% P<TD align=middle width="12%">:</TD>$ v' }* q" G# ^" H/ c$ ^
<TD align=middle width="12%">\</TD>3 p" C& K- ?1 q
<TD align=middle width="12%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/yo.gif" border=0></TD> ( `, ^6 t* q2 u; R7 |. J/ x2 Y5 r9 _0 }<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/u.gif" border=0></TD> 3 C; @7 P* o7 Q2 Q. N6 Z<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/ko.gif" border=0></TD>& j8 u7 V% _6 n5 u
<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/so.gif" border=0></TD>8 F: D1 I/ a* |! l* W% K
<TD align=middle width="13%">EOS</TD></TR></TABLE> 8 a7 K9 L! A/ A7 f1 s9 ~<> 当使用 GetConfigFileName() 检查尾部的''\\''时,它寻找安装目录名中最后的非0字节,看它是等于''\\''的,所以没有重新增加一个''\\''。结果是代码返回了错误的文件名。 7 b7 W# `6 J$ Q6 d 哪里出错了呢?看看上面两个被用蓝色高量显示的字节。斜杠''\\''的值是0x5c。'' ''的值是83 5c。上面的代码错误的读取了一个 trail byte,把它当作了一个字符。 1 a$ I8 i) b5 A, ] 正确的后向遍历方法是使用能够识别DBCS字符的函数,使指针移动正确的字节数。下面是正确的代码。(指针移动的地方用红色标明) </P><RE>bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )3 f$ f5 |4 V: [: Q" o
{ % a4 ^7 q. k5 P/ k5 b* c char szConfigFilename[MAX_PATH];% F2 Z2 F9 J; U6 B
- e+ L4 i8 `1 B2 ?
// Read install dir from registry... we''ll assume it succeeds. 1 M8 ?2 w* `. [" r$ s: d, } ! s9 ?: Z. ~- w+ g, L% c // Add on a backslash if it wasn''t present in the registry value. * ~* f; R) k: v // First, get a pointer to the terminating zero. ) D$ J4 j% c5 B& o, f0 g3 d y char* pLastChar = _mbschr ( szConfigFilename, ''\0'' );7 c7 M& m6 L. e; D p# _" W
1 k2 D! n- `- ~& @' f) |
// Now move it back one double-byte character. . D9 P7 c- @( ]1 }& f7 F <FONT color=#ff0000> pLastChar = CharPrev ( szConfigFilename, pLastChar );</FONT> 8 u$ b& H4 I/ T * \3 n$ r" h3 o7 m. n# `/ I0 x V if ( *pLastChar != ''\\'' ) ' u$ ]1 L4 u D: C _mbscat ( szConfigFilename, "\\" ); # @; Z3 O( J s* V9 _ ' @, R% _* O8 E& D" |/ ?8 i
// Add on the name of the config file. , C& T# P. E+ Z; D- l# P _mbscat ( szConfigFilename, "config.bin" );. g& d+ x' v7 T5 {7 P( o
b4 Q) y9 i: D z
// If the caller''s buffer is big enough, return the filename.' z7 d2 G( @; u$ e: r5 j+ s
if ( _mbslen ( szInstallDir ) >= nBuffSize )5 ?- x8 Z' ]3 k$ V$ ~
return false;6 ~6 ?% |3 l# ~
else 9 B* c, h: ^! i) V { ( Q1 F" z; s: A7 K- V _mbscpy ( pszName, szConfigFilename ); . o/ O7 X1 A* A$ i return true; * O; i1 X: m" Z4 }4 R }, |+ p" e& V( S1 n8 Q$ x9 b
}9 b. R+ ?. i3 P2 |/ B% I8 ]
</PRE> 上面的函数使用CharPrev() API使pLastChar向后移动一个字符,这个字符可能是两个字节长。在这个版本里,if条件正常工作,因为lead byte永远不会等于0x5c。 , a6 r2 \" J4 d 让我们来想象一个违背规则1的场合。例如,你可能要检测一个用户输入的文件名是否多次出现了'':''。如果,你使用++操作来遍历字符串,而不是使用CharNext(),你可能会发出不正确的错误警告如果恰巧有一个trail byte它的值的等于'':''的值。8 G& K/ w/ v7 b8 |( h
与规则2相关的关于字符串索引的规则:<RE>2a. 永远不要使用减法去得到一个字符串的索引。</PRE>7 e" i0 R% ]1 E! T
<>违背这条规则的代码和违背规则2的代码很相似。例如,</P><RE>char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];</PRE> * b: N! f& k s3 @<>这和向后移动一个指针是同样的效果。% S: i3 m% N" V# @$ g' E