如何在纯C/C++(cout/printf)中显示进度指示器?

100

我正在用 C++ 写一个控制台程序来下载一个大文件。我知道文件的大小,并且开了一个工作线程来下载它。我想显示一个进度指示器,使它看起来更酷。

如何在 cout 或 printf 中同一位置不同时显示不同的字符串?


2
请查看 PDCurses 库 http://pdcurses.sourceforge.net/ - Lefteris
2
C++控制台进度指示器可能会有所帮助。 - David L.
3
不能生成一个wget进程? - Alexandre C.
[tag:curses] ... [tag:ncurses][tag:curses]...[tag:ncurses] - hippietrail
可能是将std :: cout倒回到行的开头的重复问题。 - kiranpradeep
只是好奇,你是如何下载文件的?你使用了哪些库或其他工具? - Flare Cat
12个回答

128

如果您希望输出有一个固定的宽度,请尝试使用以下内容:

float progress = 0.0;
while (progress < 1.0) {
    int barWidth = 70;

    std::cout << "[";
    int pos = barWidth * progress;
    for (int i = 0; i < barWidth; ++i) {
        if (i < pos) std::cout << "=";
        else if (i == pos) std::cout << ">";
        else std::cout << " ";
    }
    std::cout << "] " << int(progress * 100.0) << " %\r";
    std::cout.flush();

    progress += 0.16; // for demonstration only
}
std::cout << std::endl;

http://ideone.com/Yg8NKj

[>                                                                     ] 0 %
[===========>                                                          ] 15 %
[======================>                                               ] 31 %
[=================================>                                    ] 47 %
[============================================>                         ] 63 %
[========================================================>             ] 80 %
[===================================================================>  ] 96 %
请注意,这个输出会在每一行下面显示,但是在终端仿真器(我认为在Windows命令行中也是如此)中,它将被打印在同一行上
最后,在打印更多内容之前不要忘记打印一个换行符。
如果您想删除结尾的条形图,必须使用空格覆盖它,以打印一些较短的内容,例如"完成。"
同样,当然也可以使用C中的 printf 来完成同样的操作;调整上面的代码应该很容易。

68

要创建一个具有可调进度条宽度的C解决方案,您可以使用以下代码:

#define PBSTR "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
#define PBWIDTH 60

void printProgress(double percentage) {
    int val = (int) (percentage * 100);
    int lpad = (int) (percentage * PBWIDTH);
    int rpad = PBWIDTH - lpad;
    printf("\r%3d%% [%.*s%*s]", val, lpad, PBSTR, rpad, "");
    fflush(stdout);
}

它将输出类似于这样的内容:

 75% [||||||||||||||||||||||||||||||||||||||||||               ]

注意,第一个#define结尾处不应该有; - Robb1
7
这是我迄今为止找到的最简单、最好的解决方案。 - horro
1
@Sylvain 方括号中的字符串有两个部分:左边和右边。左边部分由使用%.*s格式说明符打印的PBSTR字符的lpad长度组成,而右边部分则由我们选择为空字符串""的左填充字符串的rpad长度组成,以便我们只使用%*s格式说明符打印rpad个空格。 - razz
哦,右侧已经是“左填充空格”的了。谢谢!!这确实是一个很棒的解决方案。 - Sylvain
1
我认为“percentage”参数名称是误导性的,因为该函数期望的是一个分数,即0.1、0.2、0.5等,而不是百分比,即10.0、20.0、50.0等。 - XorOrNor
显示剩余2条评论

66
你可以使用一个 "回车符" (\r),不带换行符 (\n),并希望你的控制台做正确的事情。

40
手动刷新,否则内容不会立即显示,因为输出是缓冲的。 - leemes
如果用户不小心按下回车键,它就会崩溃 :( 除此之外,这可能是最便携的解决方案,+1。 - Ali
@Ali 为了避免这种情况,您需要禁用回显(请参阅man termios)。 - leemes
1
@leemes #include <termios.h>,在M$ Windows上试试看吧 :) 不管怎样,感谢你的建议,我可能会在Linux上尝试一下。 - Ali
2
@Ali,可能有类似于Windows的替代品,但我不知道。 ;) - leemes
使用 "\r\e[0K" 也可以帮助清除行。 (在Linux下测试) - YuMS

14

您可以打印回车符(\r)将输出“光标”移回当前行的开头。

若要更高级的方法,请查看类似于ncurses(用于文本界面控制台的API)的东西。


6
手动刷新,否则它将不会立即显示,因为输出被缓冲。 - leemes
3
  • '\b' 用于将光标向左移动一个位置。
- Alexey Frunze
1
+1 for ncurses。如果你想做一些更复杂的事情,这绝对是正确的选择。 - Leo

14

7

我知道我有点晚回答这个问题,但是我制作了一个简单的类,可以实现你想要的功能。(请记住,在此之前我写了using namespace std;。)

