详解c++---特殊类设计

目录标题

  • 设计一个不能被拷贝的类
  • 设计一个只能从堆上创建对象的类
  • 设计一个只能在栈上创建对象的类
  • 设计一个无法被继承的类
  • 什么是单例模式
  • 饿汉模式
  • 饿汉模式的缺点
  • 懒汉模式
  • 懒汉模式的优点
  • 懒汉模式的缺点
  • 特殊的懒汉

设计一个不能被拷贝的类

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。那么c++98采用的方式就是将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可,比如说下面的代码:

class CopyBan
{
    // ...

private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    //...
};

设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就不能禁止拷贝了,只声明不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数,那么这里的代码就如下:

class CopyBan
{
    // ...
    CopyBan(const CopyBan&)=delete;
    CopyBan& operator=(const CopyBan&)=delete;
    //...
};

设计一个只能从堆上创建对象的类

我们可以在三个位置上创建类的对象,分别为堆上栈上和静态区上,创建的方式如下:

int main()
{
    HeapOnly tmp1;//栈上
    HeapOnly* tmp2 = new HeapOnly;//堆上
    static HeapOnly tmp3;//静态区上
}

创建对象的时候必须得调用构造函数,那么这里就可以将构造函数放到私有里面,然后创建一个函数通过new来着堆上创建对象,但是这里会有一个先有鸡还是先有蛋的问题,所以我们把这个创建对象的函数变为静态的,比如说下面的代码:

class HeapOnly
{
public:
    static HeapOnly* CreateObject()
    {
        return new HeapOnly;
    }
private:
    HeapOnly() {}
};

这样写就可以让该容器智能在堆上创建空间,那么上面的代码运行的结果就如下:
在这里插入图片描述
可以看到上面的代码就出问题原因时无法调用构造函数,那么这里要想创建对象就智能调用里面的函数,比如说下面的代码

int main()
{
    HeapOnly* tmp = HeapOnly::CreateObject();
}

虽然上面的写法可以有效的避免在栈上和静态区上创建对象,但是它依然可以通过拷贝构造在栈上创建对象,所以还得把拷贝构造禁止掉,比如说下面的代码:

class HeapOnly
{
public:
    static HeapOnly* CreateObject()
    {
        return new HeapOnly;
    }
private:
    HeapOnly() {}
    HeapOnly(const HeapOnly&) = delete;
};

这里还有个方法就是将析构函数私有化,因为栈上的变量会自动销毁并调用析构函数,所以当对象的生命周期结束之后就会自动地调用析构函数来释放空间,但是析构函数私有化了所以掉不动,也就进一步阻止了对象在栈上船舰,但是堆上创建的对象也是掉不了析构函数,所以这里可以创建一个函数给堆上的变量能够调用析构函数来释放空间,那么这里地代码就如下:

#include<iostream>
using namespace std;
class HeapOnly
{
public:
    HeapOnly()
    {}
    void Destory()
    {
        this->~HeapOnly();
    }
private:
    ~HeapOnly()
    {}
    HeapOnly(const HeapOnly& tmp) = delete;
};
int main()
{
    HeapOnly* tmp1 = new HeapOnly;
    tmp1->Destory();
    return 0;
}

运行地结果也是没有错误的
在这里插入图片描述

设计一个只能在栈上创建对象的类

这里也是跟上面的思路差不多,创建一个函数,函数中栈上创建对象并返回该对象,比如说下面的代码:

class StackOnly
{
public:
    static StackOnly creatobj()
    {
        return StackOnly();
    }
private:
    StackOnly()
    {}
};

那么我们就可以用下面的代码在栈上创建对象:

{
    StackOnly tmp1 = StackOnly::creatobj();
    return 0;
}

如果在其他位置上创建空间的话就会直接报错,比如说下面的代码:

int main()
{
    StackOnly* tmp1 = new StackOnly;
    static StackOnly tmp2;
    return 0;
}

报错的内容如下:
在这里插入图片描述
但是这种方法没有完全的封死,我们依然可以通过拷贝构造在静态区上开辟空间,比如说下面的代码:

int main()
{
    static StackOnly tmp1 = StackOnly::creatobj();
    return 0;
}

那能不能把拷贝构造函数也封掉的呢?答案使不行的,因为拷贝构造函数被封掉之后不仅静态区上无法创建空间,而且栈上也没有办法创建对象,这里还有种方法就是封掉operator new和operator delete,比如说下面的代码:

class StackOnly
{
public:
    StackOnly()
    {}
    void* operator new(size_t size) = delete;
    void operator delete(void* p) = delete;
private:
};

那么这个时候再使用new创建对象就会直接报错,比如说下面的运行结果:
在这里插入图片描述

但是这种方法只能保证不能在堆上开辟空间,无法保证在静态区上也不能开辟空间,比如说下面的代码还是可以正常运行的:

int main()
{
    static StackOnly tmp2;
    return 0;
}

所以痛过常规方法这里没有办法完全封死的,要想完全封死的话就只有一个办法就是封掉拷贝构造函数并且不接受对象,通过引用或者创建临时匿名对象来直接调用函数,比如说下面的代码:

int main()
{
    StackOnly::creatobj().Print();
	const StackOnly& so4 = StackOnly::creatobj();
	so4.Print();
    return 0;
}

代码的运行结果如下:
在这里插入图片描述
但是这样的实现存在一个缺陷就是无法修改对象里面的内容。

设计一个无法被继承的类

方法一就是构造函数私有, C++98中构造函数私有化,派生类中调不到基类的构造函数,所以无法继承

class NonInherit
{
public:
 static NonInherit GetInstance()
 {
 return NonInherit();
 }
private:
 NonInherit()
 {}
};

方法二就是添加final,final修饰类表示该类不能被继承,那么这里的代码就如下:

class NonInherit  final
{
    // ....
};

什么是单例模式

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理,单例模式有两种实现方式第一种就是饿汉模式,第二种就是懒汉模式,我们首先来看看饿汉模式的原理。

饿汉模式

单例模式的特点就是全局只有一个唯一对象,如何保证全局只有一个唯一对象呢?首先构造函数封死,如果构造函数不封起来的话使用者可以用这个类创建多个对象,那么我们就可以创建一个类,类中含有一个map容器并装着一些数据,然后类里面就提供了一些函数用于访问map的数据,修改map的数据等等,然后把这个类的构造函数放到私有里面,比如说下面的代码:

class InfoSingleton
{
public:
    
private:
    InfoSingleton()
    {}
    map<string, int> _info;
};

然后这里就存在一个问题如何来创建对象,并且保证对象的个数就只有一个呢?答案是在类里面创建一个静态的变量然后提供一个静态成员函数来获取对象的引用,比如说下面的代码:

class InfoSingleton
{
public:
   	static InfoSingleton& GetInstance()
    {
        return _sins;
    }
private:
    InfoSingleton()
    {}
    map<string, int> _info;
    static InfoSingleton  _sins;
};
InfoSingleton InfoSingleton::_sins;

外面的静态成员变量是定义,内部的静态成员变量是声明,然后为了方便往对象里面的插入数据和修改数据,我们还要创建一个insert函数,在里面通过方括号来修改内部的数据,比如说下面的代码:

void insert(string name, int salary)
{
    _info[name] = salary;
}

然后我们就可以在main函数里面通过引用或者匿名调用的方式来插入或者修改数据,比如说下面的代码:

int main()
{
    InfoSingleton::GetInstance().insert("张三", 10000);
    InfoSingleton::GetInstance().insert("李四", 12000);
    InfoSingleton& tmp = InfoSingleton::GetInstance();
    tmp.insert("王五", 13000);
    tmp.insert("老六", 14000);
    return 0;
}

然后我们还可以创建一个print函数用来打印内部的数据,比如说下面的代码:

void print()
{
    for (auto& ch : _info)
    {
        cout << ch.first << ":" << ch.second << endl;
    }
}

然后我们就可以查看容器里面的数据,那么这里的运行结果如下:
在这里插入图片描述这种方式就是饿汉模式一开始就创建对象,但是这种方法存在一个问题就是拷贝构造和赋值重载会多创建出来一个对象不符合特征,所以这里得把拷贝构造和赋值重载也去掉,那么这里的代码就如下:

class InfoSingleton
{
public:
    static InfoSingleton& GetInstance()
    {
        return _sins;
    }
    void insert(string name, int salary)
    {
        _info[name] = salary;
    }
    void print()
    {
        for (auto& ch : _info)
        {
            cout << ch.first << ":" << ch.second << endl;
        }
    }
private:
    InfoSingleton()
    {}
    InfoSingleton(InfoSingleton const&) = delete;
    InfoSingleton& operator=(InfoSingleton const&) = delete;
    map<string, int> _info;
    static InfoSingleton  _sins;
};
InfoSingleton InfoSingleton::_sins;

