Mencetak em-dash ke jendela konsol menggunakan printf? [duplikat]

Masalah sederhana: Saya sedang menulis program ruang obrolan dalam C++ (tetapi pada dasarnya bergaya C) untuk sebuah kelas, dan saya mencoba mencetak, “#help — menampilkan daftar perintah...” ke jendela keluaran . Meskipun saya dapat menggunakan dua tanda hubung (--) untuk mendapatkan kira-kira efek yang sama, saya lebih suka menggunakan em-dash (—). printf(), namun, tampaknya tidak mendukung pencetakan em-dash. Sebaliknya, konsol hanya mencetak karakter, ù, sebagai gantinya, meskipun faktanya memasukkan em-dash langsung ke prompt berfungsi dengan baik.

Bagaimana cara agar karakter Unicode sederhana ini muncul?

Melihat kode kunci alt Windows, menurut saya menarik bagaimana alt+0151 adalah "—" dan alt+151 adalah "ù". Apakah ini ada hubungannya dengan masalah saya, atau hanya kebetulan belaka?


person Gabe Troyan    schedule 14.09.2017    source sumber
comment
Masalahnya adalah konsol windows menggunakan halaman kode bukan Unicode.   -  person Richard Critten    schedule 14.09.2017
comment
Masalahnya adalah em-dash adalah karakter unicode dan Anda mencoba mencetaknya dalam string ascii   -  person code1x1.de    schedule 14.09.2017
comment
Coba ini std::wcout ‹‹ wchar_t(0x2014); dan baca utas itu stackoverflow.com/questions/33029906/   -  person code1x1.de    schedule 14.09.2017
comment
Anda memerlukan atau menggunakan keluaran unicode WriteConsoleW atau terlebih dahulu mengonversi unicode ke multibyte dengan menggunakan WideCharToMultiByte(GetConsoleOutputCP(),..) untuk digunakan dalam fungsi keluaran A   -  person RbMm    schedule 14.09.2017
comment
@RichardCritten - untuk konsol windows asli persis Unicode. dan jika menggunakan keluaran unicode - tidak ada masalah. halaman kode ini hanyalah nilai saat ini untuk melakukan konversi dari multi-byte ke unicode   -  person RbMm    schedule 14.09.2017
comment
@RbMm pernyataan C printf ("—\n"); dijalankan di output konsol Windows saya ÔÇö.   -  person Weather Vane    schedule 14.09.2017
comment
@WeatherVane - lalu apa? Anda perlu menggunakan WriteConsoleW dengan L"—\n". apakah Anda mengerti mengapa ada kesalahan saat Anda menggunakan versi ansi? karena menggunakan halaman kode lain (secara default CP_OEMCP) untuk menerjemahkan string Anda ke unicode (di src Anda, CP_ACP Anda digunakan)   -  person RbMm    schedule 14.09.2017
comment
@RbMm itulah sebabnya saya menandai komentar pertama dari Richard.   -  person Weather Vane    schedule 14.09.2017
comment
@WeatherVane - komentarnya salah. windows adalah sistem unicode dan unicode digunakan hampir di mana saja. di konsol juga. konsol windows adalah unicode. ketika Anda meneruskan string unicode untuk dicetak - itu akan mencetaknya apa adanya. dan L—\n ditampilkan dengan benar. ketika Anda menggunakan fungsi ansi untuk menghasilkan - konsol pertama terjemahkan string multibyte ke unicode. kesalahan karena kode sumber dan konsol Anda menggunakan halaman kode berbeda untuk diterjemahkan   -  person RbMm    schedule 14.09.2017
comment
@RbMm Saya tidak menyadari pada awalnya Anda berbicara tentang fungsi konsol Windows dan bukan konsol Windows.   -  person Weather Vane    schedule 14.09.2017
comment
@WeatherVane - saya mencoba mengatakan bahwa untuk konsol windows unicode adalah asli. semua teks dicetak sebagai unicode saja. ketika versi api A dipanggil - semua data string diterjemahkan terlebih dahulu ke unicode dan kemudian disebut versi api W. kesalahan saat menggunakan versi A (atau crt shell) dalam terjemahan halaman kode yang salah   -  person RbMm    schedule 14.09.2017
comment
Jawaban yang ditautkan oleh @ sata300.de adalah kunci untuk melakukan hal ini dengan mudah dalam banyak kasus, yaitu memanggil _setmode(_fileno(stdout), _O_U16TEXT) saat program dimulai dan menggunakan I/O C/C++ berkarakter lebar seperti wprintf dan std::wcout.   -  person Eryk Sun    schedule 15.09.2017
comment
Komentar yang mendapat suara positif dari @RichardCritten mungkin hanya diucapkan secara samar-samar. Saya pikir ini mengacu pada bagaimana konsol (misalnya conhost.exe) mendekode byte yang ditulis menggunakan halaman kode keluaran saat ini (yaitu GetConsoleOutputCP). Menurut saya komentar tersebut tidak berarti konsol secara umum tidak mendukung Unicode. Meskipun mengenai yang terakhir, konsol terbatas pada BMP (misalnya kode pengganti ditampilkan sebagai karakter default daripada mendekode pasangan pengganti UTF-16); tidak mendukung penggabungan kode; dan memerlukan font monospace dengan mesin terbang untuk karakternya (penghubung font manual membantu).   -  person Eryk Sun    schedule 15.09.2017
comment
@eryksun - current output codepage - ini adalah kalimat yang benar-benar salah. keluaran konsol selalu dalam unicode. GetConsoleOutputCP - ini adalah halaman kode untuk menerjemahkan string multi-byte ke unicode, sebelum menampilkannya   -  person RbMm    schedule 15.09.2017
comment
@RbMm, mungkin Anda hanya salah paham dengan apa yang saya tulis. Saya mengatakan konsol ... sedang mendekode byte yang ditulis menggunakan halaman kode keluaran saat ini (yang terakhir adalah terminologi Microsoft). Misalnya, WriteFile dipanggil dengan string byte. Di Windows 8+ ini memanggil NtWriteFile untuk File yang diberikan pada perangkat ConDrv. Konsol terlampir (conhost.exe) menunggu di NtDeviceIoControlFile, yang dilengkapi dengan permintaan untuk menulis byte tertentu ke buffer layar target. Konsol pertama-tama mendekode byte ini menggunakan halaman kode keluarannya dengan memanggil MultiByteToWideChar dan sejenisnya.   -  person Eryk Sun    schedule 15.09.2017
comment
@RbMm, jika Anda tidak menyukai istilah halaman kode keluaran, ambillah dengan Microsoft.   -  person Eryk Sun    schedule 15.09.2017
comment
@eryksun - tapi baca selanjutnya di halaman ini - Konsol menggunakan halaman kode keluarannya untuk menerjemahkan nilai karakter yang ditulis oleh berbagai fungsi keluaran ke dalam gambar yang ditampilkan di jendela konsol. jadi ini digunakan untuk < b>menerjemahkan tetapi tidak untuk output. outputnya selalu dalam unicode   -  person RbMm    schedule 15.09.2017
comment
@RbMm, saya hanya menggunakan nama halaman kode yang dikembalikan oleh GetConsoleOutputCP, yaitu halaman kode keluaran. Sejauh yang saya tahu, Anda mempermasalahkan nama itu. Tidak ada yang saya katakan salah tentang operasinya.   -  person Eryk Sun    schedule 15.09.2017
comment
@eryksun - dalam hal ini tulisannya buruk di msdn. benar, ucapkan halaman kode terjemahan. dan semua alasan saya mencoba menjelaskan - semua kesalahan karena terjemahan yang salah (2 terjemahan digunakan unicode-›multibyte-›unicode dalam banyak kasus dengan halaman kode yang berbeda). hanya satu cara untuk menghindari terjemahan ini - gunakan WriteConsoleW   -  person RbMm    schedule 15.09.2017
comment
@RbMm, cara sederhana (tapi tetap non-portable) adalah melalui _setmode(_fileno(stdout), _O_U16TEXT) lalu gunakan fungsi CRT berkarakter lebar seperti wprintf. Ini tidak terlalu efisien karena CRT akhirnya memanggil _putwch_nolock dalam satu lingkaran pada karakter, dan dengan demikian membuat panggilan WriteConsoleW untuk setiap karakter. Namun ini adalah konsol I/O interaktif, jadi kami tidak memerlukan kecepatan dan efisiensi ekstrem.   -  person Eryk Sun    schedule 15.09.2017
comment
@eryksun - ya, dengan _setmode(_fileno(stdout), _O_U16TEXT) wprintf mulai gunakan WriteConsoleW (char demi char) sebagai gantinya WriteFile. tapi saya pribadi tidak mengerti sama sekali - untuk apa semua masalah ini dengan CRT dan/atau output ansi ketika bisa memanggil WriteConsoleW dan tidak ada masalah sama sekali   -  person RbMm    schedule 15.09.2017
comment
@RbMm, lebih mudah saat menulis kode lintas platform dan mengadaptasi kode yang sudah ada.   -  person Eryk Sun    schedule 15.09.2017
comment
@eryksun - jika mencoba kode lintas platform mungkin ya. lagi pula ini tidak mudah. jika menulis untuk windows saja - perlu menggunakan WriteConsoleW dan main - printf dengan cara apa pun menampilkan sebagai -. hanya WriteConsoleW yang memberikan tampilan yang benar   -  person RbMm    schedule 15.09.2017
comment
Anda juga dapat menelepon SetConsoleOutputCP(CP_UTF8). Opsi kompiler /utf-8 memaksa literal string UTF-8. Saya tidak akan menggunakan ini sebelum Windows 8, dalam hal ini WriteFile ke konsol salah mengembalikan jumlah karakter yang didekodekan yang ditulis alih-alih jumlah byte yang ditulis. Selain itu, SetConsoleCP(CP_UTF8) tidak berguna untuk input non-ASCII di semua versi karena konsol membuat asumsi buggy bahwa ia mengkodekan ke ANSI (misalnya 1 byte per karakter) ketika ia mengukur buffer untuk WideCharToMultiByte, yang gagal namun ReadFile 'berhasil' dalam membaca nol byte, yaitu EOF.   -  person Eryk Sun    schedule 15.09.2017
comment
Sebenarnya di Windows 10.0.15063 (Pembaruan Pembuat Konten) pembacaan input yang berisi karakter non-ASCII di CP_UTF8 (65001) sedikit 'ditingkatkan'. Rupanya sebelum pengkodean sekarang mereka hanya mengganti semua karakter non-ASCII dengan Unicode NUL, jadi setidaknya tidak terlihat seperti EOF. Hanya saja semua karakter input non-ASCII berakhir sebagai \x00 di buffer.   -  person Eryk Sun    schedule 15.09.2017


