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

大家好,我是一位初一的编程爱好者。今天,我向大家介绍一个我自制的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一份副本,程序结束后再释放,这样就不会出现内存访问错误的问题了。但这样使代码的可读性大大降低,不小心的话还容易出问题。这里就体现出仿函数的重要性了:我们使用仿函数来当作消息处理器,本身自带要发送的信息,这样就完美解决了这个问题。

运行结果

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值