4
\$\开始组\$

我有一个需要ANSI转义序列的应用程序,所以我尝试将代码中的神奇字符串(以前是宏)抽象到一个单独的翻译层,在一个非常简单的API后面:

#ifndef序列_H#定义SEQUENCES_H 1#包括<stdint.h>#包括<stddef.h>枚举esc_seq_id:uint8_t{CLR_WIN(清除输入),CLR_WIN_UPTO_CUR、,CLR_WIN_UPTO_END(清除_输入_结束),位置CUR_TOP_LEFT,CLR_LINE_UPTO_END(清除线_打印_结束),位置_ CUR _ BOTTOM_右侧,CUR_ POS,HIDE_CUR、,显示CUR,颜色_反转,颜色_正常,重置所有属性,颜色_黑色_孔,COLOR_RED_FORE颜色,COLOR_GREEN_FORE颜色,COLOR_ YELLOW_,颜色_蓝色_前,颜色_代理_更多,COLOR_CYAN_FORE颜色,COLOR_WHITE_更多,颜色_右侧_黑色_更多,COLOR_ BRIGHT_RED_,颜色_右侧_绿色_更多,颜色_右侧_黄色_更多,颜色_右侧_蓝色_前部,颜色_右侧_代理_更多,COLOR_BRIGHT_CYAN_FORE颜色正确,COLOR_BRIGHT_WHITE_FORE(颜色_右侧_白色_更多),颜色_黑色_背面,颜色_红色_背面,颜色_绿色_背面,颜色_黄色_背面,颜色_蓝色_背面,颜色_代理_背面,颜色_颜色_背面,COLOR_DEFAULT_FORE颜色默认值,颜色_默认_背面,颜色_右侧_背面_黑色,颜色_右侧_红色_背面,颜色_右侧_绿色_背面,颜色_右侧_黄色_背面,颜色_右侧_蓝色_背面,颜色_右侧_代理_背面,颜色_正确_颜色_背面,COLOR_BRIGHT_WHITE_BACK,ESC_SEQ_COUNT/*这应该始终是最后一个元素*/};[[gnu::pure]]常量字符*get_seq_name(uint8_t id);[[gnu::pure]]size_t get_seq_len(uint8_t id);#endif/*序列_H*/

以及相应的源文件:

#包括“new.h”#包括<assert.h>#包括<stddef.h>#包括<stdint.h>#定义SLEN(大小为-1)#定义DEF(名称)\{.seq=名称,.len=SLEN(名称)}静态常量结构{const char*const seq;const size_t长度;}esc_seq[]={[CLR_WIN]=DEF(“\x1b[2J”),[CLR_WIN_UPTO_CUR]=DEF(“\x1b[1J”),[CLR_WIN_UPTO_END]=DEF(“\x1b[0J”),[POS_CUR_TOP_LEFT]=DEF(“\x1b[H”),[CLR_LINE_UPTO_END]=DEF(“\x1b[K”),[POS_CUR_BOTTOM_RIGHT]=DEF(“\x1b[999C\x1b+999B”),[GET_CUR_POS]=DEF(“\x1b[6n”),[HIDE_CUR]=DEF(“\x1b[?25l”),[SHOW_CUR]=DEF(“\x1b[?25h”),[COLOR_INVERT]=DEF(“\x1b[7m”),[COLOR_NORMAL]=DEF(“\x1b[m”),[RESET_ALL_ATTRIB]=DEF(“\x1b[0m”),[COLOR_BLACK_FORE]=DEF(“\x1b[30m”),[COLOR_RED_FORE]=DEF(“\x1b[31m”),[COLOR_GREEN_FORE]=DEF(“\x1b[32m”),[COLOR_YELLOW_FORE]=DEF(“\x1b[33m”),[COLOR_BLUE_FORE]=DEF(“\x1b[34m”),[COLOR_MAGENTA_FORE]=DEF(“\x1b[35m”),[COLOR_CYAN_FORE]=DEF(“\x1b[36m”),[COLOR_WHITE_FORE]=DEF(“\x1b[37m”),[COLOR_BRIGHT_BLACK_FORE]=DEF(“\x1b[90m”),[COLOR_BRIGHT_RED_FORE]=DEF(“\x1b[91m”),[COLOR_BRIGHT_GREN_FORE]=DEF(“\x1b[92m”),[COLOR_BRIGHT_YELLOW_FORE]=DEF(“\x1b[93m”),[COLOR_BRIGHT_BLUE_FORE]=DEF(“\x1b[94m”),[COLOR_BRIGHT_MAGENTA_FORE]=DEF(“\x1b[95m”),[COLOR_BRIGHT_CYAN_FORE]=DEF(“\x1b[96m”),[COLOR_BRIGHT_WHITE_FORE]=DEF(“\x1b[97m”),[COLOR_BLACK_BACK]=DEF(“\x1b[40m”),[COLOR_RED_BACK]=DEF(“\x1b[41m”),[COLOR_GREEN_BACK]=DEF(“\x1b[42m”),[COLOR_YELLOW_BACK]=DEF(“\x1b[43m”),[COLOR_BLUE_BACK]=DEF(“\x1b[44m”),[COLOR_MAGENTA_BACK]=DEF(“\x1b[45m”),[COLOR_CYAN_BACK]=DEF(“\x1b[46m”),[COLOR_DEFAULT_FORE]=DEF(“\x1b[39m”),[COLOR_DEFAULT_BACK]=DEF(“\x1b[49m”),[COLOR_BRIGHT_BACK_BLACK]=DEF(“\x1b[100m”),[COLOR_BRIGHT_RED_BACK]=DEF(“\x1b[101m”),[COLOR_BRIGHT_GREEN_BACK]=DEF(“\x1b[102m”),[COLOR_BRIGHT_YELLOW_BACK]=DEF(“\x1b[103m”),[COLOR_BRIGHT_BLUE_BACK]=DEF(“\x1b[104m”),[COLOR_BRIGHT_MAGENTA_BACK]=DEF(“\x1b[105m”),[COLOR_BRIGHT_CYAN_BACK]=DEF(“\x1b[106m”),[COLOR_BRIGHT_WHITE_BACK]=DEF(“\x1b[107m”),};static_assert(大小esc_seq/大小esc_seq[0]==esc_seq_COUNT,“esc_seq数组与枚举esc_seq_id不匹配!”);[[gnu::pure]]常量字符*get_seq_name(uint8_t id){/*如果定义了NDEBUG,则此断言将在发布版本中被禁用*/断言(id<ESC_SEQ_COUNT);返回esc_seq[id].seq;}[[gnu::pure]]size_t get_seq_len(uint8_t id){/*如果定义了NDEBUG,则此断言将在发布版本中被禁用*/断言(id<ESC_SEQ_COUNT);返回esc_seq[id].len;}

