• 如何使用C/C++刷新在终端上已经打印的内容


    写本文的起源是因为在安装一些工具的时候,发现在终端上并行安装的情况下,显示安装信息是会修改之前已经打印出来的内容,这是怎么做到的呢?抱着对这个问题的好奇我进行了一些探索。

    终端是如何运行的

    首先是最关键的问题:终端是如何运行的?

    这个问题并不是我思考的第一个问题,但是在写本文的时候,我认为这是最关键的问题,思考了这个文件,那么面对一些问题就很好解释了:

    下面介绍一下标准输出(stdout)和 C/C++ 之间的工作流程:

    第一,标准输出(stdout)是一个只读文件,并不能进行修改,终端将会显示这些内容。
    第二,如果是 C 语言,那么printf()将内容输出到标准输出(stdout)中,然后终端将会显示这些内容。
    第三,如果是 C++,那么cout将会输出内容到缓冲streambuf中,最后在合适的机会将其传递给标准输出stdout中打印出来,比如说遇到fflush()刷新或者\n换行符的时候。

    可能你对上面的一些点还是很迷惑,下面仔细来说说看。

    刷新单行内容的最佳方法

    如果是单行刷新,可以使用转义字符\r\b:前者将会跳转到这行的开头再打印,而后者会移到前一个字符的位置再打印(带入一下旧式的打字机就可以理解了)。

    举个例子,在同一行里,从1循环到100,既可以使用\b\b\b(因为最大是三位数):

    #include 
    #include 
    
    int main(int argc, char *argv[]) {
        int i = 1;
        while (i<=100) {
            printf("%d\b\b\b", i++);
            //休眠一秒再进入下次循环,不然显示太快了
            sleep(1);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    比较推荐使用\r,这样就可以应对不同长度的数字。

    但是二者在stdout中的内容并不会被覆盖,而是如下情况:

    1\r2\r3\r4\r.......
    
    • 1

    所以如果需要刷新多行内容这种方法就不行了。因为\r\b本质上并不是删除了之前的内容,而是在这里跳转了光标进行重新渲染输出,标准输出中的内容并没有发生任何改变。而且二者的跳转都是横向跳转的,\n是纵向的变化。

    那么多行刷新应该怎么办呢?

    多行内容刷新的解决方案一:使用 ANSI Code

    这是一个诞生于上世纪七十年代的产物,它被用于控制终端上光标的位置、颜色、字体等属性。ANSI Code 本质是一个 ASCII Code 的组合,也是一种转义字符,结构为\033[XX\033在 ASCII 中就是 “ESC”的意思,转义字符的英文就是 “Escape Character”),并且广泛应用于众多类 Unix 系统的终端中。

    如果想打印出下面这样的情况(只刷新第一行的数字):

    39
    倒计时中
    
    • 1
    • 2

    那么就可以使用下面的代码(注意还是使用了\r,因为当前光标上移可能是在中间或最后的位置):

    #include 
    #include 
    
    int main(int argc, char *argv[]) {
        int i = 1;
        while (i<=100) {
            printf("%d\n", i++);
            printf("倒计时中\033[A\r");
            sleep(1);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    此外个人建议如果使用这种方法,最好在循环外加上printf("\n");,不然结束程序也可能会影响显示。

    这里有篇文章记录了各种移动光标的转义字符,可以当做手册查看:《Bash Prompt HOWTO: Chapter 6. ANSI Escape Sequences: Colours and Cursor Movement》

    你如果和我一样遍历过/bin,那么你可能会发现知道ls列出的第一个程序就是[(又名test),也是确定“condition”的。不过这个是评估条件的,而不是位置的(这句话是一个小双关),和 ANSI Code 并没有任何关系,只是巧合。

    多行内容刷新的解决方案二:使用ncurses或Windows Console API

    这种方法需要使用其他的库,根据平台选择 ncurses(类 Unix)或Windows Console API(Windows)。

    个人不是很推荐这种方法:

    • 第一,不是自带的,有些终端不能用;
    • 第二,编译构建安装的时间有点长;
    • 第三,这种方法类似less会新建一个窗口或者清空窗口进行显示。这种方法的样式不是我需要的。

    不过作为技术储备,我还是进行了一些研究。

    ncurses 的下载地址为https://invisible-island.net/ncurses/#download_ncurses

    下载之后,解压配置安装的命令如下:

    $ tar zxvf ncurses-xxx.tar.gz
    $ cd ncurses-xxx
    $ ./configure
    $ make -j4 
    $ sudo make install 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    安装好了之后,编译的时候使用-lncurses连接库即可。下面是官方的一个案例,这里假设这个文件为hello.c

    #include 
    
    int main()
    {	
    	initscr();			/* Start curses mode 		  */
    	printw("Hello World !!!");	/* Print Hello World		  */
    	refresh();			/* Print it on to the real screen */
    	getch();			/* Wait for user input */
    	endwin();			/* End curses mode		  */
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    编译命令为:

    $ cc hello.c `-lncurses
    
    • 1

    然后运行即可看到结果:

    请添加图片描述

    这里有很详细的官方文档:https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/可以看看。

    希望能帮到有需要的人~

  • 相关阅读:
    相机图像质量研究(23)常见问题总结:CMOS期间对成像的影响--紫晕
    LocalStroage,SessionStroage,Cookide,IndexedDB
    理解 React 服务器组件
    java 版本企业招标投标管理系统源码+多个行业+tbms+及时准确+全程电子化
    【网页前端】CSS进阶综合案例
    09-JVM垃圾收集底层算法实现
    nginx(CVE-2022-41741和41742) 漏洞修复
    河南工业大学人工智能与大数据学院学子在第三届“火焰杯”软件测试开发选拔赛中 取得佳绩
    数组题目总结 ---- 田忌赛马
    Qt环境配置VTK
  • 原文地址:https://blog.csdn.net/qq_33919450/article/details/133521183