Кирилица из базы данных MySQL выводится в виде краказабр.
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
| ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
| [216.73.216.0] |
|
|
Правила раздела C/C++: Базы данных
| Страницы: (2) [1] 2 все ( Перейти к последнему сообщению ) |
Кирилица из базы данных MySQL выводится в виде краказабр.
|
|
|
|
|
Здравсвуйте!
При выводи русских символов из базы данных отображаются кракозябры. Подскажите, пожалуйста, как сделать чтобы кирилица отображалась корректно? ![]() ![]() Подключение установлено успешно! Результаты на экране: ====================== _group | name | ====================== 1 | ヤᄉᄑᄌチ | 2 | ロムᄑマ | ====================== Пытался указать кодировку подключения к БД вот таким образром: ![]() ![]() db.executeQuery("SET NAMES 'utf8mb4'"); ![]() ![]() #include "MySQLConnection.h" #include "windows.h" using namespace school; int main() { system("chcp 65001 > nul"); std::string sql_query = "SELECT _group, name FROM learners"; try { MySQLConnection db( "localhost", 3306, "root", "", "school" ); if (db.isConnected()) { std::cout << u8"Подключение установлено успешно!\n\n"; db.executeQuery("SET NAMES 'utf8mb4'"); // ← Добавить // Выводим результаты на экран std::cout << u8"Результаты на экране:" << std::endl; db.executeQuery(sql_query); std::cout << "\n" << std::string(50, '=') << std::endl; } } catch (sql::SQLException& e) { std::cerr << "Ошибка MySQL: " << e.what() << std::endl; } return 0; } ![]() ![]() Подключение установлено успешно! No result available D:\Programming\VS\source\repos\school\x64\Release\school.exe (процесс 31968) завершил работу с кодом 0 (0x0). Нажмите любую клавишу, чтобы закрыть это окно… |
|
Сообщ.
#2
,
|
|
|
|
Всё не так, дружище!!!
Если ты хочешь работать с "усечёнными" кодировками (типа только для Европы) - ты можешь использовать в кодировках своих таблиц что-то типа ascii_general_ci или latin1_bin. Но это будет твой первый самый знаковый фэйл!!! Щяс в мире гомоглобальная глоболизация, и если ты проигноришь восточные знаковые таблицы - ты будешь лузером! А мы с тобой не будем =) Поэтому так, и только так ты сможешь сохранить все (и своё и чисто западное): кодировка таблиц - строго utf8mb4. При этом имеет смысл выбирать collation как utf8mb4_unicode_520_ci. Это последняя из версий сортировок строк по "национальным признакам сортировок". И вот тогда, и только тогда, многие из нас (по крайней мере я) помогут тебе с выводом данных из БД... А пока ты будешь пробовать неизвестные нам сеты кодировок - вряд ли кто тебе что-то подскажет. Хотя вероятность ненулевая есть. |
|
Сообщ.
#3
,
|
|
|
|
А таблица в базе в какой кодировке?
Табличку на экране кто рисует? Не вижу кода вывода. Только вывод отчеркушки. |
|
Сообщ.
#4
,
|
|
|
|
Цитата sharky72 @ А таблица в базе в какой кодировке? В utf8_general_ci. Добавлено Цитата sharky72 @ Табличку на экране кто рисует? Не вижу кода вывода. Только вывод отчеркушки. ![]() ![]() namespace school { // ================================================= Класс для работы таблицы ===================================================== // Реализация методов MyString MyString::MyString() : std::wstring() {} MyString::MyString(std::wstring s) : std::wstring(s) {} MyString::MyString(const wchar_t* s) : std::wstring(s) {} // Явная инстанциация шаблонов template class MyVector<MyString>; template class MyVector<MyVector<MyString>>; // Реализация TableFormatter TableFormatter::TableFormatter(size_t& cmax, MyVector<MyVector<MyString>>& data, MyVector<MyString>& header) : m(data), h(header), CMAX(cmax) { } void TableFormatter::format() { calculateMaxColumns(); resizeRows(); addRowNumbers(); calculateColumnWidths(); alignColumns(); alignHeaders(); } void TableFormatter::calculateMaxColumns() { for (const auto& e : m) if (CMAX < e.size()) CMAX = e.size(); } void TableFormatter::resizeRows() { for (auto& e : m) if (CMAX > e.size()) e.resize(CMAX); if (CMAX > h.size()) h.resize(CMAX); } void TableFormatter::addRowNumbers() { for (size_t r = 0; r < m.size(); ++r) { m[r][0] = StringConverter::toString(r + 1); } } void TableFormatter::calculateColumnWidths() { std::vector<size_t> n(CMAX, 0); // Инициализация ширинами заголовков for (size_t i = 0; i < h.size() && i < CMAX; ++i) { n[i] = h[i].size(); } // Нахождение максимальных ширин for (size_t c = 0; c < CMAX; ++c) { for (size_t r = 0; r < m.size(); ++r) { if (n[c] < m[r][c].size()) n[c] = m[r][c].size(); } } // Выравнивание столбцов for (size_t c = 0; c < CMAX; ++c) { for (size_t r = 0; r < m.size(); ++r) { if (n[c] > m[r][c].size()) m[r][c].resize(n[c], ' '); } } // Выравнивание заголовков for (size_t c = 0; c < CMAX; ++c) { if (h[c].size() < m[0][c].size()) h[c].resize(m[0][c].size(), ' '); } } void TableFormatter::alignColumns() {} void TableFormatter::alignHeaders() {} // Реализация TablePrinter TablePrinter::TablePrinter( size_t& cmax, MyVector<MyVector<MyString>>& data, MyVector<MyString>& header) : CMAX(cmax), m(data), h(header) { } void TablePrinter::print(std::wostream& o) { std::wstring line = createLine(); o << line << '\n'; printHeader(o); o << '\n' << line << '\n'; printRows(o); o << line << '\n' << '\n'; } std::wstring TablePrinter::createLine() { std::wstring line; for (size_t c = 0; c < CMAX; ++c) { line += std::wstring(h[c].size() + 3, '='); } return line; } void TablePrinter::printHeader(std::wostream& o) { for (size_t c = 0; c < CMAX; ++c) { o << ' ' << h[c] << " |"; } } void TablePrinter::printRows(std::wostream& o) { for (const auto& r : m) { for (const auto& e : r) { o << ' ' << e << " |"; } o << '\n'; } } // Реализация Tab Tab::Tab() = default; MyVector<MyString>& Tab::operator[](const size_t i) { if (i >= m.size()) m.resize(i + 1); return *(m.begin() + i); } void Tab::formater() { TableFormatter formatter(CMAX, m, h); formatter.format(); } // Реализация оператора вывода std::wostream& operator<<(std::wostream& o, Tab& t) { t.formater(); TablePrinter printer(t.CMAX, t.m, t.h); printer.print(o); return o; } // Реализация TabTest Tab TabTest::createTabFromResultSet(std::unique_ptr<sql::ResultSet> res) { ///-----------------------| /// Создаём табулятор. | ///-----------------------: Tab tab; if (!res) return tab; try { sql::ResultSetMetaData* meta = res->getMetaData(); int columnCount = meta->getColumnCount(); // Создаем заголовки for (int i = 1; i <= columnCount; i++) { std::string columnName = meta->getColumnName(i); std::wstring wColumnName(columnName.begin(), columnName.end()); tab.head(wColumnName); } // Заполняем данными int row = 0; while (res->next()) { for (int col = 1; col <= columnCount; col++) { std::string value = res->getString(col); std::wstring wValue(value.begin(), value.end()); tab[row][col - 1] = res->wasNull() ? L"NULL" : wValue; } row++; } } catch (const sql::SQLException& e) { std::wcout << L"Ошибка при создании таблицы: " << e.what() << std::endl; } return tab; }; // ========================================= Класс для работы с данными базы данных MySQL ========================================= MySQLConnection::MySQLConnection(const std::string& host, int port, const std::string& user, const std::string& password, const std::string& database) { sql::mysql::MySQL_Driver* driver = sql::mysql::get_mysql_driver_instance(); std::string connectionString = "tcp://" + host + ":" + std::to_string(port); connection.reset(driver->connect(connectionString, user, password)); connection->setSchema(database); } void MySQLConnection::executeQuery(const std::string& query) { std::unique_ptr<sql::Statement> stmt(connection->createStatement()); std::unique_ptr<sql::ResultSet> res(stmt->executeQuery(query)); TabTest tabTest; Tab resultTab = tabTest.createTabFromResultSet(std::move(res)); std::wcout << resultTab; // Вывести когда нужно } bool MySQLConnection::isConnected() { return connection.get() != nullptr && !connection->isClosed(); } } Прикреплённая картинка
|
|
Сообщ.
#5
,
|
|
|
|
![]() ![]() for (int col = 1; col <= columnCount; col++) { std::string value = res->getString(col); std::wstring wValue(value.begin(), value.end()); tab[row][col - 1] = res->wasNull() ? L"NULL" : wValue; } Вообще то конвертация utf8 -> utf16 так не работает. Вернее работает, но только для первой половины ASCII таблицы, поэтому названия столбцов у вас выводятся корректно. в utf8 один символ может быть представлен последовательностью от 1 до 4 байт, т.е. символ в кодировке имеет переменную длину. Для кириллицы например 2. А вы тупо копируете каждый char в wchar_t, т.е. 1 байт просто копируете в 2 (второй будет нулевой). Хотя надо было считать 2 и преобразовать их в Unicode символ. Почитайте про utf8. https://ru.wikipedia.org/wiki/UTF-8 Поищите на развалах функцию конвертации utf8->utf16 и наоборот. Ну или воспользуйтесь системной MultiByteToWideChar и WideCharToMultiByte ![]() ![]() std::wstring ConvertUtf8ToWide(const std::string& str) { int count = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.size(), NULL, 0); std::wstring wstr(count, 0); MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.size(), &wstr[0], count); return wstr; } |
|
Сообщ.
#6
,
|
|
|
|
Вот еще одну функцию нашел...
![]() ![]() std::wstring utf8_to_utf16(const std::string& utf8) { wstring_convert<codecvt_utf8_utf16<char16_t>, char16_t> convert; u16string utf16 = convert.from_bytes(utf8); wstring wstr(utf16.begin(), utf16.end()); return wstr; } Только я не понял, где их нужно вызывать и что им передавать? |
|
Сообщ.
#7
,
|
|
|
|
В моем сообщении процитирован ваш код. Вот его надо заменить. То же самое для названий колонок.
У вас база возвращает строки в UTF8. Вам надо их преобразовать в utf16 (wide char) для вывода в консоль. (Которая wide char преобразует в utf8 или другую кодировку консоли )![]() ![]() for (int col = 1; col <= columnCount; col++) { tab[row][col - 1] = res->wasNull() ? L"NULL" : ConvertUtf8ToWide(res->getString(col)); } utf8 можно хранить в std::string. Но для хранения строки в std::wstring нужно проводить преобразование кодовой страницы. CP_UTF8 -> CP_UNICODE P.S. utf8 может и прекрасно, как нам вещает majesto, но это эрзац созданный для экономии памяти и у которого дофига подводных камней. Да и вообще CodePage это сплошная головная боль. P.P.S. Кстати учитывайте тот фактор что SET NAMES подразумевает что ВСЕ общение между вами и сервером идет в указанной кодовой странице (см. доку на MySql). |
|
|
|
|
|
Цитата sharky72 @ В моем сообщении процитирован ваш код. Вот его надо заменить. То же самое для названий колонок. У вас база возвращает строки в UTF8. Вам надо их преобразовать в utf16 (wide char) для вывода в консоль. (Которая wide char преобразует в utf8 или другую кодировку консоли )![]() ![]() for (int col = 1; col <= columnCount; col++) { tab[row][col - 1] = res->wasNull() ? L"NULL" : ConvertUtf8ToWide(res->getString(col)); } utf8 можно хранить в std::string. Но для хранения строки в std::wstring нужно проводить преобразование кодовой страницы. CP_UTF8 -> CP_UNICODE P.S. utf8 может и прекрасно, как нам вещает majesto, но это эрзац созданный для экономии памяти и у которого дофига подводных камней. Да и вообще CodePage это сплошная головная боль. P.P.S. Кстати учитывайте тот фактор что SET NAMES подразумевает что ВСЕ общение между вами и сервером идет в указанной кодовой странице (см. доку на MySql). Спасибо, заработало!!! Но есть другой вопрос. В таблиц первый столбец под иминем id, второй столбец - под иминем _group. Столбец под иминем id содержаться значения: 1, а столбец под иминем _group содержит значение 11. Почему при таком запросе: ![]() ![]() std::string sql_query = "SELECT _group FROM learners"; выводится такой результат?: ![]() ![]() Подключение установлено успешно! Результаты на экране: ========= _group | ========= 1 | ========= То есть, заголовок выводится прввилно, а содержимое выводится первого столбца. Почему так? |
|
Сообщ.
#9
,
|
|
|
|
А вы дебагом сами пройтись можете?
Насколько я понимаю запрос должен вернуть один столбец. Что вернулось в res->getString() Вы уверены что вам возвращают именно строку? Если в столбце должно быть число. Короче надо убедится что это данные именно из id. А не затертая 1 в числе 11 |
|
Сообщ.
#10
,
|
|
|
|
Цитата sharky72 @ ... Короче надо убедится что это данные именно из id. А не затертая 1 в числе 11 Я изменил значения столбца _group на 66 и 44, и вот что получается. При таком запросе: ![]() ![]() std::string sql_query = "SELECT id, _group FROM learners"; Вот такой вывод: ![]() ![]() Подключение установлено успешно! Результаты на экране: ============== id | _group | ============== 1 | 66 | 2 | 44 | ============== ================================================== D:\Programming\VS\source\repos\school\x64\Release\school.exe (процесс 1308) завершил работу с кодом 0 (0x0). Нажмите любую клавишу, чтобы закрыть это окно: А при таком запросе: ![]() ![]() std::string sql_query = "SELECT _group FROM learners"; Вот такой вывод: ![]() ![]() Подключение установлено успешно! Результаты на экране: ========= _group | ========= 1 | 2 | ========= ================================================== D:\Programming\VS\source\repos\school\x64\Release\school.exe (процесс 8784) завершил работу с кодом 0 (0x0). Нажмите любую клавишу, чтобы закрыть это окно: |
|
Сообщ.
#11
,
|
|
|
|
Не знаю. Я попробовал у себя сделать подобный запрос - и у меня база возвращает один столбец и в нем правильные данные для этого столбца.
Выложите свой проект. И опишите параметры вашей таблицы. Попробую собрать и проверить на своем myqsl. |
|
Сообщ.
#12
,
|
|
|
|
Я вот тут посмотрел участки кода от ТС. Сразу скажу, что использование std::wstring для "внутренних" манипуляций не лучшая идея. Обычного std::string для работы с UTF-8 вполне достаточно. Единственное, что нужно помнить - длина std::string в частых случаях не соответствует количеству символов. Ну это специфика UTF-8.
Для винды самый простой и быстрый вариант: ![]() ![]() #include <windows.h> #include <string> size_t utf8_count_windows(const std::string& utf8) { int len = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0); return len > 0 ? len - 1 : 0; } Кроссплатформенный вариант: ![]() ![]() #include <string> size_t utf8_count_cross(const std::string& utf8) { size_t len = 0; for (unsigned char c : utf8) if ((c & 0xC0) != 0x80) len++; return len; } Единственный момент со вторым вариантом: неправильно считает, если в строке встречаются графемы, или строка с невалидным содержимым для UTF-8. Ну а std::wstring нужен только для взаимодействия с Windows API. |
|
|
|
|
|
Цитата sharky72 @ Не знаю. Я попробовал у себя сделать подобный запрос - и у меня база возвращает один столбец и в нем правильные данные для этого столбца. Выложите свой проект. И опишите параметры вашей таблицы. Попробую собрать и проверить на своем myqsl. Вот мой проект: https://disk.yandex.ru/d/bgJz7OSEq-lb1g Прикреплённая картинка
Прикреплённая картинка
|
|
Сообщ.
#14
,
|
|
|
|
Ох... Плохо когда слушают советы, но не делают то что говорят.
1. VC++ Directories это глобальная вещь. Что то там править нужно только в крайнем случае. Inculde/Lib directories правятся только в c++/link настройках вашего проект 2. Используйте макросы Visual Studio! Иначе ваш проект будет собираться ТОЛЬКО на вашем компьютере. Абсолютные пути - зло! 3. Если сказано заменить код, то его заменяют, а не оставляют старый и добавляют новый. Должно быть так ![]() ![]() // Создаем заголовки for (int i = 1; i <= columnCount; i++) { std::string columnName = meta->getColumnName(i); tab.head(ConvertUtf8ToWide(columnName)); } // Заполняем данными int row = 0; while (res->next()) { for (int col = 1; col <= columnCount; col++) { std::string value = res->getString(col); tab[row][col - 1] = res->wasNull() ? L"NULL" : ConvertUtf8ToWide(value); } row++; } 4. Проблема в том что вы сами выводите addRowNumbers, а не значения столбцов и как раз эту нумерацию и видите. ![]() ![]() void TableFormatter::format() { calculateMaxColumns(); resizeRows(); //addRowNumbers(); calculateColumnWidths(); alignColumns(); alignHeaders(); } Я немного причесал ваш проект на предмет путей к include и lib В добавок добавил debug варианты mysqlconn и добавил их в правильные пути для запуска включая плагины. Так что теперь можете запускать и Debug вариант. https://disk.yandex.ru/d/YpqYjdmPHLORkw |
|
Сообщ.
#15
,
|
|
|
|
Цитата sharky72 @ Ох... Плохо когда слушают советы, но не делают то что говорят. 1. VC++ Directories это глобальная вещь. Что то там править нужно только в крайнем случае. Inculde/Lib directories правятся только в c++/link настройках вашего проект 2. Используйте макросы Visual Studio! Иначе ваш проект будет собираться ТОЛЬКО на вашем компьютере. Абсолютные пути - зло! 3. Если сказано заменить код, то его заменяют, а не оставляют старый и добавляют новый. Должно быть так ![]() ![]() // Создаем заголовки for (int i = 1; i <= columnCount; i++) { std::string columnName = meta->getColumnName(i); tab.head(ConvertUtf8ToWide(columnName)); } // Заполняем данными int row = 0; while (res->next()) { for (int col = 1; col <= columnCount; col++) { std::string value = res->getString(col); tab[row][col - 1] = res->wasNull() ? L"NULL" : ConvertUtf8ToWide(value); } row++; } 4. Проблема в том что вы сами выводите addRowNumbers, а не значения столбцов и как раз эту нумерацию и видите. ![]() ![]() void TableFormatter::format() { calculateMaxColumns(); resizeRows(); //addRowNumbers(); calculateColumnWidths(); alignColumns(); alignHeaders(); } Я немного причесал ваш проект на предмет путей к include и lib В добавок добавил debug варианты mysqlconn и добавил их в правильные пути для запуска включая плагины. Так что теперь можете запускать и Debug вариант. https://disk.yandex.ru/d/YpqYjdmPHLORkw Спасибо большое! Все работает!!! ![]() ![]() Подключение установлено успешно! Результаты на экране: ============================================================ _group | name | sex | age | teleg_id | teleg_nickname | ============================================================ 44 | Иван | м | 75 | XXXXXXXXX | Иванко | 66 | Николай | м | 40 | ХХХХХХХХХ | нико | ============================================================ ================================================== D:\Programming\VS\source\repos\school\x64\Release\school.exe (процесс 18024) завершил работу с кодом 0 (0x0). Нажмите любую клавишу, чтобы закрыть это окно: А насчет Debug... Да, проект запускаеться, но выходят ошибки. ![]() ![]() Unable to connect to D:\Programming\VS\source\repos\school\x64\Debug\school.exe (процесс 6620) завершил работу с кодом 0 (0x0). Нажмите любую клавишу, чтобы закрыть это окно: ![]() ![]() Missing option OPT_MULTI_HOST = true D:\Programming\VS\source\repos\school\x64\Debug\school.exe (процесс 28784) завершил работу с кодом 0 (0x0). Нажмите любую клавишу, чтобы закрыть это окно: ![]() ![]() Unable to connect to X D:\Programming\VS\source\repos\school\x64\Debug\school.exe (процесс 39012) завершил работу с кодом 0 (0x0). Нажмите любую клавишу, чтобы закрыть это окно: |