c++ 设计模式 的课本范例(下)

news2024/11/18 13:05:47

(19) 桥接模式 Bridge,不是采用类继承,而是采用类组合,一个类的数据成员是类对象,来扩展类的功能。源码如下:

class OS  // 操作系统负责绘图
{
public:
	virtual ~OS() {}
	virtual void draw(char * ptrCache , int lengthCache) = 0 ;
};

class OS_Window : public OS
{
public:
	void draw(char* ptrCache, int lengthCache) { cout << " windows 下绘图\n\n"; }
};

class OS_Linux : public OS
{
public:
	void draw(char* ptrCache, int lengthCache) { cout << " Linux 下绘图\n\n"; }
};

class Image  // 本基类有成员函数负责解析与加载图片至缓存,但绘图调用跟操作系统有关的底层函数
{
protected: OS * ptrOS;
public:
	virtual ~Image() {}
	Image(OS* p) : ptrOS(p) {}
	virtual void parseImage( const char * name) = 0;
};

class Image_jpg : public Image
{
public:
	Image_jpg(OS * p) : Image(p){}

	void parseImage(const char* name)
	{
		cout << " 解析 jpg 图片  ,  ";
		char c[100];                  // 假定分析 jpg 格式后的图片信息统一用 100 字节存储。
		ptrOS->draw(c , 100);
	}
};

class Image_png : public Image
{
public:
	Image_png(OS* p) : Image(p) {}

	void parseImage( const char* name)
	{
		cout << " 解析 png 图片  ,  ";
		char c[50];                  // 假定分析 png 格式后的图片信息统一用 50 字节存储。
		ptrOS->draw(c, 50);
	}
};

int main()
{
	OS_Window osWindow;
	OS_Linux osLinux;

	Image_jpg jpgA(&osLinux) , jpgB(&osWindow) ;
	Image_png pngA(&osLinux) , pngB(&osWindow) ;

	jpgA.parseImage("aaa"); 
	jpgB.parseImage("b");
	pngA.parseImage("c");
	pngB.parseImage("dd");

	return 0;
}

测试结果如下:

在这里插入图片描述

(20)中介者模式 Mediator 。比如 UI 设计,页面上的各种控件,牵一发而动全身,彼此联系紧密。若把控件间的联系代码写入各个控件,会很繁琐,庞大。因此可以创建一个中介对象,专门处理当一个 UI 控件的状态变化时的逻辑。也像电脑主板上的总线,借着总线,实现电脑各部件见的连接与通信。以下代码配套的是这个 UI 登录界面:

在这里插入图片描述

配套的代码如下:

class Control;  // 类的前向声明

class Media  // 中介者的基类
{
public:
	virtual ~Media() {}
	virtual void changed(Control * ptrCtrl) = 0;
};

class Control  // 控件的基类
{
protected: 
	string name;       // 控件名称,以此区分控件,所以该名称将是唯一的
	Media* ptrMedia;   // 显示本控件属于哪个中介管理
public:
	virtual ~Control() {}
	Control(const string& s, Media* pM) : name(s), ptrMedia(pM) {}
	virtual void enable(bool enabled) = 0;
	virtual void changed() { ptrMedia->changed(this); }  // 控件产生了变化,交由 中介者 来处理这种相关联的变化
};

class Control_Button : public Control
{
public:
	Control_Button(const string& s, Media* ptr) : Control(s, ptr) {}
	void enable(bool enabled)
	{ 
		if (enabled)
			cout << " 按钮 " << name << "  被启用\n\n";
		else
			cout << " 按钮 " << name << "  被禁用\n\n";
	}
};

class Control_Radio : public Control
{
public:
	Control_Radio(const string& s, Media* ptr) : Control(s, ptr) {}
	void enable(bool enabled) {}  // 对于单选按钮,不必使用此函数

	void select(bool clicked)   // 对于单选按钮,增加此函数
	{
		if(clicked)
			cout << " 单选按钮 " << name << "  被启用\n\n";
		else
			cout << " 单选按钮 " << name << "  被禁用\n\n";
	}
};

class Control_text : public Control
{
private : string text;  // 文本框里保存了账号或密码的内容
public:
	Control_text(const string & t , const string& s, Media* ptr) : text(t) , Control(s, ptr) {}
	Control_text(const string& s, Media* ptr) : text(""), Control(s, ptr) {}

	void enable(bool enabled)
	{
		if (enabled)
			cout << " 文本框 " << name << "  被启用\n\n";
		else
			cout << " 文本框 " << name << "  被禁用\n\n";
	}

	void setText(const string& s) { text = s; }  // 对文本框增加此函数
	bool isEmpty() { return text.empty(); }      // 判断文本框是否为空
};

class Media_UI : public Media
{
private:
	Control_Button* pLogin, * pLogout;
	Control_Radio* pRadioTourist, * pRadioAccount;
	Control_text *pTextName, * pTextPwd;
public:
	Media_UI(){}

	void init(Control_Button* pLin, Control_Button*pLout, Control_Radio* pTourist, Control_Radio* pAccount, Control_text* pName, Control_text* pPwd)
	{
		pLogin = pLin; pLogout = pLout;
		pRadioTourist = pTourist; pRadioAccount = pAccount;
		pTextName = pName; pTextPwd = pPwd;
	}

	virtual void changed(Control* ptrCtrl)
	{
		if (ptrCtrl == pLogout) { cout << " 游戏退出!\n\n"; return; }
	
		if (ptrCtrl == pRadioTourist)
		{
			pRadioTourist->select(true);
			pRadioAccount->select(false);

			pTextName->enable(false);
			pTextPwd->enable(false);
			pLogin->enable(true);

			return;
		}

		if (ptrCtrl == pLogin) { cout << " 开始登录验证\n\n"; }

		if (ptrCtrl == pRadioAccount)
		{
			pRadioTourist->select(false);
			pRadioAccount->select(true);

			pTextName->enable(true);
			pTextPwd->enable(true);

			if (pTextName->isEmpty() || pTextPwd->isEmpty())
				pLogin->enable(false);
			else
				pLogin->enable(true);
		}

		if (ptrCtrl == pTextName || ptrCtrl == pTextPwd)
		{
			if (pTextName->isEmpty() || pTextPwd->isEmpty())
				pLogin->enable(false);
			else
				pLogin->enable(true);
		}

	}
};

int main()
{
	Media_UI media_UI;
	
	Control_Button login("登录", &media_UI), logout("退出" , &media_UI);
	Control_Radio tourist("游客登录", &media_UI), account("账户登录" , &media_UI);
	Control_text name("昵称", &media_UI), password("密码" , &media_UI);

	media_UI.init(&login , &logout , & tourist , & account , & name , & password);

	login.changed();	logout.changed();
	tourist.changed();  account.changed();
	name.changed();     password.changed();

	name.setText("aaa"); password.setText("1234556");
	login.changed(); account.changed(); name.changed();

	return 0;
}

测试结果如下:

在这里插入图片描述

(21) 备忘录模式 Memento , 也可以翻译为更简单的 Note 。例如游戏里在某个时刻保存玩家的当时信息。

class Fighter; 

class Note  // 备忘录模式
{
private:
	int life, magic, attack;

	friend Fighter;  // 本类对 Fighter 类开放所有权限

	Note(int life, int m, int a) :life(life), magic(m), attack(a) {}  // 私有的构造函数

	int getLife() { return life; }
	int getMagic() { return magic; }
	int getAttack() { return attack; }
	void setLife(int t) { life = t; }
	void setMagic(int t) { magic = t; }
	void setAttack(int t) { attack = t; }
public:
	
};

class Fighter
{
private:
	int life, magic, attack;
public:
	Fighter(int life, int m, int a) :life(life), magic(m), attack(a) {}

	Note* createNote() { return new Note(life ,magic , attack); }  // 此处返回去的指针要记得回收内存,可以用智能指针包装下;

	void restoreFromNote(Note* ptr) { life = ptr->life; magic = ptr->magic; attack = ptr->attack; }

	void show() 
	{
		cout << " 玩家当前的 life , magic , attack : " << life << "  " << magic << "  " << attack << '\n';
	}

