7. 终端控制(Terminal Control)
在终端中打印信息时,我们可以使用 ANSI 转义序列来控制光标的位置、清除屏幕等操作。\033
是转义字符,用于引导 ANSI 控制码来控制终端显示。可以将它理解为“命令前缀”,后面跟着具体的指令代码来控制终端的行为。
7.1 示例代码:使用 \033
控制终端光标和屏幕
以下是一个示例代码,展示如何使用 \033
来清屏、控制光标位置,并在屏幕上打印信息。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int isStop;
// 清屏
printf("\033[2J");
// 在不同位置打印信息
for (isStop = 1; isStop < 11; isStop++) {
// 设置光标位置:移动到 (11 - isStop) 行的第 10 列
printf("\033[%d;10H", (11 - isStop));
printf("Printing within loop %d!\n", isStop);
sleep(1); // 等待 1 秒
}
// 移动光标到左上角并清除屏幕
printf("\033[H\033[2J");
return 0;
}
代码解释
-
清除屏幕:
printf("\033[2J");
\033[2J
:清除整个屏幕。\033
是转义字符,[2J
是清除屏幕的命令。
-
设置光标位置并打印信息:
printf("\033[%d;10H", (11 - isStop));
\033[%d;10H
:将光标移动到指定的位置。%d
表示行号,这里(11 - isStop)
是动态计算行号,10
是列号。这样可以让打印的信息从屏幕的第 11 行开始依次向上打印。
-
等待:
sleep(1);
- 通过
sleep(1)
使程序在每次循环时等待 1 秒,这样每次打印可以有明显的时间间隔。
- 通过
-
结束时再次清屏:
printf("\033[H\033[2J");
\033[H
:将光标移动到屏幕的左上角(第 1 行,第 1 列)。\033[2J
:清除整个屏幕。
输出效果
当运行这个程序时,你会看到以下行为:
- 程序开始时,屏幕会被清除。
- 在每次循环中,程序会将光标定位到一个新的位置(行号逐渐减小,但列号保持不变),并打印信息。例如:
信息每次会从屏幕的不同位置打印,每次向上移动一行。Printing within loop 1! Printing within loop 2! ...
- 每次打印后会等待 1 秒,这样可以看到打印过程是逐行向上的。
- 循环结束后,屏幕会再次被清除。
\033
控制序列的常用命令
\033[H
:将光标移动到屏幕左上角(第 1 行,第 1 列)。\033[2J
:清除屏幕内容。\033[n;mf
:设置文本颜色,其中n
是颜色代码。- 例如:
\033[31m
将文本设置为红色。
- 例如:
\033[nA
:将光标向上移动n
行。\033[nB
:将光标向下移动n
行。\033[nC
:将光标向右移动n
列。\033[nD
:将光标向左移动n
列。
通俗解释
可以把使用 \033
控制终端显示比作是写黑板时控制粉笔的位置。\033
是控制粉笔移动的指令,通过指定行和列的坐标来移动粉笔,让它在指定的位置书写。
在终端应用中,这种控制非常有用,例如:
- 实现命令行界面中的进度条。
- 更新输出的状态信息而不清屏。
- 实现动态显示的内容,比如一个简单的文本游戏或用户交互的应用程序。
为什么需要这些控制字符?
- 动态输出:使用控制字符可以实现屏幕上信息的动态更新,而不是每次都打印新的内容。这对于游戏、实时监控程序等非常有用。
- 改善用户体验:可以避免在终端中打印过多信息,让输出更加有条理。通过控制光标位置,可以实现更直观的输出形式,例如进度条、状态更新等。
示例应用场景
- 游戏:像贪吃蛇这样的终端游戏,可以使用光标控制来实现蛇的移动,而不是不断刷新整个屏幕。
- 实时监控:在显示一些实时数据(如 CPU 使用率、内存占用等)时,可以直接在原来的位置更新数据,而不是一行一行地追加输出。
- 命令行界面:例如用于选择菜单选项、进度指示器等。
总结
\033
是 ANSI 控制序列的起始符号,用于向终端发送控制命令。- 通过使用 控制字符,可以更好地控制终端上的光标位置、文本颜色、清屏等功能,从而实现更复杂的显示效果。
- 使用这些控制字符可以使终端程序更加交互和友好,在开发游戏、监控工具、动态显示工具时非常有用。
8. 暂停执行线程(Suspend Executing Thread)
usleep(useconds_t usec)
函数用于暂停线程的执行,单位是微秒。它可以让线程暂停指定的时间,然后再继续执行。这种功能非常有用,例如控制任务的执行节奏或使线程在等待某些条件时休眠。
usleep()
:用于暂停调用线程指定的微秒数(1 秒 = 1,000,000 微秒)。sleep()
:用于暂停线程指定的秒数。nanosleep()
:用于以纳秒精度指定休眠时间。适合需要更高精度的时间控制的场合。
8.1 示例代码:使用 usleep()
、sleep()
以下是一个简单的示例,展示如何使用 sleep()
和 usleep()
来控制程序的执行。
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
int main(int argc, char *argv[]) {
// 使用 sleep() 函数暂停程序执行 5 秒
printf("Sleep program for 5 seconds\n");
sleep(5);
// 使用 usleep() 函数暂停程序执行 1000000 微秒(即 1 秒)
printf("Sleep program for 1000000 microseconds (1 second)\n");
usleep(1000000);
// 程序结束
printf("Program finished\n");
return 0;
}
代码解释
-
sleep()
函数:sleep(5)
:使当前线程暂停 5 秒,然后继续执行后续代码。- 作用:在执行某些任务之前强制等待一定的时间,这对需要做定时延迟的操作非常有用。
-
usleep()
函数:usleep(1000000)
:使当前线程暂停 1,000,000 微秒,即 1 秒。- 作用:提供比
sleep()
更高精度的延迟功能,可以精确到微秒级别。 - 需要注意的是,
usleep()
精度较高,但具体暂停时间可能会受到系统的活动和计时器粒度的影响,因此有可能比设定的时间稍长。
-
输出:
Sleep program for 5 seconds Sleep program for 1000000 microseconds (1 second) Program finished
-
- 程序首先等待 5 秒,然后输出第二行信息,再等待 1 秒,最后输出结束信息。
usleep()
、sleep()
和 nanosleep()
的区别
-
sleep(seconds)
:- 暂停线程指定的秒数。
- 适用场合:适合需要较长时间延迟(例如秒级别)的场合。
-
usleep(useconds_t usec)
:- 暂停线程指定的微秒数。
- 适用场合:适合需要比
sleep()
更精细的延迟控制,比如毫秒级的等待。
-
nanosleep()
:- 允许用户以纳秒精度指定休眠时间,适合需要更高精度的时间控制。
- 它的函数声明是:
int nanosleep(const struct timespec *req, struct timespec *rem);
-
- 参数:
req
:指定需要暂停的时间,包含秒和纳秒。rem
:如果休眠被中断,则返回剩余的休眠时间。
- 参数:
usleep()
的使用场景
-
多线程中的延迟:
- 在多线程编程中,线程之间可能需要一些延迟来防止资源竞争,
usleep()
可以让线程在执行任务之间休眠一些时间,从而降低 CPU 占用并减少资源争用。
- 在多线程编程中,线程之间可能需要一些延迟来防止资源竞争,
-
游戏循环和动画:
- 在简单的游戏开发中,可以使用
usleep()
来控制帧率,确保每一帧之间有一个固定的时间间隔,从而实现平滑的动画效果。
- 在简单的游戏开发中,可以使用
-
定时任务:
- 在编写定时任务时,可以使用
usleep()
或sleep()
来定时地执行某些操作,例如每隔 1 秒采集一次传感器数据。
- 在编写定时任务时,可以使用
代码应用场景
-
在很多应用场合中,线程之间的任务执行需要有一个节奏,不是所有的任务都需要立即连续地执行。例如,在对某个传感器进行定时读取时,可能每隔 500 毫秒读取一次数据,使用
usleep(500000)
就可以方便地实现这样的功能。 -
在多线程应用中,
usleep()
可以用来暂时暂停当前线程的执行,以便其他线程有机会运行。例如,一个线程在等待某些资源可用时可以通过usleep()
暂时休眠,以避免反复轮询导致 CPU 资源的浪费。
注意事项
-
精度限制:
- 尽管
usleep()
提供微秒级的控制,但具体的延迟时间可能会因系统负载等原因而稍长。对于需要非常精确的计时任务,可能需要更复杂的同步机制。
- 尽管
-
系统调用影响:
- 在一些系统中,调用
usleep()
可能会受到系统定时器的粒度影响。如果需要非常精确的延迟控制,建议使用nanosleep()
。
- 在一些系统中,调用
nanosleep()
示例
以下是一个使用 nanosleep()
的简单示例,用于精确控制线程暂停的时间。
#include <stdio.h>
#include <time.h>
int main() {
struct timespec ts;
ts.tv_sec = 0; // 秒数部分
ts.tv_nsec = 500000000; // 纳秒部分(0.5 秒)
printf("Sleeping for 0.5 seconds...\n");
nanosleep(&ts, NULL);
printf("Wake up after 0.5 seconds\n");
return 0;
}
总结
sleep()
、usleep()
和nanosleep()
都是用于暂停线程执行的函数,适用于不同精度的时间控制。usleep()
提供微秒级的延迟控制,在多线程和定时任务中非常有用。nanosleep()
提供纳秒级的控制,适合需要高精度延迟的任务。- 这些函数在需要控制任务执行节奏、避免资源争用或实现实时响应时非常有用。