Учу C++ по книжке Страуструпа, не выводятся русские символы.
Вот код:
#include
#include
using namespace std;
int main()
{
setlocale(LC_ALL, "Russian");
string previous = " ";
string current;
while (cin >> current)
{
if (previous == current)
{
cout << "Повторяющееся слово: " << current << endl;
}
previous = current;
}
cin.get();
return 0;
}
"Повторяющееся слово: " - отображается нормально благодаря setlocale.
То что после - крякозяблы, хотя повторяющееся слова находит.
setlocale пробовал разные (0, ""), "", "Rus" и пр.
В Code::Blocks всё работает и без крякозяблов. Даже без setlocale.
Ответы
Ответ 1
Для данной задачи существует множество решений. Если вам нужно быстрое и не обязательн
универсальное решение, чтобы сильно не разбираться, прокручивайте к разделу «Менее правильные, но пригодные решения».
Правильное, но сложное решение
Для начала, проблема у консоли Windows состоит в том, что её шрифты, которые стоя
«по умолчанию», показывают не все символы. Вам следует сменить шрифт консоли на юникодный
это позволит работать даже на английской Windows. Если вы хотите поменять шрифт только для вашей программы, в её консоли нажмите на иконку в левом верхнем углу → Свойства → Шрифт. Если хотите поменять для всех будущих программ, то же самое, только заходите в Умолчания, а не Свойства.
Lucida Console и Consolas справляются со всем, кроме иероглифов. Если ваши консольны
шрифты позволят, вы сможете вывести и 猫, если нет, то лишь те символы, которые поддерживаются.
Дальнейшее рассмотрение касается лишь Microsoft Visual Studio. Если у вас друго
компилятор, пользуйтесь предложенными на свой страх и риск, никакой гарантии нету.
Теперь, кодировка входных файлов компилятора. Компилятор Microsoft Visual Studi
(по крайней мере, версии 2012 и 2013) компилирует исходники в однобайтных кодировка
так, как будто бы они на самом деле в ANSI-кодировке, то есть для случая русской систем
— CP1251. Это означает, что кодировка исходников в CP866 — неправильна. (Это важно, если вы используете L"..."-строки.) С другой стороны, если вы храните исходники в CP1251, то эти же исходники не будут нормально собираться на нерусской Windows. Поэтому стоит хранить исходники в Unicode (например, UTF-8).
Настроив среду, перейдём к решению собственно задачи.
Правильным решением является уйти от однобайтных кодировок, и использовать Unicod
в программе. При этом вы получите правильный вывод не только кириллицы, но и поддержк
всех языков (изображение отсутствующих в шрифтах символов будет отсутствовать, но вы сможете с ними работать). Для Windows это означает переход с узких строк (char*, std::string) на широкие (wchar_t*, std::wstring), и использование кодировки UTF-16 для строк.
(Ещё одна проблема, которую решает использование широких строк: узкие строки пр
компиляции кодируются в однобайтную кодировку используя текущую системную кодовую страницу, то есть, ANSI-кодировку. Если вы компилируете вашу программу на английской Windows, это приведёт к очевидным проблемам.)
Вам нужно _setmode(_fileno(...), _O_U16TEXT); для переключения режима консоли:
#include
#include
#include
int wmain(int argc, wchar_t* argv[])
{
_setmode(_fileno(stdout), _O_U16TEXT);
_setmode(_fileno(stdin), _O_U16TEXT);
_setmode(_fileno(stderr), _O_U16TEXT);
std::wcout << L"Unicode -- English -- Русский -- Ελληνικά -- Español." << std::endl;
// или
wprintf(L"%s", L"Unicode -- English -- Русский -- Ελληνικά -- Español.\n");
return 0;
}
Такой способ должен работать правильно с вводом и выводом, с именами файлов и перенаправлением потоков.
Важное замечание: потоки ввода-вывода находятся либо в «широком», либо в «узком
состоянии — то есть, в них выводится либо только char*, либо только wchar_t*. После первого вывода переключение не всегда возможно. Поэтому такой код:
cout << 5; // или printf("%d", 5);
wcout << L"привет"; // или wprintf(L"%s", L"привет");
вполне может не сработать. Используйте только wprintf/wcout.
Если очень не хочется переходить на Unicode, и использовать однобайтную кодировку
будут возникать проблемы. Для начала, символы, не входящие в выбранную кодировку (например
для случая CP1251 — базовый английский и кириллица), работать не будут, вместо них буде
вводиться и выводиться абракадабра. Кроме того, узкие строковые константы имеют ANSI-кодировку, а это значит, что кириллические строковые литералы на нерусской системе не сработают (в них будет зависимая от системной локали абракадабра). Держа в голове эти проблемы, переходим к изложению следующей серии решений.
Менее правильные, но пригодные решения
В любом случае, поставьте юникодный шрифт в консоли. (Это первый абзац «сложного» решения.)
Убедитесь, что ваши исходники в кодировке CP 1251 (это не само собой разумеется
особенно если у вас не русская локаль Windows). Если при добавлении русских букв и сохранении Visual Studio ругается на то, что не может сохранить символы в нужной кодировке, выбирайте CP 1251.
(1) Если компьютер ваш, вы можете поменять кодовую страницу консольных программ на вашей системе. Для этого сделайте вот что:
Запустите Regedit.
На всякий пожарный экспортируйте куда-нибудь реестр (этот шаг все почему-то пропускают, так что когда всё сломается, мы вас предупреждали).
В разделе HKEY_CURRENT_USER\Console найдите ключ CodePage (если нету, создайте ключ с таким названием и типом DWORD).
Установите значение по ключу (левая клавиша/изменить/Система счисления = десятичная) на 1251.
Не забудьте перегрузиться после изменений в реестре.
Преимущества способа: примеры из книг начнут работать «из коробки». Недостатки: смен
реестра может повлечь за собой проблемы, кодировка консоли меняется глобально и перманентн
— это может повлиять сломать другие программы. Плюс эффект будет только на вашем компьютере (и на других, у которых та же кодировка консоли). Плюс общие проблемы неюникодных способов.
Примечание. Установка глобальной кодовой страницы консоли через параметр реестр
HKEY_CURRENT_USER\Console\CodePage не работает в Windows 10, вместо него будет использован
кодовая страница OEM - предположительно баг в conhost. При этом установка кодовой страницы консоли на уровне конкретного приложения (HKEY_CURRENT_USER\Console\(путь к приложению)\CodePage) работает.
(2) Вы можете поменять кодировку только вашей программы. Для этого нужно сменит
кодировку консоли программным путём. Из вежливости к другим программам не забудьте потом вернуть кодировку на место!
Это делается либо при помощи вызова функций
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
в начале программы, либо про помощи вызова внешней утилиты
system("chcp 1251");
(То есть, у вас должно получиться что-то вроде
#include
int main(int argc, char* argv[])
{
std::system("chcp 1251");
...
или
#include
int main(int argc, char* argv[])
{
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
...
и дальше обыкновенный код программы.)
Можно обернуть эти вызовы в класс, чтобы воспользоваться плюшками автоматического управления временем жизни объектов C++.
Пример:
#include
#include
int chcp(unsigned codepage)
{
// составить команду из кусочков
std::string command("chcp ");
command += codepage;
// выполняем команду и возвращаем результат
return !std::system(command.c_str());
}
// этот код будет запущен перед main
static int codepage_is_set = chcp(1251);
(если выполняете задание из Страуструпа можно вставить в конец заголовочного файла std_lib_facilities.h)
Или так:
#include
class ConsoleCP
{
int oldin;
int oldout;
public:
ConsoleCP(int cp)
{
oldin = GetConsoleCP();
oldout = GetConsoleOutputCP();
SetConsoleCP(cp);
SetConsoleOutputCP(cp);
}
// поскольку мы изменили свойства внешнего объекта — консоли, нам нужно
// вернуть всё как было (если программа вылетит, пользователю не повезло)
~ConsoleCP()
{
SetConsoleCP(oldin);
SetConsoleOutputCP(oldout);
}
};
// и в программе:
int main(int argc, char* argv[])
{
ConsoleCP cp(1251);
std::cout << "русский текст" << std::endl;
return 0;
}
Если вам нужен не русский, а какой нибудь другой язык, просто замените 1251 на идентификато
нужной кодировки (список указан ниже в файле), но, разумеется, работоспособность не гарантируется.
Остались методы, которые тоже часто встречаются, приведём их для полноты.
Методы, которые работают плохо (но могут помочь вам)
Метод, который часто рекомендуют — использование конструкции setlocale(LC_ALL, "Russian")
У этого варианта (по крайней мере в Visual Studio 2012) гора проблем. Во-первых, проблем
с вводом русского текста: введённый текст передаётся в программу неправильно! Нерусский текст (например, греческий) при этом вовсе не вводится с консоли. Ну и общие для всех неюникодных решений проблемы.
Ещё один метод, не использующий Unicode — использование функций CharToOem и OemToChar
Этот метод требует перекодировки каждой из строк при выводе, и (кажется) слабо поддаётс
автоматизации. Он также страдает от общих для неюникодных решений недостатков. Кром
того, этот метод не будет работать (не только с константами, но и с runtime-строками!) на нерусской Windows, т. к. там OEM-кодировка не будет совпадать с CP866. В дополнение можно так же сказать что эти функции поставляются не со всеми версиями Visual Studio — например в некоторых версиях VS Express их просто нет.
Источники:
Как выводить на экран и вводить данные типа wchar_t[]?
к сожалению, автор того вопроса пользовался компилятором MinGW под Cygwin и WinXP, что делает большинство современных решений неприменимыми.
Output unicode strings in Windows console app
Conventional wisdom is retarded, aka What the @#%&* is _O_U16TEXT?
What's the difference between printf(“%s”), printf(“%ls”), wprintf(“%s”), and wprintf(“%ls”)?
Русский язык в исходном коде в Dev C++
Code Page Identifiers
Ответ 2
Поэтому стоит хранить исходники в Unicode (например, UTF-8).
Причем сохранить следует с сигнатурой
Ситуацию частично спасает пересохранение исходников в кодировке UTF-8 с обязательны
символом BOM, без него Visual Studio начинает интерпретировать «широкие» строки с кириллице
весьма своеобразно. Однако, указав BOM (Byte Order Mark — метка порядка байтов) кодировки UTF-8 — символ, кодируемый тремя байтами 0xEF, 0xBB и 0xBF, мы получаем узнавание кодировки UTF-8 в любой системе
Пишем по-русски в коде
Ответ 3
Для Clion:
Cygwin - при установке в выборе пакетов нужно найти и отметить всякие cmake, GD
и прочие, кем-нибудь рекомендуемые к установке.
Сlion - File - Settings - Editor - File Encodings: IDE Encoding,
Project Encoding, main.cpp (Ваш исполняемый файл) - UTF-8, Default
encoding for properties files - IBM866
В окне редактора внизу - UTF-8.
Включить в проект заголовочный файл Windows.h
SetConsoleCP(866); SetConsoleOutputCP(866);
Ответ 4
Стоит пояснить кое-что для тех, кто ищет правильный ответ по поводу функции setlocale:
Метод, который часто рекомендуют — использование конструкции setlocale(LC_ALL
"Russian"); У этого варианта (по крайней мере в Visual Studio 2012) гора проблем. Во-первых
проблема с вводом русского текста: введённый текст передаётся в программу неправильно! Нерусский текст (например, греческий) при этом вовсе не вводится с консоли. Ну и общие для всех неюникодных решений проблемы.
Я добавлю по этому методу побольше информации: Его вообще не правильно рекомендуют!
Начнём с первого: Во втором параметре функция принимает не название страны или языка
хотя в некоторых случаях она сработает, а языковый идентификатор, согласно ISO 3166-1. Поэтому правильно и корректно указывать: "ru-RU".
Теперь второе: в документации к этой функции написано чёрным по белому: "If executio
is allowed to continue, the function sets errno to EINVAL and returns NULL." Что буквально толкуется: при возникновении ошибки, функция устанавливает значение переменной errno в EINVAL и возвращает NULL.
В случае возникновения ошибки, errno всегда будет равен EINVAL, что означает: н
верный аргумент. Поэтому её проверять нет смысла, а вот исполнение функции должно быть проверено. Поэтому правильный вызов функции setlocale выглядит следующим образом:
if (setlocale(LC_ALL, "ru-RU") == NULL) {
cout << "Error set locale ru-RU." << endl;
return -1;
// или принудительно ставим таблицу 1251 через SetConsoleCP.
// выше пример есть. И не забываем проверять результат SetConsoleCP
// Если ошибка возникла, то код ошибки смотрим через GetLastError.
}
И не забывайте, что setlocale устанавливает локальную таблицу только для ANSI кодировки, поэтому и не будут отображаться греческие, испанские, китайские и даже японские знаки.
Для русского языка это будет таблица номер 1251.
И важно: почему эта функция является надёжней, нежели прямая установка таблицы символо
через SetConsoleCP, ибо потому, что она переключает все внутренние надстройки именно для раскладки под язык. Начиная от стандарта отображения даты, заканчивая знаками разделителя.
И да, не стоит устанавливать языковый указатель виде "ru", так как в зависимост
от сборки самой ось и имеющихся языковых пакетов, может установиться ru-BY, ru-UA, ru-M
и другие языковые стандарты, значительно отличающиеся от ru-RU. И категорично нельз
указывать "Russia", "Russian", "Russian Federation" (да, такую вакханалию уже встречал пару раз). Хотя функция производит проверку и по названию региона, не всегда в таблице локализации это указано, или может быть указано "Россия" или "Русский" уже на нашей раскладке. Это и есть основная ошибка, из-за которой функция setlocale зачастую отказывается работать.
И да, для приложения, работающего в режиме юникогда, стоит использовать функцию _wsetlocale
Она идентична, и также устанавливает базовые настройки для локализации. Кроме того
если проект приложения в Visual Studio настроен в режим юникода, то и будет работать только _wsetlocale, так как setlocale, по документации, не приспособлена к работе с юникодом вообще никак.
UPD.
Совсем забыл указать, что функция setlocale и _wsetlocale, в случае успеха вернёт именно идентификатор региона. То есть, в нашем случае строку "ru_RU\0".
Комментариев нет:
Отправить комментарий