C/C++轻量级并发TCP服务器框架Zinx-游戏服务器开发004:游戏核心消息处理 - 玩家类的实现

文章目录

  • 0 代码仓库
  • 1 需求
  • 2 AOI设计
    • 2.1 AOI算法简介
    • 2.2 AOI数据结构及实现
      • 2.2.1 玩家
      • 2.2.2 网格对象
      • 2.2.3 游戏世界矩形
      • 2.2.4 获取周围玩家的实现
      • 2.2.5 代码测试
    • 2.3 GameRole结合AOI创建玩家
      • 2.3.1 创建游戏世界全局对象-GameRole继承AOIWorld的Player
      • 2.3.2 把玩家到游戏世界的加入与删除
      • 2.3.3 玩家上线时的处理:新客户端连接后,向自己发送ID和名称
      • 2.3.4 新客户端连接后,向其发送**周围**玩家的位置
      • 2.3.5 新客户端连接后,向**周围**玩家发送其位置
      • 2.3.6 游戏测试:
      • 2.3.7 世界聊天
        • 2.3.7.1 创建广播
        • 2.3.7.2 发送给所有人
    • 2.4 玩家移动处理
      • 2.4.1 视野出现和消失
      • 2.4.2 跨网格处理
      • 2.4.3 广播新位置给周围玩家
    • 2.5 随机出生
    • 2.7 退出程序
      • 2.7.1 定时器设计
      • 2.7.2 最后一个玩家,启动定时器
      • 2.7.3 初始化的完善
      • 2.7.4 主函数的完善
    • 2.8 随机姓名的设计与实现
      • 2.8.1 姓和常用名的定义
      • 2.8.2 取名字
      • 2.8.3 还名字
      • 2.8.4 读取文件组建姓名的线性表
  • 3 架构回顾

游戏相关的核心消息处理逻辑都是要在该类中实现的。

0 代码仓库

https://github.com/Chufeng-Jiang/TCP_Concurrent_Server_Framework_Zinx-Game_Server_Development_Project

1 需求

  • 新客户端连接后,向其发送ID和名称
  • 新客户端连接后,向其发送周围玩家的位置
  • 新客户端连接后,向周围玩家发送其位置
  • 收到客户端的移动信息后,向周围玩家发送其新位置
  • 收到客户端的移动信息后,向其发送周围新玩家位置
  • 收到客户端的聊天信息后,向所有玩家发送聊天内容
  • 客户端断开时,向周围玩家发送其断开的消息

关键字:周围

以上所列出的需求,基本都是这样的套路:在XXX的时候,发送XXX给XXX。

  • 发送时机
  • 消息内容
  • 发送对象:怎样表示周围玩家?

2 AOI设计

2.1 AOI算法简介

定义: 获取感兴趣的区域(Area Of Interest)的算法。

解决的问题: 形成周围的概念。在多人游戏中,各个游戏客户端之间需要通过服务器向彼此更新自身状态。但对于当玩家来说,我们不需要获取“太远”的玩家的信息,所以,在服务器端,我们通过AOI算法可以获取到某个客户端“周围”的玩家,进而只在该小范围内同步信息。

网格法AOI

  • 参考游戏世界的坐标,创建一个边界相同的矩形。
  • 选取适当的颗粒度,将矩形分割成几×几的网格。
  • 每个客户端都要按照实际坐标添加到某个格子里。
  • 客户端所在格子的周围八个格子内的玩家就是周围玩家。

举例: 世界坐标是X[20,200],Y[50,230],划分成6×6的网格为:
在这里插入图片描述

  • 已知玩家坐标(x,y),该玩家在几号网格?
  • 网格编号=(x-x轴起始坐标)/x轴网格宽度 + (y-y轴起始坐标)/y轴宽度*x轴网格数量
  • x轴网格宽度=(x轴结束坐标-x轴起始坐标)/x轴网格数量;
  • y轴的计算方式相同
  • 已知玩家在n号网格,周围的格子(包括自己)有哪些?

在这里插入图片描述

2.2 AOI数据结构及实现

目的:获取周围玩家
模型:将游戏世界的坐标分割成网格,玩家属于某个网格
周围:玩家所属网格周围8个相邻网格内的玩家
游戏世界矩形:包含固定数量网格对象的容器
网格对象:包含若干玩家的容器
玩家:拥有横纵坐标的对象

