Я думаю, что важно, чтобы люди понимали, как обращаться с юникодом, поэтому я закончил тем, что написал ответ монстра, но в духе 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]);
}
Все еще со мной? Хорошо!
Текущий принятый ответ, кажется, путает байты с символами / буквами. Это распространенная проблема при обнаружении юникода, особенно на фоне Си. Строки в Objective-C представлены в виде символов Юникода (unichar
), которые намного больше байтов и не должны использоваться со стандартными функциями манипуляции со строками языка Си.
( Редактировать : Это не полная история! К моему великому стыду, я полностью забыл учесть составные символы, где «буква» состоит из нескольких кодовых точек Юникода Это дает вам ситуацию, когда вы можете иметь одну «букву», разрешающую несколько unichars, каждый из которых, в свою очередь, состоит из нескольких байтов. Ху, мальчик. Пожалуйста, обратитесь к этому замечательному ответу для получения подробной информации об этом.)
Правильный ответ на вопрос зависит от того, хотите ли вы выполнить итерацию по символам / буквам (в отличие от типа 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, шаг, на котором легко ошибиться и использовать строку -length
. Одна из основных причин, по которой это очень легко сделать неправильно, особенно для американского разработчика, заключается в том, что строка с буквами, попадающими в 7-битный спектр ASCII, будет иметь равных байтов и длину букв . Это связано с тем, что UTF8 кодирует 7-битные буквы ASCII одним байтом, поэтому простая тестовая строка и базовый текст на английском языке могут прекрасно работать.
Правильный способ сделать это - использовать метод -lengthOfBytesUsingEncoding:NSUTF8StringEncoding
(или другую кодировку), выделить буфер с этой длиной, , а затем преобразовать строку в ту же кодировку с помощью -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]);
}
Просто для того, чтобы понять, почему важно сохранять ясность, я покажу пример кода, который обрабатывает эту итерацию четырьмя различными способами, два неправильных и два правильных. Это код:
#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;
}
Запуск этого кода приведет к выводу следующего (с обрезанным символом NSLog), показывающего, насколько точно могут различаться представления байтов и букв (два последних выхода):
getCharacters:range: with char buffer
Byte 0: 1
Byte 1:
Byte 2: C
Byte 3:
Byte 4: :
strncpy with UTF8String
Byte 0: Ð
Byte 1: ±
Byte 2: Ñ
Byte 3:
Byte 4: Ð
getCharacters:range: with unichar buffer
Letter 0: б
Letter 1: у
Letter 2: к
Letter 3: в
Letter 4: а
strncpy with proper length
Byte 0: Ð
Byte 1: ±
Byte 2: Ñ
Byte 3:
Byte 4: Ð
Byte 5: º
Byte 6: Ð
Byte 7: ²
Byte 8: Ð
Byte 9: °