【问题标题】:Read / Write special characters (like tilde, ñ,...) in a console application C在控制台应用程序 C 中读/写特殊字符(如波浪号、ñ、...)
【发布时间】:2018-09-08 11:51:55
【问题描述】:

我正在尝试 C 控制台应用程序可以读取(使用键盘)scanfgets 中的特殊西班牙字符,例如重音符号、“ñ”等,然后也使用 printf 打印它.

感谢包locale.h,我已经实现了正确显示这些字符(存储在变量中或直接来自printf)。我举个例子:

#include <stdio.h>
// Add languaje package
#include <locale.h>

int main(void)
{
    char string[254];

    // Set languaje to Spanish
    setlocale(LC_ALL, "spanish");

    // Show correctly spanish special chars 
    printf("¡Success!. It is shown special chars like 'ñ' or 'á'.\n\n\n");

    // Gets special chars by keyboard
    printf("Input spanish special chars (such 'ñ'): ");
    gets(string);

    printf("Your string is: %s", string);

    return 0;   
}

但我还没有实现用上面提到的功能正确拾取它们。

有人知道怎么做吗?

谢谢。


编辑 1:

在测试中,我观察到:

  • setlocale(LC_ALL, "spanish"); 正确显示西班牙语字符,但不从键盘收集。
  • setlocale(LC_ALL, "es_ES"); 它可以正确地从键盘中提取西班牙语字符,但不能很好地显示它们。


编辑 2:

我也尝试过setlocale(LC_ALL, "");setlocale(LC_ALL, "es_ES.UTF-8");setlocale(LC_ALL, "es_ES.ISO_8859-15");,结果与 EDIT 1 相同(或者从键盘很好地捕捉字符或在控制台中很好地显示它们,但永远不会同时在同时)。

【问题讨论】:

  • 在什么操作系统上?你读过utf8everywhere.org 吗?你可能想要(至少在 Linux 上)setlocale(LC_ALL, "es_ES.UTF-8"); 什么终端模拟器?什么字体?
  • @BasileStarynkevitch 我相信你是对的,你应该把这个作为答案。
  • 顺便说一句,gets 已过时且危险。使用fgetsgetlinereadline
  • @BasileStarynkevitch 是的。我也尝试过setlocale(LC_ALL, "es_ES.UTF-8");setlocale(LC_ALL, "es_ES.ISO_8859-15");,但它们也没有工作。我在 Windows 上使用 CodeBlocks、GCC 调试器运行 C 程序
  • 您忘记在问题中提及 Windows(至少作为标签)和 CodeBlocks。 BTW GCC 是一个编译器(调试器可能是 gdb

标签: c windows locale codeblocks stdio


【解决方案1】:

Microsoft 的 C 运行时库 (CRT) 不支持 UTF-8 作为语言环境编码。它仅支持 Windows 代码页。此外,“es_ES”不是有效的 CRT 语言环境字符串,因此 setlocale 会失败,从而使您处于默认的 C 语言环境中。 Microsoft 的 CRT 的较新版本支持 Windows 语言环境名称,例如“es-ES”(连字符,而不是下划线)。否则,CRT 使用全名或旧的 3 字母缩写,例如“spanish_spain”、“esp_esp”或“esp_esp.1252”。

但这不是故事的结局。当使用传统文本编码而不是 Unicode 从控制台读取和写入时,控制台本身还有另一层翻译。为避免 mojibake,您必须设置控制台输入和输出代码页(即 SetConsoleCPSetConsoleOutputCP)以匹配区域设置代码页。如果您仅限于西班牙语或拉丁语 1,那么应该可以将语言环境设置为“西班牙语”并通过 SetConsoleCP(1252)SetConsoleOutputCP(1252) 设置控制台代码页。更一般地,您可以查找给定语言环境名称的 ANSI 代码页,设置控制台代码页,并保存它们以便在退出时重置控制台。例如:

wchar_t *locale_name = L"es-ES";
if (_wsetlocale(LC_ALL, locale_name)) {
    int codepage;
    gPrevConsoleCP = GetConsoleCP();
    if (gPrevConsoleCP) { // The process is attached to a console.
        gPrevConsoleOutputCP = GetConsoleOutputCP();
        if (GetLocaleInfoEx(locale_name, 
                            LOCALE_IDEFAULTANSICODEPAGE | 
                            LOCALE_RETURN_NUMBER, 
                            (LPWSTR)&codepage, 
                            sizeof(codepage) / sizeof(wchar_t))) {
            if (!codepage) { // The locale doesn't have an ANSI codepage.
                codepage = GetACP();
            }
            SetConsoleCP(codepage);
            SetConsoleOutputCP(codepage);
            atexit(reset_console);
        }
    }
}

也就是说,在使用控制台时,如果您将stdinstdout 设置为使用_O_U16TEXT 模式并使用宽字符函数(例如fgetwswprintf),通常会更好。最终,如果 C 运行时库支持,这应该使用宽字符控制台 I/O 函数 ReadConsoleWWriteConsoleW。使用 UTF-16 宽字符模式的缺点是它需要完全重写您的代码以使用 wchar_t 字符串和宽字符函数,并且还需要为使​​用多字节编码字符串的库实现适配器(最好是 UTF -8)。

【讨论】:

  • 非常感谢。一个非常解释和简洁的答案。
  • 如果没有解释,投反对票是没有用的。如果你不告诉我问题是什么,我应该如何解决问题?如果我弄错了或解释得不好,我愿意接受建议。如果我完全错了,我会毫不犹豫地删除答案。我不在乎分数,只在乎答案的质量。
  • 我对你投了赞成票,但我也不明白为什么要对你投反对票。我的假设是,这可能是由于在 cole 的某些部分你需要“研究和修复”它,就像最后一样 atexit(reset_console);: for reset_console in Windows 不能正常工作,或者需要其他库,我愿意不知道。在gPrevConsoleCP = GetConsoleCP() 行中需要像int gPrevConsoleCP 这样的init var 等等,但我认为这不应该是没有任何解释的downvoting 的原因。我的建议是:修改代码并更新答案。人们应该再次投票。我希望它有所帮助。
  • 另外,我认为包含#include 的必要条件应该会有所帮助。如果代码可以是 MCVE 那就完美了(如果只需要复制粘贴到 IDE 中就可以执行,那么,代码是如何工作的将是完美的答案)
  • 我已经提出了一个问题,试图解决您的答案中发布的代码中的错误,因为我整天都在努力让它工作,但我做不到。问题在这里:stackoverflow.com/questions/49733087/…。请问你能回答吗?谢谢。
猜你喜欢
  • 1970-01-01
  • 2018-03-10
  • 1970-01-01
  • 2013-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多