2.2.1 玩家

class Player {
public:virtual int GetX() = 0;virtual int GetY() = 0;
};

2.2.2 网格对象

  • 添加玩家的时候,计算出玩家的坐标,然后将坐标就添加到网格对象中。
  • 网格对象是以网格为单位的矩形,里面装的是该网格范围内的所有玩家(坐标)。
class Grid {
public:std::list<Player *> m_players;
};
  • 添加玩家到网格对象
bool AOIWorld::AddPlayer(Player * _player)
{/*计算所属网格号*///网格编号=(x-x轴起始坐标)/x轴网格宽度 + (y-y轴起始坐标)/y轴宽度*x轴网格数量int grid_id = (_player->GetX() - x_begin) / x_width + (_player->GetY()-y_begin) / y_width * x_count;/*添加到该网格中*/m_grids[grid_id].m_players.push_back(_player);return true;
}
  • 删除玩家
void AOIWorld::DelPlayer(Player * _player)
{int grid_id = (_player->GetX() - x_begin) / x_width + (_player->GetY() - y_begin) / y_width * x_count;m_grids[grid_id].m_players.remove(_player);
}

2.2.3 游戏世界矩形

class AOIWorld
{int x_begin = 0;int x_end = 0;int y_begin = 0;int y_end = 0;int x_count = 0;int y_count = 0;int x_width = 0;int y_width = 0;
public:std::vector<Grid> m_grids;/*通过构造函数指定矩形的大小和分割粒度*/AOIWorld(int _x_begin, int _x_end, int _y_begin, int  _y_end, int _x_count, int _y_count);virtual ~AOIWorld();/*获取周围玩家*/std::list<Player *> GetSrdPlayers(Player *_player);/*添加玩家到AOI网格*/bool AddPlayer(Player *_player);/*摘除玩家*/void DelPlayer(Player *_player);
};
  • 初始化世界并创建格子对象
AOIWorld::AOIWorld(int _x_begin, int _x_end, int _y_begin, int _y_end, int _x_count, int _y_count):x_begin(_x_begin),x_end(_x_end),y_begin(_y_begin),y_end(_y_end),x_count(_x_count),y_count(_y_count)
{//x轴网格宽度=(x轴结束坐标-x轴起始坐标)/x轴网格数量;y轴的计算方式相同x_width = (x_end - x_begin) / x_count;y_width = (y_end - y_begin) / y_count;/*创建格子们*/for (int i = 0; i < x_count * y_count; i++){Grid tmp;m_grids.push_back(tmp);}
}

2.2.4 获取周围玩家的实现

在这里插入图片描述

std::list<Player*> AOIWorld::GetSrdPlayers(Player * _player)
{list<Player *> ret;/*计算所属编号*/int grid_id = (_player->GetX() - x_begin) / x_width + (_player->GetY() - y_begin) / y_width * x_count;/*判断具体情况,取出邻居网格的玩家们*///计算当前网格横着数和纵着数的个数, 当前网格在世界的坐标int x_index = grid_id % x_count; //横着的坐标,colint y_index = grid_id / x_count;  //纵向坐标,rowif (x_index > 0 && y_index > 0) //有左上角的格子{list<Player *> &cur_list = m_grids[grid_id - 1 - x_count].m_players;ret.insert(ret.begin(), cur_list.begin(),cur_list.end());}if (y_index > 0) //正上方的格子{list<Player *> &cur_list = m_grids[grid_id - x_count].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}if (x_index < x_count - 1 && y_index > 0) //右上角的格子{list<Player *> &cur_list = m_grids[grid_id - x_count + 1].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}if (x_index > 0) //左方的格子{list<Player *> &cur_list = m_grids[grid_id - 1].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}//自己所在位置list<Player *> &cur_list = m_grids[grid_id].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());if (x_index < x_count - 1) //右方的格子{list<Player *> &cur_list = m_grids[grid_id +1 ].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}if (x_index > 0 && y_index < y_count - 1) //左下方的格子{list<Player *> &cur_list = m_grids[grid_id - 1 + x_count].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}if (y_index < y_count - 1) //正下方的格子{list<Player *> &cur_list = m_grids[grid_id + x_count].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}if (x_index < x_count - 1 && y_index < y_count - 1)  //右下方的格子{list<Player *> &cur_list = m_grids[grid_id + 1+x_count].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}return ret;
}

