智能指针之shared_ptr初始化,引用计数,常用操作和自定义删除器等等03

2023-10-26

一 share_ptr

1 share_ptr基础

  • 1)共享所有权,不是被一个shared_ptr拥有,而是被多个shared_ptr之间相互协作。shared有额外开销。
  • 2)工作原理:利用引用计数的方法管理一片内存,每增加一个shared_ptr,count数加1,同理,每减少一个shared_ptr,count减1;这种引用也称为强引用。
  • 3)最后一个指向该内存对象的shared_ptr在什么情况下,会释放该对象呢?
    1. 这个shared_ptr被析构的时候。
    2. 这个shared_ptr指向其他对象时。

2 shared_ptr初始化

shared_ptr的初始化可以通过裸指针初始化,返回值初始化,和make_shared函数初始化。
1)通过裸指针与返回值初始化

shared_ptr<int> make(int value){
    return shared_ptr<int>(new int(value));
}

void test02(){
    shared_ptr<int> pi1(new int(100)); //裸指针初始化,pi指向一个值为100的int型数据
    shared_ptr<int> pi2 = make(200);//返回值初始化

    cout<< (*pi1.get()) << endl;
    cout<< (*pi2.get()) << endl;
}

int main(){

    test02();

    return 0;
}

2)通过make_shared函数初始化

  • 1)注意,裸指针可以初始化shared_ptr,但是不推荐,智能指针不要穿插用,否则会出问题。所以我们不建议使用的初始化,而是通过make_shared初始化。
  • 2)make_shared是标准库里的函数模板,安全,高效的分配和使用shared_ptr,它能够在动态内存中,分配并初始化一个对象,然后返回指向此对象的shared_ptr。更高效安全的原因:
//1 性能更好(高效)
/*
	 ①性能更好(高效):用new来构造shared_ptr指针,那么new的过程是一次堆上面的内存分配,
	 而在构造shared_ptr对象的时候,由于需要使用堆上面共享的引用计数(指针),又需要在堆上面
	 分配一次内存,即需要分配两次内存,而如果用make_shared函数,则只需分配一次内存,
	 所以性能会好很多。
	 
	 伪代码解释:下面可以看到,ptr传进来已经new一次,然后引用计数_pRefCount也会new一次
	 造成两次分配内存。
*/
template <class T>
class SharedPtr
{
public:
	//
	SharedPtr(T* ptr = nullptr): _ptr(ptr), _pRefCount(new int(1)), _pMutex(new mutex){
		// 如果是一个空指针对象,则引用计数给0
		if (_ptr == nullptr)
			*_pRefCount = 0;
	}

private:
	int*   _pRefCount; // 引用计数
	T*     _ptr;       // 指向管理资源的指针
	mutex* _pMutex;    // 互斥锁
}

//2 更安全
/*
	 ②更加安全:若我们使用裸指针对shared_ptr构造时,包含两步操作:
	(1)new一个堆内存。
	(2)分配一个引用计数区域管理该内存空间(指上面的_pRefCount)。
	 但是并没有保证这两个步骤的原子性,当做了第(1)步,没有做第二步如果程序抛出了异常,将导致内存泄露。
	 而make_shared内部有对这两个步骤合成一步进行处理,
	 因此更推荐使用make_shared来分配内存。

*/

//3 make_shared缺点
/*
	虽然make_shared针对裸指针更好,但它也有缺点。
	③缺点:make_shared一次性分配堆内存的做法,在释放的时候可能会导致内存延迟释放,
	因为如果有weak_ptr持有了指针,引用计数不会释放,而引用计数和实际的对象分配在同一块堆内存,
	因此无法将该对象释放,如果两块内存分开申请,则不存在这个延迟释放的问题。
*/

//4 总结
/*
	实际开发可能不会考虑这些,使用裸指针或者make_shared都行,但是大家一定要知道它们可能存在的问题。
*/

在上面介绍为何使用make_shared顶替裸指针初始化后,下面正式说明make_shared如何初始化shared_ptr。

#include <iostream>
#include <string>
#include <memory>
using namespace std;

