设计模式【cpp实现版本】

news2025/5/11 21:50:17

文章目录

  • 设计模式
    • 1.单例模式代码设计
      • 1.饿汉式单例模式
      • 2.懒汉式单例模式
    • 2.简单工厂和工厂方法
      • 1.简单工厂
      • 2.工厂方法
    • 3.抽象工厂模式
    • 4.代理模式
    • 5.装饰器模式
    • 6.适配器模式
    • 7.观察者模式

设计模式

1.单例模式代码设计

​ 为什么需要单例模式,在我们的项目设计中,有时候有些类我们只需要一个就好了,就比如我们的日志类,数据库连接池类等等。在整个项目中使用同一个即可完成我们的需求功能。但这也导致可能会有多线程同时的去访问我们单例类,所以单例类的对象创建必须是线程安全的!

1.饿汉式单例模式

单例模式分为两种,一种是饿汉式,当程序还没有获取对象,实力对象就已经产生了,实力对象是一个静态对象,存放在程序的数据段。并且由于类中的静态对象在程序开始运行时就已经被创建,所以饿汉式单例模式一定是线程安全的!饿汉式单例模式实现如下:

class Singleton {
public:
	static Singleton* getInstance() {
		return &instance;
	}
private:
	static Singleton instance;
	
	Singleton() =default;
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
};
Singleton Singleton::instance;

​ 饿汉式单例模式几个需要注意的点:

  1. 构造函数私有化,默认实现即可。拷贝构造,赋值运算符重载 delete掉
  2. 提供一个类的静态成员方法,该方法是唯一能获取类的实例化对象的接口
  3. 类里面定义的静态成员对象,需要在类外进行初始化。

2.懒汉式单例模式

​ 懒汉式单例模型的创建方式是,只有当程序用到时,才会去创建,这也是正常项目中经常会去使用的模式,毕竟对于任何一款产品,没有人希望在我玩之前需要初始化一大堆内容才可以使用。

​ 懒汉式的单例模式,有两种实现方式,分为加锁版本和不加锁版本。我们先来看下加锁版本的实现:

mutex mtx;
class Singleton {
public:
	static Singleton* getInstance() {
		//锁+双重条件判断
        if (instance == nullptr) {
			lock_guard<mutex> guard(mtx);
			if (instance == nullptr) {
				instance = new Singleton();
			}
		}
		return instance;
	}
private:
	//线程所共享的内容,别缓存
	static Singleton* volatile instance;
	Singleton() =default;
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
};
Singleton* volatile Singleton::instance = nullptr;

​ 这份代码有以下的几个实现上的关键点:

  1. 锁+双重if语句判断条件。因为instance = new Singleton();这行代码其实涉及三个过程,并非原子性操作。通过外层的i可以挡掉大部分的申请。
    1. 开辟内存
    2. 构造对象
    3. 给instance赋值
  2. 线程所共享的内容,别缓存,加上volatile关键字确保安全。
  3. 类里面定义的静态成员变量,需要类外初始化。

​ 懒汉式单例模式还有一种写法,编译器也是会确保线程安全的,并且不用用户加锁,编译器在编译时会堆这种写法进行自动的加锁操作。下面这段代码非常的优美,也是我写懒汉式单例模式最喜欢使用的方法。

class Singleton {
public:
	static Singleton* getInstance() {
		static Singleton instance;
		return &instance;
	}
private:
	Singleton() = default;
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
};
  1. 函数里面的静态变量定义,只有第一次在运行到这里的时候才会初始化,保证了单例对象的唯一性。
  2. 静态对象的创建,在汇报指令上已经自动添加线程互斥指令了。可以通过Linux下,g++ -o run test.cpp -g,gdb run 验证

​ 上面这段代码即不用定义静态成员变量,也不需要加锁,非常推荐使用!非常的优美!

​ 下面是一段用于测试单例模式的代码,通过判断p1,p2,p3三个对象的地址是否相同,可以知道我们写的类是不是单例模型的!

Singleton* p1 = Singleton::getInstance();
Singleton* p2 = Singleton::getInstance();
Singleton* p3 = Singleton::getInstance();
cout << p1 <<endl<< p2 <<endl<< p3 << endl;

2.简单工厂和工厂方法

1.简单工厂

