大家好,我是一位初一的编程爱好者。今天,我向大家介绍一个我自制的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));
else
mMatchProcessors.push_back(Processor(regex, func));
}
void CMessageProcessor::RemoveMatchProcessor(const size_t& index)
{
if (mIsProcessingMessage)
mMatchProcessors.at(index).isDeleted = 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).isDeleted = true;
else
mSearchProcessors.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());//删除等待被删除的MatchPrecessor
mSearchProcessors.erase(std::remove_if(mSearchProcessors.begin(), mSearchProcessors.end(), [](const Processor& p) {
return p.isDeleted;
}), mSearchProcessors.end());//删除等待被删除的SearchPrecessor
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), 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);
else
cout << "没有该指令!" << 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一份副本,程序结束后再释放,这样就不会出现内存访问错误的问题了。但这样使代码的可读性大大降低,不小心的话还容易出问题。这里就体现出仿函数的重要性了:我们使用仿函数来当作消息处理器,本身自带要发送的信息,这样就完美解决了这个问题。
运行结果