C++:动态刷新打印内容


目录

  • 1.简介
    • [1.1 Display类原理简述](#1.1 Display类原理简述)
  • 2.代码
    • [2.1 main.cpp:无注释版](#2.1 main.cpp:无注释版)
    • [2.2 main.cpp:有注释版](#2.2 main.cpp:有注释版)
  • 3.编译运行

1.简介

本文介绍一个用于命令行动态覆盖输出的C++实现(Display类);

效果说明:

  • 普通输出会直接换行显示。

  • 分段输出(如进度条)会在同一行动态刷新,模拟进度条效果。

  • 中文内容也能正确显示和自动换行。

  • 你可以修改max_word_per_line参数,观察自动换行效果。

.

1.1 Display类原理简述

  • Display类用于在命令行中动态刷新输出内容,适合进度条、分段日志等场景。

  • 支持中英文混合输出,自动换行。

  • 在Windows和Linux/macOS下分别做了兼容性处理。

  • 通过Print(int32_t segment_id, const std::string &s)方法输出内容:

    • segment_id为-1时为普通输出,其他值用于分段刷新。

    • 同一段内容会被"覆盖式"刷新,不同段内容会换行显示。

.

2.代码

2.1 main.cpp:无注释版

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <thread>
#include <chrono>

class Display {
 public:
  explicit Display(int32_t max_word_per_line = 20)
      : max_word_per_line_(max_word_per_line) {}

  virtual void Print(int32_t segment_id, const std::string &s) {
#ifdef _MSC_VER
    if (segment_id != -1) {
      if (last_segment_ != segment_id) {
          fprintf(stderr, "\n%d:%s", segment_id, s.c_str());
          last_segment_ = segment_id;
      } else {
          fprintf(stderr, "\r%d:%s", segment_id, s.c_str());
      }
    } else {
      fprintf(stderr, "%s\n", s.c_str());
    }
    return;
#endif
    if (last_segment_ == segment_id) {
      Clear();
    } else {
      if (last_segment_ != -1) {
        fprintf(stderr, "\n\r");
      }
      last_segment_ = segment_id;
      num_previous_lines_ = 0;
    }

    if (segment_id != -1) {
      fprintf(stderr, "\r%d:", segment_id);
    }

    int32_t i = 0;
    for (size_t n = 0; n < s.size();) {
      if (s[n] > 0 && s[n] < 0x7f) {
        fprintf(stderr, "%c", s[n]);
        ++n;
      } else {
        std::string tmp(s.begin() + n, s.begin() + n + 3);
        fprintf(stderr, "%s", tmp.data());
        n += 3;
      }

      ++i;
      if (i >= max_word_per_line_ && n + 1 < s.size() &&
          (s[n] == ' ' || s[n] < 0)) {
        fprintf(stderr, "\n\r ");
        ++num_previous_lines_;
        i = 0;
      }
    }
  }

 private:
  void Clear() {
    fprintf(stderr, "\33[2K\r");
    while (num_previous_lines_ > 0) {
      fprintf(stderr, "\033[1A\r");
      fprintf(stderr, "\33[2K\r");
      --num_previous_lines_;
    }
  }

 private:
  int32_t max_word_per_line_;
  int32_t num_previous_lines_ = 0;
  int32_t last_segment_ = -1;
};

int main() {
    Display display(20);

    display.Print(-1, "Hello, this is a normal output.");
    std::this_thread::sleep_for(std::chrono::seconds(1));

    for (int i = 0; i <= 5; ++i) {
        display.Print(1, "Progress: " + std::to_string(i * 20) + "%");
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }

    display.Print(2, "这是一个中文测试,看看能否正确换行和显示。");
    std::this_thread::sleep_for(std::chrono::seconds(1));

    display.Print(1, "Progress: 100% 完成!");
    std::this_thread::sleep_for(std::chrono::seconds(1));

    display.Print(-1, "All done!");

    return 0;
}

2.2 main.cpp:有注释版

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <thread>
#include <chrono>

// 用于命令行动态刷新输出的Display类
class Display {
 public:
  // 构造函数,设置每行最大字符数,默认20
  explicit Display(int32_t max_word_per_line = 20)
      : max_word_per_line_(max_word_per_line) {}

  // 动态输出函数
  // segment_id: 段编号(-1表示普通输出,其他值用于分段刷新)
  // s: 要输出的字符串
  virtual void Print(int32_t segment_id, const std::string &s) {
#ifdef _MSC_VER
    // =========================
    // Windows下的输出逻辑说明
    // =========================
    // Windows终端对ANSI转义序列支持较差,因此采用简单的回车和换行控制输出。
    // 如果segment_id不是-1,表示需要分段输出(如进度条等):
    if (segment_id != -1) {
      // 如果当前段编号和上一次不同,先换行再输出新内容,并更新last_segment_
      if (last_segment_ != segment_id) {
          fprintf(stderr, "\n%d:%s", segment_id, s.c_str());
          last_segment_ = segment_id;
      } else {
          // 如果是同一段,直接用回车覆盖当前行内容
          fprintf(stderr, "\r%d:%s", segment_id, s.c_str());
      }
    } else {
      // segment_id为-1,表示普通输出,直接输出字符串并换行
      fprintf(stderr, "%s\n", s.c_str());
    }
    // Windows下不支持多行清除,直接返回
    return;
#endif
    // =========================
    // Linux/macOS下的输出逻辑
    // =========================
    // 如果是同一段,清除之前的输出(为刷新做准备)
    if (last_segment_ == segment_id) {
      Clear();
    } else {
      // 如果是新段落,先换行
      if (last_segment_ != -1) {
        fprintf(stderr, "\n\r");
      }
      last_segment_ = segment_id;
      num_previous_lines_ = 0;
    }

    // 如果是分段输出,先输出段编号
    if (segment_id != -1) {
      fprintf(stderr, "\r%d:", segment_id);
    }

    int32_t i = 0; // 当前行已输出字符数
    for (size_t n = 0; n < s.size();) {
      if (s[n] > 0 && s[n] < 0x7f) {
        // 英文字符直接输出
        fprintf(stderr, "%c", s[n]);
        ++n;
      } else {
        // 中文字符(UTF-8下3字节)特殊处理
        std::string tmp(s.begin() + n, s.begin() + n + 3);
        fprintf(stderr, "%s", tmp.data());
        n += 3;
      }

      ++i;
      // 达到最大行宽且下一个字符是空格或特殊字符时换行
      if (i >= max_word_per_line_ && n + 1 < s.size() &&
          (s[n] == ' ' || s[n] < 0)) {
        fprintf(stderr, "\n\r ");
        ++num_previous_lines_;
        i = 0;
      }
    }
  }

 private:
  // 清除当前段的输出
  void Clear() {
    // 清除当前行:ClearCurrentLine()
    fprintf(stderr, "\33[2K\r");
    // 如果有多行输出,逐行向上清除
    while (num_previous_lines_ > 0) {
      // 光标上移一行:GoUpOneLine()
      fprintf(stderr, "\033[1A\r");
      // 清除当前行:ClearCurrentLine()
      fprintf(stderr, "\33[2K\r");
      --num_previous_lines_;
    }
  }

 private:
  int32_t max_word_per_line_;      // 每行最大字符数
  int32_t num_previous_lines_ = 0; // 之前输出的行数
  int32_t last_segment_ = -1;      // 上一次输出的段编号
};

int main() {
    Display display(20);

    // 普通输出
    display.Print(-1, "Hello, this is a normal output.");
    std::this_thread::sleep_for(std::chrono::seconds(1));

    // 段落1,模拟进度刷新
    for (int i = 0; i <= 5; ++i) {
        display.Print(1, "Progress: " + std::to_string(i * 20) + "%");
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }

    // 段落2,输出中文
    display.Print(2, "这是一个中文测试,看看能否正确换行和显示。");
    std::this_thread::sleep_for(std::chrono::seconds(1));

    // 段落1,再次刷新
    display.Print(1, "Progress: 100% 完成!");
    std::this_thread::sleep_for(std::chrono::seconds(1));

    // 普通输出
    display.Print(-1, "All done!");

    return 0;
}

3.编译运行

shell 复制代码
# 在Linux/macOS上
g++ -std=c++11 -o main main.cpp
./main

# 在Windows(PowerShell/CMD)上
g++ -std=c++11 -o main.exe main.cpp
.\main.exe

注意:

  • Windows下建议使用PowerShell或WSL终端,CMD对ANSI转义序列支持较差,刷新效果可能不理想。

  • 中文输出需保证终端支持UTF-8编码。

.


声明:资源可能存在第三方来源,若有侵权请联系删除!

相关推荐
一伦明悦დ几秒前
嵌入式系统C语言编程常用设计模式---参数表驱动设计
c语言·开发语言·单片机·设计模式
丶Darling.3 分钟前
Day125 | 灵神 | 二叉树 | 二叉树中的第K大层和
数据结构·c++·学习·算法·二叉树
Want5956 分钟前
Python炫酷烟花
开发语言·python·pygame
androidwork16 分钟前
Android 内存溢出(OOM)的 Kotlin 排查与优化指南
android·开发语言·kotlin
androidwork17 分钟前
Kotlin与Flutter:跨平台开发的互补之道与实战指南
开发语言·flutter·kotlin
明月看潮生1 小时前
青少年编程与数学 02-020 C#程序设计基础 02课题、开发环境
开发语言·青少年编程·c#·开发环境·编程与数学
明月看潮生1 小时前
青少年编程与数学 02-020 C#程序设计基础 03课题、开始编程
开发语言·青少年编程·c#·编程与数学
Cherl.1 小时前
C语言 贪吃蛇小游戏的实现
c语言·开发语言·链表·贪吃蛇
Susea&1 小时前
初识C++:模版
c语言·开发语言·c++
V文宝1 小时前
R语言速查表
开发语言·r语言