大家好,我是一位初一的编程爱好者。今天,我向大家介绍一个我自制的C++字符串消息处理器类。看到标题,大家可能有些疑惑,字符串消息处理器怎么能和游戏编程扯上关系呢?其实,很多游戏中都需要用到消息处理,特别是一些人机交互的游戏。此时,如果没有一个类进行管理,是非常不方便的。其实,具体到我们以前的游戏框架中,Actor就可以看作一个消息处理器,Component相当于这里的Processor。当然,这是一个单独的项目,没有使用SDL,但它的原理和思想值得借鉴。

项目介绍

在实际开发中,我们经常会用到消息处理,即用户发来一些消息,让我们进行处理,典型的例子是QQ机器人。如果简单地用几个if,会导致程序非常混乱,可读性差。这时候,就要新建一个类对这些消息进行管理。当然,我只是实现了一个最简单的版本,还有很多功能,大家可以自行完善。其实,这个类功能和实现都很简单,但我通过这个类,开发了一个人机聊天的项目,比较有意思。
项目名称:C++字符串消息处理器类
开发环境:Visual Studio 2022
C++标准:C++11及以上

代码分析及实现

选择匹配方式

对于字符串消息,有多种处理方法,但使用最广泛的还是正则表达式。这个项目就是基于正则表达式匹配的。

基本原理

其实,消息处理器的原理很简单,就是对混乱的if语句进行封装,把每个正则和对应的处理函数存储到vector容器中,然后处理消息的时候,遍历容器进行匹配就行了。别看原理简单,它能大大改善代码质量。这里要提醒大家注意一点:这里的消息处理器使用的是std::function模板类,这个类可以封装一个lambda表达式或仿函数(一个重载了operator()的类)。一定要注意一点,它可以封装仿函数!这使得外部的参数可以传入消息处理器中,消息处理器也可以向外部传递信息,这一点是在实际开发中非常有用的!这样说可能不太好理解,在后面的机器人代码中,我就专门写了一个功能来说明并测试这种写法。

CMessageProcessor类代码

CMessageProcessor.h:

