Есть ли простой переносимый способ определения порядка двух символов в 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);
}