【Linux】系统编程基于环形队列生产者消费者模型(C++)

目录

【1】引入POSIX信号量

【1.1】初始化信号量

【1.2】销毁信号量

【1.3】等待信号量

【1.4】发布信号量

【2】基于环形队列的生产消费模型

【2.1】生产消费模型打印数字模型

【2.2】生产消费模型计算公式模型

【2.3】生产消费模型计算公式加保存任务模型


【1】引入POSIX信号量

        POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。

        什么是信号量:信号量本身是一把计数器,只要拥有信号量,就在未来一定能够拥有临界资源的一部分,申请信号量的本质就是对临界资源种特定小块资源的预定机制,线程要访问临界资源种的某一区域,需要先申请信号量,所有人必须要先看到信号量,信号量本身必须是公共资源。

【计数器】

  • 递减or递增 sem_t sem = 10;

  • sem-- : 申请资源 - 必须保证操作的原子性 - P

  • sem++: 归还资源 - 必须保证操作的原子性 - V

【信号量核心操作】PV原语。

【1.1】初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
// 参数:
// pshared:0表示线程间共享,非零表示进程间共享
// value:信号量初始值

【1.2】销毁信号量

int sem_destroy(sem_t *sem);

【1.3】等待信号量

// 功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()

【1.4】发布信号量

// 功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()

【2】基于环形队列的生产消费模型

        环形队列采用数组模拟,用模运算来模拟环状特性。

        环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态。

【2.1】生产消费模型打印数字模型

【makefile文件】

cc=g++
standard=-std=c++11ringQueue:RingQueue.cc$(cc) -o $@ $^ $(standard) -l pthread.PHONY:clean
clean:rm -rf ringQueue

【RingQueue.hpp文件】

