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

问题描述 投票:0回答:13

我正在用 C++ 编写一个控制台程序来下载大文件。我知道文件大小,然后启动一个工作线程来下载它。我想显示一个进度指示器,让它看起来更酷。

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

c++ c user-interface c++11 io
13个回答
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命令行中)它将被打印在同一行

最后,不要忘记在打印更多内容之前打印换行符。

如果您想删除末尾的横线,则必须用空格覆盖它,以打印更短的内容,例如

"Done."

此外,当然可以使用 C 中的

printf
来完成同样的操作;调整上面的代码应该是直接的。


69
投票

对于具有可调节进度条宽度的

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% [||||||||||||||||||||||||||||||||||||||||||               ]

66
投票

您可以使用“回车符”( ) 不带换行符 ( ),并希望您的控制台做正确的事情。


14
投票

您可以打印回车符 (

\r
) 将输出“光标”移回到当前行的开头。

对于更复杂的方法,请查看 ncurses(用于基于控制台文本的界面的 API)之类的东西。


14
投票

看看boost Progress_display

http://www.boost.org/doc/libs/1_52_0/libs/timer/doc/original_timer.html#Class%20progress_display

我认为它可以满足您的需要,并且我相信它只是一个标头库,因此无需链接


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
投票

另一种方式可以显示“点”或您想要的任何字符。下面的代码将每隔 1 秒将进度指示器 [某种加载...]打印为点。

PS:我在这里使用睡眠。如果担心性能,请三思。

#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);
}

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%

为现代 CPU 编译时,

memset
应优化为 1 或 2 个向量指令,比静态存储整个字符串要小得多,而且速度同样快。

在我使用它的程序中,我喜欢在调用

progressbar
的循环之前以这种方式打印 0%:

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

如果您希望数字位于横线之前,请更改

fprintf
中的
progressbar

  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" , '#');
}

0
投票

很简单,你可以使用字符串的 fill 构造函数:

#include <iostream> //for `cout`
#include <string>   //for the constructor
#include <iomanip>  //for `setprecision`

using namespace std;

int main()
{
    const int cTotalLength = 10;
    float lProgress = 0.3;
    cout << 
        "\r[" <<                                            //'\r' aka carriage return should move printer's cursor back at the beginning of the current line
            string(cTotalLength * lProgress, 'X') <<        //printing filled part
            string(cTotalLength * (1 - lProgress), '-') <<  //printing empty part
        "] " <<
        setprecision(3) << 100 * lProgress << "%";          //printing percentage
    return 0;
}

将打印:

[XXX-------] 30%

如果你需要纯C语言

并且您希望能够在运行时自定义大小和填充字符:

#include <stdio.h>  //for `printf`
#include <stdlib.h> //for `malloc`
#include <string.h> //for `memset`

int main()
{
    const int cTotalLength = 10;
    char* lBuffer = malloc((cTotalLength + 1) * sizeof *lBuffer); //array to fit 10 chars + '\0'
    lBuffer[cTotalLength] = '\0'; //terminating it
    
    float lProgress = 0.3;

    int lFilledLength = lProgress * cTotalLength;
    
    memset(lBuffer, 'X', lFilledLength); //filling filled part
    memset(lBuffer + lFilledLength, '-', cTotalLength - lFilledLength); //filling empty part
    printf("\r[%s] %.1f%%", lBuffer, lProgress * 100); //same princip as with the CPP method

    //or you can combine it to a single line if you want to flex ;)
    //printf("\r[%s] %.1f%%", (char*)memset(memset(lBuffer, 'X', lFullLength) + lFullLength, '-', cTotalLength - lFullLength) - lFullLength, lProgress * 100);

    free(lBuffer);

    return 0;
}

但是如果你不需要在运行时自定义它:

#include <stdio.h>  //for `printf`
#include <stddef.h> //for `size_t`

int main()
{
    const char cFilled[] = "XXXXXXXXXX";
    const char cEmpty[]  = "----------";
    float lProgress = 0.3;
    
    size_t lFilledStart = (sizeof cFilled - 1) * (1 - lProgress);
    size_t lEmptyStart  = (sizeof cFilled - 1) * lProgress;

    printf("\r[%s%s] %.1f%%",
        cFilled + lFilledStart, //Array of Xs starting at `cTotalLength * (1 - lProgress)` (`cTotalLength * lProgress` characters remaining to EOS)
        cEmpty  + lEmptyStart,  //Array of -s starting at `cTotalLength * lProgress`...
        lProgress * 100 //Percentage
    );

    return 0;
}

0
投票

我需要创建一个进度条,这里的一些答案会导致进度条闪烁或在完成时显示低于 100% 的百分比。这是一个除了模拟 CPU 工作之外没有循环的版本,它仅在下一个进度单位递增时打印。

#include <iostream>
#include <iomanip> // for setw, setprecision, setfill
#include <chrono>
#include <thread> // simulate work on cpu

int main()
{
    int batch_size = 4000;
    int num_bars = 50;
    int batch_per_bar = batch_size / num_bars;

    int progress = 0;

    for (int i = 0; i < batch_size; i++) {
        if (i % batch_per_bar == 0) {    
            std::cout << std::setprecision(3) <<
                      // fill bar with = up to current progress
                      '[' << std::setfill('=') << std::setw(progress) << '>'
                      // fill the rest of the bar with spaces
                      << std::setfill(' ') << std::setw(num_bars - progress + 1)
                      // display bar percentage, \r brings it back to the beginning
                      << ']' << std::setw(3) << ((i + 1) * 100 / batch_size) << '%'
                      << "\r";
            progress++;
        }
        
        // simulate work
        std::this_thread::sleep_for(std::chrono::nanoseconds(1000000));
    }
}

0
投票

我提供了一种使用 std::setfill() 和 std::setw() 的替代方法。

#include <iostream>
#include <iomanip>
#include <chrono>
#include <thread>

static void printProgressBar(int percent, int progressWidth=60)
{
    std::cout << "\r[" << std::setfill('#') << std::setw(percent*progressWidth/100) << '#';
    std::cout << std::setfill(' ') << std::setw(progressWidth*(100-percent)/100) << "]";
    std::cout << std::setw(3) << percent << "%";
    std::cout.flush();
}

int main()
{
    using namespace std::chrono_literals;
    
    for (int i=0; i<=100; i+=1)
    {
        printProgressBar(i, 100);
        std::this_thread::sleep_for(20ms);
    }
    
    return 0;
}
© www.soinside.com 2019 - 2024. All rights reserved.