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

news2024/12/26 11:17:01

目录标题

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

设计一个不能被拷贝的类

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。那么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则可以写,如果不支持则不能写。

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

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

相关文章

Apache Doris (三十一):Doris 数据导入(九)Spark Load 4- 导入Hive数据及注意事项

目录 1. Spark Load导入Hive非分区表数据 2. Spark Load 导入Hive分区表数据 3. 注意事项 进入正文之前&#xff0c;欢迎订阅专题、对博文点赞、评论、收藏&#xff0c;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; 宝子们订阅、点赞、收藏不迷路&#xff01;抓紧…

这8种算法

一个程序员一生中可能会邂逅各种各样的算法&#xff0c;但总有那么几种&#xff0c;是作为一个程序员一定会遇见且大概率需要掌握的算法。今天就来聊聊这些十分重要的“必抓&#xff01;”算法吧~ 算法一&#xff1a;快速排序法 快速排序法是对冒泡排序的一种改进&#xff0c…

Spring Cloud Gateway - 新一代微服务API网关

Spring Cloud Gateway - 新一代微服务API网关 文章目录 Spring Cloud Gateway - 新一代微服务API网关1.网关介绍2.Spring Cloud Gateway介绍3.Spring Cloud Gateway的特性4.Spring Cloud Gateway的三大核心概念5.Gateway工作流程6.Gateway核心配置7.动态路由8.Predicate自定义P…

vue 集成tinymce2实现图片,视频以及文件的上传

vue 集成tinymce2实现图片&#xff0c;视频以及文件的上传 1. 安装插件 &#xff08;1&#xff09;安装tinymce npm install tinymce -S &#xff08;2&#xff09;安装tinymce-vue npm install tinymce/tinymce-vue3.0.1 -S 2. 复制静态文件到public目录 资源下载路径&…

day40-Mybatis(resultMap拓展)

0目录 Mybatis-resultMap拓展 1.2.3 1.数据库字段和javabean实体类属性不一致时 解决方案1&#xff1a;将sql语句中给予别名&#xff08;别名同javabean中实体类保持一致&#xff09; 解决方案2&#xff1a;使用resultMap 2.两表关联&#xff08;用户表和角色表关联查询&…

QGIS绘制一张地图——建立打印布局在地图中添加图例和比例尺后,将地图保存为图片(出图)

前言 本节所述内容,基于上节所绘制的北京市区地图为例,特此说明! 北京市区地图如图所示: 一、直接保存为图片 依次点击工程、导入/导出、导出地图为图片: 设置比例尺、像素等信息,点击保存: 保存出来的地图的显示区域是和QGIS中看到的地图区域一样的: 二、建立…

qiankun:react18主应用 + 微应用 react18 + vue3

一&#xff1a;主应用 搭建react项目 npx create-react-app react-qiankun-main安装Antd npm install antd –save在 index.js中引入 import { ConfigProvider } from "antd"; import zhCN from "antd/locale/zh_CN"; import "antd/dist/reset.css…

心电前置放大电路制作与原理详细分析(附电路板实物图)

1、软件平台:Multisim仿真软件、EDA原理图绘制软件、医学电子学开发平台 2、硬件平台:心电示教仪、示波器、信号发生器、除颤仪、电烙铁 3、元件清单: 实验电路图 1、心电放大器原理图 2、50Hz双T陷波滤波器原理图 原理解释与计算 (1)一级放大电路 一级放大电路由…

【PCB专题】如何在Allegro中定义字体及批量修改丝印

在PCB板上丝印往往包含了很多信息,比如元件边界、元件参数、元件编号、极性、静电标识、板号等,这些信息在生产、测试及后期维护等都需要使用。一个好的设计往往都能从丝印的布局、丝印的完整性上体现出来。如下所示PCB在电解电容旁有极性丝印、电阻旁有电阻的位号信息等。 …

前端 | (七)浮动 | 尚硅谷前端html+css零基础教程2023最新

学习来源&#xff1a;尚硅谷前端htmlcss零基础教程&#xff0c;2023最新前端开发html5css3视频 文章目录 &#x1f4da;浮动介绍&#x1f407;元素浮动后的特点&#x1f407;浮动小练习&#x1f525;盒子1右浮动&#x1f525;盒子1左浮动&#x1f525;所有盒子都浮动&#x1f5…