#pragma once
#include<functional>
#include<regex>
#include<vector>
namespace MyStd
{class CMessageProcessor{public:using ProcessorFunction = std::function<void(const std::smatch&)>;class Processor{friend class CMessageProcessor;public:std::regex regex;ProcessorFunction function;std::string regexStr;Processor(const std::string& reg, const ProcessorFunction& func);private:bool isDeleted;};CMessageProcessor();void AddMatchProcessor(const std::string& regex, const ProcessorFunction& func);void RemoveMatchProcessor(const size_t& index);void AddSearchProcessor(const std::string& regex, const ProcessorFunction& func);void RemoveSearchProcessor(const size_t& index);size_t ProcessMessage(const std::string& message);const std::vector<Processor>& GetMatchProcessorsList()const noexcept;const std::vector<Processor>& GetSearchProcessorsList()const noexcept;private:std::vector<Processor> mMatchProcessors;std::vector<Processor> mSearchProcessors;std::vector<Processor> mPendingMatchProcessors;std::vector<Processor> mPendingSearchProcessors;bool mIsProcessingMessage;//为了防止在遍历容器处理消息的过程中添加或删除Processor};
}

CMessageProcessor.cpp:

#include "CMessageProcessor.h"
namespace MyStd
{CMessageProcessor::CMessageProcessor() :mIsProcessingMessage(false){}void CMessageProcessor::AddMatchProcessor(const std::string& regex, const ProcessorFunction& func){if (mIsProcessingMessage)mPendingMatchProcessors.push_back(Processor(regex, func));elsemMatchProcessors.push_back(Processor(regex, func));}void CMessageProcessor::RemoveMatchProcessor(const size_t& index){if (mIsProcessingMessage)mMatchProcessors.at(index).isDeleted = true;elsemMatchProcessors.erase(mMatchProcessors.begin() + index);}void CMessageProcessor::AddSearchProcessor(const std::string& regex, const ProcessorFunction& func){if (mIsProcessingMessage)mPendingSearchProcessors.push_back(Processor(regex, func));elsemSearchProcessors.push_back(Processor(regex, func));}void CMessageProcessor::RemoveSearchProcessor(const size_t& index){if (mIsProcessingMessage)mSearchProcessors.at(index).isDeleted = true;elsemSearchProcessors.erase(mMatchProcessors.begin() + index);}size_t CMessageProcessor::ProcessMessage(const std::string& message){std::smatch m;size_t count = 0;mIsProcessingMessage = true;for (const auto& iter : mMatchProcessors){if (std::regex_match(message, m, iter.regex)){++count;iter.function(m);}}for (const auto& iter : mSearchProcessors){if (std::regex_search(message, m, iter.regex)){++count;iter.function(m);}}mIsProcessingMessage = false;while (!mPendingMatchProcessors.empty())//将等待中的MatchProcessor添加到正式容器中{mMatchProcessors.push_back(mPendingMatchProcessors.back());mPendingMatchProcessors.pop_back();}while (!mPendingSearchProcessors.empty())//将等待中的SearchProcessor添加到正式容器中{mSearchProcessors.push_back(mPendingSearchProcessors.back());mPendingSearchProcessors.pop_back();}mMatchProcessors.erase(std::remove_if(mMatchProcessors.begin(), mMatchProcessors.end(), [](const Processor& p) {return p.isDeleted;}), mMatchProcessors.end());//删除等待被删除的MatchPrecessormSearchProcessors.erase(std::remove_if(mSearchProcessors.begin(), mSearchProcessors.end(), [](const Processor& p) {return p.isDeleted;}), mSearchProcessors.end());//删除等待被删除的SearchPrecessorreturn count;}const std::vector<CMessageProcessor::Processor>& CMessageProcessor::GetMatchProcessorsList() const noexcept{return mMatchProcessors;}const std::vector<CMessageProcessor::Processor>& CMessageProcessor::GetSearchProcessorsList() const noexcept{return mSearchProcessors;}CMessageProcessor::Processor::Processor(const std::string& reg, const ProcessorFunction& func) :regex(reg), regexStr(reg), function(func), isDeleted(false){}
}

代码的原理很简单,也很好理解,这里就不做过多介绍,只提一点:因为在遍历容器的过程中不能添加或删除元素,所以要设置mIsProcessingMessage变量,如果在遍历过程中添加或删除指令,需要保存到等待队列中。下面,我们将重点介绍用这个类做一个人机对话的机器人。

机器人代码

先上代码:

#include<iostream>
#include"CMessageProcessor.h"
using namespace MyStd;
using namespace std;
class Main
{public:CMessageProcessor systemProcessor, userProcessor;Main(){systemProcessor.AddMatchProcessor("添加指令\\s*(.*)---(.*)", [this](const smatch& m) {class Func{private:string s;public:Func(const string& msg) :s(msg) {}void operator()(const smatch& m){cout << s << endl;}};userProcessor.AddMatchProcessor(m.str(1), Func(m.str(2)));});systemProcessor.AddMatchProcessor("删除指令\\s*([0-9]+)", [this](const smatch& m) {const size_t size = stoul(m.str(1));if (size < userProcessor.GetMatchProcessorsList().size())userProcessor.RemoveMatchProcessor(size);elsecout << "没有该指令!" << endl;});systemProcessor.AddMatchProcessor("指令列表", [this](const smatch& m) {const auto& vs = systemProcessor.GetMatchProcessorsList();std::cout << "系统指令(共" << vs.size() << "个)" << (vs.size() > 0 ? ":" : "") << endl;for (size_t i = 0; i < vs.size(); i++){cout << i << '\t' << vs[i].regexStr << endl;}const auto& vu = userProcessor.GetMatchProcessorsList();std::cout << "用户自定义指令(共" << vu.size() << "个)"<< (vu.size() > 0 ? ":" : "") << endl;for (size_t i = 0; i < vu.size(); i++){cout << i << '\t' << vu[i].regexStr << endl;}});class MyFunction{private:int num;public:MyFunction() :num(0) {}void SetNum(int n){n = num;}int GetNum()const{return num;}void operator()(const std::smatch& m)//仿函数部分{cout << "请选择(0.存储数据;1.显示数据):";bool choose;cin >> choose;if (choose)cout << "你输入的消息:" << m.str(0) << "\nnum的值:" << num << endl;else{cout << "请输入一个整数:";int a;cin >> a;num = a;}}}object;systemProcessor.AddMatchProcessor("仿函数测试", object);//将仿函数作为消息处理函数可以实现使用外部信息systemProcessor.ProcessMessage("指令列表");string str;while (getline(cin, str)){const size_t num = systemProcessor.ProcessMessage(str) + userProcessor.ProcessMessage(str);cout << "共有" << num << "个消息处理函数被调用!" << endl;}}
};
int main()
{Main a;return 0;
}   {const size_t num = systemProcessor.ProcessingMessage(str) + userProcessor.ProcessingMessage(str);cout << "共有" << num << "个消息处理函数被调用!" << endl;}for (auto p : v)delete p;}
};
int main()
{MyClass a;return 0;
}

通过大致看代码,大家肯定发现,这个机器人没有任何自定义的聊天功能,只能通过几个默认指令添加自定义指令。其实,这样做有很大的好处,因为这样实现了动态的添加指令,想加一个新指令,无需重新编译。下面,我们简单介绍一下代码。
首先,在MyClass类中,我们新建了两个消息处理器,分别是systemProcessor和userProcessor,分别存储系统指令和自定义指令(为了防止用户误删系统指令,所以把它们分开)。接着,系统指令添加了几个默认元素,自定义指令没有添加任何元素。接下来,我们就可以通过用户不断输入来实现对话了。这里重点讲一下添加指令的代码,这里要注意了:相信很多人一开始都想直接在内层添加一个lambda输出m.str(2),但你们忽略了一点:内层lambda在调用的过程中,外层lambda并不会被调用,所以说,如果这样做,会输出随机值(其它的内存里的内容),甚至导致程序崩溃等严重后果!这也是lambda中捕获外部变量中一个非常容易犯的错误。如果非得用lambda,解决方法是:将m.str(2)再new一份副本,程序结束后再释放,这样就不会出现内存访问错误的问题了。但这样使代码的可读性大大降低,不小心的话还容易出问题。这里就体现出仿函数的重要性了:我们使用仿函数来当作消息处理器,本身自带要发送的信息,这样就完美解决了这个问题。

运行结果




C++游戏编程教程(六)——C++字符串消息处理器类相关推荐