2.2.5 代码测试

#include "GameChannel.h"
#include "GameMsg.h"
#include "msg.pb.h"
#include "AOIWorld.h"class myPlayer :public Player {
public:myPlayer(int _x, int _y, std::string _name) :x(_x), y(_y), name(_name) {}int x;int y;std::string name;// 通过 Player 继承virtual int GetX() override{return x;}virtual int GetY() override{return y;}
};int main()
{AOIWorld w(20, 200, 50, 230, 6, 6);myPlayer p1(60, 107, "1");myPlayer p2(91, 118, "2");myPlayer p3(147, 133, "3");w.AddPlayer(&p1);w.AddPlayer(&p2);w.AddPlayer(&p3);auto srd_list = w.GetSrdPlayers(&p1);for (auto single : srd_list){std::cout << dynamic_cast<myPlayer*>(single)->name << std::endl;}ZinxKernel::ZinxKernelInit();/*添加监听通道*/ZinxKernel::Zinx_Add_Channel(*(new ZinxTCPListen(8899, new GameConnFact())));ZinxKernel::Zinx_Run();ZinxKernel::ZinxKernelFini();
}

在这里插入图片描述

2.3 GameRole结合AOI创建玩家

2.3.1 创建游戏世界全局对象-GameRole继承AOIWorld的Player

proto文件中对应

message Position{float X=1;float Y=2;	float Z=3;	float V=4;int32 BloodValue=5;
}
#pragma once
#include <zinx.h>
#include "AOIWorld.h"
#include "GameMsg.h"class GameProtocol;
class GameRole :public Irole,public Player
{float x = 0;float y = 0;//高float z = 0;float v = 0;int iPid = 0;std::string szName;GameMsg *CreateIDNameLogin();GameMsg *CreataSrdPlayers();GameMsg *CreateSelfPostion();GameMsg *CreateIDNameLogoff();
public:// 通过 Player 继承virtual int GetX() override;virtual int GetY() override;
};
  • 注意在人物角色中,y是人物的高度,不是二维平面的地点坐标,y由前端那边设计。
/*创建游戏世界全局对象*/
static AOIWorld world(0, 400, 0, 400, 20, 20);int GameRole::GetX()
{return (int)x;
}int GameRole::GetY()
{return (int)z;
}

2.3.2 把玩家到游戏世界的加入与删除

  • 连接到来(玩家初始化)时
属性pid赋值为socket值
属性name写成tom
初始坐标100,100
向自己发内容是ID和姓名的1号消息
向自己发内容是若干周围玩家信息的202号消息
向周围玩家发送内容是自己位置的200号消息
bool GameRole::Init()
{/*添加自己到游戏世界*/bool bRet = false;/*设置玩家ID为当前连接的fd*/iPid = m_pProto->m_channel->GetFd(); //获取文件连接描述符,这个是唯一的bRet = world.AddPlayer(this);if (true == bRet){/*向自己发送ID和名称*/auto pmsg = CreateIDNameLogin();ZinxKernel::Zinx_SendOut(*pmsg, *m_pProto);/*向自己发送周围玩家的位置*/pmsg = CreataSrdPlayers();ZinxKernel::Zinx_SendOut(*pmsg, *m_pProto);/*向周围玩家发送自己的位置*/auto srd_list = world.GetSrdPlayers(this);for (auto single : srd_list){pmsg = CreateSelfPostion();auto pRole = dynamic_cast<GameRole *>(single);// 注意第二个参数ZinxKernel::Zinx_SendOut(*pmsg, *(pRole->m_pProto));}}return bRet;
}void GameRole::Fini()
{/*向周围玩家发送下线消息*/auto srd_list = world.GetSrdPlayers(this);for (auto single : srd_list){auto pMsg = CreateIDNameLogoff();auto pRole = dynamic_cast<GameRole *>(single);ZinxKernel::Zinx_SendOut(*pMsg, *(pRole->m_pProto));}world.DelPlayer(this);
}