我们就把这样的实现方式成为饿汉模式,因为它在程序的一开始就创建了一个对象。

饿汉模式的缺点

1.饿汉模式初始化时如果数据太多,会导致启动的速度较慢,因为饿汉模式在main函数之前就得进行初始化,而数据的含量可能会非常的多并且数据可能还要链接数据库等等,所以可能会导致启动的速度很慢。
2.多个单例类有初始化依赖关系,饿汉模式无法控制。比如说A和B都是单利类,要求先初始化A,再初始化B,因为B会依赖A,但是饿汉模式中的变量都是全局变量无法保证初始化顺序,所以这里就可能会出错,那么未来解决这个问题有人就提出了懒汉模式。

懒汉模式

饿汉模式是程序一开始就创建对象而懒汉模式则是先不着急创建对象,等需要的时候再创建对象,那么我们就把类里面的静态对象修改成为一个静态的指针对象,在类外面将其初始化为空,在GetInstance函数里面就判断当前的指针是否为空,如果为空的话就创建对象最后返回当前的指针指向的对象,那么这里的代码就如下:

class InfoSingleton
{
public:
    static InfoSingleton& GetInstance()
    {
    //第一次调用的时候创建对象
        if (_psins == nullptr)
        {
            _psins = new InfoSingleton;
        }
        return *_psins;
    }
    void insert(string name, int salary)
    {
        _info[name] = salary;
    }
    void print()
    {
        for (auto& ch : _info)
        {
            cout << ch.first << ":" << ch.second << endl;
        }
    }
private:
    InfoSingleton()
    {}
    InfoSingleton(InfoSingleton const&) = delete;
    InfoSingleton& operator=(InfoSingleton const&) = delete;
    map<string, int> _info;
    static InfoSingleton*  _psins;
};
InfoSingleton* InfoSingleton::_psins=nullptr;

那么这里就不是一开始就创建对象,而是等你调用的时候创建对象。

懒汉模式的优点

1.对象在main函数之后才会创建,不会影响启动顺序。
2.可以主动控制初始化顺序。比如说A依赖于B,那么我们就可以先调用A再调用B来解决这个问题。

懒汉模式的缺点

多个线程一起调用单例对象的时候创建对象时也可能会创建多个对象,第一个线程看到的当前类没有对象会new一个,第一个线程还没有创建完成第二个线程跑过来发现依然没有所以这个时候就又会创建一个对象出来,所以这个时候就得在类里面添加一个枷锁变量,因为静态的成员函数没有this指针不能访问非静态的成员变量,所以这里就得创建一个静态的枷锁,那么这里的代码如下:

class InfoSingleton
{
public:
    static InfoSingleton& GetInstance()
    {
        mtx.lock();
        if (_psins == nullptr)
        {
            _psins = new InfoSingleton;
        }
        mtx.unlock();
        return *_psins;
    }
    void insert(string name, int salary)
    {}
    void print()
    {}
private:
    InfoSingleton()
    {}
    InfoSingleton(InfoSingleton const&) = delete;
    InfoSingleton& operator=(InfoSingleton const&) = delete;
    map<string, int> _info;
    static InfoSingleton*  _psins;
    static mutex mtx;
};
mutex InfoSingleton::mtx;
InfoSingleton* InfoSingleton::_psins=nullptr;

可是这里就存在一个问题,我们只会在第一次创建对象的时候出现问题,而我们每次使用这个函数的时候都得进行枷锁解锁,这里是不是就会产生时间消耗啊,所以能不能把枷锁放到if的里面呢?答案是不行的这里会出现线程安全,因为可能会两个线程都进入到创建对象里面,并且这种方法会加剧线程安全的风险,所以这里就可以添加双层检查来进行枷锁保护,那么这里的代码就如下:

static InfoSingleton& GetInstance()
{
    if (_psins == nullptr)//对象new出来了,避免每次都枷锁的检查,提高性能。
    {
        mtx.lock();
        if (_psins == nullptr)//保证线程安全且执行一次
        {
            _psins = new InfoSingleton;
        }
        mtx.unlock();
    }
    return *_psins;
}

