C 语言练习——实现终端进度条
记录一下通过C语言实现在终端中展示进度条的动画效果,代码同时支持 Linux 和 Windows。
头文件 pbar.h
内容如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extern "C" {
struct pbar *pbar_create(int style); // style: 0,1,2,3
void pbar_update(struct pbar *pb, double pct);
double pbar_time_cost(struct pbar *pb);
}
这里导出的接口同时支持 C 和 Cpp。
具体实现的源文件 pbar.c
内容如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
static LARGE_INTEGER freq; // time frequency
struct pbar_tp {
long long counter;
struct timespec ts;
};
static void pbar_get_time(struct pbar_tp *tp) {
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
tp->counter = now.QuadPart;
clock_gettime(CLOCK_MONOTONIC, &(tp->ts));
}
static double pbar_time_diff(struct pbar_tp *tp_start, struct pbar_tp *tp_end) {
return (double)(tp_end->counter - tp_start->counter)
/ (double)freq.QuadPart; // seconds
return (double)(tp_end->ts.tv_sec - tp_start->ts.tv_sec)
+ (double)(tp_end->ts.tv_nsec - tp_start->ts.tv_nsec)
/ 1e9; // seconds
}
static void pbar_time_str(double dt, char *time_buffer,
size_t time_buffer_len) {
if (!time_buffer || time_buffer_len == 0) { return; }
if (dt < 3600) { // mm:ss
int minutes = (int)(dt / 60);
int seconds = (int)(dt) % 60;
snprintf(time_buffer, time_buffer_len, "%02d:%02d", minutes, seconds);
}
else if (dt < 86400) { // hh:mm:ss
int hours = (int)(dt / 3600);
int minutes = ((int)(dt) % 3600) / 60;
int seconds = (int)(dt) % 60;
snprintf(time_buffer, time_buffer_len, "%02d:%02d:%02d", hours, minutes,
seconds);
}
else { // >xxh
int hours = (int)(dt / 3600);
snprintf(time_buffer, time_buffer_len, ">%dh", hours);
}
}
struct pbar {
int style;
int initialized;
double last_pct;
double last_rate;
struct pbar_tp start_time;
struct pbar_tp last_time;
};
struct pbar *pbar_create(int style) { // style: 0,1,2,3
QueryPerformanceFrequency(&freq);
struct pbar *pb = (struct pbar *)malloc(sizeof(struct pbar));
memset(pb, 0, sizeof(struct pbar));
pbar_get_time(&pb->start_time);
pb->last_time = pb->start_time;
pb->style = style;
return pb;
}
double pbar_time_cost(struct pbar *pb) {
if (pb == NULL) { return 0; }
return pbar_time_diff(&pb->start_time, &pb->last_time);
}
void pbar_update(struct pbar *pb, double pct) {
if (pb == NULL || pct < pb->last_pct) { return; }
struct pbar_tp time_now;
pbar_get_time(&time_now);
double elapsed_time = pbar_time_diff(&pb->last_time, &time_now);
double cur_rate = (pct - pb->last_pct) / elapsed_time;
if (!pb->initialized || isnan(pb->last_rate) || isinf(pb->last_rate)) {
pb->last_rate = cur_rate;
pb->initialized = 1;
}
else {
const double alpha = 0.4;
pb->last_rate = alpha * cur_rate + (1 - alpha) * pb->last_rate;
}
pb->last_time = time_now;
pb->last_pct = pct;
char color_label_buf[20] = {0};
if (pct < 0.5) {
snprintf(color_label_buf, 20, "\033[38;2;%d;%d;%dm", 255,
(int)(255 * (pct / 0.5)), 0);
}
else {
snprintf(color_label_buf, 20, "\033[38;2;%d;%d;%dm",
(int)(255 * (1 - (pct - 0.5) / 0.5)), 255, 0);
}
double cost_time = pbar_time_cost(pb);
char cost_time_buf[20] = {0};
pbar_time_str(cost_time, cost_time_buf, 20);
double eta_time = (1.0 - pct) / pb->last_rate;
char eta_time_buf[20] = {0};
pbar_time_str(eta_time, eta_time_buf, 20);
char bar_buf[21] = {0};
int filled_num = (int)(pct * 20);
for (int i = 0; i < 20; ++i) { bar_buf[i] = (i < filled_num) ? '#' : ' '; }
bar_buf[20] = '\0';
switch (pb->style) {
case 1: printf("\r %s%6.2f%%\033[0m", color_label_buf, pct * 100); break;
case 2:
printf("\r %6.2f%% |%s| [%s<%s]", pct * 100, bar_buf, cost_time_buf,
eta_time_buf);
break;
case 3:
printf("\r \033[91m%6.2f%%\033[0m %s|%s|\033[0m \033[93m[%s<%s]\033[0m",
pct * 100, color_label_buf, bar_buf, cost_time_buf,
eta_time_buf);
break;
case 0: // default
default: printf("\r %6.2f%%", pct * 100); break;
}
fflush(stdout);
}
这里提供了四种进度条样式:
- 数字百分比
- 彩色数字百分比(颜色会随着进度逐渐变化)
- 数字百分比 + 进度条
- 数字百分比 + 彩色进度条
几种进度条的具体效果如下
测试文件 test.c
内容如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
void common_sleep(int ms) {
if (ms <= 0) { return; }
Sleep((DWORD)ms);
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000L;
nanosleep(&ts, NULL);
}
int main(void) {
// style 0
{
struct pbar *demo1 = pbar_create(0);
for (int i = 1; i <= 100; i++) {
pbar_update(demo1, i / 100.0);
common_sleep(i / 5);
}
printf("\ntime_cost: %.2f\n", pbar_time_cost(demo1));
free(demo1);
}
// style 1
{
struct pbar *demo1 = pbar_create(1);
for (int i = 1; i <= 100; i++) {
pbar_update(demo1, i / 100.0);
common_sleep(i / 5);
}
printf("\ntime_cost: %.2f\n", pbar_time_cost(demo1));
free(demo1);
}
// style 2
{
struct pbar *demo1 = pbar_create(2);
for (int i = 1; i <= 100; i++) {
pbar_update(demo1, i / 100.0);
common_sleep(i);
}
printf("\ntime_cost: %.2f\n", pbar_time_cost(demo1));
free(demo1);
}
// style 3
{
struct pbar *demo1 = pbar_create(3);
for (int i = 1; i <= 100; i++) {
pbar_update(demo1, i / 100.0);
common_sleep(i);
}
printf("\ntime_cost: %.2f\n", pbar_time_cost(demo1));
free(demo1);
}
return 0;
}