2.3.3 玩家上线时的处理:新客户端连接后,向自己发送ID和名称

GameMsg * GameRole::CreateIDNameLogin()
{pb::SyncPid *pmsg = new pb::SyncPid();pmsg->set_pid(iPid);pmsg->set_username(szName);GameMsg *pRet = new GameMsg(GameMsg::MSG_TYPE_LOGIN_ID_NAME, pmsg);return pRet;
}

2.3.4 新客户端连接后,向其发送周围玩家的位置

  • 设置protobuf类型消息的repeated类型
    • add_XXXX函数
    • 调用后,会向当前消息添加一个数组成员,返回数组成员的指针
GameMsg * GameRole::CreataSrdPlayers()
{pb::SyncPlayers *pMsg = new pb::SyncPlayers();auto srd_list = world.GetSrdPlayers(this);//周围玩家有多个for (auto single : srd_list){auto pPlayer = pMsg->add_ps();auto pRole = dynamic_cast<GameRole *>(single);//设置到遍历到的玩家的信息pPlayer->set_pid(pRole->iPid); pPlayer->set_username(pRole->szName);//把子消息挂到父消息里面,并返回子消息的指针auto pPostion = pPlayer->mutable_p();pPostion->set_x(pRole->x);pPostion->set_y(pRole->y);pPostion->set_z(pRole->z);pPostion->set_v(pRole->v);}GameMsg *pret = new GameMsg(GameMsg::MSG_TYPE_SRD_POSTION, pMsg);return pret;
}

2.3.5 新客户端连接后,向周围玩家发送其位置

message BroadCast{int32 Pid=1;int32 Tp=2;/*根据Tp不同,Broadcast消息会包含:聊天内容(Content)或初始位置(P)或新位置P*/oneof Data{string Content=3;Position P=4;/*ActionData暂时预留*/int32 ActionData=5;}string Username=6;
}
GameMsg * GameRole::CreateSelfPostion()
{pb::BroadCast *pMsg = new pb::BroadCast();pMsg->set_pid(iPid);pMsg->set_username(szName);pMsg->set_tp(2); //客户端决定的要设置成2auto pPosition = pMsg->mutable_p();pPosition->set_x(x);pPosition->set_y(y);pPosition->set_z(z);pPosition->set_v(v);GameMsg *pret = new GameMsg(GameMsg::MSG_TYPE_BROADCAST, pMsg);return pret;
}

2.3.6 游戏测试:

在程序安装文件夹下进入控制台运行客户端

client.exe 192.168.111.135 8899

鼠标右键控制V

在这里插入图片描述

2.3.7 世界聊天

在这里插入图片描述

2.3.7.1 创建广播
class GameRole :public Irole,public Player
{void ProcTalkMsg(std::string _content);void ProcMoveMsg(float _x, float _y, float _z, float _v);void ViewAppear(GameRole *_pRole);void ViewLost(GameRole *_pRole);GameMsg *CreateTalkBroadCast(std::string _content);
};
2.3.7.2 发送给所有人
void GameRole::ProcTalkMsg(std::string _content)
{//发给所有人auto role_list = ZinxKernel::Zinx_GetAllRole();for (auto pRole : role_list){auto pGameRole = dynamic_cast<GameRole *>(pRole);auto pmsg = CreateTalkBroadCast(_content);ZinxKernel::Zinx_SendOut(*pmsg, *(pGameRole->m_pProto));}
}
  • MSG_TYPE_BROADCAST 200号消息
GameMsg * GameRole::CreateTalkBroadCast(std::string _content)
{pb::BroadCast *pmsg = new pb::BroadCast();pmsg->set_pid(iPid);pmsg->set_username(szName);pmsg->set_tp(1);pmsg->set_content(_content);GameMsg *pRet = new GameMsg(GameMsg::MSG_TYPE_BROADCAST, pmsg);return pRet;
}

2.4 玩家移动处理

  • 广播新位置给周围玩家
  • 若跨网格,视野切换(获取移动前周围玩家S1,获取移动后的周围 玩家S2)
    • 新邻居:互相能看见({x|x 属于S2 && x 不属于S1})—>发送200号消息
    • 旧邻居:互相看不见({x|x属于S1 && x不属于S2})—>201号消息
