当前位置: 首页 > ai >正文

【在线五子棋对战】六、项目结构设计 工具模块实现

文章目录

  • Ⅰ. 项目模块划分
  • Ⅱ. 业务处理模块的子模块划分
  • Ⅲ. 项目流程图
      • 玩家角度流程图
      • 服务器角度流程图
  • 前言
  • Ⅰ. 日志宏封装
  • Ⅱ. mysql工具类实现
  • Ⅲ. json工具类实现
  • Ⅳ. string工具类实现
  • Ⅴ. file工具类实现
  • 完整的 util.hpp 头文件代码

在这里插入图片描述

Ⅰ. 项目模块划分

项目的实现,咱们将其划分为三个大模块来进行:

  • 数据管理模块:基于 MySQL 数据库进行用户数据的管理
  • 前端界面模块:基于前端技术实现前端页面(注册、登录、游戏大厅、游戏房间)的动态控制以及与服务器的通信
  • 业务处理模块:搭建 WebSocket 服务器与客户端进行通信,接收请求并进行业务处理

​ 为什么说分为这三大块呢,下面画个图来大概描述一下整个客户端和服务器的通信过程:

在这里插入图片描述

​ 而如果要实现这些功能,那么就需要对业务处理模块再次进行细分为多个模块来实现各个功能。

Ⅱ. 业务处理模块的子模块划分

  • 网络通信模块:基于 websocketpp 库实现 Http & WebSocket 服务器的搭建,提供网络通信功能
  • 会话管理模块:对客户端的连接进行 cookie & session 管理,实现 http 短连接时客户端身份识别功能
  • 在线管理模块:对进入游戏大厅与游戏房间中用户进行管理,提供用户是否在线以及获取用户连接的功能
  • 房间管理模块:为匹配成功的用户创建对战房间,提供实时的五子棋对战与聊天业务功能
  • 用户匹配模块:根据天梯分数不同进行不同层次的玩家匹配,为匹配成功的玩家创建房间并加入房间

Ⅲ. 项目流程图

玩家角度流程图

在这里插入图片描述

服务器角度流程图

在这里插入图片描述

前言

​ 实用工具类模块主要是负责提前实现⼀些项目中会用到的边缘功能代码,提前实现好了就可以在项目中用到的时候直接使用了,主要有五种,下面我们大概来说一下每个的作用:

  • 日志宏:实现程序日志的打印,方便调试
  • mysql_util:数据库的连接、初始化,以及句柄的销毁,语句的执行
  • json_util:封装实现 json 的序列化和反序列化
  • string_util:主要是封装实现字符串分割功能(其实 boost 库中有提供类似接口,但这里主要是为了学习)
  • file_util:主要封装了文件数据的读取功能(对于 html 文件数据进行读取)

💥为了方便管理,接下来的几个工具类,都一并写到 util.hpp 中,所以 makefile 文件为:

.PHONY:gobang
gobang:gobang.cc logger.hpp util.hppg++ -o$@ $^ -std=c++11 -L/usr/lib64/mysql/ -lmysqlclient -ljsoncpp -lboost_system -lpthread.PHONY:clean
clean:rm -f gobang

Ⅰ. 日志宏封装

​ 目的就是为了实现一些宏函数,让它们辅助我们进行日志信息的打印,我们想要的格式像下面这样子:

[2023/6/21 21:28:30 main.c:28] 文件打开失败

​ 也就是这样子:

[时间 出现错误的文件:行号] 错误信息

​ 对于出现错误的文件和行号来说其实不难,因为 C语言 本身就给我们提供了对应的宏,分别是 __FILE____LINE__

​ 而对于时间来说,这得用几个函数来帮忙:

time_t time(NULL); // 获取系统时间戳struct tm* localtime(time_t* t); // 通过系统时间戳参数来获取本地时间的结构体tmchar* strftime(char* buf, int max, char* format, struct tm* tm); // 将结构体tm通过format形式根据max大小存放到buf中去int fprintf(FILE* fp, char* format, ...); // 通过format格式将可变参数写到文件指针所指的文件中去

​ 首先,创建一个 logger.hpp 头文件,里面存放的就是我们的日志宏封装。因为我们要用到宏,那么就得用 stdio.h 头文件,所以要将其包含进来。

​ 接着我们先在 logger.hpp 中写一个简单宏定义:

#ifndef __MY_LOG_H__
#define __MY_LOG_H__
#include <stdio.h>#define LOG(format, ...) fprintf(stdout, "[%s:%d] " format, __FILE__, __LINE__, __VA_ARGS__)#endif

​ 这里的 LOG 就是一个日志宏,它的第一个参数是一个格式化字符串 format,用于指定输出信息的格式,注意 format 只是我们在宏定义常常起的参数名,并不是一个关键字或预定义标识符

LOG 的第二个参数是可变参数列表的一种表示,用 ... 也就是三个点来表示,而 fprintf 函数中的 __VA_ARGS__C语言 中的一个 预处理器宏,也是用于表示一个可变参数列表,当我们调用 LOG 宏的时候,... 中的多个可变参数都会在预处理阶段传递给 __VA_ARGS__

​ 比如说下面的例子:

LOG("%s: %d", "liren", 10);
会在预处理阶段被替换为如下形式:
fprintf(stdout, "[%s:%d] %s: %d", __FILE, __LINE, "liren", 10);

​ 至于具体 __VA_ARGS__... 的区别可以看下面这段理解!

__VA_ARGS__ ... 的区别:

__VA_ARGS__... 都是用来表示可变参数列表的语法元素,但它们的使用方式和作用范围有所不同。

... 是 C99 标准引入的 语法,用于表示函数或宏定义中的可变参数列表。在函数定义或宏定义中,... 必须放在参数列表的最后一个位置,用来表示后面还有一些可变数量的参数。例如,下面是一个使用 ... 表示可变参数的函数定义:

void my_printf(const char* format, ...);

​ 在函数调用时,可以使用类似于 printf 函数的方式传递可变数量的参数,例如:

my_printf("The value of x is %d\n", x);
my_printf("Hello, %s!\n", name);

​ 在这种情况下,编译器会将可变参数列表转换为一个类型为 va_list 的对象,然后可以使用 stdarg.h 中定义的函数和宏如 va_start()vsnprintf() 等来访问和处理这些参数。

__VA_ARGS__ 则是一个预处理器宏,用于表示宏定义中的可变参数列表。在宏定义中,__VA_ARGS__ 可以出现在参数列表的任意位置,用来表示可变数量的参数。例如,下面是一个使用 __VA_ARGS__ 表示可变参数的宏定义:

#define LOG(format, ...) printf(format, __VA_ARGS__)

​ 在这种情况下,预处理器会将 __VA_ARGS__ 展开为一系列逗号分隔的参数,然后将它们传递给宏定义中的 printf 函数进行输出。

​ 总的来说,...__VA_ARGS__ 都是用来表示可变参数列表的语法元素,但是 前者用于函数定义和函数调用中后者只能用于宏定义中。它们的作用和使用方式有所不同,但都可以方便地处理可变数量的参数。

​ 接下来我们再来加入时间等信息,让宏日志更完善一点!一般我们如果想要在宏定义的时候写多行代码,都会使用 do while(0) 语句来配合,涉及到换行的话要使用反斜杠 \ 在语句最后面,下面给出结合打印时间的代码的完善日志宏:

#ifndef __MY_LOG_H__
#define __MY_LOG_H__
#include <stdio.h>
#include <time.h>#define LOG(format, ...) do{\char timebuffer[128];\time_t timestamp = time(NULL);\struct tm* timeinfo = localtime(&timestamp);\strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", timeinfo);\fprintf(stdout, "[%s %s:%d] " format, timebuffer, __FILE__, __LINE__, __VA_ARGS__);\
}while(0)#endif

​ 调用的结果如下:

LOG("%s-%d\n", "liren", 100);
结果:
[2023-06-21 23:17:03 gobang.cc:5] liren-100

​ 这样子就结束了吗❓❓❓当然不是,因为还有 bug,因为如果我们使用 LOG 宏的时候不传可变参数的话,那么预处理时候就会报错,如下所示:

LOG("liren");
编译时候会报错:[liren@VM-8-7-centos source]$ make
g++ -ogobang gobang.cc logger.hpp 
In file included from gobang.cc:1:0:
gobang.cc: In function ‘int main():
logger.hpp:11:86: error: expected primary-expression before ‘)’ tokenintf(stdout, "[%s %s:%d] " format, timebuffer, __FILE__, __LINE__, __VA_ARGS__);\^
gobang.cc:5:5: note: in expansion of macro ‘LOG’LOG("liren");^~~
make: *** [makefile:2: gobang] Error 1

​ 解决这个问题很简单,只需要使用 ##__VA_ARGS__ 来表示展开后的参数列表。这个语法中的 ## 表示将 __VA_ARGS__ 前面的逗号去掉,避免在展开后出现语法错误!需要注意的是,## 的使用在不同的编译器和平台上可能有所不同。在使用 ## 时需要注意平台兼容性和语法规则。

​ 所以修改完代码如下:

#ifndef __MY_LOG_H__
#define __MY_LOG_H__
#include <stdio.h>
#include <time.h>#define LOG(format, ...) do{\char timebuffer[128] = {0};\time_t timestamp = time(NULL);\struct tm* timeinfo = localtime(&timestamp);\strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", timeinfo);\fprintf(stdout, "[%s %s:%d] " format "\n", timebuffer, __FILE__, __LINE__, ##__VA_ARGS__);\
}while(0)#endif

​ 这样子就结束了吗❓❓❓还是没结束,因为我们到时候项目中会打印很多日志,如果我们不对日志分等级的话,那么可能会导致日志比较乱,下面我们定义一些等级的宏:

#define INF 0    // 提示型等级
#define DEBUG 1  // 调试型等级
#define ERROR 2  // 错误型等级
#define DEFAULT_LOG_LEVEL DEBUG  // 默认的日志等级

​ 然后我们再将这些等级宏和我们刚才写的日志宏封装起来:

#define ILOG(format, ...) LOG(INF, format, ##__VA_ARGS__)
#define DLOG(format, ...) LOG(DEBUG, format, ##__VA_ARGS__)
#define ELOG(format, ...) LOG(ERROR, format, ##__VA_ARGS__)

​ 此时看到 LOG 宏的第一个参数传入的是对应的等级,那我们想要去改一下原来的日志宏的参数,多加一个参数 level,并且我们判断一下当前的等级也就是 DEFAULT_LOG_LEVEL 是否小于传入进来的等级,是的话我们就不需要去打印,因为我们此时程序说明不需要上升到这种级别的日志打印!

#define LOG(level, format, ...) do{\if(DEFAULT_LOG_LEVEL > level) break\char timebuffer[128] = {0};\time_t timestamp = time(NULL);\struct tm* timeinfo = localtime(&timestamp);\strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", timeinfo);\fprintf(stdout, "[%s %s:%d] " format "\n", timebuffer, __FILE__, __LINE__, ##__VA_ARGS__);\
}while(0)

​ 所以完整的代码是这样子的:

#ifndef __MY_LOG_H__
#define __MY_LOG_H__
#include <stdio.h>
#include <time.h>#define INF 0    // 提示型等级
#define DEBUG 1  // 调试型等级
#define ERROR 2  // 错误型等级
#define DEFAULT_LOG_LEVEL DEBUG  // 默认的日志等级#define LOG(level, format, ...) do{\if(DEFAULT_LOG_LEVEL < level) break;\char timebuffer[128] = {0};\time_t timestamp = time(NULL);\struct tm* timeinfo = localtime(&timestamp);\strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", timeinfo);\fprintf(stdout, "[%s %s:%d] " format "\n", timebuffer, __FILE__, __LINE__, ##__VA_ARGS__);\
}while(0)// 将等级和日志打印封装起来
#define ILOG(format, ...) LOG(INF, format, ##__VA_ARGS__)
#define DLOG(format, ...) LOG(DEBUG, format, ##__VA_ARGS__)
#define ELOG(format, ...) LOG(ERROR, format, ##__VA_ARGS__)#endif

​ 下面我们测试一下:

ILOG("this is INF");
DLOG("this is DEBUG");
ELOG("this is ERROR");// 运行结果:
[liren@VM-8-7-centos source]$ ./gobang 
[2023-06-21 23:41:55 gobang.cc:5] this is INF
[2023-06-21 23:41:55 gobang.cc:6] this is DEBUG

Ⅱ. mysql工具类实现

之前我们讲过 mysql API 使用的一个步骤,现在来回顾一下:

  1. 初始化句柄
  2. 连接服务器
  3. 设置字符集
  4. 选择数据库
  5. 执行语句
  6. 保存结果到本地
  7. 获取结果集条数并遍历结果集
  8. 释放句柄

​ 可以看到我们其实并不能将这些步骤全都包含在一个接口中,因为 1~4 步骤是属于初始化,而第 5 步其实过程是不确定的,因为有可能执行的是查询语句,那么就得包含第 6、7 步,这个我们还是单独拎出来作为一个接口比较好。最后就是释放句柄的步骤也就是第 8 步也要提取出来作为单独的一个接口。

​ 也就是说,第 1~4 步是一个初始化接口第 5 步是一个执行语句接口第 8 步是一个释放接口,这样安排比较妥当!而 第 6~7 步的话,这最好是放在我们调用执行语句接口的地方自行来判断!

​ 下面给出基本的 mysql 工具类的框架:

#ifndef __MY_UTIL_H__
#define __MY_UTIL_H__
#include "logger.hpp"
#include <iostream>
#include <string>
#include <mysql/mysql.h>// 因为这个头文件的工具都是对外提供服务的接口
// 所以基本都是设置为public和static(就可以不用实例化对象)属性class mysql_util
{
public:// 初始化接口static MYSQL* mysql_create(const std::string& host,const std::string& user,const std::string& passwd,const std::string& dbname,uint16_t port = 3306){}// 执行语句接口static bool mysql_exec(MYSQL* mysql, const std::string& query){}// 释放句柄接口static void mysql_destroy(MYSQL* mysql){}
};#endif

​ 其实实现并不难,因为我们之前在前置知识部分已经讲过了,按照步骤来就对了!主要不同的就是加入了日志宏的使用,并且在执行语句接口中要注意得先判断 mysql 句柄是否有效,否则直接执行的话会造成段错误!

// 初始化接口
static MYSQL* mysql_create(const std::string& host,const std::string& user,const std::string& passwd,const std::string& dbname,uint16_t port = 3306)
{// 1.初始化句柄MYSQL* mysql = mysql_init(NULL);if(mysql == NULL){// 错误处理ELOG("init mysql failed");return NULL;}// 2.连接服务器if(mysql_real_connect(mysql, host.c_str(), user.c_str(), passwd.c_str(), dbname.c_str(), port, NULL, 0) == NULL){// 错误处理ELOG("connect mysql server failed : %s", mysql_error(mysql));mysql_close(mysql);return nullptr;}// 3.设置字符集if(mysql_set_character_set(mysql, "utf8") != 0){// 错误处理ELOG("mysql set character failed : %s", mysql_error(mysql));mysql_close(mysql);return NULL;}// 4.连接选择的数据库 -- 可以不做,但是为了错误处理这里还是进行判断if(mysql_select_db(mysql, dbname.c_str()) != 0){// 错误处理ELOG("mysql select database failed : %s", mysql_error(mysql));mysql_close(mysql);return NULL;}// 最后一定一定要记得返回句柄!!!return mysql;
}// 执行语句接口
static bool mysql_exec(MYSQL* mysql, const std::string& query)
{// 先判断句柄是否有效if(mysql == NULL){ELOG("MYSQL handle invalid");return false;}int n = mysql_query(mysql, query.c_str()); // 执行语句if(n != 0){// 错误处理ELOG("%s --> sql query failed : %s", query.c_str(), mysql_error(mysql));return false;}return true;
}// 释放句柄接口
static void mysql_destroy(MYSQL* mysql)
{if(mysql != NULL)mysql_close(mysql);
}

​ 测试代码:

void mysql_test()
{MYSQL* mysql = mysql_util::mysql_create(HOST, USER, PASSWD, DBNAME, PORT);const char* query = "insert stu value(null, '利刃', 1314, 100, 150, 149);";bool ret = mysql_util::mysql_exec(mysql, query);if(ret == false)return;mysql_util::mysql_destroy(mysql);
}

