迭代NSString中所有字符的最佳方法是什么?你想循环遍历字符串的长度并使用该方法。
[aNSString characterAtIndex:index];
或者您是否希望使用基于NSString的char缓冲区?
答案 0 :(得分:136)
我认为人们了解如何处理unicode非常重要,所以我最终写了一个怪物答案,但本着 tl; dr 的精神,我将从一个片段开始应该工作正常。如果您想了解详细信息(您应该知道!),请在摘录后继续阅读。
NSUInteger len = [str length];
unichar buffer[len+1];
[str getCharacters:buffer range:NSMakeRange(0, len)];
NSLog(@"getCharacters:range: with unichar buffer");
for(int i = 0; i < len; i++) {
NSLog(@"%C", buffer[i]);
}
还在我身边吗?好!
当前接受的答案似乎是用字符/字母混淆字节。遇到unicode时这是一个常见问题,尤其是来自C背景。 Objective-C中的字符串表示为unicode字符(unichar
),它们比字节大得多,不应该与标准C字符串操作函数一起使用。
(编辑:这不是完整的故事!令我非常遗憾的是,我完全忘了考虑可组合字符,其中有一个&#34;字母&#34 ;由多个unicode代码点组成。这给你一个情况,你可以有一个&#34;字母&#34;解析为多个unichars,每个unichars又是多个字节。呜呜。请参考{{3}关于那个细节。)
问题的正确答案取决于您是否要迭代字符/字母(与类型char
不同)或字节字符串(类型char
实际意味着什么)。本着限制混淆的精神,我将从现在开始使用术语 byte 和 letter ,避免使用可能不明确的术语字符。
如果你想做前者并迭代字符串中的字母,你需要专门处理unichars(对不起,但我们现在将来,你不能再忽略它了) 。查找字母数量很容易,它是字符串的长度属性。一个示例代码段就是这样(与上面相同):
NSUInteger len = [str length];
unichar buffer[len+1];
[str getCharacters:buffer range:NSMakeRange(0, len)];
NSLog(@"getCharacters:range: with unichar buffer");
for(int i = 0; i < len; i++) {
NSLog(@"%C", buffer[i]);
}
另一方面,如果要迭代字符串中的字节,它会开始变得复杂,结果将完全取决于您选择使用的编码。合适的默认选择是UTF8,这就是我要展示的内容。
执行此操作时,您必须确定生成的UTF8字符串的字节数,这是一个很容易出错的步骤,并使用字符串&#39; s -length
。这很容易做错的一个主要原因,特别是对于美国开发人员而言,字母落入7位ASCII频谱的字符串将具有相等的字节和字母长度。这是因为UTF8使用单个字节编码7位ASCII字母,因此简单的测试字符串和基本的英文文本可能完全正常。
执行此操作的正确方法是使用方法-lengthOfBytesUsingEncoding:NSUTF8StringEncoding
(或其他编码),分配具有该长度的缓冲区,然后将字符串转换为使用{{{ 1}}并将其复制到该缓冲区中。示例代码:
-cStringUsingEncoding:
为了说明为什么保持原状很重要,我将展示以四种不同方式处理此迭代的示例代码,两个错误,两个正确。这是代码:
NSUInteger byteLength = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
char proper_c_buffer[byteLength+1];
strncpy(proper_c_buffer, [str cStringUsingEncoding:NSUTF8StringEncoding], byteLength);
NSLog(@"strncpy with proper length");
for(int i = 0; i < byteLength; i++) {
NSLog(@"%c", proper_c_buffer[i]);
}
运行此代码将输出以下内容(修剪出NSLog cruft),显示字节和字母表示的完全不同(最后两个输出):
#import <Foundation/Foundation.h>
int main() {
NSString *str = @"буква";
NSUInteger len = [str length];
// Try to store unicode letters in a char array. This will fail horribly
// because getCharacters:range: takes a unichar array and will probably
// overflow or do other terrible things. (the compiler will warn you here,
// but warnings get ignored)
char c_buffer[len+1];
[str getCharacters:c_buffer range:NSMakeRange(0, len)];
NSLog(@"getCharacters:range: with char buffer");
for(int i = 0; i < len; i++) {
NSLog(@"Byte %d: %c", i, c_buffer[i]);
}
// Copy the UTF string into a char array, but use the amount of letters
// as the buffer size, which will truncate many non-ASCII strings.
strncpy(c_buffer, [str UTF8String], len);
NSLog(@"strncpy with UTF8String");
for(int i = 0; i < len; i++) {
NSLog(@"Byte %d: %c", i, c_buffer[i]);
}
// Do It Right (tm) for accessing letters by making a unichar buffer with
// the proper letter length
unichar buffer[len+1];
[str getCharacters:buffer range:NSMakeRange(0, len)];
NSLog(@"getCharacters:range: with unichar buffer");
for(int i = 0; i < len; i++) {
NSLog(@"Letter %d: %C", i, buffer[i]);
}
// Do It Right (tm) for accessing bytes, by using the proper
// encoding-handling methods
NSUInteger byteLength = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
char proper_c_buffer[byteLength+1];
const char *utf8_buffer = [str cStringUsingEncoding:NSUTF8StringEncoding];
// We copy here because the documentation tells us the string can disappear
// under us and we should copy it. Just to be safe
strncpy(proper_c_buffer, utf8_buffer, byteLength);
NSLog(@"strncpy with proper length");
for(int i = 0; i < byteLength; i++) {
NSLog(@"Byte %d: %c", i, proper_c_buffer[i]);
}
return 0;
}
答案 1 :(得分:25)
如果你想迭代 字符串的字符,其中之一 你不应该做的事情是使用 要检索的
characterAtIndex:
方法 每个角色分开。这种方法 不适合重复访问。 相反,考虑取出 一次性使用的字符getCharacters:range:
方法和 直接迭代字节。如果要搜索字符串 具体的字符或子串,做 不要遍历角色之一 一个人。相反,使用更高级别 诸如
rangeOfString:
之类的方法,rangeOfCharacterFromSet:
,或substringWithRange:
,是 针对搜索NSString
进行了优化 字符。
请参阅此Stack Overflow answer on How to remove whitespace from right end of NSString
,了解如何让rangeOfCharacterFromSet:
迭代字符串的字符而不是自己完成。
答案 2 :(得分:25)
虽然Daniel的解决方案可能大部分时间都可以使用,但我认为解决方案取决于上下文。例如,我有一个拼写应用程序,需要迭代屏幕上显示的每个字符,这可能与它在内存中的表示方式不对应。对于用户提供的文本尤其如此。
在NSString上使用类似的东西:
- (void) dumpChars
{
NSMutableArray *chars = [NSMutableArray array];
NSUInteger len = [self length];
unichar buffer[len+1];
[self getCharacters: buffer range: NSMakeRange(0, len)];
for (int i=0; i<len; i++) {
[chars addObject: [NSString stringWithFormat: @"%C", buffer[i]]];
}
NSLog(@"%@ = %@", self, [chars componentsJoinedByString: @", "]);
}
像喂mañana这样的话可能会产生:
mañana = m, a, ñ, a, n, a
但它可以很容易地产生:
mañana = m, a, n, ̃, a, n, a
如果字符串是预先组合的unicode形式,则会生成前者,如果是分解形式,则生成前者。
您可能认为可以通过使用NSString的precomposedStringWithCanonicalMapping或precomposedStringWithCompatibilityMapping的结果来避免这种情况,但Apple并未在Technical Q&A 1225中警告这种情况。例如,即使在转换为预先组合的表单之后,像e̊gâds
这样的字符串(我完全组成)仍会生成以下内容。
e̊gâds = e, ̊, g, â, d, s
我的解决方案是使用NSString的enumerateSubstringsInRange传递NSStringEnumerationByComposedCharacterSequences作为枚举选项。将前面的示例重写为如下所示:
- (void) dumpSequences
{
NSMutableArray *chars = [NSMutableArray array];
[self enumerateSubstringsInRange: NSMakeRange(0, [self length]) options: NSStringEnumerationByComposedCharacterSequences
usingBlock: ^(NSString *inSubstring, NSRange inSubstringRange, NSRange inEnclosingRange, BOOL *outStop) {
[chars addObject: inSubstring];
}];
NSLog(@"%@ = %@", self, [chars componentsJoinedByString: @", "]);
}
如果我们提供此版本e̊gâds
,那么我们就会
e̊gâds = e̊, g, â, d, s
正如所料,这就是我想要的。
Characters and Grapheme Clusters上的文档部分也可能有助于解释其中的一些内容。
注意:看起来我使用的一些unicode字符串在格式化为代码时会跳闸。我使用的字符串是mañana和e̊gâds。
答案 3 :(得分:19)
我肯定会首先得到一个char缓冲区,然后迭代它。
NSString *someString = ...
unsigned int len = [someString length];
char buffer[len];
//This way:
strncpy(buffer, [someString UTF8String]);
//Or this way (preferred):
[someString getCharacters:buffer range:NSMakeRange(0, len)];
for(int i = 0; i < len; ++i) {
char current = buffer[i];
//do something with current...
}
答案 4 :(得分:2)
虽然从技术上讲你会获得单独的NSString值,但这里有另一种方法:
NSRange range = NSMakeRange(0, 1);
for (__unused int i = range.location; range.location < [starring length]; range.location++) {
NSLog(@"%@", [aNSString substringWithRange:range]);
}
( __ unused int i 位是使编译器警告静音所必需的。)
答案 5 :(得分:1)
尝试使用块的枚举字符串
创建NSString的类别
·H
@interface NSString (Category)
- (void)enumerateCharactersUsingBlock:(void (^)(NSString *character, NSInteger idx, bool *stop))block;
@end
的.m
@implementation NSString (Category)
- (void)enumerateCharactersUsingBlock:(void (^)(NSString *character, NSInteger idx, bool *stop))block
{
bool _stop = NO;
for(NSInteger i = 0; i < [self length] && !_stop; i++)
{
NSString *character = [self substringWithRange:NSMakeRange(i, 1)];
block(character, i, &_stop);
}
}
@end
示例
NSString *string = @"Hello World";
[string enumerateCharactersUsingBlock:^(NSString *character, NSInteger idx, bool *stop) {
NSLog(@"char %@, i: %li",character, (long)idx);
}];
答案 6 :(得分:1)
你不应该使用
NSUInteger len = [str length];
unichar buffer[len+1];
你应该使用内存分配
NSUInteger len = [str length];
unichar* buffer = (unichar*) malloc (len+1)*sizeof(unichar);
并最终使用
free(buffer);
为了避免记忆问题。
答案 7 :(得分:0)
这个问题的解决方案几乎没有什么不同,但是我认为这可能对某人有用。我想要的是实际上迭代为NSString中的实际unicode字符。因此,我找到了解决方案:
NSString * str = @"hello ";
NSRange range = NSMakeRange(0, str.length);
[str enumerateSubstringsInRange:range
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange,
NSRange enclosingRange, BOOL *stop)
{
NSLog(@"%@", substring);
}];