C语言与VT100控制码编程

简介: C语言与VT100控制码编程 声明: 1. 如果您打算阅读本文,希望您已经了解过C语言的基本语法,本文不对C语言的基本语法进行说明,因为那些东西几乎唾手可得; 2.

C语言与VT100控制码编程 声明:
1. 如果您打算阅读本文,希望您已经了解过C语言的基本语法,本文不对C语言的基本语法进行说明,因为那些东西几乎唾手可得; 2. 本文在vim中编辑,请尽量是用vim进行阅读,因为有不对齐的现象; 3. 本人强烈建议您先编译,运行本文最后提供的sinDemo源代码,再看本文的正文,因为您看了运行效果,您就知道本人为什么要写这篇文章; \\\\\\\\\\\\--*目录*--/////////// | 一. 需求背景 | | 二. VT100控制码是什么 | | 三. sin函数动态图sinDemo示例 | \\\\\\\\\\\\\\\\\//////////////// 一. 需求背景 以前很长时间里,打开Ubuntu的终端,使用会产生动态效果的shell命令(如top)让我觉得不可思议,于是很多时候也希望自己的程序也能那样动起来,但由于自己的知识面的原因,不知道的东西太多: 1. 如何改变字符输出的位置? 2. 如何改变前景色,背景色? 3. 最重要的是,查资料时用什么关键字查也不知道? 当然,也许有人会说,去问人,可问题是我也不知道怎么去描述我的需求,更不知道谁知道这玩意.那时候觉这是一件挺难的事,于是我开始凭着感觉是用不同的关键字百度,最后是通过python找到tput,然后通过tput找到VT100码,因为时间已经过去挺久了,具体的经过也基本上忘记了. :) 在使用了一段时间的VT100码以后,我发现,我们学C语言的时候,就应该学会配合使用VT100码,因为这样你可以在黑白的终端世界里做出很酷的东西,比如俄罗斯方块,贪吃蛇等等,还有其他的一些经典的动画效果,个人觉得早期开发游戏的那些人,也许就是这么干的,本文的Demo提供了一个生成sin函数的动画效果.
本人也使用VT100码实现了在终端绘制方框,填充方框,使用不同的字符绘制直线,等等内容,并且把这些做成了一个库供自己使用.其实shell命令里的tput也是这么做,Ncurses的底层也是这么干,只不过貌似他们做得比我好,考虑得更周到. :) 二. VT100控制码是什么 VT100是一个终端类型定义,VT100控制码是用来在终端扩展显示的代码.比如在终端上任意坐标用不同的颜色显示字符.所有的控制符是
'\033'(033是八进制的数,十进制对应的是27,即ESC的ASCII码,如果需要查看,可以使用shell命令:man ascii)开头.用输出字符语句来输出,在C程序中用printf来输出VT100的控制字符. 1. VT100 控制码归类如下。 \033[0m 取消之前所有属性 \033[1m 设置高亮度 \033[4m 下划线 \033[5m 闪烁 \033[7m 反显 \033[8m 消隐 \033[30m -- \033[37m 设置前景色 |------------+ \033[40m -- \033[47m 设置背景色 |------------+ \033[nA 光标上移 n 行 | \033[nB 光标下移 n 行 | \033[nC 光标右移 n 行 | \033[nD 光标左移 n 行 | \033[y;xH 设置光标位置 | \033[2J 清屏 | \033[K 清除从光标到行尾的内容 | \033[s 保存光标位置 | \033[u 恢复光标位置 | \033[?25l 隐藏光标 | \033[?25h 显示光标 | V +------<----------------------------<--+ | V 2. VT100 的颜色输出分为,前景色和背景色可以分别输出,如果不需要之前所有的设置可以用\033[0m取消。 1. 字背景颜色范围:40----49 40:黑 41:深红 42:绿 43:黄色 44:蓝色 45:紫色 46:深绿 47:白色 2. 字前景颜色范围:30----39 30:黑 31:红 32:绿 33:黄 34:蓝色 35:紫色 36:深绿 37:白色 3. 输出一个字符串( something here )有前景色和背景色代码如下: printf("\033[41;36m something here \033[0m"); 三. sin函数动态图sinDemo示例 1. 示例终端输出一屏图像: | SinDemo | 0123456789012345678901234567890123456789 0000 --------------------+------@--------------> Y 0001 |----------* 0002 |---------------* 0003 |------------------* 0004 |------------------* 0005 |------------------* 0006 |---------------* 0007 |----------* 0008 |-----* 0009 * 0010 *-----| 0011 *----------| 0012 *---------------| 0013 *------------------| 0014 *------------------| 0015 *------------------| 0016 *---------------| 0017 *----------| 0018 *-----| 0019 * 0020 V X 2. sinDemo源代码:
  1 /******************************************************************
  2  *                        sinDemo
  3  *
  4  *   1. 本Demo主要目标是为了实现在终端下实现sin函数的动态效果;
  5  *   2. 本Demo之前是使用shell tput和C语言实现的,这次将其改为
  6  *       C语言+VT100控制码的形式;
  7  *
  8  *                              2015-3-27 阴 深圳  曾剑峰
  9  *
 10  *****************************************************************/
 11 #include <stdio.h>
 12 #include <math.h>
 13 #include <unistd.h>
 14 
 15 /**
 16  * 定义圆周率的值
 17  */
 18 #define PI  3.14
 19 /**
 20  * 本Demo中假设sin曲线周期为20,幅值也是20,幅值分正负幅值,
 21  * 所以后面的很多地方有SIN_AMPLITUDE*2,也就是Y轴方向上的值.
 22  */
 23 #define SIN_AMPLITUDE  20      
 24 /**
 25  * 定义每次刷新图形时间间隔为100ms
 26  */
 27 #define DELAY_TIME  100000  
 28 /**
 29  * 定义圆的一周角度为360度
 30  */
 31 #define TRIANGLE  360.0   
 32 /**
 33  * 输出的时候,数字行放在哪一行,也就是输出图形中的这行数字:
 34  *   0123456789012345678901234567890123456789
 35  * 本Demo中把上面这行数字放在界面的第3行
 36  */
 37 #define Y_NUMBER_BEGIN_LINE  3
 38 /**
 39  * 在本Demo中,图形就在上面数字行的下一行,也就是输出图形中如下面的内容:
 40  *     0000    --------------------@--------------------> Y
 41  *     0001                        |-----*             
 42  *     0002                        |----------*        
 43  *     0003                        |---------------*   
 44  *     0004                        |------------------*
 45  *     0005                        |------------------*
 46  *     0006                        |------------------*
 47  *     0007                        |---------------*   
 48  *     0008                        |----------*        
 49  *     0009                        |-----*             
 50  *     0010                        *                   
 51  *     0011                  *-----|                   
 52  *     0012             *----------|                   
 53  *     0013        *---------------|                   
 54  *     0014     *------------------|                   
 55  *     0015     *------------------|                   
 56  *     0016     *------------------|                   
 57  *     0017        *---------------|                   
 58  *     0018             *----------|                   
 59  *     0019                  *-----|                   
 60  *     0020                        V X  
 61  */
 62 #define SIN_GRAPH_BEGIN_LINE (Y_NUMBER_BEGIN_LINE+1)
 63 
 64 int main(int argc, char* argv[]){
 65 
 66     /**
 67      * 局部变量说明:
 68      *     1. i                 : 主要用于循环计算;
 69      *     2. lineNumber        : 用于保存行号;
 70      *     3. offsetCenter      : 用于保存sin曲线上的点的相对于中心轴的偏移;
 71      *     4. nextInitAngle     : 保存下一屏要输出图形的初始角度制角度(如30度);
 72      *     5. currentInitAngle  : 当前一屏要输出的图形的初始角度制角度(如30度);
 73      *     6. currentInitradian : 当前一屏要输出的图形的初始弧度制弧度(如PI/6)
 74      *                            根据currentInitAngle换算而来,因为sin函数需要
 75      *                            角度制进行求值;
 76      *
 77      */
 78     int    i                   = 0;    
 79     int    lineNumber          = 0;    
 80     int    offsetCenter        = 0;    
 81     int    nextInitAngle       = 0;
 82     double currentInitAngle    = 0;    
 83     double currentInitradian   = 0;    
 84 
 85     //软件开始运行,清一次屏,保证屏幕上没有无关内容
 86     printf("\033[2J");
 87         
 88     //输出标题,因为这个软件名字叫: SinDemo
 89     printf("\033[1;1H                       | SinDemo |\t");
 90 
 91     /**
 92      * 这里主要是完成那一行重复的0-9,SIN_AMPLITUDE*2是因为sin曲线的
 93      * 最高点和最低点是2倍的幅值
 94      */
 95     printf("\033[%d;1H\t", Y_NUMBER_BEGIN_LINE);
 96     for (i = 0; i < SIN_AMPLITUDE*2; i++) 
 97         printf("%d", i%10);
 98     printf("\n");
 99 
100     /**
101      * while循环主要完成内容:
102      *     1. 每次循环对局部变量重新初始化;
103      *     2. 将下一屏图形的初始角度赋值给当前的图形初始角;              
104      *     3. 将下一屏图形的初始角度加上间隔角度(TRIANGLE/SIN_AMPLITUDE),
105      *        TRIANGLE/SIN_AMPLITUDE在本Demo中是360/20=18度,就相当于X轴 
106      *        每格代表18度 
107      *     2. 调整光标到固定的位置;
108      *     3. 重新绘制整屏图形;
109      */
110     while(1){
111 
112         //重新初始化局部变量,因为每一屏图形都像一个新的开始
113         i                  = 0;        
114         offsetCenter       = 0;       
115         lineNumber         = 0;      
116         currentInitradian  = 0;     
117 
118         //从nextInitAngle中获取当前的初始化角度
119         currentInitAngle   =  nextInitAngle;
120 
121         //为下一次循环提供下一次的初始化角度
122         nextInitAngle     += TRIANGLE/SIN_AMPLITUDE;  
123 
124         //将光标移动到开始绘图的位置去
125         printf("\033[%d;1H", SIN_GRAPH_BEGIN_LINE);
126 
127         /**
128          * 根据不同的情况绘制图形, 每一次循环,就是绘制了图形中的一行
129          */
130         while(1){
131             //判断是不是最后一行,lineNumber起始行是从0开始
132             if(lineNumber == SIN_AMPLITUDE){
133                 //打印最后一行前面的数字行号
134                 printf("\033[%d;1H%04d\t", lineNumber+SIN_GRAPH_BEGIN_LINE, lineNumber);
135                 for (i = 0; i < SIN_AMPLITUDE*2; i++)
136                     /**
137                      * 判断是否到达中间位置,因为中间位置要放V的箭头,同时在旁边输出一个X,
138                      * 代表这是X轴方向.
139                      */
140                     i == SIN_AMPLITUDE ? printf("V X") : printf(" ");    
141                 break;
142             }
143 
144 
145             /**
146              * 对currentInitAngle角度进行修整,比如370度和10度是对应相同的sin值
147              * 其实这一步可以不用,但是这里保留了,后面是将currentInitAngle角度制的值
148              * 换算成对应的弧度制的值,便于sin求值.
149              */
150             currentInitAngle = ((int)currentInitAngle)%((int)TRIANGLE);
151             currentInitradian = currentInitAngle/(TRIANGLE/2)*PI;    
152 
153             /**
154              * 算出当前次currentInitradian对应的sin值,并乘以幅值SIN_AMPLITUDE,获取sin曲线
155              * 在Y轴上相对于中心轴的偏移offsetCenter,offsetCenter可能是正值,也可能是负值,
156              * 因为中心轴在中间.
157              */
158             offsetCenter = (int)(sin(currentInitradian)*SIN_AMPLITUDE);                
159 
160             /**
161              * 在正确的地方输出正确的行号   :)
162              */
163             printf("\033[%d;1H%04d", lineNumber+SIN_GRAPH_BEGIN_LINE, lineNumber);
164 
165             //用一个制表符,给出行号与图形的空间距离
166             printf("\t");
167 
168             /**
169              * 第一行,和其他的行不一样,有区别,输出结果如下:
170              * 0000    ------------@-------+--------------------> Y
171              */
172             if(lineNumber == 0){
173                 for (i = 0; i < SIN_AMPLITUDE*2; i++){
174                     /**
175                      * 判断当前输出的字符位置是否是X,Y轴交叉的位置,如果是就输出'+',
176                      * 不是就输出'-'
177                      */
178                     i == SIN_AMPLITUDE ? printf("+") : printf("-");
179                     /**
180                      * 判断当前输出的字符位置是否是sin曲线上的点对应的位置,
181                      * 如果是就输出'@'
182                      */
183                     if(i == offsetCenter+SIN_AMPLITUDE)
184                         printf("@");
185                 }
186                 //代表这个方向是Y轴
187                 printf("-> Y\n");
188             } else { 
189                 for (i = 0; i < SIN_AMPLITUDE*2; i++){
190                     //判断当前输出的字符位置是否是sin曲线上的点对应的位置,如果是就输出'*'
191                     if(i == (offsetCenter+SIN_AMPLITUDE)){
192                         printf("*");
193                     //判断当前输出的字符位置是否是X轴上对应的位置,如果是就输出'|'
194                     }else if(i == SIN_AMPLITUDE){
195                         printf("|");
196                     }else{
197                         /**
198                          * 这里主要是要处理一行里面除了画'*'、'|'、之外的'-'、' '
199                          * 其中的SIN_AMPLITUDE到SIN_AMPLITUDE+offsetCenter正好就是需要输出'-'的地方
200                          * 其他的地方输出' '
201                          */
202                         (((i > SIN_AMPLITUDE) && (i < SIN_AMPLITUDE+offsetCenter)) || \
203                             ((i < SIN_AMPLITUDE) && (i > SIN_AMPLITUDE+offsetCenter))) \
204                                 ? printf("-") : printf(" ");
205                     }
206                     //行尾,输出换行符
207                     if(i == (SIN_AMPLITUDE*2-1)) 
208                         printf("\n");
209                 }
210             }
211 
212             /**
213              * 一行输出完成,为下一行输出作准备,下一行比上一行在角度上多加TRIANGLE/SIN_AMPLITUDE,
214              * 在本Demo中相当于360/20=18,也就是加18度.
215              */
216             currentInitAngle += TRIANGLE/SIN_AMPLITUDE;      
217 
218             //行号加1
219             lineNumber++;                                    
220         }
221         /**
222          * 一屏图像输出完毕,最后输出一个换行符,并且延时一段时间再开始绘制下一屏图形
223          */
224         printf("\n");
225         usleep(DELAY_TIME);
226     }
227 
228     return 0;
229 }

 

目录
相关文章
|
1月前
|
存储 编译器 C语言
【C语言】数据类型全解析:编程效率提升的秘诀
在C语言中,合理选择和使用数据类型是编程的关键。通过深入理解基本数据类型和派生数据类型,掌握类型限定符和扩展技巧,可以编写出高效、稳定、可维护的代码。无论是在普通应用还是嵌入式系统中,数据类型的合理使用都能显著提升程序的性能和可靠性。
50 8
|
2月前
|
C语言
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性。本文探讨了C语言中的错误类型(如语法错误、运行时错误)、基本处理方法(如返回值、全局变量、自定义异常处理)、常见策略(如检查返回值、设置标志位、记录错误信息)及错误处理函数(如perror、strerror)。强调了不忽略错误、保持处理一致性及避免过度处理的重要性,并通过文件操作和网络编程实例展示了错误处理的应用。
78 4
|
3月前
|
NoSQL C语言 索引
十二个C语言新手编程时常犯的错误及解决方式
C语言初学者常遇错误包括语法错误、未初始化变量、数组越界、指针错误、函数声明与定义不匹配、忘记包含头文件、格式化字符串错误、忘记返回值、内存泄漏、逻辑错误、字符串未正确终止及递归无退出条件。解决方法涉及仔细检查代码、初始化变量、确保索引有效、正确使用指针与格式化字符串、包含必要头文件、使用调试工具跟踪逻辑、避免内存泄漏及确保递归有基准情况。利用调试器、编写注释及查阅资料也有助于提高编程效率。避免这些错误可使代码更稳定、高效。
552 12
|
4月前
|
存储 算法 Linux
C语言 多进程编程(一)进程创建
本文详细介绍了Linux系统中的进程管理。首先,文章解释了进程的概念及其特点,强调了进程作为操作系统中独立可调度实体的重要性。文章还深入讲解了Linux下的进程管理,包括如何获取进程ID、进程地址空间、虚拟地址与物理地址的区别,以及进程状态管理和优先级设置等内容。此外,还介绍了常用进程管理命令如`ps`、`top`、`pstree`和`kill`的使用方法。最后,文章讨论了进程的创建、退出和等待机制,并展示了如何通过`fork()`、`exec`家族函数以及`wait()`和`waitpid()`函数来管理和控制进程。此外,还介绍了守护进程的创建方法。
C语言 多进程编程(一)进程创建
|
4月前
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
|
4月前
|
Linux C语言
C语言 多进程编程(四)定时器信号和子进程退出信号
本文详细介绍了Linux系统中的定时器信号及其相关函数。首先,文章解释了`SIGALRM`信号的作用及应用场景,包括计时器、超时重试和定时任务等。接着介绍了`alarm()`函数,展示了如何设置定时器以及其局限性。随后探讨了`setitimer()`函数,比较了它与`alarm()`的不同之处,包括定时器类型、精度和支持的定时器数量等方面。最后,文章讲解了子进程退出时如何利用`SIGCHLD`信号,提供了示例代码展示如何处理子进程退出信号,避免僵尸进程问题。
|
4月前
|
消息中间件 Unix Linux
C语言 多进程编程(五)消息队列
本文介绍了Linux系统中多进程通信之消息队列的使用方法。首先通过`ftok()`函数生成消息队列的唯一ID,然后使用`msgget()`创建消息队列,并通过`msgctl()`进行操作,如删除队列。接着,通过`msgsnd()`函数发送消息到消息队列,使用`msgrcv()`函数从队列中接收消息。文章提供了详细的函数原型、参数说明及示例代码,帮助读者理解和应用消息队列进行进程间通信。
|
4月前
|
缓存 Linux C语言
C语言 多进程编程(六)共享内存
本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。
|
4月前
|
消息中间件 Unix Linux
C语言 多进程编程(二)管道
本文详细介绍了Linux下的进程间通信(IPC),重点讨论了管道通信机制。首先,文章概述了进程间通信的基本概念及重要性,并列举了几种常见的IPC方式。接着深入探讨了管道通信,包括无名管道(匿名管道)和有名管道(命名管道)。无名管道主要用于父子进程间的单向通信,有名管道则可用于任意进程间的通信。文中提供了丰富的示例代码,展示了如何使用`pipe()`和`mkfifo()`函数创建管道,并通过实例演示了如何利用管道进行进程间的消息传递。此外,还分析了管道的特点、优缺点以及如何通过`errno`判断管道是否存在,帮助读者更好地理解和应用管道通信技术。
|
4月前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。