目录
- 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编码。
.
声明:资源可能存在第三方来源,若有侵权请联系删除!