class pBar {
public:
    void update(double newProgress) {
        currentProgress += newProgress;
        amountOfFiller = (int)((currentProgress / neededProgress)*(double)pBarLength);
    }
    void print() {
        currUpdateVal %= pBarUpdater.length();
        cout << "\r" //Bring cursor to start of line
            << firstPartOfpBar; //Print out first part of pBar
        for (int a = 0; a < amountOfFiller; a++) { //Print out current progress
            cout << pBarFiller;
        }
        cout << pBarUpdater[currUpdateVal];
        for (int b = 0; b < pBarLength - amountOfFiller; b++) { //Print out spaces
            cout << " ";
        }
        cout << lastPartOfpBar //Print out last part of progress bar
            << " (" << (int)(100*(currentProgress/neededProgress)) << "%)" //This just prints out the percent
            << flush;
        currUpdateVal += 1;
    }
    std::string firstPartOfpBar = "[", //Change these at will (that is why I made them public)
        lastPartOfpBar = "]",
        pBarFiller = "|",
        pBarUpdater = "/-\\|";
private:
    int amountOfFiller,
        pBarLength = 50, //I would recommend NOT changing this
        currUpdateVal = 0; //Do not change
    double currentProgress = 0, //Do not change
        neededProgress = 100; //I would recommend NOT changing this
};

使用示例:

int main() {
    //Setup:
    pBar bar;
    //Main loop:
    for (int i = 0; i < 100; i++) { //This can be any loop, but I just made this as an example
        //Update pBar:
        bar.update(1); //How much new progress was added (only needed when new progress was added)
        //Print pBar:
        bar.print(); //This should be called more frequently than it is in this demo (you'll have to see what looks best for your program)
        sleep(1);
    }
    cout << endl;
    return 0;
}

注意:我将所有类的字符串都设为公共的,这样可以轻松更改酒吧的外观。

5
另一种方法是显示“点”或您想要的任何字符。下面的代码将在每秒钟打印一个点作为进度指示器[类似于加载中...].
注意:此处使用了sleep,如果性能是问题,请三思而后行。
#include<iostream>
using namespace std;
int main()
{
    int count = 0;
    cout << "Will load in 10 Sec " << endl << "Loading ";
    for(count;count < 10; ++count){
        cout << ". " ;
        fflush(stdout);
        sleep(1);
    }
    cout << endl << "Done" <<endl;
    return 0;
}

3

这是我制作的一个简单的例子:

#include <iostream>
#include <thread>
#include <chrono>
#include <Windows.h>
using namespace std;

int main() {
   // Changing text color (GetStdHandle(-11), colorcode)
   SetConsoleTextAttribute(GetStdHandle(-11), 14);
   
   int barl = 20;
   cout << "[";     
   for (int i = 0; i < barl; i++) {         
      this_thread::sleep_for(chrono::milliseconds(100));
      cout << ":";  
   }
   cout << "]";

   // Reset color
   SetConsoleTextAttribute(GetStdHandle(-11), 7);
}

不要使用system(),尤其是color命令。而应该使用SetConsoleTextAttribute(hConsole, color),并别忘了重置(颜色7)。 - Felierix

1
我的非常简单的C语言解决方案:
#include <stdio.h>
#define S_(x) #x
#define S(x) S_(x)
#define PBWIDTH 64
#define PBCHAR '#'
static void progressbar(unsigned percent) {
  char pbstr[PBWIDTH];
  memset(pbstr, PBCHAR, PBWIDTH);
  fprintf(stderr, "\r[%-" S(PBWIDTH) ".*s] %u%%",
      percent * PBWIDTH / 100, pbstr, percent);
}
int main(void) {
  progressbar(70);
  fprintf(stderr, "\n");
}

输出:

[############################################                    ] 70%

memset在编译为现代CPU时应该进行优化,可以使用1或2个向量指令,比静态存储整个字符串要小得多,速度也一样快。

在我使用的程序中,在调用progressbar的循环之前,我喜欢以这种方式打印0%:

fprintf(stderr, "[%-" S(PBWIDTH) ".*s] %u%%", 0, "", 0);

如果你希望数字在进度条之前显示,请在progressbar中更改fprintf
  fprintf(stderr, "\r%3u%% [%-" S(PBWIDTH) ".*s]",
      percent, percent * PBWIDTH / 100, pbstr);

而且如果你选择了可选的0%部分:
fprintf(stderr, "%3u%% [%-" S(PBWIDTH) ".*s]", 0, 0, "");

随你修改这一切。 :)

0

也许这段代码可以帮到你 -

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <cmath>

using namespace std;

void show_progress_bar(int time, const std::string &message, char symbol)
{
    std::string progress_bar;
    const double progress_level = 1.42;

    std::cout << message << "\n\n";

    for (double percentage = 0; percentage <= 100; percentage += progress_level)
    {
        progress_bar.insert(0, 1, symbol);
        std::cout << "\r [" << std::ceil(percentage) << '%' << "] " << progress_bar;
        std::this_thread::sleep_for(std::chrono::milliseconds(time));       
    }
    std::cout << "\n\n";
}

int main()
{
    show_progress_bar(100, "progress" , '#');
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接