EasyCVR视频融合平台能正常播放其他协议流,但无法播放HLS流的原因排查

EasyCVR基于云边端一体化架构&#xff0c;支持海量视频汇聚管理&#xff0c;平台支持多协议与多类型设备接入&#xff0c;具体包括国标GB28181、RTMP、RTSP/Onvif、海康Ehome、海康SDK、大华SDK、宇视SDK等&#xff0c;能对外分发RTMP、RTSP、HTTP-FLV、WS-FLV、HLS、WebRTC等。…

【Spring Boot】拦截器与统一功能处理:统一登录验证、统一异常处理与统一数据返回格式

前言 Spring AOP是一个基于面向切面编程的框架&#xff0c;用于将横切性关注点&#xff08;如日志记录、事务管理&#xff09;与业务逻辑分离&#xff0c;通过代理对象将这些关注点织入到目标对象的方法执行前后、抛出异常或返回结果时等特定位置执行&#xff0c;从而提高程序的…

浏览器显示ERR_NETWORK_ACCESS_DENIED,安全设置或防火墙可能正在阻止连接,无法上网

环境: Win10 专业版 HP台式机 问题描述: 浏览器显示ERR_NETWORK_ACCESS_DENIED,安全设置或防火墙可能正在阻止连接,无法上网 1.无线连接状态正常 打不开网站 2.可以ping通百度DNS解析正常 3.防火墙已关闭 这样的错误可能由于多种原因而发生 原因分析 1.防火墙/防…

ETHERCAT转ETHERCAT网关西门子为什么不支持ethercat两个ETHERCAT设备互联

1.1 产品功能 远创智控YC-ECT-ECT是自主研发的一款ETHERCAT从站功能的通讯网关。该产品主要功能是将2个ETHERCAT网络连接起来。 本网关连接到ETHERCAT总线中做为从站使用。 1.2 技术参数 1.2.1 远创智控YC-ECT-ECT技术参数 ● 网关做为ETHERCAT网络的从站&#xff0c;可以连接…

【Linux】内存使用相关

free 命令 查看内存大小 free -g :G单位 free -h : 可读性较高较理解 free -m : MB单位 total: 总内存used: 正在运行的进程使用的内存(used total – free – buff/cache)free: 未使用的内存 (free total – used – buff/cache)shared: 多个进程共享的内存buffers: 内存保留…

promise规范及应用(进阶)

##promise解析 *啥是异步? //异步执行let count 1let timer setTimeout(function () {countconsole.log(in, count);}, 1000);console.log(out);// out>1000>in//循环执行let count 1let timer setInterval(function () {countconsole.log(in, count);}, 1000);con…

isaac sim添加孔网格

isaac sim仿真和其它仿真实际上一样&#xff0c;对于孔的仿真&#xff0c;是没那么简单的 在此记录一下踩过的坑 1&#xff0c;首先&#xff0c;你需要在soildworks中将你的孔画出来&#xff0c;并导出stl 2&#xff0c;你可以在win10中使用3D画图查看孔的网格&#xff0c;看…

代码随想录算法训练营day7 | 454. 四数相加 II,383. 赎金信,15. 三数之和,18. 四数之和

目录 454. 四数相加 II 383. 赎金信 15. 三数之和 18. 四数之和 454. 四数相加 II 难度&#xff1a;medium 类型&#xff1a;哈希表 思路&#xff1a; 本题是使用哈希法的经典题目&#xff0c;而0015.三数之和 (opens new window)&#xff0c;0018.四数之和 (opens new …

迁移 Gitee 仓库到 Github

Step1: 在Gitee找到你要迁移的仓库, 并复制 克隆|下载 链接 Step2: 打开 Github, 找到 按钮选择 Import Step3: 打开 Github, 找到 按钮选择 Import Step4: Waiting... 等待导入成功 Over~ 还有一种镜像更新的方案, Gitee 支持镜像同步, 但是我使用时无法获取到仓库名,…

开源的短视频生成和编辑工具 Open Chat Video Editor

GitHub - SCUTlihaoyu/open-chat-video-editor: Open source short video automatic generation tool