0.效果图

可以用方向键进行选择,看起来高级点而且可以防止乱输入。
1.引入:
这是我经常写的选择:

相信这应该也是很多人在控制台的时候会用的吧,
的确这个简单容易写。
但!是!
人要有理想,控制台也是
所以我开始想把它写成一般游戏那种上下选择的样子:

所以就有了这篇博文,
接下来让我们进入正题。
2.思路
我们首先把这部分cout出来

然后我们需要使用到控制台里的光标移动函数gotoxy()(头文件:<windows.h>)
图中最后的白块就是光标,光标在哪,输出的东西就会从那开始。(应该都是懂的吧...)
注意:gotoxy()并不是c++标准库里的,windows.h里其实也没有这玩意,
所以我们需要借助<windows.h>来自己“写一个”。
- 1 void gotoxy(int x, int y)//光标移动函数, 且在这里把x定义为纵坐标,y为横坐标
- 2 {
- 3 COORD pos = { y,x };
- 4 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄
- 5 SetConsoleCursorPosition(hOut, pos); //两个参数分别是指定哪个窗体,具体位置
- 6 }
因为之前写一个小游戏的时候习惯了x为纵坐标,
如果你喜欢x为横坐标的话可以把函数里的第一行代码的{y,x}改为{x,y}即可。
借助gotoxy()移动到选项前的空白

怎么移动呢??
直接看图里,选项“自动模式”是在第3行,我们编程老传统默认0为开始,所以这应该认为是第2行。
gotoxy( 2 , 0 );//把光标移动到第2(实际第3)行第0(实际第1)个。
然后在这里cout << ">>>" ;
就会有这样的效果:

怎么样,看起来是不是有感觉了!
然后我们只需要监控键盘的方向键,控制“>>>”的上下就可以了。
怎么样,听起来是不是简简单单!
但!是!
仅仅依靠这一个移动光标的函数是不够的,
因为在让选择的这部分字打印之前肯定是会有一些其他的提示,
抑或是之前已经有了一些输出了,例如:

我们是要把它搞成一个可以经常用的函数,不可能每次需要就自己数数在哪一行,
所以我们需要一个函数来获取在这之前光标的位置。
- 1 int lightgetxy(int i) //获取光标位置,0为获取x,1为获取y,失败返回-1
- 2 {
- 3 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
- 4 COORD coordScreen = { 0, 0 }; //光标位置
- 5 CONSOLE_SCREEN_BUFFER_INFO csbi;
- 6
- 7 if (GetConsoleScreenBufferInfo(hConsole, &csbi))
- 8 {
- 9 if (i) // 0为获取y,1为获取x
- 10 return csbi.dwCursorPosition.X;
- 11 else // 获取y
- 12 return csbi.dwCursorPosition.Y;
- 13 }
- 14 else
- 15 return -1;
- 16 }
所以现在就是利用lightgetxy()来获取选择之前光标的位置,把行数+1那就是下一行,然后开始cout我们我们的选项就可以了。
详细的让我们来看看具体的实现代码吧。
这就是我们这篇博文的主体函数:
- 1 /*
- 2 选项选择函数;返回选择的contentstr[]的位置,默认1开始
- 3 max:contentstr的数组大小,contentstr[]:选项内容, tipstr:选择前提示
- 4 */
- 5 int switchcase(const int max, const string contentstr[], const string tipstr)
- 6 {
- 7 if (tipstr != "") //判断是否有提示字符串
- 8 cout << tipstr << endl;
- 9 cout << "<< 请用上下方向键选择..." << endl;
- 10 int nowi = 1, nowx = lightgetxy(0) - 1;
- 11 //nowi 记录选择的选项编号
- 12 cout << ">>> " << contentstr[0] << endl;
- 13 for (int i = 2; i <= max; ++i) //先打印选项内容
- 14 cout << " " << contentstr[i - 1] << endl;
- 15
- 16 while (1)
- 17 {
- 18 while (_kbhit()) //判断是否有按键按下
- 19 {
- 20 gotoxy(nowx + nowi, 0); //移动到原来的编号选项前
- 21 cout << " "; //覆盖掉它的">>>"
- 22 switch (_getch()) //获取按下的按键的ask2值
- 23 {
- 24 case 13: //Enter键确定
- 25 {
- 26 gotoxy(nowx + max + 1, 0);
- 27 return nowi; //返回当前选项编号
- 28 }break;
- 29 case 72: //上
- 30 {
- 31 if (nowi > 1) //在第一个再按上键会到最后一个
- 32 --nowi;
- 33 else
- 34 nowi = max;
- 35
- 36 }break;
- 37 case 80: //下
- 38 {
- 39 if (nowi < max) //在最后一个按下键会到第一个
- 40 nowi++;
- 41 else
- 42 nowi = 1;
- 43 }break;
- 44 }
- 45 gotoxy(nowx + nowi, 0); //移动到目标位置
- 46 cout << ">>>";
- 47 gotoxy(nowx + max + 1, 0); //记得把光标移动回输出的最后
- 48 }
- 49 Sleep(50);
- 50 }
- 51 }
最后让我们来找给简单的例子测试一下。
3.测试代码
-
1 #define _CRT_SECURE_NO_WARNINGS
2 #include<string>
- 3 #include<iostream>
- 4 #include<windows.h>
- 5 #include<conio.h>
- 6 using namespace std;
- 7
- 8 //光标移动函数, 且在这里把x定义为纵坐标,y为横坐标
- 9 void gotoxy(int x, int y)
- 10 {
- 11 COORD pos = { y,x };
- 12 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄
- 13 SetConsoleCursorPosition(hOut, pos); //两个参数分别是指定哪个窗体,具体位置
- 14 }
- 15
- 16 //获取光标位置。i为0获取x,非零获取y,执行失败返回-1
- 17 int lightgetxy(int i)
- 18 {
- 19 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
- 20 COORD coordScreen = { 0, 0 }; //光标位置
- 21 CONSOLE_SCREEN_BUFFER_INFO csbi;
- 22
- 23 if (GetConsoleScreenBufferInfo(hConsole, &csbi))
- 24 {
- 25 if (i) // 0获取y,非零获取x
- 26 return csbi.dwCursorPosition.X;
- 27 else // y
- 28 return csbi.dwCursorPosition.Y;
- 29 }
- 30 else
- 31 return -1;
- 32 }
- 33
- 34 //选择函数,返回以contentstr的位置+1
//max是选项数量,contentstr[]是选项的内容,注意这是个数组,tipstr是在选项前的提示内容 - 35 int switchcase(const int max, const string contentstr[], const string tipstr)
- 36 {
- 37 if (tipstr != "") //判断是否有提示字符串
- 38 cout << tipstr << endl;
- 39 cout << "<< 请用上下方向键选择..." << endl;
- 40 int nowi = 1, nowx = lightgetxy(0) - 1;
- 41 //nowi 记录选择的选项编号
- 42 cout << ">>> " << contentstr[0] << endl;
- 43 for (int i = 2; i <= max; ++i) //先打印选项内容
- 44 cout << " " << contentstr[i - 1] << endl;
- 45
- 46 while (1)
- 47 {
- 48 while (_kbhit()) //判断是否有按键按下
- 49 {
- 50 gotoxy(nowx + nowi, 0); //移动到原来的编号选项前
- 51 cout << " "; //覆盖掉它的">>>"
- 52 switch (_getch()) //获取按下的按键的ask2值
- 53 {
- 54 case 13: //Enter键确定
- 55 {
- 56 gotoxy(nowx + max + 1, 0);
- 57 return nowi; //返回当前选项编号
- 58 }break;
- 59 case 72: //上
- 60 {
- 61 if (nowi > 1) //在第一个再按上键会到最后一个
- 62 --nowi;
- 63 else
- 64 nowi = max;
- 65
- 66 }break;
- 67 case 80: //下
- 68 {
- 69 if (nowi < max) //在最后一个按下键会到第一个
- 70 nowi++;
- 71 else
- 72 nowi = 1;
- 73 }break;
- 74 }
- 75 gotoxy(nowx + nowi, 0); //移动到目标位置
- 76 cout << ">>>";
- 77 gotoxy(nowx + max + 1, 0); //记得把光标移动回输出的最后
- 78 }
- 79 Sleep(50);
- 80 }
- 81 }
- 82
- 83 int main()
- 84 {
- 85 cout << "歪比巴布占用了这一行" << endl;
- 86 cout << "玛卡巴卡占用了这一行" << endl;
- 87 cout << "反冲斗士芭芭拉也需要一行" << endl;
- 88 string str[] =
- 89 {
- 90 "自动模式",
- 91 "手动模式",
- 92 };
- 93 cout << "\n<< 您选择了选项" << switchcase(2, str, "<< 您希望以什么模式开始游戏?") << endl;
- 94 return 0;
- 95 }
运行结果:

注意:在switchcase()函数的最后有gotoxy(nowx + max + 1, 0);
如果不加这个移动,那么在调用这个函数后,你的光标会停留在某一个选项前,
然后你再cout什么东西的话就会在那一行进行覆盖,导致看起来很乱。
4.其他
利用获取光标位置和移动光标的函数还能有其他一些不错的功能,
例如一些颜文字的打印动画,实现进度条等等,我们以后再聊聊。
coolight大字打印动画:

进度条:
