当前位置: 华文世界 > 数码

轻松玩转windows控制台(一):窗口标题

2024-02-10数码

开篇预告

前段时间,我在其他平台发布了一篇介绍「轻松玩转windows控制台」的文章,被不少粉丝私信批评,甚至还有个别言辞激烈的。

是因为文章写的不好吗?应该不是,因为两天时间就突破了90多个收藏,说明大家还是挺喜欢这种实用的、技巧性的技术文章,但是为什么还是被批评呢?

因为文章通篇以代码为主,虽然功能很多,但并没有详细的解释,导致大家在用的时候,有很多地方出现问题。

基于此,我决定围绕「轻松玩转windwos控制台」主题,写一个系列性的文章,通过详细介绍控制台方面的各种操作技巧,从而提高c语言初学者的学习兴趣。

我大致的规划主要分为这几块:

第一个系列是对控制台的各种「魔改」,包括标题修改、输出文字的字体大小、字体样式、文字颜色、屏幕背景颜色、控制台窗口的移动、居中显示、窗口大小、以及文字在窗口内任意位置的显示、中文字符的正确显示等等。

这个系列看完之后,就开始发布控制台窗口的图形化操作的系列文章。

比如控制台如何控制鼠标、键盘,如何将屏幕背景改成彩色图片,如何在控制台内实现动画效果等等。为后面第三个系列控制台图形游戏开发打下基础。

第三个系列我准备围绕一个游戏的一步步实现来发布文章,可以是一个超级玛丽游戏,或者一个台球游戏都可以,游戏本身我早就已经调测运行。

话不多说,开始我们的第一个「玩转之旅」。

控制台介绍

本段是控制台的基础介绍,大家不感兴趣,可以直接跳过,也不影响后面阅读。

我们用c语言刚刚编写程序时,一般都是会在屏幕上弹出一个黑乎乎的窗口,然后黑底白字的显示一行行内容。这个黑乎乎的窗口界面,我们一般称为「控制台」,英文是Console,而在控制台界面内运行的程序,我们一般称为「控制台程序」。

我们把通过控制台运行程序的方式,称为CUI,即Command User Interface,命令用户界面。相对应的,还有一种图形化用户界面,GUI,即Graphics User Interface。有时候,我们也把CUI模式称为CLI模式,即Command Line Interface ,命令行界面,都可以。

我们一般通过执行cmd.exe命令进入控制台窗口,从而运行控制台程序。

以上这些都是最基础的概念,后面文章中用到什么就介绍什么,尽量降低复杂度。

控制台标题(Console Ttile)

我们先从控制台标题入手。

控制台程序启动时,窗口上方会有一个title(标题)。这个title显示的是程的绝对路径和程序名称。这个title字符串我们称为「原始的窗口标题名称」,即original title。如图:

这个字符串显示的真的很丑,我们一般都会改成我们喜欢的内容,如下所示:

围绕控制台窗口的标题,可以实现三个功能。分别是「修改控制台标题」、「获取控制台标题」、「获取原始控制台标题」。控制台标题被我们修改以后,有时候我们可能需要知道程序所在的绝对路径和程序名称,通过「获取原始控制台标题」这个功能就很方便。下面来看一下这3个功能对应的函数如何使用的, 并简单的介绍下windows的win32API调用。

注释:如果对Win32API不感兴趣,可以直接复制代码使用即可。

功能:修改控制台标题

若修改控制台当前标题,需要用到的函数原型如下:

BOOL SetConsoleTitle(LPCTSTR lpConsoleTitle);

在学习如何使用这个函数时,我们先介绍一下一些windos程序设计的基本概念,以便于后面能熟练的使用各种控制台函数。

对初学者而言,使用windows的API函数,应该会有段很痛苦的适应过程。因为微软把c/c++的基本数据类型和自定义数据类型,都采用了匈牙利命名法进行了封装,我本人对这个命名法是非常厌恶的,但是还是要学习下。[狗头]

函数名: SetConsoleTitle,设置当前控制台标题。参数类型: LPCTSTR, 这是微软开发团队自定义的数据类型,我们来分析下。

L表示long类型,用来表示长整型数据;P是pointer,表示指针类型;LP表示长整型指针(即32位或64位指针类型,取决于系统是32位或64位);C表示const,LPC表示这个指针类型指向的数据是常量数据;T是type,用来表示通用类型的含义(泛型),表示有unicode版本,或者ANSI版本,暂时不用理会;STR是字符串string的含义。

