目录
回车换行和换行的区别
倒计时程序
进度条程序
进度条代码的优化 version2
进度条代码的优化version3
在编写进度条程序之前我们需要先了解一个概念:回车换行和换行的区别。
刚听到的时候会很好奇,回车换行和换行有什么区别吗?我们之前都使用过 ‘ \n ’ 作为换行符让我们的输出结果更换所在行。但是他发挥的作用只是回车换行的作用吗?
在我们所熟知的 \n 的作用下我们会发现输出结果确实到达了下一行。但是我们很容易忽略一件事:我们的光标的位置也到了下一行的行首。这就是回车换行的关键。
换行的含义顾名思义就是更换我们的输出行,回车的含义代表了需要让我们的光标回到本行的行首。不知道大家还记不记得我们之前学过的 \r 他的作用其实就是回车。作用就是让我们的光标的位置回到我们本行的行首。之后在进行输出,会忽略后面的字符,输出的时候会进行覆盖操作。简单观察一下现象:
输出结果:
我们在运行上述代码的时候会发现一个很奇怪的现象:我们代码当中打印的明明是两个hello world呀!为什么只出现一个呢?其实第二个hello world也被打印出来了,只不过被我们的命令行提示符遮盖掉了。
我们可以根据上述原理编写一个简单的倒计时程序,在我们的预期当中我们打印的数字在同一个位置逐渐减小,最终变成零。(实际上就是每次打印一个数字,之后再通过回车回到本行的行首部位,再进行打印输出。)
为了方便我们观察,我们还可以调用sleep函数,让我们输出的结果停留一定的时间之后再继续变化。可是等我们运行的结果却和我们想象当中的并不一样。
我们得到的效果是程序经过了十秒之后才打印出0,其他步骤都被忽略了。
这个现象涉及到了缓冲区,为了减缓系统的占有时间,当我们的输出数据较少的时候就会暂时放到缓冲区当中不进行输出,等到数据足够的时候或者程序结束的时候才进行输出。所以我们的所有数据都放到缓冲区当中,同时数据又被 \r 覆盖,最后就有了这个效果。要想让我们的程序输出想要的结果。我们就需要调用fflush函数,fflush函数会主动刷新缓冲区,输出我们的结果。将我们的代码修改成为如下示例即可:
修改完成之后使用gcc进行编译,我们可以发现程序一切都正常进行。
当我们的倒计时程序编写完毕之后,我们就可以来编写我们的进度条程序了。
在我们的想象中,进度条需要随着时间的推荐逐渐向前推进,后面还需要有显示已经下载的进度,最后是正在下载的标志。也类似于我们日常生活当中见到的转圈圈的标志。
我们想象当中的进度条的样子就是上面的。如果感兴趣的话我们之后还可以使用图形化界面进行封装,最后就可以得到我们平常见到的进度条了,但是今天我们先来学习底层逻辑。
首先我们来完成进度条的主体。像我们看到的一样,我们进度条的主体是一堆用 = 以及 > 组成的字符,所以我们就可以使用一个字符串数组进行表示,其中因为加载进度为 0-100 所以我们需要100个有效的字符,加上我们表示字符串结束的标志一共需要101个有效的字符空间。每次我们只需要对我们的字符数组进行修改并输出即可。
在程序编写之前我们可以创建好,向上面的三个文件。在 test.c 文件当中我们只需要调用函数即可。我们的程序想要成功的输出我们也只需要在 progress.c 文件当中实现progress函数即可。
就像是上图中最右边的文件当中所示的那样:我们每次打印数组当中的内容,之后将目标字符输入到字符数组当中,最后刷新缓冲区,并休眠一段时间即可。代码产生的效果如下:
类似于我们正常进度条一样,一点一点向右边进行推进。
但是只有一个进度条的主体是完全不够的,我们还需要进行调整。加上我们打印的进度条的进度,以及正在打印的标志。我们只需要修改打印输出的那一行内容即可:
代码修改为:
需要注意的是我们在输出 % 的时候需要使用转义字符进行输出,否则可能会产生意想不到的输出效果。
我们会发现这和我们平时的进度条程序看起来确实很像,但是如何使用呢?
事实上想要使用我们编写好的进度条程序的话我们还需要对我们的代码进行简单的进行修改以及优化,我们需要设置一个参数用于接收下载的进度,进而打印出指定的进度条,这样才可以反映出我们具体的下载的情况,所以我们需要将代码修改成为:
我们将参数修改成比例,可以作为我们进度条打印的标志。我们的progress程序只需要将我们这个这个比例下的进度条输出即可,至于其他的操作细节只需要交给我们的用户完成即可。也就是我们右边的 test.c 当中的代码。这样可以增加我们进度条的灵活程度,条件变量我们还可以写成一个新的函数,例如:cur+=change() 我们可以创建一个函数检测一段时间内下载的文件大小,之后就可以实时返回我们的下载进度了。测试效果如下:
和我们想要的效果也完全相同。
但是我们又会发现,让用户做这么多事情会不会岂不是违背了我们函数封装的原理?要是有多个下载的内容又该怎么办呢?
为了解决上面出现过的问题,就有了我们第三版的进度条代码。我们可以将 test.c 当中的内容封装起来,采用我们的回调函数,将progress打印进度条的函数作为参数传给我们的dowload函数。用户只需要在main函数当中嗲用download函数即可。这样不仅减少了用户的代码操作,增加了函数的封装性,还允许我们进行多次的下载操作。修改代码如下:
首先我们使用typedef将我们的函数指针重命名,便于我们函数的传参,之后我们将之前 test.c 当中的代码全部封装起来,简单的进行修改就成为了我们的 download 函数,之后载 test.c 文件当中只需要调用我们指定的函数即可。代码运行的效果如下:
但是我们会发现我们的代码仍然有bug,我们在打印的时候只有第一个进度条当中的内容是变化的,其余的程序都是直接显示全满的内容,之后后面的数字和变化符号在变化。
这是因为我们在第一次打印下载操作执行完成的时候会将我们数组当中的所有的内容从 ‘ \0 ’ 修改成为 = 因此再继续打印的时候会显示字符数组当中的所有的字符。我们可以在每一次下载完毕之后,将我们的数组当中的内容置为 ‘ \0 ’ 以修改这个bug。
我们只需要在download代码的最后一行加上memset函数,将字符数组重置为0即可。测试效果如下:
我们发现我们的输出变得符合我们的预期。那么我们的进度条程序也就全部书写完毕了。