本文在windows平台下,采用MinGW编译器。
一、一个示例
上学期,带学生C语言课程时,有学生写出了一段错误程序,如下:
// con_error.c
#include <stdio.h>
int main(void)
{
print("hello,world\n")
return 0;
}
看到,这个程序中有明显的错误,printf()函数行语句后缺少分号,这将导致错误,如下图
让学生不解的是,他们写的程序都是黑底白字的枯燥输出,而这里,会看到,控制台的某些关键字体颜色改变了,我们来实现,美中不足,这玩应要依赖平台特性。
二、一个控制台命令color
按win+r,在“运行”对话框中输入cmd,调出控制台,默认的控制台颜色是黑底(近似黑色),淡灰色字体,一般默认情况下:
背景颜色RGB值为(12,12,12);
字体颜色RGB值为(204,204,204)。
可以使用控制台命令color改变背景色和前景色。color命令格式为:
color xy
其中x代表背景色,y代表前景色。windows中的定义如下
我们做个试验,启动控制台,看到黑底白字的默认界面,然后输入:
color 24
界面将呈现绿色背景色,红色字体。这个函数有个返回值,保存在系统变量errorlevel中,再执行:
echo %errorlevel%
将返回0。代表执行正确,如下图。
但是,你不能将背景色和前景色设置为相同颜色,再启动一个控制台,执行:
color 22
控制台背景和前景色不会有任何变化,再执行:
echo %errorlevel%
将返回错误代码1,如下图
三、C程序调用cmd命令
在C程序中可以调用cmd命令,使用函数:system(),该函数定义在头文件stdlib.h中,完整格式为:
int system (const char *);
也就是说它有一个返回值,这个函数从vc6.0的时代就存在,有兴趣的可以查看一下它的源码。其输入的字符串为windows的cmd命令,并有一个返回值,做实验。
实验一:system()函数的使用
程序代码如下:
/* fun_system.c */
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
int ret;
ret = system("mkdir mydir");
printf("ret = %d\n",ret);
return 0;
}
然后编译这个程序:
gcc fun_system.c -o fun_system.exe
这个程序完成两项工作,一个是让C程序执行“mkdir mydir”命令,同时得到它的返回值。
运行这个程序后,将在程序目录下生产一个mydir文件夹,同时打印:
ret = 0
同样,返回值0代表顺利执行,我们再次运行这个程序,得到:
子目录或文件 mydir 已经存在。
ret = 1
得到一个错误报告,同时返回为1,这里的“子目录或文件 mydir 已经存在”由mkdir命令产生。
四、C语言使用color命令改变控制台颜色
现在就可以在C语言程序中使用system()函数改变控制台颜色。
实验二:C语言改变控制台颜色——初级版本
程序如下:
/* c_change_conclr.c */
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
int ret;
ret = system("color 24");
if(ret == 0) {
printf("Changed console color is ok.\n");
} else {
printf("Error!!!\n");
}
return 0;
}
编译程序,执行后,效果如下图:
五、更精细的控制
上面的使用color命令存在严重问题,控制太过于粗糙,还是无法得到最初的改变某个字颜色的效果。我们需要更为精细的控制。
这需要用到windows的平台sdk。涉及的函数可以是:
BOOL SetConsoleTextAttribute(STD_OUTPUT_HANDLE, WORD wAttributes)
关于这个函数有很多高级用法,有兴趣的请参看:
在上面给出的文档中,有这样一句话:
SetConsoleTextAttribute已经不建议被使用,但是微软公司依然会支持它,网上有很多这方面的讨论,有兴趣的可以参加,下面的博文:
我们采用一种更加稳健的方式。使用SetConsoleMode 函数。在
两篇文章中,你可以找到这个函数的用法,我们用这个函数来解决最初的问题。函数定义如下:
BOOL WINAPI SetConsoleMode(
_In_ HANDLE hConsoleHandle,
_In_ DWORD dwMode
);
hConsoleHandle为控制台输入缓冲区或控制台屏幕缓冲区的句柄;
dwMode要设置的输入或输出模式
使用流程为:
Step1:获取控制台句柄。
Step2:使用GetConsoleMode函数获取控制台当前的输入输出模式。
Step3:在模式中添加windows系统的一个预定义宏:
ENABLE_VIRTUAL_TERMINAL_PROCESSING,其值为0x0004,该标志的作用:使用 WriteFile 或 WriteConsole 写入时,将为 VT100 和类似控制字符序列分析字符,这些字符序列可控制光标移动、颜色/字体模式以及其他也可通过现有控制台 API 执行的操作。
Step4:在输出的格式串中进行格式设置。
设置格式为:
这里SGR为signature的缩写,这里就是对应<n>的值,其部分定义见下表:
完整表格请参看:
表格太长了,不便全部拷贝过来。做实验。
实验三:C语言改变控制台颜色
为了得到我们要的效果,代码如下:
/* c_change_conclr_v2.c */
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#include <stdio.h>
#include <windows.h>
int main()
{
// 获取控制台句柄
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOut == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
// 获取控制台当前色输入输出模式
DWORD dwMode = 0;
if (!GetConsoleMode(hOut, &dwMode))
{
return GetLastError();
}
// 控制台当前色输入输出模式中添加ENABLE_VIRTUAL_TERMINAL_PROCESSING
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (!SetConsoleMode(hOut, dwMode))
{
return GetLastError();
}
printf("\x1b[31mThis text has a red foreground using SGR.31.\r\n");
printf("\x1b[32mThis text has a green foreground using SGR.31.\r\n");
printf("\x1b[39mThis text has restored the foreground color only.\r\n");
printf("\x1b[49mThis text has restored the background color only.\r\n");
return 0;
}
编译执行后,输出为:
这样便改变了字体颜色。为了使用方面,我将它封装为一个头文件。代码如下:
/* console_color.h */
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#define COLOR_FORE_RED "\x1b[31m"
#define COLOR_FORE_GREEN "\x1b[32m"
#define COLOR_FOR_DEFAULT "\x1b[39m"
#define COLOR_BACK_DEFLAUT "\x1b[49m"
#include <windows.h>
#define CONFIG_CONSOLE_COLOR \
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); \
if (hOut == INVALID_HANDLE_VALUE) \
{ \
return GetLastError(); \
} \
DWORD dwMode = 0; \
if (!GetConsoleMode(hOut, &dwMode)) \
{ \
return GetLastError(); \
} \
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; \
if (!SetConsoleMode(hOut, dwMode)) \
{ \
return GetLastError(); \
}
这样改变字体颜色,只需要引入这个头文件,然后引用这个宏就可以了。如下列程序:
/* change_conclr_macro.c */
#include <stdio.h>
#include "console_color.h" // 引入头文件
int main()
{
CONFIG_CONSOLE_COLOR // 引入宏
// 每个输出格式串中添加一个格式控制的宏
printf(COLOR_FORE_RED "This text has a red foreground using SGR.31.\r\n");
printf(COLOR_FORE_GREEN "This text has a green foreground using SGR.31.\r\n");
printf(COLOR_FOR_DEFAULT "This text has restored the foreground color only.\r\n");
printf(COLOR_BACK_DEFLAUT "This text has restored the background color only.\r\n");
return 0;
}
编译执行后:
六、总结
微软放弃SetConsoleTextAttribute函数的主要原因是跨平台,如下格式
printf("\x1b[31mThis text has a red foreground using SGR.31.\r\n");
就是来源于Unix操作系统。
这玩应想完全实现跨平台,还是有一段路要走。