本次教程内容:

  • 动态数组
  • 映射
  • 地图管理器

如果我们想从0到1地建立一个稍复杂一点的软件,持续的代码重构就是必不可少的。

在本教程之前,我们一直在用静态数组存储我们的地图信息。但正如我们从昨天的代码中看到,一个静态的数组对于存储可变大小的地图,是有一些问题的。问题无非表现在两个方面:

  • 1、如果地图小了,空间留的大,则出现浪费的问题(这个问题相对比较小)
  • 2、如果地图大了,而空间留得不足,则无法正常读入(会产生严重的问题)

所以我们可能会想到,如果数组的空间大小可以根据读入数据量的大小静态地改变,就可以很好的解决这个问题。
很多语言都动态数组的机制,c++也不例外。
在c++中,使用动态数组,最方便的方法莫过于vector容器。
所谓容器,是指一种纯形式的对象。比如静态数组就可以理解为一种容器,数组只是一种单纯的形式,我们可以建立任何具体类型的数组。我们这里所说的vector就是一种动态数组的容器,它只是提供了一种结构,让我们可以方便地操作任何类型的动态数组。
为什么用vector代表动态数组?vector在英语里是向量的意思,所谓的向量,也可以理解为一种一维矩阵,所以用来代表动态数组。名称这种东西只能会意,不具有什么必然性,所以记住能用即可。比如同样的功能在python中叫做list,而在c++中list另有含义。

让我们看一下具体怎样使用这个容器。
首先,include一个新的库vector

# include <vector> 

然后把我们的mapInfo原来的定义是 string mapInfo[100];
现在改为

vector <string> mapInfo;

这句话的意思是声明mapInfo为一个字符串类型的动态数组。
当我们访问动态数组时,语法和访问静态数组完全一样。
这里我们注意到尖括号又出现了,在这里它的作用是标识泛型的类型。泛型概念的提出,确实很方便,用同样的逻辑结构可以处理各种类型。但从这一点来看,python这种根本就无须声明类型的语言,自由度更大。

而扩展静态数组则须使用不同的语句。我们看代码:

void loadMap(string aFileName){ifstream fin(aFileName.c_str());if (fin) {while (!fin.eof()){string str1;getline(fin, str1);mapInfo.push_back(str1);}}fin.close();
}

其中push_back指令的意思在mapInfo变量的大房间中增加一个新的小房间,并将其中的数据保存为字符串str1。
注意到,在这里我们已经不再继续记录地图的高度,因为现在的数组是动态增加大,地图的高度可以用数组的大小来获得。现实地图的代码,修改如下:

void showMap(){for (int i=0; i< mapInfo.size(); i++){cout << mapInfo[i]<< endl;}
}

mapInfo.size()就是数组的大小,在这里就是地图的行数。以前的静态数组,同样有这个方法,但那个数据是固定的,对我们显示可变大小的地图没有用处。

代码重构无处不在,一旦我们有了多张地图,一个地图管理器就是必不可少的。
如果读者运行了上一课的代码,我们可能会注意到第二关的地图明显不正常,因为第二张地图的默认入口和第一、三张地图不一样,但地图显示的时候,英雄却都显示在同一个位置。
究其原因,我们只是修改了同一个地图对象的地图数据,并没有改变英雄初始位置的数据。
现在看来,一个更好的做法是我们把这些地图作为不同的对象来管理。
因为我们可能事先不知道一个游戏有多少张地图,读者或许会想继续使用vector容器来管理多张地图。
vector是可以的,但考虑到我们将来会有一个需求就是根据地图的名字找到地图,所以我们打算使用另一种容器:map容器。这个map与我们的地图无关,它的含义是映射。在python中,类似的功能叫做字典。map提供一种“关键字+值”的映射关系。对比一下在我们的精灵大楼中的vector类房间,里面的每个小房间是通过0~N的编号来访问的;而map类房间,里面的每个小房间则可以通过指定的关键字来访问。
我们看一下地图管理器的代码:

class MapManager{map <string, Map> mapList;public:void addMap(string aName, int ax= 0, int ay= 1){if (mapList.count(aName)== 0){Map map1;map1.x= ax;map1.y= ay;map1.loadMap(aName+ ".txt");mapList.insert(make_pair(aName, map1));}}void jumpMap(string aName){if (mapList.count(aName)== 1){mapList[aName].work();}}
};

地图管理器有两个公开函数addMap和jumpMap,这段代码有6个值得注意的点。
1    void addMap(string aName, int ax= 0, int ay= 1)
    参数的默认值。当一个地图没有设置启示位置时,我们就默认为(0,1)位置,通过这样的方式来设定。
2         if (mapList.count(aName)== 0)
   我们向地图管理器添加一张地图前,首先判断这张地图是否已经存在,如果不存在才进行添加操作。