​ 为什么需要工厂模式:主要是封装了对象的创建,不封装的结果就是用户自己调用new去获取想要的对象。下面我们定义两个产品,分布是车和灯,代码如下:

//产品系列1:车
class Car {
public:
	Car(string name) :_name(name) {}
	virtual void show() = 0;
protected:
	string _name;
};
class Bmw :public Car {
public:
	Bmw(string name) :Car(name) {}
	void show() {}
		cout << "a car of Bmw " <<_name<< endl;
	}
};
class Audi :public Car {
public:
	Audi(string name) :Car(name) {}
	void show() {
		cout << "a car of Audi "<<_name<< endl;
	}
};

​ 如果我们不使用工厂,会怎么样呢?通过下面的代码我们会发现,不使用工厂,我们得知道车的型号才行,才能创建出一辆车,但是,我可能就只要一辆车而已并不在乎车的型号,但是我们创建对象却一定要给定型号,这就好比:一个士兵,他想要一把枪,但是现在这个士兵必须知道枪的型号才能拿到这个枪,这不是多此一举吗。简而言之,我只需要创建出来对象能用就行,而并不关心这个对象的创建细节。鉴于此,引入简单工厂。

Car* p1 = new Bmw("X1");
Car* p2 = new Audi("A6");
p1->show();
p2->show();

​ 下面是一个简单工厂,这个简单工厂的作用是:封装对象的创建过程,用户去创建一个工厂,工厂中封装了各个对象的new过程,对外提供的是一个createCar接口和一个想要的车的类型的参数即可。

enum CarType {
	BMW,
	AUDI
};
class SimpleFactory {
public:
	Car* createCar(CarType ct) {
		switch (ct) {
		case BMW:
			return new Bmw("X1");
		case AUDI:
			return new Audi("A6");
		default:
			cerr << "传入工厂的参数不正确" << ct << endl;
			break;
		}
		return nullptr;
	}
};

​ 现在我们再来看下简单工厂应该如何使用,当然,正常的业务场景中我们应该使用智能指针管理空间。

SimpleFactory* factory=new SimpleFactory();
Car* p1=factory->createCar(BMW);
Car* p2=factory->createCar(AUDI);
p1->show();
p2->show();

​ 通过这个简单工厂,用户就可以不需要知道想要的车的型号是上面,怎么创建的,只需要向工厂提出申请即可。
​ 但是简单工厂也有不好之处:一般来说:同一个工厂不可能创建多个不同牌子的车,而且当前这一个工厂需要做的事情也太多了吧,而且这个工厂设计不封闭!如果要增加一辆新车呢?要修改的地方可就太多了!正常来说一个工厂应该只负责一种产品的创建!所以这里我们需要引入工厂方法;

2.工厂方法

​ 为了解决简单工厂不封闭的问题,工厂方法的设计如下,定义一个工厂基类,对于每一种车的类型,都定义一个工厂,这些工厂都继承与工厂基类。目前的为了能使用多态。

class Factory {
public:
	virtual Car* createCar(string name) = 0;
};
//宝马工厂
class BMWFactory :public Factory {
public:
	Car* createCar(string name="X6") {
		return new Bmw(name);
	}
};
//奥迪工厂
class AUDIFactory :public  AbstractFactory {
public:
	Car* createCar(string name="A8") {
		return new Audi(name);
	}
};

​ 那么工厂方法该怎么用呢?

Factory* bmwfactory=new BMWFactory();
Factory* audifactory=new AUDIFactory();

Car* p1 = bmwfactory->createCar();
Car* p2 = audifactory->createCar();

p1->show();
p2->show();

​ 如果现在需要再增加一个奔驰工厂,我们并不需要去修改原有的工厂,即对已有的功能封闭!这里的这个工厂方法就是一种设计模式!

3.抽象工厂模式

​ 工厂方法已经很nice了,但还是会有一些不足之处,比如:现在考虑产品,一类产品(有关联关系的系列产品),就比如华为耳机,手机,转接头,这种一个系列的产品应该属于一个工厂。我们现在继续增加一个产品,各厂商的车灯信息:

//系列产品2 灯
class Light {
public:
	virtual void show() = 0;
};
class BmwLight :public Light {
public:
	void show() { cout << "BMW Light!" << endl; }
};
cass AudiLigh :public Light {
public:
	void show() { cout << "Audi Light" << endl; 
};

​ 我们将工厂方法改成抽象工厂,即对有一组关联关系的产品簇提供产品对象的统一创建,我们将工厂方法中的Factory基类改名成AbstractFactory,然后对这个基类进行实现:

class AbstractFactory {
public:
	virtual Car* createCar(string name) = 0;
	virtual Light* createCarLight() = 0;
	//工厂方法,创建汽车关联的产品,车灯
};

​ 那么现在的宝马工厂和奥迪工厂就变成了:

//宝马工厂
class BMWFactory :public AbstractFactory {
public:
	Car* createCar(string name) {
		return new Bmw(name);
	}
	Light* createCarLight() {
		return new BmwLight();
	}
};
//奥迪工厂
class AUDIFactory :public  AbstractFactory {
public:
	Car* createCar(string name) {
		return new Audi(name);
	}
	Light* createCarLight() {
		return new AudiLigh();
	}
};

​ 我们在使用时:

AbstractFactory* bmwfty=new BMWFactory();
AbstractFactory* audifty=new AUDIFactory();

Car* p1=bmwfty->createCar();
Light* l1=bmwfty->createCarLight();

p1->show();
l1->show();

​ 但是抽象工厂也有缺点:假如现在宝马做了一个业务,其他公司没有,如果在基类中增加某个纯虚函数。其他所以的工厂都要去重写这个业务,不重写的话自己就成抽象类了,以后就无法再进行实例化对象!几种工厂模式的总结如下:

  1. 简单工厂:Simple Factory
    • 把对象的创建封装在一个接口函数里面,通过传入不同的标识返回创建的对象。客户不用对自己负责new对象,不用了解对象创建的详细过程
    • 缺点:提供创建对象实例的接口函数不闭合,不能对修改关闭
  2. 工厂方法:Factory Method
    • Factory基类,提供一个纯虚函数(创建产品),定义派生类(具体产品的工厂)负责创建对应的产品,可以做到不同的产品,在不同的工厂里面创建,能够对现有工厂,以及产品的修改关闭。
    • 缺点:很多产品是有关联关系的,是属于一个产品簇(例子中的车和车灯),不应该放在不同的工厂里面去创建这样一是不符合实际的产品对象创建逻辑,二是工厂类太多了不好维护
  3. 抽象工厂:
    • 把有关联关系的属于一个管理簇的所有产品创建的接口函数,放在一个抽象工厂里面 AbstractFactory,派生类(具体产品的工厂)应该负责创建该产品簇里面所有的产品
    • 几个工厂的产品需要保持一致,如果某个工厂有自研的,其他工厂没有的产品将无法处理!

4.代理模式

​ 结构型模式,关心类和对象的组合,主要有代理模式,装饰器模式,适配器模式。代理Proxy模式,平时见的非常多的!通过代理类,来控制实际对象的访问权限!示意图如下:
image-20250510111829626

​ 比如现在有,客户,助理(proxy),老板(委托类),我们需要对访问老板的权限进行控制,不是任意的客户都可以访问老板!

​ 现在我们举一个视频网站的例子,有三种角色,免费,vip电影,用券才能观看的电影。我们定义出这个网站类:

class VideoSite {
public:
	virtual void freeMovie() = 0;//免费
	virtual void vipMovie() = 0;//vip电影
	virtual void ticketMovie() = 0;//用券观看电影
};

​ 假如我们实现了这三个功能,可以去服务器后台观看电影的接口,我们将这个类命名为FixBugVideoSite:

class FixBugVideoSite:public VideoSite {
public:
	virtual void freeMovie() {
		cout << "观看免费电影" << endl;
	}
	virtual void vipMovie() {
		cout << "观看vip电影" << endl;
	}
	virtual void ticketMovie() {
		cout << "观看ticket电影" << endl;
	}
};

​ 那么现在我们就可以创建一系列的代理类了,用根据用户的级别,来控制用户的观影权限。

class FreeVideoSiteProxy :public VideoSite {
public:
	FreeVideoSiteProxy() { 
        pVideo = new FixBugVideoSite(); 
    }
	~FreeVideoSiteProxy() { 
        delete pVideo; 
    }
	virtual void freeMovie() {
		pVideo->freeMovie();
	}
	virtual void vipMovie() {
		cout << "需升级vip" << endl;
	}
	virtual void ticketMovie() {
		cout << "需升级ticket" << endl;
	}
private:
	VideoSite* pVideo;
};

//同理
class VipVideoSiteProxy :public VideoSite {};

​ 设计的思想是:组合,代理类通过一个抽象类的指针指向委托类【具有全部权限】的对象,从而进行权限的控制。

​ 在具体的使用时,客户直接访问的是代理对象,所提供的有限的功能,使用方法如下:

void watchMovie(VideoSite* ptr){
	ptr->freeMovie();
	ptr->vipMovie();
	ptr->ticketMovie();
}

//普通用户
VideoSite* p1=new FreeVideoSiteProxy();
watchMovie(p1);

//vip用户
VideoSite* p2=new VipVideoSiteProxy();
watchMovie(p2);

​ 总结:客户直接访问的是代理对象所提供的有限的功能。

5.装饰器模式

​ 装饰器模式(结构模式):跟访问权限没关系,主要是增加现有类的功能(装饰),起到一个增强的功能。但是,增加现有类的功能,还有一个方法,就是新增加一个子类。为什么不通过增加子类的方法而是通过装饰器模式呢?请看下图的分析:
image-20250510112349290

​ 就拿我们之前举例子的汽车类来说,如果要给这三个品牌的汽车都增加定速巡航和自动刹车这两个功能,如果通过增加子类的方式,需要新添加6个子类,需要增加的类太多辣!为了增强现有类的功能,通过实现子类的方式重写接口,确实可以完成任务,功能扩展的,但代码中有太多的子类添加进来了!

​ 所以我们可以通过装饰器模式,来对一个类实现更多的功能扩展。如下图所示,如果通过装饰器模式,只需要提供(1+2)个类即可,几个功能几个类。
image-20250510113208266

​ 其中:CarDecorator 持有一个需要被装饰的对象(Car* 指针),ConcreteDecorator01 实现定速巡航,ConcreteDecorator02 实现自动刹车。接下来我们通过代码进行演示:

//装饰器的基类(可省略,如果没有公共的方法)
class CarDecorator :public Car {
public:
	CarDecorator(Car* p) :pCar(p) { }
private:
	Car* pCar;
};

//装饰器1 定速巡航的功能
class ConcreteDecorate01 :public Car {
public:
	ConcreteDecorate01(Car* p) :_pCar(p) {}
	void show() {
		_pCar->show();
		cout << ",定速巡航功能" ;
	}
private:
	Car* _pCar;
};

//装饰器1 定速刹车的功能
class ConcreteDecorate02 :public Car {
public:
	ConcreteDecorate02(Car* p) :_pCar(p) { }

	void show() {
		_pCar->show();
		cout << ",定速刹车" ;
	}
private:
	Car* _pCar;
};

​ 在使用的时候,使用方法如下,对于已有的类型,可以进行任意的装饰,任意的嵌套套娃。

Car* p1 = new ConcreteDecorate01(new Bmw());
//可进行任意的嵌套
p1 = new ConcreteDecorate02(p1);
Car* p2 = new ConcreteDecorate02(new Audi());

p1->show();
p2->show();

6.适配器模式

​ 适配器模式一般是为了兼容第三方库,从而要写很多适配器类,让不兼容的接口可以在一起工作。

​ 在公司中为了兼容第三方库:有条件,重构代码。无条件,适配器模式,让不兼容的接口在一起工作。

​ 在我们的生活中也会经常有类似的场景,比如我们的电脑和(TV)投影仪接口之间的转换。假如我们现在有一个只支持VGA接口的电脑和一个只支持VGA接口的投影仪:

//VGA接口类
class VGA{  
public:
	virtual void play() = 0;
};

//TV01表示支持VGA接口的投影仪
class TV01 :public VGA {
public:
	void play() {
		cout << "通过VGA接口连接投影仪,进行视频播放" << endl;
	}
};

//实现一个电脑类(只支持VGA接口)
class Computer {
public:	
	void playVideo(VGA* pVGA) {
		pVGA->play();
	}
};

​ 由于电脑只支持VGA接口,所以该方法的参数也只能支持VGA接口的指针/引用,假如现在引进了一批新的投影仪,但是新的投影仪都是只支持HDMI接口,那该怎么办呢?

  1. 换一个支持HDMI接口的电脑,这个就叫代码重构。即在电脑类中实现一个void playVideo(HMDI* pVGA)方法。
  2. 买一个转换头,能把VGA信号转成HDMI信号,这个转换头就叫适配器类

​ 我们先定义出新引进的HDMI接口的显示器TV02

class HDMI {
public:
	virtual void play() = 0;
};

class TV02 :public HDMI {
public:
	void play() {
		cout << "通过HDMI接口连接投影仪,进行视频播放" << endl;
	}
};

​ 由于电脑(VGA接口)和投影仪(HDMI接口)无法直接相连,所以需要添加适配器类,让VGA可以连接HDMI,我们定义出下面这个类:

class VGAToHMDIAdapter :public VGA {
public:
	VGAToHMDIAdapter(HDMI* p) :pHdmi(p) {}
	void play() {
		pHdmi->play();
	}
private:
	HDMI* pHdmi;
};

​ 这个类里面的void play(),该方法就是相当于转换头,做不同信号接口的转换的,有了这个转换头,我们该如何去使用呢?

Computer computer;

computer.playVideo(new VGAToHMDIAdapter(new TV02()));

​ 因为VGAToHMDIAdapter的构造函数中,需要传入一个HDMI类型的对象,但是这个类又是VGA的子类,所以可以被computer的void playVideo(VGA* pVGA)方法所接收,发生多态,在这个playVideo里面,调用pVGA->play(),实际上调用到的是适配器里面的play方法,这个play方法,去调用适配器里面传去HDMI对象的play,最后调到了TV02里面的paly方法!总结一句就是适配器模式,真的是充分的利用了多态的性质去解决问题,非常的巧妙!

7.观察者模式

​ 观察者模式是一种行为型模式:主要关注的是对象之间的通信。观察者-监听者模式(发布-订阅模式)设计模式:主要关注的是对象的一对多的关系,也就是多个对象都依赖一个对象,当该对象的状态发生改变时,其他对象都能够接收到相应的通知。

​ 一组数据(对象)=> 通过这一组数据 => 曲线图(对象1)/柱状图(对象2)/ 圆饼图(对象3)。当数据发生改变时,另外三个对象及时收到通知,做出相应的改变。我们不想去关注,我们只需要订阅后就做自己的事情。再举一个实际生活中的例子:当出版社出版报纸后,因为我们已经订阅过,报纸就会通知订阅了的对象。

​ 在代码中,我们定义出Observer1 Observer2 Observer3三个观察者类,当Subject(主题) 主题有更改,应该及时通知相应的观察者,去处理相应的事件。代码如下:

// 观察者抽象类
class Observer {
public:
	virtual void handle(int msgid) = 0;
};
//第一个观察者实例,对1号和2号消息感兴趣
class Observer1 :public Observer {
public:
	void handle(int msgid) {
		switch (msgid) {
		case 1:
			cout << "Observer1 recv 1 msg!" << endl;
			break;
		case 2:
			cout << "Observer1 recv 2 msg!" << endl;
			break;
		default:
			cout << "Observer1 recv unknown msg!" << endl;
			break;
		}
	}
};
//第二个,对2号消息感兴趣
class Observer2 :public Observer {};
//第三个,对3号消息感兴趣
class Observer3 :public Observer {};

​ 接下来我们创建主题Subject类:

class Subject {
public:
	void addObserver(Observer* obser, int msgid) {
		_subMap[msgid].push_back(obser);
	}
	void dispathch(int msg) {
		auto it = _subMap.find(msg);
		if (it != _subMap.end()) {
			for (auto pObser : it->second) {
				pObser->handle(msg);
			}
		}
	}
private:
	unordered_map<int, list<Observer*>> _subMap;
};

​ 在主题类中,放着一个哈希表,key:消息id, value:观察者基类的指针。 通过addObserver函数接口给主题增加观察者对象,通过dispathch接口,主题检测发生改变,通知相应的观察者对象处理事件

​ 在主函数中,我们应该这样使用,首先定义出观察者和监听者对象,并且通过观察着提供的addObserver方法,注册每个观察者所感兴趣的消息id。

Subject subject;
Observer* p1 = new Observer1();
Observer* p2 = new Observer2();
Observer* p3 = new Observer3();

subject.addObserver(p1, 1);
subject.addObserver(p1, 2);
subject.addObserver(p2, 2);
subject.addObserver(p3, 3);

​ 然后如果相应的id触发,主题类通过dispatch类通知到观察者类,通过观察者的指针调用到观察者相应的方法进行处理!

int msgid = 0;
while (true) {
	cout << "输入消息id:";
	cin >> msgid;
	subject.dispathch(msgid);
}

​ 以上就是观察者模式的整体架构。

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

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

相关文章

阿维塔汽车CAN总线数据适配技术解析与免破线数据采集实践

在智能电动汽车快速迭代的背景下&#xff0c;阿维塔凭借其高度集成的电子电气架构成为行业焦点。昨天我们经过实测&#xff0c;适配了该车型CAN总线数据适配的核心技术&#xff0c;从硬件接口定位、无损伤接线方案到关键动力系统数据解码进行系统性剖析&#xff0c;为智能诊断、…

用纯HTML和CSS仿写知乎登录页面

这是知乎的官方的登录页面 这是我的登录页面&#xff0c;使用 HTML CSS 进行编写。我觉得这是一个供前端新手练手的一个不错的小项目&#xff0c; 在这个系列&#xff0c;我将会用 HTML CSS 编写各大知名网站的登录界面&#xff0c;欢迎大家交流探讨。 源码展示: body{ba…

【Redis】C++如何使用redis

文章目录 1. redis客户端2. 使用通用命令3. 使用string命令3. 使用list命令4. 使用set命令5. 使用hash命令6. 使用zset命令 1. redis客户端 在前面的学习种&#xff0c;我们都是使用redis命令行客户端手动执行操作的&#xff1b;但是更多的时候&#xff0c;需要使用redis的api…

考研系列-408真题计算机组成原理篇(2010-2014)

写在前面 此文章是本人在备考过程中408真题计算机组成原理部分(2010年-2014年)的易错题及相应的知识点整理,后期复习也常常用到,对于知识提炼归纳理解起到了很大的作用,分享出来希望帮助到大家~ # 2010年 1.DRAM芯片的排列和编址方式 这个区别于多体交叉编址:这个可以理…

47.电压跌落与瞬时中断干扰的防护改善措施

电压跌落与瞬时中断干扰的防护改善措施 1. 电压跌落与瞬时中断的影响机理2. 解决措施 1. 电压跌落与瞬时中断的影响机理 跌落发生的常见场景如下&#xff1a; &#xff08;1&#xff09;电源插头接触不良&#xff0c;瞬态中断即刻恢复&#xff1b; &#xff08;2&#xff09;电…

极狐Gitlab 里程碑功能介绍

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 里程碑 (BASIC ALL) 极狐GitLab 中的里程碑是一种跟踪议题和合并请求的方法&#xff0c;这些请求是为了在特定时间段内实现更…

一次Android Fragment内存泄露的bug解决记录|Fragment not attach to an Activity

Bug描述 前些天出现了一个 bug。Activity 页面里放了一个 ViewPager2&#xff0c;其中的每一页是一个 Fragment。其中第一页的 Fragment 实现了一个监听器&#xff0c;当事件发生和首次添加到监听器管理者 listener manager 时&#xff0c;manager 会通知所有监听者&#xff0…

LVGL图像导入和解码

LVGL版本&#xff1a;8.1 概述 在LVGL中&#xff0c;可以导入多种不同类型的图像&#xff1a; 经转换器生成的C语言数组&#xff0c;适用于页面中不常改变的固定图像。存储系统中的外部图像&#xff0c;比较灵活&#xff0c;可以通过插卡或从网络中获取&#xff0c;但需要配置…

project从入门到精通(五)

目录 创建资源的基本信息 在project中创建资源工作表 ​编辑信息详解 最大单位 标准费率与加班费率 每次使用成本 成本累算 基准日历 三类资源工作表的总结——不同的资源必须要设置的属性 除了资源名称是必须设置的之外&#xff0c;剩余的资源的可设置选项如下图所…

第3.2.3节 Android动态调用链路的获取

3.2.3 Android App动态调用链路 在Android应用中&#xff0c;动态调用链路指的是应用在运行时的调用路径。这通常涉及到方法调用的顺序和调用关系&#xff0c;特别是在应用的复杂逻辑中&#xff0c;理解这些调用链路对于调试和性能优化非常重要。 1&#xff0c;动态调用链路获…

亿级流量系统架构设计与实战(六)

微服务架构与网络调用 当某个业务从单体服务架构转变为微服务架构后,多个服务之间会通过网络调用形式形成错综复杂的依赖关系。 在微服务架构中 , 一个微服务正常工作依赖它与其他微服务之间的多级网络调用。 网络是脆弱的 , RPC 请求有较大的概率会遇到超时 、 抖动 、 断…

浅聊find_package命令的搜索模式(Search Modes)

背景 find_package应该算是我们使用最多的cmake命令了。但是它是如何找到上游库的.cmake文件的&#xff1f; 根据官方文档&#xff0c;整理下find_package涉及到的搜索模式。 搜索模式 find_package涉及到的搜索模式有两种&#xff1a;模块模式(Module mode)和配置模式(Conf…

【LLaMA-Factory】使用LoRa微调训练DeepSeek-R1-Distill-Qwen-7B

【LLaMA-Factory】使用LoRa微调训练DeepSeek-R1-Distill-Qwen-7B 本地环境说明禁用开源驱动nouveau安装nvidia-smi安装Git环境安装Anaconda(conda)环境下载DeepSeek-R1-Distill-Qwen-7B模型安装LLaMA-Factory下载LLaMA-Factory安装LLaMA-Factory依赖修改环境变量安装deepspeedA…

使用lldb查看Rust不同类型的结构

目录 前言 正文 标量类型 复合类型——元组 复合类型——数组 函数 &str struct 可变数组vec Iter String Box Rc Arc RefCell Mutex RwLock Channel 总结 前言 笔者发现这个lldb挺好玩的&#xff0c;可以查看不同类型的结构&#xff0c;虽然这好像是C的东…

【Linux】线程POSIX信号量

目录 1. 整体学习思维导图 2. 信号量的概念 3. 基本接口 4. 基于环形队列的生产者消费者模型(信号量) 1. 整体学习思维导图 2. 信号量的概念 POSIX信号量和SystemV信号量作用相同&#xff0c;都是用于同步操作&#xff0c;达到无冲突的访问共享资源目的。但 POSIX可以用于线…

MySQL事务和JDBC中的事务操作

一、什么是事务 事务是数据库操作的最小逻辑单元&#xff0c;具有"全有或全无"的特性。以银行转账为例&#xff1a; 典型场景&#xff1a; 从A账户扣除1000元 向B账户增加1000元 这两个操作必须作为一个整体执行&#xff0c;要么全部成功&#xff0c;要么全部失败…

每日脚本学习5.10 - XOR脚本

xor运算的简介 异或就是对于二进制的数据可以 进行同0异1 简单的演示 &#xff1a; 结果是 这个就是异或 异或的作用 1、比较两数是否相等 2、可以进行加密 加密就是需要key 明文 :0b010110 key : 0b1010001 这个时候就能进行加密 明文 ^ key密文 还有这个加密比…

【编译原理】总结

核心 闭包&#xff0c;正则闭包 产生式&#xff08;规则&#xff09; 文法 G[S](&#xff0c;&#xff0c;P&#xff0c;S) 一组规则的集合 &#xff1a;非终结符 &#xff1a;终结符 P&#xff1a;产生式 S&#xff1a;开始符号 推导 归约 规范&#xff08;最右&#xff…

docker创建一个centOS容器安装软件(以宝塔为例)的详细步骤

备忘&#xff1a;后续偶尔忘记了docker虚拟机与宿主机的端口映射关系&#xff0c;来这里查看即可&#xff1a; docker run -d \ --name baota \ --privilegedtrue \ -p 8888:8888 \ -p 8880:80 \ -p 8443:443 \ -p 8820:20 \ -p 8821:21 \ -v /home/www:/www/wwwroot \ centos…

OpenVLA:开源的视觉-语言-动作模型

1. 简介 让我们先来介绍一下什么是OpenVLA&#xff0c;在这里&#xff1a; https://openvla.github.io/ 可以看到他们的论文、数据、模型。 OpenVLA 是一个拥有 70亿参数的开源 **视觉-语言-动作&#xff08;VLA&#xff09;**模型。它是在 Open X-Embodiment 数据集 中的 97万…