但是这里new可能会抛出异常所以这里得捕捉异常,如果没捕捉的话这里就不会解锁了,那么改进后的代码就如下:

static InfoSingleton& GetInstance()
 {
     if (_psins == nullptr)
     {
         mtx.lock();
         try
         {
             if (_psins == nullptr)
             {
                 _psins = new InfoSingleton;
             }
         }
         catch (...)
         {
             mtx.unlock();
             throw;
         }
     }
     return *_psins;
 }

上面的释放方式有点不好看,所以这里我们可以使用智能指针的思想来解决这里的问题,创建一个类类中含有一个锁的引用对象,那么这个类的构造函数就是对这个锁进行上锁,类的析构函数就是对这个类进行解锁,那么这里的代码就是这样:

template<class Lock>
class LockGuard
{
public:
    LockGuard(Lock& lk)
        :_lk(lk)
    {
        _lk.lock();
    }

    ~LockGuard()
    {
        _lk.unlock();
    }

private:
    Lock& _lk;
};

那么上面的函数我们就可以写成下面这样:

static InfoSingleton& GetInstance()
{
    if (_psins == nullptr)
    {
        LockGuard<mutex> lock(mtx);
        if (_psins == nullptr)
        {
            _psins = new InfoSingleton;
        }
    }
    return *_psins;
}

一般单例对象不需要考虑内存释放,因为单例对象一般都是在整个程序里面进行使用,但是单例对象在不用时必须得手动处理,让一些资源进行报错,所以这个时候就得提供一个delete函数来手动释放,那么这里的函数代码如下:

static void DelInstance()
{
    	/*保存数据到文件
    	...*/
    std::lock_guard<mutex> lock(mtx);
    if (_psins)
    {
    	delete _psins;
    	_psins = nullptr;
    }
}

那这里能不能做到自动释放该类呢?答案时可以的,我们可以定义一个内部类并且该类的析构函数里面调用DelInstance,然后在外部类里面添加一个该类的静态对象,这样当单例的那个类被销毁时内部类的对象就会被销毁,内部类对象被销毁时就会调用它的析构函数,然后析构函数就调用GetInstance函数来释放,那么完整的代码就如下:

template<class Lock>
class LockGuard
{
public:
    LockGuard(Lock& lk)
        :_lk(lk)
    {
        _lk.lock();
    }

    ~LockGuard()
    {
        _lk.unlock();
    }

private:
    Lock& _lk;
};
class InfoSingleton
{
public:
    static InfoSingleton& GetInstance()
    {
        if (_psins == nullptr)
        {
            LockGuard<mutex> lock(mtx);
                if (_psins == nullptr)
                {
                    _psins = new InfoSingleton;
                }
         
        }
        return *_psins;
    }
    void insert(string name, int salary)
    {
        _info[name] = salary;
    }
    void print()
    {
        for (auto& ch : _info)
        {
            cout << ch.first << ":" << ch.second << endl;
        }
    }
    static void DelInstance()
    {
        	/*保存数据到文件
        	...*/
        std::lock_guard<mutex> lock(mtx);
        if (_psins)
        {
        	delete _psins;
        	_psins = nullptr;
        }
    }
    class GC
    {
    public:
        ~GC()
        {
        	if (_psins)
        	{
        		cout << "~GC()" << endl;
        		DelInstance();
        	}
        }
    }; 	
private:
    InfoSingleton()
    {}
    InfoSingleton(InfoSingleton const&) = delete;
    InfoSingleton& operator=(InfoSingleton const&) = delete;
    map<string, int> _info;
    static InfoSingleton*  _psins;
    static mutex mtx;
    static GC _gc;  
};
mutex InfoSingleton::mtx;
InfoSingleton* InfoSingleton::_psins=nullptr;
InfoSingleton::GC InfoSingleton::_gc;

特殊的懒汉

class InfoSingleton
{
public:
	static InfoSingleton& GetInstance()
	{
		static InfoSingleton sinst;
		return sinst;
	}

	void Insert(string name, int salary)
	{
		_info[name] = salary;
	}

	void Print()
	{
		for (auto kv : _info)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;
	}

private:
	InfoSingleton()
	{
		cout << "InfoSingleton()" << endl;
	}

	InfoSingleton(const InfoSingleton& info) = delete;
	InfoSingleton& operator=(const InfoSingleton& info) = delete;


	map<string, int> _info;
	// ...
};