	void setStatus() { life /= 2; magic /= 2; attack /= 2; }
};

int main()
{
	Fighter f(100,200,300);

	unique_ptr<Note> uniNote(f.createNote());

	f.show();
	f.setStatus();
	f.show();
	f.restoreFromNote(uniNote.get());
	f.show();

	return 0;
}

测试结果如下:

在这里插入图片描述

( 22) 责任链模式 Chain Of Responsibility 。比如员工的加薪申请,申请的金额越大,就越需要更高层的领导审批。可以把负责审批的管理,创建为一个个对象,并把它们串起来。

class RaiseRequest   // 本类的含义是来自某个人的加薪请求
{
private:
	string name;
	int request;
public:
	RaiseRequest(const string& s, int t) : name(s), request(t) {}
	int getRequest() const { return request; }  // 这里的 const 不能省略,const 修饰的是 this
};

class Manager // 可以审批加薪请求的领导
{
private:
	Manager* next = nullptr;
public:
	virtual ~Manager() {}
	void setChain(Manager* n) { next = n; }
	Manager* getNext() { return next; }
	virtual void processRequest(const RaiseRequest& raiReq) = 0;
};

class Manager_Low : public Manager
{
public:
	virtual void processRequest(const RaiseRequest& raiReq)
	{
		auto t = raiReq.getRequest();
		if (t < 5000)
			cout << " 加薪请求 " << t << "  已被低级管理处理\n\n";
		else if (getNext())
			getNext()->processRequest(raiReq);
		else
			cout << " 没有合适的管理;来处理此加薪请求\n\n";
	}
};

class Manager_High : public Manager
{
public:
	virtual void processRequest(const RaiseRequest& raiReq)
	{
		auto t = raiReq.getRequest();
		if ( t >= 5000 && t < 10000)
			cout << " 加薪请求 " << t << "  已被高级管理处理\n\n";
		else if (getNext())
			getNext()->processRequest(raiReq);
		else
			cout << " 没有合适的管理来处理此  " << t << "  的加薪请求\n\n";
	}
};

int main()
{
	Manager_Low mL;
	Manager_High mH;
	mL.setChain(&mH);

	RaiseRequest a("zhangsan" , 500) , b("Lisi" , 6000) , c("wangWu" , 13000);

	mL.processRequest(a);
	mL.processRequest(b);
	mL.processRequest(c);

	return 0;
}

测试结果如下:

在这里插入图片描述

以上的责任链中,只需要有一个链节处理事务即可,叫单纯责任链。但换个场景,比如网络用语过滤,需要进行性 、脏话 、 政治敏感词,多重过滤,就叫非单纯的责任链模式,链中的每个链节都要被调用,处理事务。

class Filter // 过滤器过滤文本,以使文本文明
{
private:
	Filter* next = nullptr;
public:
	virtual ~Filter() {}
	void setChain(Filter* n) { next = n; }
	Filter* getNext() { return next; }
	virtual void processRequest(string& str) = 0; // 以形参引用带回返回值
};

class Filter_Sexy : public Filter    // 过滤性敏感词
{
public:
	virtual void processRequest(string& str)
	{
		cout << " sexy 敏感被处理\n\n";

		str += "XXX";

		if(getNext())
			getNext()->processRequest(str);
	}
};

class Filter_Policy : public Filter  // 过滤政治敏感词
{
public:
	virtual void processRequest(string& str)
	{
		cout << " policy 敏感被处理\n\n";

		str += "ZZZ";

		if (getNext())
			getNext()->processRequest(str);
	}
};

int main()
{
	Filter_Sexy fSex;
	Filter_Policy fPlcy;
	fSex.setChain(&fPlcy);

	string s("  测试文字  ");
	fSex.processRequest(s);

	cout << "  最终的测试文字:  " << s << "\n\n";
	return 0;
}

测试结果如下:

在这里插入图片描述

(23) 访问者模式 Visitor 。此模式是为了解决如下案例的编码:

在这里插入图片描述

