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

大家好,我是一位初一的编程爱好者。今天,我向大家介绍一个我自制的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;//为了防止在遍历容器处理消息的过程中添加或删除Processor
};

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())//将等待中的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.bDeleted;
		}), mMatchProcessors.end());//删除等待被删除的MatchPrecessor
	mSearchProcessors.erase(std::remove_if(mSearchProcessors.begin(), mSearchProcessors.end(), [](const Processor& p) {
    
    
		return p.bDeleted;
		}), 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), 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一份副本,程序结束后再释放,这样就不会出现内存访问错误的问题了。

运行结果

运行结果
运行结果
运行结果
运行结果

猜你喜欢

转载自blog.csdn.net/qq_54121864/article/details/121445699