我们之前说过函数中的静态成员变量是在第一次调用该函数的时候进行创建并且初始化,第一次调用完之后就不会再执行该代码,所以上面的代码能够保证只创建一个对象,并且该对象的创建也发生了main函数之后,可是面对多线程的时候上面的代码可能会出现线程安全问题吗?答案是C++11之前,这里是不能保证sinst的初始化是线程安全的,C++11之后可以保证安全。所以这种写法不一定安全并不是通用的方法,所以对于这种写法如果编译器支持c++11则可以写,如果不支持则不能写。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Jmeter+Maven+jenkins+eclipse搭建自动化测试平台

背景&#xff1a; 首先用jmeter录制或者书写性能测试的脚本&#xff0c;用maven添加相关依赖&#xff0c;把性能测试的代码提交到github&#xff0c;在jenkins配置git下载性能测试的代码&#xff0c;配置运行脚本和测试报告&#xff0c;配置运行失败自动发邮件通知&#xff0c…

Go-新手速成-流程语句

1if Go的if不建议写&#xff08;&#xff09;&#xff0c;over //if条件判断age : 16if age < 18 {fmt.Println("未成年")} 2for循环 Go摈弃了while和do while 循环&#xff0c;因为他做到了极简(也不要括号) 这么写可以 total : 0for i : 0; i < 100; i {…

Goby 漏洞发布|天擎终端安全管理系统 YII_CSRF_TOKEN 远程代码执行漏洞

漏洞名称&#xff1a;天擎终端安全管理系统 YII_CSRF_TOKEN 远程代码执行漏洞 English Name&#xff1a;Tianqing terminal security management system YII_CSRF_TOKEN remote code execution vulnerability CVSS core: 9.8 影响资产数&#xff1a;875 漏洞描述&#xff1…

2023婴幼儿奶粉市场数据分析(天猫数据中心)

我国婴幼儿奶粉市场一直保持着相当大的规模&#xff0c;虽然近几年新生人口数量不断下降&#xff0c;但伴随消费者的消费升级不断加速、大龄孩童吃奶粉的时间延长等&#xff0c;整体来看&#xff0c;婴幼儿奶粉行业市场规模保持平稳。 根据鲸参谋电商数据分析平台的相关数据显示…

centos升级龙蜥

centos升级龙蜥 龙蜥简介龙蜥官方社区centos升级龙蜥首先确认自己的centos版本下载迁移镜像源安装epel源迁移工具安装i686包查看执行迁移脚本结果查看重启机器查看系统信息 龙蜥简介 2021年10月19日的大会上&#xff0c;阿里云发布全新操作系统“龙蜥”并宣布开源。龙蜥操作系…

【实战篇】docker-compose部署go项目

一、场景&#xff1a; 二、需求 三、实操 Stage 1&#xff1a;GoLand 中 build 生成二进制文件 Stage 2&#xff1a;编写 Dockerfile Stage 3&#xff1a;编写 docker-compose.yaml Stage 4&#xff1a;文件上传到 ubuntu 服务器上&#xff0c;并设置文件读写权限 Stage…

写给后端开发的『vue3』请求后端接口

本文分享一下在vue3前端项目中请求后端接口获取数据。比较简单&#xff0c;内容如下&#xff1a; 1、使用axios请求后端接口 首先npm install axios&#xff0c;添加axios依赖&#xff0c;就靠它来请求后端接口了&#xff0c;基本等同于使用jquery发ajax。 # src/main.js i…

ElasticSearch文档(document)在index上的增删改查

文章目录 一、document定义&#xff1a;二、单条增删改查1、创建索引&#xff1a;2、添加文档&#xff1a;3、获取文档&#xff1a;4、更新文档&#xff1a;5、删除文档&#xff1a; 三、批量增删改查&#xff1a;1、批量添加文档&#xff1a;2、批量更新文档&#xff1a;3、批…

将 QtPropertyBrowser 加入到 vs2015 qt版中工程中

我是直接将QtPropertyBrowser的所有文件当子文件夹全部加入到工程里的 加完之后&#xff0c;ok&#xff0c;编译错误 出现一堆以下错误&#xff0c;我只拿出一条 moc_qtbuttonpropertybrowser.cpp(94): error C2027: 使用了未定义类型“QtButtonPropertyBrowserPrivate” 然…

grpc中间件之链路追踪(otel+jaeger)