UserData * GameRole::ProcMsg(UserData & _poUserData)
{GET_REF2DATA(MultiMsg, input, _poUserData);for (auto single : input.m_Msgs){/*测试:打印消息内容*/cout << "type is" << single->enMsgType << endl;cout << single->pMsg->Utf8DebugString() << endl;auto NewPos = dynamic_cast<pb::Position *>(single->pMsg);switch (single->enMsgType){case GameMsg::MSG_TYPE_CHAT_CONTENT:ProcTalkMsg(dynamic_cast<pb::Talk *>(single->pMsg)->content());break;case GameMsg::MSG_TYPE_NEW_POSTION:ProcMoveMsg(NewPos->x(), NewPos->y(),NewPos->z(),NewPos->v());break;default:break;}}return nullptr;
}

2.4.1 视野出现和消失

void GameRole::ViewAppear(GameRole * _pRole)
{/*向自己发参数的200消息*/auto pmsg = _pRole->CreateSelfPostion();ZinxKernel::Zinx_SendOut(*pmsg, *m_pProto);/*向参数玩家发自己的200消息*/pmsg = CreateSelfPostion();ZinxKernel::Zinx_SendOut(*pmsg, *(_pRole->m_pProto));
}void GameRole::ViewLost(GameRole * _pRole)
{/*向自己发送参数玩家的201消息*/auto pmsg = _pRole->CreateIDNameLogoff();ZinxKernel::Zinx_SendOut(*pmsg, *m_pProto);/*向参数玩家发自己的201消息*/pmsg = CreateIDNameLogoff();ZinxKernel::Zinx_SendOut(*pmsg, *(_pRole->m_pProto));
}

2.4.2 跨网格处理

  1. 获取原来的邻居s1。
  2. 摘出旧格子,更新坐标,添加新格子,获取新邻居s2。
  3. 遍历s2,若元素不属于s1, 视野出现。
  4. 遍历s1,若元素不属于s2,视野消失。