Jawaban (2)


windows adalah sistem unicode (UTF-16). konsol unicode juga. jika Anda ingin mencetak teks unicode - Anda perlu (dan ini paling efektif) menggunakan WriteConsoleW

BOOL PrintString(PCWSTR psz)
{
    DWORD n;
    return WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), psz, (ULONG)wcslen(psz), &n, 0);
}
PrintString(L"—");

dalam hal ini dalam file biner Anda akan berupa karakter lebar (2 byte 0x2014) dan konsol mencetaknya apa adanya.

jika fungsi ansi (multi-byte) dipanggil untuk konsol keluaran - seperti WriteConsoleA atau WriteFile - konsol terlebih dahulu menerjemahkan string multi-byte ke unicode melalui MultiByteToWideChar dan sebagai gantinya CodePage akan digunakan nilai yang dikembalikan oleh GetConsoleOutputCP. dan di sini (terjemahan) bisa menjadi masalah jika Anda menggunakan karakter > 0x80

pertama-tama kompiler dapat memberi Anda peringatan: File berisi karakter yang tidak dapat diwakili dalam halaman kode saat ini (angka). Simpan file dalam format Unicode untuk mencegah kehilangan data. (C4819). tetapi bahkan setelah Anda menyimpan file sumber dalam format Unicode, bisa menjadi yang berikutnya:

wprintf(L"ù"); // no warning
printf("ù"); //warning C4566

karena L"ù" disimpan sebagai string karakter lebar (sebagaimana adanya) dalam file biner - di sini semuanya baik-baik saja dan tidak ada masalah dan peringatan apa pun. tetapi "ù" disimpan sebagai char string (string byte tunggal). kompiler perlu mengonversi string lebar "ù" dari file sumber menjadi string multi-byte dalam file biner (.obj, dari mana linker membuat pe daripada). dan penggunaan kompiler untuk ini WideCharToMultiByte< /a> dengan CP_ACP (Halaman kode ANSI Windows default sistem saat ini.)

jadi apa yang terjadi jika Anda mengatakan panggil printf("ù");?

  1. string unicode "ù" akan dikonversi menjadi multi-byte WideCharToMultiByte(CP_ACP, ) dan ini akan dilakukan pada waktu kompilasi. string multi-byte yang dihasilkan akan disimpan dalam file biner
  2. konsol itu run-time ubah string multi-byte Anda menjadi karakter lebar sebesar MultiByteToWideChar(GetConsoleOutputCP(), ..) dan cetak string ini

jadi Anda mendapat 2 konversi: unicode -> CP_ACP -> multi-byte -> GetConsoleOutputCP() -> unicode

