У меня есть строка, которую я хочу сделать заглавной, но она может содержать польские специальные буквы (ą, ć, ę, ł, ń, ó, ś, ż, ź). Функция transform(string.begin(), string.end(), string.begin(), ::toupper); пишет только латинский алфавит с заглавной буквы, поэтому я написал такую функцию:
string to_upper(string nazwa)
{
transform(nazwa.begin(), nazwa.end(), nazwa.begin(), ::toupper);
for (int i = 0; i < (int)nazwa.size(); i++)
{
switch(nazwa[i])
{
case u'ą':
{
nazwa[i] = u'Ą';
break;
}
case u'ć':
{
nazwa[i] = u'Ć';
break;
}
case u'ę':
{
nazwa[i] = u'Ę';
break;
}
case u'ó':
{
nazwa[i] = u'Ó';
break;
}
case u'ł':
{
nazwa[i] = u'Ł';
break;
}
case u'ń':
{
nazwa[i] = u'Ń';
break;
}
case u'ś':
{
nazwa[i] = u'Ś';
break;
}
case u'ż':
{
nazwa[i] = u'Ż';
break;
}
case u'ź':
{
nazwa[i] = u'Ź';
break;
}
}
}
return nazwa;
}
Я также пытался использовать if вместо switch, но это ничего не меняет.
В Qt Creator рядом с каждой заглавной буквой, которую нужно вставить, кроме u'Ó', выдает аналогичную ошибку: Implicit conversion from 'char16_t' to 'std::basic_string<char>::value_type' (aka 'char') changes value from 260 to 4 (это от u'Ą'). После запуска программы символы в строке не меняются местами.
@Yunnosch OP упомянул об этом в вопросе. Это будет работать, только если вы установите локаль, которая поддерживает польские символы.
У меня есть это, и оно все еще не работает: `setlocale(LC_ALL, "pl_PL"); unsigned char c = nazwa[i]; nazwa[i] = toupper(c);`
@Greg - вам нужно использовать либо (шаблон) std::toupper(), который принимает const std::locale & в качестве второго аргумента, либо std::towupper(). В любом случае вам нужно будет передать широкий символ (например, wchar_t не тип char) Имейте в виду, что оба сопоставляют только один символ с одним символом (например, их нельзя использовать, если преобразование в верхний регистр сопоставляет один символ с парой символов ).
Вам нужно начать с решения, какой кодировкой закодирован string. std::string не подразумевает какой-либо конкретной кодировки.
@Yksisarvinen Спасибо, пропустил из-за отсутствия (). Виноват. И я не знал о зависимости от локали. (Рад, что спросил, вместо того, чтобы извергать неправильные нерешения.... ;-))
@Peter - у меня есть это, и ни одна из двух последних строк не работает (я не использую обе одновременно), я все равно получаю строчную букву: setlocale(LC_ALL, "pl_PL"); wchar_t c = nazwa[i]; nazwa[i] = toupper(c, locale("pl_PL")); nazwa[i] = towupper(c);
Это может быть полезно не для решения проблемы с кодировкой, а для оптимизации кода. Посмотрите следующее: old.unicode-table.com/en/blocks/latin-extended-a В таблице Unicode значение заглавные буквы - это просто значение меньших - 1. Поэтому просто проверьте, находится ли код chracater между 0x100 и 0x17E, и если это нечетное число, вычтите из него 1, чтобы сделать его заглавной буквой. Хотя это может быть сложно реализовать на практике, потому что ваш std::string здесь закодирован в UTF-8, поэтому один символ не равен одной букве.
Какую кодировку вы используете? 8859-2? Юникод UTF-8 (65001)? Windows-1250? Мак-10029?
@RedStoneMatt - я уже пробовал это, это не работает, для «ą» это дает мне умлаут с большой буквы.
@Eljay - я думаю, что использую UTF-8
Другой вариант — использовать библиотеку ICU.
Какая у вас ОС и компилятор? А где вы берете свою струну? (Терминал, файл на диске, сетевое подключение, что-то еще?)
Что еще более важно, зачем вам нужно использовать строку с заглавной буквы? Большинству людей, которые думают, что им нужно использовать заглавные буквы в строках, на самом деле требуется только сопоставление регистра, что является гораздо более простой проблемой (в целом использование заглавных букв на удивление сложно, если вам нужно иметь дело со многими языками).
@Greg «Я уже пробовал это, это не работает, для «ą» это дает мне умляут с большой буквы», вероятно, потому, что вы вычли 1 из обоих байтов, составляющих ą. Пожалуйста, смотрите мой полный ответ ниже, он объясняет, как UTF-8 работает с вашим конкретным случаем, и дает функцию, которая должна выполнять эту работу за вас.