某人去看病,大夫列了一个药品单。这些药品就是一个个被访问的对象。这些对象会被不同的人访问。收费处要根据药的价格收费,药房根据药名取药,营养师根据药制定食谱,健身教练根据药制定训练方案。这里引入双重分派机制(double dispatch):谁被访问,又是谁来访问。非常贴合访问者模式。以下给出代码例子与测试结果。

class Visitor;

class Medicine
{
public:
	virtual ~Medicine() {}
	virtual string getName() = 0;
	virtual double getPrice() = 0;
	virtual void accept(Visitor* visitor) = 0;
};

class Medicine_Eye : public Medicine
{
public:
	virtual string getName() { return " 眼药 "; }
	virtual double getPrice() { return 10.5; }
	virtual void accept(Visitor* visitor);       // 这一步声明不可少,不然报错,而且与上面的 = 0 ,还不一样
};

class Medicine_Nose : public Medicine
{
public:
	virtual string getName() { return " 鼻药 "; }
	virtual double getPrice() { return 100.5; }
	virtual void accept(Visitor* visitor);       // 这一步声明不可少
};

class Visitor
{
public:
	virtual ~Visitor() {}
	virtual void visit_MediEye(Medicine_Eye * mediEye) = 0;  
	virtual void visit_MediNose(Medicine_Nose * mediNose) = 0;  
};

class Visitor_Charge : public Visitor   // 收费人员
{
private: double totalPrice = 0;
public:
	void visit_MediEye(Medicine_Eye* mediEye)
	{
		totalPrice += mediEye->getPrice();

		cout << " 收费人员统计药品  " << mediEye->getName() << "  ,价格为: " << mediEye->getPrice() << "\n\n";
	}

	void visit_MediNose(Medicine_Nose* mediNose)
	{
		totalPrice += mediNose->getPrice();

		cout << " 收费人员统计药品  " << mediNose->getName() << "  ,价格为: " << mediNose->getPrice() << "\n\n";
	}

	double getTotalPrice() { return totalPrice; }
};

class Visitor_Distribute : public Visitor   // 收费人员
{
public:
	void visit_MediEye(Medicine_Eye* mediEye)
	{
		cout << " 药房人员分发药品  " << mediEye->getName() << "\n\n";
	}

	void visit_MediNose(Medicine_Nose* mediNose)
	{
		cout << " 药房人员分发药品  " << mediNose->getName() << "\n\n";
	}
};

class Visitor_Trainer : public Visitor
{
public:
	void visit_MediEye(Medicine_Eye* mediEye)
	{
		cout << " 健身教练认为要做眼保健操\n\n";
	}

	void visit_MediNose(Medicine_Nose* mediNose)
	{
		cout << " 健身教练认为要多跑步\n\n";
	}
};

void Medicine_Eye ::accept(Visitor* visitor) { visitor->visit_MediEye(this);  }
void Medicine_Nose::accept(Visitor* visitor) { visitor->visit_MediNose(this); }

int main()
{
	Medicine_Eye  mediEye;
	Medicine_Nose mediNose;

	Visitor_Charge     visiCharge;
	Visitor_Distribute visiDistri;
	Visitor_Trainer    visiTrainer;

	mediEye.accept(&visiCharge);
	mediEye.accept(&visiDistri);
	mediEye.accept(&visiTrainer);
	
	cout << "  -------------------------------\n\n";

	mediNose.accept(&visiCharge);
	mediNose.accept(&visiDistri);
	mediNose.accept(&visiTrainer);

	cout << " 总的药钱: " << visiCharge.getTotalPrice() << endl;

	return 0;
}

测试结果如下:

在这里插入图片描述

(24) 解释器模式 Interpreter 。一门计算机语言,必须背后有一个匹配的编译器对其支持,将其翻译为机器语言,此门新的编程语言才是有效的。编译器模式就是讲如何为一门语言编写匹配的编译器。为举例简单,规定本语言只支持整数的加减运算,没有括号,且表示整数的字符必须是单个的英文字母。整数与加减运算符之间,书写的时候不留空格。因为为语言编写编译器具有类似的地方,故称其为一种模式。
以下是例子代码:

#include<xstring>
#include<map>
#include<stack>

class Node  // 对语言翻译的结果是将其组织成语法树。故需要定义树节点的结构
{
public:
	char type;  // 节点类型,取值: v  +  -

	virtual ~Node() {}
	Node(char c) : type(c) {}

	virtual int interpret(map<char , int>& mapVar) = 0;  // 形参对运算符节点没用,但对整数节点有用,返回变量名对应的值
};

class Node_Var : public Node  // 变量节点
{
private : char varName ; 
public:
	Node_Var(char name, char type) : varName(name), Node(type) {}
	int interpret( map<char, int>& mapVar) { return mapVar[varName]; }
};

class Node_Symbol : public Node  // 运算符节点的父类
{
protected : Node* ptrLeft, * ptrRight;
public:
	Node_Symbol(Node* left, Node* right, char type) : ptrLeft(left), ptrRight(right), Node(type) {}

	Node* getLeftChild()  { return ptrLeft;  }
	Node* getRightChild() { return ptrRight; }
};

class Node_Add : public Node_Symbol       // 加号运算符
{
public:
	Node_Add(Node* left, Node* right, char type) : Node_Symbol(left , right , type) {}

	virtual int interpret(map<char, int>& mapVar)  // 本函数可以被递归调用,返回左子树与右子树的值的和
	{
		return ptrLeft->interpret(mapVar) + ptrRight->interpret(mapVar);
	}
};

class Node_Sub : public Node_Symbol       // 减号运算符
{
public:
	Node_Sub(Node* left, Node* right, char type) : Node_Symbol(left, right, type) {}

	virtual int interpret(map<char, int>& mapVar)  // 本函数可以被递归调用,返回左子树与右子树的值的差
	{
		return ptrLeft->interpret(mapVar) - ptrRight->interpret(mapVar);
	}
};

Node* buildTree(string& expr)   // #include<stack>
{
	stack<Node* > stackNode;                // 这个栈很有作用
	Node* ptrLeft = nullptr, * ptrRight = nullptr;

	for (int i = 0; i < expr.size(); i++)
		switch (expr[i])
		{
		case '+':
			ptrLeft = stackNode.top();
			++i;
			ptrRight = new Node_Var(expr[i], 'v');
			stackNode.push( new Node_Add(ptrLeft , ptrRight , '+') );
			break;
		case '-':
			ptrLeft = stackNode.top();
			++i;
			ptrRight = new Node_Var(expr[i], 'v');
			stackNode.push(new Node_Sub(ptrLeft, ptrRight, '-'));
			break;
		default:
			stackNode.push( new Node_Var(expr[i] , 'v') );
			break;
		}

	return stackNode.top();
}

void release(Node * node)  // 回收语法树占据的内存,可能被递归调用
{
	if (node->type == 'v')
		delete node;
	else
	{
		release(((Node_Symbol*)node)->getLeftChild() );
		release(((Node_Symbol*)node)->getRightChild() );

		delete node;
	}
}

int main()
{
	string expr("a-b+c+d");

	map<char, int> mapVar;
	mapVar.insert(pair('a' , 7));
	mapVar.insert(pair('b' , 9));
	mapVar.insert(pair('c' , 3));
	mapVar.insert(pair('d' , 2));

	Node* root = buildTree(expr);

	cout << "  表达式为:  " << expr << "\n\n";
	cout << "  结果为 : " << root->interpret(mapVar) << "\n\n";

	release(root);

	return 0;
}

测试结果如下:

在这里插入图片描述

接着为本解释器模式再举一例。机器人控制,为此创建一门语言,我们可以使用如下 的语句: left walk 15 and up run 20 。
该语言有如下几个关键字 : left 、 right 、 up 、 down ,表示运动的方向; walk 、 run 表示运动的方式 , 整数表示运动的距离;and 表示衔接两个运动。用空格分隔关键字。我们为其创建解释器。

#pragma warning(disable : 4996)   // 必须要有,要不 strcpy 与 strtok 都会报错

#include<string>
#include<xstring>
#include<map>
#include<stack>
#include<vector>

class Node
{
public:
	virtual ~Node() {}
	virtual string interpret() = 0;
};

class Node_Direction : public Node
{
private: string direction;
public:
	Node_Direction(const string& s) : direction(s) {}

	virtual string interpret()
	{
		if (direction == "left")        return "向左";
		else if (direction == "right")  return "向右";
		else if (direction == "up")     return "向上";
		else if (direction == "down")   return "向下";
		else                            return "方向错误";
	}
};

class Node_mode : public Node  // walk  or   run 
{
private: string mode;
public:
	Node_mode(const string& s) : mode(s) {}

	virtual string interpret()
	{
		if (mode == "walk")        return "走步";
		else if (mode == "run")    return "跑步";
		else                       return "动作错误";
	}
};

class Node_Distance : public Node  // 距离
{
private: string distance;
public:
	Node_Distance(const string& s) : distance(s) {}

	virtual string interpret() { return " " + distance + " 米 "; }
};

class Node_Sentence : public Node
{
private:
	Node* ptrDistance;  // 这里用父类指针,指向子类对象,否则报错
	Node* ptrDirection;
	Node* ptrMode;
public:
	Node_Sentence(Node* pDirect, Node* pMode, Node* pDistance)	: ptrDirection(pDirect), ptrMode(pMode), ptrDistance(pDistance) {}

	Node* getDirection() { return ptrDirection; }
	Node* getMode()      { return ptrMode; }
	Node* getDistance()  { return ptrDistance; }

	virtual string interpret()
	{
		return ptrDirection->interpret() + ptrMode->interpret() + ptrDistance->interpret() ;
	}
};

class Node_And : public Node
{
private:
	Node* ptrLeft , * ptrRight;
public:
	Node_And(Node* pL, Node* pR) : ptrLeft(pL), ptrRight(pR) {}

	Node* getLeft()  { return ptrLeft;  }
	Node* getRight() { return ptrRight; }

	virtual string interpret() { return ptrLeft->interpret() + "  又  " + ptrRight->interpret(); }
};

Node* buidTree(string & expr)
{
	stack<Node*> stackNode;
	vector<string> vecStr;

	Node* pDirection = nullptr, * pMode = nullptr, * pDistence = nullptr, * pLeft = nullptr, * pRight = nullptr;

	char* cache = new char[expr.size() + 1 ];
	strcpy(cache , expr.c_str());

	char* pstrChild = strtok(cache , " ");
	while (pstrChild)
	{
		vecStr.push_back(string(pstrChild));  // 把原来的语句拆分成了一个个语法节点
		pstrChild = strtok(NULL , " ");
	}

	delete[] cache;

	for( auto iter = vecStr.begin() ; iter != vecStr.end() ; iter++ )
		if (*iter == "and")
		{
			pLeft = stackNode.top();
		
			++iter;
			pDirection = new Node_Direction(*iter);
			++iter;
			pMode = new Node_mode(*iter);
			++iter;
			pDistence = new Node_Distance(*iter);
			pRight = new Node_Sentence(pDirection, pMode, pDistence);

			stackNode.push(new Node_And(pLeft , pRight));
		}
		else
		{
			pDirection = new Node_Direction(* iter);
			++iter;
			pMode = new Node_mode(* iter);
			++iter;
			pDistence = new Node_Distance(* iter);
			
			stackNode.push(new Node_Sentence(pDirection , pMode , pDistence));
		}

	return stackNode.top();
}

void release(Node* ptr)   // 本函数也会被递归调用
{
	Node_And* pAnd = dynamic_cast<Node_And*>(ptr);

	if (pAnd)
	{
		release(pAnd->getLeft()) ;
		release(pAnd->getRight());
	}
	else
	{
		Node_Sentence* pSentance = dynamic_cast<Node_Sentence*>(ptr);

		if (pSentance)
		{
			release(pSentance->getDirection());
			release(pSentance->getMode());
			release(pSentance->getDistance());
		}
	}

	delete ptr;
}

int main()
{
	string expr("right walk 15 and up run 30");
	Node* ptrRoot = buidTree(expr);
	cout << "  机器人控制代码为:  " << expr << "\n\n";
	cout << " 解释器解释后: " << ptrRoot->interpret() << "\n\n";
	release(ptrRoot);
	return 0;
}

测试结果如下:

在这里插入图片描述

经 MFC 框架测试,没有内存泄露:

在这里插入图片描述

至此,王老师的 《c++ 设计模式》 的课本例子,已抄写完毕,谢谢王老师。

谢谢

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

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

相关文章

C++ thread线程库

thread库 std::thread 是 C 标准库中的一个类&#xff0c;用于管理和控制单个执行线程。线程允许程序并行执行多个函数&#xff0c;从而提高性能和响应速度。std::thread 类提供了一种便捷的方式来创建和操作线程。 1、用途 并行执行任务&#xff1a; 通过 std::thread&…

守护家庭的安全卫士:家用可燃气体探测器

在这个追求智能与安全并重的时代&#xff0c;每一个细微之处的防护都显得尤为重要&#xff0c;尤其是在我们最为依赖的家庭空间里。当谈及家居安全&#xff0c;燃气安全无疑占据着至关重要的位置。据统计&#xff0c;每年因燃气管老化、连接处松动等问题引发燃气泄漏的事故不在…

外泌体相关基因肝癌临床模型预测——2-3分纯生信文章复现——4.预后相关外泌体基因确定单因素cox回归(2)

内容如下&#xff1a; 1.外泌体和肝癌TCGA数据下载 2.数据格式整理 3.差异表达基因筛选 4.预后相关外泌体基因确定 5.拷贝数变异及突变图谱 6.外泌体基因功能注释 7.LASSO回归筛选外泌体预后模型 8.预后模型验证 9.预后模型鲁棒性分析 10.独立预后因素分析及与临床的…

STM32-HAL-FATFS(文件系统)(没做完,stm32f103zet6(有大佬的可以在评论区说一下次板子为什么挂载失败了))

1STM32Cube配置 1-1配置时钟 1-2配置调试端口 1-3配置uart 1-4配置SDIO&#xff08;注意参数&#xff09;&#xff08;其中他的初始化的异常函数给注释&#xff0c;SD卡文件写了&#xff09; 配置了还要打开中断和DMA可在我的其他文章中看一样的 1-5配置FatFs (只改了图选中…

Unity 资源 之 Sweet Cakes Icon套装,110个高品质蛋糕图标分享

Sweet Cakes Icon 套装 - 为 Unity 开发者带来甜蜜惊喜 前言资源包内容领取兑换码 前言 亲爱的 Unity 开发者们&#xff0c;今天要向你们介绍一款令人心动的图标套装 - Sweet Cakes Icon。 Sweet Cakes Icon 套装包含了超过 110 种高品质的蛋糕和纸杯蛋糕图标&#xff0c;这无…

累积分布函数的一些性质证明

性质1&#xff1a; E [ X ] ∫ 0 ∞ ( 1 − F ( x ) ) d x − ∫ − ∞ 0 F ( x ) d x ( 1 ) E[X]\int_0^{\infty}(1-F(x))dx - \int_{-\infty}^0F(x)dx\quad (1) E[X]∫0∞​(1−F(x))dx−∫−∞0​F(x)dx(1) 证明&#xff1a; E [ X ] ∫ − ∞ ∞ x p ( x ) d x E[X] …

一个人的开发团队:前后端与调动AI

作为一名大数据开发者&#xff0c;我对 AI 的应用感兴趣&#xff0c;但平常都是处理数据&#xff0c;对应前后端代码不擅长&#xff0c;幸好有了 AI&#xff0c;在 AI 的帮助下能写出很多前后端代码了。 目录 前端开发后端开发调用AI整合前后端与AI 本文将通过一个简单的项目…

GO语言入门之准备

一、Go的简介 1.什么是Go Go 是一个开源的编程语言&#xff0c;最早起源于2007年&#xff0c;在2009年正式对外发布&#xff0c;Go 语言被设计成一门应用于搭载 Web 服务器&#xff0c;存储集群或类似用途的巨型中央服务器的系统编程语言。它能让构造简单、可靠且高效的软件变…

国产麒麟v10、UOS系统在线比较两个Word文件的内容差异

调用PageOffice的WordCompare方法&#xff0c;同时在线打开两个Word文档&#xff0c;可以切换显示其中的一个文档&#xff0c;或者显示两个文档的对比结果&#xff0c;即可实现在线的文档内容比较功能。此功能可以应用在以下方面&#xff1a; 文档管理中&#xff0c;比较两个版…

Day05-01-jenkins进阶

Day05-01-jenkins进阶 10. 案例07: 理解 案例06基于ans实现10.1 整体流程10.2 把shell改为Ansible剧本10.3 jk调用ansible全流程10.4 书写剧本 11. Jenkins进阶11.1 jenkins分布式1&#xff09;概述2&#xff09;案例08&#xff1a;拆分docker功能3&#xff09;创建任务并绑定到…

AI绘画擦边变现赛道怎么玩?新手小白必看教程!

今天给大家介绍一个用 AI 搞擦边的变现赛道 而且可以说是0 成本变现的 现在真的越来越多的人都想 0 成本变现&#xff0c;那么 0 成本到底能不能变现&#xff0c;变现的上下限又是多少&#xff1f; 今天这个案例就可以很好的进行说明 可以说 AI 是现在第一生产力&#xff0…

代码随想录——柠檬水找零(Leetcode860)

题目链接 贪心 class Solution {public boolean lemonadeChange(int[] bills) {if(bills[0] 10 || bills[0] 20 || bills[1] 20){return false;}int count5 1;int count10 0;for(int i 1; i < bills.length; i){if(bills[i] 5){count5;}if(bills[i] 10){count10;…

VBA常用的字符串内置函数

前言 在VBA程序中&#xff0c;常用的内置函数可以按照功能分为字符串函数、数字函数、转换函数等等&#xff0c;本节主要会介绍常用的字符串的内置函数&#xff0c;包括Len()、Left()、Mid()、Right()、Split()、String()、StrConV()等。 本节的练习数据表以下表为例&#xff…

Mybatis实现RBAC权限模型查询

RBAC权限模型 Role-Based Access Control&#xff0c;中文意思是&#xff1a;基于角色&#xff08;Role&#xff09;的访问控制。这是一种广泛应用于计算机系统和网络安全领域的访问控制模型。 简单来说&#xff0c;就是通过将权限分配给➡角色&#xff0c;再将角色分配给➡用…

leetcode--二叉树中的最长交错路径

leetcode地址&#xff1a;二叉树中的最长交错路径 给你一棵以 root 为根的二叉树&#xff0c;二叉树中的交错路径定义如下&#xff1a; 选择二叉树中 任意 节点和一个方向&#xff08;左或者右&#xff09;。 如果前进方向为右&#xff0c;那么移动到当前节点的的右子节点&…

盘点AI做自媒体五条赚钱路径,新手小白必看(附教程)

前言 盘点用AI做自媒体赚钱的五条路径&#xff0c;只要学会使用AI工具&#xff0c;你也可以马上赚到钱。 我认为短视频是趋势&#xff0c;但相比于短视频而言&#xff0c;AI则是未来更大的趋势。 AI现在才属于萌芽期&#xff0c;好比是98年的互联网和07年的移动互联网&#x…

Jmeter使用JSON Extractor提取多个变量

1.当正则不好使时&#xff0c;用json extractor 2.提取多个值时&#xff0c;默认值必填&#xff0c;否则读不到变量

Linux - Shell 以及 权限问题

目录 Shell的运行原理 Linux权限问题 Linux权限的概念 如何实现用户账号之间的切换 如何仅提升当前指令的权限 如何将普通用户添加到信任列表 Linux权限管理 文件访问者的分类&#xff08;人&#xff09; 文件类型和访问权限&#xff08;事物属性&#xff09; 文件权限值的表…

【基于深度学习方法的激光雷达点云配准系列之GeoTransformer】——模型部分浅析(1)

【GeoTransformer系列】——模型部分 1. create_model2. model的本质3. 模型的主要结构3.1 backbone3.2 transformer本篇继续对GeoTransformer/experiments/geotransformer.kitti.stage5.gse.k3.max.oacl.stage2.sinkhorn/下面的trainval.py进行详细的解读,主要是模型部分, 可以…