shared_ptr<int> make(int value){
    return shared_ptr<int>(new int(value));
}

void test02(){
    // shared_ptr<int> pi1(new int(100)); //pi指向一个值为100的int型数据
    // shared_ptr<int> pi2 = make(200);
    // cout<< (*pi1.get()) << endl;
    // cout<< (*pi2.get()) << endl;

    shared_ptr<int> p1 = make_shared<int>(100);
    shared_ptr<string> p2 = make_shared<string>(5, 'a'); //类似于string mystr(5, 'a')
    shared_ptr<int> p3 = make_shared<int>();//默认初值为0,cout<< (*p3.get()) << endl;
    p3 = make_shared<int>(300); //指向新int,p3首先释放指向值为0的内存,然后指向这个新的300的内存
    auto p4 = make_shared<string>("I love you");

    cout<< (*p1.get()) << endl;
    cout<< p2.get()->data() << endl;
    cout<< (*p3.get()) << endl;
    cout<< p4.get()->data() << endl;
}

int main(){

    //test01();
    test02();

    return 0;
}

结果:
在这里插入图片描述

3)注意,shared_ptr不支持隐式转换。

//例1
shared_ptr<int> pi2 = new int(200); //不可以,智能指针是explicit,不可以进行隐式类型转换,必须直接初始化形式

//例2
shared_ptr<int> make(int value){
    return new int(value);   //error,无法把new得到的int *转换成shared_ptr
    return shared_ptr<int>(new int(value))//正确写法
}

3 shared_ptr的引用计数

引用计数就是多个shared_ptr共用一片内存的shared_ptr个数。当一个shared_ptr开辟一个内存,引用计数为1,然后用该shared_ptr初始化其它shared_ptr那么就是共用一个内存,引用计数为2,以此类推。而当一个shared_ptr指向其它shared_ptr的内存,或者shared_ptr生命周期结束被析构时,引用计数都会减少。

#include <iostream>
#include <string>
#include <memory>
using namespace std;

void Func1(shared_ptr<int> a)
{
    cout<<"函数1内:"<<endl;
    cout<<"值的引用数为: "<<a.use_count()<<endl;          // 2 调用完毕a被释放 引用计数减1
    cout<<"函数1结束。"<<endl;
}

shared_ptr<int> Func2(shared_ptr<int>& a)
{
    cout<<"函数2内:"<<endl;
    cout<<"引用的引用数为: "<<a.use_count()<<endl;         // 1引用不会增加引用计数
    cout<<"函数2结束。"<<endl;

    return a;
}

//主要测试参数为值,引用和返回值对引用计数的影响
void test03(){
    
    shared_ptr<int> sh1(new int(10));                  // 构造一个指向int类型对象的指针sh1,引用计数为1
    cout<<"Ref count: "<< sh1.use_count() << endl;

    {
        shared_ptr<int> sh2 = sh1;                     
        cout<<"Ref count: "<< sh1.use_count() << endl;
    }
    //sh2生命周期结束后
    cout<<"Ref count: "<< sh1.use_count() << endl;
    cout<<endl;

    //1 测试参数为值
    Func1(sh1);
	cout<<endl;
    
    //2 测试参数为引用
    Func2(sh1);
	cout<<endl;

    //3 测试接收匿名对象
    shared_ptr<int> sh3 = Func2(sh1);
    cout<<"sh3加入之后的引用数:"<<sh3.use_count()<<endl;

}

//测试引用计数增加减少
void test04(){
    auto p1 = make_shared<int>(100);
    auto p2(p1);
    auto p3 = Func2(p2);
    cout<<"Ref count: "<< p1.use_count() << endl;
    p3 = make_shared<int>(200); //p3指向新对象,计数为1,p1、p2指向对象计数恢复为2;
    p2 = make_shared<int>(300); //p2指向新对象,计数为1,p1指向对象的计数恢复为1;
    cout<<"p3,p2改变指向后,Ref count: "<< p1.use_count() << endl;

    {
        shared_ptr<int> p4 = p1;                     
        cout<<"Ref count: "<< p1.use_count() << endl;
    }
    cout<<"p4生命周期结束后,Ref count: "<< p1.use_count() << endl;
}

