Как посчитать количество многобайтовых символов?
Я хотел бы получить 5 вместо 10 для следующей программы. Кто-нибудь знает, как исправить код для подсчета количества многобайтовых символов? Благодарю.
/* vim: set noexpandtab tabstop=2 shiftwidth=2 softtabstop=-1 fileencoding=utf-8: */
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <locale.h>
size_t nchars(const char *s) {
size_t charlen, chars;
mbstate_t mbs;
chars = 0;
memset(&mbs, 0, sizeof(mbs));
while (
(charlen = mbrlen(s, MB_CUR_MAX, &mbs)) != 0
&& charlen != (size_t)-1
&& charlen != (size_t)-2
) {
s += charlen;
chars++;
}
return (chars);
}
int main() {
setlocale(LC_CTYPE, "en_US.utf8");
char * text = "öçşğü";
printf("%zu\n", nchars (text));
return 0;
}
$ ./main.exe
10
1 ответ
Вторичная проблема: вы должны инициализировать объект типа mbstate_t
через mbsinit
функция, а не memcpy
, Все байты-ноль mbsinit
не гарантируется представление начального состояния сдвига или даже любого допустимого состояния сдвига.
Основная проблема с вашим кодом заключается в том, что он анализирует строковый литерал, представление которого определяется во время компиляции на основе фактической кодировки этих символов в исходном файле, их представления в исходном наборе символов компилятора и набор символов выполнения, выбранный компилятором. Вы не можете выбрать LC_CTYPE
произвольно - оно должно соответствовать данным, чтобы функции преобразования в mb работали как задумано.
C не определяет механизм для программы, чтобы определить язык, чей LC_TYPE
соответствует набору символов выполнения и даже не требует наличия такой локали. Документация вашего компилятора должна описывать сопоставление между исходными символами и исполняющими символами, однако, возможно, с точки зрения локали или хорошо известной кодировки, и она может даже описывать способ указать это. В документации вашего компилятора также может быть указан способ указать кодировку, которую он должен использовать для исходных файлов.
Кроме того, у вас есть дополнительная потенциальная проблема с Юникодом, которая может не совпадать между тем, что вы, человек, считаете "персонажем", и символами Юникода, с которыми он представлен. Как правило, это касается символов с диакритическими знаками, таких как акценты. Многие из наиболее часто используемых из них имеют односимвольное "составное" представление, но также могут быть представлены в виде последовательности базового символа плюс один или несколько комбинирующих символов.
mbrlen()
вряд ли будет различать базовые и комбинируемые символы, поэтому даже без какой-либо путаницы в кодировании ваш наблюдаемый результат может возникнуть из-за того, что символы представлены в разложенном виде в исходных файлах или преобразованы в эту форму компилятором.
Суть в том, что ваша программа зависит от характеристик среды и реализации, которые не указаны в стандарте, поэтому она может вести себя по-разному в разных реализациях, что, как кажется, и есть наблюдение. Ваше конкретное наблюдение может возникнуть, например, из исходного файла, кодируемого в UTF-8, компилятор предполагает, что он будет закодирован в однобайтовой кодировке, такой как ISO-8859-1, а компилятор использует UTF-8. для его набора символов исполнения.
Ваш подход может работать без изменений, если вы убедитесь, что компилятор интерпретирует исходный файл в соответствии с фактической кодировкой этого файла и использует UTF-8 в качестве своего набора символов выполнения. В качестве альтернативы, в C11 или более поздней версии вы можете убедиться, что кодированием этой конкретной строки является UTF-8, используя литерал UTF-8, например, так:
char * text = u8"öçşğü";
Это заботится только о кодировании на стороне выполнения, как бы то ни было. Вам все еще нужно сопоставить кодировку исходного файла с фактической кодировкой, ожидаемой компилятором, и вы все равно можете испытывать различия между предварительно составленными и разложенными символами.