在每个应用里我们都大量使用字符串。下面我们将快速看看一些常见的操作字符串的方法,过一遍常见操作的最佳实践。
字符串的比较、搜索和排序
排序和比较字符串比第一眼看上去要复杂得多。不只是因为字符串可以包含代理对(surrogate pairs )(详见 Ole 写的这篇关于 Unicode 的文章) ,而且比较还与字符串的本地化相关。在某些极端情况下相当棘手。
苹果文档中 String Programming Guide 里有一节叫做 “字符与字形集群(Characters and Grapheme Clusters)”,里面提到一些陷阱。例如对于排序来说,一些欧洲语言将序列“ch”当作单个字母。在一些语言里,“ä”被认为等同于 ‘a’ ,而在其它语言里它却被排在 ‘z’ 后面。
而 NSString 有一些方法来帮助我们处理这种复杂性。首先看下面的方法:
1 2 3 4 |
- (NSComparisonResult)compare:(NSString *)aString options:(NSStringCompareOptions)mask range:(NSRange)range locale:(id)locale |
它带给我们充分的灵活性。另外,还有很多“便捷函数”都使用了这个方法。
与比较有关的可用参数如下:
1 2 3 4 5 6 |
NSCaseInsensitiveSearch NSLiteralSearch NSNumericSearch NSDiacriticInsensitiveSearch NSWidthInsensitiveSearch NSForcedOrderingSearch |
它们都可以用逻辑或运算组合在一起。
NSCaseInsensitiveSearch :“A”等同于“a”,然而在某些地方还有更复杂的情况。例如,在德国,“ß” 和 “SS”是等价的。
NSLiteralSearch :Unicode 的点对 Unicode 点比较。它只在所有字符都用相同的方式组成的情况下才会返回相等。LATIN CAPITAL LETTER A 加上 COMBINING RING ABOVE 并不等同于 LATIN CAPITAL LETTER A WITH RING ABOVE.
译注:这个要解释一下,首先,每一个Unicode都是有官方名字的!LATIN CAPITAL LETTER A是一个大写“A”,COMBINING RING ABOVE是一个 ̊,LATIN CAPITAL LETTER A WITH RING ABOVE,这是Å前两者的组合不等同于后者。
NSNumericSearch:它对字符串里的数字排序,所以 “Section 9” < “Section 20” < “Section 100.”
NSDiacriticInsensitiveSearch : “A” 等同于 “Å” 等同于 “Ä.”
NSWidthInsensitiveSearch : 一些东亚文字(平假名 和 片假名)有全宽与半宽两种形式。
很值得一提的是 – (NSComparisonResult)localizedStandardCompare: ,它排序的方式和 Finder 一样。它对应的选项是 NSCaseInsensitiveSearch 、 NSNumericSearch 、NSWidthInsensitiveSearch 以及 NSForcedOrderingSearch 。如果我们要在UI上显示一个文件列表,用它就最合适不过了。
大小写不敏感的比较和音调符号不敏感的比较都是相对复杂和昂贵的操作。如果我们需要比较很多次字符串那这就会成为一个性能上的瓶颈(例如对一个大的数据集进行排序),一个常见的解决方法是同时存储原始字符串和折叠字符串。例如,我们的 Contact 类有一个正常的 name 属性,在内部它还有一个foldedName 属性,它将自动在 name变化时更新。那么我们就可以使用 NSLiteralSearch 来比较 name 的折叠版本。 NSString 有一个方法来创建折叠版本:
1 2 |
- (NSString *)stringByFoldingWithOptions:(NSStringCompareOptions)options locale:(NSLocale *)locale |
搜索
要在一个字符串中搜索子字符串,最灵活性的方法是:
1 2 3 4 |
- (NSRange)rangeOfString:(NSString *)aString options:(NSStringCompareOptions)mask range:(NSRange)searchRange locale:(NSLocale *)locale |
同时,还有一些“便捷方法”,它们在最终都会调用上面这个方法,我们可以传入上面列出的参数,以及以下这些额外的参数:
1 2 3 |
NSBackwardsSearch NSAnchoredSearch NSRegularExpressionSearch |
NSBackwardsSearch :在字符串的末尾开始反向搜索。
NSAnchoredSearch : 只考虑搜索的起始点(单独使用)或终止点(当与 NSBackwardsSearch 结合使用时)。这个方法可以用来检查前缀或者后缀,以及大小写不敏感(case-insensitive)或者音调不敏感(diacritic-insensitive)的比较。
NSRegularExpressionSearch :使用正则表达式搜索,要了解更多与使用正则表达式有关的信息,请关注 Chris’s 的 String Parsing 。
另外,还有一个方法:
1 2 3 |
- (NSRange)rangeOfCharacterFromSet:(NSCharacterSet *)aSet options:(NSStringCompareOptions)mask range:(NSRange)aRange |
与前面搜索字符串不同的是, 它只搜索给定字符集的第一个字符。即使只搜索一个字符,但如果由于此字符是由元字符组成的序列(composed character sequence),所以返回范围的长度也可能大于1。
大写与小写
一定不要使用 NSString 的 -uppercaseString 或者 -lowercaseString 的方法来处理 UI 显示的字符串,而应该使用 -uppercaseStringWithLocale 来代替, 比如:
1 2 |
NSString *name = @"Tómas"; cell.text = [name uppercaseStringWithLocale:[NSLocale currentLocale]]; |
格式化字符串
同C语言中的 sprintf 函数( ANSI C89 中的一个函数 )类似, Objective C 中的 NSString 类也有如下的3个方法:
1 2 3 |
-initWithFormat: -initWithFormat:arguments: +stringWithFormat: |
需要注意这些格式化方法都是 非本地化 的 。所以这些方法得到的字符串是不能直接拿来显示在用户界面上的。如果需要本地化,那我们需要使用下面这些方法:
1 2 3 |
-initWithFormat:locale: -initWithFormat:locale:arguments: +localizedStringWithFormat: |
Florian 有一篇关于 字符串的本地化 的文章更详细地讨论了这个问题。
printf(3)的man页面有关于它如何格式化字符串的全部细节。除了所谓的转换格式(它以%字符开始),格式化字符串会被逐字复制:
1 2 3 4 |
double a = 25812.8074434; float b = 376.730313461; NSString *s = [NSString stringWithFormat:@"%g :: %g", a, b]; // "25812.8 :: 376.73" |
我们格式化了两个浮点数。注意单精度浮点数和双精度浮点数共同了一个转换格式。
对象
除了来自 printf(3) 的转换规范,我们还可以使用 %@ 来输出一个对象。在对象描述那一节中有述,如果对象响应 -descriptionWithLocale: 方法,则调用它,否则调用 -description 。 %@ 被结果替换。
整数
使用整形数字时,有些需要注意的细节。首先,有符号数(d和i)和无符号数(o、u、x和X)分别有转换规范。需要使用者选择具体的类型。
如果我们使用的东西是 printf不知道的,我们必须要做类型转换。 NSUInteger 正是这样一个例子,它在64位和32位平台上是不一样的。下面的例子可以同时工作在32位和64位平台。
1 2 3 |
uint64_t p = 2305843009213693951; NSString *s = [NSString stringWithFormat:@"The ninth Mersenne prime is %llu", (unsigned long long) p]; // "The ninth Mersenne prime is 2305843009213693951" |
Modifier | d, i | o, u, x, X |
---|---|---|
hh | signed char | unsigned char |
h | short | unsigned short |
(none) | int | unsigned int |
l (ell) | long | unsigned long |
ll (ell ell) | long long | unsigned long long |
j | intmax_t | uintmax_t |
t | ptrdiff_t | |
z | size_t |
适用于整数的转换规则有:
1 2 3 4 |
int m = -150004021; uint n = 150004021U; NSString *s = [NSString stringWithFormat:@"d:%d i:%i o:%o u:%u x:%x X:%X", m, m, n, n, n, n]; // "d:-150004021 i:-150004021 o:1074160465 u:150004021 x:8f0e135 X:8F0E135" |
%d 和 %i 具有一样的功能,它们都打印出有符号十进制数。 %o 就较为晦涩了:它使用八进制表示。 %u 输出无符号十进制数——它是我们常用的。最后 %x 和 %X 使用十六进制表示——后者使用大写字母。
对于 x% 和 X% ,我们可以在 0x 前面添加 “#” 井字符前缀看,增加可读性。
我们可以传入特定参数,来设置最小字段宽度和最小数字位数(默认两者都是0),以及左/右对齐。请查看man页面获取详细信息。下面是一些例子:
1 2 3 4 5 6 |
int m = 42; NSString *s = [NSString stringWithFormat:@"'%4d' '%-4d' '%+4d' '%4.3d' '%04d'", m, m, m, m, m]; // ‘42’ ‘42 ’ ‘ +42’ ‘ 042’ ‘0042’ m = -42; NSString *s = [NSString stringWithFormat:@"'%4d' '%-4d' '%+4d' '%4.3d' '%04d'", m, m, m, m, m]; // ‘ -42’ ‘-42 ’ ‘ -42’ ‘-042’ ‘-042’ |
%p 可用于打印出指针——它和 %#x 相似但可同时在32位和64位平台上正常工作。
浮点数
关于浮点数的转换规则有8个:eEfFgGaA。但除了 %f 和 %g 外我们很少使用其它的。对于指数部分,小写的版本使用小写 e,大写的版本就使用大写 E。
通常 %g 是浮点数的全能转换符 ,它与 %f 的不同在下面的例子里显示得很清楚: