1. 原贴
CSDN 用户 mcxd_llhn 发的帖子:
本人小白,C++代码,分享一下。下面是代码,没写完的,有大佬帮忙指出简化程序更好。
他的代码如下:
#include<iostream>
#include<stdlib.h>
using namespace std;
int main()
{
int i,sy;//i=输入,sy=死因
cout<<"如果需要操作帮助,可以在第一个输入界面输入114514";
_sleep(5*1000);//时间,要乘以1000为秒
system("cls");
cout<<"你掉入了洞穴!"<<endl;
cout<<"你的面前有三条路"<<endl;
cout<<"1.前面"<<endl;
cout<<"2.左边(该线路未更新,但是有预告)"<<endl;
cout<<"3.右边"<<endl;
cin>>i;
if(i==1)//往前
{
cout<<"你遇见了一个宝箱,要打开吗?"<<endl;
cout<<"1.那必须啊"<<endl;
cout<<"2.还是苟点好"<<endl;
cin>>i;
if(i==1)sy=3;
else if(i==2)
{
cout<<"你遇见一只小狗,他看见你的腰上没有信物,咬了上来"<<endl;
sy=4;
}
else sy=1;
}
else if(i==2)//往左
{
cout<<"你遇见了一扇门,要打开吗?(该线路尚未更新,请换一条线路)"<<endl;
}
else if(i==3)//往右
{
cout<<"轰!!!"<<endl;
_sleep(3*1000);
sy=2;
system("cls");
}
else if(i==114514)//帮助
{
cout<<"本游戏是一款文字类冒险游戏,通过数字选择进行选项冒险"<<endl;
_sleep(3*1000);
}
else
sy=1;
//死亡
if(sy==1)
{
cout<<"你没有做出正确th选项中的选择,视为放弃";
_sleep(3*1000);biov
}
else if(sy==2)
{
cout<<"你死了!死因:被通路的碎石头掩埋,缺水而死";
_sleep(3*1000);
}
else if(sy==3)
{
cout<<"你死了!死因:宝箱中弹出的刀片(你不会真的以为有东西吧?不开箱子的玩家)";
_sleep(3*1000);
}
else if(sy==4)
{
cout<<"你死了!死因:小狗撕咬" ;
_sleep(3*1000);
}
return 0;
}
2. 问题分析
这个代码确实比较乱,小问题很多,我给列的就有:
- 引入C库头文件不规范:C++代码中包含C库头文件,用法不对
- 不跨平台,用了windows特定的 cls 命令行,以及 _sleep()函数
- 代码没分层,像面条一样的窜;
- 业务逻辑似乎不完整,用户要是那条未实现的路线的后续怎么处理?
- 代码组织不合理:死因通过整数传递,这样写需要额外对应,正常做法要么定义表,是直接给出死因
- 错误处理不强壮,你试试在需要输入数字的地方,给它个字母,程序可能要狂跳
2.1 头文件不规范
这一行:
#include <stdlib.h>
在C++代码中包含C语言标准库的头文件,应该使用“#include cxx”,比如这里用到的 <stdlib.h>,应改为
#include<cstdlib>
后者能将所引入的C的头文件的内容,都变成在C++标准库名字空间 std 之下,有效降低C库中的符号和来自其地方的符号发生重名的概率。
2.2 使用了依赖于特定平台的功能
主要是 “cls” 这个控制台命令 和 “_sleep” 这个函数。
代码中为了清空控制台屏幕,方法是通过 system 这个C 函数(它就来自前面提到的 cstdlib ),以直接执行控制台命令;这是个好办法,不过,Windows下的控制台,和 Linux 下的 shell ,各自的命令相差很大。比如这个清屏用的“cls”,在Linux下,就得用“clear”。
_sleep 则一个 C 函数,用于让当前程序(的当前线程)睡眠一段时间,但它在不同平台下相差很多。其实,C++ 11 已经引入可以通用各平台的标准方法来实现这一功能,即: this_thread::sleep_for()。
2.3 代码没分层
这是最大问题,一条线实现下来去,不仅重复逻辑或相似代码多,而且阅读起来比较困难。解决办法通常是对逻辑分组,同一组的逻辑归入一个函数。比如例中用等首先要选择 直行、左转还是右转。则可以将三者的行为,各自写成一个函数。
2.4 有遗漏逻辑
一大串代码实现下来的坏处之一,就是很容易让作者自己都漏了某个分支功能的处理。注意,这里并不是指实现。代码原作者特意指明用户如果选择“左转”,那么这条分支上的功能是没有实现的——这当然可以,一个程序可以明确标明某块功能尚未实现,但“具体功能没有实现”,和这个分支未处理是两回事:比如,程序显示“抱歉,你选择的功能还没实现”后,是不是应该允许用户换一个选择?
另外,整个程序也没有实现重复玩的功能,这个或许不是因为遗漏,而是“设计就如此”,但这样的设计,会造成用户想换一个选择,就只能重新运行程序,整个游戏的友好性大受影响。
2.5 代码组织不合理
注意看原代码中的 sy (死因) 这个变量,除了用拼音缩写命名非常不推荐以外,你会发现,代码作者和阅读者都必须去记忆:死因为1时,是什么原因?为2时又是什么原因?可以有以下几种解决方法:
- 不使用立即数,改为使用枚举 (enum)
- 事先建立一张映射表(通常用数组或map),将用于表达死因的数字和死因的描述“绑”起来
但在本例中,该问题最好的解决方法,是避免问题发生(而不是问题发生后再想方法)。观察这个sy变量的前后使用,不难发现:前面在不同死因的发生处,辛苦设置了sy的不同值,到后面也应用用它做个区分,然后输出不同死因的说明而已;这种情况下,不如在玩家失败时,直接输出死因说明,无需借助sy这个变量“长距离”传递。
“长距离”传输信息的坏处:我在改写这段代码时,为了保证我不看错,也为了确保原代码逻辑无误,我就不得不在距离很远的两处代码,使劲比较这个sy的值。
2.6 容错性差
程序全程让用户输入数字,大家可以试试,但凡用户有意或无意输入一个字母,整个程序就会疯狂的一直重复输出……
3. 新代码
下面新代码解决了上面所有问题,从功能上看,用户可以反复玩,容错也处理了,并且也支持跨平台了(试了 Windows、Linux)。从代码组织上看,通过函数来实现具体功能分组。主流程因此非常短小易读。为增加趣味性,部分输出或交互也做了改进。
除解决问题之外,在新代码中,你还可以找到:
- 用到了C++的异常机制,大家可以用它来帮助理解“异常”和“错误”的区别;
- 用到了“带class的枚举”;
- 用到了 using 定义类型别名;
代码后面给了一个可以 在线运行 、试玩这个程序的链接。
#include <cstdlib>
#include <iostream>
#include <vector>
#include <thread>
void ClearScreen()
{
char const* cmd =
#if defined(_WIN32)
"cls";
#elif defined(__linux__)
"clear";
#endif
std::system(cmd);
}
int const HELP_OPTION = 99;
void HintWithSleep(char const* hint, int64_t seconds_would_wait = 0) // 显示提示
{
std::cout << hint << std::endl;
if (seconds_would_wait > 0)
{
std::this_thread::sleep_for(std::chrono::seconds(seconds_would_wait));
}
}
using Options = std::vector<char const*>;
int Select(char const* question, Options options) // 选择
{
do
{
std::cout << "\n" << question << "\n";
if (options.size() >= HELP_OPTION)
{
throw "扯淡吗?让用户做这么多选择?";
}
for (int i=0; i<options.size(); ++i)
{
std::cout << i+1 << ":\t" << options[i] << "\n";
}
std::cout << HELP_OPTION << ":\t 帮助\n";
std::cout << "请选择(1-" << options.size() << ",Ctrl+C 强行退出):";
int i;
std::cin >> i;
if (std::cin.fail())
{
std::cin.clear(); // 清除错误的流状态
std::cin.ignore();
throw "错误的输入(非法选择项)";
}
if (i == HELP_OPTION)
{
std::cout << "这一是款闻名全球的文字游戏,你只需输入选项开头的数字以\n"
"做出选择即可参与一场惊心动魄的大战!" << std::endl;
HintWithSleep("看明白了吗?即将开始重选", 4);
ClearScreen();
continue;
}
if (i < 1 || i > options.size())
{
std::cout << "你会不会玩啊!你得输入符合范围的选择!\n(碰上你们这种玩家真是烦死了!)" << std::endl;
}
return i;
}
while (false);
return -1;
}
void GameOver(char const* additions = nullptr) // game over!
{
std::cout << ">>>>>> 抱歉,你挂了!!!玩家村全村人民高兴的准备吃席 <<<<<<<"
<< std::endl;
if (additions && *additions)
{
std::cout << additions << std::endl;
}
}
enum class Result {YouLose /*你死了*/, YouWin /*你赢了*/, WrongRoad /*错误路径*/};
void ByeBye(Result r) // 退出之前...
{
if ( r == Result::YouLose)
{
std::cout << "\n下次要努力哦!再见!\n" << std::endl;
} else if (r == Result::YouWin)
{
std::cout << "\n这不可能!算你狗屎运好!再见!\n" << std::endl;
}
}
Result OnBox() // 遇见盒子
{
int sel = Select("哇!遇见了一个盒子,要不要打开?", Options {
"那必须啊!", "还是苟点好……"
});
switch(sel)
{
case 1 : // 打开
HintWithSleep("盒子里弹一把小刀,刺向你的下体……", 3);
GameOver();
break;
case 2 :
HintWithSleep("没有打开宝盒,你拿不到信物。一只观察你很久的小狗冲上来咬住你的下体……", 3);
GameOver("(但那只可爱的小狗狗说它已经吃饱了。)");
break;
}
return Result::YouLose; // 这关反正要你死
}
Result OnBomb()
{
HintWithSleep("轰!!!\n突然,从天而降的地雷……", 3);
GameOver("(地雷说:我又没爆炸,他怎么挂了?答:被砸的)");
return Result::YouLose;
}
Result UnImplemented() // 未实现的
{
HintWithSleep("此路还在开发中,欢迎各大开发商参加招投标!", 1);
return Result::WrongRoad;
}
void Run()
{
int sel = Select("你掉入了洞穴!\n你的面前有三条路", Options{
"往前继续直走", "向左拐", "向右拐"
});
enum Dir {goStraight=1, turnLeft=2, turnRight=3};
switch(sel)
{
case goStraight:
ByeBye(OnBox()); //是的,直行必死
break;
case turnRight: // 是的,往右更是死
ByeBye(OnBomb());
break;
case turnLeft: // 往左没实现
UnImplemented(); // 唯一活路还没实现...
break;
}
}
int main()
{
for(;;)
{
try
{
Run();
auto c = Select("再来一把?", Options {"好!", "滚!"});
if (c == 2)
{
std::cout << "好吧!我滚了!" << std::endl;
break;
}
ClearScreen();
}
catch (char const* e)
{
std::cerr << "\n好像出现了异常:" << e << "……必须重来!" << std::endl;
HintWithSleep("系统正在努力重启中...", 2);
ClearScreen();
continue;
}
}
}