通过指针枚举 NS 字符



如何通过从中提取每个 unichar 来枚举 NSString?我可以使用字符索引,但这比通过递增的 unichar* 来做慢。我在 Apple 的文档中没有看到任何不需要将字符串复制到第二个缓冲区的内容。

像这样的东西将是理想的:

for (unichar c in string) { ... }

unichar* ptr = (unichar*)string;

您可以通过先将其转换为IMP形式来加快-characterAtIndex:速度:

NSString *str = @"This is a test";
NSUInteger len = [str length]; // only calling [str length] once speeds up the process as well
SEL sel = @selector(characterAtIndex:);
// using typeof to save my fingers from typing more
unichar (*charAtIdx)(id, SEL, NSUInteger) = (typeof(charAtIdx)) [str methodForSelector:sel];
for (int i = 0; i < len; i++) {
    unichar c = charAtIdx(str, sel, i);
    // do something with C
    NSLog(@"%C", c);
}  

编辑:似乎CFString引用包含以下方法:

const UniChar *CFStringGetCharactersPtr(CFStringRef theString);

这意味着您可以执行以下操作:

const unichar *chars = CFStringGetCharactersPtr((__bridge CFStringRef) theString);
while (*chars)
{
    // do something with *chars
    chars++;
}

如果您不想分配内存来应对缓冲区,这是要走的路。

您唯一的选择是将字符复制到新的缓冲区中。这是因为 NSString 类不保证有可以使用的内部缓冲区。执行此操作的最佳方法是使用 getCharacters:range: 方法。

NSUInteger i, length = [string length];
unichar *buffer = malloc(sizeof(unichar) * length);
NSRange range = {0,length};
[string getCharacters:buffer range:range];
for(i = 0; i < length; ++i) {
    unichar c = buffer[i];
}

如果您使用的是可能非常长的字符串,则最好分配一个固定大小的缓冲区并以块的形式枚举字符串(这实际上是枚举工作的速度)。

我创建了一个块式枚举方法,该方法使用带有固定大小缓冲区的getCharacters:range:,按照 ughoavgfhw 在他的答案中的建议。 它避免了CFStringGetCharactersPtr返回 null 的情况,并且不必对大型缓冲区进行 malloc。 您可以将其放入 NSString 类别中,或者根据需要对其进行修改以将字符串作为参数。

-(void)enumerateCharactersWithBlock:(void (^)(unichar, NSUInteger, BOOL *))block
{
    const NSInteger bufferSize = 16;
    const NSInteger length = [self length];
    unichar buffer[bufferSize];
    NSInteger bufferLoops = (length - 1) / bufferSize + 1;
    BOOL stop = NO;
    for (int i = 0; i < bufferLoops; i++) {
        NSInteger bufferOffset = i * bufferSize;
        NSInteger charsInBuffer = MIN(length - bufferOffset, bufferSize);
        [self getCharacters:buffer range:NSMakeRange(bufferOffset, charsInBuffer)];
        for (int j = 0; j < charsInBuffer; j++) {
            block(buffer[j], j + bufferOffset, &stop);
            if (stop) {
                return;
            }
        }
    }
}

在我所知道的NSString中枚举字符的最快可靠方法是使用隐藏在众目睽睽之下的这个相对鲜为人知的核心基础宝石(CFString.h)。

NSString *string = <#initialize your string#>
NSUInteger stringLength = string.length;
CFStringInlineBuffer buf;
CFStringInitInlineBuffer((__bridge CFStringRef) string, &buf, (CFRange) { 0, stringLength });
for (NSUInteger charIndex = 0; charIndex < stringLength; charIndex++) {
    unichar c = CFStringGetCharacterFromInlineBuffer(&buf, charIndex);
}

如果您查看这些内联函数的源代码,CFStringInitInlineBuffer()CFStringGetCharacterFromInlineBuffer() ,您会发现它们处理所有令人讨厌的细节,例如CFStringGetCharactersPtr()返回NULLCFStringGetCStringPtr()返回NULL、默认为较慢的CFStringGetCharacters()并将字符缓存在 C 数组中以实现最快的访问。这个 API 确实值得更多宣传。

需要注意的是,如果以非零偏移量初始化CFStringInlineBuffer,则应将相对字符索引传递给 CFStringInlineBuffer() ,如标题注释中所述:

接下来的两个函数允许快速访问字符串的内容,假设您正在执行顺序或本地化访问。若要使用,请使用CFStringInlineBuffer(例如在堆栈上)和字符串中的范围调用 CFStringInitInlineBuffer() 要查看。然后根据需要多次调用CFStringGetCharacterFromInlineBuffer()并在该范围内使用索引(相对于该范围的开始)。这些是内联函数,最终只会偶尔调用CFString来填充缓冲区。 如果指定了原始范围之外的位置,则CFStringGetCharacterFromInlineBuffer()返回 0。

我认为你不能这样做。 NSString 是众多类的抽象接口,这些类不保证字符数据的内部存储,因此完全有可能没有字符数组可以获取指针。

如果您的问题中提到的两个选项都不适合您的应用程序,我建议您为此目的创建自己的字符串类,或者使用原始的malloc'ed unichar数组而不是字符串对象。

这将起作用:

char *s = [string UTF8String];
for (char *t = s; *t; t++)
  /* use as */ *t;

[编辑] 如果你真的需要 unicode 字符,那么你别无选择,只能使用长度字符索引。 从文档中:

NSString 类有两个基元方法 — length 和 characterAtIndex: — 它们为其接口中的所有其他方法提供了基础。length 方法返回字符串中 Unicode 字符的总数。characterAtIndex:按索引访问字符串中的每个字符,索引值从 0 开始。

所以你的代码将是:

  for (int index = 0; index < string.length; index++)
    { 
      unichar c = [string characterAtIndex: index];
      /* ... */
    }

[编辑2]

另外,不要忘记NSString是与CFString的"免费桥接",因此所有非Objective-C,直接的C代码接口功能都是可用的。 相关的是CFStringGetCharacterAtIndex

最新更新