#pragma once 
#include <iostream>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>static const int g_maxCapacity = 5;/* 环形队列类 */
template <class T>
class RingQueue
{
public:/* 构造函数 */RingQueue(const int& capacity = g_maxCapacity): _cyclicQueue(capacity), _capacity(capacity){// 对于生产者来说的初始化:关注空间资源,初始化也是空间的大小int pN = sem_init(&_pSpaceSem, 0, capacity);assert(pN == 0); (void)pN;// 对于消费者来说的初始化:关注数据资源,初始化肯定是0,因为没有数据int cN = sem_init(&_cDataSem, 0, 0);assert(cN == 0); (void)cN;// 位置清零_pSubcript = _cSubcript = 0;// 生产者锁初始化pthread_mutex_init(&_pMutex, nullptr);// 消费者锁初始化pthread_mutex_init(&_cMutex, nullptr);}/* 析构函数 */~RingQueue() {// 生产者信号量销毁sem_destroy(&_pSpaceSem);// 消费者信号量销毁sem_destroy(&_cDataSem);// 生产者锁销毁pthread_mutex_destroy(&_pMutex);// 消费者锁销毁pthread_mutex_destroy(&_cMutex);}
public:/* 生产任务 */void Push(const T& in) {// 生产前:申请空间信号量,保证一定可以进行生产,空间信号量-1P(_pSpaceSem);// 加锁pthread_mutex_lock(&_pMutex);_cyclicQueue[_pSubcript++] = in;_pSubcript %= _capacity;// 解锁pthread_mutex_unlock(&_pMutex);// 生产完:占用一个空间,数据信号量+1V(_cDataSem);}/* 消费任务 */void Pop(T* out){// 消费前:申请数据信号量,保证一定可以进行消费,数据信号量-1P(_cDataSem);// 加锁pthread_mutex_lock(&_pMutex);*out = _cyclicQueue[_cSubcript++];_cSubcript %= _capacity;// 解锁pthread_mutex_unlock(&_pMutex);// 消费完:少了一个空间,空间信号量+1V(_pSpaceSem);}private:/* 等待信号量,会将信号量的值减1 */void P(sem_t& sem){int n = sem_wait(&sem);assert(n == 0); (void)n;}/* 发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1 */void V(sem_t& sem){int n = sem_post(&sem);assert(n == 0); (void)n;}private:std::vector<T>  _cyclicQueue;   // 环形队列容器int             _capacity;      // 环形队列容量int             _pSubcript;     // 生产者脚步(下标)int             _cSubcript;     // 消费者脚步(下标)sem_t           _pSpaceSem;     // 生产者信号量(生产者看重的是空间资源)sem_t           _cDataSem;      // 消费者信号量(消费者看重的是数据资源)pthread_mutex_t _pMutex;        // 生产者锁pthread_mutex_t _cMutex;        // 消费者锁
};

【RingQueue.cc文件】

#include "RingQueue.hpp"
#include <cstdlib>
#include <ctime>/* 生产者线程 */ 
void* ProducerRoutine(void* args)
{// 获取传递的线程数据RingQueue<int>* rq = static_cast<RingQueue<int>*>(args);while(true) {sleep(1);// 生产者生产中.....int data = rand() % 100 + 1;rq->Push(data);std::cout << "生产完成,生产的数据是:" << data << std::endl;}
}/* 消费者线程 */
void* ConsumerRoutine(void* args)
{// 获取传递的线程数据RingQueue<int>* rq = static_cast<RingQueue<int>*>(args);while(true) {sleep(2);// 消费者消费中.....int data = 0;rq->Pop(&data);std::cout << "消费完成,消费的数据是:" << data << std::endl;}
}/* 程序入口函数 */
int main() 
{// 定义随机数种子srand((unsigned int)time(nullptr) ^ getpid());// 线程数据RingQueue<int>* rq = new RingQueue<int>();// 定义生产者线程和消费者线程// 生产者:Producer 消费者:Consumerpthread_t tP, tC;pthread_create(&tP, nullptr, ProducerRoutine, (void*)rq);pthread_create(&tC, nullptr, ConsumerRoutine, (void*)rq);pthread_join(tP, nullptr);pthread_join(tC, nullptr);delete rq;return 0;
}

【2.2】生产消费模型计算公式模型

【makefile文件】

cc=g++
standard=-std=c++11ringQueue:RingQueue.cc$(cc) -o $@ $^ $(standard) -l pthread.PHONY:clean
clean:rm -rf ringQueue

【RingQueue.hpp文件】

#pragma once 
#include <iostream>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>static const int g_maxCapacity = 5;/* 环形队列类 */
template <class T>
class RingQueue
{
public:/* 构造函数 */RingQueue(const int& capacity = g_maxCapacity): _cyclicQueue(capacity), _capacity(capacity){// 对于生产者来说的初始化:关注空间资源,初始化也是空间的大小int pN = sem_init(&_pSpaceSem, 0, capacity);assert(pN == 0); (void)pN;// 对于消费者来说的初始化:关注数据资源,初始化肯定是0,因为没有数据int cN = sem_init(&_cDataSem, 0, 0);assert(cN == 0); (void)cN;// 位置清零_pSubcript = _cSubcript = 0;// 生产者锁初始化pthread_mutex_init(&_pMutex, nullptr);// 消费者锁初始化pthread_mutex_init(&_cMutex, nullptr);}/* 析构函数 */~RingQueue() {// 生产者信号量销毁sem_destroy(&_pSpaceSem);// 消费者信号量销毁sem_destroy(&_cDataSem);// 生产者锁销毁pthread_mutex_destroy(&_pMutex);// 消费者锁销毁pthread_mutex_destroy(&_cMutex);}
public:/* 生产任务 */void Push(const T& in) {// 生产前:申请空间信号量,保证一定可以进行生产,空间信号量-1P(_pSpaceSem);// 加锁pthread_mutex_lock(&_pMutex);_cyclicQueue[_pSubcript++] = in;_pSubcript %= _capacity;// 解锁pthread_mutex_unlock(&_pMutex);// 生产完:占用一个空间,数据信号量+1V(_cDataSem);}/* 消费任务 */void Pop(T* out){// 消费前:申请数据信号量,保证一定可以进行消费,数据信号量-1P(_cDataSem);// 加锁pthread_mutex_lock(&_pMutex);*out = _cyclicQueue[_cSubcript++];_cSubcript %= _capacity;// 解锁pthread_mutex_unlock(&_pMutex);// 消费完:少了一个空间,空间信号量+1V(_pSpaceSem);}private:/* 等待信号量,会将信号量的值减1 */void P(sem_t& sem){int n = sem_wait(&sem);assert(n == 0); (void)n;}/* 发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1 */void V(sem_t& sem){int n = sem_post(&sem);assert(n == 0); (void)n;}private:std::vector<T>  _cyclicQueue;   // 环形队列容器int             _capacity;      // 环形队列容量int             _pSubcript;     // 生产者脚步(下标)int             _cSubcript;     // 消费者脚步(下标)sem_t           _pSpaceSem;     // 生产者信号量(生产者看重的是空间资源)sem_t           _cDataSem;      // 消费者信号量(消费者看重的是数据资源)pthread_mutex_t _pMutex;        // 生产者锁pthread_mutex_t _cMutex;        // 消费者锁
};

【Task.hpp文件】

#pragma once
#include <iostream>
#include <string>
#include <functional>/* 计算任务 */
class CalTask
{
public:using func_t = std::function<int(int, int, char)>;public:/* 构造函数 */CalTask() {}/* 构造函数 */CalTask(int x, int y, char op, func_t func): _x(x), _y(y), _op(op), _callBalk(func){}public:/* 仿函数 */std::string operator()(){int result = _callBalk(_x, _y, _op);char buffer[64];snprintf(buffer, sizeof(buffer), "%d %c %d = %d\n", _x, _op, _y, result);return buffer;}public:/* 返回打印公式 */std::string ToTaskString(){char buffer[64];snprintf(buffer, sizeof(buffer), "%d %c %d = ?\n", _x, _op, _y);return buffer;}private:int _x;int _y;char _op;func_t _callBalk;
};/* 执行计算的方法 */
int MyCalculate(int x, int y, char op)
{int result = 0;switch (op){case '+':result = x + y;break;case '-':result = x - y;break;case '*':result = x * y;break;case '/':{if (y == 0){std::cerr << "div zero error!" << std::endl;result = -1;}else{result = x / y;}break;}case '%':{if (y == 0){std::cerr << "mod zero error!" << std::endl;result = -1;}else{result = x % y;}break;}default:break;}return result;
}

【RingQueue.cc文件】

#include <cstdlib>
#include <ctime>
#include "RingQueue.hpp"
#include "Task.hpp"static std::string oper = "+-*/%";
const std::string ThreadName()
{char buffer[64];snprintf(buffer, sizeof(buffer), "Thread[0x%x]", pthread_self());return buffer;
}/* 生产者线程 */ 
void* ProducerRoutine(void* args)
{// 获取传递的线程数据RingQueue<CalTask>* rq = static_cast<RingQueue<CalTask>*>(args);while(true) {sleep(1);// 生产者生产中.....int x = rand() % 100 + 1;int y = rand() % 100 + 1;char op = oper[rand() % oper.size()];CalTask t(x, y, op, MyCalculate);rq->Push(t);std::cout << ThreadName() << "生产者生产任务:" << t.ToTaskString() << std::endl;}
}/* 消费者线程 */
void* ConsumerRoutine(void* args)
{// 获取传递的线程数据RingQueue<CalTask>* rq = static_cast<RingQueue<CalTask>*>(args);while(true) {sleep(2);// 消费者消费中.....CalTask t;rq->Pop(&t);std::cout <<  ThreadName() << "消费者消费任务:" << t() << std::endl;}
}/* 程序入口函数 */
int main() 
{// 定义随机数种子srand((unsigned int)time(nullptr) ^ getpid());// 线程数据RingQueue<CalTask>* rq = new RingQueue<CalTask>();// 定义生产者线程和消费者线程// 生产者:Producer 消费者:Consumerpthread_t tP, tC;pthread_create(&tP, nullptr, ProducerRoutine, (void*)rq);pthread_create(&tC, nullptr, ConsumerRoutine, (void*)rq);pthread_join(tP, nullptr);pthread_join(tC, nullptr);创建生产消费线程//pthread_t t_p[4];      // Producer(生产者)//pthread_t t_c[8];      // Consumer(消费者)pthread_create(&t_p, nullptr, ProducerRoutine, rq);//for(int i = 0; i < 4; i++)//{//    pthread_create(t_p + i, nullptr, ProducerRoutine, rq);//}//pthread_create(&t_c, nullptr, ConsumerRoutine, rq);//for(int i = 0; i < 8; i++)//{//    pthread_create(t_c + i, nullptr, ConsumerRoutine, rq);//}//////线程等待pthread_join(t_p, nullptr);//for(int i = 0; i < 4; i++)//{//    pthread_join(t_p[i], nullptr);//}pthread_join(t_c, nullptr);//for(int i = 0; i < 8; i++)//{//    pthread_join(t_c[i], nullptr);//}delete rq;return 0;
}

【2.3】生产消费模型计算公式加保存任务模型

【Makefile文件】

# 创建替换变量并且复制对应的含义
cc := g++
standard := -std=c++11# 创建依赖关系
myThreadRingQueue: ThreadRingQueue.cc$(cc) -o $@ $^ $(standard) -l pthread# 创建辅助命令
clean:rm -rf myThreadRingQueue .PHONY: clean

【ThreadBase.hpp】

#pragma once  
#include <cstdio>
#include <cassert>
#include <iostream>
#include <functional>
#include <string>#include <pthread.h>
class ThreadBase;/* 线程上下文数据封装类 */
class ThreadBaseConnectText
{
public:ThreadBaseConnectText(): _textThis(nullptr), _textArgs(nullptr){}
public: ThreadBase*  _textThis;void*        _textArgs;
};/* 基于原生线程库的线程封装类 */
class ThreadBase
{
private:const int ctNum = 64;public:// 定义仿函数using func_t = std::function<void*(void*)>;public:public:/* - 构造函数* - func: 线程回调函数* - args:线程回调函数参数* - num : 编写线程名称设定的编号 */ThreadBase(func_t func, void* args = nullptr, const int& num = 1): _threadCallBack(func), _threadArgs(args){   // 自定义线程名称char nameBuffer[ctNum];snprintf(nameBuffer, sizeof(nameBuffer), "thread-%d", num);_threadName = nameBuffer;// 创建线程连接上下文 - 手动释放内存 - 【01】ThreadBaseConnectText* connectText = new ThreadBaseConnectText();connectText->_textThis = this;connectText->_textArgs = _threadArgs;int state = pthread_create(&_threadId, nullptr, StartRoutine, (void*)connectText);assert(state == 0); (void)state;}/* - 析构函数*/~ThreadBase() {}public:/* - 线程等待*/void Join(){int state = pthread_join(_threadId, nullptr);assert(state == 0); (void)state;   }public: /* - 获取线程名称*/std::string GetThreadName(){return _threadName;}/* - 获取线程Id*/std::string GetThreadId(){char buffer[ctNum];snprintf(buffer, sizeof(buffer), "0x%x", _threadId);return buffer;}public:/* - 线程函数*/static void* StartRoutine(void* args){ThreadBaseConnectText* connectText = static_cast<ThreadBaseConnectText*>(args);void* retVal = connectText->_textThis->Run(connectText->_textArgs);// 释放内存 - 【01】delete connectText;// 返回return retVal;}private:/* - StartRoutine专用函数(因为C/C++混编的原因)*/void* Run(void* args){// 调用回调线程return _threadCallBack(args);}private:std::string        _threadName;         // 线程名称pthread_t          _threadId;           // 线程Idfunc_t             _threadCallBack;     // 线程回调函数void*              _threadArgs;         // 线程回调函数参数
};

【ThreadMutex.hpp】

#pragma once 
#include <pthread.h>/* 原生线程锁类封装 */
class Mutex
{
public:/* - 构造函数*/Mutex(pthread_mutex_t* mutex): _pMutex(mutex){}/* - 析构函数*/~Mutex() {}public:/* - 加锁函数*/void Lock() { pthread_mutex_lock(_pMutex); }/* - 解锁函数*/void UnLock() { pthread_mutex_unlock(_pMutex); }private:pthread_mutex_t*    _pMutex;    // 内部的线程锁
};class LockGuardMutex
{
public:/* - 构造函数*/LockGuardMutex(pthread_mutex_t* mutex): _mutex(mutex){_mutex.Lock();}/* - 析构函数*/~LockGuardMutex(){_mutex.UnLock();}private:Mutex   _mutex;
};

【ThreadRingQueue.cc】

#include <ctime>
#include <iostream>
#include <memory>
#include <unistd.h>
#include "ThreadRingQueue.hpp"
#include "ThreadBase.hpp"
#include "ThreadMutex.hpp"
#include "ThreadTask.hpp"static std::string oper = "+-*/%";/* - 生产者线程函数*/
void* ProducerThread(void* args)
{sleep(1);ThreadRingQueue<TaskCalculate>* pRQ = (static_cast<ThreadRingQueues<TaskCalculate, TaskSave>*>(args))->_cTask;while(true){int x = rand() % 100 + 1;int y = rand() % 100 + 1;char op = oper[rand() % oper.size()];TaskCalculate cTask(Calculate, x, y, op);pRQ->Push(cTask);std::cout << "生产者在生产任务-> " << "[" << cTask.TaskString() << "] - - 生产者当前位置:" << pRQ->GetPosP()  << std::endl;sleep(1);}return nullptr;
}/* - 消费者线程函数*/
void* ConsumerThread(void* args)
{sleep(1);ThreadRingQueue<TaskCalculate>* cRQ = (static_cast<ThreadRingQueues<TaskCalculate, TaskSave>*>(args))->_cTask;ThreadRingQueue<TaskSave>* sRQ = (static_cast<ThreadRingQueues<TaskCalculate, TaskSave>*>(args))->_sTask;while(true){TaskCalculate cTask;cRQ->Pop(&cTask);std::string message = cTask();std::cout << "消费者在消费任务-> " << "[" << cTask() << "] - - 消费者当前位置:" << cRQ->GetPosC() << std::endl;TaskSave sTask(FileSave, message);sRQ->Push(sTask);std::cout << "ConsumerThread-推送保存完成..." << std::endl;sleep(3);}return nullptr;
}/* - 保存者线程函数*/
void* SaveThread(void* args)
{sleep(1);ThreadRingQueue<TaskSave>* sRQ = (static_cast<ThreadRingQueues<TaskCalculate, TaskSave>*>(args))->_sTask;while(true){TaskSave sTask;sRQ->Pop(&sTask);sTask();std::cout << "SaveThread-保存任务完成..." << std::endl;sleep(3);}return nullptr;
}/* = 程序入口函数*/
int main()
{// 定义随机数种子srand((unsigned int)time(nullptr));// 定义共享资源ThreadRingQueues<TaskCalculate, TaskSave>* RQ = new ThreadRingQueues<TaskCalculate, TaskSave>();RQ->_cTask = new ThreadRingQueue<TaskCalculate>;RQ->_sTask = new ThreadRingQueue<TaskSave>;std::unique_ptr<ThreadBase> ptr_pTd(new ThreadBase(ProducerThread, (void*)RQ, 1));std::cout << "创建生产者线程完成-> 线程名:" << ptr_pTd->GetThreadName() << " 线程Id:" << ptr_pTd->GetThreadId() << std::endl;sleep(10);std::unique_ptr<ThreadBase> ptr_cTd(new ThreadBase(ConsumerThread, (void*)RQ, 2));std::cout << "创建生产者线程完成-> 线程名:" << ptr_cTd->GetThreadName() << " 线程Id:" << ptr_cTd->GetThreadId() << std::endl;sleep(5);std::unique_ptr<ThreadBase> ptr_sTd(new ThreadBase(SaveThread, (void*)RQ, 3));std::cout << "创建生产者线程完成-> 线程名:" << ptr_sTd->GetThreadName() << " 线程Id:" << ptr_sTd->GetThreadId() << std::endl;ptr_pTd->Join();ptr_cTd->Join();// ptr_sTd->Join();delete RQ->_cTask;delete RQ->_sTask;delete RQ;return 0;
}

【ThreadRingQueue.hpp】

#pragma once 
#include <cstdio>
#include <cassert>
#include <iostream>
#include <vector>
#include <string>
#include <pthread.h>
#include <sys/types.h>
#include <semaphore.h>static const size_t gCapacity = 10;template <class T>
class ThreadRingQueue
{
public:/* - 构造函数*/ThreadRingQueue(const size_t& capacity = gCapacity): _capacity(capacity), _v(capacity){// 初始化信号量int n = 0;n = sem_init(&_pSem, 0, _capacity); assert(n == 0);n = sem_init(&_cSem, 0, _capacity); assert(n == 0);(void)n;// 生产者和消费者的位置初始化_pSubscript = _cSubscript = 0;// 初始化互斥锁pthread_mutex_init(&_pMutex, nullptr);pthread_mutex_init(&_cMutex, nullptr);}/* - 析构函数*/~ThreadRingQueue(){// 释放信号量sem_destroy(&_pSem);sem_destroy(&_cSem);// 释放互斥锁pthread_mutex_destroy(&_pMutex);pthread_mutex_destroy(&_cMutex);}public:/* - 生产任务*/void Push(const T& in){// 生产前:保证可以生产->空间信号量-1P(_pSem);// 加锁pthread_mutex_lock(&_pMutex);_v[_pSubscript++] = in;_pSubscript %= _capacity;// 解锁pthread_mutex_unlock(&_pMutex);// 生产完:占用一个空间->数据信号量+1V(_cSem);}/* - 消费任务*/    void Pop(T* out){// 消费前:保证可以消费->数据信号量-1P(_cSem);// 加锁pthread_mutex_lock(&_pMutex);*out = _v[_cSubscript++];_cSubscript %= _capacity;// 解锁pthread_mutex_unlock(&_pMutex);// 消费完:减少一个空间->空间信号量+1V(_pSem);}public:/* - 获取生产者当前位置*/size_t GetPosP(){return _pSubscript;}/* - 获取消费者当前位置*/size_t GetPosC(){return _cSubscript;}private:/* - 等待信号量:会将信号量的值减1。*/void P(sem_t& sem){int n = sem_wait(&sem); assert(n == 0);(void)n;}/* - 发布信号量:表示资源使用完毕,可以归还资源了。将信号量值加1*/void V(sem_t& sem){int n = sem_post(&sem); assert(n == 0);(void)n;}private:std::vector<T>      _v;             // 环形队列容器size_t              _capacity;      // 环形队列容量int                 _pSubscript;    // 生产力位置下标int                 _cSubscript;    // 消费者位置下标sem_t               _pSem;          // 生产者资源sem_t               _cSem;          // 消费者资源pthread_mutex_t     _pMutex;        // 生产者互斥锁pthread_mutex_t     _cMutex;        // 消费者互斥锁
};

【ThreadTask.hpp】

#pragma once
#include <cstdio>
#include <iostream>
#include <string>
#include <functional>
#include "ThreadRingQueue.hpp"
class TaskCalculate;
class TaskSave;template<class C, class S>
class ThreadRingQueues
{
public:ThreadRingQueue<C>* _cTask;ThreadRingQueue<S>* _sTask;
};class TaskCalculate
{
private:// 定义仿函数using func_t = std::function<int(const int, const int, const char)>;public:/* - 无参构造函数 */TaskCalculate(){}/* - 带参数的构造函数*/TaskCalculate(func_t func, const int x, const int y, const char op): _func(func), _x(x), _y(y), _op(op){}public:/* - ()运算符重载*/std::string operator()(){int result = _func(_x, _y, _op);char buffer[64];snprintf(buffer, sizeof(buffer), "%d %c %d = %d", _x, _op, _y, result);return buffer;}public:std::string TaskString(){char buffer[64];snprintf(buffer, sizeof(buffer), "%d %c %d = ?", _x, _op, _y);return buffer;}private:int     _x;    // 第一个计算值int     _y;    // 第二个计算值char    _op;   // 第三个计算值func_t  _func; // 仿函数类型
};int Calculate(const int x, const int y, const char op)
{int calRet = 0;switch(op){case '+':{calRet = x + y;break;}case '-':{calRet = x - y;break;}case '*':{calRet = x * y;break;}case '/':{if (y == 0){std::cerr << "div zero error!" << std::endl;calRet = -1;}else{calRet = x / y;}break;}case '%':{if (y == 0){std::cerr << "mod zero error!" << std::endl;calRet = -1;}else{calRet = x % y;}break;            }default:{break;}}return calRet;
};class TaskSave
{
private:using func_t = std::function<void(const std::string&)>;public:/* - 无参构造函数 */TaskSave() {}/* - 带参构造函数 */TaskSave(func_t func, const std::string& msg) : _func(func), _msg(msg){}public:/* - ()运算符重载*/void operator()(){_func(_msg);}private:std::string _msg;   func_t      _func;
};void FileSave(const std::string& msg)
{// 创建打开文件目录std::string target = "./Log.txt";// 打开文件FILE* fpath = fopen(target.c_str(), "a+");if(fpath == nullptr){std::cerr << "fopen fail!" << std::endl;return;}// 写入文件fputs(msg.c_str(), fpath);fputs("\n", fpath);// 关闭文件fclose(fpath);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/141669.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

React中setState的原理及深层理解

1.为什么使用setState React并没有实现类似于Vue2中的Object.defineProperty或者Vue3中的Proxy的方式来监听数据的变化 我们必须通过setState来告知React数据已经发生了变化 setState方法是从Component中继承过来的。 2.setState异步更新 setState设计为异步&#xff0c;可…

深度学习技巧应用28-强化学习的原理介绍与运用技巧实践

大家好,我是微学AI,今天给大家介绍一下深度学习技巧应用28-强化学习的原理介绍与运用技巧实践, 强化学习是一种机器学习的子领域,它使得一个智能体在与环境的交互中学习如何行动以最大化某种数值奖励信号。强化学习模型的关键特性是它的试错搜索和延迟奖励。 一、强化学习…

聚势共创 多元共生——中科美菱联动清华大学助力产研融合!

9月23日&#xff0c;由中科美菱首席赞助支持的第十六届清华大学“生命科学、医学、药学”博士生学术论坛在美丽的白洋淀畔——雄安新区拉开序幕。清华大学生物物理学家、中国科学院院士隋森芳、清华大学生命科学学院党委书记吴畏、雄安新区党工委员会委员、管委会副主任丁晓龙、…

Windows下创建后门隐藏用户的常见方法

文章并没有什么技术含量,纯粹是我正好在做这个事情,同时想到自己之前没有写过,所以特意写一遍记录以下 windows 下的后门用户主要分为以下4种。 启用游客用户创建普通用户创建普通隐藏用户创建影子用户 第一种启用游客用户 通过以下命令即可启用Guest用户&#xff0c;该用户是…

怎么将aac转换成mp3格式?

怎么将aac转换成mp3格式&#xff1f;AAC&#xff08;它的全称为Advanced Audio Coding&#xff09;是一种高级音频编码格式。它采用了数字音频压缩算法&#xff0c;旨在提供更高的音频质量和更低的比特率。AAC和Mp3一样都是一种有损压缩算法&#xff0c;通过移除人耳无法察觉的…

PostgreSql 统一修改date字段为timestamp

在《Powdersigner PostgreSql 同步表结构到pg数据库》中&#xff0c;导入表结构到pg数据后&#xff0c;发下时间对不上了。mysql的datetime转换后pg的变成了date了。 再同步到数据后&#xff0c;就变成日期类型了。 因为表中基本都有创建时间和修改时间&#xff0c;两个相对固…

IDEA新建.xml文件显示为普通文本

情况如下&#xff1a; 1. 在XML文件中添加*.xml的文件名模式 2. 在文本中&#xff0c;选中*.xml进行删除

Linux 进程层次分析

Linux 进程组 每个进程都有一个进程组号 (PGID) 进程组&#xff1a;一个或多个进程的集合 (集合中的进程并不孤立)进程组中的进程通常存在父子关系&#xff0c;兄弟关系&#xff0c;或功能相近 进程组可方便进程管理 (如&#xff1a;同时杀死多个进程&#xff0c;发送一个信…

ChatGPT AIGC 非常实用的AI工具集合大全

实战AI 工具箱 AIGC ChatGPT 职场案例60集, Power BI 商业智能 68集, 数据库Mysql8.0 54集 数据库Oracle21C 142集, Office, Python ,ETL Excel 2021 实操,函数,图表,大屏可视化 案例实战 http://t.csdn.cn/zBytu

云安全之访问控制介绍

访问控制技术背景 信息系统自身的复杂性、网络的广泛可接入性等因素&#xff0c;系统面临日益增多的安全威胁&#xff0c;安全问题日益突出&#xff0c;其中一个重要的问题是如何有效地保护系统的资源不被窃取和破坏。 访问控制技术内容包括访问控制策略、访问控制模型、访问…

SpringBoot @value注解动态刷新

参考资料 Spring系列第25篇&#xff1a;Value【用法、数据来源、动态刷新】【基础系列】SpringBoot配置信息之配置刷新【基础系列】SpringBoot之自定义配置源的使用姿势【基础系列】SpringBoot应用篇Value注解支持配置自动刷新能力扩展Spring Boot 中动态更新 Value 配置 一. …

什么记事本软件记录恋爱时间准确?恋爱时间计时器app有哪些?

对于很多情侣来说&#xff0c;恋爱的每一天都值得记录。所以在恋爱期间&#xff0c;情侣对恋爱天数的记录充满了浪漫和纪念的意义。记录每一天&#xff0c;不仅是对爱情的见证&#xff0c;更是对彼此之间承诺的延续。不过情侣想要记录恋爱天数&#xff0c;就需要先找到一款恋爱…

C++实现nms和softmax

最近在面试过程中遇到了手写nms的问题&#xff0c;结束后重新实现并调通了nms和softmax的代码。 1、NMS 原理&#xff08;通俗易懂&#xff09;&#xff1a; 先假设有6个候选框&#xff0c;根据分类器类别分类概率做排序&#xff0c;从小到大分别属于车辆的概率分别为A、B、C、…

Cookie 和 Session机制

Cookie HTTP 协议自身是属于 "无状态" 协议. "无状态" 的含义指的是: 默认情况下 HTTP 协议的客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系. 但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的. 例如登陆网站成功后, 第二次访…

MATLAB 安装额外工具包

接下里即可搜索并安装 “额外工具包”

从服务器指定位置下载文件

从服务器指定位置下载文件 下载文件转换成流&#xff0c;这里说两种流的方式:1. 文件流2. 字节流 下载文件转换成流&#xff0c;这里说两种流的方式: 1. 文件流 2. 字节流 一&#xff0c;字节流 String filePath“/opt/peoject/file/123/pdf”; //这个是你服务上存放文件位置…

Flutter实现PS钢笔工具,实现高精度抠图的效果。

演示&#xff1a; 代码&#xff1a; import dart:ui;import package:flutter/material.dart hide Image; import package:flutter/services.dart; import package:flutter_screenutil/flutter_screenutil.dart; import package:kq_flutter_widgets/widgets/animate/stack.dart…

UNet网络模型学习总结

github&#xff1a;Machine_Learning/网络模型/UNet at main golitter/Machine_Learning (github.com) 因为VOC数据集太大&#xff0c;上传github很慢&#xff0c;所以就没有上传VOC数据&#xff0c;只有参考的目录位置。 数据集自行下载&#xff1a;https://host.robots.ox.…

Python 逢七拍手小游戏1.0

"""逢七拍手游戏介绍&#xff1a;逢七拍手游戏的规则是&#xff1a;从1开始顺序数数&#xff0c;数到有7&#xff0c;或者是7的倍数时&#xff0c;就拍一手。例如&#xff1a;7、14、17......70......知识点&#xff1a;1、循环语句for2、嵌套条件语句if/elif/e…

旅行季《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著作想象和世界一样宽广

旅行季《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著作想象和世界一样宽广