  1. iPhone游戏编程教程一步步教你游戏开发

    这是此系列教程的第一部分,我将从最基本开始教大家怎样编写一个iPhone游戏.众所周知,OpenGL和Quartz的学习不是那么简单的.本教程将简化开发的过程而不会使用这两种技术.我们将使用UIIma ...

  2. C++游戏编程教程(二)

    上一篇博客,我们讲了SDL环境的配置和基本程序框架.这篇博客,我本打算讲一下Game类的代码,但突然想到很多API都没有介绍,所以这篇博客先来介绍一下SDL的基础知识和游戏运行框架,Game类的实现下 ...

  3. 人人都能学会的python编程教程3:字符串和编码

    字符串 在python3中已经全面支持中文. 由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码.当Python解释器读取源 ...

  4. Visual C++游戏编程基础之利用鼠标消息实现贴图

    一.鼠标键入消息 1.WM_LBUTTONDBLCLK       双击鼠标左键: 2.WM_LBUTTONDOWN         单击鼠标左键: 3.WM_LBUTTONUP            ...

  5. C++游戏编程教程(四)

    这篇博客,我们说一下Actor.Component和DrawComponent类的代码.代码都很简单,所以写到了一篇里. Actor 这个类是角色类,保存了角色的一些基本信息. 代码 Actor.h: ...

  6. C++游戏编程教程(七)——改进飞机大战游戏

    注:在本篇博客中,对上一篇博客的飞机大战游戏进行了完善,但有很多细微的修改,由于篇幅原因,没有把所有代码列出来,大家需要仔细阅读,否则可能漏掉一些地方,导致编译错误或产生bug. PS:如果大家有什么 ...

  7. 3D游戏编程 作业六 打飞碟改进

    前言 这次的任务是实现物理学运动,结果我发现在上次作业中我全给实现了. 所以这次只要简单实现adapter就行了. model脚本:github 原版代码:上次作业 一.任务要求 二. 项目展示 我的 ...

  8. VB+DX游戏编程教程 第二话:点的艺术

    本博客所有内容均来源于  http://www.uoyo.net     . 高手还请多多包涵. 经过我们上一节的学习.我们已经可以初始化一个D3D设备了.好的,我们先来回顾一下这个步骤,毕竟这是我们 ...

  9. 人人都能学会的python编程教程(基础篇)完整版

    人人都能学会的python编程教程1:第一行代码 人人都能学会的python编程教程2:数据类型和变量 人人都能学会的python编程教程3:字符串和编码 人人都能学会的python编程教程4:关系运 ...

最新文章

  1. 谷歌无人车之父刚推出的“无人驾驶入门”课,到底能学到啥?
  2. xsy 1836 - Shop
  3. python获取文件夹下文件_Python获取目录下的所有文件
  4. apache_php_tomcat基于主机名的多虚拟主机整合笔记
  5. c++学习笔记之运算符的重载
  6. 使用C#删除一个字符串数组中的空字符串
  7. php 商品显示,php – WooCommerce仅显示购买的商品
  8. 第 6-4 课:MyBatis 核心和面试题(上)
  9. jq阻止事件冒泡(点击子级不触发父级)的两种方法
  10. eclipse启动web项目时,spring会初始化两次
  11. IT男几时而立(下)
  12. redis php 性能测试工具,Php-Redis安装测试笔记
  13. 键盘盲打练习打字软件 v6.30绿色版
  14. Spring源码下载并导入Idea
  15. Midas GTS NX To 3DEC网格转换程序
  16. 面试题28:JS实现继承有几种方式
  17. 2022-04-13 分布式任务调度Quartz
  18. PgMP: Program Management Professional Exam Study Guide
  19. pptpd mysql_安装pptpd脚本
  20. java实现记账本功能_java基础实战项目一:实现家庭记账本的简易记账功能

热门文章

  1. 【2021-11】 CKAD 17
  2. linux的环境变量设置
  3. java实现MD5,SHA256,AES
  4. 【转载】SAP各模块字段与表的对应关系
  5. 目标跟踪心得篇二:ByteTrack原理及其在MMTracking上的代码逐行分析(本文顺带分析MMTracking相关难点,后期不在赘述)
  6. 前端面试题(6)图片格式jpg,gif,png-8,png-24的区别,及其各自的使用场景
  7. Mysql数据库回滚操作
  8. Unity学习回忆--3D坦克大战
  9. linux系统时间慢解决方案
  10. 01. 计算机专业关于单位的解释和换算