首页资源分类PCB layout > C++控制台界面编程

C++控制台界面编程

已有 454692个资源

下载专区

上传者其他资源

文档信息举报收藏

标    签: Cc

分    享:

文档简介

C++控制台界面编程详解

文档预览

C/C++控制台界面编程(V 2) (wyz5@163.com 整理) C/C++控制台界面编程(V 2) 序 资料中一部分来自互联网,作者大多已无法考证,另一部分来自 MSDN,其余部分为本人(wyz5@163.com) 自己编写并重新整理、修改、增加和补充一些内容,使之更充实、完善,所有程序均在 Visual Studio 2005 SP1 中调试通过(操作系统为 Windows XP SP3),原文很多代码均有语法错误,全部作了更正,部分示例 代码的功能也进行了增加,有些示例代码功能稍多,为使示例更有针对性,在保留其知识点的基础上对代 码进行了精简,最后提供了几个简易的程序示例,以飨读者。 每一个学习 C、C++的人,最先接触到的就是在控制台窗口下进行编程学习,它是一个基于文本模式的 黑黑的窗口,它不涉及到复杂的人机交互编程,是深入学习 C、C++最直接、简单的手段。 早期的开发都使用 Turbo C(简称 TC)进行开发,由于当时标准不够完善,在 TC 的库中提供了大量 C、 C++标准之外控制台界面处理的函数,时至今日,这些界面控制程序无法在在 VC 中通过编译了,有时候为 了学习和移植早期的代码,需要将这些函数在 VC 中重新实现,本教程提供了这样的参考。 更多的时候,用户学习编程语言最先接触的是控制台的界面,通过本教程可以深入了解界面相关的操 作,对于 Win32 窗口界面的程序来说,基于控制台窗口的程序也有很多优点,操作简单、几乎不需要书写 与界面相关的代码就能编译并生成可执行文件,这样的程序开发速度非常快,一些对于界面要求不是很高 的小工具用它开发,比花更多的时间去处理窗口程序的界面要划得来。 从 Windows 2000 以后,Windows 操作系统中的控制台已经不再是那个古老的,只能在 Windows 系统划 分的一块内存中模拟运行的 16 位程序了,现在的控制台窗口是一个与窗口程序有着相同特性的特殊窗口程 序,说它特殊是它只能显示字符,而且是命令行的,但它几乎支持窗口程序的所有功能,比如多线程、网 络通信、以及其它的 Windows 程序开发用的技术等,除了涉及到界面的东西以外,它可以完成你想要的所 有功能,当然 Windows 的 API 也可以完全由它来调用。 在众多 C++开发工具中,由于 Microsoft 本身的独特优势,选用 Visual C++已越来越被众多学习者所 接受。显然,现今如果还再把 TC 作为开发环境的话,不仅没有必要,而且也不利于向 Windows 应用程序开 发的过渡,加之 TC 比较旧,对语法标准的支持也非常有限。然而,Visual C++的 C++专用库却没有 TC 所支 持的文本屏幕(控制台窗口)控制函数(相应的头文件是 conio.h)。这必然给 C++学习者在文本界面设计和编 程上带来诸多不便。要知道,文本界面设计是一种深入学习 C++、掌握交互系统的实现方法的最简单的一种 手段,它不像 C++的 Windows 图形界面应用程序,涉及知识过多。为此,本系列文章讨论在 Visual Studio 开发环境中,如何编写具有美观清晰的控制台窗口界面的 C++应用程序,并且只介绍输入、输出和界面相关 的东西,即只涉及控制台的人机交互方面,其它方面本文档并不涉及。 当然,控制台的窗口也有自己的缺点,它与窗口界面的程序没有可比性。教程中所有的示例全部可以 用 C 或 C++来实现,关于这两种语言的使用,教程中不作过多讲解,这里假设读者已经具备了熟练的 C、C++ 语言功底。对于只学习过 C 语言的读者,也不影响从本教程中获取想要的知识,但最后面的几个示例和小 程序,有的用 C++实现,可能会给读者的学习带来不便,见谅。文中如有错误和不足之处,希望各位读者批 评、指正。 wyz5@163.com 1 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 目录 C/C++控制台界面编程........................................................................................................................................ 1 目录 ..................................................................................................................................................................... 2 第一部分 控制台界面编程预备知识................................................................................................................ 3 1) Visual Studio 2005 中控制台程序的类型........................................................................................... 3 2) 转义字符及格式化输入、输出.......................................................................................................... 3 a) 制表符\t ...................................................................................................................................... 4 b) 回退字符\b.................................................................................................................................. 6 c) ASCII 码表 .................................................................................................................................... 8 d) 以%开头的格式控制符............................................................................................................. 11 e) 数据流的格式设置 ................................................................................................................... 12 3) C 和 C++库的输入、输出操作 ......................................................................................................... 13 a) stdio.h 中的常用输入、输出函数............................................................................................ 13 b) basic_stream 中的输入、输出操作 ......................................................................................... 14 4) 键盘缓冲区处理 ............................................................................................................................... 15 5) 关于 C/C++中的字符串拼接问题..................................................................................................... 17 6) 怎样从控制台复制粘贴文字 ........................................................................................................... 18 7) 将批处理 bat 转换为 exe 程序......................................................................................................... 18 8) 在 Visual Studio 2005 中设置控制台程序的图标............................................................................ 18 9) 重定向控制台程序的输出 ............................................................................................................... 19 第二部分 控制台界面编程详解 ..................................................................................................................... 20 1) 概述 ................................................................................................................................................... 20 2) 控制台文本窗口编程的一般控制步骤............................................................................................ 21 3) 控制台窗口操作函数 ....................................................................................................................... 21 4) 文本属性操作 ................................................................................................................................... 25 5) 文本输出 ........................................................................................................................................... 27 6) 文本操作示例 ................................................................................................................................... 28 7) 滚动和移动 ....................................................................................................................................... 33 8) 光标操作 ........................................................................................................................................... 35 9) 读取键盘信息 ................................................................................................................................... 36 10) 读取鼠标信息 ............................................................................................................................... 43 11) 结束语 ........................................................................................................................................... 45 第三部分 附录 ................................................................................................................................................. 46 1) 分数等级划分工具 ........................................................................................................................... 46 a) controlio.h 文件......................................................................................................................... 46 b) Main.c 文件 ............................................................................................................................... 49 2) 简易俄罗斯方块 ............................................................................................................................... 51 a) 代码 Main.c 文件 ...................................................................................................................... 52 3) 模拟实现可用鼠标、键盘控制的菜单和窗口................................................................................ 56 2 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 第一部分 控制台界面编程预备知识 1) Visual Studio 2005 中控制台程序的类型 在 VS 2005 中控制台可以支持 MFC、ATL,这些内容在本教程中用不到,不用支持,所以学习本教程的 时候使用空的工程就行了读者在练习的时候,使用空的工程类型即可,如果向导生成了 main()主函数,那么 代码可能如下:(读者最好能知道怎样通过它创建一个工程,并且知道怎样设置这个工程的字符编码方式, 在 Visual C++ 6.0 默认的字符集为多字节的,VS 2005 中默认为大字符集,为考虑移植,代码大多数使用通用 字符集来实现。) int _tmain(int argc, _TCHAR* argv[]) { return 0; } 为便于读者理解,作以下解释: C、C++中 main 主函数可能接收字符数组作为参数,由于 VS 2005 支持不同的字符集,所以 C++标准中 对所有字符处理的函数都提供了两个版本,一个为普通的接收多字节型的版本,即 char 型,另一个为接收 大字符集的版本,一般要普通版本的函数前有前缀”w”,且对名称作了细微的更改,如果需要这方面的信息 可以在 MSDN 中查询,微软对字符处理的所有函数除了提供标准中的两个版本,至少还提供了一个通用版, 可同时用于两种字符集(一般由”_t”开头,且利用编译器的 DEBUG 宏来实现的,且不属于标准中的要求, 对移植性有要求时甚用),使用大字符集编码越来越重要,读者应在学习 C++的时候就应该适当接触这些内 容,本教程中使用通用的字符处理函数,但会在必要的时候把对应的其它两个版本的函数注释出来。如果 读者不习惯于这种编程方式,请自己更改调用的字符处理函数,并设置 Visual Studio 2005 的工程属性为使 用多字节字符集 上面代码中的_TCHAR 是一个宏(常简写为 TCHAR),当字符集为多字节时为 char,当为大字符集时为 wchar_t,类似的解释以后不再作解释,读者自行查阅 MSDN 即可。 2) 转义字符及格式化输入、输出 C 语言中的输出使用 printf()实现,使用它,需要包含 stdio.h 头文件,而 C++则除了使用兼容 C 的这种 格式,还可以使用 iostream 输入输出流来实现。 使用控制台输出,对于字符格式有精确的要求,所以学 C 语言的读者务必要牢牢掌握 printf()中的格式 控制符,学习 C++的则需要掌握流的成员函数和输出流的格式控制符,并包含 iostream 头文件并引入命名空 间 std(或所用到的成员)。 学习 C 语言的读者,还应该掌握 C 语言中的其它输入、输出函数(如 putchar,getchar,getch,puts,gets 等), 学习 C++中的读者则还需要掌握字符串流以及缓冲区的刷新等操作(如 endl,flush 等),以便更好的掌握人机 交互行为。(注意输入、输出流的控制符在头文件 iomanip 中,使用之前需要包含)。 除此之外,还需要重点掌握 C、C++中的转义字符,特别是\b,\n,\t,\八进制数或十六进制数。 以上这些知识在一般的 C、C++书籍中均有详细介绍,这里不再赘述,如果掌握不扎实,是不可能随心 所欲的操作控制台的输入输出的,但更多的时候是学会举一反三,迁移运用,下面就转义字符中的\b 和\t 进行一些详细讲解,很多人对于这两个字符根本就没有理解透彻。 3 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) a) 制表符\t 默认的控制台共可以显示 80*25 个文字,每一行共 80 个字符,共 25 列,除去最后一行可能因为切换 中文输入法需要,至少可以有 24 行文字空间可以操作(注:在控制台程序中,切换输入只能用 Ctrl + 空格 来 实现,如果对于界面要求非常严格,不允许出现滚动条和任何一丁点破坏界面的操作,那么请记住把最后 一行空间留给操作系统的输入法吧,否则你完全无法释怀心里的痛) 当然控制台是可以使用一些手段来使它的界面显示文字行数增大的,最多增加到多少行,受计算机屏 幕大小决定(后面内容会讲解) 每两个制表符在控制台窗口中占据 8 个字符的宽度。这样,一个标准的控制台窗口就会被分成 10*25 块单元,每一个单元对应一个制表位,而\t 的作用是从当前光标所在的位置跳到所在行最近的一个制表位 开始的地方,因此,使用\t 所跳过的字符宽度并不是固定的,而是介于 0~8 个字符之间,这主要是看下一 个制表位距离当前光标有多远,制表位用于多行文字在列上对齐是非常方便的,但其完美的程序取决于程 序书写者对它的控制。 典型的例子是输出 9*9 乘法表: #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { for (int i = 1; i <= 9; ++i) { for (int j = 1; j <= i; ++j) { _tprintf(_T("%d*%d=%d\t"), i, j, i*j); } _tprintf("\n"); } _tsystem(_T("pause")); return 0; } 以上是通用字符集下的代码(即可用于两种字符集版本的代码) 如果是多字节字符集可以如下书写: #include "stdafx.h" #include // 多了一个头文件包含 int main(int argc, char* argv[]) { for (int i = 1; i <= 9; ++i) { for (int j = 1; j <= i; ++j) { printf("%d*%d=%d\t", i, j, i*j); 4 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) } printf("\n"); } system("pause"); // 注意需要包含头文件stdlib.h或cstdlib(C++中) return 0; } 如果是大字符集下可以这样写: #include "stdafx.h" int main(int argc, wchar_t* argv[]) { for (int i = 1; i <= 9; ++i) { for (int j = 1; j <= i; ++j) { wprintf(L"%d*%d=%d\t", i, j, i*j); } wprintf(L"\n"); } _wsystem(L"pause"); return 0; } 说明: #include "stdafx.h" 是用向导生成的代码,它相当于包含了两个头文件(其实还有一些其它的东西): #include #include 代码中的 L 表示将字符或字符串转换成大字符集编码,如果不使用表示为多字节编码,使用_T 表示根 据当前工程属性的需要,要么与 L 的含义一样,要么相当于不使用,即为通用版本,也可以写成_TEXT, 它只是一个宏,在使用的时候,需要将要处理的字符串用括号括起来,但 L 在使用时则可以不用括号。 printf 用于多字节字符集环境中,wprintf 用于大字符集环境中,_tprintf 则可以根据工程属性自动选 择表示 printf 还是 wprintf,即为通用函数,其它的函数也一样,还有三个字符串的数据类型也是一样 的含义,即 char,wchar_t,TCHAR,其中 TCHAR 为通用版本。 system 在头文件 stdlib.h 头文件中,其它两个版本则不用包含这个头文件,这个函数的功能是执行系 统命令的,它能使用的系统命令在不同的电脑中不相同,如果需要查看,可以在命令行窗口中输入 help 即可,但只列举系统提供的,这里给出两个,一个是 system(“pause”)用于暂停执行,并显示一行文 字“按任意皱键继续„”,另一个是 system(“cls”)用于清除窗口中的文字,其实只要是在系统目录 system32 下的可执行文件都可用这个命令执行,所以用户也可以将自己需要的程序(为保证兼容性, 最好是命令行的程序)拷到对应的目录中,即可利用 system 执行。 代码中: system(“pause”); 相当于 C 语言的: printf(“按任意键继续…\n”); getch(); 或者是 C++的: cout << “按任意键继续…” << endl; // 不需要\n getch(); 说明: VS 2005 中 getch() 相当于函数 _getch() 5 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 代码中使用了制表符对齐,当然也可以不用制表符,那样的话,用户只能通过计算每一个表达式的文 字长度,并用空格填充不够的地方,才能对齐,代码量会很大,逻辑也会变得复杂,可见合理使用制 表符及转义字符可以为输出带来方便。 程序中使用的是 C 语言的代码,典型的 C++代码如下(只提供通用版的代码): int _tmain(int argc, TCHAR* argv[]) { for (int i = 1; i <= 9; ++i) { for (int j = 1; j <= i; ++j) { cout << i << _T("*") << j << _T("=") << i*j << _T("\t") << flush; } cout << endl;; } system("pause"); return 0; } 输出代码中的 flash 是刷新缓冲区的,但不向缓冲区插入任何字符,endl 是在缓冲区中插入一个回车 符,并刷新(注意\n 并不等同于 endl,\n 是字符,可以用在任何能用字符的地方,表示换行符,而 endl 不是个字符,只用于流输出中,表示向流中插入一个\n 字符并刷新缓冲区)。这只是顺便提及一 下, 如果不明白,可以复习 C++基础,个人觉得学习 C++的读者非常有必要精深的掌握 C 语言,这方 面个人觉得 C Primer(蓝色的封面)很精典,另谭浩强的 C 语言程序设计是轻量级的 C 语言经典教程, C++学习开始以复习 C 为基础,推荐 C++ Primer Plus(蓝色封面)这本书讲法讲解很完善,对 C++有 全面细致的学习后,可以从头到尾精细研究 C++ Primer(红色封面)这本书是学习 C++的经典之作, 但初学者看起来非常吃力。 b) 回退字符\b \b 可以让当前光标向后回退一个字符,下一次输出的时候,就会从当前位置输出,如果回退到的地方 有文字,那么将会被覆盖(屏幕上会覆盖,如果是在打印机上输出则前后两次输出会重叠在一起), 这个字符有着非常重要的作用,在教程的后面会讲述到一个 API 函数,可以让光标跳转到任意位置, 也可以完成\b 的功能,此处暂不作讲述。 \b 典型的用途是控制输入,控制台的输入一般不受用户掌控,很容易因为输入过多的字符而导致界面 被破坏,当对界面格式要求很严格的时候,不能使用普通的输入函数,除了使用系统检测键盘按键的 API 以外,C、C++都可以使用 getch()函数,这个函数与 getchar()作用一样,接收一个字符,但 getch() 不回显到屏幕上,而且不使用缓冲区缓存多余字符,保证了用户按下任意一个按键就一定结束这个函 数的调用,但 getchar()则需要到按了回车或强制刷新缓冲区才行。所以利用这个函数结合\b 就能完 成很多功能,比如删除一个字符等。这里为了更好的说明这个问题,给用户提供了一个小程序,用于 输入很多个学生的成绩,最后对成绩进行分数等级划分,这个程序使用了其它的一些界面编程技术, 这里不对其讲解,用户只需要阅读控制输入数值的代码,并结合界面进行观察就能理解了,具体代码 见附录中的源代码《分数划分工具》,这里仅摘抄输入一个 0-100 之间的分数的代码,用到了\b 而且 可以使用 BackSpace 来删除输入错误的数字。代码如下: //在控制台程序下输入一个整数 //tag=1有下划线,无下划线 //return -1表示取消输入 //return -2,-3表示参数不正确 //return -5表示进行统计 //return -4表示运行出错 //n表示待输入数据的最大位数 6 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) //附:这是早期初学时写的代码,格式、结构均很糟糕,用户根据自己的经验进行改写,特别 //是返回值,使用枚举更合理;错误处理使用断言更合理,程序结构应再进行模块化,使逻辑 //更清楚;变量名命名毫无意义;代码风格和格式比较杂乱等。此处不再进行旧代码的更改,直 //接贴在文档中,只求能说清楚问题即可。 //如果移植到VS 2005中请注意代码的字符集为多字节,读者也可以将其改写为大字符集,即通 //用字符集 int GetInteger(int n,int tag) { int kn=0,s=0,i; char c; if(n<=0 || n>=10) { printf("\n无法输入规定的数据!\n"); return -2; } if(!(tag==1 || tag==0)) { printf("\n函数参数不合法!\n"); return -3; } if(tag==1) { for(i=0;i=0 && s<=100)return s; } if(c==27)return -1; if(c=='s' || c=='S') return -5; if(c==8 && kn>0) { if(tag==0) printf("\b \b");// 这里用到了\b else printf("\b_\b");// 这里用到了\b if(kn==0)kn=0; else { kn--; s=s/10; } } if(c>='0'&& c<='9' && kn 3F ? 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G 48 H 49 I 4A J 4B K 4C L 4D M 4E N 9 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 1001111 79 1010000 80 1010001 81 1010010 82 1010011 83 1010100 84 1010101 85 1010110 86 1010111 87 1011000 88 1011001 89 1011010 90 1011011 91 1011100 92 1011101 93 1011110 94 1011111 95 1100000 96 1100001 97 1100010 98 1100011 99 1100100 100 1100101 101 1100110 102 1100111 103 1101000 104 1101001 105 1101010 106 1101011 107 1101100 108 1101101 109 1101110 110 1101111 111 1110000 112 1110001 113 1110010 114 1110011 115 1110100 116 1110101 117 1110110 118 1110111 119 1111000 120 1111001 121 4F O 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W 58 X 59 Y 5A Z 5B [ 5C \ 5D ] 5E ^ 5F _ 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g 68 h 69 i 6A j 6B k 6C l 6D m 6E n 6F o 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w 78 x 79 y 10 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 1111010 122 1111011 123 1111100 124 1111101 125 1111110 126 1111111 127 7A z 7B { 7C | 7D } 7E ~ 7F DEL (delete) 删除 d) 以%开头的格式控制符 C 语言中控制台的输出最常用的是 printf(),这个函数有两种用法,一种用法是直接输出字符串,如: printf("Test string"); 另一种用法是使用格式控制符,如: printf("Test NUM : %d\nTest string : %s", 10, "STRING"); 第二种用法中第一个参数为一个字符串,以其以%开头后接特定含义的字符(一般为一个,少数情况下 会有多个)表示格式说明,这是 C 语言中的内容,读者可参考相应书籍,格式控制符既可用于输出也可用 于输入,输入时使用 scanf()函数,在此不再细讲,仅列举常见的格式说明符。  类型:类型字符用以表示输出数据的类型,其格式符和意义如下表所示: 格式字符 意 义 d 以十进制形式输出带符号整数(正数不输出符号) o 以八进制形式输出无符号整数(不输出前缀 0) x,X 以十六进制形式输出无符号整数(不输出前缀 Ox) u 以十进制形式输出无符号整数 f 以小数形式输出单、双精度实数 e,E 以指数形式输出单、双精度实数 g,G 以%f 或%e 中较短的输出宽度输出单、双精度实数 c 输出单个字符 s 输出字符串  标志:标志字符为-、+、#、空格四种,其意义下表所示: 标志 意 义 + 空格 结果左对齐,右边填空格 输出符号(正号或负号) 输出值为正时冠以空格,为负时冠以负号 对 c,s,d,u 类无影响;对 o 类,在输出时加前缀 o;对 x 类,在输出时加 # 前缀 0x;对 e,g,f 类当结果有小数时才给出小数点  输出最小宽度:用十进制整数来表示输出的最少位数。若实际位数多于定义的宽度,则按实际位数 输出,若实际位数少于定义的宽度则补以空格或 0。  精度:精度格式符以“.”开头,后跟十进制整数。本项的意义是:如果输出数字,则表示小数的位数; 如果输出的是字符,则表示输出字符的个数;若实际位数大于所定义的精度数,则截去超过的部 分。  长度:长度格式符为 h,l 两种,h 表示按短整型量输出,l 表示按长整型量输出。 示例: #include void main() { 11 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) int float double char a = 15; b = 123.1234567; c = 12345678.1234567; d = 'p'; printf("a = %d,%5d,%o,%x\n", a, a, a, a); printf("b = %f,%lf,%5.4lf,%e\n", b, b, b, b); printf("c = %lf,%f,%8.4lf\n", c, c, c); printf("d = %c,%8c\n", d, d); } e) 数据流的格式设置 C++也可以兼容并使用 C 语言中的控制符进行输出,但更好的方法是使用 C++的数据流进行输出,主要 使用的是 cin 和 cout,读者可参考标准 C++的语法书籍,这里仅列举一些对流格式进行控制的方法和函数: 控制符 dec hex oct setbase(n) setfill(n) setprecision(n) setw(n) setiosflags(ios::fixed) setiosflags(ios::scientific) setiosflags(ios::left) setiosflags(ios::right) setiosflags(ios::skipws) setiosflags(ios::uppercase) setiosflags(ios::showpos) resetiosflags() 作 用 设置整数为十进制 设置整数为八进制 设置整数为十六进制 设置整数为 n 进制(n=8,10,16) 设置字符填充,c 可以是字符常或字符变量 设置浮点数的效数字为 设置字段宽度为 n 位 设置浮点数以固定的小数位数显示 设置浮点数以科学计数法表示 输出左对齐 输出右对齐 忽略前导空格 在以科学计数法输出 E 与十六进制输出 X 以大写输出,否则小写 输出正数时显示"+"号 终止已经设置的输出格式状态,在括号中应指定内容 【注】如果使用到以上控制符,除了包含 头外,还要包含 头文件并引入 std 命名空间。 示例: #include #include //输入输出流控制符需包含头文件 using namespace std; void main() { // dec:返回数值的十进制,oct:返回数值的八进制,hex:返回数值的十六进制 cout << "123456的十进制:" << dec << 123456 << ",八进制为:" << oct << 123456 12 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) << ",十六进制为:" << hex <<123456 <>和=两个操作符,最常用的是通过 cin >> ~~~来给一个变 量赋值就是因为重载了>>的原因: operator>> Calls a function on the input stream or reads formatted data from the input stream. 14 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) operator= Assigns the basic_istream on the right side of the operator to this object. This is a move assignment involving an rvalue reference that does not leave a copy behind.  basic_ostream(来自 MSDN) flush Flushes the buffer. put Puts a character in a stream. seekp Resets position in output stream. sentry The nested class describes an object whose declaration structures the formatted output functions and the unformatted output functions. swap Exchanges the values of this basic_ostream object for those of the provided basic_ostream object. tellp Reports position in output stream. write Puts characters in a stream. 重载的运算符: operator= Assigns the value of the provided basic_ostream object parameter to this object. operator<< Writes to the stream. 以下两条语句是我们最常用的 cout 的使用形式: cout << "testing" << endl; cout << "testing" << flush; 这里 endl 的全称是 std::endl,flush 的全称是 std::flush,两者都会把已经放到输出流缓冲区中的数据刷 新到标准输出设备(即使不加,在正常情况下,程序也会在缓冲区装满或者程序结束等需要的时候自动刷 新到设备,但最好是手动来刷新,原因可以参阅 C++陷阱和 C++ Primer 这类书籍,此处不对这些额外的内容 进行讲解。) 这两个的区别是 endl 会在刷新缓冲区前输出一个换行,而 flush 则不会。 注意这里的 flush 是 std::flush,不是 basic_ostream 中的成员 flush,要使用 basic_ostream 中的成员函数 flush 可以这样调用: cout.flush(); 为什么这里的 endl 和 flush 可以使用<<来连接使用呢?原因是 cout 的重载运算符<<返回值是一个 basic_ostream 对象,所以可以不断的用<<来连接输出,至于 endl 和 flush 也能用的原因是二者也是 basic_ostream 类型的对象(这可以查源代码即可知道)。 4) 键盘缓冲区处理 计算机中的缓冲区作用非常大,在控制台的输入、输出编程中也随时会接触到,本文不讲述过多缓冲 区的内容,读者有兴趣可参考相关书籍。 控制台程序中使用最多的是键盘缓冲区和文件缓冲区(文件缓冲区在本文中不涉及),一般情况下,每 当我们从键盘输入数据时,数据都会放到键盘缓冲区中,当按了回车以后,输入函数才会从缓冲区读取数 据,所以大多时候我们说从键盘输入数据,其它指的是从键盘输入数据到键盘缓冲区,再从缓冲区中读取 数据。 15 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) C/C++中的常规输入函数和输入流也是从键盘缓冲区中读取数据,当键盘缓冲区中没有数据时会等待用 户输入,当用户输入数据并且按了回车后才会从缓冲中读取,如果缓冲区的数据没有全部被读取,那么下 一次这类输入函数会直接从缓冲区剩余的数据中读取,这时就不会要求从键盘输入数据,也就不会出现等 待输入的情况,比如有以下代码: int main() { int a = 0; char str[100]; cin >> a; gets(str); } 读者可能会认为程序会要求我们输入两次内容,其实并没有,假如我们从键盘输入 123 并按了回车, 由于整数不能接收回车字符,那么就会有一个回车字符留在键盘缓冲区中,下一次 gets()读取数据的时候发 现键盘缓冲区中还有数据,那么直接读取,由于缓冲区中第一个字符串是回车字符,那么 str 内容就为空字 符串,只有一个结束符在 str 的 0 号元素中,这里不会让用户输入 str 的内容,结果是 a 为 123,str 为回车 符,同理,如果我们输入 123abc 并按回车,那么 a 为 123,str 为“abc”. 字符串读取的函数读到回车符时一般会把字符串连同换行符一起读取出来,并把换行符(即回车时输 入的换行符)丢弃,将剩余数据作为字符串内容赋予字符串,并自动添加结束符’\0’ 上面程序如果不是先输入一个数字再输入一个字符串,而是两次都输入字符串则不会出现这样的情况, 因为读取第一个字符串之后,键盘缓冲区中没有未接收的字符留在里面,第二次读取的时候就会等待用户 输入数据。 为了解决上面示例代码中出现的问题,我们就需要在读取第二个数据之前先将键盘缓冲区清空,其实 可以在每一次数据读取之前清空缓冲区,但是没必要这么麻烦,理解了输入函数的行为以后,只需要当前 面的读取操作会留下多余的数据影响下一次读取时,才需要在下一次读取之前清空缓冲区就可以了。对上 面的代码稍作修改即可正常工作: int main() { int a = 0; char str[100]; cin >> a; cin.sync(); // 使用这个方法可以达到清空缓冲区的目的 gets(str); } 缓冲区的清理工作可以有很多方法来完成,最简单的办法是使用字符读取函数将缓冲区中的内容全部 读取即可,如: char c; while((c = getchar()) != '\n' && c != EOF); 使用时注意包含头文件,并注意(c = getchar())的外面有一个括号!!!为什么?读者可以看一下运算符的 优先级就知道了,这里的 EOF 是一个宏,表示缓冲区的内容终止符 为了让它更有移植性,可以这样写更好(相应的内容会在后面宽字符的相关内容中讲述): 16 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) TCHAR c; while((c = getchar()) != _T('\n') && c != EOF); 同样的,也是要注意包含头文件。用这种方法有一个问题,如果缓冲区中没有数据,那么至少需要用 户按一下回车才行,有些情况下会让用户觉得好像要输入两次才生效,对于程序的可用性有一点点影响, 不过如果能确保使用它时缓冲区中有残留数据倒不会有这个问题。 除此之外,要清除键盘缓冲区的内容还可以用以下的任何一种方法: cin.sync(); fflush(stdin); rewind(stdin); setbuf(stdin, NULL); cin.ignore(numeric_limits::max(),’\n’); //清除当前行 cin.ignore(numeric_limits::max()); //清除 cin 里所有内容 使用这些函数和方法时注意包含所需的头文件,至于为它们的功能和这样使用的原因读者自行查阅, 限于篇幅不再讲解,附上一段网络上关于 cin.ignore()的说明文字,作者不详: 通常大家会用 sync()函数来清除输入缓冲区的内容。个人感觉还是用 ignore 更好。 先简单说下 sync(),sync()的作用就是清除输入缓冲区。成功时返回 0,失败时 badbit 会置位,函数返回-1. 另外,对于绑定了输出的输入流,调用 sync(),还会刷新输出缓冲区。 但由于程序运行时并不总是知道外部输入的进度,很难控制是不是全部清除输入缓冲区的内容。通常我们有可能只是希 望放弃输入缓冲区中的一部分,而不是全部。比如清除掉当前行、或者清除掉行尾的换行符等等。但要是缓冲区中已经有了 下一行的内容,这部分可能是我们想保留的。这个时候最好不要用 sync()。可以考虑用 ignore 函数代替。 cin.ignore(numeric_limits::max(),’\n’);//清除当前行 cin.ignore(numeric_limits::max()); //清除 cin 里所有内容 不要被长长的名字吓倒,numeric_limits::max()不过是 climits 头文件定义的流使用的最大值,你也可以用一个足够大 的整数代替它。 使用 ignore 显然能比 sync()更精确控制缓冲区。 还有 ignore()这样用,可以清除一个字符。不过这个用的不多,对于清楚知道要弃置一个字符的情况,完全可以由程序 做一次读操作,然后放弃读入内容来实现。 需要说明的是,以上几种方法在 MinGW 5.1 和 VS 2010 中都测试通过,Linux 中仅测试过 setbutf()有 效,加之 fflush, rewind 主要用于文件缓冲区处理,作者推荐使用数据流时可用 cin.sync(),使用 stdio.h 时可用 setbuf()这样移植性方面会好一些,处理文件缓冲区时使用 fflush()、rewind()、setbuf()都行。 【建议:一般情况下,控制台的数据输入输出不要大量混用 C 的 stdio.h 和 C++的 iostream 具有相同功 能的函数,由于两种库实现机制不一样,可能会有冲突或者兼容性方面的问题产生。】 5) 关于 C/C++中的字符串拼接问题 本来这一块的内容并不属于控制台输入、输出范畴,但由于输入、输出与字符串的关系非常紧密,所 以在此提及一下,读者如果不明白,可以复习一下 C 和 C++中的语法。 在输出非常长的字符串的时候,可以有以下三种方法来缩短每一行的字符量:  使用一个临时的变量,并采用字符串连接函数来分段连接字符,以便实现折行书写代码,如: CString strTmp; strTmp = strTmp + “vecry vecry vecry vecry vecry vecry vecry vecry long string” + “vecry vecry vecry vecry vecry vecry vecry vecry long string” + “vecry vecry vecry vecry vecry vecry vecry vecry long string”; puts(strTmp); // VS 2005 及以后的 CString 重载了 C 风格的字符指针强制类型转换操作 17 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理)  在使用 cout 输出时可以借助<<操作折行书写代码: cout << “vecry vecry vecry vecry vecry vecry vecry vecry long string” << “vecry vecry vecry vecry vecry vecry vecry vecry long string” << “vecry vecry vecry vecry vecry vecry vecry vecry long string”;  标准 C++语法中允许使用“\”在行尾来把下一行的代码拼接成一行,编译器编译时会把它当作一 行来处理,不过这种方式只允许折行后的字符串从行开始的地方写,否则字符串前面的空格和制 表符也会被拼接成字符的一部分,而且“\”后面不能有其它字符,只能是回车符: char str[1000] = "vecry vecry vecry vecry vecry vecry vecry vecry long string \ vecry vecry vecry vecry vecry vecry vecry vecry long string \ vecry vecry vecry vecry vecry vecry vecry vecry long string"; 注意:这种方式下,第二行及以后的行,凡是在字符串没有结束前,行前面的缩进也会算作字符串的 一部分,所以不要使用缩进。细心的读者会发现只有每一行有一个引号,以及最后一行有一个引号。 这种方式对于任何的代码都有效,特别是对于比较长的宏定义最有用,因为宏只能定义在一行,使用 这种方式,编译器会把它当作是书写成一行的。  标准 C++语法中,字符串可以使用拼接的多个引号引起来的间断字符串: char str[1000] = "vecry vecry vecry vecry vecry vecry vecry vecry long string" "vecry vecry vecry vecry vecry vecry vecry vecry long string" "vecry vecry vecry vecry vecry vecry vecry vecry long string"; 这种方式每行都有双引号,可以不用理会行前面的缩进以及行尾的其它空白字符。 6) 怎样从控制台复制粘贴文字 本教程除了讲解界面相关的编程以外,还对一些控制台用的重要操作进行说明,以便读者能更好的在 控制台界面下工作。 要复制粘贴命令行窗口中的文字,可以点击鼠标右键,选择标记,并用鼠标拖动标记一片区域并点击 右键即可完成复制操作。 粘贴则点击鼠标右键选择粘贴即可。 7) 将批处理 bat 转换为 exe 程序 前面讲解过 system,这个函数除了执行命令行功能和 system32 下的程序以外,最重要的一个作用是可 以利用它来把批处理文件中的命令逐一的转换为 system()的调用,并编译生成可执行文件,这样的兼容性是 最好的,而且还能加入其它的一些程序功能,显著增强程序的功能,网络上也有很多将批处理转换为 EXE 的工具,但一般都存在兼容性问题,且难以更改或扩展生成文件的功能。 8) 在 Visual Studio 2005 中设置控制台程序的图标 Windows 下的程序一般都有一个程序的图标,控制台也可以抛弃原始的应用程序默认图标,让自己的 程序编译后有个性化的程序图标,具体操作如下: 在 VS 2005 中打开“资源视图窗口”(Resource View)导入添加一个 ico 的图标文件(图标可以利用 VS 自带的图标工具来新建,也可以利用专业的 Icon Workshop 等工具来制作,但 VS 自带的图标工具仅支持 256 18 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 色,表现力太差,关于 ico 图标的一些制作常识可以慢慢摸索,本教程不作过多讲解),查看属性窗口,保 证图标的 ID 标识在所有图标中是最小的,这样编译器会把最小标识的那个图标当作应用程序的图标。 默认情况下 VC++ 6.0 并没有生成.rc 文件,也就不会出现在资源视图窗口中,这时只需创建.rc 资源文件 即可看到,VS 2005 中用向导则生成这个文件,如果生成的时候选择的是空的工程,也可以采用相同方法添 加。 9) 重定向控制台程序的输出 对于控制台的应用程序,可以从命令提示符中输入所在路径及文件名来启动,默认情况下,控制台程 序启动后,输出设备为显示器,如果重定向输出为文件,程序执行期间的所有输出将输出到文件,不会在 屏幕上显示任何字符,就算需要用户输入一些字符以继续执行的程序也不会有任何显示。 重定向输出在 Windows 系统中使用主要用在两个方面,一是没有源代码的命令行程序只提供输出到屏 幕的功能,没有保存结果到文件的功能,如果需要把计算的结果输出到文件,则可利用输出重定向实现, 这样不用对程序进行任何更改即可将原本输出到屏幕的计算结果输出到文件;二是控制台程序在开发的时 候,只是显示在屏幕上,但由于开的程序时就只把它作为一个小工具,临时用一下,就没有必要为它提供 输入到文件的功能,这样可以不用写这些功能代码,更用不到去测试它,从而减少许多的开发测试时间, 所以这个时候也可以借助重写向技术来让程序间接的支持输出到文件的功能。 重定向的使用方法为:在命令行中输入命令后,再输入一个空格,后接两个大于号,并跟上一个文件 名,这样即可实现,如在命令提示符窗口中输入命令并按回车: pause >> c:\test.txt 即可在 C:\test.txt 中看到输出的文件中有程序执行的内容,这里的路径也可以使用相对路径,相对路径 的起始节点在命令行中所需执行的程序所在的目录。 一味的使用重定向功能可能是个麻烦,如果命令行程序的用户交互过程较多,那么,所有的输入、输 出都只能摸黑操作,非常不便,所以,如有可能在程序中提供将输出结果保存到文件的功能,会更方便一 些。 19 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 第二部分 控制台界面编程详解 文本界面的控制台应用程序开发是深入学习 C++、掌握交互系统的实现方法的最简单的一种手段。然而, Visual C++的 C++专用库却没有 TC 所支持的文本(字符)屏幕控制函数,为此本系列文章从一般控制步骤、控 制台窗口操作、文本(字符)控制、滚动和移动、光标、键盘和鼠标等几个方面讨论控制台窗口界面的编程控 制方法。 在众多 C++开发工具中,由于 Microsoft 本身的独特优势,选用 Visual C++已越来越被众多学习者所接受。 显然,现今如果还再把 TC 作为开发环境的话,不仅没有必要,而且也不利于向 Windows 应用程序开发的过 渡。然而,Visual C++的 C++专用库却没有 TC 所支持的文本屏幕(控制台窗口)控制函数(相应的头文件是 conio.h)。 这必然给 C++学习者在文本界面设计和编程上带来诸多不便。要知道,文本界面设计是一种深入学习 C++、 掌握交互系统的实现方法的最简单的一种手段,它不像 C++的 Windows 图形界面应用程序,涉及知识过多。 为此,本系列文章来讨论在 Visual C++开发环境中,如何编写具有美观清晰的控制台窗口界面的 C++应用程 序。 1) 概述 所谓控制台应用程序,就是指那些需要与传统 DOS 操作系统保持某种程序的兼容,同时又不需要为用 户提供完善界面的程序。简单地讲,就是指在 Windows 环境下运行的 DOS 程序。一旦 C++控制台应用程序 在 Windows 9x/NT/2000 操作系统中运行后,就会弹出一个窗口。例如新建一个空的工程,添加一个 cpp 源 程序文件,在文档窗口中输入下列代码,并编译执行(在 VS 2005 中此处设置为多字节字符集): #include using namespace std; void main() { cout << "Hello, Console!" << endl; } 程序运行后,弹出下图的窗口。 注意:有很多初学 C、C++的读者,喜欢使用快捷键,我个人比较喜欢将 VS 2005 的键盘快捷键设置为 VC 6 的,这里也以 VC 6 的快捷键作说明。使用 F5 是运行程序,这个程序运行后马上就结束了,用户来不及 查看窗口输出,如果要看到窗口输出有两个办法,一是使用 Ctrl + F5,二是在 main()函数结束的大括号之前 添加条语句:systme(“pause”),并用 F5 执行,使用 F5 与生成的应用程序的方式一样,所以推荐后一种方法, 两种方法执行的操作是不太一样的,读者自己查看相应的快捷键对应的功能即可知晓,此处不再赘述。  图片中的窗口就是控制台窗口,与传统的 DOS 屏幕窗口相比最主要的区别有: 20 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) (1) 默认的控制台窗口有系统菜单和标题,它是一个内存缓冲区窗口,缓冲区大小取决于 Windows 操作 系统的分配;而 DOS 屏幕是一种物理窗口,不具有 Windows 窗口特性,其大小取决于 ROM BIOS 分配的内 存空间。 (2) 控制台窗口的文本操作是调用低层的 Win32 API,而 DOS 屏幕的文本操作是通过调用 BIOS 的 16(10h) 中断而实现的。 (3) 默认的控制台窗口可以接收键盘和鼠标的输入信息,设备驱动由 Windows 管理,而 DOS 屏幕窗口 接收鼠标时需要调用 33h 中断,且鼠标设备驱动程序由自己安装。 2) 控制台文本窗口编程的一般控制步骤 除了使用第一部分介绍的控制台界面控制的相关方法以外,用得最多的是调用系统的控制台 API 来对其 进行操作,要调用这些 API,需要提供控制台窗口的句柄(句柄用于 Windows 程序中标识窗口等资源对的, 关于它的详细概念读者自行了解,对于这些编程知识,不再本教程的讲解范围之内,在此仅提及一下),才 能利用这个句柄对它进行各种操作,控制台窗口界面的一般编程控制步骤如下: 第一步:调用 GetStdHandle 获取当前的标准输入(STDIN)和标准输出(STDOUT)设备句柄。函数原型为: HANDLE GetStdHandle( DWORD nStdHandle ); 其中,nStdHandle 可以是 STD_INPUT_HANDLE(标准输入设备句柄)、STD_OUTPUT_HANDLE(标准输出设 备句柄)和 STD_ERROR_HANDLE(标准错误句柄)。需要说明的是,“句柄”是 Windows 最常用的概念。它通常 用来标识 Windows 资源(如菜单、图标、窗口等)和设备等对象。虽然可以把句柄理解为是一个指针变量类型, 但它不是对象所在的地址指针,而是作为 Windows 系统内部表的索引值来使用的。 第二步:调用相关文本界面控制的 API 函数。这些函数可分为三类。一是用于控制台窗口操作的函数(包 括窗口的缓冲区大小、窗口前景字符和背景颜色、窗口标题、大小和位置等);二是用于控制台输入输出的 函数(包括字符属性操作函数);其他的函数并为最后一类。 第三步:调用 CloseHandle()来关闭输入输出句柄。 注意,这些函数都是 Windows 的 API 函数,所以在使用之前还必须包含头文件 Windows.h。在使用系 统所提供的 API 过程中还需要了解和认识一些 Windows.h 中定义的数据结构,用到的时候会逐一讲解。 还需要说明的是,虽然在 C++中,iostream.h 定义了 cin 和 cout 的标准输入和输出流对象,以及一些精 细控制输入、输出的标记,在 C 中提供了格式控制符,但它们只能实现基本的输入输出操作,最多能控制 文字的位置、数目,对于控制台窗口文字界面颜色和其它非文字性质的控制却无能为力,而且 C++中的流操 作不能与 stdio.h 和 conio.h 友好相处,因为 iostream.h 和它们是 C++两套不同的输入输出操作方式,使用时 要特别注意,不要在代码中混用操作相同的对象,或者在一个工程中只用一种方案。 3) 控制台窗口操作函数 用于控制台窗口操作的常用 API 函数如下: GetConsoleScreenBufferInfo 获取控制台窗口信息 GetConsoleTitle 获取控制台窗口标题 ScrollConsoleScreenBuffer 在缓冲区中移动数据块 SetConsoleScreenBufferSize 更改指定缓冲区大小 SetConsoleTitle 设置控制台窗口标题 SetConsoleWindowInfo 设置控制台窗口信息(包括窗口大小、位置等) 完整的 VS 2005 中能用的控制台函数列举如下(绝大多数在 VC 6 中也能用),以便读者查阅使用, 还有一部分这里的常用函数没有列举出来的,后面用到也会进行讲解,读者如有不懂的地方,可自己查阅 MSDN,这里需要提醒的一点是有些函数在 VS 2005 中可以原型和参数类型发生了一些微小的变化,在 VC 6 中可以需要适当更改才能正常使用,对于掌握了 C++语法的读者会很轻松就能完成: 21 / 58 Function(函数) AddConsoleAlias AllocConsole AttachConsole CreateConsoleScreenBuffer FillConsoleOutputAttribute FillConsoleOutputCharacter FlushConsoleInputBuffer FreeConsole GenerateConsoleCtrlEvent GetConsoleAlias GetConsoleAliases GetConsoleAliasesLength GetConsoleAliasExes GetConsoleAliasExesLength GetConsoleCP GetConsoleCursorInfo GetConsoleDisplayMode GetConsoleFontSize GetConsoleMode GetConsoleOutputCP GetConsoleProcessList GetConsoleScreenBufferInfo GetConsoleSelectionInfo GetConsoleTitle C/C++控制台界面编程(V 2) (wyz5@163.com 整理) Description(描述) Defines a console alias for the specified executable. Allocates a new console for the calling process. Attaches the calling process to the console of the specified process. Creates a console screen buffer. Sets the text and background color attributes for a specified number of character cells. Writes a character to the console screen buffer a specified number of times. Flushes the console input buffer. Detaches the calling process from its console. Sends a specified signal to a console process group that shares the console associated with the calling process. Retrieves the specified alias for the specified executable. Retrieves all defined console aliases for the specified executable. Returns the size, in bytes, of the buffer needed to store all of the console aliases for the specified executable. Retrieves the names of all executables with console aliases defined. Returns the size, in bytes, of the buffer needed to store the names of all executables that have console aliases defined. Retrieves the input code page used by the console associated with the calling process. Retrieves information about the size and visibility of the cursor for the specified console screen buffer. Retrieves the display mode of the current console. Retrieves the size of the font used by the specified console screen buffer. Retrieves the current input mode of a console's input buffer or the current output mode of a console screen buffer. Retrieves the output code page used by the console associated with the calling process. Retrieves a list of the processes attached to the current console. Retrieves information about the specified console screen buffer. Retrieves information about the current console selection. Retrieves the title bar string for the current console window. 22 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) GetConsoleWindow Retrieves the window handle used by the console associated with the calling process. GetCurrentConsoleFont Retrieves information about the current console font. GetLargestConsoleWindowSize Retrieves the size of the largest possible console window. GetNumberOfConsoleInputEvents Retrieves the number of unread input records in the console's input buffer. GetNumberOfConsoleMouseButtons Retrieves the number of buttons on the mouse used by the current console. GetStdHandle Retrieves a handle for the standard input, standard output, or standard error device. HandlerRoutine An application-defined function used with the SetConsoleCtrlHandler function. PeekConsoleInput Reads data from the specified console input buffer without removing it from the buffer. ReadConsole Reads character input from the console input buffer and removes it from the buffer. ReadConsoleInput Reads data from a console input buffer and removes it from the buffer. ReadConsoleOutput Reads character and color attribute data from a rectangular block of character cells in a console screen buffer. ReadConsoleOutputAttribute Copies a specified number of foreground and background color attributes from consecutive cells of a console screen buffer. ReadConsoleOutputCharacter Copies a number of characters from consecutive cells of a console screen buffer. ScrollConsoleScreenBuffer Moves a block of data in a screen buffer. SetConsoleActiveScreenBuffer Sets the specified screen buffer to be the currently displayed console screen buffer. SetConsoleCP Sets the input code page used by the console associated with the calling process. SetConsoleCtrlHandler Adds or removes an application-defined HandlerRoutine from the list of handler functions for the calling process. SetConsoleCursorInfo Sets the size and visibility of the cursor for the specified console screen buffer. SetConsoleCursorPosition Sets the cursor position in the specified console screen buffer. SetConsoleDisplayMode Sets the display mode of the specified console screen buffer. SetConsoleMode Sets the input mode of a console's input buffer or the output mode of a console screen buffer. SetConsoleOutputCP Sets the output code page used by the console associated with the calling 23 / 58 SetConsoleScreenBufferSize SetConsoleTextAttribute SetConsoleTitle SetConsoleWindowInfo SetStdHandle WriteConsole WriteConsoleInput WriteConsoleOutput WriteConsoleOutputAttribute WriteConsoleOutputCharacter C/C++控制台界面编程(V 2) (wyz5@163.com 整理) process. Changes the size of the specified console screen buffer. Sets the foreground (text) and background color attributes of characters written to the console screen buffer. Sets the title bar string for the current console window. Sets the current size and position of a console screen buffer's window. Sets the handle for the standard input, standard output, or standard error device. Writes a character string to a console screen buffer beginning at the current cursor location. Writes data directly to the console input buffer. Writes character and color attribute data to a specified rectangular block of character cells in a console screen buffer. Copies a number of foreground and background color attributes to consecutive cells of a console screen buffer. Copies a number of characters to consecutive cells of a console screen buffer. 下列举一个示例,程序如下(在 VS 2005 多字节字符集下调试通过,注意这里使用的是 C++工程,不是 C 的,如果需要移植到 C 的工程的读者,请确保变量的声明放到大括号的最前面,因为 C 语言中的变量的声 明必须放在大括号中的最前面,否则编译不过!!!): #include #include #include void main() { // 1.获取控制台句柄 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄 // 2.使用控制台API对它进行各种操作 { // 2.1 示例:获取窗口标题 char strTitle[255]; GetConsoleTitle(strTitle, 255); // 获取窗口标题 printf("当前窗口标题是:%s\n", strTitle); getch(); } { // 2.2 示例:设置窗口标题 SetConsoleTitle("控制台窗口操作"); // 获取窗口标题 printf("窗口标题变成了\"控制台窗口操作\"\n"); getch(); } { // 2.4 示例:设置窗口大小 SMALL_RECT rc = {0,0, 50-1, 15-1}; // 重置窗口位置和大小,设置为50*15 SetConsoleWindowInfo(hOut, true , &rc); 24 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) printf("窗口的大小被改变了\n"); getch(); } // 3.关闭句柄 CloseHandle(hOut); } // 关闭标准输出设备句柄 需要说明的是,控制台窗口的原点坐标是(0, 0),而最大的坐标是缓冲区大小减 1,例如当缓冲区大小为 80*25 时,其最大的坐标是(79, 24),这个最大坐标可以利用 API 来调大或调小,但由于中文输入法的切换会 占据最后一行,如果对界面要求非常严格的情况下,最好不要使用最后一行进行文字的输出,以免切换为 中文输入法时界面被破坏。程序中并没有使用代码来调整缓冲区大小,而是使用系统默认的缓冲区大小, 请注意:实际应用中设置窗口大小的同时还要考虑缓冲区的大小,并进行调整,保证缓冲区大小不小于窗 口大小。 程序中用到的 API 函数后面会逐一讲解,这里只对获取、关闭句柄作讲解,其中关闭句柄比较简单, 知道怎么调用即可,下面详细说明一下获取句柄的函数以及涉及到的数据结构: HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄 GetStdHandle 函数不属于控制台的编程函数,是系统用于获取标准设备句柄的函数,它需要一个参数, 这个参数是一个宏,共有三种取值,分别为:标准输出设备 STD_INPUT_HANDLE(值为双字节的-10), 标准输入设备 STD_OUTPUT_HANDLE(值为双字节的-11),标准错误设备 STD_ERROR_HANDLE(值 为双字节的-12),而 HANDLE 则是 Windows 的句柄数据类型。这个语句相当于: HWND hOut = GetConsoleWindow();// 获取标准输出设备句柄 两个函数都能获取控制台窗口句柄,后一个函数无参数,返回的是 HWND 的句柄(HWND 一般是与 窗口相关的句柄)代码中之所以不用这个函数是因为它有可能导致编译错误,编译器提示无法找到标识符, 原因在 MSDN 中有详细说明,大意是说 Windows.h 头文件是在 VC ++发布的的时候一起发布的,当时还没 有这个函数,要正常调用需要用户更新 SDK 头文件,并在代码中作一些其它处理才行,所以才使用 GetStdHandle 函数,MSDN 2005 中部分原文如下: Microsoft Visual C++ includes copies of the Windows header files that were current at the time Visual C++ was released. Therefore, if you install updated header files from an SDK, you may end up with multiple versions of the Windows header files on your computer. If you do not ensure that you are using the latest version of the SDK header files, you will receive the following error code when compiling code that uses features that were introduced after Visual C++ was released: error C2065: undeclared identifier. Certain functions that depend on a particular version of Windows are declared using conditional code. This enables you to use the compiler to detect whether your application uses functions that are not supported on its target version(s) of Windows. To compile an application that uses these functions, you must define the appropriate macros. Otherwise, you will receive the C2065 error message. 顺便提及一下,MSDN 中的好多示例代码,都没有关闭获取到的句柄,不知道有没有内存泄漏问题, 为以防万一,最好在不用的时候显式的关闭它,且尽量保证同一设备只获取一次,并用变量将句柄保存下 来,以后再用到可以从变量中获取,尽量不要再次调用获取句柄的函数。 4) 文本属性操作 与 DOS 字符相似,控制台窗口中的字符也有相应的属性。这些属性分为:文本的前景色、背景色和双 字节字符集(DBCS)属性三种。事实上,我们最关心是文本颜色,这样可以构造出美观的界面。颜色属性都是 一些预定义标识: 值 颜色 FOREGROUND_BLUE 蓝色 25 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) FOREGROUND_GREEN 绿色 FOREGROUND_RED 红色 FOREGROUND_INTENSITY 加强 BACKGROUND_BLUE 蓝色背景 BACKGROUND_GREEN 绿色背景 BACKGROUND_RED 红色背景 BACKGROUND_INTENSITY 背景色加强 COMMON_LVB_REVERSE_VIDEO 反色 这些宏所标识的是一些常用的颜色,其实颜色共有 16 种,其中前景色取值为 0~15,背景色取值为 0~240 且只能为 16 的倍数1,且可以直接用数字取 0~255 之间的任意数,如果用到,可以直接传数值,当然,也可 以定义一个自己的宏来使用会更好,要正确的按要求的前景色和背景色显示字符,需要将前景色和背景色 对应的数值进行按位或运算即可得到颜色值。下面是个人用循环生成的所有的控制台能用的前景色和背景 色及对应的值,其中第一个由于前景色和背景色都是黑色,即都为 0,所以看不到文字2,如图: 1 【其实本质上无论前景色和背景色是存储在一个 8 位的整数中,其中低四位二进制位存储背景色,高四位存储前景色, 所以一个数值,其实同时存储了前景色和背景色,16 的倍数,高四位为 0 表示前景色为空,只有背景色,0~16 的数,低四位 为空,表示背景色为空,只有前景色,所以颜色值可以用一个无符号的 char 型数据来存储,其值可以为 0~255,255 为 240+15, 这与 RGB 中的颜色值为 0~255 的含义不相同,不要混淆了,有关 RGB 方面的颜色知识可以参考一些窗口界面编程的书箱中调 色板相关的章节或美术方面的书籍,此处请注意最后一个“反色”,它并不是种颜色,而是取当前颜色的反色,其实就是表示 颜色的值的二进制取反运算得到的颜色值】 2 【如果不知道前景色和背景色的概念可以查阅一些美工的基础书籍,在控制台窗口中这两个概念很简单,前景色指显 示的字符颜色,背景色指字符背后的颜色,请注意前景色和背景色的概念可不局限于此,这里只是方便用户理解才这样说的】 26 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 与文本属性相关的主要函数有: BOOL FillConsoleOutputAttribute( // 填充字符属性 HANDLE hConsoleOutput, // 句柄 WORD wAttribute, // 文本属性 DWORD nLength, // 个数 COORD dwWriteCoord, // 开始位置 LPDWORD lpNumberOfAttrsWritten // 返回填充的个数 ); BOOL SetConsoleTextAttribute( // 设置WriteConsole等函数的字符属性 HANDLE hConsoleOutput, // 句柄 WORD wAttributes // 文本属性 ); BOOL WriteConsoleOutputAttribute( // 在指定位置处写属性 HANDLE hConsoleOutput, // 句柄 CONST WORD *lpAttribute, // 属性 DWORD nLength, // 个数 COORD dwWriteCoord, // 起始位置 LPDWORD lpNumberOfAttrsWritten // 已写个数 ); 另 外 , 获 取 当 前 控 制 台 窗 口 的 文 本 属 性 是 通 过 调 用 函 数 GetConsoleScreenBufferInfo 后 , 在 CONSOLE_SCREEN_ BUFFER_INFO 结构成员 wAttributes 中得到。 5) 文本输出 文本输出函数有: BOOL FillConsoleOutputCharacter( // 填充指定数据的字符 HANDLE hConsoleOutput, // 句柄 TCHAR cCharacter, // 字符 DWORD nLength, // 字符个数 COORD dwWriteCoord, // 起始位置 LPDWORD lpNumberOfCharsWritten // 已写个数 ); BOOL WriteConsole( // 在当前光标位置处插入指定数量的字符 HANDLE hConsoleOutput, // 句柄 CONST VOID *lpBuffer, // 字符串 DWORD nNumberOfCharsToWrite, // 字符个数 LPDWORD lpNumberOfCharsWritten, // 已写个数 LPVOID lpReserved // 保留 ); BOOL WriteConsoleOutput( // 向指定区域写带属性的字符 HANDLE hConsoleOutput, // 句柄 CONST CHAR_INFO *lpBuffer, // 字符数据区 COORD dwBufferSize, // 数据区大小 COORD dwBufferCoord, // 起始坐标 PSMALL_RECT lpWriteRegion // 要写的区域 ); BOOL WriteConsoleOutputCharacter( // 在指定位置处插入指定数量的字符 HANDLE hConsoleOutput, // 句柄 LPCTSTR lpCharacter, // 字符串 27 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) DWORD nLength, // 字符个数 COORD dwWriteCoord, // 起始位置 LPDWORD lpNumberOfCharsWritten // 已写个数 ); 可以看出:WriteConsoleOutput 函数功能相当于 SetConsoleTextAttribute 和 WriteConsole 的功能。 而 WriteConsoleOutputCharacter 函数相当于 SetConsoleCursorPosition(设置光标位置)和 WriteConsole 的功能。不过在具体使用要注意它们的区别。另外,用户也可以利用设置光标和设置文本颜色再配合 printf 或 cout 来实现输出也行,只不过代码稍微多一些而已。 6) 文本操作示例 下面看一个示例程序,用文本操作来模拟一个窗口(VS 2005 下编译运行通过,代码为通用字符集, 读者也可以移植为双字节字符集,工程为 C++工程,需要移植为 C 工程时请注意变量的声明位置,运行环 境为 Windows 7 64bit 旗舰版以及 Windows XP 皆运行通过。输出的文本只能为单行,文本长度为奇数和 偶数皆测试通过,含有中文字符也测试通过,代码中大量使用了根据字符集和字符长度来计算所需值的代 码,这是为了移植性和通用性考虑,如果只是简单的输出最终的结果,可以缩减到一半的代码。): #include #include // 支持通用字符集的一些操作头文件,使用双字节或大字符集时可以不包含它 HANDLE hOut; void ShadowWindowLine(TCHAR *str); // 在具有阴影效果的窗口中显示一行字符,窗口为居中显示 void DrawBox(SMALL_RECT rc); // 绘制边框 void _tmain() { hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄 ShadowWindowLine(_T("Display a line of words, and center the window with shadow.")); CloseHandle(hOut); // 关闭标准输出设备句柄 _tsystem(_T("pause")); } void ShadowWindowLine(TCHAR *str) { CONSOLE_SCREEN_BUFFER_INFO bInfo; // 用于存储窗口缓冲区信息 GetConsoleScreenBufferInfo(hOut, &bInfo); // 获取窗口缓冲区信息 // 计算显示窗口大小和位置 int x1(0), y1(0), x2(0), y2(0), chNum(NULL); chNum = (int)_tcslen(str); // 求字符串长度strlen的通用字符集版本 // 注意窗口的大小与窗口缓冲区的大小并不一定相等,所以不能使用缓冲区大小来当作窗口大小 // 此处获取的窗口的区域坐标,再使用右边坐标减左边坐标即是窗口宽,高也一样的原理 x1 = (bInfo.srWindow.Right - bInfo.srWindow.Left - chNum) / 2 - 2; y1 = (bInfo.srWindow.Bottom - bInfo.srWindow.Top) / 2 - 2; // 此处根据字符串长度来保证边框适当的增减一个字符,以便输入时不会和四角空缺 x2 = x1 + chNum + 4 + (int)_tcslen(str) % 2; y2 = y1 + 6; WORD att1 = BACKGROUND_INTENSITY; // 阴影属性 WORD att0 = FOREGROUND_RED | FOREGROUND_GREEN |FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_BLUE; // 文本属性 WORD attText = FOREGROUND_RED |FOREGROUND_INTENSITY; // 文本属性 28 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) // 设置阴影 COORD posShadow = {x1 + 1, y1 + 1}, posText = {x1, y1}; for (int i = 0; i < 5; ++i) { FillConsoleOutputAttribute(hOut, att1, chNum + 5, posShadow, NULL); ++posShadow.Y; } // 填充窗口背景(因为窗口边框使用中文字符绘制,所以最好 // 保证窗口的宽度为适当增加一个或减少一个字符后边框能衔接正常) for (int i = 0; i < 5; ++i) { FillConsoleOutputAttribute(hOut, att0, chNum + 5, posText, NULL); ++posText.Y; } // 写文本和边框 posText.X = x1 + 2; posText.Y = y1 + 2; WriteConsoleOutputCharacter(hOut, str, (int)_tcslen(str), posText, NULL); SMALL_RECT rc = {x1, y1, x2-2, y2-2}; DrawBox(rc); SetConsoleTextAttribute(hOut, bInfo.wAttributes); // 恢复原来的属性 } void DrawBox(SMALL_RECT rc) { TCHAR chBox[6][3] = {_T("┌"), _T("┐"), _T("└"), _T("┘"), _T("─"), _T("│")}; COORD pos = {rc.Left, rc.Top}; // 先输出边线,再输出四个角,防止四个角被连线压住 // 输出时为两个字符,主要是为了防止出现汉字占两个字节时输出异常 // 这样如果为大字符集或多字节字符集的英文字符则输出对应字符和一个'\0' // 如果为多字节字符的中文字符则输出字符,且没有'\0' // 水平线一个字符占两个字节,所以步长为 // 输出的长度使用长度计算可以屏蔽不同字符集上长度不相同时造成的空格输出破坏界面 for (pos.X = rc.Left; pos.X < rc.Right; pos.X += 2) { pos.Y = rc.Top; WriteConsoleOutputCharacter(hOut, chBox[4], (int)_tcslen(chBox[4]), pos, NULL);// 顶 pos.Y = rc.Bottom; WriteConsoleOutputCharacter(hOut, chBox[4], (int)_tcslen(chBox[4]), pos, NULL);// 底 } for (pos.Y = rc.Top; pos.Y < rc.Bottom; ++pos.Y) { pos.X = rc.Left; WriteConsoleOutputCharacter(hOut, chBox[5], (int)_tcslen(chBox[5]), pos, NULL);// 左 pos.X = rc.Right; WriteConsoleOutputCharacter(hOut, chBox[5], (int)_tcslen(chBox[5]), pos, NULL);// 右 } pos.X = rc.Left; pos.Y = rc.Top; WriteConsoleOutputCharacter(hOut, chBox[0], (int)_tcslen(chBox[0]), pos, NULL);// 左上 角 pos.X = rc.Right; pos.Y = rc.Top; WriteConsoleOutputCharacter(hOut, chBox[1], (int)_tcslen(chBox[1]), pos, NULL);// 右上 角 29 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) pos.X = rc.Left; pos.Y = rc.Bottom; WriteConsoleOutputCharacter(hOut, chBox[2], (int)_tcslen(chBox[2]), pos, NULL);// 左下 角 pos.X = rc.Right; pos.Y = rc.Bottom; WriteConsoleOutputCharacter(hOut, chBox[3], (int)_tcslen(chBox[3]), pos, NULL);// 右下 角 } 程序运行结果如下图所示。 下面对程序中用到的一些数据结构作简要介绍:  CONSOLE_SCREEN_BUFFER_INFO 结构体包含控制台屏幕缓冲区的信息,数据结构如下: typedef struct _CONSOLE_SCREEN_BUFFER_INFO { COORD dwSize; COORD dwCursorPosition; WORD wAttributes; SMALL_RECT srWindow; COORD dwMaximumWindowSize; } CONSOLE_SCREEN_BUFFER_INFO; 成员: dwSize COORD 类型的结构体,包含了控制台屏幕缓冲区的尺寸,行和列以字符为单位计数 dwCursorPosition 30 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) COORD 类型的结构体,包含了光标在屏幕缓冲区中的列和行坐标。 wAttributes 使用 WriteFile 和 WriteConsole 函数写到屏幕缓冲区,或者是使用 ReadFile 和 ReadConsole 函数回 显到屏幕缓冲区的字符属性,更多信息参见字符属性数据结构介绍。 srWindow SMALL_RECT 类型的结构体,包含了所显示控制台窗口左上角到右下角的屏幕缓冲区坐标。 dwMaximumWindowSize COORD 类型的结构体,包含了控制台窗口的最大尺寸值,行和列以字符为单位计数。提供了屏幕缓冲区大小、 字体以及屏幕大小(注:指的是控制台窗口屏幕,不是桌面)。  COORD 结构体定义了控制台屏幕缓冲区中的字符坐标单元。坐标系统的原点(0,0)位于缓冲区的左上角。 typedef struct _COORD { SHORT X; SHORT Y; } COORD, *PCOORD; 成员: X 水平坐标或列值,单位依赖于调用的函数(注:这里的含义是说结构体中的数值为 short 短整型,没有单位,所表 示的具体单位含义由调用它的函数自己确定)。 Y 垂直坐标或行值,单位依赖于调用的函数(注:同上)  WORD 为一个字的长度,实际的数据类型为 unsigned short  SMALL_RECT 结构体定义了由左上角到右下角区域的坐标。 typedef struct _SMALL_RECT { SHORT Left; SHORT Top; SHORT Right; SHORT Bottom; } SMALL_RECT; 成员: Left 矩形区域左上角 X 坐标。 Top 矩形区域左上角 Y 坐标。 Right 矩形区域右下角 X 坐标。 Bottom 31 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 矩形区域右下角 Y 坐标。 备注: 这个结构体用于控制台函数在使用字符单位表示的行和列的屏幕缓冲区中指定屏幕缓冲区矩形区域。 顺便说一下,控制台的程序常常需要绘制表格或线框,这是怎么实现的呢?其实是用一系列的字符显 示完成的,以下给出了所有用于绘制线框的字符,其中可以绘制又线框的表格,也可以绘制单线框的表格, 甚至于双线框和单线框同时都有的情况,合理的组合它们来绘制线框能让你的程序更漂亮: ─│┌┐└┘├┤┬┴┼═║╒╓╔╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬ 需要说明的是,上述程序在不同的字符代码页面(code page)下显示的结果是不同的。例如,中文 Windows 操作系统的默认代码页是简体中文(936),在该代码页面下值超过 128 的单字符在 Windows NT/XP 是显示不出来的。下表列出了可以使用的代码页。如果这个程序在非简体中文的系统中执行,那么用于绘 制边框的中文字符可能显示不正常,因为它会按操作系统的默认设置语言来显示,出现这样的情况,可以 使用 SetConsoleCP 函数来设置代码页(即用于显示双字节字符的语言),可取的值如下(如果用户使用 的不是双字节字符集,而是大字符集,那么不用关心代码页,因为大字符情况下,不会出现显示为乱码不 正常的情况) 代码页(Code page) 说明 1258 越南文 1257 波罗的海文 1256 阿拉伯文 1255 希伯来文 1254 土耳其语 1253 希腊文 1252 拉丁文(ANSI) 1251 斯拉夫文 1250 中欧文 950 繁体中文 949 韩文 936 简体中文 932 日文 874 泰文 850 使用多种语言(MS-DOS 拉丁文) 437 MS-DOS 美语/英语 在代码页设置之前,可以使用 API 判断一下某个代码页是否有效,函数为:IsValidCodePage() 最后作一点扩充,上面的例子演示了怎样在控制台显示一个模拟的窗口,这样显示出来的窗口风格与 程序界面非常协调,移植性好,开篇的时候教程就说过控制台中可以调用 Windows 的 API 函数,那么如果 只是提示用户消息的话,可以使用 Windows 系统中的 MessageBox 函数来弹出一个消息框(这个函数是系统 API,如果控制台添加预编译支持,还可以使用 afxMessageBox 函数,这个函数所需的参数更少),当然也可 以弹出其它的应用程序窗口。关于这两个函数的详细用法,请参照 MSDN,此处不讲解,但要注意这两个函 数会阻塞程序的执行,用户必须关闭消息框后控制台程序才会继续执行,示例如下: #include #include // 支持通用字符集的一些操作头文件,使用双字节或大字符集时可以不包含它 32 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) void _tmain() { MessageBox(NULL, _T("还可以更改提示图标和按钮哦~~~"), _T("这是标题~~~"), MB_OK | MB_ICONEXCLAMATION); _tsystem(_T("pause")); } 运行结果如下: 7) 滚动和移动 函数 ScrollConsoleScreenBuffer 是实现文本区滚动和移动的 API 函数。它可以将指定的一块文本区域移 动到另一个区域,被移空的那块区域由指定字符填充。函数的原型如下: BOOL ScrollConsoleScreenBuffer( HANDLE hConsoleOutput, // 句柄 CONST SMALL_RECT* lpScrollRectangle, // 要滚动或移动的区域 CONST SMALL_RECT* lpClipRectangle, // 裁剪区域 COORD dwDestinationOrigin, // 新的位置 CONST CHAR_INFO* lpFill // 填充字符 ); 利用这个 API 函数还可以实现删除指定行的操作。下面来举一个例子,程序如下(程序在 VS 2005 中多 字节字符集和大字符集两种情况下皆编译运行通过,代码为通用字符集版本,工程为 C++工程): #include #include #include #include HANDLE hOut; void DeleteLine(int row); // 删除一行 void MoveText(int x, int y, SMALL_RECT rc); // 移动文本块区域 void ClearScreen(void); // 清屏 void _tmain() 33 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) { hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄 WORD att = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_BLUE ; // 背景是蓝色,文本颜色是黄色(代码中黄色是使用红色和绿色混合得到的) SetConsoleTextAttribute(hOut, att); ClearScreen(); _tprintf(_T("\n\nThe soul selects her own society,\n")); _tprintf(_T("Then shuts the door;\n")); _tprintf(_T("On her devine majority;\n")); _tprintf(_T("Obtrude no more.\n\n")); CONSOLE_SCREEN_BUFFER_INFO bInfo; GetConsoleScreenBufferInfo( hOut, &bInfo ); COORD endPos = {0, bInfo.srWindow.Bottom - bInfo.srWindow.Top - 1}; SetConsoleCursorPosition(hOut, endPos); // 设置光标位置 SMALL_RECT rc = {0, 2, 40, 5}; getch(); MoveText(10, 5, rc); getch(); DeleteLine(5); CloseHandle(hOut); // 关闭标准输出设备句柄 } void DeleteLine(int row) { SMALL_RECT rcScroll, rcClip; COORD crDest = {0, row - 1}; CHAR_INFO chFill; CONSOLE_SCREEN_BUFFER_INFO bInfo; GetConsoleScreenBufferInfo( hOut, &bInfo ); rcScroll.Left = 0; rcScroll.Top = row; rcScroll.Right = bInfo.srWindow.Right - bInfo.srWindow.Left; rcScroll.Bottom = bInfo.srWindow.Bottom - bInfo.srWindow.Top; rcClip = rcScroll; chFill.Attributes = bInfo.wAttributes; // 使用条件编译保证无论使用哪种字符集皆能正常运行 // 如果不使用通用字符集,则只需要设置对应的字符编码即可,不用条件编译 // chFill.Char为共用体,不能两个都设置为_T(' ') #ifdef UNICODE chFill.Char.UnicodeChar = _T(' '); #else chFill.Char.AsciiChar = _T(' '); #endif ScrollConsoleScreenBuffer(hOut, &rcScroll, &rcClip, crDest, &chFill); } void MoveText(int x, int y, SMALL_RECT rc) { COORD crDest = {x, y}; CHAR_INFO chFill; CONSOLE_SCREEN_BUFFER_INFO bInfo; GetConsoleScreenBufferInfo( hOut, &bInfo ); chFill.Attributes = bInfo.wAttributes; // 使用条件编译保证无论使用哪种字符集皆能正常运行 // 如果不使用通用字符集,则只需要设置对应的字符编码即可,不用条件编译 // chFill.Char为共用体,不能两个都设置为_T(' ') #ifdef UNICODE chFill.Char.UnicodeChar = _T(' '); 34 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) #else chFill.Char.AsciiChar = _T(' '); #endif ScrollConsoleScreenBuffer(hOut, &rc, NULL, crDest, &chFill); } void ClearScreen(void) { CONSOLE_SCREEN_BUFFER_INFO bInfo; GetConsoleScreenBufferInfo( hOut, &bInfo ); COORD home = {0, 0}; WORD att = bInfo.wAttributes; unsigned long size = (bInfo.srWindow.Right - bInfo.srWindow.Left) * (bInfo.srWindow.Bottom - bInfo.srWindow.Top); FillConsoleOutputAttribute(hOut, att, size, home, NULL); FillConsoleOutputCharacter(hOut, _T(' '), size, home, NULL); } 程序演示了怎样移动一块窗口中的文字。运行后会显示一块文字,并暂停,如下图: 当用户按下任意键后,文字被移动到其它位置。如下图: 程序中,实现删除行的操作 DeleteLine 的基本原理是:首先将裁剪区域和移动区域都设置成指定行 row(包括该行)以下的控制台窗口区域,然后将移动的位置指定为(0, row-1)。这样,超出裁剪区域的内容被 裁剪掉,从而达到删除行的目的。 需要说明的是,若裁剪区域参数为 NULL,则裁剪区域为整个控制台窗口。 8) 光标操作 控制台窗口中的光标反映了文本插入的当前位置,通过 SetConsoleCursorPosition 函数可以改变这个“当 35 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 前”位置,这样就能控制字符(串)输出。事实上,光标本身的大小和显示或隐藏也可以通过相应的 API 函数进 行设定。例如: BOOL SetConsoleCursorInfo( // 设置光标信息 HANDLE hConsoleOutput, // 句柄 CONST CONSOLE_CURSOR_INFO *lpConsoleCursorInfo // 光标信息 ); BOOL GetConsoleCursorInfo( // 获取光标信息 HANDLE hConsoleOutput, // 句柄 PCONSOLE_CURSOR_INFO lpConsoleCursorInfo // 返回光标信息 ); 这两个函数都与 CONSOLE_CURSOR_INFO 结构体类型有关,其定义如下: typedef struct _CONSOLE_CURSOR_INFO { DWORD dwSize; // 光标百分比大小 BOOL bVisible; // 是否可见 } CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO; 需要说明的是,dwSize 值反映了光标的大小,它的值范围为 1-100;当为 1 时,光标最小,仅是一条最 靠下的水平细线,当为 100,光标最大,为一个字符大小的方块。 9) 读取键盘信息 键盘事件通常有字符事件和按键事件,这些事件所附带的信息构成了键盘信息。它是通过 API 函数 ReadConsoleInput 来获取的,其原型如下: BOOL ReadConsoleInput( HANDLE hConsoleInput, // 输入设备句柄 PINPUT_RECORD lpBuffer, // 返回数据记录 DWORD nLength, // 要读取的记录数 LPDWORD lpNumberOfEventsRead // 返回已读取的记录数 ); 其中,INPUT_RECORD 定义如下: typedef struct _INPUT_RECORD { WORD EventType; // 事件类型 union { KEY_EVENT_RECORD KeyEvent; MOUSE_EVENT_RECORD MouseEvent; WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; MENU_EVENT_RECORD MenuEvent; FOCUS_EVENT_RECORD FocusEvent; } Event; } INPUT_RECORD; 与键盘事件相关的记录结构 KEY_EVENT_RECORD 定义如下: typedef struct _KEY_EVENT_RECORD { BOOL bKeyDown; // TRUE表示键按下,FALSE表示键释放 WORD wRepeatCount; // 按键次数 WORD wVirtualKeyCode; // 虚拟键代码 WORD wVirtualScanCode; // 虚拟键扫描码 union { WCHAR UnicodeChar; // 宽字符 36 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) CHAR AsciiChar; // ASCII字符 } uChar; // 字符 DWORD dwControlKeyState; // 控制键状态 } KEY_EVENT_RECORD; 我们知道,键盘上每一个有意义的键都对应着一个唯一的扫描码,虽然扫描码可以作为键的标识,但 它依赖于具体设备的。因此,在应用程序中,使用的往往是与具体设备无关的虚拟键代码。这种虚拟键代 码是与设备无关的键盘编码。在 Visual C++中,最常用的虚拟键代码已被定义在 Winuser.h 中【用户打开后 能看到所有的虚拟键值,实在找不到,就用 getch()获取后显示出来,但要注意比如方向键返回值大于 128 的,接收返回值时用整型变量,不要用字符型变量,否则可能导致这样的键值接收不完全。】,例如:VK_SHIFT 表示 SHIFT 键,VK_F1 表示功能键 F1 等。 上述结构定义中,dwControlKeyState 用来表示控制键状态,它可以是 CAPSLOCK_ON(CAPS LOCK 灯亮)、 ENHANCED_KEY(按下扩展键)、LEFT_ALT_PRESSED(按下左 ALT 键)、LEFT_CTRL_PRESSED(按下左 CTRL 键)、 NUMLOCK_ON (NUM LOCK 灯亮)、RIGHT_ALT_PRESSED(按下右 ALT 键)、RIGHT_CTRL_PRESSED(按下右 CTRL 键)、 SCROLLLOCK_ON(SCROLL LOCK 灯亮)和 SHIFT_PRESSED(按下 SHIFT 键)中的一个或多个值的组合。 下面的程序是将用户按键的字符输入到一个控制台窗口的某个区域中,并当按下 NUM LOCK、CAPS LOCK 和 SCROLL LOCK 键时,在控制台窗口的最后一行显示这些键的状态。 【程序在 VS 2005 中编译、调试通过,操作系统 Windows XP 下正常,Windows 7 64bit 旗舰版边框 显示有一点点间距,但效果一样,C++工程,没有使用类进行设计,所以很容易移植到 C 语言的工程,只 需要注意变量的声明位置即可,采用通用字符集】 #include #include // 提供通用字符集处理的支持 HANDLE hOut; HANDLE hIn; void DrawBox(SMALL_RECT rc); void ClearScreen(void); void CharWindow(TCHAR ch, SMALL_RECT rc); void ControlStatus(DWORD state); void DeleteTopLine(SMALL_RECT rc); // 绘制线框 // 清屏 // 将字符输出到指定的窗口中 // 在最后一行显示控制键的状态 // 删除指定区域中最上面的行并滚动 void _tmain() { hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄 hIn = GetStdHandle(STD_INPUT_HANDLE); // 获取标准输入设备句柄 // 背景是蓝色,文本颜色是黄色 WORD att = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_BLUE ; SetConsoleTextAttribute(hOut, att); ClearScreen(); // 清屏 INPUT_RECORD keyRec; DWORD state = 0, res = 0; TCHAR ch = NULL; SMALL_RECT rc = {20, 2, 40, 12}; COORD pos = {rc.Left + 2, rc.Top + 1}; // 边框线的宽为两个字符,高为一个字符 DrawBox(rc); SetConsoleCursorPosition(hOut, pos); // 设置光标位置 // 循环不会消耗大量的CPU资源,因为没有输入的情况下,ReadConsoleInput不会 // 返回,循环也不会进行。 37 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) while(true) { ReadConsoleInput(hIn, &keyRec, 1, &res); if (keyRec.Event.KeyEvent.dwControlKeyState != state) // 状态处理 { state = keyRec.Event.KeyEvent.dwControlKeyState; ControlStatus(state); // 状态信息的获取依赖于ReadConsoleInput的返回信息 } // 键盘事件响应,代码只处理按键事件,未处理字符事件 if (KEY_EVENT == keyRec.EventType) { // 按ESC键退出循环 if (VK_ESCAPE == keyRec.Event.KeyEvent.wVirtualKeyCode) break; // 按键事件响应代码 if (keyRec.Event.KeyEvent.bKeyDown) { #ifdef UNICODE // 使用条件编译可以保证支持不同的字符集 ch = keyRec.Event.KeyEvent.uChar.UnicodeChar; #else ch = keyRec.Event.KeyEvent.uChar.AsciiChar; #endif CharWindow(ch, rc); } // 可以在此处添加字符事件响应代码 // ToDo; } } pos.X = 0; pos.Y = 0; SetConsoleCursorPosition(hOut, pos); // 设置光标位置 CloseHandle(hOut); // 关闭标准输出设备句柄 CloseHandle(hIn); // 关闭标准输入设备句柄 } void CharWindow(TCHAR ch, SMALL_RECT rc) // 将字符输入到指定的窗口中 { static COORD chPos = {rc.Left + 2, rc.Top+1}; // 边框线的宽为两个字符,高为一个字符 SetConsoleCursorPosition(hOut, chPos); // 设置光标位置 if ((ch<0x20)||(ch>0x7e)) return; // 仅仅显示标准ASCII编码中的可打印字符(32~126) WriteConsoleOutputCharacter(hOut, &ch, 1, chPos, NULL); if (chPos.X>=(rc.Right-1)) { chPos.X = rc.Left + 1; chPos.Y++; } if (chPos.Y>(rc.Bottom-1)) { DeleteTopLine(rc); chPos.Y = rc.Bottom-1; } chPos.X++; SetConsoleCursorPosition(hOut, chPos); // 设置光标位置 } void ControlStatus(DWORD state) // 在倒数第二行显示控制键的状态 38 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) { // 状态条显示 CONSOLE_SCREEN_BUFFER_INFO bInfo; GetConsoleScreenBufferInfo( hOut, &bInfo ); COORD home = {0, bInfo.srWindow.Bottom - bInfo.srWindow.Top - 1}; // 倒数第二行 WORD att0 = BACKGROUND_INTENSITY; WORD att1 = FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_RED; FillConsoleOutputAttribute(hOut, att0, bInfo.srWindow.Right - bInfo.srWindow.Left, home, NULL); FillConsoleOutputCharacter(hOut, _T(' '), bInfo.srWindow.Right - bInfo.srWindow.Left, home, NULL); SetConsoleTextAttribute(hOut, att1); // 状态信息显示 COORD staPos = {bInfo.srWindow.Right - bInfo.srWindow.Left - 16, bInfo.srWindow.Bottom - bInfo.srWindow.Top - 1}; // 倒数第二行 SetConsoleCursorPosition(hOut, staPos); if (state & NUMLOCK_ON) WriteConsole(hOut, _T("NUM"), 3, NULL, NULL); staPos.X += 4; SetConsoleCursorPosition(hOut, staPos); if (state & CAPSLOCK_ON) WriteConsole(hOut, _T("CAPS"), 4, NULL, NULL); staPos.X += 5; SetConsoleCursorPosition(hOut, staPos); if (state & SCROLLLOCK_ON) WriteConsole(hOut, _T("SCROLL"), 6, NULL, NULL); // 环境恢复 SetConsoleTextAttribute(hOut, bInfo.wAttributes); // 恢复原来的属性 SetConsoleCursorPosition(hOut, bInfo.dwCursorPosition); // 恢复原来的光标位置 } void DeleteTopLine(SMALL_RECT rc) { COORD crDest; CHAR_INFO chFill; SMALL_RECT rcClip = rc; rcClip.Left += 2; rcClip.Right -= 1; rcClip.Top++; rcClip.Bottom--; crDest.X = rcClip.Left; crDest.Y = rcClip.Top - 1; CONSOLE_SCREEN_BUFFER_INFO bInfo; GetConsoleScreenBufferInfo( hOut, &bInfo ); chFill.Attributes = bInfo.wAttributes; #ifdef UNICODE // 使用条件编译可以保证支持不同的字符集 chFill.Char.UnicodeChar = _T(' '); #else chFill.Char.AsciiChar = _T(' '); #endif ScrollConsoleScreenBuffer(hOut, &rcClip, &rcClip, crDest, &chFill); } void DrawBox(SMALL_RECT rc) { TCHAR chBox[6][3] = {_T("┌"), _T("┐"), _T("└"), _T("┘"), _T("─"), _T("│")}; COORD pos = {rc.Left, rc.Top}; // 先输出边线,再输出四个角,防止四个角被连线压住 39 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) // 输出时为两个字符,主要是为了防止出现汉字占两个字节时输出异常 // 这样如果为大字符集或多字节字符集的英文字符则输出对应字符和一个'\0' // 如果为多字节字符的中文字符则输出字符,且没有'\0' // 水平线一个字符占两个字节,所以步长为 // 输出的长度使用长度计算可以屏蔽不同字符集上长度不相同时造成的空格输出破坏界面 for (pos.X = rc.Left; pos.X < rc.Right; pos.X += 2) { pos.Y = rc.Top; WriteConsoleOutputCharacter(hOut, chBox[4], (int)_tcslen(chBox[4]), pos, NULL);// 顶 pos.Y = rc.Bottom; WriteConsoleOutputCharacter(hOut, chBox[4], (int)_tcslen(chBox[4]), pos, NULL);// 底 } for (pos.Y = rc.Top; pos.Y < rc.Bottom; ++pos.Y) { pos.X = rc.Left; WriteConsoleOutputCharacter(hOut, chBox[5], (int)_tcslen(chBox[5]), pos, NULL);// 左 pos.X = rc.Right; WriteConsoleOutputCharacter(hOut, chBox[5], (int)_tcslen(chBox[5]), pos, NULL);// 右 } pos.X = rc.Left; pos.Y = rc.Top; WriteConsoleOutputCharacter(hOut, chBox[0], (int)_tcslen(chBox[0]), pos, NULL);// 左 上角 pos.X = rc.Right; pos.Y = rc.Top; WriteConsoleOutputCharacter(hOut, chBox[1], (int)_tcslen(chBox[1]), pos, NULL);// 右 上角 pos.X = rc.Left; pos.Y = rc.Bottom; WriteConsoleOutputCharacter(hOut, chBox[2], (int)_tcslen(chBox[2]), pos, NULL);// 左 下角 pos.X = rc.Right; pos.Y = rc.Bottom; WriteConsoleOutputCharacter(hOut, chBox[3], (int)_tcslen(chBox[3]), pos, NULL);// 右 下角 } void ClearScreen(void) { _tsystem(_T("cls")); } 程序运行结果如下图所示: 40 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 这一程序的主要功能有四方面,一是严格处理键盘输入,二是严格规范字符输出到指定窗口,三是屏 幕缓冲区中内容的移动,四是键盘状态信息的检测,这几方面的技巧实用性非常高。 代码中状态信息没有放到最后一行,原因是如果放到最后一行的话,使用 Ctrl + 空格切换为中文输入法 后,最后一行要显示输入法的信息及候选文字,这样原先整个窗口的文字就会被往上移动一行,被挤上去, 对于界面要求很高的程序,这样无疑会破坏界面的美观性和可见性,所以放在倒数第二行。关于这一说法 读者可以自行测试。 严格控制文字输入输出除了使用示例中的方法,还有两个常用的方法,一是使用 API 来扫描键盘信息(这 在游戏中常用到,Windows 也专门提供这样的 API 函数来获取键盘的信息,函数为 GetKeyboardState,二是 使用 getch() 纯手工获取信息并使用输出语句输出,这两种方式都非常麻烦,但灵活性与可控性最强,这两 种方法有一点不太一样,使用 API 函数只会对键盘的状态进行检测,哪怕这个按键是其它进程的,一样会被 检测到,而且不会像键盘钩子一样被杀毒软件关注,这个函数估计能做点小玩意,但没实践过,可行性未 知„„)。 另外,ReadConsoleInput() 函数有阻塞性,即需要有按键信息才会返回,所以可以放心用在死循环中, 并不会造成循环消耗大量的 CPU,这个函数返回的信息非常多,但是也有一个不好的地方,即所有的信息 (包括键盘状态,比如大、小写状态,数字键盘锁定状态等)都需要调用这个函数后才能返回,所以上面 的这处示例就有一个缺点:刚开始运行的时候,没有任何的按键信息时,无法显示当前的键盘状态,至少 要按了一次按键后才会有显示,如果用户有很高的要求,一定要在没有任何按键之前显示当前的键盘状态, 可以使用 API 来发送一个键盘的按键消息(注意要不会影响程序的运行,也不要发送程序中已经使用的按键 消息)。 由于 ReadConsoleInput() 函数有阻塞性的特点,所以在游戏中一般不用它,其实是在需要在处理其它任 务的时候检测键盘都不会用它,因为用它之后,其它的任务就无法继续了,所以使用 API 是个好办法,以前 TC 中有一个函数 kbhit()可用于检测键盘状态,不会阻塞程序,头文件为 conio.h,由于它是标准 C 和 C++中 的函数,所以在 VC++和 VS 中也有这个函数,使用它与使用 API 效果一样,但要注意有些键是双字节的,不 能用 char 型变量接收返回值,以免数据损失,其实在 C、C++标准中,只有_getch 和_kbhit,而 getch()和 kbhit 是微软公司为方便调用自己重命名的,如果考虑移植性的话,请使用_getch 代替 getch,并使用_kbhit 代替 kbhit,这里简要摘抄一下 MSDN 中 kbhit 的讲解: 41 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) int _kbhit( void ); Checks the console for keyboard input. Return Value _kbhit returns a nonzero value if a key has been pressed. Otherwise, it returns 0. Remarks The _kbhit function checks the console for a recent keystroke. If the function returns a nonzero value, a keystroke is waiting in the buffer. The program can then call _getch or _getche to get the keystroke. Requirements Routine Required header Compatibility _kbhit Windows 95, Windows 98, Windows 98 Second Edition, Windows Millennium Edition, Windows Millennium Edition, Windows NT 4.0, Windows 2000, Windows XP Home Edition, Windows XP Professional, Windows Server 2003 循环中如果需要让程序的执行延时一下,请不要使用空循环来完成,因为这样会消耗大量的 CPU 资源, 应该使用 sleep 函数,其中 C、C++库中有这个函数,Windows 的 API 中也有同名的函数,但名字的大小写和 形式稍有不同,它们都可以让程序休眠一段时间,这样的延时发生时 CPU 不会对它执行代码,几乎不消耗 CPU 资源。 【注:附录中的俄罗斯方块代码便使用了 kbhit 和 Sleep 函数,读者可作参考。】 最后,关于清屏函数,这里使用的是调用系统命令 cls 来实现,但这依赖于系统提供的命令,一般为省 事才这样,更合理的做法是使用控制台函数,这样不依赖于外部的程序(cls 由系统的控制台程序 cls.exe 执 行实行的)就能清除缓冲区内容,并按背景色和前景色来设置属性,适当的时候还要加入恢复窗口大小、 调整缓冲区大小、重新初始化界面等工作。典型的清屏代码如下(改编自 MSDN,且经过测试,读者可以用 它来替换上面示例中的 ClearScreen 函数,即可直接编译运行。): void ClearScreen(void) { COORD coordScreen={0,0};//设置清屏后光标返回的屏幕左上角坐标 BOOL bSuccess; DWORD cCharsWritten; CONSOLE_SCREEN_BUFFER_INFO csbi;//保存缓冲区信息 DWORD dwConSize;//当前缓冲区可容纳的字符数 GetConsoleScreenBufferInfo(hOut, &csbi);//获得缓冲区信息 dwConSize = csbi.dwSize.X*csbi.dwSize.Y;//缓冲区容纳字符数目 FillConsoleOutputCharacter(hOut, _T(' '), dwConSize, coordScreen, &cCharsWritten); //用空格填充缓冲区 GetConsoleScreenBufferInfo(hOut, &csbi);//获得缓冲区信息 FillConsoleOutputAttribute(hOut, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten); //填充缓冲区属性 SetConsoleCursorPosition(hOut, coordScreen);//光标返回屏幕左上角坐标 } 当然,很多时候,也简写为以下代码(在示例工程中测试通过): void ClearScreen(void) { CONSOLE_SCREEN_BUFFER_INFO bInfo; GetConsoleScreenBufferInfo( hOut, &bInfo ); COORD home = {0, 0}; unsigned long size = bInfo.dwSize.X * bInfo.dwSize.Y; // 计算缓冲区大小 FillConsoleOutputAttribute(hOut, bInfo.wAttributes, size, home, NULL); 42 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) FillConsoleOutputCharacter(hOut, _T(' '), size, home, NULL); } 10) 读取鼠标信息 与读取键盘信息方法相似,鼠标信息也是通过 ReadConsoleInput 来获取的,其 MOUSE_EVENT_RECORD 具有下列定义: typedef struct _MOUSE_EVENT_RECORD { COORD dwMousePosition; // 当前鼠标位置 DWORD dwButtonState; // 鼠标按钮状态 DWORD dwControlKeyState; // 键盘控制键状态 DWORD dwEventFlags; // 事件状态 } MOUSE_EVENT_RECORD; 其中,dwButtonState 反映了用户按下鼠标按钮的情况,它可以是:FROM_LEFT_1ST_BUTTON_PRESSED(最 左边按钮)、RIGHTMOST_BUTTON_PRESSED(最右边按钮)、FROM_LEFT_2ND_BUTTON_PRESSED(左起第二个按 钮)、FROM_LEFT_3RD_BUTTON_PRESSED(左起第三个按钮)和 FROM_LEFT_4TH_BUTTON_PRESSED (左起第四个 按 钮 ) 。 而 dwEventFlags 表 示 鼠 标 的 事 件 , 如 DOUBLE_CLICK( 双 击 ) 、 MOUSE_MOVED( 移 动 ) 和 MOUSE_WHEELED(滚轮滚动,只适用于 Windows 2000/XP)。dwControlKeyState 的含义同前。 下面举一个例子。这个例子能把鼠标的当前位置显示在控制台窗口的最后一行上,若单击鼠标左键, 则在当前位置处写一个字符‘A’,若双击鼠标任一按钮,则程序终止。具体代码如下: 【代码在 VS 2005 中编译运行通过,字符集为通用字符集,操作系统为 Windows XP,Windows 7 64bit 旗舰版系统也测试通过,工程为 C++工程】 #include #include #include #include HANDLE hOut; HANDLE hIn; void ClearScreen(void); void DispMousePos(COORD pos); // 在最后一行显示鼠标位置 void _tmain() { hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄 hIn = GetStdHandle(STD_INPUT_HANDLE); // 获取标准输入设备句柄 WORD att = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_BLUE ; // 背景是蓝色,文本颜色是黄色 SetConsoleTextAttribute(hOut, att); ClearScreen(); // 清屏 INPUT_RECORD mouseRec; DWORD state = 0, res; COORD pos = {0, 0}; for(;;) // 无限循环,也可用while(true)(双击鼠标退出循环) { ReadConsoleInput(hIn, &mouseRec, 1, &res); 43 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) if (mouseRec.EventType == MOUSE_EVENT) { if (DOUBLE_CLICK == mouseRec.Event.MouseEvent.dwEventFlags) break; // 双击鼠标退出循环 pos = mouseRec.Event.MouseEvent.dwMousePosition; DispMousePos(pos); if (FROM_LEFT_1ST_BUTTON_PRESSED == mouseRec.Event.MouseEvent.dwButtonState) FillConsoleOutputCharacter(hOut, _T('#'), 1, pos, NULL); } } pos.X = 0; pos.Y = 0; SetConsoleCursorPosition(hOut, pos); // 设置光标位置 CloseHandle(hOut); // 关闭标准输出设备句柄 CloseHandle(hIn); // 关闭标准输入设备句柄 } void DispMousePos(COORD pos) // 在最后一行显示鼠标位置 { // 说明:不要把光标位置显示在最后一行,以防输入法切换为中文时界面时被破坏 // 鼠标每次移动都会要显示当前位置,这样就可能会造成屏幕闪烁不停。 CONSOLE_SCREEN_BUFFER_INFO bInfo; GetConsoleScreenBufferInfo( hOut, &bInfo ); COORD home = {0, bInfo.srWindow.Bottom - bInfo.srWindow.Top - 1}; // 倒数第 二行 WORD att0 = BACKGROUND_INTENSITY ; FillConsoleOutputAttribute(hOut, att0, bInfo.srWindow.Right - bInfo.srWindow.Left, home, NULL); FillConsoleOutputCharacter(hOut, _T(' '), bInfo.srWindow.Right - bInfo.srWindow.Left, home, NULL); TCHAR s[20] = _T(""); _stprintf(s,_T("X = %2lu, Y = %2lu"),pos.X, pos.Y); // _stprintf为sprintf 的通用字符集版本 SetConsoleTextAttribute(hOut, att0); SetConsoleCursorPosition(hOut, home); WriteConsole(hOut, s, _tcslen(s), NULL, NULL); // _tcslen为strlen的通用 字符集版本 SetConsoleTextAttribute(hOut, bInfo.wAttributes); // 恢复原来的属性 SetConsoleCursorPosition(hOut, bInfo.dwCursorPosition); // 恢复原来的光标位置 } void ClearScreen(void) { CONSOLE_SCREEN_BUFFER_INFO bInfo; GetConsoleScreenBufferInfo( hOut, &bInfo ); COORD home = {0, 0}; unsigned long size = bInfo.dwSize.X * bInfo.dwSize.Y; // 缓冲区大小 FillConsoleOutputAttribute(hOut, bInfo.wAttributes, size, home, NULL); FillConsoleOutputCharacter(hOut, _T(' '), size, home, NULL); } 说明:不要把光标位置显示在最后一行,以防输入法切换为中文时界面时被破坏,鼠标每次移动都会 要显示当前位置,这样就可能会造成屏幕闪烁不停。 如果要退出程序,可以点击关闭,或者双击任一鼠标键。 另外,如果读者对这个小程序感兴趣,可以自己为它做一个程序图标,并使用相应的控制台函数来改 变窗口大小,这样增大绘图的区域会更好,甚至读者可以提供切换颜色的功能,能绘制不同颜色的线条等。 顺便说一下,利用鼠标、键盘、以及前面所学的各种操作,读者可以试试怎样在控制台窗口中实现菜 44 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 单的功能,让菜单能响应鼠标和键盘,这可能很有意思哦~~~读者可以自己动手试试„„ 程序运行结果如下: 11) 结束语 综上所述,利用控制台窗口的 Widows API 函数可以设计简洁美观的文本界面,使得用 Visual C++开 发环境深入学习 C++以及文本界面设计成为一件比较容易的事件。当然文本界面的设计还需要一定的方法 和技巧,限于篇幅,这里不再阐述。 第二部分的文档结构和示例来自网络,作者无从考证,网络上充斥了大量的复制品,代码错误百出, 不知是原文如此,还是在网络中传播中造成的,本人收集之后,对所有代码进行了梳理,并在原来示例的 基础上全部使用通用字符集的工程,做到与时俱进,同时也对代码进行了大量的更改,并充分了考虑了兼 容性,并在其中加入了许多个人的经验。教程中所有代码皆能编译通过,各位读者多多指教。 【提醒】教程中的示例,如果读者创建的控制台工程类型不对,无法通过编译时,需要读者手动添加相应的头文件包含 命令。在教程中没有使用到 MFC,最多只需在 CPP 文件的第一行包含预编译的头文件:#include "stdafx.h" 即可。 wyz5@163.com 2011.02.26 45 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 第三部分 附录 附录中提供源代码是为了确保文档的完整性,让读者能看到想要的东西,并自行新建工程运行代码。 1) 分数等级划分工具 功能:输入一系列的成绩,每个成绩只允许在 0-100 之间,当人数达到 63 个或者按 S 键则进行统计显 示,90-100 分标记为 A,80-89 分标记为 B,70-79 分标记为 C,60-69 分标记为 D,0-59 分标记为 E,详细的 说明文字在程序运行界面中会有显示。程序是我早年学习 C 语言时编写,因当时经验、技术等限制,语法、 格式、结构非常糟糕,开发时用的是 VC++ 6.0,引入教程中时顺便移植到 VS 2005 中进行编译测试,代码仅 供参考,读者可自行更改为 C++的输出方式。本打算为这本教程亲自写一个精美的控制台贪食蛇示例游戏, 苦于没有时间,看来只能留给读者了~~~ 工程为生成的程序添加了图标,更改了标题栏文字,共两个文件,一个为 controlio.h,另一个为 Main.c, VC++6.0 SP6 编写,工程语言 C,测试系统 Windows XP SP3 专业版 和 Windows 7 64bit 旗舰版,字符集为多 字节字符集。 a) controlio.h 文件 #ifndef CONTROLIO_H #define CONTROLIO_H 46 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) #include #include #include //目前只支持红、蓝、白、绿 enum Color { FH = 0, //黑色 FB = FOREGROUND_BLUE, FG = FOREGROUND_GREEN, FR = FOREGROUND_RED, FI = FOREGROUND_INTENSITY, //白色 BH = 0, //黑色 BB = BACKGROUND_BLUE, BG = BACKGROUND_GREEN, BR = BACKGROUND_RED, BI = BACKGROUND_INTENSITY }; //设置颜色,第一个参数是字体颜色,第二个参数是背景颜色,颜色的取值见上面枚举常量 void SetColor(int FC,int BC) { HANDLE handle = GetStdHandle( STD_OUTPUT_HANDLE ); SetConsoleTextAttribute(handle, (WORD)(FC | BC)); } //清除用户设置的颜色,恢复到没有设置之前 void ClearColor(void) { SetColor(0,7); } //让当前输出位置直接跳到第row行,第col列处,此函数没有写过多的处理非法数据的语句,目的 //是提高程序速度,减少程序量,使用时不要让参数超出范围 void SetPos(int row, int col) { COORD coord = {col, row}; SetConsoleCursorPosition( GetStdHandle(STD_OUTPUT_HANDLE), coord ); } //tag=1有下划线,无下划线 //return -1表示取消输入 //return -2,-3表示参数不正确 //return -5表示进行统计 //return -4表示运行出错 //n表示待输入数据的最大位数 int GetInteger(int n,int tag) { int kn=0,s=0,i; char c; if(n<=0 || n>=10) { printf("\n无法输入规定的数据!\n"); return -2; } if(!(tag==1 || tag==0)) { printf("\n函数参数不合法!\n"); 47 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) return -3; } if(tag==1) { for(i=0;i=0 && s<=100)return s; } if(c==27)return -1; if(c=='s' || c=='S') return -5; if(c==8 && kn>0) { if(tag==0) printf("\b \b"); else printf("\b_\b"); if(kn==0)kn=0; else { kn--; s=s/10; } } if(c>='0'&& c<='9' && kn>>>>>>>>>>>>>>>>>>>>>>>> 分数等级转换小工具V1.0 <<<<<<<<<<<<<<<<<<<<<<<<<"); SetColor(0,3); SetPos(16,2); printf(" 本工具能对学生成绩进行等级划分,使用的过程中请注意以下几点:\n"); SetPos(17,2); printf(" 1.学生的分数只能是~100之间(包含和)的整数,且学生的人数最多个。\n"); SetPos(18,2); printf(" 2.每个学生的成绩输入完成后按[Enter]键确认该人成绩,否则该人成绩不进行\n"); SetPos(19,2); printf("统计,任何时候按[ESC]键清空所有数据,重新输入数据,按[S]键进行结果统计。\n"); SetPos(20,2); printf(" 3.本工具有待完善,暂时还不支持鼠标操作,不支持已确认数据的修改。\n"); SetColor(32,11);SetPos(14,2);printf("A:"); SetColor(160,14);SetPos(14,14);printf("B:"); SetColor(144,13);SetPos(14,26);printf("C:"); SetColor(224,12);SetPos(14,38);printf("D:"); SetColor(64,12);SetPos(14,50);printf("E:"); SetColor(0,13);SetPos(14,62);printf("总人数:"); } #endif b) Main.c 文件 49 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) #include #include "controlio.h" void main() { int i,tmp=0; int sc[63]={0},n=0; int re[5]={0}; while(1) { for(i=0;i<63;i++) sc[i]=0; n=0; tmp=0; for(i=0;i<5;i++) re[i]=0; ClearColor(); SetPos(0,0); InitPro(); for(i=0;tmp>=0 && i<63;i++) { if (i%7==0) SetPos(3+i/7,4); SetColor(0,8); if(tmp>=0 && tmp<=100) printf("<%2d>:",i+1); SetColor(0,15); tmp=GetInteger(3,1); if(tmp>=0 && tmp<=100) sc[n++]=tmp; if(tmp>=0 && tmp<=9) printf(" "); else if(tmp>=10 && tmp<=99) printf(" "); else if(tmp==100)printf(" "); } if(tmp==-1) continue; if(!(tmp==-5 || i==63)) exit(1); for(i=0;i:",i+1); SetColor(0,15); printf("%-3d",sc[i]); switch(sc[i]/10) { case 0: case 1: case 2: case 3: case 4: case 5: SetColor(64,12);printf("E");re[4]++;break; case 6: SetColor(224,12);printf("D");re[3]++;break; case 7: SetColor(144,13);printf("C");re[2]++;break; case 8: SetColor(160,14);printf("B");re[1]++;break; case 9: case 10: SetColor(32,11);printf("A");re[0]++;break; } ClearColor(); printf(" "); } 50 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) DisResult(re[0],re[1],re[2],re[3],re[4],n); ClearColor(); getch(); } } 2) 简易俄罗斯方块 功能:简易的 C 语言俄罗斯方块程序,使用 W、A、D 来控制方块的移动方向,S 快速下降,E 退出程 序,空格暂停,暂停后按任意方向键可继续。 游戏足够的简单,非常适合学习和理解它的逻辑,算法精简、小巧,结构设计合理,没有任何多余的 功能,很容易就能看懂,是初学者学习俄罗斯方块算法的优秀例子,不过程序也有以下缺点:  代码风格极不合理,命名规范很糟糕。  扩展性太差,代码可读性也不高。  大量使用魔数,想要改变桶的宽和高非常麻烦。  方块随机产生的出现位置是给定值,不会根据桶宽来计算中间位置。  程序中没有隐藏光标,应该把它隐藏(此处未做修改)。 本程序是从刘红石先生的三维画网站下载而来(大概是 2008 年下载的,网址:http://www.liuhs.com 进入“源程序下载”专区即可),是一个比较适合初学者参详的控制台程序,所以收录到教程中,工程原来 采用 Turbo C 开发,由于使用了一些 TC 中特有的函数,在移植到 VC++中时,无法编译通过,本着尽量不改 动原来代码的原则,移植了一些调用到的函数,并进行了一些细微的修补,列举如下(需要原版代码的读 者可到刘红石先生的网站上下载):  为程序添加了两个宏,用于设置桶的宽度和高度,并修改代码使之能正常工作。再就是修改了方 块随机产生时出现的位置,让其能适应桶的宽度,始终出现在正中间。  修改了一些相应的地方,让最后一行不进行任何显示,桶也收缩到倒数第二行,因为在实际运行 中在最后一行输出,会严重干扰程序界面。 留给读者的话:这个算法很精巧,代码短小,构思独到,功能简单,所以适合初学者学习,如果读者 有兴趣,在理解透这个程序后,可以使用 C 或 C++来改写代码,让它支持计分和关卡,让界面更漂亮,比如 不同形状的方块有不同的颜色,提供预览下一个方块等,进一步优化代码,使用更好的代码风格。由于代 码是别人书写,出于尊重别人劳动成果和版权,本人只进行移植性方面的修正,其它地方尽量保持原样。 程序在 Windows XP 和 Windows 7 64bit 系统上调试运行通过,VC++ 6.0 和 VS 2005 皆运行通过,这是一 个原汁原味的 C 语言程序,只有唯一的一个源代码文件,读者在 VC++ 6.0 或 VS 2005 中任意创建一个空的 C 工程并添加代码(C++工程也可以),即可编译运行。VS 2005 中请使用多字节字符集模式,否则编译错误。 51 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) a) 代码 Main.c 文件 /**************************************************/ /* 俄罗斯方块游戏 */ /**************************************************/ # include # include # include # include # include # include #define JIANGETIME 300 /* 间隔时间 */ #define HANG 23 /* 桶内的宽度,不含桶壁 */ #define LIE 25 /* 桶内的高度,不含桶壁 */ /* 七种拼块的形状数据*/ char fk[7][4][2]={ 1,1, 1,2, 1,3, 1,4, 1,1, 1,2, 1,3, 2,3, 2,1, 2,2, 1,3, 2,3, 1,1, 1,2, 2,2, 2,3, 2,1, 2,2, 1,3, 2,3, 1,1, 2,1, 1,2, 2,2, 1,1, 1,2, 1,3, 2,2}; /* 存放组成拼块的四个小方块X和Y坐标*/ int kx[4],ky[4]; /* 桶*/ int tong[LIE + 1][HANG]; void gotoxy(int x,int y) { 52 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) COORD coord = {x, y}; if(x<0) x=0;else if(x>80) x=80; if(y<0) y=0;else if(y>HANG) y=HANG; SetConsoleCursorPosition( GetStdHandle(STD_OUTPUT_HANDLE), coord ); } /* 等待按键并延时的函数*/ char key(int s) { clock_t t1,t2; char c; t1=clock(); do t2=clock(); while (((t2-t1)LIE)||(ky1[i]<1)||(ky1[i]>HANG)) {t=0; break;}; if (tong[kx1[i]][ky1[i]]==1) {t=0; break;}; } if(t==1) for(i=0;i<4;i++){ kx[i]=kx1[i]; ky[i]=ky1[i]; }; hua(); } /* 计算一行中的方块数*/ int fangkuaishu(int h) { 54 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) int i,p=0; for(i=1;i<=LIE;i++) p=p+tong[i][h]; if(p==LIE) Sleep(JIANGETIME); /* 这个函数首字母大写,是Windows中的API函数,不是C中的 */ return p; } /* 桶中方块除去一行,在此行上面的方块下移一行*/ void yihang(int h) { int k,j,q; for(k=h;k>0;k--){ q=0; for(j=1;j<=LIE;j++){ if(tong[j][k]!=tong[j][k-1]){ kuai(j,k,tong[j][k-1]); tong[j][k]=tong[j][k-1]; } q=q+tong[j][k]; } if(q==0) break; } } /* 检查有无完成的行,并处理之*/ void jiancha() { int i,n; i=HANG; do{ n=fangkuaishu(i); if(n==LIE) yihang(i); else i--; } while((i>0)&&(n>0)); } /* 开始函数,初始化,画出空桶*/ void kaishi() { int i,j; for(i=1;i<=HANG;i++) for(j=1;j<=LIE;j++) tong[j][i]=0; system("cls"); for(i=0;i='a')&&(kk<='z')) kk=kk-32; /* 将小写字母转换为大写*/ /* 根据按键作相应处理*/ switch (kk) { case 'S': s=1; break; case 'W': zhuan(); break; case 'A': yidong(-1); break; case 'D': yidong(1); break; case ' ': while(kbhit()==0);break; case 'E': exit(0); break; /* 按E,结束游戏*/ default : t=xialuo(); /* 没按上面的键,或未按任何键,拼块下落一格*/ } if(t==0) break; /* 拼块落到桶底或不能再下落退出循环*/ } jiancha(); /* 检查是否有完成的行,并处理之*/ } return 0; } 3) 模拟实现可用鼠标、键盘控制的菜单和窗口 (因个人近期时间紧,代码未完成,没附到文档中,留给读者自己实现,以后有时间可能会加入到教 程中。最经典的示例是古老的 Turbo C++ 3.0 开发工具,它的界面实现了控制台下的菜单、弹出窗口、以及 可任意调整的平铺窗口、键盘及鼠标响应功能及以 Turbo C 2.0。附几张网络收集到的图片,以供参考。这两 个工具在 32 位的 Windows 2000 及 XP 上能正常运行,更高版本的操作系统未测试过,但 Win 7 64bit 无法运 行,以兼容模式也无法启动,可能 32 位的 Win 7 能以兼容模式运行。) 56 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 57 / 58 C/C++控制台界面编程(V 2) (wyz5@163.com 整理) 58 / 58

Top_arrow
回到顶部
EEWORLD下载中心所有资源均来自网友分享,如有侵权,请发送举报邮件到客服邮箱bbs_service@eeworld.com.cn 或通过站内短信息或QQ:273568022联系管理员 高进,我们会尽快处理。