int main(){

    //test01();
    //test02();
    test03();

    cout<<endl;
    cout<<"====test04 begin===="<<endl;

    test04();

    return 0;
}

结果:
在这里插入图片描述

从上面结果可以得出:

  • 1)传值会使引用计数加1,传引用则不会。
  • 2)当返回值为shared_ptr类型,若我们使用变量接收,则引用计数会加1,不接收则不加。归根结底是因为返回值类型时,编译器会自动创建一个匿名对象返回。例如Func2(sh1);与shared_ptr sh3 = Func2(sh1);。
  • 3)使用同一片内存为其它shared_ptr赋值,会使引用计数加1。例如sh2 = sh1或者sh2(sh1);

上面是针对于会使引用计数增加的总结。下面总结引用计数减少的。

  • 1)改变指向其它内存的shared_ptr会减1。
  • 2)生命周期结束的shared_ptr会减1。当最终引用计数减为0时,就会释放该内存。

4 shared_ptr的其它成员函数

4.1 use_count()
use_count()是获取该片内存有多个shared_ptr个对象正在引用。

4.2 unique()
unique:是否该智能指针独占某个指向的对象,也就是若只有一个智能指针指向某个对象,则unique()返回true,多个返回fasle。(为空时也不属于独享)

4.3 reset()与shared_ptr的比较
reset()函数分为无参与有参的使用。

  • 1)无参时,调用该函数的shared_ptr对象的引用计数为0,而该片内存并不为0,只是减1。
  • 2)有参时,调用该函数的shared_ptr对象指向新的内存,引用计数加1。而原本的内存减1。
  • 3)由于reset被置空后,一般需要比较,所以将shared_ptr的比较放在reset一起,当然也可以使用use_count代替比较来判断是否被置空。
  • 4)下面例子可以看到,空指针也可以通过reset重新初始化。即sh1。

看例子。

//shared_ptr的比较 与 reset函数方法
void test05(){

	shared_ptr<int> sh1=make_shared<int>(3);                 
	cout<<sh1.use_count()<<endl;                             // count=1

	shared_ptr<int> sh3=sh1;                                 
	cout<<sh1.use_count()<<endl;                             // count=2

	//1 比较 重载了==与!=运算符  实际上当成指针比较就好了
	if(sh1!=NULL && sh3!=NULL){
		cout<<"sh1和sh3不为空!"<<endl;
	}

	//2.1 无参reset函数  使调用的shared_ptr指向一个空资源 只是该shared_ptr的引用计数变为0 其他的因为sh1调用reset而减1 
	sh1.reset();                                             // 使sh1指向的count为0
	cout<<sh1.use_count()<<endl;                             // count=0
    if (sh1 == nullptr){
        cout << "sh1被置空" << endl;
    }
	cout<<sh3.use_count()<<endl;                             // count=1

	//2.2 有参reset 使shared_ptr指向参数new出的资源对象
	sh1.reset(new int(5));                                   // sh1指向该参数new出的资源
	cout<<sh1.use_count()<<endl;                             // count=1

}

结果:
在这里插入图片描述

4.4 解引用的意思
解引用就是获取该裸指针的对象。
例如。

shared_ptr<int> p(new int(123));
cout << *p << endl;

4.5 get()
get():获取裸指针操作。考虑到有些函数的参数需要的是一个内置裸指针,而不是智能指针。例如上面的初始化使用过get函数。

4.6 swap()
swap():交换两个智能指针所指向的对象。

shared_ptr<string> ps1(new string("1111111"));
shared_ptr<string> ps2(new string("2222222"));
std::swap(ps1, ps2); //交换ps1指向222222222
ps1.swap(ps2);    //在交换ps1指向11111111

5 自定义删除回调函数(删除器)

分析:当传给shared_ptr构造函数不止一个对象时,例如是一个对象数组时,因为shared_ptr析构默认用的是delete a,只能delete掉一个对象,所以我们需要自定义回调函数,用于析构传进的对象数组。

