【问题标题】:How can I add line breaks in a wchar_t array?如何在 wchar_t 数组中添加换行符?
【发布时间】:2021-03-13 13:11:40
【问题描述】:

我正在开发一款主机游戏。它在更新地图后使用屏幕缓冲区刷新控制台窗口。

这是while 的主要循环。

while (true) {
    //player.doStuff(_kbhit());
    //map.update();
    WriteConsoleOutputCharacter(
        console.getScreenBuffer(),
        (LPWSTR)map.getScreen(),
        map.getWidth() * map.getHeight(),
        { 0, 0 }, console.getBytesWritten()
    );
    Sleep(1000 / 30);
}

在此循环之前,我从.txt 文件中获取地图布局:

class Map {
    int width, height;
    wchar_t* screen;
public:
    wchar_t* getScreen() {
        return screen;
    }
    void setScreen(std::string layoutFile, std::string levelDataFile) {
        std::ifstream levelData(levelDataFile);
        levelData >> width >> height;
        screen = new wchar_t[(width + 1) * height];
        levelData.close();

        std::wifstream layout(layoutFile);
        std::wstring line;

        for (int j = 0; j < height; j++) {
            std::getline<wchar_t>(layout, line);
            for(int i = 0; i < width; i++) {
                screen[j * width + i] = line.at(i);
            }
            screen[width * (j + 1)] = L'\n';
        }
        layout.close();
    }
};


map.setScreen("demo.txt", "demo_data.txt");

问题是打印出来的地图显示为一个没有任何换行符的字符串,像这样:

00000__00000

当我期望它看起来像这样时:

0000
0__0 
0000

我尝试在每行写入后添加L'\n'L'\r\n',但它不起作用。

【问题讨论】:

  • 一行的长度是width+1,包括换行符。由于这个原因,索引的公式(j * width + iwidth * (j + 1))是错误的。还要小心以空字符结束字符串,而不是换行符。
  • @fabian 但问题是 getline 只返回一个没有 \n 的字符串,在这种情况下,一行的长度是 width 而不是 width+1。因此,我尝试使地图 1 符号比线宽,并手动添加换行符,这是行不通的

标签: c++ winapi console windows-console


【解决方案1】:

总之

这里有两个独立的问题:

  • 第一个问题是换行符被覆盖。
  • 第二个,一旦您更正了第一个,Windows 控制台 API 不处理换行符

更多详情

覆盖问题

我假设宽度不包括每行末尾的尾随换行符,因为您对屏幕的分配是:

new wchar_t[(width + 1) * height];   // real width is width + 1 for '\n'

现在查看您的逻辑,您添加到该行的最后一个 '\n' 设置为:

screen[ width * (j + 1) ]   // same as screen[ j * width + width ]

根据您的索引方案,这似乎很好,因为您将布局字符复制到:

screen[ j * width + i ]`   // where `i` is between `0` and `width-1` 

所以换行符是screen[ j * width + width ]

不幸的是,使用您的索引公式,下一行的第一个字符会覆盖相同的位置:将 j 替换为 j+1 并将 i 替换为 0 给出:

screen[ (j + 1) * width + 0 ]  

这是

screen[ (j + 1) * width ]    // same location as screen [ width * (j+1)]

在每一行添加尾随换行符的解决方案

更正您的索引方案,考虑到线的实际宽度是width+1

所以索引公式变为:

    for(int i = 0; i < width; i++) {
        screen[j * (width+1) + i] = line.at(i);
    }

当然还有尾随的换行符:

    screen[j * (width+1) + width] = L'\n';  // self-explaining
    screen[(j+1) * (width+1) -1] = L'\n';   // or alternative 

控制台 API 的问题

WriteConsoleOutputCharacter() 不提供对换行符和控制字符的真正支持。这些显示为问号。

文档提到了根据控制台模式处理这些控制字符的可能性,但我已经尝试使用 Windows 10,甚至使用变体 WriteConsoleOutputCharacterA()(以确保排除宽字符的问题),它根本不起作用。

您有两个解决方案来完成这项工作,但这两个需要一些返工:

  • 逐行显示输出并相应控制光标位置
  • 使用WriteConsoleOutput(),它允许您指定目标矩形(高度和宽度)并将字符写入矩形而不需要换行符。不幸的是,数组应该是 CHAR_INFO 而不是简单的字符。

第二种方式的例子:

std::string w = "SATORAREPOTENETOPERAROTAS";
SMALL_RECT sr{ 2, 2, 6, 6 };
CHAR_INFO t[25];
for (int i = 0; i < 25; i++) { t[i].Char.AsciiChar = w[i]; t[i].Attributes = BACKGROUND_GREEN; }
WriteConsoleOutputA(
    hOut,
    t,
    { 5,5 },
    { 0,0 },
    &sr
);

【讨论】:

  • 这似乎是正确的,但输出仍然不正确:0000?0 0?00.
  • 程序由于某种原因无法读取\n,它写入?而不是换行
  • @kastrycznik 不清楚您是如何得出这个“?”的。您如何读取输出文件以及如何显示它?因为原则上,它甚至可以使用简单的 '\n' (参见在线演示:ideone.com/LcDpJI)。当然,如果你写到windows控制台缓冲区,你不应该添加一个新行,而是写在缓冲区的正确位置。
  • 我逐行读取输出文件,然后将其写入 wstring 而不是 wchar_t (就像你说的那样),然后我用这个命令显示这个字符串:WriteConsoleOutputCharacter( console.getScreenBuffer(), (LPWSTR)map.getScreen().c_str(), map.getWidth() * map.getHeight(), { 0, 0 }, console.getBytesWritten());map.getScreen() 返回std::wstring 屏幕。我还必须在我的 wstring 上使用 c_str() 方法,因为 WriteConsoleOutputCharacter 只需要 LPWSTR
  • 我也尝试使用std::wcout &lt;&lt; map.getScreen() &lt;&lt; std::endl;。在这种情况下,输出有换行符,但是当我使用 WriteConsoleOutputCharacter() 时,会显示问号而不是换行符
猜你喜欢
  • 2021-06-15
  • 2019-10-08
  • 1970-01-01
  • 1970-01-01
  • 2011-03-05
  • 2016-06-01
  • 2012-05-13
  • 2012-05-07
相关资源
最近更新 更多