void GameRole::ProcMoveMsg(float _x, float _y, float _z, float _v)
{/*1.跨网格处理*//*获取原来的邻居s1*/auto s1 = world.GetSrdPlayers(this);/*摘出旧格子*/world.DelPlayer(this);/*更新坐标,添加新格子,获取新邻居s2*/x = _x;y = _y;z = _z;v = _v;world.AddPlayer(this);auto s2 = world.GetSrdPlayers(this);/*遍历s2,若元素不属于s1, 视野出现*/for (auto single_player : s2){if (s1.end() == find(s1.begin(), s1.end(), single_player)){//视野出现ViewAppear(dynamic_cast<GameRole *>(single_player));}}/*遍历s1,若元素不属于s2,视野消失*/for (auto single_player : s1){if (s2.end() == find(s2.begin(), s2.end(), single_player)){//视野消失ViewLost(dynamic_cast<GameRole *>(single_player));}}

2.4.3 广播新位置给周围玩家

	/*2.广播新位置给周围玩家*/	//遍历周围玩家发送/*向周围玩家发送自己的位置*/auto srd_list = world.GetSrdPlayers(this);for (auto single : srd_list){//组成待发送的报文pb::BroadCast *pMsg = new pb::BroadCast();auto pPos = pMsg->mutable_p();pPos->set_x(_x);pPos->set_y(_y);pPos->set_z(_z);pPos->set_v(_v);pMsg->set_pid(iPid);pMsg->set_tp(4);pMsg->set_username(szName);auto pRole = dynamic_cast<GameRole *>(single);//封装成消息发出去ZinxKernel::Zinx_SendOut(*(new GameMsg(GameMsg::MSG_TYPE_BROADCAST, pMsg)), *(pRole->m_pProto));}
}

2.5 随机出生

设计: GameRole对象创建时,随机生成合理范围内的坐标。

生成随机数的方法:std::default_random_engine

详细资料:http://www.cplusplus.com/reference/random/default_random_engine/

  • 构造函数参数用于指定种子(一般使用当前时间)
  • ()操作符返回随机数(无符号整形)
static default_random_engine random_engine(time(NULL));GameRole::GameRole()
{szName = random_name.GetName();x = 100 + random_engine() % 50;z = 100 + random_engine() % 50;
}

2.7 退出程序

  • 玩家全部退出后20s后服务器退出
    创建定时任务:20秒周期,超时处理–》退出框架
    添加时机:玩家fini的时候若总玩家 == 1
    摘除时机:玩家init的时候若总玩家 == 0

2.7.1 定时器设计

class ExitTimer :public TimerOutProc {// 通过 TimerOutProc 继承virtual void Proc() override{ZinxKernel::Zinx_Exit();}virtual int GetTimeSec() override{return 20;}
};
static ExitTimer g_exit_timer;

2.7.2 最后一个玩家,启动定时器

void GameRole::Fini()
{/*向周围玩家发送下线消息*/auto srd_list = world.GetSrdPlayers(this);for (auto single : srd_list){auto pMsg = CreateIDNameLogoff();auto pRole = dynamic_cast<GameRole *>(single);ZinxKernel::Zinx_SendOut(*pMsg, *(pRole->m_pProto));}world.DelPlayer(this);/*判断是否是最后一个玩家--->起定时器*/if (ZinxKernel::Zinx_GetAllRole().size() <= 1){//起退出定时器TimerOutMng::GetInstance().AddTask(&g_exit_timer);}
}

2.7.3 初始化的完善

bool GameRole::Init()
{if (ZinxKernel::Zinx_GetAllRole().size() <= 0){TimerOutMng::GetInstance().DelTask(&g_exit_timer);}
......
}

2.7.4 主函数的完善

int main()
{ZinxKernel::ZinxKernelInit();/*添加监听通道*/ZinxKernel::Zinx_Add_Channel(*(new ZinxTCPListen(8899, new GameConnFact())));// 添加定时器ZinxKernel::Zinx_Add_Channel(*(new ZinxTimerChannel()));ZinxKernel::Zinx_Run();ZinxKernel::ZinxKernelFini();
}

2.8 随机姓名的设计与实现

设计: 在文件中存储一定量的常用姓和名,GameRole创建时随机组合姓名

线性表存姓和名组成的线性表
取名字:随机取姓,随机取名 
还名字:尾部追加姓或名
读姓文件的同时读名文件,边追加节点
  • 设计数据结构存储随机姓名池,进程启动时构造
  • 生成随机名称:取第随机个姓,取第随机个名
  • 进程启动时读取文件构建姓名池
  • 玩家类构造时从姓名池获取姓名
  • 玩家类析构时释放姓名回姓名池
    在这里插入图片描述

2.8.1 姓和常用名的定义

//姓和    名组成的线性表
class FirstName {
public:std::string m_first;//使用vectorstd::vector<std::string> m_last_list;
};class RandomName
{std::vector<FirstName *> m_pool;
public:RandomName();std::string GetName();void Release(std::string _name); //还名字void LoadFile(); //通过文件来构造virtual ~RandomName();
};

2.8.2 取名字

static default_random_engine rand_engine(time(NULL));std::string RandomName::GetName()
{//取姓auto num = rand_engine() % m_pool.size();auto first = m_pool[num]->m_first;//取名auto last = m_pool[num]->m_last_list[rand_engine() % m_pool[num]->m_last_list.size()];//特殊情况:若本姓的所有名都取完了,把姓删掉if (m_pool[num]->m_last_list.size() <= 0){delete m_pool[num];m_pool.erase(m_pool.begin() + num);}return first + " " + last;
}

2.8.3 还名字

void RandomName::Release(std::string _name)
{//分割名字,得到姓和名auto space_pos = _name.find(" ", 0);auto first = _name.substr(0, space_pos);auto last = _name.substr(space_pos + 1, _name.size() - space_pos - 1);bool found = false;for (auto first_name : m_pool){if (first_name->m_first == first) // 先找姓{found = true;first_name->m_last_list.push_back(last); // 把名字追加到后面break;}}if (false == found) //如果没找到姓,也就是这个姓的名字用完了,当前的名字是第一个归还的{auto first_name = new FirstName(); //创建一个新的姓first_name->m_last_list.push_back(last); // 添加名字m_pool.push_back(first_name);}
}

2.8.4 读取文件组建姓名的线性表

void RandomName::LoadFile()
{ifstream first("random_first.txt");ifstream last("random_last.txt");//读取所有名字组成一个线性表string last_name;vector<string> tmp;while (getline(last, last_name)){tmp.push_back(last_name);}//读取所有姓,创建姓名池节点,拷贝名字组成的线性表string first_name;while (getline(first, first_name)){auto first_name_list = new FirstName();first_name_list->m_first = first_name;first_name_list->m_last_list = tmp;m_pool.push_back(first_name_list);}
}

在这里插入图片描述

3 架构回顾

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/139351.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.docker linux离线环境安装 20.1.0.12

目录 概述下载解压docker 卸载docker 安装检查安装环境常用命令结束 概述 docker离线环境安装 20.1.0.12 , centos 7.x 下载 安装包下载 解压 [roothadoop01 soft]# unzip docker_20_1_0_12.zip [roothadoop01 soft]# cd docker_20_1_0_12 [roothadoop01 docker_20_1_0_1…

数据结构:树的基本概念(二叉树,定义性质,存储结构)

目录 1.树1.基本概念1.空树2.非空树 2.基本术语1.结点之间的关系描述2.结点、树的属性描述3.有序树、无序树4.森林 3.树的常考性质 2.二叉树1.基本概念2.特殊二叉树1.满二叉树2.完全二叉树3.二叉排序树4.平衡二叉树 3.常考性质4.二叉树的存储结构1.顺序存储2.链式存储 1.树 1.…

亚马逊云AI应用科技创新下的Amazon SageMaker使用教程

目录 Amazon SageMaker简介 Amazon SageMaker在控制台的使用 模型的各项参数 pytorch训练绘图部分代码 Amazon SageMaker简介 亚马逊SageMaker是一种完全托管的机器学习服务。借助 SageMaker&#xff0c;数据科学家和开发人员可以快速、轻松地构建和训练机器学习模型&#…

ARM寄存器及功能介绍/R0-R15寄存器

1、ARM 寄存器组介绍 ARM 处理器一般共有 37 个寄存器&#xff0c;其中包括&#xff1a; &#xff08;1&#xff09; 31 个通用寄存器&#xff0c;包括 PC&#xff08;程序计数器&#xff09;在内&#xff0c;都是 32 位的寄存器。 &#xff08;2&#xff09; 6 个状态寄存器…

服务号如何升级订阅号

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;首先我们要知道服务号和订阅号有什么区别。服务号侧重于对用户进行服务&#xff0c;每月可推送4次&#xff0c;每次最多8篇文章&#xff0c;发送的消息直接显示在好友列表中。订阅号更侧重于信息传…

通信信道:无线信道中衰落的类型和分类

通信信道&#xff1a;无线信道中衰落的类型和分类 在进行通信系统仿真时&#xff0c;简单的情况下选择AWGN信道&#xff0c;但是AWGN信道和真是通信中的信道相差甚远&#xff0c;所以需要仿真各种其他类型的信道&#xff0c;为了更清楚理解仿真信道的特点&#xff0c;首先回顾…

linux安装并配置git连接github

git安装 sudo apt-get install git git信息配置 git config --global uer.name "yourname" git config --global user.email "youremail" 其中&#xff0c;yourname是你在github上配置的用户名&#xff0c;youremail是你在github上设置的邮箱 查看git…

Apinto 网关进阶教程,使用 API Mock 生成模拟数据

什么是 API Mock &#xff1f; API Mock 是一种技术&#xff0c;它允许程序员在不依赖后端数据的情况下&#xff0c;模拟 web服务器端 API 的响应。通常使用 API Mock 来测试前端应用程序&#xff0c;而无需等待后端程序构建完成。API Mock 可以模拟任何 HTTP 请求方法&#x…

谷歌提出 AGI 完整路线图:目前 ChatGPT 只处于 AGI 的第一阶段

本心、输入输出、结果 文章目录 谷歌提出 AGI 完整路线图:目前 ChatGPT 只处于 AGI 的第一阶段前言谷歌 DeepMind 发布 AGI 分级框架发展 AGI 必须遵循6个基本原则什么是AGI图灵测试详解六大原则AGI 的五大发展过程阶段原文参考弘扬爱国精神谷歌提出 AGI 完整路线图:目前 Cha…

LeetCode(7)买卖股票的最佳时机【数组/字符串】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 121. 买卖股票的最佳时机 1.题目 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票…

基于Python+Django的图书管理系统

项目介绍 图书是人类文明传播的一个重要方式&#xff0c;很多历史悠久的文明都是通过图书来进行传递的&#xff0c;虽然随着时代的进步电子信息技术发展很快&#xff0c;但是纸质图书的地位仍然是非常稳固的&#xff0c;为了能够让知识拥有更加快捷方便的传递方式我们开发了本…

CSS注入的四种实现方式

目录 CSS注入窃取标签属性数据 简单的一个实验&#xff1a; 解决hidden 方法1&#xff1a;jsnode.js实现 侧信道攻击 方法2&#xff1a;对比波兰研究院的方案 使用兄弟选择器 方法3&#xff1a;jswebsocket实现CSS注入 实验实现&#xff1a; 方法4&#xff1a;window…

centos7下安装主从仲裁三台结构的MongoDB 7.0.4

安装手册英文版在这里 https://www.mongodb.com/docs/v7.0/tutorial/install-mongodb-on-red-hat/ 我的安装过程 1&#xff09;基础安装 1、创建 /etc/yum.repos.d/mongodb-org-7.0.repo文件 下面的代码复制到这个文件中&#xff0c;保存 [mongodb-org-7.0] nameMongoDB Re…

大模型+人形机器人,用AI唤起钢筋铁骨

《经济参考报》11月8日刊发文章《多方布局人形机器人赛道,智能应用前景广》。文章称&#xff0c;工信部日前印发的《人形机器人创新发展指导意见》&#xff0c;按照谋划三年、展望五年的时间安排&#xff0c;对人形机器人创新发展作了战略部署。 从开发基于人工智能大模型的人…

【CASS精品教程】cass3d 11.0加载超大影像、三维模型、点云数据

CAD2016+CASS11.0(内置3d)下载与安装: 【CASS精品教程】CAD2016+CASS11.0安装教程(附CASS11.0安装包下载)https://geostorm.blog.csdn.net/article/details/132392530 一、cass11.0 3d支持的数据 cass11.0中的3d模块增加了多种数据的支持,主要有: 1. 三维模型 点击…

CSS实现透明度效果的两种方法—— opacity 和 rgba()

在实际开发过程中&#xff0c;为了给用户呈现一些效果&#xff0c;我们需要控制元素的透明度。CSS 提供了 opacity 属性和 rgba() 函数给我们控制透明度&#xff0c;接下来通过一个例子来感受一下两种方法的区别。 <style>.transparentBox {display: inline-block;width…

AI驱动的软件测试,何时可以信赖?

综合编译&#xff5c;TesterHome社区 作者&#xff5c;Yuliya Vasilko&#xff0c;数据工程师 以下为作者观点&#xff1a; 越来越多的组织转向人工智能&#xff08;AI&#xff09;驱动的测试解决方案&#xff0c;以简化质量保证流程并提高软件可靠性。 随着对人工智能的依赖程…

功能案例 -- 拖拽上传文件,生成缩略图

直接看效果 实现代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>拖拽上传文件</title>&l…

低价寄快递寄件微信小程序 实际商用版,对接了低价快递渠道,运营平台赚取差价,支持市面上全部主流快递

盈利模式 快递代下CPS就是用户通过线上的渠道&#xff08;快递小程序&#xff09;&#xff0c;线上下单寄快递来赚取差价&#xff0c;例如你的成本价是5元&#xff0c;你在后台比例设置里面设置 首重利润是1元&#xff0c;续重0.5元&#xff0c;用户下1kg的单页面显示的就是6元…

ElasticSearch知识点

什么是ElasticSearch ElasticSearch: 智能搜索&#xff0c;分布式的搜索引擎&#xff0c;是ELK的一个非常完善的产品&#xff0c;ELK代表的是: E就是ElasticSearch&#xff0c;L就是Logstach&#xff0c;K就是kibana Elasticsearch是一个建立在全文搜索引擎 Apache Lucene基础…