下面我列个表,就看的比较清爽了。

  • L:long类型
  • P:pointer,指针类型
  • LP:长整型指针
  • C:const
  • LPC:指针指向常量
  • T:type,泛型(暂时忽略)
  • STR:string,字符串
  • LPCTSTR:LP + C + (T) + STR
  • 由此可知,LPCTSTR,就是一个指向字符串常量的指针(字符串本身就是常量数据),可以近似理解成c语言中的const char*。

    返回值:BOOL

    BOOL,就是布尔类型,其实就是一个int类型:

    typedef int BOOL;

    如果真,就是TRUE,假就是FALSE。其实TRUE就是1,FALSE就是0。

    本函数的参数是输入型参数(什么是输入型参数和输出型参数,可以查看我的这篇文章:c语言解剖课:函数的输入参数和输出参数)

    这个参数在使用之前,其所在的外部变量是一个字符指针类型,必须被初始化,并且已经存放了新的标题字符串,字符个数必须小于 64K(等于64*1024个字节,谁能用这么多?)当函数执行成功,当前标题修改成新的字符串时,就返回为TRUE,执行失败就返回FALSE。

    我们来写一个演示代码:

    #include <stdio.h>#include <windows.h>int main() { LPCSTR newStr = "hello,title!" ; if(SetConsoleTitle(newStr) == TRUE){ printf("Title is changed!\n"); } system("pause"); return 0;}

    首先,文件的后缀需要是 .cpp 或其他C++源文件的格式,而 不能是.c的后缀 了。

    其次,需要包含windows.h头文件,因为对控制台操作的函数,都是这个头文件提供的。我们需要先定义一个字符串,存放新的标题。

    当然,你也可以用 char* 来定义,但是建议最好和windows编程风格保持一致,就用这个非常别扭的 LPCSTR类型,然后判断是否设置成功。

    随后的System函数,参数是一个字符串,这个字符串实际上就是一个DOS命令,这里是一个暂停命令。如果没有这条语句,运行这个程序时,屏幕会一闪而过。运行效果如图:

    窗口中的「请按任意键继续...」,就是system("pause");这行代码中的pause暂停命令起的作用。当然,为了简洁,你也可以直接写成如下形式:

    #include <stdio.h>#include <windows.h>int main() { SetConsoleTitle("hello,title!"); system("pause"); return 0;}

    但是,最好不要这么写,因为随着功能越来越强大(复杂),会很不利于维护。那么我们想显示中文行不行?我们先测试一下,请看下面这段代码:

    #include <windows.h>int main() { SetConsoleTitle("你好世界"); system("pause"); return 0;}

    看一下执行的效果,如图:

    控制台标题输出的是乱码,当然,有可能你的编译器运行后,输出的是中文。没关系,继续往下看就好了。为什么会这样?不用急。我们右击标题栏,点「属性」进去,注意看,下面这个截图中的红框,显示了系统当前使用的编码。

    代码页,即Code Page,简写为CP,表明了当前控制台的编码情况。如果是437,则是美国英语;如果是936,则是ANSI/OEM - 简体中文默认的GBK编码;如果是950 ,则是繁体中文;如果是65001,则是UTF-8编码。当然,代码页有很多种,我们只需要关心一个就可以了,就是UTF-8编码。用列表显示代码页标识符和编码标准,会更清楚:

  • 437 :美国英语
  • 936 : ANSI/OEM - GBK
  • 950 : 繁体中文
  • 650001 :UTF-8
  • 如果控制台显示乱码,我们可以将当前代码页设置为65001,即UTF-8,就可以正确显示中文。 注意,并不是因为当前代码页是936,所以就显示乱码。

    显示乱码是编译器的编码规范和系统的编码规范不一致,需要手动修改编码规范。通过下面介绍的方法, 通过修改代码页为UTF-8,可以快速解决问题,但不是唯一的解决办法。

    函数SetConsoleCP是设置控制台 输入字符 使用的编码标准,SetConsoleOutputCP是设置控制台 输出字符 的编码标准,函数原型如下:

    BOOL SetConsoleCP(UINT wCodePageID);BOOL SetConsoleOutputCP(UINT wCodePageID);

    参数类型:UINT

    微软将基本数据类型int封装成INT,约定为32位的有符号整数,取值范围是 -2147483648 到 2147483647。即:

    typedef int INT;

    而无符号整型unsigned int 被封装成UINT,约定为32位无符号整数,取值范围为 0 到 4294967295。即:

    typedef unsigned int UINT;

    参数的值就是代码页的标识符,比如要显示中文,UINT变量的值就是655001即可。

    返回值是BOOL类型,前已介绍过,如果执行成功,则返回TRUE,否则返回FALSE。

    举例如下:

    #include <stdio.h>#include <windows.h>int main() { SetConsoleCP(65001);//控制台标题 SetConsoleOutputCP(65001);//输出内容 LPCSTR newStr = "你好世界!" ; if(SetConsoleTitle(newStr) == TRUE){ printf("标题修改成功!\n"); } system("pause"); return 0;}

    我们看下执行结果:

    我们通过 SetConsoleOutputCP ,设置了 输出字符的代码页 (Code Page,CP)为UTF-8,所以prinftf函数的输出内容,正常显示中文了,同样的 输入字符 显示中文,通过 SetConsoleCP 就可以了。

    进一步的,我们在程序里,如果需要显示中文,我们需要知道当前代码页的数值是不是65001,如果不是,我们需要设置成6501。我们如知晓呢?

    可以通过GetConsoleCP获取输入状态的代码页,GetConsoleOutputCP获取输出状态的代码页,从而进行判断。函数原型如下:

    UINT GetConsoleCP(void);UINT WINAPI GetConsoleOutputCP(void);

    这两个函数没有参数,返回值是UINT类型,也就是当前所用的代码页标识符。第一个函数返回的是输入模式的代码页标识符,第二个函数返回的是输出模式的代码页标识符。通过对代码页判断,然后始终保持当前代码页标识符为65001即可。

    示例代码如下:

    #include <stdio.h>#include <windows.h>int main(){ UINT codePage = GetConsoleOutputCP(); if(codePage != 65001){ SetConsoleCP(65001); SetConsoleOutputCP(65001); } LPCSTR newStr = "你好,世界!" ; if(SetConsoleTitle(newStr) == TRUE){ printf("标题修改成功!\n"); } return 0;}

    我们现在可以随心所欲的修改窗口标题名了,并且也可以正确显示中文内容了。接下来,我们如何获取当前窗口的标题名呢?

    功能:获取控制台标题

    通过GetConsoleTtile函数,就可以获取道当前控制台的标题。函数原型如下:

    DWORD GetConsoleTitle(LPTSTR lpConsoleTitle, DWORD nSize );

    函数名: GetConsoleTitle;第一个参数类型:LPTSTR

    这个参数的类型和上面介绍的SetConsoleTtitle函数的LPCTSTR类型,很相似。本质的区别在于字母C,LPCTSTR是指向常量字符串,不允许修改,属于输入性参数。而LPTSTR类型,指向的是可以被修改的一段区域,一般我们定义成一个数组,或者一段动态内存分配区域。

    第二个参数类型:DWORD

    windows编程规范中,把8个位(bit)当作1个字节BYTE,把2个BYTE当作1个字,即WORD,2个WORD为双字,即DOUBLE WORD,简写为DWORD。

    1个DWORD类型的变量,等于4个字节,表示32 位无符号整数。 范围为 0 到十进制4294967295。其实就是c语言中的 unsigned long ,实际上也是这样定义的:

    typedef unsigned long DWORD;

    返回值和第二个参数一样,也是DWORD。

    第一个参数是输出型参数,接受函数的返回数据。(什么是输入型参数和输出型参数,可以查看我的这篇文章:c语言解剖课:函数的输入参数和输出参数),用来存放获取的当前窗口标题字符串。

    第二个参数是要显式的说明第一个参数所指向的内存区域的大小,以字符为单位。

    如果函数成功,则 返回值为控制台窗口标题的字符个数 。如果函数失败,则返回值为零。

    下面是一段演示代码:

    #include <stdio.h>#include <windows.h>#include <stdlib.h>#include <malloc.h>int main(){ DWORD charactor_total = MAX_PATH; LPTSTR title = (LPTSTR)malloc(sizeof(CHAR)*MAX_PATH); DWORD n = GetConsoleTitle(title,charactor_total); printf("标题字符个数:%d\n",n); system("pause"); return 0;}

    程序执行结果如图:

    大家可以数一下标题的字符数,是不是91个。[憨笑]

    功能:获取原始标题

    当控制台窗口标题被修改后,我们有时候需要获取窗口的原始标题,也就是程序的绝对路径和程序全名(包括后缀),这非常有用,因为可以根据这个字符串提取出程序文件所在的目录。

    获取原始标题的函数为GetConsoleOriginalTitle(),当窗口标题未被修改时,GetConsoleTtile()和它的功能是一样的,当被修改后,就出现了本质的差别,所以小心不要用错函数。

    函数原型如下:

    DWORD GetConsoleOriginalTitle( LPTSTR lpConsoleTitle, DWORD nSize);

    这个函数的参数和用法,就不再讲解,和GetConsoleTitle函数类似。

    总 结

    今天因为是第一篇文章,特别是对windows程序设计比较陌生的读者,对微软的自定义数据结构估计也会不适应,所以本文对微软自定义的数据结构讲解的比较详细,后面文章都会对出现的数据结构进行详细讲解,感兴趣的读者可以持续关注我,感谢。

    段誉,2024年2月9日(除夕),写于合肥。