大家好,我是一位初一的编程爱好者。今天,我向大家介绍一个我自制的C++字符串消息处理器类。看到标题,大家可能有些疑惑,字符串消息处理器怎么能和游戏编程扯上关系呢?其实,很多游戏中都需要用到消息处理,特别是一些人机交互的游戏。此时,如果没有一个类进行管理,是非常不方便的。其实,具体到我们以前的游戏框架中,Actor就可以看作一个消息处理器,Component相当于这里的Processor。当然,这是一个单独的项目,没有使用SDL,但它的原理和思想值得借鉴。
项目介绍
在实际开发中,我们经常会用到消息处理,即用户发来一些消息,让我们进行处理,典型的例子是QQ机器人。如果简单地用几个if,会导致程序非常混乱,可读性差。这时候,就要新建一个类对这些消息进行管理。当然,我只是实现了一个最简单的版本,还有很多功能,大家可以自行完善。其实,这个类功能和实现都很简单,但我通过这个类,开发了一个人机聊天的项目,比较有意思。 项目名称:C++字符串消息处理器类 开发环境:Visual Studio 2022 C++标准:C++11及以上
代码分析及实现
选择匹配方式
对于字符串消息,有多种处理方法,但使用最广泛的还是正则表达式。这个项目就是基于正则表达式匹配的。
基本原理
其实,消息处理器的原理很简单,就是对混乱的if语句进行封装,把每个正则和对应的处理函数存储到vector容器中,然后处理消息的时候,遍历容器进行匹配就行了。别看原理简单,它能大大改善代码质量。这里有一个比较常用的功能,那就是在传递消息的时候,同时传递一个对象到函数里,这样就可以灵活操作了。比如说,一个消息处理器收到消息后想移动位置,但没有对应的Actor,所以无法移动,如果把对象也作为一个参数传递给lambda函数,在函数体内就可以执行了。不过由于时间原因,我没有添加,大家可以根据这个功能,把这个类改成模板类实现。
CMessageProcessor类代码
CMessageProcessor.h:
#pragma once
#include<functional>
#include<regex>
#include<vector>
class CMessageProcessor
{
public:
using ProcessorFunction = std::function<void(const std::smatch&)>;
struct Processor
{
std::regex regex;
ProcessorFunction function;
std::string regexStr;
bool bDeleted;
Processor(const std::string& reg, const ProcessorFunction& func);
};
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 ProcessingMessage(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;
};
CMessageProcessor.cpp:
#include "CMessageProcessor.h"
CMessageProcessor::CMessageProcessor() :mIsProcessingMessage(false)
{
}
void CMessageProcessor::AddMatchProcessor(const std::string& regex, const ProcessorFunction& func)
{
if (mIsProcessingMessage)
mPendingMatchProcessors.push_back(Processor(regex, func));
else
mMatchProcessors.push_back(Processor(regex, func));
}
void CMessageProcessor::RemoveMatchProcessor(const size_t& index)
{
if (mIsProcessingMessage)
mMatchProcessors.at(index).bDeleted = true;
else
mMatchProcessors.erase(mMatchProcessors.begin() + index);
}
void CMessageProcessor::AddSearchProcessor(const std::string& regex, const ProcessorFunction& func)
{
if (mIsProcessingMessage)
mPendingSearchProcessors.push_back(Processor(regex, func));
else
mSearchProcessors.push_back(Processor(regex, func));
}
void CMessageProcessor::RemoveSearchProcessor(const size_t& index)
{
if (mIsProcessingMessage)
mSearchProcessors.at(index).bDeleted = true;
else
mSearchProcessors.erase(mMatchProcessors.begin() + index);
}
size_t CMessageProcessor::ProcessingMessage(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())
{
mMatchProcessors.push_back(mPendingMatchProcessors.back());
mPendingMatchProcessors.pop_back();
}
while (!mPendingSearchProcessors.empty())
{
mSearchProcessors.push_back(mPendingSearchProcessors.back());
mPendingSearchProcessors.pop_back();
}
mMatchProcessors.erase(std::remove_if(mMatchProcessors.begin(), mMatchProcessors.end(), [](const Processor& p) {
return p.bDeleted;
}), mMatchProcessors.end());
mSearchProcessors.erase(std::remove_if(mSearchProcessors.begin(), mSearchProcessors.end(), [](const Processor& p) {
return p.bDeleted;
}), mSearchProcessors.end());
return 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), bDeleted(false)
{
}
代码的原理很简单,也很好理解,这里就不做过多介绍,只提一点:因为在遍历容器的过程中不能添加或删除元素,所以要设置mIsProcessingMessage变量,如果在遍历过程中添加或删除指令,需要保存到等待队列中。下面,我们将重点介绍用这个类做一个人机对话的机器人。
机器人代码
先上代码:
#include<iostream>
#include"CMessageProcessor.h"
#include<vector>
using namespace std;
class MyClass
{
public:
vector<string*>v;
CMessageProcessor systemProcessor, userProcessor;
MyClass()
{
systemProcessor.AddMatchProcessor("添加指令\\s*(.*)---(.*)", [=](const smatch& m) {
string& s=*new string(m.str(2));
v.push_back(&s);
userProcessor.AddMatchProcessor(m.str(1), [=](const smatch& im) {
cout << s << endl;
});
});
systemProcessor.AddMatchProcessor("删除指令\\s*([0-9]+)", [=](const smatch& m) {
const size_t size = stoul(m.str(1));
if (size < userProcessor.GetMatchProcessorsList().size())
userProcessor.RemoveMatchProcessor(size);
else
cout << "没有该指令!" << endl;
});
systemProcessor.AddMatchProcessor("指令列表", [=](const smatch& m) {
const auto& vs = systemProcessor.GetMatchProcessorsList();
std::cout << "系统指令(共" << vs.size() << "个):" << 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() << "个):" << endl;
for (size_t i = 0; i < vu.size(); i++)
{
cout << i << '\t' << vu[i].regexStr << endl;
}
});
string str;
while (getline(cin, str))
{
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中捕获外部变量中一个非常容易犯的错误。解决方法是:将m.str(2)再new一份副本,程序结束后再释放,这样就不会出现内存访问错误的问题了。
运行结果
|