secara default GetConsoleOutputCP() == CP_OEMCP != CP_ACP meskipun Anda menjalankan program di komputer tempat Anda mengkompilasinya. (di komputer lain dengan CP_OEMCP lain khususnya)

masalah dalam konversi yang tidak kompatibel - halaman kode berbeda yang digunakan. tetapi bahkan jika Anda mengubah halaman kode konsol ke CP_ACP - konversi tetap dapat salah menerjemahkan beberapa karakter.

dan tentang CRT api wprintf - berikut situasinya:

wprintf pertama mengonversi string yang diberikan dari unicode ke multi-byte dengan menggunakannya arus internal locale (dan perhatikan bahwa lokal crt tidak bergantung pada lokal dan berbeda dengan lokal konsol). lalu panggil WriteFile dengan string multi-byte. konsol ubah kembali string multi-byte ini menjadi unicode

unicode -> current_crt_locale -> multi-byte -> GetConsoleOutputCP() -> unicode

jadi untuk menggunakan wprintf pertama-tama kita perlu menyetel lokal crt saat ini ke GetConsoleOutputCP()

char sz[16];
sprintf(sz, ".%u", GetConsoleOutputCP());
setlocale(LC_ALL, sz);
wprintf(L"—");

tapi bagaimanapun di sini saya melihat (di komputer saya) - di layar, bukan . jadi -— jika memanggil PrintString(L"—"); (yang dulunya WriteConsoleW) setelah ini.

jadi satu-satunya cara yang dapat diandalkan untuk mencetak karakter unicode apa pun (didukung oleh windows) - gunakan api WriteConsoleW.

person RbMm    schedule 14.09.2017

Setelah membaca komentar, saya menemukan solusi eryksun adalah yang paling sederhana (...dan paling mudah dipahami):

#include <stdio.h>
#include <io.h>
#include <fcntl.h>

int main()
{
    //other stuff
    _setmode(_fileno(stdout), _O_U16TEXT);
    wprintf(L"#help — display a list of commands...");

Portabilitas bukan urusan saya, dan ini memecahkan masalah awal saya—tidak lagi ù—em-dash kesayangan saya dipajang.

Saya mengakui pertanyaan ini pada dasarnya adalah duplikat dari yang ditautkan oleh sata300.de. Meskipun demikian, dengan printf menggantikan cout, dan ocehan yang tidak perlu menggantikan informasi yang relevan.

person Gabe Troyan    schedule 15.09.2017