目录
一、基本操作
字符数组
char buf[recv_buff_size] = {0};
len = socket->recv(buf, recv_buff_size);
memset(buf, 0, recv_buff_size);
字符串char*
定义
char* m_content = new char[m_content_len];
memset(m_content, 0, m_content_len);
// 拷贝
memcpy(m_content+m_content_recv_len, buf_start, length);
// 也可以用
strcpy(m_content,"he11o mike!!");
// 释放
delete[] m_content;
转string
const char* s = "你好";
string str = string(s);
查找
const char CRLF[] = "\r\n";
const char* buf = "POST /reply?test=1 HTTP/1.1\r\nContent-Type: application/json\r\nPostman-Token: 2aa2d15d-cc43-4664-8d4c-8a653aa75299\r\nConnection: keep-alive\r\nContent-Length: 22\r\n";
int index = 0;
do{
const char* buf_start = buf+index;
const char* lineEnd = search(buf_start, buf+len, CRLF, CRLF + 2);
int index_pre = index;
index = index_pre+int(lineEnd-buf_start);
std::string line(buf_start, lineEnd);
LOG_DEBUG("m_state:%d,m_content_recv_len: %d, len:%d,index_pre:%d index: %d, line: len:%d, %s",m_state,m_content_recv_len,len,index_pre,index,line.length(), line.c_str());
index = index +2;
}while(index<len);
结果是
POST /reply?test=1 HTTP/1.1
Content-Type: application/json
Postman-Token: 2aa2d15d-cc43-4664-8d4c-8a653aa75299
Connection: keep-alive
Content-Length: 22
比较strncmp
C 库函数 int strncmp(const char *str1, const char *str2, size_t n) 把 str1 和 str2 进行比较,最多比较前 n 个字节。
参数
str1 – 要进行比较的第一个字符串。
str2 – 要进行比较的第二个字符串。
n – 要比较的最大字符数。
返回值
该函数返回值如下:
如果返回值 < 0,则表示 str1 小于 str2。
如果返回值 > 0,则表示 str1 大于 str2。
如果返回值 = 0,则表示 str1 等于 str2。
参考:https://www.runoob.com/cprogramming/c-function-strncmp.html
比较strcasecmp忽略大小写
strcasecmp(const char* str1, const char* str2);
传递char*作为参数赋值
定义
void ApiManager::response_json(char** body, int code ,string& msg)
{
cJSON* root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "code", code);
cJSON_AddStringToObject(root, "msg", msg.data());
char *data = cJSON_Print(root);
// 设置返回值
char *temp = (char *)malloc(strlen(data)+1);
strcpy(temp,data); //间接赋值
*body = temp;
cJSON_Delete(root);
}
char* body;
string r = "ok";
response_json(body, 0, r);
调用
字符串string
方法
查找子串find
find() 函数用于在 string 字符串中查找子字符串出现的位置,如果没有查找到子字符串,那么会返回一个无穷大值 4294967295。它其中的两种原型为:
size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;
示例:
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1 = "first second third";
string s2 = "second";
int index = s1.find(s2,5);
if(index < s1.length())
cout<<"Found at index : "<< index <<endl;
else
cout<<"Not found"<<endl;
return 0;
}
注意如果是判断请用int转换,否则无法进入判断
if(int(line.find(m_boundary))>-1){
}
rfind() 函数
rfind() 和 find() 很类似,同样是在字符串中查找子字符串,不同的是 find() 函数从第二个参数开始往后查找,而 rfind() 函数则最多查找到第二个参数处,如果到了第二个参数所指定的下标还没有找到子字符串,则返回一个无穷大值4294967295。
请看下面的例子:
#include
#include <string>
using namespace std;
int main(){
string s1 = "first second third";
string s2 = "second";
int index = s1.rfind(s2,6);
if(index < s1.length())
cout<<"Found at index : "<< index <<endl;
else
cout<<"Not found"<<endl;
return 0;
}
运行结果:
Found at index : 6
find_first_of() 函数
find_first_of() 函数用于查找子字符串和字符串共同具有的字符在字符串中首次出现的位置。请看下面的代码:
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1 = "first second second third";
string s2 = "asecond";
int index = s1.find_first_of(s2);
if(index < s1.length())
cout<<"Found at index : "<< index <<endl;
else
cout<<"Not found"<<endl;
return 0;
}
运行结果:
Found at index : 3
本例中 s1 和 s2 共同具有的字符是 ’s’,该字符在 s1 中首次出现的下标是3,故查找结果返回3。
截取字符串substr
头文件:
#include <string> //注意没有.h
#include <iostream>
#include <string>
using namespace std ;
void main()
{
string s="ABCD";
cout << s.substr(2) <<endl ; //从字符串下标为2的地方开始截取,截取到末尾,输出CD
cout << s.substr(0,2) <<endl ; //从字符串下标为0的地方开始截取,截取长度为2,输出AB
cout << s.substr(1,2) <<endl ; //输出BC
}
int 转字符串
int i = 0;
to_string(i);
long 转字符串
unsigned long i = 0;
to_string(i);
函数返回值值string
参考:https://wenku.baidu.com/view/bdbd512b874769eae009581b6bd97f192279bfc7.html
#include<iostream>
#include<string>
using namespace std;
string TestStringC_STR()
{
string strTest = "This is a test.";
return strTest;
}
int main()
{
string str1 = TestStringC_STR();
const char *pc = str1.c_str();
cout << pc << std::endl;
return0;
}
转换char*
string cpu = "test";
char *temp = (char *)malloc(cpu.length()+ 1 );
strcpy(temp,id); //间接赋值
*cpu_uid = temp;
注意:一定要用一个string变量先接收函数返回值,不然因为strTest是局部变量,会立马为空
int
字符串转int
const char* v = "80";
atoi(v);
long
unsigned long计算
double occupy = (double)a/(double)b;
结构体
定义
typedef struct NALU {
int naluType;
int naluLen;
unsigned char *pData;
} T_NALU, *PT_NALU;
赋值memset
T_NALU nalu;
// memset(&nalu, 0, sizeof(T_NALU));
// 建议如下
T_NALU nalu = {};
nalu->naluLen = pos-startCodeLen;
nalu->pData = realloc(pNalu->pData, nalu->naluLen);
// char* 赋值
memcpy(nalu->pData, gBuf+startCodeLen, nalu->naluLen);
指针方式赋值calloc
nalu = (T_NALU *)calloc(1, sizeof(T_NALU));
client = malloc(sizeof(T_NALU));
文件操作
获取文件信息stat
stat():用于获取文件的状态信息,使用时需要包含**<sys/stat.h>**头文件。
int main(int argc, char* argv[])
{
struct stat buf;
char* path = "E:\\1.txt";
int res = stat(path, &buf);
if (res <0 )
{
perror("Problem getting information");
switch (errno)
{
case ENOENT:
printf("File %s not found.\n", path);
break;
case EINVAL:
printf("Invalid parameter to _stat.\n");
break;
default:
/* Should never be reached. */
printf("Unexpected error in _stat.\n");
}
}
//获取文件类型
if (buf.st_mode & S_IFREG) // 或者用S_ISREG(buf.st_mode)
printf("regular file\n");
else if (buf.st_mode & S_IFDIR) // 或者用S_ISDIR(buf.st_mode)
printf("dir \n");
else
printf("other\n");
//获取大小
printf("SIZE %d\n", buf.st_size);
char timeBuf[] = { };
printf("%lld\n", buf.st_ctime);
//文件最后访问时间
errno_t err = ctime_s(timeBuf, ,&buf.st_atime);
if (err)
{
printf("Invalid arguments to ctime_s.");
return -;
}
printf("Time visit %s\n", timeBuf);
//文件最后修改时间
err = ctime_s(timeBuf, , &buf.st_mtime);
if(err)
{
printf("Invalid arguments to ctime_s.");
return -;
}
printf("Time modified %s\n", timeBuf);
system("pause");
return ;
}
写文件
FILE* fp = fopen(CONFIG_GB28181.data(),"w");
fwrite(data,sizeof(char),strlen(data)+1,fp);
fclose(fp);
sync();
const char * buf, int len;
// 第一个参数要写入的内容,第二个参数写入的买个元素多大,第三个参数写入多少个元素
fwrite(buf,1,write_len,fp);
获取切换工作目录
char pwd_path[256] = "";
char *path = getenv("PWD");
strcpy(pwd_path, path);
// 合并字符串
strcat(pwd_path, "/web-http");
// 切换工作目录
chdir(pwd_path);
指针
指针转换
PyramidImageFramePtr rgb_image = nullptr;
for (const auto &output : xstream_out->datas_)
{
// LOGD << output->name_ << ", type is " << output->type_;
if (output->name_ == "rgb_image" || output->name_ == "image")
{
rgb_image = std::dynamic_pointer_cast<PyramidImageFrame>(output);
}
}
智能指针
C++类循环依赖破解:前向声明与智能指针的妙用
// 文件 A.h
#pragma once
#include <memory>
class B; // 前向声明 B 类
class A {
public:
A();
void SetB(std::shared_ptr<B> b); // 使用 B 类的智能指针
void DoSomething();
private:
std::shared_ptr<B> b_; // 成员变量使用 B 类的智能指针
};
// 文件 B.h
#pragma once
#include <memory>
class A; // 前向声明 A 类
class B {
public:
B();
void SetA(std::shared_ptr<A> a); // 使用 A 类的智能指针
void DoSomething();
private:
std::shared_ptr<A> a_; // 成员变量使用 A 类的智能指针
};
// 文件 A.cpp
#include "A.h"
#include "B.h"
A::A() {}
void A::SetB(std::shared_ptr<B> b) {
b_ = b;
}
void A::DoSomething() {
if (b_) {
b_->DoSomething();
}
}
// 文件 B.cpp
#include "B.h"
#include "A.h"
B::B() {}
void B::SetA(std::shared_ptr<A> a) {
a_ = a;
}
void B::DoSomething() {
if (a_) {
a_->DoSomething();
}
}
在这个示例中,使用了 std::shared_ptr 作为成员变量,这是一种现代 C++ 中更安全、便利的智能指针。智能指针能够在对象不再需要时自动释放资源,避免了内存泄漏的风险。
通过这两个示例,展示了使用前向声明和指针(或智能指针)的方法来解决类之间循环依赖的问题。这种技术在大型项目中尤其有用,确保代码结构清晰而没有不必要的编译错误。
正则
解析header request_line
bool Request::parse_request_line(const string& line) {
regex patten("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$");
smatch subMatch;
if(regex_match(line, subMatch, patten)) {
m_method = subMatch[1];
m_uri = subMatch[2];
int index = m_uri.find('?');
if(index>-1){
m_path = m_uri.substr(0,index);
}else{
m_path = m_uri;
}
m_proto = subMatch[3];
m_state = HEADERS;
return true;
}
LOG_ERROR("RequestLine Error");
return false;
}
解析headerbody
void Request::parse_header(const string& line) {
// LOG_ERROR("ParseHeader_:%s", line.c_str());
regex patten("^([^:]*): ?(.*)$");
smatch subMatch;
if(regex_match(line, subMatch, patten)) {
m_headers[subMatch[1]] = subMatch[2];
string key(subMatch[1]),value(subMatch[2]);
//printf("key:%s\n, value: %s",key.c_str(), value.data());
if(key == "Content-Length" ){
m_content_len = atoi(value.c_str());
//printf("body总长度是:%d\n",content_total_len);
}else if(key == "Content-Type" ){
int index = value.find("application/json");
if(index>-1){
m_content_type = JSON;
}else{
m_content_type = FORM_DATA;
regex patten1("multipart/form-data; boundary=?(.*)$");
smatch subMatch1;
if(regex_match(value, subMatch1, patten1)) {
string boundary(subMatch1[1]);
m_boundary = "--"+boundary;
m_boundary_end = "--"+boundary+"--";
//printf("boundary:%s\n",boundary.c_str());
}
}
//printf("body总长度是:%d\n",content_total_len);
}
}else {
m_state = BODY;
}
}
日期计算
引入头文件
#include <iostream>
#include <time.h>
获取当前日期时间
time_t curTime = time(NULL);
struct tm *time;
time = localtime(&curTime);
char save_name[100] = "";
sprintf(save_name,"../result/%d%02d%02d%02d%02d%02d.jpg", (1900 + time->tm_year), (1 + time->tm_mon), time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec);
// 日期
time_t tm_time = time(0);
tm current_time = *localtime(&tm_time);
char time_str[100] = {0};
sprintf(time_str,"%d-%02d-%02d %02d:%02d:%02d", (1900 + gt->tm_year), (1 + gt->tm_mon), gt->tm_mday, gt->tm_hour, gt->tm_min, gt->tm_sec);
// 时间
time_t tm_time = time(0);
tm current_time = *localtime(&tm_time);
char time_str[100] = {0};
sprintf(save_name,"%d-%02d-%02d", (1900 + gt->tm_year), (1 + gt->tm_mon), gt->tm_mday);
获取当前日期时间时分秒毫秒
#include <iostream>
#include <chrono>
#include <ctime>
int main() {
auto now = std::chrono::system_clock::now();
auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
auto value = now_ms.time_since_epoch().count();
std::time_t current_time = std::chrono::system_clock::to_time_t(now);
char buffer[100];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", std::localtime(¤t_time));
std::cout << buffer << "." << (value % 1000) << std::endl;
return 0;
}
比如计算3个月前的日期,计算60天后的日期,计算两年前的日期
https://blog.csdn.net/luchengtao11/article/details/107079786
vector
引入
#include <vector>
定义
vector<double> data;
vector<string> algoWarnImg;
vector<cv::Point> polygon[8];
int size = m_cpuOccupy.size();
插入
data.push_back(v);
获取大小v.size()
移除第一个元素
vector<double>::iterator k = m_cpuOccupy.begin();
m_cpuOccupy.erase(k);//删除第一个元素
map
引入
#include <map>
定义
typedef struct {
PT_FlvContext flvCtx;
int first_send_idr = 0; // 是否发送了第一帧
int chnNum; // 通道
} T_CLIENT,*PT_CLIENT;
map<int, PT_CLIENT> client_map;
map<int,PT_CLIENT>::iterator client_map_it;
插入
T_CLIENT client;
memset(&client, 0, sizeof(T_CLIENT));
client.first_send_idr = 0;
client.chnNum = acpt_soc;
client.flvCtx = &flvCtx;
client_map.insert(pair<int, PT_CLIENT>(acpt_soc, &client));
大小client_map.size()
遍历
for(client_map_it=client_map.begin();client_map_it!=client_map.end();++client_map_it)
{
// key
int client_socket = client_map_it->first;
// value
PT_CLIENT client = client_map_it->second;
}
查找find和移除erase
client_map_it = client_map.find(client_socket);
if (client_map_it != client_map.end())
{
// 从map中移除
client_map.erase(client_map_it);
close(client_socket);
}
遍历删除
for(auto i=map.begin();i!=map.end())
{
if(i.first==value)
i=map.erase(i);
else
i++;
}
for(auto i=map.begin();i!=map.end())
{
i=map.erase(i);
}
打印
打印字符串
char* str = "112";
printf("%s",str);
打印数字
int d = 1;
printf("%d",d);
打印16进制
for(int i = 0;i<sps_nalu.naluLen;i++)
{
printf("%2x ",sps_nalu.pData[i]);
}
函数
回调函数
目前主要用于epoll反应堆中,可以参考相关文档
void senddata(int fd,int events,void *arg);
/*描述就绪文件描述符的相关信息*/
struct myevent_s
{
int fd; //要监听的文件描述符
int events; //对应的监听事件,EPOLLIN和EPLLOUT
void *arg; //指向自己结构体指针
void (*call_back)(int fd,int events,void *arg); //回调函数
int status; //是否在监听:1->在红黑树上(监听), 0->不在(不监听)
char buf[BUF_SIZE];
int len;
long last_active; //记录每次加入红黑树 g_efd 的时间值
};
// 设置回调
void eventset(struct myevent_s *ev, int fd, void (*call_back)(int fd,int events,void *arg), void *arg)
{
ev->fd = fd;
ev->call_back = call_back;
ev->events = 0;
ev->arg = arg;
ev->status = 0;
if(ev->len <= 0)
{
memset(ev->buf, 0, sizeof(ev->buf));
ev->len = 0;
}
ev->last_active = time(NULL); //调用eventset函数的时间
return;
}
回调函数精解
#include <iostream>
#include <functional>
// 定义高级回调函数类型
using AdvancedCallback = std::function<int(int)>;
// 高级回调函数
int advancedCallbackFunction(int result) {
std::cout << "高级回调函数被调用,参数为: " << result << std::endl;
return result * 2;
}
// 函数接受高级回调函数作为参数
int performAdvancedOperation(int data, AdvancedCallback callback) {
std::cout << "执行高级操作,参数为: " << data << std::endl;
// 调用高级回调函数并获取结果
int result = callback(data);
std::cout << "高级操作的结果为: " << result << std::endl;
return result;
}
int main() {
// 使用高级回调函数作为参数调用函数
int result = performAdvancedOperation(42, advancedCallbackFunction);
printf("result:%d\n",result);
// 使用Lambda表达式作为高级回调函数
result = performAdvancedOperation(99, [](int result) {
std::cout << "Lambda高级回调函数被调用,参数为: " << result << std::endl;
return result * 3;
});
printf("result:%d\n",result);
return 0;
}
在这个例子中,基础使用演示了如何定义和使用一个简单的回调函数。而高级使用展示了如何定义返回值非 void 的回调函数,并且使用Lambda表达式作为一种更灵活的回调方式。在高级例子中,回调函数不仅被调用,还返回了一个结果,进一步扩展了回调的灵活性。
运行结果如下:
执行高级操作,参数为: 42
高级回调函数被调用,参数为: 42
高级操作的结果为: 84
result:84
执行高级操作,参数为: 99
Lambda高级回调函数被调用,参数为: 99
高级操作的结果为: 297
result:297
虚函数
所谓“纯虚函数”,其实就是没有具体实现的虚函数,通常定义在基类中提供类似于“接口”的功能。因为没有具体实现,也即该函数没有具体功能,拥有纯虚函数的基类通常被称作“抽象类”,所以抽象类无法实例化对象,一般只能作为基类被其他派生类继承使用。继承了抽象基类的派生类必须重写所有的纯虚函数,也即为类提供具体的功能,才能实例化对象使用。
参考:C++语言中的“虚函数”就像C语言中的指针,必须要弄懂的
进制转换
字符转数字
'8' => '8'-'0' =>8
e => 'e' - 'a' + 10 => 14
# "e8" =>0xe8
'e8' = ( 'e' - 'a' + 10)*16 + ('8'-'0') == 0xe8
内部链接和外部链接(全局变量)
实例源代码:
// File1.cpp
#include <iostream>
// 声明具有外部链接的全局变量
extern int globalVariable;
int main() {
std::cout << "File1: " << globalVariable << std::endl;
return 0;
}
// File2.cpp
#include <iostream>
// 定义具有外部链接的全局变量
int globalVariable = 42;
int main() {
std::cout << "File2: " << globalVariable << std::endl;
return 0;
}
内存分配
C语言中动态内存管理方式:malloc/calloc/realloc/free
void Test()
{
int* p1 = (int*)malloc(sizeof(int));
free(p1);
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int) * 10);
free(p3);
}
- malloc:这个函数向内存申请⼀块连续可用的空间,并返回指向这块空间的指针。参数 size 指的是申请的空间的大小
- calloc:函数的功能是为 num 个大小为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。
- realloc:realloc 函数可以对动态开辟内存大小进行调整,返回值为调整之后的内存起始位置。
- free:free函数用来释放动态开辟的内存。
C++内存管理方式new/delete操作内置类型
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[3];
delete ptr4;
delete ptr5;
delete[] ptr6;
}
new返回的是该数据类型的指针
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用
new[]和delete[],注意:匹配起来使用。
操作对象
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A) * 10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
二、socket编程
常见错误
linux c++ tcosocket 解决客户端退出,服务器自动退出问题
在recv()函数返回值<0的情况下家还是那个信号屏蔽即可
#include<signal.h>
signal(SIGPIPE, SIG_IGN);
参考:https://blog.csdn.net/zbr794866300/article/details/119374038
三、线程
休眠
sleep(1); // 秒
usleep(10); // 微妙
四、异常
// try_catch_test.cpp
struct SomeOtherStruct { };
struct AnotherError { };
struct MyError { /* ... */ };
void foo() {
SomeOtherStruct other_struct;
throw MyError();
return;
}
void bar() {
try {
foo();
} catch (MyError err) {
// do something with err
} catch (AnotherError err) {
// do something with err
} catch (...) {
// do something
}
}
int main() {
return 0;
}
五、引入库
技巧
可以使用man命令查找系统函数依赖的库
比如查找sleep
# 3表示第三页,因为第一页和第二页可能没有,输入man sleep默认是第一页
root@sony-HP-Notebook:/usr/local/cpp_demo/fun# man 3 sleep
SLEEP(3) Linux Programmer's Manual SLEEP(3)
NAME
sleep - sleep for a specified number of seconds
SYNOPSIS
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
常用
#include <algorithm> // std::search
#include <vector> // vector
#include <unistd.h> // sleep
#include <regex> //regex
redis操作库hiredis
参考 hiredis使用,封装代码示例
C++封装Redis操作函数
反射是用库
参考:C++ Boost.Reflection库(现在是Boost.PFR)的下载、安装、使用详细教程
现代 C++ 编译时 结构体字段反射
·需要在CMakeLists中设置 -std=c++14·,同时也需要增加如下
# 指定C++14标准,否则boost用不了
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_14)
#include <boost/pfr.hpp>
#include <boost/type_index.hpp>
const type_info& ti = typeid(T);
size_t size = sizeof(T);
std::cout << "结果:"<< ti.name() <<" has " << boost::pfr::tuple_size<T>::value
<< " fields: " << boost::pfr::io(data) << " " <<size <<"\n";
boost::pfr::for_each_field(data, [&](const auto& field, std::size_t index) {
std::string name;
// boost::pfr::detail::member_to_string(field, name);
// constexpr auto fieldName = boost::pfr::
// constexpr auto fieldName = boost::pfr::flat_tuple_name<ti, index>();
// std::cout << "Field Name: " << fieldName << std::endl;
std::cout << "Field Name: " << boost::typeindex::type_id_runtime(field) << std::endl;
std::cout << "Field Type: " << typeid(field).name() << std::endl;
std::cout << "Field Value: " << field << std::endl;
});
输出如下
结果:6Client has 3 fields: {1, "你好", 0} 48
Field Name: int
Field Type: i
Field Value: 1
Field Name: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >
Field Type: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Field Value: 你好
Field Name: float
Field Type: f
Field Value: 0
序列化和反序列化
实现结构体和xml和json转换
静态反射的开源库iguana(需要c++20)
https://github.com/qicosmos/iguana
Ubpa UDRefl(需要c++20)
https://github.com/Ubpa/UDRefl
nlohmann::json(推荐)
对c++版本没有要求,
参考:
c++中nlohmann json的基本使用教程
nlohmann/json 的用法示例
引入:https://github.com/nlohmann/json/tree/develop/single_include/nlohmann/json.hpp
网络库
libevent
linuv
Boost.Asio
六、工具
查看内存泄露
valgrind ./a.out
七、 编译
-O2优化
加个-O2优化