Есть ли простой переносимый способ определения порядка двух символов в C?
В соответствии со стандартом:
Значения членов набора символов выполнения определены как реализация.
(ISO/IEC 9899: 1999 5.2.1/1)
Далее в стандарте:
... значение каждого символа после 0
в приведенном выше списке десятичных цифр должно быть больше, чем значение предыдущего.
(ISO/IEC 9899: 1999 5.2.1/3)
Похоже, что стандарт требует, чтобы набор символов выполнения включал 26 прописных и 26 строчных букв латинского алфавита, но я не вижу необходимости в том, чтобы эти символы были упорядочены каким-либо образом. Я вижу только порядок для десятичных цифр.
Это, по-видимому, означает, что, строго говоря, нет гарантии, что 'a' < 'b'
. Теперь буквы алфавита упорядочены в каждом из ASCII, UTF-8 и EBCDIC. Но для ASCII и UTF-8 имеем 'A' < 'a'
, а для EBCDIC - 'A' < 'a'
.
Хорошо бы иметь функцию в ctype.h
, которая сравнивает буквенные символы переносимо. Коротко это или что-то подобное, мне кажется, что нужно искать в локали, чтобы найти значение CODESET
и действовать соответственно, но это не кажется простым.
Моя кишка говорит мне, что это почти никогда не проблема; для большинства случаев алфавитные символы могут обрабатываться путем преобразования в нижний регистр, поскольку для наиболее часто используемых наборов символов буквы находятся в порядке.
Вопрос: учитывая два символа
char c1;
char c2;
существует простой, переносимый способ определить, предшествует ли c1
c2
в алфавитном порядке? Или мы предполагаем, что строчные и прописные символы всегда встречаются последовательно, даже если это не гарантируется стандартом?
Чтобы прояснить любую путаницу, меня действительно интересуют 52 буквы латинского алфавита, которые гарантированы стандартом в наборе символов исполнения. Я понимаю, что другие наборы букв важны, но кажется, что мы даже не можем знать о упорядочении этого небольшого подмножества букв.
Изменить
Я думаю, что мне нужно уточнить немного больше. Проблема, как я вижу, заключается в том, что мы обычно думаем о 26 строчных буквах латинского алфавита, которые заказываются. Я хотел бы иметь возможность утверждать, что "a" предшествует "b", и мы имеем удобный способ выразить это в коде как 'a' < 'b'
, когда мы даем интегральные значения "a" и "b". Но стандарт не дает никаких заверений в том, что приведенный выше код будет оцениваться как ожидалось. Почему нет? Стандарт действительно гарантирует это поведение для цифр 0-9, и это кажется разумным. Если я хочу определить, предшествует ли одна буква char другой, скажем, для сортировки, и если я хочу, чтобы этот код был действительно портативным, кажется, что стандарт не предлагает никакой помощи. Теперь я должен полагаться на соглашение о том, что ASCII, UTF-8, EBCDIC и т.д. Приняли, что 'a' < 'b'
должно быть правдой. Но это не очень переносимо, если только используемые наборы символов не полагаются на это соглашение; это может быть правдой.
Этот вопрос возник для меня в другом вопросе: Проверьте, есть ли письмо до или после другого письма в C. Здесь несколько человек предложили вам определить порядок двух букв, хранящихся в char
, используя неравенства. Но один комментатор отметил, что это поведение не гарантируется стандартом.
Ответы
Ответ 1
Для A-Z,a-z
нечувствительным к регистру (и используя составные литералы):
char ch = foo();
az_rank = strtol((char []){ch, 0}, NULL, 36);
При 2 char
, которые известны как A-Z, a-z, но могут быть ASCII или EBCDIC.
int compare2alpha(char c1, char c2) {
int mask = 'A' ^ 'a'; // Only 1 bit is different between upper/lower
return (c1 | mask) - (c2 | mask);
}
В качестве альтернативы, если ограничение ограничено 256, отличное от char
, можно использовать справочную таблицу, которая отображает char
в ее ранг. Конечно, таблица зависит от платформы.
Ответ 2
strcoll предназначен для этой цели. Просто установите две строки по одному символу. (обычно вы хотите сравнить строки, а не символы).
Ответ 3
Существуют исторически используемые коды, которые не просто упорядочивают алфавит. Бодо, например, ставит гласные перед согласными, поэтому "A" < 'B', но 'U' < "B".
Существуют также такие коды, как EBCDIC, которые упорядочены, но с пробелами. Таким образом, в EBCDIC "I" < 'J', но 'I' + 1!= 'J'.
Ответ 4
Возможно, вы могли бы просто создать таблицу для символов, которую стандартные гарантии будут содержать номера символов ASCII. Например.
#include <limits.h>
static char mytable[] = {
['a'] = 0x61,
['b'] = 0x62,
// ...
['A'] = 0x41,
['B'] = 0x42,
// ...
};
Компилятор будет отображать все символы в текущем наборе символов (который может быть любым сумасшедшим набором символов) в ASCII-коды, а символы, которые не гарантируются для существования, будут сопоставлены с нулем. Затем вы можете использовать эту таблицу для упорядочения при необходимости.
Как вы сказали,
char c1;
char c2;
Возможно, переносимость будет проверена в алфавитном порядке, проверив
(c1 < sizeof(mytable) && c2 < sizeof(mytable) ? mytable[c1] < mytable[c2] : 0)
Я действительно использовал это в исследовательском проекте, который работает на ASCII и EBCDIC для предсказуемого упорядочения, но достаточно портативен для работы с любым набором символов. Изменить. Я фактически разрешил размер таблицы пустым, чтобы он вычислил необходимый минимум из-за DeathStation 9000, на котором байт может иметь 32 бита и, следовательно, CHAR_MAX
может быть до 4294967295 или выше.
Ответ 5
С C11 код может использовать _Static_assert()
для обеспечения в времени компиляции, чтобы символы имели желаемый порядок.
Преимущество такого подхода состоит в том, что, поскольку подавляющие кодировки символов все готовы соответствовать желаемому требованию A-Z, если новая или эзотерическая платформа использует что-то другое, может потребоваться кодирование или настройка, которые не предсказуемы. Этот лучший код может в этом случае не скомпилироваться.
Пример использования
// Sample case insensitive string sort routine that insures
// 1) 'A' < 'B' < 'C' < ... < 'Z'
// 2) 'a' < 'b' < 'c' < ... < 'z'
int compare_string_case_insensitive(const void *a, const void *b) {
_Static_assert('A' < 'B', "A-Z order unexpected");
_Static_assert('B' < 'C', "A-Z order unexpected");
_Static_assert('C' < 'D', "A-Z order unexpected");
// Other 21 _Static_assert() omitted for brevity
_Static_assert('Y' < 'Z', "A-Z order unexpected");
_Static_assert('a' < 'b', "a-z order unexpected");
_Static_assert('b' < 'c', "a-z order unexpected");
_Static_assert('c' < 'd', "a-z order unexpected");
// Other 21 _Static_assert() omitted for brevity
_Static_assert('y' < 'z', "a-z order unexpected");
const char *sa = (const char *)a;
const char *sb = (const char *)b;
int cha, chb;
do {
cha = toupper((unsigned char) *sa++);
chb = toupper((unsigned char) *sb++);
} while (cha && cha == chb);
return (cha > chb) - (cha < chb);
}