Самый простой способ справиться с этим — использовать широкую строку. Единственная ловушка - правильная обработка кодировки/локали.
Итак, попробуйте следующее:
#include <algorithm>
#include <iostream>
#include <locale>
#include <string>
int main()
try {
std::locale cLocale{ "C.UTF-8" };
std::locale::global(cLocale);
std::locale sys { "" };
std::wcin.imbue(sys);
std::wcout.imbue(sys);
std::wstring line;
while (getline(std::wcin, line)) {
std::transform(line.begin(), line.end(), line.begin(), [&cLocale](auto ch) { return std::toupper(ch, cLocale); });
std::wcout << line << L'\n';
}
} catch (const std::exception& e) {
std::cerr << e.what() << '\n';
}
https://godbolt.org/z/3cKaEeW3z
Сейчас:
cLocale определяет локаль, которая будет использоваться стандартной библиотекой при взаимодействии с вашей программой.sys — это языковой стандарт системы, который определяет, какую кодировку следует использовать для входных и выходных потоков.
Обратите внимание, какой перегружатель используется.Тот же код должен работать с std::string и std::cinstd::cout, только если вы используете однобайтовую кодировку, которая работает для польского языка. В таком случае вы должны изменить строку в cLocale на:
#include <algorithm>
#include <iostream>
#include <locale>
#include <string>
int main()
try {
std::locale cLocale{ ".1250" };
std::locale::global(cLocale);
std::locale sys { "" };
std::cin.imbue(sys);
std::cout.imbue(sys);
std::string line;
while (getline(std::cin, line)) {
std::transform(line.begin(), line.end(), line.begin(), [&cLocale](auto ch) { return std::toupper(ch, cLocale); });
std::cout << line << '\n';
}
} catch (const std::exception& e) {
std::cerr << e.what() << '\n';
}
Обратите внимание, что это имя локали зависит от платформы и компилятора, а также система должна быть настроена для работы. Выше работает в Windows с MSVC (я проверял это). Не могу продемонстрировать это, так как нет онлайн-компилятора, поддерживающего польскую локаль.
Если используется многобайтовая кодировка, преобразование завершится ошибкой, так как не удастся обработать эти многобайтовые символы.
Я получаю эту ошибку: collate_byname<char>::collate_byname не удалось построить для .1250
Вы не написали, какую ОС и компилятор вы использовали. Я написал, что это зависит от платформы и компилятора и протестировал это с помощью MSVC в Windows 10 с настроенной поддержкой польского языка. Версия с широкими строками должна просто работать.
Работаю на MacOS в Qt Creator
Затем введите терминал locale -a, чтобы увидеть возможные названия локалей.
В моей MacOS есть pl_PL.ISO8859-2, что эквивалентно кодовой странице Windows 1250.
Хорошо, не работает на MacOS :(.
На моей машине с MacOS широкая строковая версия не работает, но странным образом. Он дублирует символы и добавляет впереди немного мусора, по крайней мере, повторяющиеся символы являются правильными в верхнем регистре. Поддержка локалей для C++ отстой.
@MarekR Это вина libc++. У него нет рабочего wcin/wcout.
std::string хранит символы как chars, длина которых составляет один байт, поэтому их значение может быть только от 0 до 255.
Это делает невозможным хранение u'ą' в одном char, например, поскольку значение Юникода для ą равно 0x105 (= 261 в десятичном виде, что больше, чем 255).
Чтобы избежать этой проблемы, люди изобрели UTF-8, стандарт кодирования символов, который позволяет кодировать любые символы Unicode в виде байтов. Разумеется, для кодирования символов с более высоким значением потребуется несколько байтов.
Весьма вероятно, что ваши символы std::string закодированы в UTF-8. (Я говорю очень вероятно, потому что ваш код прямо не указывает на это, но почти на 100% уверен, что это так, потому что это единственный универсальный способ кодирования букв с диакритическими знаками в строках на основе char. Чтобы быть абсолютно на 100% конечно, вам нужно проверить код Qt, так как это похоже на то, что вы используете)
Результатом этого является то, что вы не можете просто использовать for для перебора char ваших std::string так, как вы есть, потому что вы в основном предполагаете, что один char равен одной букве, что просто не так.
Например, в случае ą он будет закодирован как байты C4 85, поэтому у вас будет один char со значением 0xC4 (= 196), за которым следует другой char со значением 0x85 (= 133).
Часть Latin Extended-A таблицы Unicode ( архив), к счастью, показывает нам, что эти специальные заглавные буквы идут прямо перед их строчными копиями.
Более того, мы можем видеть, что:
Это облегчит преобразование строчных кодовых точек в прописные, поскольку все, что нам нужно сделать, это проверить, соответствует ли индекс символа строчному, и если да, вычесть из него единицу, чтобы сделать его прописным.
Чтобы закодировать их в UTF-8 (источник):
110xxxxx, замените xxxxx старшими пятью байтами двоичного кода символа.10xxxxxx, замените xxxxxx младшими шестью байтами двоичной кодовой точки символа.Итак, для ą значение 0x105 в шестнадцатеричном формате, поэтому 00100000101 в двоичном формате.
Тогда значение первого байта равно 11000100 (= 0xC4).
Тогда значение второго байта равно 10000101 (= 0x85).
Обратите внимание, что этот «метод» кодирования работает, потому что символы, которые вы хотите использовать с заглавными буквами, имеют свое значение (кодовую точку) между 0x80 и 0x7FF. Оно меняется в зависимости от того, насколько велико значение, см. документацию здесь.
Я переписал вашу функцию to_upper в соответствии с тем, что я написал до сих пор:
string to_upper(string nazwa)
{
for (int i = 0; i < (int)nazwa.size(); i++)
{
// Getting the current character we are working with
char chr1 = nazwa[i];
// We want to find UTF-8-encoded polish letters here
// So we are looking for a character that has first three bits set to 110,
// as all polish letters encoded in UTF-8 are in UTF-8 Class 1 and therefore
// are two bytes long, the first byte being of binary value 110xxxxx
if (((chr1 >> 5) & 0b111) != 0b110) {
nazwa[i] = toupper(chr1); // Do the std toupper here for regular characters
continue;
}
// If we are here, then the character we are dealing with is two bytes long, so get its value.
// We won't need to check for that second byte during next iteration, so we increment i
i++;
char chr2 = nazwa[i];
// Get the unicode value of the encoded character
uint16_t fullChr = ((chr1 & 0b11111) << 6) | (chr2 & 0b111111);
// Get the various conditions to check for lowercase code points
bool lowercaseIsOdd = (fullChr >= 0x100 && fullChr <= 0x137) || (fullChr >= 0x14A && fullChr <= 0x177);
bool lowercaseIsEven = (fullChr >= 0x139 && fullChr <= 0x148) || (fullChr >= 0x179 && fullChr <= 0x17E);
bool chrIndexIsOdd = (fullChr % 2) == 1;
// Depending of whether the code point needs to be odd or even to be lowercase and depending of if the code point
// is odd or even, decrease it by one to make it uppercase
if ((lowercaseIsOdd && chrIndexIsOdd)
|| (lowercaseIsEven && !chrIndexIsOdd))
fullChr--;
// Support for some additional, more commonly used accented letters
if (fullChr >= 0xE0 && fullChr <= 0xF6)
fullChr -= 0x20;
// Re-encode the character point in UTF-8
nazwa[i-1] = (0b110 << 5) | ((fullChr >> 6) & 0b11111); // We incremented i earlier, so subtract one to edit the first byte of the letter we're encoding
nazwa[i] = (0b10 << 6) | (fullChr & 0b111111);
}
return nazwa;
}
Примечание: не забудьте #include <cstdint>, чтобы uint16_t работало.
Примечание 2: я добавил поддержку некоторых букв Latin 1 Supplement ( архив), потому что вы просили об этом в комментариях. Хотя мы вычитаем 0x20 из строчных кодовых точек, чтобы получить прописные, это почти тот же принцип, что и для других букв, которые я рассмотрел в этом ответе.
Я включил много комментариев в свой код, пожалуйста, прочтите их для лучшего понимания.
Я протестировал его со строкой "ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽž", и он преобразовал ее в "ĀĀĂĂĄĄĆĆĈĈĊĊČČĎĎĐĐĒĒĔĔĖĖĘĘĚĚĜĜĞĞĠĠĢĢĤĤĦĦĨĨĪĪĬĬĮĮİİIJIJĴĴĶĶĸĹĹĻĻĽĽĿĿŁŁŃŃŅŅŇŇŊŊŌŌŎŎŐŐŒŒŔŔŖŖŘŘŚŚŜŜŞŞŠŠŢŢŤŤŦŦŨŨŪŪŬŬŮŮŰŰŲŲŴŴŶŶŸŹŹŻŻŽŽ", так что все работает отлично:
int main() {
string str1 = "ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽž";
string str2 = to_upper(str1);
printf("str1: %s\n", str1.c_str());
printf("str2: %s\n", str2.c_str());
}
Примечание. Все терминалы используют UTF-8 по умолчанию, метки Qt также, в основном ВСЕ используют UTF-8, КРОМЕ Windows CMD, поэтому, если вы тестируете приведенный выше код на Windows CMD или Powershell, вам необходимо изменить их на UTF. -8 с помощью команды chcp 65001 или добавив вызов Windows API для изменения кодировки CMD при выполнении кода.
Примечание 2. Когда вы пишете необработанные строки непосредственно в своем коде, ваш компилятор по умолчанию кодирует их в UTF-8. Вот почему моя версия функции to_upper работает с польскими буквами, написанными непосредственно в коде без дальнейших модификаций. Когда я говорю, что ВСЕ использует UTF-8, я имею в виду именно это.
Примечание 3. Я сохранил его, чтобы не создавать проблем с вашим текущим кодом, но вы используете string вместо std::string, подразумевая, что где-то в вашем коде есть using namespace std;. В этом случае см. Почему «использование пространства имен std;» считается плохой практикой?
Пожалуйста, имейте в виду, что мой ответ очень специфичен для вашего случая. Он направлен на то, чтобы, как вы и просили, сделать польские буквы заглавными.
Другие ответы основаны на функциях std, которые, по-видимому, более универсальны и работают со всеми языками, поэтому я предлагаю вам взглянуть на них.
Всегда лучше полагаться на существующие функции, чем изобретать велосипед, но я думаю, что также хорошо иметь самодельную альтернативу, которая может быть проще для понимания, а иногда и более эффективна.
«Это делает невозможным хранение u'ą', например, в одном символе». Люди делали это задолго до появления Unicode и делают это до сих пор (см. Кодовые страницы Windows и т. д.). Не то чтобы это хороший способ решить проблему в 2023 году, но, безусловно, существующий и в какой-то степени работающий способ.
@n.m.couldbeanAI Кодовые страницы Windows не только считаются ОЧЕНЬ плохой практикой, но и (очевидно) предназначены только для Windows. OP сказал, что они используют Qt Creator, подразумевая, что разрабатываемое ими приложение может быть универсальным, поэтому UTF-8 является обязательным. Но да, действительно, различные расширения ASCII добавили поддержку такого типа символов, но она очень ограничена, поскольку эти расширения по-прежнему ограничены пределом значений 0-255 для каждого символа. Таким образом, они добавляют символы для значений от 128 до 255, но всегда будут неподдерживаемые символы. UTF-8 от этого совершенно не страдает.
Смысл UTF-8 в том, что он поддерживает все символы Unicode, которые теоретически могут иметь такое большое значение, какое мы хотим. На данный момент существует только четыре диапазона символов, но при необходимости их можно будет добавить в один прекрасный день. Кроме того, поскольку, как я упоминал в своем предыдущем комментарии, OP использует Qt, UTF-8, вероятно, является единственным способом заставить эти акцентированные символы работать в конкретном случае OP, потому что им, скорее всего, нужна строка, чтобы оставаться в кодировке UTF-8 для Qt понять это.
«Смысл UTF-8 в том, что он поддерживает все символы Юникода». Продолжается с жестко закодированной вручную таблицей капитализации для небольшой части Юникода и неправильной для загрузки (посмотрите на свой ıŚśŜŝŞ).
OP запросил полированные символы, поэтому я написал свой код только для этого. Потребуется вечность, чтобы пройтись по всем строчным символам в таблице Unicode и преобразовать их в прописные. Вы правы из-за ошибки в конце строки, которую я включил в свой ответ! Я был недостаточно внимателен, я это исправлю. Спасибо, что сообщили об этом, хотя я бы предпочел, чтобы вы сказали это менее агрессивно :)
«Потребуется целая вечность, чтобы просмотреть все символы нижнего регистра в таблице Unicode и преобразовать их в верхний регистр». Вот почему вы используете таблицы, которые уже скомпилированы для вас и предустановлены на вашем компьютере.
@n.m.couldbeanAI В настоящее время изучает неправильные буквы; по какой-то причине кажется, что значение второго байта из них неверно при получении их из строки. Это довольно странно, потребуется некоторое время, чтобы выяснить, как это исправить. Кроме того, если вы знаете лучший способ ответить на вопрос ОП, то, пожалуйста, напишите на него ответ, гораздо лучше будет объяснить вашу точку зрения, чем комментарии. Я всегда готов узнать больше, поэтому не стесняйтесь показывать нам, как использовать эти таблицы, я даже не знал, что это так, и для этого и нужны ответы.
Просто не пишите свое собственное преобразование регистра. Используйте стандартный. Это все необходимое исправление.
Исправлена проблема, о которой вы сообщили @n.m.couldbeanAI, виновником на самом деле был stdtoupper, который превращал символы со значениями 0x9C и 0x9E в 0x8C и 0x8E соответственно, тем самым нарушая мою функцию. Я исправил это, сделав вызовы to_upper только для обычных символов, что также делает код менее сложным, поскольку теперь он выполняет итерацию по строке только один раз вместо двух.
"Используйте стандартный", тогда ответьте пожалуйста и покажите стандартный способ. Я копался в stackoverflow и нашел только «Стандартных способов нет» и «Используйте какую-нибудь библиотеку, верхний регистр не является точным термином». Если вы знаете решение, пожалуйста, дайте решение, в этом смысл этого сайта.
Я попросил разъяснения у ОП и могу ответить, когда получу его. Между тем, другой ответ работает лучше вашего и использует стандартные средства. Попробуй это.
@n.m.couldbeanAI Я попробую другой ответ. Однако, пожалуйста, посмотрите комментарии под другим ответом, кажется, что это не сработает для OP.
Это не сработает, если OP нужно будет прочитать символы со стандартного ввода, о котором мы не знаем. Некоторые подобные методы будут работать. Я попросил разъяснений, но пока их нет.
@RedStoneMatt - Спасибо, это в основном решает мою проблему, но пара 'ó' и 'Ó' (0xD3, 0xF3) не работает, есть ли способ изменить вашу программу, чтобы поменять их местами, или это совершенно другое?
@Greg Непосредственно перед комментарием // Re-encode the character point in UTF-8 в моем коде добавьте if (fullChr >= 0xE0 && fullChr <= 0xF6) fullChr -= 0x20;, это добавит поддержку набора букв Дополнения к латинице 1, включая ó. Они не являются эксклюзивными для польского языка, поэтому я не включил их в свой ответ. Как я упоминал в одном из своих комментариев, поддержка всех строчных букв Unicode заняла бы целую вечность и привела бы к очень большой функции.
Возможно, вы также обратите внимание на предложение @n.m.couldbeanAI, если оно сработает для вас, Грег.
Я обновил свой ответ по вашему запросу, Грег.
Спасибо @RedStoneMatt, это решает все мои проблемы с этим.
Это должно работать на большинстве систем Unix-y, за исключением странных случаев, таких как турецкий I и, возможно, немецкий ß.
#include <clocale>
#include <locale>
#include <iostream>
#include <string>
#include <cwctype>
#include <codecvt>
inline std::wstring stow(const std::string& p)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>> wconv;
return wconv.from_bytes(p);
}
inline std::string wtos(const std::wstring& p)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>> wconv;
return wconv.to_bytes(p);
}
int main()
{
std::locale loc("");
// AFAICT the calls below are optional on a Mac
// for this particular task but it could be a
// good idea to use them anyway
// std::setlocale(LC_ALL, "");
// std::locale::global(loc);
// std::cin.imbue(loc);
// std::cout.imbue(loc);
std::string s;
std::getline(std::cin, s);
std::wstring w = stow(s);
for (auto& c: w)
{
c = std::toupper(c, loc);
}
std::cout << wtos(w) << "\n";
}
Обратите внимание, что он использует устаревшие средства C++ для преобразования кода UTF-8. Если вас это беспокоит, подставьте любые преобразователи UTF-8 в UTF-32 и обратно в stow и wtos. Также не стесняйтесь заменять локаль, существующую в вашей системе (может быть "pl_PL.UTF-8" или аналогичная).,
Вам нужно будет использовать кодовые точки Unicode. Поскольку эти буквы не представлены в таблице ASCII. Взгляните на stackoverflow.com/questions/331690 и stackoverflow.com/questions/3010739