这样使用:

#包括<unistd.h>#包括“new.h”int main(无效){写入(STDOUT_FILENO,获取seq_name(CLR_WIN),获取seq _len(CLR_ WIN));睡眠(1);写入(STDOUT_FILENO,获取seq_name(POS_CUR_TOP_LEFT),获取seq _len(POS_CUR_TOP_LETF));睡眠(1);写入(STDOUT_FILENO,获取seq_name(COLOR_RED_FORE),获取seq _len(COLOR_RED_FOR));睡眠(1);write(STDOUT_FILENO,“你好,世界!”,大小为“你好,全球!”-1);睡眠(1);写入(STDOUT_FILENO,获取seq_name(CLR_WIN),获取seq _len(CLR_ WIN));睡眠(1);写入(STDOUT_FILENO,get_seq_name(POS_CUR_TOP_LEFT),get_seq_len(POS_CUR_TOP_LEFT));}

审查请求:

风格、可维护性、不良实践等。

\$\端组\$
1
  • 1
    \$\开始组\$ 好的,我首先看到的是,这里的所有符号都没有共同的前缀。(当我成为评审员而不是编码员时,我的感知就会改变。) \$\端组\$
    – 哈里斯
    5月16日21:44

2个答案2

重置为默认值
4
\$\开始组\$

的顺序#包括标题:
建议排序标题包括从“一般”到“特定”:

  1. C库标头(对于任何/所有C程序都是不可变的泛型),然后
  2. 操作系统库标头(不可变的目标平台API),然后
  3. 应用程序标题(项目翻译单位之间“全球”共享的信息)

请注意#包括<stddef.h>include<标准输入。小时然后,可以从应用程序的头文件中删除新增。小时(或可能序列。小时??). 它是翻译单元(新增。c(c)??) 注意包括所有初步标题之前它使用#包括“new.h”.

建议通过分离添加“抽象”级别颜色光标运动一般展示服务通过添加琐碎服务功能新增。c(c)。想象下一个项目是可视化编辑器具有关键字突出显示就像现在大多数IDE一样。请记住白色用于字符常量,以及粉红色关键字,以及。。。将驱动编码器疯了!不要为颜色、光标和显示函数共用约40个标记,每个标记只能通过2个门户检索,而是使用有意义的名称编写单独的函数。这个第一银行出租车是您的cls()(或清除()在某些环境中)。函数名说明了一切;无需公开ANSI Escape Code或引用该代码的“自制”标记。只是一个非常特定的函数调用,它执行一个非常特殊的函数。考虑一下。。。(例如,电子表格使用每个单元格及其所有属性的内部表示。单元格B52型在公式中如何引用和渲染一个单元格。在内部,细胞可能被称为QZH374_FF型内部模型和用户所见之间的分离/转换提供了极大的灵活性!)例如,玩,set_text();set_alert();作为功能知道并始终输出正确的ANSI字符串,表示“黑底白”文本和“红底白”文本。

