C++控制台编程
我们的控制台文本窗口是基于win32 api实现的
我们以例子来学习这种编程的方法:
控制台程序的外观和操作就像MS-DOS窗口一样,控制台有一个输入缓冲区以及一个或多个屏幕缓冲区:
- 输入缓存区(input buffer)包含一组输入记录(input records),其中每个记录都是一个输入事件的数据。输入事件的例子包括键盘输入,鼠标点击,以及用户调整控制台窗口大小
- 屏幕缓冲区(screen buffer)是字符与颜色数据的二维数组,他会影响控制台窗口文本的外观
例1:
1 2 3 4 5 6 7 8 9
| #include <stdio.h> #include <windows.h> int main() { printf("Hello World!\n"); Sleep(1000); system("cls"); return 0; }
|
我们发现这个程序和我们的一般的hello world程序不同,它实现的功能是在1000ms后清空控制台的内容,使用了sleep和system命令。
例2:
1 2 3 4 5 6 7 8
| #include <windows.h> #include <stdio.h>
int main(void){ SetConsoleTitle(L"hello world!"); printf("hello world!"); return 0; }
|
我们的使用了windows.h里的函数,我们想要编写控制台应用肯定需要用到这个库
SetConsoleTitle
用于设置窗口标题,这里L代表宽字符,用L标识的是宽字符,标准的字符是一个字符一个字节,宽的是一个字符两个字节。
用于控制台窗口操作的API函数如下:
- GetConsoleScreenBufferInfo 获取控制台窗口信息
- GetConsoleTitle 获取控制台窗口标题
- ScrollConsoleScreenBuffer 在缓冲区中移动数据块
- SetConsoleScreenBufferSize 更改指定缓冲区大小
- SetConsoleTitle 设置控制台窗口标题
- SetConsoleWindowInfo 设置控制台窗口信息
例3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <iostream> #include <windows.h> using namespace std;
int main() { HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO bInfo; GetConsoleScreenBufferInfo(hOutput, &bInfo); SMALL_RECT rc = {0, 0, 10, 10}; SetConsoleWindowInfo(hOutput, true ,&rc); cout << "窗口显示坐标位置:" << bInfo.srWindow.Left << ", " <<
COORD dSiz = {80, 80}; SetConsoleScreenBufferSize(hOutput, dSiz);
CloseHandle(hOut); return 0; }
|
GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO *bInfo)
第一个参数hConsoleOutput参数(标准控制句柄)通过GetStdHandle()函数返回值获得
第二个参数CONSOLE_SCREEN_BUFFER_INFO 保存控制台信息结构体指针
属性如下:
COORD dwSize; // 缓冲区大小
COORD dwCursorPosition; //当前光标位置
WORD wAttributes; //字符属性
SMALL_RECT srWindow; //当前窗口显示的大小和位置
COORD dwMaximumWindowSize; //最大的窗口缓冲区大小
若bInfo是一个CONSOLE_SCREEN_BUFFER_INFO类型的结构体,那么访问属性的方法如下:
- bInfo.srWindow.Top 获取窗口相对于Top位置
- bInfo.srWindow.Right 获取窗口相对于Right的位置
- bInfo.dSiz.X 获取缓存区的X位置
其余一些原型如下:
SetConsoleWindowInfo(HANDLE, BOOL, SMALL_RECT *);
SetConsoleScreenBufferSize(HANDLE hConsoleOutput, COORD dwSize)
GetConsoleTitle(LPTSTR lpConsoleTitle, DWORD nSize)
lpConsoleTitle为指向一个缓冲区指针以接收包含标题的字符串;nSize由lpConsoleTitle指向的缓冲区大小
例4
1 2 3 4 5 6 7 8 9 10 11
| #include "stdafx.h" #include<windows.h>
int main() { HWND window;
window = FindWindow(NULL,"文本.txt"); SendMessage(window,WM_CLOSE,0,0); return 0; }
|
句柄不用多说,在Windows中,句柄是一个系统内部数据结构的引用。例如当你操作一个窗口时系统会给你一个该窗口的句柄,系统会通知你:你正在操作142号窗口,就此你的应用程序就能要求系统对142号窗口进行操作——移动窗口、改变窗口大小、把窗口最小化等等。
例5
操作文本输出函数有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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, // 字符串 DWORD nLength, // 字符个数 COORD dwWriteCoord, // 起始位置 LPDWORD lpNumberOfCharsWritten); // 已写个数
BOOL ScrollConsoleScreenBuffer( //移动文本位置位置 HANDLE hConsoleOutput, //句柄 CONST SMALL_RECT* lpScrollRectangle, //裁剪区域 CONST SMALL_RECT* lpClipRectangle, //目标区域 COORD dwDestinationOrigin, //新的区域 CONST CHAR_INFO* lpFill); //填充字符
|
看例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <iostream> #include <windows.h> using namespace std; int main() { HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄 WORD wr1 = 0xfa;//定义颜色属性;第一位为背景色,第二位为前景色 SetConsoleTextAttribute(hOut, wr1); cout << "hello world!" << endl; WORD wr2 = FOREGROUND_RED | FOREGROUND_INTENSITY;//方法二用系统宏定义颜色属性 SetConsoleTextAttribute(hOut, wr2); cout << "hello world!" << endl;
//输出文本 SetConsoleTextAttribute(hOut, 0x0f); cout << "00000000000000000000000000000" << endl; cout << "00000000000000000000000000000" << endl; cout << "00000000000000000000000000000" << endl; cout << "00000000000000000000000000000" << endl;
SMALL_RECT CutScr = {1, 2, 10, 4}; //裁剪区域与目标区域的集合行成剪切区域 SMALL_RECT PasScr = {7, 2, 11, 9}; //可以是NULL,即全区域 COORD pos = {1, 8}; //起点坐标,与裁剪区域长宽构成的区域再与目标区域的集合为粘贴区
//定义填充字符的各个参数及属性 SetConsoleTextAttribute(hOut, 0x1); CONSOLE_SCREEN_BUFFER_INFO Intsrc; GetConsoleScreenBufferInfo(hOut, &Intsrc); CHAR_INFO chFill = {'A', Intsrc.wAttributes}; //定义剪切区域填充字符 ScrollConsoleScreenBuffer(hOut, &CutScr, &PasScr, pos, &chFill); //移动文本
CloseHandle(hOut); // 关闭标准输出设备句柄 return 0; }
|
由于剪切区域是两个区域的集合,我们第一行到第三行的第7个0到第11个0会被剪掉(前面有2行helloworld),注意,这里的rect的位置为其前面的字符个数或行数
例6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream> #include <windows.h> using namespace std; int main() { cout << "hello world!" << endl; Sleep(2000); HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); COORD w = {0, 0}; SetConsoleCursorPosition(hOut, w); CONSOLE_CURSOR_INFO cursorInfo = {1, FALSE}; Sleep(2000); SetConsoleCursorInfo(hOut, &cursorInfo); CursorInfo.bVisible = false; Sleep(2000); CloseHandle(hOut); return 0; }
|
输出hello world!后延时两秒,光标从第二行移到行首,再2秒后光标隐藏不显示,再过2秒程序结束
1 2 3 4 5 6 7 8
| //设置光标位置 SetConsoleCursorPosition(HANDLE hConsoleOutput,COORD dwCursorPosition); //设置光标信息 BOOL SetConsoleCursorInfo(HANDLE hConsoleOutput, PCONST PCONSOLE_CURSOR_INFO lpConsoleCursorInfo); //获取光标信息 BOOL GetConsoleCursorInfo(HANDLE hConsoleOutput, PCONSOLE_CURSOR_INFO lpConsoleCursorInfo); //参数1:句柄;参数2:CONSOLE_CURSOR_INFO结构体: //DWORD dwSize;(光标大小取值1-100)BOOL bVisible;(是否可见)
|
例7
GetCursorPos和SetCursorPos一组可以获取和设置鼠标的位置
SetCursorPos给一组x,y就可以设置
需要注意的是GetCursorPos:
POINT mouse; //用来储存鼠标的x y坐标
GetCursorPos(&mouse); //调用GetCursorPos函数获取坐标值
printf("%d,%d\n",mouse.x,mouse.y);
当要设置控制台光标的时候,使用SetConsoleCursorPosition
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <iostream> #include <windows.h> #include <conio.h> using namespace std; HANDLE hOut;
void cle(COORD ClPos) { SetConsoleCursorPosition(hOut, ClPos); cout << " " << endl; }
void prin(COORD PrPos) { SetConsoleCursorPosition(hOut, PrPos); cout << "@" << endl; }
void Move(COORD *MoPos, int key) { switch(key) { case 72: MoPos->Y--;break; case 75: MoPos->X--;break; case 77: MoPos->X++;break; case 80: MoPos->Y++;break; default: break; } }
int main() { cout << "用方向键移动下行输出内容" << endl; hOut = GetStdHandle(STD_OUTPUT_HANDLE); COORD CrPos = {0, 1}; prin(CrPos); while(1) { if(kbhit()) { cle(CrPos); Move(&CrPos, getch()); prin(CrPos); } } return 0; }
|
例8
读取键盘信息操作
键盘事件通常有字符事件和按键事件,这些事件所附带的信息构成了键盘信息。它是通过API函数ReadConsoleInput来获取的,其原型如下:
BOOL ReadConsoleInput(
HANDLE hConsoleInput, // 输入设备句柄
INPUT_RECORD lpBuffer, // 返回数据记录
DWORD nLength, // 要读取的记录数
LPDWORD lpNumberOfEventsRead // 返回已读取的记录数
);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| #include <windows.h> #include <stdio.h>
int main(void) { HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
DWORD dwRes, dwState=0; INPUT_RECORD keyRec; COORD crHome={0, 0}, crPos; bool bCaps, bNum, bScroll; char ch; CONSOLE_SCREEN_BUFFER_INFO bInfo;
printf("状态:CAPS LOCK:CLOSE NUM LOCK:CLOSE SCROLL LOCK:CLOSE\n");
while (1) { ReadConsoleInput(hIn, &keyRec, 1, &dwRes);
if (keyRec.EventType == KEY_EVENT) { if (keyRec.Event.KeyEvent.bKeyDown) { if (dwState != keyRec.Event.KeyEvent.dwControlKeyState) { dwState = keyRec.Event.KeyEvent.dwControlKeyState; bCaps = bNum = bScroll = false;
if (dwState & CAPSLOCK_ON) { bCaps = true; } if (dwState & NUMLOCK_ON) { bNum = true; } if (dwState & SCROLLLOCK_ON) { bScroll = true; }
GetConsoleScreenBufferInfo(hOut, &bInfo); SetConsoleCursorPosition(hOut,crHome);
printf("状态:CAPS LOCK:%s NUM LOCK:%s SCROLL LOCK:%s\n", bCaps?"OPEN ":"CLOSE", bNum?"OPEN ":"CLOSE", bScroll?"OPEN ":"CLOSE");
SetConsoleCursorPosition(hOut, bInfo.dwCursorPosition); }
switch(keyRec.Event.KeyEvent.wVirtualKeyCode) { case VK_RETURN: printf("\n"); break;
case VK_SPACE: printf(" "); break;
case VK_BACK: GetConsoleScreenBufferInfo(hOut, &bInfo); crPos = bInfo.dwCursorPosition; if (crPos.X!=0) { crPos.X -= 1; } SetConsoleCursorPosition(hOut,crPos); printf(" "); SetConsoleCursorPosition(hOut,crPos); break;
case VK_ESCAPE: CloseHandle(hOut); CloseHandle(hIn); return 0;
default: break; }
ch = keyRec.Event.KeyEvent.uChar.AsciiChar;
if (ch>0x20 && ch<0x7e) { putchar(ch); } } } }
return 0; }
|
EventType有5种,当要使用键盘事件时,应该先判断 EventType 是否为 KeyEvent,然后使用 KEY_EVENT_RECORD,判断现在的键盘是什么情况。其他事件也是一样的(一般只使用键盘和鼠标事件)
-
FOCUS_EVENT
-
KEY_EVENT // 键盘事件
-
MENU_EVENT
-
MOUSE_EVENT // 鼠标事件
-
WINDOW_BUFFER_SIZE_EVENT
键盘的结构如下:
typedef struct _KEY_EVENT_RECORD
{
BOOL bKeyDown; // TRUE表示键按下,FALSE表示键释放
WORD wRepeatCount; // 按键次数
WORD wVirtualKeyCode; // 虚拟键代码
WORD wVirtualScanCode; // 虚拟键扫描码
union {
WCHAR UnicodeChar; // 宽字符
CHAR AsciiChar; // ASCII字符
} uChar; // 字符
DWORD dwControlKeyState; // 控制键状态
}KEY_EVENT_RECORD;
所以当我们想要打印字符的时候,就使用
1 2 3 4 5
| ch = keyRec.Event.KeyEvent.uChar.AsciiChar;
if (ch>0x20 && ch<0x7e){ putchar(ch); }
|