Ⅲ. json工具类实现

​ 对于 json 工具类其实就没什么好说的了,就是一个序列化和反序列化的接口:

#include <jsoncpp/json/json.h>
#include <memory>
#include <sstream>class json_util
{
public:static bool serialize(const Json::Value& root, std::string& out){}static bool unserialize(const std::string& in, Json::Value& root){}
};

​ 先来看它们的参数,因为序列化的目的就是要将数据对象转化为 json 字符串,但是问题来了,我们怎么知道数据对象有多少个,所以为了方便,我们统一数据对象都存放到 Json::Value 对象中,然后再传给序列化接口进行使用!

​ 而反序列化就是将已有的 json 字符串,通过输出型参数 root 传出去!

​ 它们的返回值都是 bool 类型,成功返回 true,失败返回 false

​ 下面我们完善一下函数体:

class json_util
{
public:static bool serialize(const Json::Value& root, std::string& out){// 1.实例化StreamWriterBuilder工厂类对象Json::StreamWriterBuilder swb;// 2.通过工厂类对象生产一个StreamWriter对象std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());if(sw.get() == nullptr){ELOG("json produce writer failed");return false;}// 3.通过StreamWriter对象进行序列化std::stringstream ss;int ret = sw->write(root, &ss);if(ret != 0){// 错误处理ELOG("json serialize failed");return false;}// 4.输出型参数赋值out = ss.str(); return true;}static bool unserialize(const std::string& in, Json::Value& root){// 1.实例化一个CharReaderBuilder工厂类对象Json::CharReaderBuilder crb;// 2.通过工厂类对象生产一个CharReader类对象std::unique_ptr<Json::CharReader> cr(crb.newCharReader());if(cr.get() == nullptr){// 错误处理ELOG("json produce reader failed");return false;}// 3.通过CharReader类对象进行反序列化std::stringstream ss;std::string err;bool ret = cr->parse(in.c_str(), in.c_str() + in.size(), &root, &err);if(ret == false){// 错误处理ELOG("json unserialize failed");return false;}return true;}
};

​ 唯一要注意的是在 判断 unique_ptr 对象中的指针是否是有效的时候,需要用 get() 函数,比如代码中的 sw.get()cr.get() 来获取其原生指针,才能判断是否为空,而不是直接拿 unique_ptr 对象去判空!

​ 测试代码:

void json_test()
{// 序列化Json::Value root;root["姓名"] = "liren";root["年龄"] = 1314;root["成绩"].append(100);root["成绩"].append(200);root["成绩"].append(300);std::string str;json_util::serialize(root, str);DLOG("%s", str.c_str());// 反序列化Json::Value newroot;json_util::unserialize(str, newroot);std::cout << newroot["姓名"].asString() << std::endl;std::cout << newroot["年龄"].asInt() << std::endl;int size = newroot["成绩"].size();for(int i = 0; i < size; ++i){std::cout << newroot["成绩"][i].asInt() << std::endl;}
}

Ⅳ. string工具类实现

​ 这个工具类我们只需要提供给一个接口 split(),它的主要任务就是传过来的字符串按照分隔符,将其分为多个子串,然后放到一个 vector<string> 中进行返回即可!

#include <vector>
class string_util
{
public:// src:要分割的字符串// sep:分隔符// res:存放子串的结果集数组static int split(const std::string& src, const std::string& sep, std::vector<std::string>& res){auto pos = src.find(sep);int pre = 0;while(pos != std::string::npos){// 这个判断是避免sep是',',而字符串是",,,,"的时候,下面在push_back中pos-pre是0,会插入一个空字符串的情况if(pos == pre){pre += sep.size();pos = src.find(sep, pre);continue;}res.push_back(src.substr(pre, pos - pre));      // 注意是加sep.size()而不是只加1pre = pos + sep.size(); pos = src.find(sep, pre);}res.push_back(src.substr(pre)); // 别忘了还有最后一个子串return res.size();}
};

Ⅴ. file工具类实现

​ 这个工具类的实现我们也是只需要一个接口实现,就是 读取接口,它的作用是将 html 文件读取到本地,方便后面响应给客户端,因为 html 文件是放在硬盘中存储的!

class file_util
{
public:static bool read(const std::string& filename, std::string& out){// 1.打开文件(注意要用二进制形式打开)std::ifstream ifs(filename, std::ios::binary);if(!ifs.is_open()){ELOG("%s file open failed", filename.c_str());return false;}// 2.获取文件大小(通过偏移量获取)ifs.seekg(0, std::ios::end); // 把文件指针从流的末尾开始偏移0个偏移量,相当于指到末尾size_t size = ifs.tellg();   // 读取当前文件指针到开头的偏移量,相当于文件大小ifs.seekg(0, std::ios::beg); // 将文件指针重新指向开头out.resize(size); // 将out大小进行调整// 3.读取文件数据到字符串out中//   这里第一个参数传的是首地址,所以要用out[0]的取地址,不能直接使用out.c_str(),因为它是const属性ifs.read(&out[0], size);if(ifs.good() == false){ELOG("read %s file content failed", filename.c_str());ifs.close();return false;}// 4.关闭文件ifs.close();return true;}
};

​ 要注意的是我们 打开文件的时候要用二进制形式打开,因为 html 文件中可能有些特殊字符需要特殊处理,如果不用二进制打开的话很容易出现乱码错误的情况。

​ 另外就是 c++ 方式读写文件的方式的细节,都在代码中,仔细阅读!

​ 测试代码:

void file_test()
{std::string str;bool ret = file_util::read("./makefile", str);if(ret == false)return;DLOG("%s", str.c_str());
}

完整的 util.hpp 头文件代码

#ifndef __MY_UTIL_H__
#define __MY_UTIL_H__
#include "logger.hpp"
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <sstream>
#include <fstream>
#include <jsoncpp/json/json.h>
#include <mysql/mysql.h>#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
using wsserver_t = websocketpp::server<websocketpp::config::asio>;// 因为这个头文件的工具都是对外提供服务的接口
// 所以基本都是设置为public和static(就可以不用实例化对象)属性class mysql_util
{
public:// 初始化接口static MYSQL* mysql_create(const std::string& host,const std::string& user,const std::string& passwd,const std::string& dbname,uint16_t port = 3306){// 1.初始化句柄MYSQL* mysql = mysql_init(NULL);if(mysql == NULL){// 错误处理ELOG("init mysql failed");return NULL;}// 2.连接服务器if(mysql_real_connect(mysql, host.c_str(), user.c_str(), passwd.c_str(), dbname.c_str(), port, NULL, 0) == NULL){// 错误处理ELOG("connect mysql server failed : %s", mysql_error(mysql));mysql_close(mysql);return nullptr;}// 3.设置字符集if(mysql_set_character_set(mysql, "utf8") != 0){// 错误处理ELOG("mysql set character failed : %s", mysql_error(mysql));mysql_close(mysql);return NULL;}// 4.连接选择的数据库 -- 可以不做,但是为了错误处理这里还是进行判断if(mysql_select_db(mysql, dbname.c_str()) != 0){// 错误处理ELOG("mysql select database failed : %s", mysql_error(mysql));mysql_close(mysql);return NULL;}// 最后一定一定要记得返回句柄!!!return mysql;}// 执行语句接口static bool mysql_exec(MYSQL* mysql, const std::string& query){// 先判断句柄是否有效if(mysql == NULL){ELOG("MYSQL handle invalid");return false;}int n = mysql_query(mysql, query.c_str()); // 执行语句if(n != 0){// 错误处理ELOG("%s --> sql query failed : %s", query.c_str(), mysql_error(mysql));return false;}return true;}// 释放句柄接口static void mysql_destroy(MYSQL* mysql){if(mysql != NULL)mysql_close(mysql);}
};class json_util
{
public:static bool serialize(const Json::Value& root, std::string& out){// 1.实例化StreamWriterBuilder工厂类对象Json::StreamWriterBuilder swb;// 2.通过工厂类对象生产一个StreamWriter对象std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());if(sw.get() == nullptr){ELOG("json produce writer failed");return false;}// 3.通过StreamWriter对象进行序列化std::stringstream ss;int ret = sw->write(root, &ss);if(ret != 0){// 错误处理ELOG("json serialize failed");return false;}// 4.输出型参数赋值out = ss.str(); return true;}static bool unserialize(const std::string& in, Json::Value& root){// 1.实例化一个CharReaderBuilder工厂类对象Json::CharReaderBuilder crb;// 2.通过工厂类对象生产一个CharReader类对象std::unique_ptr<Json::CharReader> cr(crb.newCharReader());if(cr.get() == nullptr){// 错误处理ELOG("json produce reader failed");return false;}// 3.通过CharReader类对象进行反序列化std::stringstream ss;std::string err;bool ret = cr->parse(in.c_str(), in.c_str() + in.size(), &root, &err);if(ret == false){// 错误处理ELOG("json unserialize failed");return false;}return true;}
};class string_util
{
public:// src:要分割的字符串// sep:分隔符// res:存放子串的结果集数组static int split(const std::string& src, const std::string& sep, std::vector<std::string>& res){auto pos = src.find(sep);int pre = 0;while(pos != std::string::npos){// 这个判断是避免sep是',',而字符串是",,,,"的时候,下面在push_back中pos-pre是0,会插入一个空字符串的情况if(pos == pre){pre += sep.size();pos = src.find(sep, pre);continue;}res.push_back(src.substr(pre, pos - pre));      // 注意是加sep.size()而不是只加1pre = pos + sep.size(); pos = src.find(sep, pre);}res.push_back(src.substr(pre)); // 别忘了还有最后一个子串return res.size();}
};class file_util
{
public:static bool read(const std::string& filename, std::string& out){// 1.打开文件(注意要用二进制形式打开)std::ifstream ifs(filename, std::ios::binary);if(ifs.is_open() == false){ELOG("%s file open failed", filename.c_str());return false;}// 2.获取文件大小(通过偏移量获取)ifs.seekg(0, std::ios::end); // 把文件指针从流的末尾开始偏移0个偏移量,相当于指到末尾size_t size = ifs.tellg();   // 读取当前文件指针到开头的偏移量,相当于文件大小ifs.seekg(0, std::ios::beg); // 将文件指针重新指向开头out.resize(size); // 将out大小进行调整// 3.读取文件数据到字符串out中//   这里第一个参数传的是首地址,所以要用out[0]的取地址,不能直接使用out.c_str(),因为它是const属性ifs.read(&out[0], size);if(ifs.good() == false){ELOG("read %s file content failed", filename.c_str());ifs.close();return false;}// 4.关闭文件ifs.close();return true;}
};#endif

在这里插入图片描述

http://www.xdnf.cn/news/14169.html

相关文章:

  • 【unitrix】 1.6 数值类型基本结构体(types.rs)
  • 商用油烟净化器日常维护的标准化流程
  • Arduino入门教程:4-1、代码基础-进阶
  • 静态变量详解(static variable)
  • 微博项目(总体搭建)
  • Javascript什么是原型和原型链,八股文
  • java面试总结-20250609
  • 数据结构 学习 图 2025年6月14日 12点57分
  • spring如何处理bean的循环依赖
  • NuttX 调度器源码学习
  • 吃透 Golang 基础:方法
  • 湖南源点(市场研究)咨询 DNF下沉市场用户研究项目之调研后感
  • 03、继承与多态
  • 使用C/C++的OpenCV 构建人脸识别并自动抓拍系统
  • 使用DuckDB查询DeepSeek历史对话
  • AI首次自主发现人工生命
  • C++编程语言
  • Spring Cloud 原生中间件
  • Linux免驱使用PCAN,使用方法以Ubuntu为例
  • Java基础复习之static
  • Dify动手实践教程1
  • Day 49 训练
  • 1.4、SDH网状拓扑
  • 5、ZYNQ PL 点灯--流水灯
  • 深入解析JVM类加载机制
  • 人工智能学习22-Pandas
  • Java大模型开发入门 (7/15):让AI拥有记忆 - 使用LangChain4j实现多轮对话
  • 【Linux知识】curl命令行从入门到进阶实战
  • Visual studio 中 使用QT插件 编辑UI文件打开 Qt Designer 报错 问题解决方案
  • 威科达VE运动控制器:工业自动化核心,高效精准掌控每一环节