请注意,所有这些枚举标记仅对使用它们的源文件可用。它们不必公开,因此整个枚举应该是静止的在源文件中使用两个获取工具栏(_foobar)功能。这只剩下头文件中的两个函数原型(应该是这样)。(是吗新增。小时序列。小时?) (供将来参考:将每个文件的名称作为代码的第一行。例如://我的文件1.c。在SO上为您自己和我们这样做。写,现在是xxx文件很难引用所引用的代码段。)

(琐碎:#定义SEQUENCES_H就足够了。虚假的1在那一行是没有必要的。)

应用程序头文件很少需要#包括其他头文件。C标准库标题绕过了这一建议,但这些标题是由专家编写的,以方便非专家。

复杂项目可以组合按等级划分来自许多其中任何“层”都可以访问下级API,而这些API又可以访问自己的下级API(低级“助手”功能)结构,一个人可以达到不透明度编写健壮代码的圣杯封装并简化了写入/运行单元测试代码。
如果组织良好,“顶级”源文件可能不需要知道真正的低级处理是如何发生的(即,它们不需要包含任何内容,只需要包含自己的下级头文件。)
“一路上都是乌龟……”


未测试:(有点微不足道……)测试线束清除终端显示(两次)。
光标应该自动恢复到行/列(1,1)没有显式地将其移动到那里。


考虑交换名称片段的顺序:
颜色_绿色_更多颜色_红色_绿色
(前缀是“COLOR”,然后是“FORE/BACK”,最后是特定的“粗糙”颜色名称)。

甚至(品尝)。。。
前_彩色_绿色正如在正常讲话中所说的那样。

例子:

前_彩色_红色前_彩色_白色前_颜色_蓝色

我的首选是能够识别,然后忽视,在扫描/搜索相关“标记”列表时(必须)重复的前缀。


如果你能容忍极端的助记符缩写:

FG_彩色_红色FG_彩色_白色FG_颜色_蓝色//行故意留空黑色_彩色_红色例如,BK_COLOR_WHITE//或BG_COLOR-WHITE黑色_彩色_蓝色


以下是提供给OP的示例代码,目的是“了解其他做事方式”。OP已接受此提议。这些代码片段来自我的自制数独解算器,它可以在VT100兼容设备(Windows终端)上进行闪烁显示。所有这些都是“紧凑”的,有些删节,可能包含卵子。

//显示.h枚举{CLR_WhtOnBlk,//<=与ANSI字符串数组对齐CLR_BlkOnBlk、,清除阻止组,CLR_BlkOnYlw,清除,CLR_ BlkOnPnk,CLR_BlkOnGry,清除,CLR_Normal=CLR_WhtOnBlk,//给定有意义的名称CLR_Puzl=CLR_BlkOnGrn,CLR_Keep=CLR_BlkOnGrn,CLR_Elim=CLR_BlkOnYlw,CLR_Erase=CLR_BlkOnBlk,CLR_Pause=CLR_BlkOnGry,};char*clrStr(int clr);void dispCell(单元格*p,int v,int clr,int hld);void dispExam(单元格*p,int v,int clr);无效光标(int ONoff);void dispDebugErase(int r);
//显示.c#include(包括)<stdio.h>、<string.h>和<stdarg.h#包括“main.h”、“solve.h”、“helper.h”、“display.h”//您将获得漂移char*clrStr(const int clr){静态字符*cstr[]={“\033[0;0m”,//wht on blk-CLR_WhtOnBlk“\033[40;30m”,//黑色-CLR_BlkOnBlk“\033[102;30m”,//blk on grn-CLR_BlkOnGrn“\033[103;30m”,//blk on ylw-CLR_BlkOnYlw“\033[105;30m”,//blk on pnk-CLR_BlkOnPnk“\033[47;30m”,//黑色,灰色-CLR_BlkOnGry“\033[106;30m”,//蓝色黑色};返回cstr[clr];}静态void printAt(int r,int c,int clr,char*str){打印f(“\033[%d;%dH%s%s”,r,c,clrStr(clr),str);}无效光标(int ONoff){打印(“\033[?25%c”,“lh”[开关]);}//最后一个参数'hld'是在继续之前暂停的持续时间。是时候思考了。void dispCell(单元格*p,int v,int clr,int hld){int r=PuzlTL_r+p->行*2;int c=PuzlTL_c+p->col*4;char str[]=“”;str[1]=v2ch(v);//将int 7转换为“7”printAt(r,c,clr,str);//output@row,col和fg/bg颜色,字符串。。。}void dispDebugErase(int r){打印位置(r,1,CLR_Normal,“\033[0J”);}
\$\端组\$
21
  • 1
    \$\开始组\$ “(new.c??)”===>我懒得打字序列。小时在编写此代码时。从今往后,我会确保每个文件的名称都以注释的形式出现在开头。谢谢你对命名的评论,我正在找人谈论他们。 \$\端组\$
    – 哈里斯
    5月17日12:42
  • \$\开始组\$ @Harith嘿!只是在枚举标记和调用函数之间添加了一个关于“另一层分离”的长段。如:my_cls();不需要公开任何令牌。所有必要的信息都在函数名中。home();cr();都是简单的单字指令。home表示TopLeft,不清除显示,cr表示移动到当前行的第1列。。。诸如此类。。。很晚了。。。我要离开这里。。。干杯!:-) \$\端组\$ 5月17日12:50
  • 1
    \$\开始组\$ @Fe2O3考虑将您的代码作为问题发布在code Review上,这样我们也可以查看:) \$\端组\$ 5月17日14:47
  • 1
    \$\开始组\$ @G.公司。斯利彭:“你会走进我的客厅吗?”蜘蛛对苍蝇说,“这是你曾经监视过的最漂亮的小客厅;进我的客厅的路是一个蜿蜒的楼梯,你在那里的时候我有很多奇怪的事情要告诉你。”“哦,不,不,”小苍蝇说,“问我是徒劳的,因为谁上了你蜿蜒的楼梯,就再也不会下来了。”——玛丽·霍伊特。。。什么?你认为我是昨天出生的吗干杯!! \$\端组\$ 5月17日21:40
  • 1
    \$\开始组\$ @Harith只是想到了一个类比。。。在“单文件”应用程序中,通常不需要显式函数原型。组织功能定义位置main()在文件末尾。祝你快乐!组织#包括线条基本相同<仆人>在最前面,然后是“法庭”和“皇家”(即:应用程序标题)增加的的级别复杂度(?)…每个发现其所需的基础已经到位。就像两个函数相互调用的情况一样,类比并不完美,但你会明白。。。:-)干杯! \$\端组\$ 5月17日22:03