5.1 使用函数模板作为自定义删除器

//用来释放malloc出来的函数对象
template<class T>
class FreeFunc{
public:
	void operator()(T* ptr)
	{
		cout << "free:" << ptr << endl;
		free(ptr);
	}
};

//用来释放new[]出来的函数对象
template<class T>
class DeleteArrayFunc {
public:
	void operator()(T* ptr)
	{
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

//用来释放文件描述符的函数对象
template<class T>
class ClosefdFunc{
public:
	void operator()(T* fd)
	{
		cout << "close fd" << fd << endl;
		fclose(fd);
	}
};

void test06(){

	FreeFunc<int>        Object1;
	shared_ptr<int>      sp1((int*)malloc(sizeof(int)*4), Object1);         // 回调函数是可调用对象,可以是普通的函数名或者函数对象或者lambda表达式

	DeleteArrayFunc<int> Object2;
	shared_ptr<int>      sp2(new int[4], Object2);

	ClosefdFunc<FILE>    Object3;
	shared_ptr<FILE>     sp3(fopen("myfile.txt","w"), Object3);

}

结果看到,三个自定义析构函数均被调用,并且可以发现文件对象会被编译器优先释放。
在这里插入图片描述

5.2 使用其它新特性作为删除器

  • 1)可以用default_delete来做删除器,这个是标准库里的模板类。
class A {
public:
    A() {};
    ~A() {};
};
 
void test07(){
    shared_ptr<A> p(new A[10], std::default_delete<A[]>());
}
  • 2)可以用C++新规则的简单写法。
void test08(){
    shared_ptr<A[]> p1(new A[10]);//A类在上面
    shared_ptr<int[]> p2(new int[10]);
    p2[0] = 12;
    p2[1] = 15;

    cout<<p2[0]<<endl;//输出12
    cout<<p2[1]<<endl;//输出15
}

5.3 模板+lambda表达式实现开发时常用的删除器
由于我们开发时很少使用到像5.1这种模板,因为比较长,且需要额外定义对象传入,所以我们开发时更倾向使用lambda表达式,代码更少。但是并不是不能使用5.1这种方法。

  • 1)不带删除数组的模板+lambda表达式删除器。
template <class T>
shared_ptr<T> my_make_shared(){
    return shared_ptr<T>(new T,[](T *ptr){delete ptr,ptr=nullptr;});
}

void test09(){
    //对于类中没有成员的,只有使用裸指针
    shared_ptr<int> sh1 = my_make_shared<int>();
    *sh1.get() = 2;
    cout<<*sh1.get()<<endl;

    //对于类中有成员的,可以直接使用智能指针
    shared_ptr<A> sh2 = my_make_shared<A>();
    sh2->SetI(100);
    cout<<sh2->GetI()<<endl;
}
  • 2)带删除数组的模板+lambda表达式删除器。下面我将lambda表达式换成default_delete函数,因为上面已经有lambda,当然你也可以换一下。
template<typename T>
shared_ptr<T> my_make_shared_array(size_t size)
{
    return shared_ptr<T>(new T[size], default_delete<T[]>());
}
void test10(){
    shared_ptr<A> parray = my_make_shared_array<A>(15);
    parray->SetI(200);
    //parray.get()[0].SetI(100);//也行,但直接使用智能指针更安全
    cout<<parray.get()[0].GetI()<<endl;//输出200
}

5.4 指定删除器额外说明
就算是两个shared_ptr指定了不同的删除器,只要他们所指向的对象类型相同,那么这两个shared_ptr也是属于同一类型,所以它们可以改变指向,改变指向后,若引用计数变为0,则先用就的删除器删除内存,然后它将使用新的删除器。
例如。

auto lambda1 = [](int *p)
{
    delete p;
};
 
auto lambda2 = [](int *p)
{
    delete p;
};
 
 
shared_ptr<int> p1(new int(100), lambda1);
shared_ptr<int> p2(new int(100), lambda2);
p2 = p1; //p2会先调用lambda2把自己所指向的对象释放,然后指向p1所指向的对象。p1所指向 
         //的对象引用计数为2,整个main执行完成后还会调用lambda1来释放p1,p2共同指向的 
         //对象

