理解Windows控制台ANSI转义字符序列

使用ANSI转义字符序列

开发环境:
IDE:Dev-C++5.11
OS:Windows10(Windows7不支持ANSI转义字符序列)

1.首先要开启虚拟终端的功能

Window10默认控制台是关闭虚拟终端功能的

#include 
#include 
using namespace std ;
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING  0x0004
bool OpenANSIControlChar() {//开启ANSI转义序列功能
    //获取控制台的控制句柄
    HANDLE  hStd = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hStd == INVALID_HANDLE_VALUE) {
        return false;
    }
    //获取控制台模式
    DWORD dwMode = 0;
    if (!GetConsoleMode(hStd, &dwMode)) {
        return false;
    }
    //增加控制台模式的选项:启用虚拟终端
    dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    if (!SetConsoleMode(hStd, dwMode)) {
        return false;
    }
    return true;
}
int main(int argc, char* argv[]) {
    if(!OpenANSIControlChar()) {
        cout << "开启转义序列功能失败!"<< endl;
        return 1 ; 
    }
    system("pause") ;
    return 100;
}

2.理解转义序列

2.1. 先理解转义字符是:用\表示转义
2.2. 转义序列是:用一堆字符的组合表示转义
2.3. 理解转义序列的构成:
2.3.1. 我咋知道你是转义序列?
用"ESC["开头,遇到这个开头,虚拟终端就知道这是一个转义序列。
这个"ESC["开头,给人表达的时候,这个ESC[ CSI,
给计算机表达的时候,ESC要用ASCII码表示,\033[或者\x1b[。
八进制是033,十六进制是x1b
2.3.2. 我咋知道你这个转义要实现什么功能?
用一个特定字母做结尾,每个特定的字母表示特定的功能。
例如:
CSIA,CSI和A前后组合就表示,光标向上移动的功能。
CSIB,CSI和B前后组合就表示,光标向下移动的功能。
2.3.3. 问题来了,光标上移几行?
CSI2A,中间的那2就表示上移几行。
2.3.3.1. 控制光标上移两行

#include 
#include 
using namespace std ;
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING  0x0004
#define CSI "\x1b["
#define CURSOR_UP "A"
#define CURSOR_DOWN "B"
#define CURSOR_FORWARD "C"
#define CURSOR_BACK "D"
bool OpenANSIControlChar() {//开启ANSI转义序列功能
    //获取控制台的控制句柄
    HANDLE  hStd = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hStd == INVALID_HANDLE_VALUE) {
        return false;
    }
    //获取控制台模式
    DWORD dwMode = 0;
    if (!GetConsoleMode(hStd, &dwMode)) {
        return false;
    }
    //增加控制台模式的选项:启用虚拟终端
    dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    if (!SetConsoleMode(hStd, dwMode)) {
        return false;
    }
    return true;
}
int main(int argc, char* argv[]) {
    if(!OpenANSIControlChar()) {
        cout << "开启转义序列功能失败!"<< endl;
        return 1 ;
    }
    cout << "1----------" << endl;
    cout << "2----------" << endl;
    cout << "3----------" << endl;
    string ansi = "" ;
    ansi = ansi + CSI + "2" +CURSOR_UP ;
    Sleep(1000) ;
    cout << ansi ;
    system("pause") ;
    return 100;
}

2.3.4. 问题又来了,如果同时移动行和列?
CSIH
n表示行,m表示列。值从1开始,默认为1;1,左上角

#define CSI "\x1b["
#define CURSOR_YX "H"
    string ansi = "" ;
    ansi = ansi + CSI + "" + CURSOR_YX ;
    ansi = ansi + CSI + "2;3" + CURSOR_YX ;

2.3.5. 理解了上面的启动光标,清屏功能同样好理解,就是换了一个结尾,重新传参。
CSIJ 清空屏幕, 当 code 为:
0: 清空光标以下区域,清除光标所在位置以及光标右方下方的数据,光标位置停留在清空之前的位置不动!
1: 清空光标以上区域
2: 清空全部
CSIK 清空行, 当 code 为:
0: 清空光标之后区域,清除光标所在位置以及光标右边的数据,光标位置停留在清空之前的位置不动!
1: 清空光标之前区域
2: 清空整行,光标位置停留在清空之前的位置不动!

#include 
#include 
using namespace std ;
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING  0x0004
#define CSI "\x1b["
#define CURSOR_UP "A"
#define CURSOR_DOWN "B"
#define CURSOR_RIGHT "C" 
#define CURSOR_BACK "D"
#define CURSOR_YX "H"
#define SGR "m"
#define CLEAN_LINE "K"
#define CLEAN_AREA "J"
bool OpenANSIControlChar() {//开启ANSI转义序列功能
    //获取控制台的控制句柄
    HANDLE  hStd = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hStd == INVALID_HANDLE_VALUE) {
        return false;
    }
    //获取控制台模式
    DWORD dwMode = 0;
    if (!GetConsoleMode(hStd, &dwMode)) {
        return false;
    }
    //增加控制台模式的选项:启用虚拟终端
    dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    if (!SetConsoleMode(hStd, dwMode)) {
        return false;
    }
    return true;
}
int main(int argc, char* argv[]) {
    if(!OpenANSIControlChar()) {
        cout << "开启转义序列功能失败!"<< endl;
        return 1 ;
    }
    cout << "1----------" << endl;
    cout << "2----------" << endl;
    cout << "3----------" << endl;
    string ansi = "" ;
    //ansi = ansi + CSI + "" + CURSOR_YX ;
    ansi = ansi + CSI + "2;3" + CURSOR_YX ;
    Sleep(1000) ;
    cout << ansi ;
    ansi = "" ;
    ansi = ansi + CSI + "1" + CLEAN_AREA ;
    cout << ansi ;
    Sleep(10000) ;
    system("pause") ;
    return 100;
}

Windows10新版控制台使用转义序列隐藏光标

#include 
#include 
int main(int argc, char* argv[]) {
    system("chcp 65001");//设置当前页的编码为UTF-8编码 
    printf("\033[?25l"); //隐藏光标
    system("pause") ;
    printf("\033[?25h"); //显示光标
    system("pause") ;
    return 0;
}

2.4. 使用SGR控制文字颜色与背景,核心思想就是用组合参数表示一个意思。
2.4.1. 设置下划线
CSIM

#define SGR "m"
    string text = "hello" ;
    ansi = ansi + CSI + "4" + SGR + text;//4表示下划线

2.4.2. 初始的规格只有8种颜色,只给了它们的名字。SGR参数30-37选择前景色,40-47选择背景色

string text = "hello" ;
    ansi = ansi + CSI + "30;47" + SGR + text;//;前面的参数30表示前景色,;后面的参数47表示背景色

2.4.3.使用256色设置屏幕颜色
下面的"38;5;"是组合参数,组合起来表示一个完整的意思,表示我要设置前景色
上面的"31"是一个独立的参数,组合起来表示一个意思:用什么前景色
下面的"48;5;"是组合参数,组合起来表示一个完整的意思,表示我要设置背景色
上面的"44"是一个独立的参数,组合起来表示一个意思:用什么背景色

#define SGR "m"
    string text = "hello" ;
    ansi = ansi + CSI + "38;5;"+"31" + SGR + text;
    ansi = ansi + CSI + "48;5;"+"44" + SGR + text;

转义字符的由来

    真实的硬件终端设备由键盘和显示器组成,用于与远程主机(计算机)进行通信。除了与主机距离比较远(在隔壁房间或者在其它城市)以外,它的使用方式与一台个人计算机没有什么区别。程序是在主机上被执行,但结果是显示在终端屏幕上。终端通常仅限于显示接收到的信息以及在键盘上输入发送给主机的信息。功能单一的终端设备在上世纪70年代和80年代比较盛行。人们在其上编制程序、运行程序、编写文档或发出打印命令等。终端使用一根电缆与主机相连,有时需要通过modem与在远距离以外的主机连接。
    如今使用实际的终端设备已经很少。人们通常利用软件把个人计算机仿真成一个终端与主机相连。现在几乎所有使用Linux的人都使用终端仿真方法。对于不使用X window的情况下,人们使用字符界面的终端(虚拟终端)。这通常也被称为命令行接口。而在X window环境下,我们可以获得多个终端窗口,也有不少的终端仿真程序可以选用,例如xterm、rxvt或者zterm等。所有这些都是使用了仿真的方法来模拟一台真实的终端设备。
    实际的终端设备和与主机相连的显示器不同,因为它们有不同的硬件设施。一个实际的字符终端通常通过一根长电缆连接到主机的串行端口上,而主机的显示器则是连接在主机的显示卡上的。对于主机的显示器来讲,其视频图象是保存在主机显示卡上的显示内存中,而终端设备显示屏的图象是保存在终端设备的功能简单的显示卡上的。
    为了能控制发送到终端的信息在屏幕上显示的格式、位置等属性,主机需要向终端发送控制码(Control Code)和/或转义字符序列(Escape Sequence)。

控制码(或称为控制字符 Control Character)、

    是指ASCII码表中前32个字符。这些控制字符包括:回车符(Carriage-Return)、换行符(Line-Feed)、退格符(Backspace)、逃逸符(转义符Escape-Character)、制表符(Tab)和响铃符(Bell)。这些控制字符本身通常不会显示在终端屏幕上。
    由于控制字符太少,远远不够用来控制终端的各种属性,因此发明出使用转义序列来控制终端属性的方法。转义序列由转义符(Escape – ESC)后跟普通字符序列组成。终端在收到一个转义符时,就会把其后面的几个字符当作主机发送的命令来对待,并对该字符序列作出诠释。在识别出有效的转义序列结束后,终端执行主机的控制命令。随后所接收到的字符将仍然会显示在屏幕上(除非它们也是控制字符或者转义字符序列)。

ASC II码表

    我们输入的显示字符对于目前的系统而言都是ASC II的标准,了解这个有必要。对于一些特殊的指令,ASC II则用转义字符更为方便。

ANSI转义序列

    转义序列具有不同的长度,所有序列都以ASCII字符ESC(27 / 十六进制0x1B)开头,第二个字节则是0x40–0x5F(ASCII @A–Z[]^_)范围内的字符。标准规定,在8位环境中,这两个字节的序列可以合并为0x80-0x9F范围内的单个字节。但是,在现代设备上,这些代码通常用于其他目的,例如UTF-8的一部分或CP-1252字符,因此并不使用这种合并的方式。
    除ESC之外的其他C0代码(通常是BEL,BS,CR,LF,FF,TAB,VT,SO和SI)在输出时也可能会产生与某些控制序列相似或相同的效果。按下键盘上的特殊键,以及输出xterm CSI、DCS或OSC序列,常常用于产生从终端发送到计算机的CSI,DCS或OSC序列,就像用户使用键盘输入的一样。

CSI序列

    CSI序列由ESC [、若干个(包括0个)“参数字节”、若干个“中间字节”,以及一个“最终字节”组成。各部分的字符范围如下:
组成部分 字符范围 ASCII
参数字节 0x30–0x3F 0–9:;<=>?
中间字节 0x20–0x2F 空格、!"#$%&'()*+,-./
最终字节 0x40–0x7E @A–Z[]^_a–z{
    所有常见的序列都只是把参数用作一系列分号分隔的数字,如1;2;3。缺少的数字视为0(如1;;3相当于中间的数字是0,ESC[m这样没有参数的情况相当于参数为0)。某些序列(如CUU)把0视为1,以使缺少参数的情况下有意义:F.4.2。
    一部分字符定义是“私有”的,以便终端制造商可以插入他们自己的序列而不与标准相冲突。包括参数字节<=>?的使用,或者最终字节0x70–0x7F(p–z{|}~)
    例如VT320(英语:VT320)序列CSI?25h和CSI?25l的作用是打开和关闭光标的显示。
    当CSI序列含有超出0x20–0x7E范围的字符时,其行为是未定义的。这些非法字符包括C0控制字符(范围0–0x1F)、DEL(0x7F),以及高位字节。

参考文献:下面的四篇可以作为操作手册,非常实用。
<计算机知识>:ANSI转义序列以及输出颜色字符详解 - 萧蔷ink - 博客园 (cnblogs.com)
ANSI 转义序列_嵌入式技术-CSDN博客_ansi转义序列
【C++】Windows控制台API基本使用(上)_Takanawa-door的博客-CSDN博客_windows 控制台api
ANSI转义序列详解_ScilogyHunter的博客-CSDN博客_ansi转义序列

你可能感兴趣的:(理解Windows控制台ANSI转义字符序列)