6
\$\开始组\$

这不是一个抽象概念

你的帖子的标题是“抽象ANSI转义序列”,但你所做的只是给它们命名。真正的抽象应该是诅咒图书馆,它还允许您移动光标和更改文本颜色,而无需硬编码到ANSI转义序列的一对一映射。

命名事物

避免不必要的缩写。为什么不写转义序列而不是esc等效值总的来说,输入并没有那么多,尤其是对于具有制表符补全功能的代码编辑器。

不要将序列本身称为“名称”。

依靠编译器优化

在您的代码中,esc_seq[]不仅存储包含序列的字符串,还显式存储序列的长度。但是,您可以致电strlen()任何时候都可以在stirng上获取其长度,因此您不需要自己存储长度。如果您担心性能,那么编译器已经涵盖了:它可以执行strlen()在许多情况下,如果启用优化和链接时优化,则在编译时执行。如果您愿意依赖编译器来完成这项工作,那么您可以简化代码。

这也让我想到:

考虑将转义序列存储为静态常量字符*

您可以这样将它们存储在头文件中,而不是使用包含每个序列信息的枚举和数组:

static const char*CLR_WIN=“\x1b[2J”;静态常量char*CLR_WIN_UPTO_CUR=“\x1b[1J”;静态常量char*CLR_WIN_UPTO_END=“\x1b[0J”;

这避免了必须同时维护枚举声明和数组定义,并允许更好的编译时检查。

\$\端组\$
  • \$\开始组\$ re:“考虑将转义序列存储为静态常量字符*s”,因为我们谈论的是C23,constexpr静态const char[]s也值得考虑。 \$\端组\$ 5月17日13:19
  • 2
    \$\开始组\$ @Brian61354270并添加[[可能未使用]]每个字符数组,这样当编译器看到45个字符中只有5个被使用时,它就不会抱怨。 \$\端组\$
    – 哈里斯
    5月17日13:25
  • 1
    \$\开始组\$ @Harith OR。。。删除未使用的代码或将其括起来#如果为0/#endif/**/没有理由,也没有理由不去残留的源文件中的活动代码或变量定义。读者(明年可能是你)会浪费时间仔细检查甚至没有使用的活动代码??这毫无意义。。。一个好的链接器会丢弃编译器花在解析和翻译上的时间。如果人类出生时没有阑尾,许多手术和/或悲剧都可以避免。 \$\端组\$ 5月21日0:58

你的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.