Заменить все строчные буквы на заглавные и наоборот, но только на кириллице Си

С латиницей всё довольно просто:

#include <stdio.h>

int main()
{
    char s[] = "Hello, WoRlldoQQ!!";

    for (int i = 0; s[i] != '\0'; i++) {
        if (s[i] >= 'a' && s[i] <= 'z')
            s[i] -= 'a' - 'A';
        else if (s[i] >= 'A' && s[i] <= 'Z')
            s[i] += 'a' - 'A';
    }

    printf("%s\n", s);
    return 0;
}

А вот Русские символы кодируются 2 байтами в UTF-8 и я не знаю как это можно реализовать, помогите)


Ответы (2 шт):

Автор решения: TEAMENT

В итоге использовал широкие строки

#include <stdio.h>
#include <locale.h>

int main()
{
    setlocale(LC_ALL, "Rus");
    wchar_t s[] = L"ПриВет МиР; HelLO WORld!!!";

    for (int i = 0; s[i] != '\0'; i++) {
        wchar_t c = s[i];

        if (c >= 'a' && c <= 'z')
            s[i] -= 'a' - 'A';

        else if (c >= 'A' && c <= 'Z')
            s[i] += 'a' - 'A';

        else if (c >= L'а' && c <= L'я')
            s[i] -= L'а' - L'А';
            
        else if (c >= L'А' && c <= L'Я')
            s[i] += L'а' - L'А';
    }

    printf("%S\n", s);
    return 0;
}
→ Ссылка
Автор решения: avp

Вот пример, как можно сделать оставаясь в UTF-8
(признаюсь, парой сравнений дело не ограничилось, но как и обещал в комментариях, совершенно без перекодировочных таблиц в памяти и без преобразований в unicode (т.е. wide chars)).

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ctype.h>

// 5 high bits of first utf-8 byte is key to the size of utf-8 symbol
// 1 1 1 1  1 1 1 1
// 1 1 1 1  1 1 1 1 // 16 ascii
// 1 1 1 1  1 1 1 1 // 8  cont
// 2 2 2 2  3 3 4 1 // 1 -- err

#define UTF8_LTAG (0x3a55ULL << 48)

#define UTF8_LEN(C) ({                          \
      int _x = ((C) >> 3) & 0x1f;               \
      int _l = (UTF8_LTAG >> (_x << 1)) & 3;    \
      _l + 1; })

void
change_utf8_rus_case (unsigned char *s)
{
  if (s[0] == 0xd0) {
    if ((s[1] > 0x8f && s[1] < 0xa0) || s[1] > 0xaf)
      s[1] ^= 0x20;
    else if ((s[1] > 0x9f && s[1] < 0xb0))
      s[0] ^= 1, s[1] ^= 0x20;
    else if (s[1] == 0x81)  // Ё
      s[0] = 0xd1, s[1] = 0x91;
  } else if (s[0] == 0xd1) {
    if (s[1] >= 0x80 && s[1] < 0x90)
      s[0] ^= 1, s[1] ^= 0x20;
    else if (s[1] == 0x91)
      s[0] = 0xd0, s[1] = 0x81;  // Ё
  }
}

int
main (int ac, char *av[])
{
  char str[1024] = "ПриВет МиР; ёЁ; HelLO WORld!!!";

  puts(str);

  int step;
  for (int i = 0; str[i]; i += step) {
    step = UTF8_LEN(str[i]);
    if (step == 1 && isalpha(str[i]))
      str[i] ^= 0x20;
    else if (step == 2)
      change_utf8_rus_case((unsigned char *)(str + i));
  }

  puts(str);

  return puts("End") == EOF;
}

Компилируем и запускаем

avp@avp-desktop:~/avp/hashcode$ g++ -O2 utfrus.cpp && ./a.out
ПриВет МиР; ёЁ; HelLO WORld!!!
пРИвЕТ мИр; Ёё; hELlo worLD!!!
End
avp@avp-desktop:~/avp/hashcode$

Еще одна реализация функции для изменения регистра русских букв использует перевод символа из кодировки utf-8 в 32-бит Unicode, но так же не использует никаких структур в памяти
(обе реализации функций смены регистра можно вызывать только для 2-х байтных символов utf-8).

void
chng_case_rus_utf (unsigned char *s)
{
  uint32_t v = (s[0] & 0x1f) << 6;
  v |= (s[1] & 0x3f);

  if (v > 0x40f && v < 0x450) {
    if (v < 0x430)
      v += 32;
    else
      v -= 32;
    s[0] = 0xc0 | ((v >> 6) & 0x1f);
    s[1] = 0x80 | (v & 0x3f);
  } else if (v == 0x401) // Ё
    s[0] = 0xd1, s[1] = 0x91;
  else if (v == 0x451) // ё
    s[0] = 0xd0, s[1] = 0x81;

}

Обе реализации имеют примерно одинаковый размер на ассемблере (от 80 до 128 байт, первенство зависит от оптимизации (с -Os chng_case_rus_utf немного больше, а с -O2 чуть меньше)) и выполняются за примерно одинаковое время (chng_case_rus_utf чуть медленнее).


Что не понятно, спрашивайте.

→ Ссылка