并且注意,我们5.3常用删除迭代器为何不使用make_shared,因为使用make_shared这种方法我们无法指定自己的删除器。所以只能使用new。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

智能指针之shared_ptr初始化,引用计数,常用操作和自定义删除器等等03 的相关文章

  • C++ 析构函数和函数调用顺序

    假设我有以下代码片段 Foo foo return bar 现在 C 标准是否保证 bar 将在 foo Foo 之前调用 或者这是编译器 实现的选择 Thanks 这是有保证的行为 实际执行过程如下 0 enter block scope
  • LINQ TO ENTITY 无法与枚举类型进行比较

    下面是枚举叶子 public enum Leaves Annual 0 Medical 1 Hospitalization 2 Unpaid 3 下面是linq查询 public ActionResult ApproveLeave int
  • 在opencv中保存帧而不压缩

    我正在尝试使用写 OpenCV 函数 我想保存帧 TIFF扩大 我遇到的问题是保存的图像被压缩 所以我无法使用它们 知道如何摆脱这种压缩吗 提前致谢 不要介意西奇说的话 TIFF 标志通过 LZW 压缩硬编码在 opencv 二进制文件中
  • 获取 WSA 错误代码的格式化消息

    我在 win32 C 应用程序中使用winsock2 我将使用 MessageBox 显示可以通过调用 WSAGetLastError 检索的网络错误 我怎样才能做到这一点 我看到 FormatMessage 但我不明白如何使用它 例如 以
  • C# Socket.receive连续接收0字节且循环中不阻塞

    我正在尝试用 C 编写一个最简单的多线程 TCP 服务器 它接收来自多个客户端的数据 每次连接新客户端时 都会建立套接字连接 并将套接字作为参数传递给新类函数 之后运行 while 循环并接收数据 直到客户端连接为止 这里的问题是 sock
  • 如何解决“HTTP 错误 404.3 - 未找到”错误?

    简单的问题 我启动 VS2008 并创建一个新的 WCF 服务应用程序 这将创建一个默认应用程序 并使用一些测试方法来显示它的工作原理 我按 CTRL F5 确实有效 伟大的 但是 它使用 Visual Studio 开发服务器 我不想支持
  • F1 2019 UDP解码

    我目前正在为 F1 方向盘开发自己的显示器 F1 2019 由codemasters提供 通过UDP发送数据 该数据存储在字节数组中 我在解码返回的数组时遇到一些问题 问题是我得到了很多信息 但我不知道如何处理它们 我将向您介绍我所尝试过的
  • 将 MyGeneration 与 Fluent NHibernate 结合使用

    我在这里找到了一个使用 MyGeneration 生成 NHibernate 代码的绝佳模板 http vucetica blogspot com 2009 01 nhibernate template for my Generation
  • 正确别名向量

    我无法在其他地方找到答案 所以我想我只需要问这个 我正在尝试获取向量 其中存储 int 指针 的别名 如下所示 void conversion Engine ENGINES The Engine class has a vector of
  • 以 ASCII 字符串形式获取 MemoryStream 内容的快速方法

    我在 MemoryStream 中有一个 JSON 字符串 我使用以下代码将其作为 ASCII 字符串获取 MemoryStream memstream new MemoryStream Write a JSON string to mem
  • 如何设置属性选择器的值 Expression>

    我需要使用模式工厂的想法将 Person 类实体中的实体属性 Address 与 FactoryEntities 类中的表达式 linq 相关联 看看这就是我所拥有的并且我想要做的 Address address new Address a
  • 在 C++ 中重用异常处理代码

    我有这两个函数 具有重复的异常处理 其唯一目的是显示错误消息 void func1 noexcept try do task do another task catch const std out of range e show msg O
  • Clang 使用 -nostdlib 生成崩溃代码

    我正在尝试为可执行文件设置自己的运行时环境 但无法使用 clang v3 4 1ubuntu1 目标 x86 64 pc linux gnu 来生成没有段错误的可执行文件 我已将问题简化为以下内容 如果我有一个文件 crt1 c 除了满足
  • 验证仅适用于数组的第一项

    给定这个模型代码 Required Display Name Name public string Name get set 以下查看代码有效 Html LabelFor model gt model Name Html TextBoxFo
  • 初学者友好的方法来获取所有文件和目录的列表

    使用 NET 3 0 我得到了下面的方法 它可以正确返回指定目录的所有文件和目录 以及子目录 的集合 如果可能的话 我想将其简化为仅使用我非常熟悉的结构 具体来说 有以下几点我不太清楚 1 IEnumerable
  • 使用全局 Web API 过滤器属性进行 Unity 依赖注入

    参考这个CodePlex 统一文章 http unity codeplex com discussions 446780我能够使用 WebAPI 控制器获取过滤器属性 如下所示 MyFilterAttribute public class
  • 使用C#在SQL Server上执行sql文件

    我有很多程序 视图 函数等文件 我想在 SQL Server 2005 2008 上的适当数据库中执行这些文件 创建组件 还有一点是我想使用 C 来执行它们 另一点需要提及的是 我希望应用程序也可以在远程 SQL Server 上执行此文件
  • OpenMP 动态调度与引导调度

    我正在研究 OpenMP 的调度 特别是不同的类型 我了解每种类型的一般行为 但澄清一下何时进行选择会很有帮助dynamic and guided调度 英特尔的文档 https software intel com en us articl
  • 如何让c代码执行hex机器代码?

    我想要一个简单的 C 方法能够在 Linux 64 位机器上运行十六进制字节码 这是我的 C 程序 char code x48 x31 xc0 include
  • 有C语言的解释器吗? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话