3             map1.loadMap(aName+ ".txt");
   在这里,我们把地图的名字和它的扩展名分开,使用起来显得更自然。
4 mapList.insert
   向地图管理器中添加映射对象,使用的insert函数。insert函数接收一种特殊的参数,叫做映射对。
   这种映射对,只有下面这个函数能够生成。
5 make_pair
    这个函数接收任何两个参数,并把它的值组成一个映射对,用于给map类的insert函数使用。
6         if (mapList.count(aName)== 1){
    与前一点的思路类似,在显示一个地图之前,我们同样必须先做一个判断,只有当这个名字对应的地图存在,我们才能进行后面的操作。

经过这样改造后,主程序只须关注地图管理器,已经不再对具体的地图进行直接操作了。

//*
int main(){HideCursor(); MapManager mm1;mm1.addMap("map1");mm1.addMap("map2", 0, 19);mm1.addMap("map3");mm1.addMap("mapWin");//mm1.jumpMap("map1");mm1.jumpMap("map2");mm1.jumpMap("map3");mm1.jumpMap("mapWin");
}
//*

注意添加地图map2的时候,特别定义了英雄的起始位置。如果你看不懂后面连续4个地图的载入动作,这是正常的,因为我们对Map类也做了一些调整,方便我们可以调试多张地图。
我们将相关的调整完整地贴上来。

// 代码调整位置1
class MapManager;class Map{vector <string> mapInfo;int w, h;int x, y;// 代码调整位置2friend MapManager;void showMap(){// 代码调整位置3system("cls");for (int i=0; i< mapInfo.size(); i++){cout << mapInfo[i]<< endl;}}void showHero(){gotoxy(x,y);cout << "♀";}void hideHero(){gotoxy(x,y);cout << "  ";}void tryMove(int ax, int ay){hideHero();if (mapInfo[ay][ax]== ' ') {x= ax;y= ay;}showHero();}void listen(){int a;while(1){a= getch();if (a==72) {// 向上 tryMove(x, y-1); }if (a==80) {// 向下 tryMove(x, y+1); }if (a==75) {// 向左 tryMove(x- 2, y); }if (a==77) {// 向右 tryMove(x+ 2, y); }// 代码调整位置4if (a==13) {// 回车键 break;}}}// 代码调整位置5,取消了publicvoid loadMap(string aFileName){ifstream fin(aFileName.c_str());if (fin) {while (!fin.eof()){string str1;getline(fin, str1);mapInfo.push_back(str1);}}fin.close();}void work(){showMap();showHero();listen();}
}; 

其中代码调整的位置,已经用注释标明了,共有5处。
其中第1,2,5处的调整,是相关联的。
由于MapManager类在addMap函数中会使用到Map类的英雄初始位置:x和y两个私有属性,所以必须用friend来标识一下MapManager对Map的可访问性。这就是调整2,这个语句让MapManager类可以随意访问Map类的全部变量和函数。
但在Map定义的时候,还没有MapManager类。而我们也不能把MapManager拿到Map定义之前,因为它的代码中有访问Map对象,这种两难的境地,通过预先声明来解决。这就是调整1,首先声明一下,MapManager是某个类,这样Map类在声明friend的时候就没有任何误解。同时,在后面MapManager补充完整了自己的代码,做到两全其美。
既然MapManager类被声明为Map类的朋友,具备了对Map类所有属性的访问权限,而主函数对Map类已经不再关心。所以以前的public声明索性也去掉了。这就是调整5。在编程的方法中,各个对象类型之间,不是知识越多越好,而是在知识够用的情况下,越少越好。
其中,第3处调整的作用是清屏幕。以前我们每次运行只显示一张地图,所以没有请屏幕的需求,现在则不同了,切换多张地图前,应当把屏幕清除一下。这个语句的另外一个作用是清除屏幕的同时,把光标移动到了左上角的(0,0)点。
第4处的调整,增加了对回车键的监听。当我们按下回车键,精灵将执行break指令。这是一个无条件跳转的指令,配合各种循环使用。含义是直接跳到循环外的第一条语句执行。由于这里没有下一条语句,也就代表着本地图的退出。

课程小结:

本课讲了两个stl(标准模板库)的容器vector和map,这类数据结构在编程中会大量用到。我们认真体会这种抽象数据结构存在的意义,它们的目标不是解决某个具体的问题,都是为了解决各类一般性的问题而设计的。另外讲到了friend的概念,是对类内部可见性的一种调整。介绍了与循环语句配合使用的break指令,它是一种无条件跳转指令。
现在的测试模式,我们一步不走,直接按下回车键,就可以进入下一幅地图。怎样让系统能够判断,我们走到了迷宫的尽头,无需按任何键,自动进入下一幅地图呢?

本章的完整代码下载地址。

第二章教程12:地图管理器相关推荐

  1. 第二章教程14:管理器夺权

    本次教程内容: 播放背景音乐 键盘监听功能转移 结构体 融入事件机制 数字与字符串互相转换 不同的需求如果能用统一的操作方法来实现,无论是客户还是程序员都会受益. 再回顾一下上节课中小Pa提出的5个需 ...

  2. (转)MVS-OS390系统管理-第二章 大型服务器外存管理

    http://zmdxyboyandy.blog.bokee.net/bloggermodule/blog_viewblog.do?id=205821 更多文章首页 > 文章 > IBM大 ...

  3. 【XJTUSE软件项目管理复习笔记】 第二章 软件项目整体管理

    仅供学习参考,禁止商用与转载 文章目录 软件项目管理复习笔记 第二章 软件项目整体管理 什么是项目整体管理 战略计划和项目选择 项目选择 项目的财务分析 净现值分析(重点) 投资收益率(ROI)分析法 ...

  4. Python基础教程:上下文管理器 context manager(with...as...)

    一.概念 上下文管理器:就是实现了上下文管理协议的对象.主要用于保存和恢复各种全局状态,关闭文件等.上下文管理器本身是一种装饰器. 上下文允许可以自动的开始和结束一些和事情.例如当利用with-as打 ...

  5. Expression Blend实例中文教程(11) - 视觉管理器快速入门Visual State Manager(VSM)

    Visual State Manager,中文又称视觉状态管理器(简称为VSM),是Silverlight 2中引进的一个概念.通过使用VSM,开发人员和设计人员可以轻松的改变项目控件的视觉效果,在项 ...

  6. 第二章 系统集成及服务管理知识点1

    这第二章主要讲了下集成及服务管理的内容.制度.意义.管理办法.以及一些管理方面的服务概念.跟着小老弟把内容给归纳归纳,后面来复习的时候也能够省不少时间! 1信息系统集成及服务管理的内容 在信息化建设过 ...

  7. 你以为文言编程只是闹着玩?三个月后,人家IDE、教程、包管理器都有了

    . 参与:思,Jamin 用文言文写的官方编程教程<文言陰符>,类似 pip 那样的包管理工具「文淵閣」,还有文言编程开源 IDE「文言齋」,文言编程语言已经这么成熟了? 机器之心曾介绍过 ...

  8. python教程:上下文管理器详细教程

    我想你对 Python 中的with语句一定不陌生,尤其是在文件的读写操作中,不过我想,大部分人可能习惯了它的使用,却并不知道隐藏在其背后的"秘密". 那么,究竟with语句要怎么 ...

  9. Linux就该这么学---第七章(LVM逻辑卷管理器)

    第七章节-LVM技术 逻辑卷管理器(LVM,Logical Volume Manager) 1.物理卷(PV,physical Volumn) 2.卷组(VG,Volume Group) 3.逻辑卷( ...

最新文章

  1. 大规模业务服务器开发总结
  2. 2021 4 21 管理心得
  3. linux 追加多行文件,linux多行文件信息追加
  4. Android之非root手机run-as命令获取debug版本apk里面的数据(shared_prefs文件,lib下面的so,数据库文件)
  5. 图像增强_Keras 常用的图像增强方式
  6. 俄罗斯四人***团伙黑掉整个城市ATM机
  7. Git在window的使用(TortoiseGit)之一
  8. PHP开源AJAX框架
  9. Python爬虫实战02:分析Ajax请求并抓取今日头条街拍
  10. 在项目中使用redis的原因
  11. c++ string substr_【函数分享】PHP函数substr ()分享(2020929)
  12. php电子病历毕业设计,电子病历管理系统的设计毕业论文.doc
  13. windows安装pyspider教程
  14. 久其报表大厅_久其报表是什么?
  15. Android 应用开发---TextView(文本框)详解
  16. 冬奥、亚运会、世界杯,顶级运动员与头部品牌们的营销盛宴
  17. 【FF14】工匠配方爬取
  18. Flash 多人在线游戏教程 - TicTacToe
  19. Sublime and Markdown
  20. git clone速度太慢的解决办法

热门文章

  1. 罗技x56c语言编程,【罗技X56评测】模拟飞行好伙伴,X56在x-plane中的应用
  2. 语义网知识表示方法:RDF,RDFS与OWL
  3. 黑色沙漠服务器修改密码,黑色沙漠二次密码重置方法介绍 怎么重置二次密码...
  4. 《笨办法学python》学习心得
  5. 帮助中心能帮助企业解决什么问题?
  6. arduino 读取当前时间_Arduino 101/Genuino 101 时间控制函数
  7. DataGrip 用鼠标控制Query console中字体缩放大小
  8. 把WPS/Excel里的单元格为图片url链接转换为图片显示
  9. 如何正确使用Pushy 推送IOS SDK
  10. TabLayout自定义选择背景滑块