参考文档 https://github.com/grpc-ecosystem/go-grpc-middleware/blob/main/examples/client/main.go https://github.com/grpc-ecosystem/go-grpc-middleware/blob/main/examples/server/main.go https://github.com/open-telemetry/opentelemetry-go/blob/main/example/jaeg…

学会项目成本管理计算,PMP计算题就是送分题

学会项目成本管理计算&#xff0c;PMP计算题就是送分题 PMP中的计算主要在 <项目成本管理> 的控制成本部分&#xff0c;服务于挣值管理&#xff08;EVM&#xff0c;Earned Value Management&#xff09;、挣值分析&#xff08;EVA&#xff0c;Earned Value Analysis&…

基于SSM的校园二手交易平台

零、源码获取&#xff1a; 链接点击直达&#xff1a;下载链接 一、设计概要 本次设计的是一个校园二手交易平台&#xff08;C2C&#xff09;&#xff0c;C2C指个人与个人之间的电子商务&#xff0c;买家可以查看所有卖家发布的商品&#xff0c;并且根据分类进行商品过滤&…

微信怎么自动加好友,通过好友后自动打招呼

很多客户朋友每天花大量的时间用手机搜索添加好友&#xff0c;这样的添加很集中也容易频繁&#xff0c;而且效率还低。对方通过后&#xff0c;有时也不能及时和客户搭建链接&#xff0c;导致客户也流失了。 现在可以实现自动添加和自动打招呼哦&#xff0c;只需要导入数据、设置…

SpringCloud(三)LoadBalancer负载均衡

一、负载均衡 实际上&#xff0c;在添加LoadBalanced注解之后&#xff0c;会启用拦截器对我们发起的服务调用请求进行拦截&#xff08;注意这里是针对我们发起的请求进行拦截&#xff09;&#xff0c;叫做LoadBalancerInterceptor&#xff0c;它实现ClientHttpRequestIntercep…

店铺记账用什么软件好?应该如何选购?

店铺记账过程中&#xff0c;会遇到各种问题&#xff1a;手写记账容易出错、效率低下、数据容易丢失&#xff1b;手动整理数据导致实际库存和账面库存不匹配&#xff0c;影响补货和订单管理。 而借助专业的店铺记账软件&#xff0c;可以有效解决上面这些问题&#xff0c;通过自动…

redis穿透问题

1.概述 一个热点数据在高并发情况下过期时间到了&#xff0c;会导致大量流量查询redis为null&#xff0c;进而请求数据库进行更新数据&#xff0c;从流量上来说请求打到了数据库上&#xff0c;这种情况可能会造成mysql服务崩溃。 2. 解决方式之一&#xff08;加锁解决之本地锁&…

『红外图像 数据增强』DDE(Digital Detail Enhancement)算法

DDE处理的细节 分离背景层和细节层&#xff1a;使用特殊的滤波器&#xff0c;将图像分成背景层和细节层。背景层通常包含低频信息&#xff0c;而细节层包含高频信息。 对背景层进行灰度增强&#xff1a;通过对背景层应用适当的灰度增强算法&#xff0c;提高背景层的对比度和视…

PostgreSQL 考试认证指南:考前准备和考试概述

下面是关于考前准备和考试概述的指南&#xff1a; 考前准备&#xff1a; 1.确定考试内容&#xff1a;详细了解考试的内容范围和考试要求。可以查阅PostgreSQL官方网站或认证考试指南&#xff0c;以获取相关信息。 2.学习和实践&#xff1a;系统地学习和掌握与PostgreSQL相关…

Vant源码解析(四)----Popup弹出层,详解样式方法

这个功能&#xff0c;自己也手写过&#xff0c;毕竟有很多弹窗的嘛。 我自己写就是&#xff1a;一个背景层&#xff0c;然后一个盒子里面放内容。再写个显示隐藏事件。够够的了。 Vant的Popup弹出层 页面结构 短短一个背景加内容盒子&#xff0c;vant套了几层。 这是引用的组件…

抖音矩阵系统源码:开发搭建与技术详解

一、 抖音矩阵系统源码开发概述 抖音短视频seo矩阵系统源码是一款在高速数据处理和分析方面表现卓越的系统。它结合了各种先进的技术&#xff0c;包括深度学习、大数据分析和可视化等&#xff0c;使得抖音在信息处理时更加高效和准确。 该系统源码的开发搭建需要多方面的技术支…