随机推荐

  • 华为校招机试题- 新员工座位安排系统-2023年

    题目描述 工位由序列F1 F2 Fn组成 Fi值为0 1或2 其中0代表空置 1代表有人 2代表障碍物 1 某一空位的友好度为左右连续老员工数之和 2 为方便新员工学习求助 优先安排友好度高的空位 给出工位序列 求所有空位中友好度的最大值
  • 统计学习方法 例7.1 超详细求解过程

    例7 1 已知一个如图所示的训练数据集 其正例点是 x 1 3 3
  • C#软件开发实例.私人订制自己的屏幕截图工具(九)使用自定义光标,QQ截图时的光标

    本实例全部文章目录 一 功能概览 二 创建项目 注册热键 显示截图主窗口 三 托盘图标及菜单的实现 四 基本截图功能实现 五 针对拖拽时闪烁卡顿现象的优化 六 添加配置管理功能 七 添加放大镜的功能 八 添加键盘操作截图的功能 九 使用自定
  • HashMap实现原理, 扩容机制,面试题和总结

    文章目录 1 讲下对HashMap的认识 2 HashMap的一些参数 3 为什么HashMap的长度必须是2的n次幂 4 HashMap 为什么在获取 hash 值时要进行位运算 5 HashMap在JDK1 7和JDK1 8中有哪些不同
  • PHP实现读取指定目录下的所有文件

    在php中读取指定目录下的文件主要用到了opendir和readdir函数 一 opendir 打开目录句柄 1 语法 opendir path context 2 参数说明 参数 描述 path 必需 规定要打开的目录路径 context
  • 【傻瓜向装系统】电脑重装&&加固态硬盘

    市面上大部分PE制作工具都会在操作系统中内嵌广告或软件等 慎重使用 重装 顺序步骤 重要数据备份 备份到移动硬盘或其他设备 格式化U盘 至少8G 准备作为系统启动盘 安装PE制作工具 如老毛桃等 制作启动盘 下载对应操作系统放到U盘中 某个
  • 极大极小树

    博弈树作为传统AI领域的一个传统又经典的算法 有着广泛的应用 尤其是棋类AI 记得曾经刚学C语言的时候 用控制台写了一个五子棋的程序 后来突发奇想 给它增加可以人机对战的AI 设计了一个简单的根据当前局面判断最优落子的AI 但是只能想到两手
  • DNS、HTTP 与 HTTPS

    DNS HTTP与HTTPS DNS 域名的层级 查找过程 优化 常见的 DNS 攻击 使用的协议 CDN 路由解析 内容分发 HTTP 协议 HTTP 请求报文 GET 和 POST 有什么区别 幂等性和非幂等性 前后端传参 HTTP响应
  • Ubuntu 20.04 开启SSH服务

    更新软件下载源 sudo apt update 安装ssh服务 sudo apt install openssh server 开启防火墙ssh的服务端口 sudo ufw allow ssh 附 还可以查看或更改ssh服务的状态 查看ss
  • Javaweb实现增删改查操作操作

    Javaweb实现增删改查操作操作 一 准备工作 1 Idea编辑器 eclispe和myeclispe都可以 个人推荐使用idea 新建一个web项目 2 数据库mysql 3 需要提前了解的知识点 servlet el和jstl表达式
  • 怎么检测两张照片的相似度,两张图片相似度测试

    计算图像相似度的算法有哪些 SIM StructuralSIMilarity 结构相似性 这是一种用来评测图像质量的一种方法 由于人类视觉很容易从图像中抽取出结构信息 因此计算两幅图像结构信息的相似性就可以用来作为一种检测图像质量的好坏 首
  • 服务器盘符名称修改,linux下powerpath对盘与更改盘符名的教程

    PowerPath 软件在服务器上运行并管理服务器和存储系统中的虚拟磁盘之间的路径 如果一条路径出现故障 它可以将I O 转发到有效路径中 并提供负载平衡来平均分配各条路径中的I O 负载 另外这里的路径由HBA 硬件和驱动程序 光纤 两个
  • docker web mysql_在 Docker 中完整部署 Web 应用

    原标题 在 Docker 中完整部署 Web 应用 一个完整的 Web 应用包含前端页面 数据库 后台逻辑等 按照一般流程去构建需要配置 Nginx MySQL 以及后台服务器 运维涉及到的部分十分复杂 而 Docker 可以将这些东西 数
  • STM8 学习笔记13:PWM

    PWM Gitee 空间跳转 https gitee com galoc stm8 git 1 概述 PWM也叫脉冲宽度调制 是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术 频率 周期 占空比 1 1 PWM 频率 是指在
  • Java常用类System类、Math、BigInteger 和 BigDecimal

    1 System类 public void test1 String javaVersion System getProperty java version System out println java的version javaVersi
  • Windows磁盘管理中的压缩卷操作

    基本磁盘 主分区在压缩卷操作后为黑色的未分配 逻辑分区在压缩卷操作后为绿色的可用空间 动态磁盘 简单卷在压缩卷操作后为黑色的未分配
  • Figma怎么汉化?这个Figma 汉化插件早知道就好了!

    Figma官方目前没有中文版 可能与早期的封禁大疆事件和面向非洲地区扩张策略有关 但是可以使用第三方汉化插件来获得中文版本的Figma Figma cool是一个提供汉化插件的网站 支持多个平台 另外 还有一款名为即时设计的中文设计工具 提
  • java.lang.IllegalArgumentException 异常报错完美解决

    目录 修改JDK使用版本 修改开发工具idea配置 eclipse的直接跳过这个看下面 修改开发工具eclipse配置 学习spring依赖注入的时候碰到这个坑 折腾了许久 记录一下以防其他小伙伴入坑 该异常主要原因是因为JDK与Sprin
  • Lisp-Stat 翻译 —— 第四章 其它Lisp特性

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 第四章 其它Lisp特性 上一章介绍了Lisp编程的基础 在那章里重点展示了对编写Lisp函数有用的编程技术 为了最高效地使用这些技术 知道Lisp和Lisp Stat提供
  • 智能指针之shared_ptr初始化,引用计数,常用操作和自定义删除器等等03

    一 share ptr 1 share ptr基础 1 共享所有权 不是被一个shared ptr拥有 而是被多个shared ptr之间相互协作 shared有额外开销 2 工作原理 利用引用计数的方法管理一片内存 每增加一个shared