Каков наилучший способ сравнения строк без учета регистра в С++ без преобразования строки во все прописные или все строчные буквы?
Пожалуйста, укажите, совместимы ли эти методы с Unicode и насколько они переносимы.
Каков наилучший способ сравнения строк без учета регистра в С++ без преобразования строки во все прописные или все строчные буквы?
Пожалуйста, укажите, совместимы ли эти методы с Unicode и насколько они переносимы.
std::stricmp
. В противном случае прочитайте, что Херб должен сказать. 26.08.2008 strcasecmp
не является частью стандарта и отсутствует по крайней мере в одном общем компиляторе. 01.12.2014 Boost включает в себя удобный алгоритм для этого:
#include <boost/algorithm/string.hpp>
// Or, for fewer header dependencies:
//#include <boost/algorithm/string/predicate.hpp>
std::string str1 = "hello, world!";
std::string str2 = "HELLO, WORLD!";
if (boost::iequals(str1, str2))
{
// Strings are identical
}
Воспользуйтесь преимуществами стандарта char_traits
. Напомним, что std::string
на самом деле является typedef для std::basic_string<char>
или, точнее, std::basic_string<char, std::char_traits<char> >
. Тип char_traits
описывает, как символы сравниваются, как они копируются, как преобразуются и т. д. Все, что вам нужно сделать, это определить новую строку поверх basic_string
и предоставить ей свой собственный char_traits
, который сравнивает регистр без учета регистра.
struct ci_char_traits : public char_traits<char> {
static bool eq(char c1, char c2) { return toupper(c1) == toupper(c2); }
static bool ne(char c1, char c2) { return toupper(c1) != toupper(c2); }
static bool lt(char c1, char c2) { return toupper(c1) < toupper(c2); }
static int compare(const char* s1, const char* s2, size_t n) {
while( n-- != 0 ) {
if( toupper(*s1) < toupper(*s2) ) return -1;
if( toupper(*s1) > toupper(*s2) ) return 1;
++s1; ++s2;
}
return 0;
}
static const char* find(const char* s, int n, char a) {
while( n-- > 0 && toupper(*s) != toupper(a) ) {
++s;
}
return s;
}
};
typedef std::basic_string<char, ci_char_traits> ci_string;
Подробности в гуру недели номер 29.
typedef std::basic_string<char, ci_char_traits<char> > istring
, а не typedef std::basic_string<char, std::char_traits<char> > string
. 09.10.2012 find
должен быть std::size_t
, а не int
. К сожалению, я не могу редактировать, потому что вопрос закрыт. Также возможно реализовать find
и compare
с точки зрения eq
, lt
и ne
. 16.07.2018 std::string
, преобразование между ними тривиально с помощью конструктора диапазона. 11.10.2019 std::unordered_map
. (Или, по крайней мере, реализация строкового хэша в стандартной библиотеке MSVC, по-видимому, не использует признаки char для чего-либо.) Таким образом, при использовании этого с std::unordered_map
, вероятно, также потребуется специализация std::hash
. 19.03.2020 std::toupper
следует не вызывать char
напрямую, а static_cast
для unsigned char
нужно. 26.09.2020 Проблема с повышением заключается в том, что вы должны связываться с повышением и зависеть от него. В некоторых случаях это непросто (например, Android).
А использование char_traits означает, что все ваши сравнения нечувствительны к регистру, а это обычно не то, что вам нужно.
Этого должно быть достаточно. Он должен быть достаточно эффективным. Однако не обрабатывает юникод или что-то в этом роде.
bool iequals(const string& a, const string& b)
{
unsigned int sz = a.size();
if (b.size() != sz)
return false;
for (unsigned int i = 0; i < sz; ++i)
if (tolower(a[i]) != tolower(b[i]))
return false;
return true;
}
Обновление: бонусная версия С++ 14 (#include <algorithm>
):
bool iequals(const string& a, const string& b)
{
return std::equal(a.begin(), a.end(),
b.begin(), b.end(),
[](char a, char b) {
return tolower(a) == tolower(b);
});
}
std::equal
недоступен в C++11. 22.06.2018 std::tolower
следует не вызывать char
напрямую, а static_cast
для unsigned char
нужно. 26.09.2020 -O3
, и все они работают нормально. Я предполагаю, что если бы вы сделали компилятор, который этого не сделал, он не смог бы скомпилировать много существующего кода. 02.03.2021 Если вы работаете в системе POSIX, вы можете использовать strcasecmp. Однако эта функция не является частью стандартного C и недоступна в Windows. Это будет выполнять сравнение без учета регистра 8-битных символов, если локаль POSIX. Если локаль не POSIX, результаты не определены (поэтому может выполняться локализованное сравнение, а может и нет). Эквивалент широкого символа недоступен.
В противном случае большое количество исторических реализаций библиотеки C имеют функции stricmp() и strnicmp(). Visual C++ в Windows переименовал их все, добавив к ним префикс подчеркивания, потому что они не являются частью стандарта ANSI, поэтому в этой системе они называются _stricmp или _strnicmp. Некоторые библиотеки могут также иметь функции, эквивалентные расширенным символам или многобайтовым символам (обычно называемые, например, wcsicmp, mbcsicmp и т. д.).
И C, и C++ в значительной степени не осведомлены о проблемах интернационализации, поэтому нет хорошего решения этой проблемы, кроме использования сторонней библиотеки. Ознакомьтесь с IBM ICU (International Components for Unicode), если вам нужна надежная библиотека для C/C++. ICU предназначен как для систем Windows, так и для Unix.
Вы говорите о глупом сравнении без учета регистра или о полном нормализованном сравнении Unicode?
Глупое сравнение не найдет строки, которые могут быть одинаковыми, но не равными в двоичном виде.
Пример:
U212B (ANGSTROM SIGN)
U0041 (LATIN CAPITAL LETTER A) + U030A (COMBINING RING ABOVE)
U00C5 (LATIN CAPITAL LETTER A WITH RING ABOVE).
Все эквивалентны, но они также имеют разные двоичные представления.
Тем не менее, Нормализация Unicode должна быть обязательна к прочтению, особенно если вы планируете поддерживать хангыль, тайский и другие языки. азиатские языки.
Кроме того, IBM в значительной степени запатентовала наиболее оптимизированные алгоритмы Unicode и сделала их общедоступными. Они также поддерживают реализацию: IBM ICU
boost::iequals не совместим с utf-8 в случае строки. Вы можете использовать boost::locale.
comparator<char,collator_base::secondary> cmpr;
cout << (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") << endl;
Моей первой мыслью для версии без юникода было сделать что-то вроде этого:
bool caseInsensitiveStringCompare(const string& str1, const string& str2) {
if (str1.size() != str2.size()) {
return false;
}
for (string::const_iterator c1 = str1.begin(), c2 = str2.begin(); c1 != str1.end(); ++c1, ++c2) {
if (tolower(static_cast<unsigned char>(*c1)) != tolower(static_cast<unsigned char>(*c2))) {
return false;
}
}
return true;
}
std::tolower
следует не вызывать char
напрямую, а static_cast
для unsigned char
нужно. 26.09.2020 if (tolower(static_cast<unsigned char>(*c1)) != tolower(static_cast<unsigned char>(*c2))
подойдет? 27.09.2020 Вы можете использовать strcasecmp
в Unix или stricmp
в Windows.
Одна вещь, которая до сих пор не упоминалась, заключается в том, что если вы используете строки stl с этими методами, полезно сначала сравнить длину двух строк, поскольку эта информация уже доступна вам в классе строк. Это может предотвратить дорогостоящее сравнение строк, если две сравниваемые строки изначально не имеют одинаковой длины.
Я пытаюсь собрать хороший ответ из всех сообщений, поэтому помогите мне отредактировать это:
Вот способ сделать это, хотя он преобразует строки и не поддерживает Unicode, он должен быть переносимым, что является плюсом:
bool caseInsensitiveStringCompare( const std::string& str1, const std::string& str2 ) {
std::string str1Cpy( str1 );
std::string str2Cpy( str2 );
std::transform( str1Cpy.begin(), str1Cpy.end(), str1Cpy.begin(), ::tolower );
std::transform( str2Cpy.begin(), str2Cpy.end(), str2Cpy.begin(), ::tolower );
return ( str1Cpy == str2Cpy );
}
Из того, что я прочитал, это более переносимо, чем stricmp(), потому что stricmp() на самом деле не является частью библиотеки std, а реализовано только большинством поставщиков компиляторов.
Похоже, чтобы получить действительно дружественную к Unicode реализацию, вы должны выйти за пределы библиотеки std. Одной из хороших сторонних библиотек является IBM ICU (International Components for Unicode).
Также boost::iequals предоставляет довольно хорошую утилиту для такого рода сравнений.
transform
всю строку перед сравнением 06.06.2019 std::tolower
следует не вызывать char
напрямую, а static_cast
для unsigned char
нужно. 26.09.2020 Строковые функции Visual C++, поддерживающие Unicode: http://msdn.microsoft.com/en-us/library/cc194799.aspx
тот, кого вы, вероятно, ищете, это _wcsnicmp
К вашему сведению, strcmp()
и stricmp()
уязвимы к переполнению буфера, поскольку они просто обрабатываются до тех пор, пока не достигнут нулевой терминатор. Безопаснее использовать _strncmp()
и _strnicmp()
.
stricmp()
и strnicmp()
не являются частью стандарта POSIX :-( Однако вы можете найти strcasecmp()
, strcasecmp_l()
, strncasecmp()
и strncasecmp_l()
в заголовке POSIX strings.h
:-) см. opengroup.org 11.04.2013 str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), [](auto a, auto b){return std::tolower(a)==std::tolower(b);})
Вы можете использовать приведенный выше код на C++ 14, если не можете использовать boost. Вы должны использовать std::towlower
для широких символов.
str1.size() == str2.size() &&
впереди, чтобы не выходить за пределы, когда str2 является префиксом str1. 01.08.2017 См. std::lexicographical_compare
:
// lexicographical_compare example
#include <iostream> // std::cout, std::boolalpha
#include <algorithm> // std::lexicographical_compare
#include <cctype> // std::tolower
// a case-insensitive comparison function:
bool mycomp (char c1, char c2) {
return std::tolower(c1) < std::tolower(c2);
}
int main () {
char foo[] = "Apple";
char bar[] = "apartment";
std::cout << std::boolalpha;
std::cout << "Comparing foo and bar lexicographically (foo < bar):\n";
std::cout << "Using default comparison (operator<): ";
std::cout << std::lexicographical_compare(foo, foo + 5, bar, bar + 9);
std::cout << '\n';
std::cout << "Using mycomp as comparison object: ";
std::cout << std::lexicographical_compare(foo, foo + 5, bar, bar + 9, mycomp);
std::cout << '\n';
return 0;
}
std::tolower
работает, только если символ закодирован в ASCII. Для std::string
такой гарантии нет, поэтому это может быть легко неопределенное поведение. 27.03.2018 Библиотека Boost.String содержит множество алгоритмов для делать сравнения без учета регистра и так далее.
Вы можете реализовать свои собственные, но зачем беспокоиться, когда это уже сделано?
Коротко и красиво. Никаких других зависимостей, кроме extended std C lib.
strcasecmp(str1.c_str(), str2.c_str()) == 0
возвращает true, если str1
и str2
равны. strcasecmp
может и не быть, могут быть аналоги stricmp
, strcmpi
и т.д.
Пример кода:
#include <iostream>
#include <string>
#include <string.h> //For strcasecmp(). Also could be found in <mem.h>
using namespace std;
/// Simple wrapper
inline bool str_ignoreCase_cmp(std::string const& s1, std::string const& s2) {
if(s1.length() != s2.length())
return false; // optimization since std::string holds length in variable.
return strcasecmp(s1.c_str(), s2.c_str()) == 0;
}
/// Function object - comparator
struct StringCaseInsensetiveCompare {
bool operator()(std::string const& s1, std::string const& s2) {
if(s1.length() != s2.length())
return false; // optimization since std::string holds length in variable.
return strcasecmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator()(const char *s1, const char * s2){
return strcasecmp(s1,s2)==0;
}
};
/// Convert bool to string
inline char const* bool2str(bool b){ return b?"true":"false"; }
int main()
{
cout<< bool2str(strcasecmp("asd","AsD")==0) <<endl;
cout<< bool2str(strcasecmp(string{"aasd"}.c_str(),string{"AasD"}.c_str())==0) <<endl;
StringCaseInsensetiveCompare cmp;
cout<< bool2str(cmp("A","a")) <<endl;
cout<< bool2str(cmp(string{"Aaaa"},string{"aaaA"})) <<endl;
cout<< bool2str(str_ignoreCase_cmp(string{"Aaaa"},string{"aaaA"})) <<endl;
return 0;
}
Вывод:
true
true
true
true
true
stricmp
, strcmpi
, strcasecmp
и т. д. Спасибо. сообщение отредактировано. 21.10.2016 cout << boolalpha
, а не мой bool2str
, потому что он неявно преобразует логическое значение в символы для потока. 01.06.2017 Для моих базовых потребностей в сравнении строк без учета регистра я предпочитаю не использовать внешнюю библиотеку, а также не хочу иметь отдельный класс строк с чертами, нечувствительными к регистру, которые несовместимы со всеми моими другими строками.
Итак, что я придумал, так это:
bool icasecmp(const string& l, const string& r)
{
return l.size() == r.size()
&& equal(l.cbegin(), l.cend(), r.cbegin(),
[](string::value_type l1, string::value_type r1)
{ return toupper(l1) == toupper(r1); });
}
bool icasecmp(const wstring& l, const wstring& r)
{
return l.size() == r.size()
&& equal(l.cbegin(), l.cend(), r.cbegin(),
[](wstring::value_type l1, wstring::value_type r1)
{ return towupper(l1) == towupper(r1); });
}
Простая функция с одной перегрузкой для char и другой для whar_t. Не использует ничего нестандартного, поэтому должно работать на любой платформе.
Сравнение равенства не будет учитывать такие проблемы, как кодирование переменной длины и нормализацию Unicode, но basic_string не поддерживает это, о чем я в любом случае знаю, и обычно это не проблема.
В тех случаях, когда требуются более сложные лексикографические манипуляции с текстом, вам просто нужно использовать стороннюю библиотеку, такую как Boost, что и следовало ожидать.
Сделать это без использования Boost можно, получив указатель строки C с помощью c_str()
и используя strcasecmp
:
std::string str1 ="aBcD";
std::string str2 = "AbCd";;
if (strcasecmp(str1.c_str(), str2.c_str()) == 0)
{
//case insensitive equal
}
Предполагая, что вы ищете метод, а не волшебную функцию, которая уже существует, откровенно говоря, лучшего пути не найти. Мы все могли бы написать фрагменты кода с хитрыми приемами для ограниченных наборов символов, но в конце концов в какой-то момент вам придется преобразовать символы.
Лучший подход для этого преобразования — сделать это до сравнения. Это дает вам большую гибкость, когда дело доходит до схем кодирования, о которых ваш фактический оператор сравнения не должен знать.
Конечно, вы можете «скрыть» это преобразование за своей собственной строковой функцией или классом, но вам все равно нужно преобразовать строки перед сравнением.
Я написал нечувствительную к регистру версию char_traits для использования с std::basic_string, чтобы сгенерировать std::string, который не чувствителен к регистру при выполнении сравнений, поиска и т. д. с использованием встроенных функций-членов std::basic_string.
Другими словами, я хотел сделать что-то подобное.
std::string a = "Hello, World!";
std::string b = "hello, world!";
assert( a == b );
... который std::string не может обработать. Вот использование моих новых char_traits:
std::istring a = "Hello, World!";
std::istring b = "hello, world!";
assert( a == b );
...и вот реализация:
/* ---
Case-Insensitive char_traits for std::string's
Use:
To declare a std::string which preserves case but ignores case in comparisons & search,
use the following syntax:
std::basic_string<char, char_traits_nocase<char> > noCaseString;
A typedef is declared below which simplifies this use for chars:
typedef std::basic_string<char, char_traits_nocase<char> > istring;
--- */
template<class C>
struct char_traits_nocase : public std::char_traits<C>
{
static bool eq( const C& c1, const C& c2 )
{
return ::toupper(c1) == ::toupper(c2);
}
static bool lt( const C& c1, const C& c2 )
{
return ::toupper(c1) < ::toupper(c2);
}
static int compare( const C* s1, const C* s2, size_t N )
{
return _strnicmp(s1, s2, N);
}
static const char* find( const C* s, size_t N, const C& a )
{
for( size_t i=0 ; i<N ; ++i )
{
if( ::toupper(s[i]) == ::toupper(a) )
return s+i ;
}
return 0 ;
}
static bool eq_int_type( const int_type& c1, const int_type& c2 )
{
return ::toupper(c1) == ::toupper(c2) ;
}
};
template<>
struct char_traits_nocase<wchar_t> : public std::char_traits<wchar_t>
{
static bool eq( const wchar_t& c1, const wchar_t& c2 )
{
return ::towupper(c1) == ::towupper(c2);
}
static bool lt( const wchar_t& c1, const wchar_t& c2 )
{
return ::towupper(c1) < ::towupper(c2);
}
static int compare( const wchar_t* s1, const wchar_t* s2, size_t N )
{
return _wcsnicmp(s1, s2, N);
}
static const wchar_t* find( const wchar_t* s, size_t N, const wchar_t& a )
{
for( size_t i=0 ; i<N ; ++i )
{
if( ::towupper(s[i]) == ::towupper(a) )
return s+i ;
}
return 0 ;
}
static bool eq_int_type( const int_type& c1, const int_type& c2 )
{
return ::towupper(c1) == ::towupper(c2) ;
}
};
typedef std::basic_string<char, char_traits_nocase<char> > istring;
typedef std::basic_string<wchar_t, char_traits_nocase<wchar_t> > iwstring;
У меня есть хороший опыт использования международных компонентов для библиотек Unicode — они очень мощные и предоставить методы для преобразования, поддержки локали, рендеринга даты и времени, отображения регистра (что вам, похоже, не нужно) и сопоставление, которое включает сравнение без учета регистра и диакритических знаков (и многое другое). Я использовал только версию библиотек C++, но, похоже, у них есть и версия Java.
Существуют методы для выполнения нормализованных сравнений, как указано в @Coincoin, и могут даже учитывать локаль - например (и это пример сортировки, а не строгое равенство), традиционно в испанском языке (в Испании) комбинация букв «ll» сортируется между «л» и «м», поэтому «лз» ‹ «лл» ‹ «ма».
Просто используйте strcmp()
для учета регистра и strcmpi()
или stricmp()
для сравнения без учета регистра. Которые оба в заголовочном файле <string.h>
формат:
int strcmp(const char*,const char*); //for case sensitive
int strcmpi(const char*,const char*); //for case insensitive
Использование:
string a="apple",b="ApPlE",c="ball";
if(strcmpi(a.c_str(),b.c_str())==0) //(if it is a match it will return 0)
cout<<a<<" and "<<b<<" are the same"<<"\n";
if(strcmpi(a.c_str(),b.c_str()<0)
cout<<a[0]<<" comes before ball "<<b[0]<<", so "<<a<<" comes before "<<b;
Вывод
apple и ApplE — это одно и то же
а предшествует b, поэтому яблоко предшествует мячу
Поздно на вечеринку, но вот вариант, который использует std::locale
и, таким образом, правильно обрабатывает турецкий язык:
auto tolower = std::bind1st(
std::mem_fun(
&std::ctype<char>::tolower),
&std::use_facet<std::ctype<char> >(
std::locale()));
дает вам функтор, который использует активную локаль для преобразования символов в нижний регистр, который вы затем можете использовать через std::transform
для генерации строк в нижнем регистре:
std::string left = "fOo";
transform(left.begin(), left.end(), left.begin(), tolower);
Это также работает для строк на основе wchar_t
.
Просто примечание о том, какой метод вы, наконец, выберете, если этот метод включает использование strcmp
, что предлагают некоторые ответы:
strcmp
вообще не работает с данными Unicode. В общем, он даже не работает с кодировками Unicode на основе байтов, такими как utf-8, поскольку strcmp
выполняет только побайтовые сравнения, а кодовые точки Unicode, закодированные в utf-8, могут занимать более 1 байта. Единственный конкретный случай Unicode, который strcmp
правильно обрабатывается, - это когда строка, закодированная с помощью байтовой кодировки, содержит только кодовые точки ниже U + 00FF - тогда достаточно побайтового сравнения.
По состоянию на начало 2013 года проект ICU, поддерживаемый IBM, является довольно хорошим ответом на этот вопрос.
ICU — это «полная переносимая библиотека Unicode, полностью соответствующая отраслевым стандартам». Для конкретной проблемы сравнения строк объект Collation делает то, что вы хотите.
Проект Mozilla принял ICU для интернационализации в Firefox в середине 2012 года; вы можете отслеживать инженерное обсуждение, включая вопросы систем сборки и размера файла данных, здесь:
Похоже, что приведенные выше решения не используют метод сравнения и снова реализуют общее количество, поэтому вот мое решение и надеюсь, что оно сработает для вас (оно работает нормально).
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
string tolow(string a)
{
for(unsigned int i=0;i<a.length();i++)
{
a[i]=tolower(a[i]);
}
return a;
}
int main()
{
string str1,str2;
cin>>str1>>str2;
int temp=tolow(str1).compare(tolow(str2));
if(temp>0)
cout<<1;
else if(temp==0)
cout<<0;
else
cout<<-1;
}
Простой способ сравнить две строки в С++ (проверено для Windows) — использовать _stricmp.
// Case insensitive (could use equivalent _stricmp)
result = _stricmp( string1, string2 );
Если вы хотите использовать с std::string, пример:
std::string s1 = string("Hello");
if ( _stricmp(s1.c_str(), "HELLO") == 0)
std::cout << "The string are equals.";
Дополнительные сведения см. здесь: https://msdn.microsoft.com/it-it/library/e0z9k731.aspx
Если вы не хотите использовать библиотеку Boost, то вот решение, использующее только стандартный заголовок ввода-вывода C++.
#include <iostream>
struct iequal
{
bool operator()(int c1, int c2) const
{
// case insensitive comparison of two characters.
return std::toupper(c1) == std::toupper(c2);
}
};
bool iequals(const std::string& str1, const std::string& str2)
{
// use std::equal() to compare range of characters using the functor above.
return std::equal(str1.begin(), str1.end(), str2.begin(), iequal());
}
int main(void)
{
std::string str_1 = "HELLO";
std::string str_2 = "hello";
if(iequals(str_1,str_2))
{
std::cout<<"String are equal"<<std::endl;
}
else
{
std::cout<<"String are not equal"<<std::endl;
}
return 0;
}
Если вам нужно чаще сравнивать исходную строку с другими строками, одним из элегантных решений является использование регулярных выражений.
std::wstring first = L"Test";
std::wstring second = L"TEST";
std::wregex pattern(first, std::wregex::icase);
bool isEqual = std::regex_match(second, pattern);
error: conversion from 'const char [5]' to non-scalar type 'std::wstring {aka std::basic_string<wchar_t>}' requested
15.05.2015 bool insensitive_c_compare(char A, char B){
static char mid_c = ('Z' + 'a') / 2 + 'Z';
static char up2lo = 'A' - 'a'; /// the offset between upper and lowers
if ('a' >= A and A >= 'z' or 'A' >= A and 'Z' >= A)
if ('a' >= B and B >= 'z' or 'A' >= B and 'Z' >= B)
/// check that the character is infact a letter
/// (trying to turn a 3 into an E would not be pretty!)
{
if (A > mid_c and B > mid_c or A < mid_c and B < mid_c)
{
return A == B;
}
else
{
if (A > mid_c)
A = A - 'a' + 'A';
if (B > mid_c)/// convert all uppercase letters to a lowercase ones
B = B - 'a' + 'A';
/// this could be changed to B = B + up2lo;
return A == B;
}
}
}
это, вероятно, можно было бы сделать намного эффективнее, но вот громоздкая версия со всеми ее битами.
не все так портативно, но хорошо работает с тем, что есть на моем компьютере (понятия не имею, мне нравятся картинки, а не слова)
Простой способ сравнить строки, которые отличаются только строчными и заглавными буквами, — это выполнить сравнение ascii. Все прописные и строчные буквы в таблице ascii отличаются на 32 бита, используя эту информацию, мы имеем следующее...
for( int i = 0; i < string2.length(); i++)
{
if (string1[i] == string2[i] || int(string1[i]) == int(string2[j])+32 ||int(string1[i]) == int(string2[i])-32)
{
count++;
continue;
}
else
{
break;
}
if(count == string2.length())
{
//then we have a match
}
}
boost::iequals
подчиняетсяstd::locale()
, который не может справиться с этими вещами. Все, что не относится к отделению интенсивной терапии, на данный момент лжет сквозь зубы. 07.02.2020