智能指针(Newbie Note)
智能指针专题
- 1.普通指针的问题
- 2.智能指针是什么
- 什么是所有权
- 3.智能指针三个好处:
- 4.C++11提供的智能指针
- 4.1 shared_ptr(共享所有权指针)
- 4.1.1 分配内存
- 4.1.2 成员函数
- 4.1.3 计数情况汇总:
- 4.1.4 示例代码(计数)
- 4.1.5 示例代码(reset)
- 4.1.6 自定义删除器
- 4.1.7 shared_ptr的陷阱
- 4.2 unique_ptr(独占所有权指针)
- 4.2.1 分配内存
- 4.2.2 成员函数
- 4.2.3 拷贝赋值
- 4.2.4 release
- 4.2.5 reset
- 4.2.6 自定义删除器
- 4.2.7 unique_ptr的陷阱
- 不要与裸指针混用
- 5.性能
- 6.选择指针
- 6.1 unique_ptr场景示例:
- 6.1.1 作为参数
- 6.1.2 作为返回值
- 6.2 shared_ptr场景示例:
- 6.2.1 作为参数
- 参考资料
1.普通指针的问题
-
1.访问失败:如果一块内存被多个指针引用,但其中的一个指针释放且其余的指针并不知道,这样的情况下,就发生了访问失败。
-
2.内存泄漏:从堆中申请了内存后不释放回去,就会引发内存泄漏。
2.智能指针是什么
在构造的时候分配内存,当离开作用域的时候,自动释放分配的内存,这样的话开发人员就可以从手动动态管理内存的繁杂内容中解放出来。
每种智能指针都是以类模板的方式实现的,shared_ptr 也不例外。
什么是所有权
https://blog.csdn.net/weixin_39722329/article/details/96301534
-
单一所有权:所有权拥有着有义务去释放或转移所有权,同一时刻只会有一个所有权拥有者
-
共享所有权:和使用裸指针一样,所有权的使用者不必释放内存,引用计数器会负责释放内存,同一时刻可以有多个所有权拥有者。
3.智能指针三个好处:
-
1.明确资源的所属权
-
2.避免忘记delete,这种比较容易犯错误
-
3.更好的处理异常
//函数结束后shared_ptr自动释放内存
void f(){shared_ptr<int> sp(new int(11));//假设抛出了异常,而且在f中未捕获
}//函数结束后ip所指向的内存没有被释放。
void f1(){int* ip = new int(12);//假设delete语句前抛出了异常,而且在f中未捕获delete ip;
}
重要性:1>>2>3
4.C++11提供的智能指针
4.1 shared_ptr(共享所有权指针)
共享指针shared_ptr是具有共享所有权语义的智能指针。 每当共享指针shared_ptr的最后一个所有者被销毁时,关联对象都将被删除(或关联资源被清除)。
4.1.1 分配内存
std::make_shared
// make_shared<int>分配一块int类型大小的内存,并值初始化为100(返回值是shared_ptr类型,因此可以直接赋值给sp)
shared_ptr<int> sp = std::make_shared<int>(100);
new 接受指针参数的智能指针构造函数是explicit的,直接初始化形式
// 错误! 不会进行隐式转换,类型不符合
shared_ptr<int> sp1 = new int(100);
// 正确,直接初始化调用构造函数
shared_ptr<int> sp2(new int(100000));
4.1.2 成员函数
p.get()
p.get()的返回值就相当于一个裸指针的值,使用遵守以下几个约定
1.不要保存p.get()的返回值
2.无论是保存为裸指针还是shared_ptr都是错误的
3.保存为裸指针不知什么时候就会变成空悬指针
4.保存为shared_ptr则产生了独立指针
5.不要delete p.get()的返回值
6.会导致对一块内存delete两次的错误swap(p,q)
交换p、q中保存的指针shared_ptr<T> p(q)
p是q的拷贝,它们指向同一块内存,互相关联p = q
用q为p赋值,之后p、q指向同一块内存,q引用计数+1,p(原来内存空间的)引用计数-1p.use_count()
返回与p共享对象的智能指针数量shared_ptr<T> p(q,d)
q是一个可以转换为T*的指针,d是一个可调用对象(作为删除器),p接管q所指对象的所有权,用删除器d代替delete释放内存p.reset()
将p重置为空指针p.reset(p)
将p重置为p(的值)p.reset(p,d)
将p重置为p(的值)并使用d作为删除器shared_ptr为什么没有release()
对于shared_ptr,是可能存在多个shared_ptr指向同一块内存,如果提供了release可能会造成错误的释放,导致其他shared_ptr出现错误。
4.1.3 计数情况汇总:
赋值(增加)
auto sp = make_shared<int>(1024); // sp的引用计数为1
#include <iostream>
#include <memory>using namespace std;
// compile:g++ test.cpp -o a.exe -std=c++11int main()
{{auto sp1 = make_shared<string>("obj1");auto sp2(sp1);auto sp3 = make_shared<string>("obj2");cout << "before sp2->use_count() = " << sp2.use_count() << '\n';cout << "before sp3->use_count() = " << sp3.use_count() << '\n';sp1 = sp3; // 该操作会减少sp2的引用计数,增加sp3的引用计数cout << "after sp2->use_count() = " << sp2.use_count() << '\n';cout << "after sp3->use_count() = " << sp3.use_count() << '\n';}return 0;
}
该操作会减少sp2的引用计数,增加sp3的引用计数。(sp1、sp2指向对象obj1,sp3指向对象obj2,那么赋值之后,sp1也会指向obj2,那就是说指向obj1的就少了,指向obj2的就会多。)
拷贝(增加)
auto sp2 = make_shared<int>(1024);
auto sp1(sp2);
该操作会使得sp1和sp2都指向同一个对象。
传参(拷贝)(增加)
而关于拷贝比较容易忽略的就是作为参数传入函数:
auto sp2 = make_shared<int>(1024);
func(sp2); // func的执行会增加其引用计数
reset(减少)
释放sp指向的对象( 而如果sp是唯一指向该对象的,则该对象被销毁 )
sp.reset()
4.1.4 示例代码(计数)
参考:https://en.cppreference.com/w/cpp/memory/shared_ptr
#include <iostream>
#include <memory>// g++ test.cpp -o a.exe -std=c++11 class Base
{
public:Base() { std::cout << " Base::Base()\n"; }~Base() { std::cout << " Base::~Base()\n"; }
};class Derived: public Base
{
public:Derived() { std::cout << " Derived::Derived()\n"; }~Derived() { std::cout << " Derived::~Derived()\n"; }
};void test(std::shared_ptr<Base> p)增加引用计数
{ std::cout << "local pointer in a function:\n"<< " p.get() = " << p.get()<< ", p.use_count() = " << p.use_count() << '\n';std::shared_ptr<Base> lp = p; std::cout << "local pointer in a function:\n"<< " lp.get() = " << lp.get()<< ", lp.use_count() = " << lp.use_count() << '\n';
}//销毁ptr,减少引用计数int main()
{std::shared_ptr<Base> p = std::make_shared<Derived>();std::cout << "func before:\n"<< " p.get() = " << p.get()<< ", p.use_count() = " << p.use_count() << '\n';test(p);std::cout << "func after:\n"<< " p.get() = " << p.get()<< ", p.use_count() = " << p.use_count() << '\n';
}
运行结果
Base::Base()
Derived::Derived()
func before:p.get() = 0x1028c30, p.use_count() = 1
local pointer in a function:p.get() = 0x1028c30, p.use_count() = 2
local pointer in a function:lp.get() = 0x1028c30, lp.use_count() = 3
func after:p.get() = 0x1028c30, p.use_count() = 1
Derived::~Derived()
Base::~Base()
4.1.5 示例代码(reset)
重置共享指针,减少计数
#include<memory>
#include<iostream>
// 编译: g++ test.cpp -o a.exe -std=c++11using namespace std;
class A {
public:int i ;A() { cout << "construct\n"; }~A() { cout << "delete "<< i <<"\n"; }
};
int main()
{// 共享指针a,b,c都指向堆内存new A的位置shared_ptr<A> a( new A);shared_ptr<A> b(a);shared_ptr<A> c(b);shared_ptr<A> d(new A);a->i = 10;cout << a.use_count() << endl;cout << b.use_count() << endl;d->i = 30;// 错:不要用p.get()的返回值为shared_ptr赋值,因为返回的是裸指针,很容易被共享指针重复释放,造成错误//a.reset(d.get())// 令a释放指向的空间 A//a.reset();// 令a释放指向的空间 A,指向新空间a.reset(new A);a->i = 20;cout << b.use_count() << endl;cout << "end" <<endl;return 0;
}
4.1.6 自定义删除器
如果用shared_ptr管理非new对象或是没有析构函数的类时,应当为其传递合适的删除器,原理是:当删除器的指针Deleter传给shared_ptr/unique_ptr时,shared_ptr/unique_ptr不会使用默认的delete val来释放其管理的资源,而是使用Deleter(val)来释放资源,这样就调用了Deleter来释放管理的资源。
1.普通删除函数定义类似于:
void Deleter(T *val){// 其他代码// 释放val的内存delete val;// 或者(如果val是数组)delete[] val;
}
#include <iostream>
#include <memory>
#include <string>
// 编译: g++ test.cpp -o a.exe -std=c++11
using namespace std;class Connection{
public:string _name;explicit Connection(string name):_name(name){}string get_name() const {return _name;}
};void close(Connection* connection){cout << string("关闭") + connection->get_name() + "管理的连接中..." << endl;// 关闭连接的代码// .....cout << "关闭完成。" << endl;
}// 函数式删除器
void Deleter(Connection *connection){close(connection);delete connection;
}int main(){// 新建管理连接Connection的智能指针shared_ptr<Connection> sp(new Connection("shared_ptr"), Deleter);sp->_name = "hello";
}
- 自定义释放规则,用于申请的动态数组
对于申请的动态数组来说,shared_ptr 指针默认的释放规则是不支持释放数组的,只能自定义对应的释放规则,才能正确地释放申请的堆内存。释放规则可以使用 C++11 标准中提供的 default_delete 模板类,我们也可以自定义释放规则:
//1.指定 default_delete 作为释放规则
std::shared_ptr<int> p6(new int[10], std::default_delete<int[]>());//2.自定义释放规则
void deleteInt(int*p) {delete []p;
}
//初始化智能指针,并自定义释放规则
std::shared_ptr<int> p7(new int[10], deleteInt);//3.lambda方式构造和释放
std::shared_ptr<int> p7(new int[10], [](int* p) {delete[]p; });
4.1.7 shared_ptr的陷阱
1.不要与裸指针混用
错误场景1:
int *x(new int(10));
shared_ptr<int> sp1(x);
shared_ptr<int> sp2(x);//x随时可能变成空悬指针而无从知晓
错误场景2:
int *x(new int(10));
//创建了一个指向x指针所指内存的共享指针,引用计数为1,是引用这块内存的唯一共享指针func(shared_ptr<int> (x));
//离开函数即离开共享指针的作用域,这块内存即被删除
2.谨慎使用p.get()的返回值
shared_ptr<int> sp1(new int(10));
shared_ptr<int> sp2(sp1), sp3;
sp3 = sp1;//一个典型的错误用法
shared_ptr<int> sp4(sp1.get());
cout << sp1.use_count() << " " << sp2.use_count() << " " << sp3.use_count() << " " << sp4.use_count() << endl;//输出:
3
3
3
1(独立)
sp1,sp2,sp3是相互关联的共享指针,共同控制所指内存的生存期,sp4虽然指向同样的内存,却是与sp1,sp2,sp3独立的,sp4按自己的引用计数来关联内存的释放。
4.2 unique_ptr(独占所有权指针)
两个unique_ptr不能指向同一个对象,不能进行复制操作,只能进行移动操作。
4.2.1 分配内存
与shared_ptr不同,unique_ptr没有定义类似make_shared的操作,因此只可以使用new来分配内存,并且由于unique_ptr不可拷贝和赋值,初始化unique_ptr必须使用直接初始化的方式。
unique_ptr<int> up1(new int()); // okay:直接初始化std::unique_ptr<int> p4(new int);
std::unique_ptr<int> p5(std::move(p4)); // okay:调用移动构造函数,p5 将获取 p4 所指堆空间的所有权,而 p4 将变成空指针(nullptr)unique_ptr<int> up2 = new int(); // error! 避免隐式转换
unique_ptr<int> up3(up1); // error! 不允许拷贝
4.2.2 成员函数
-
up.release() up放弃对它所指对象的控制权,并返回保存的指针,将up置为空,不会释放内存
-
up.reset()
参数可以为空,内置指针,先将up所指对象释放,然后重置up的值
4.2.3 拷贝赋值
前面说了unique_ptr不可拷贝和赋值,那要怎样传递unique_ptr参数和返回unique_ptr呢? 事实上不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr
// 从函数返回一个unique_ptr
unique_ptr<int> func1(int a)
{return unique_ptr<int> (new int(a));
}// 返回一个局部对象的拷贝
unique_ptr<int> func2(int a)
{unique_ptr<int> up(new int(a));return up;
}
或者是引用
void func1(unique_ptr<int> &up){cout<<*up<<endl;
}unique_ptr<int> func2(unique_ptr<int> up){cout<<*up<<endl;return up;
}// 使用up作为参数
unique_ptr<int> up(new int(10));// 传引用,不拷贝,不涉及所有权的转移
func1(up);// 暂时转移所有权,函数结束时返回拷贝,重新收回所有权
up = func2(unique_ptr<int> (up.release()));
// 如果不用up重新接受func2的返回值,这块内存就泄漏了
4.2.4 release
释放方法:注意!注意!注意!这里的释放并不会摧毁其指向的对象,而且将其指向的对象释放出去。
#include <iostream>
#include <memory>//编译:g++ test.cpp -o a.exe -std=c++11int main () {std::unique_ptr<int> auto_pointer(new int);int * manual_pointer;*auto_pointer=10;manual_pointer = auto_pointer.release();// (auto_pointer is now empty)std::cout << "manual_pointer points to " << *manual_pointer << '\n';delete manual_pointer;return 0;
}
执行结果为:
manual_pointer points to 10
4.2.5 reset
重置方法,销毁由该智能指针管理的任何可能存在的对象,该智能指针被指为空
#include <iostream>
#include <memory>//编译:g++ test.cpp -o a.exe -std=c++11int main () {std::unique_ptr<int> up; // emptyup.reset (new int); // takes ownership of pointer*up=5;std::cout << *up << '\n';up.reset (new int); // deletes managed object, acquires new pointer*up=10;std::cout << *up << '\n';up.reset(); // deletes managed objectreturn 0;
}
执行结果:
5
10
4.2.6 自定义删除器
#include <iostream>
#include <memory>
#include <string>
// 编译: g++ test.cpp -o a.exe -std=c++11
using namespace std;class Connection{
public:string _name;explicit Connection(string name):_name(name){}string get_name() const {return _name;}
};void close(Connection* connection){cout << string("关闭") + connection->get_name() + "管理的连接中..." << endl;// 关闭连接的代码// .....cout << "关闭完成。" << endl;
}// 函数式删除器
void Deleter(Connection *connection){close(connection);delete connection;
}int main(){// 新建管理连接Connection的智能指针unique_ptr<Connection, decltype(Deleter)*> up(new Connection("unique_ptr"), Deleter);up->_name = "hello";
}
4.2.7 unique_ptr的陷阱
不要与裸指针混用
错误做法
int *x(new int());
unique_ptr<int> up1,up2;
// 会使up1 up2指向同一个内存
up1.reset(x);
up2.reset(x);
unique_ptr不允许两个独占指针指向同一个对象,在没有裸指针的情况下,我们只能用release获取内存的地址,同时放弃对对象的所有权,这样就有效避免了多个独占指针同时指向一个对象。
正确做法
unique_ptr<int> up1(new int()); // okay,直接初始化
unique_ptr<int> up2;
up2.reset(up1.release());
5.性能
- 内存占用高
shared_ptr 的内存占用是裸指针的两倍。因为除了要管理一个裸指针外,还要维护一个引用计数。
因此相比于 unique_ptr, shared_ptr 的内存占用更高 - 原子操作性能低
考虑到线程安全问题,引用计数的增减必须是原子操作。而原子操作一般情况下都比非原子操作慢。 - 使用移动优化性能
shared_ptr 在性能上固然是低于 unique_ptr。而通常情况,我们也可以尽量避免 shared_ptr 复制。
如果,一个 shared_ptr 需要将所有权共享给另外一个新的 shared_ptr,而我们确定在之后的代码中都不再使用这个 shared_ptr,那么这是一个非常鲜明的移动语义。对于此种场景,我们尽量使用 std::move,将 shared_ptr 转移给新的对象。因为移动不用增加引用计数,性能比复制更好。
6.选择指针
不是说任何地方都要使用智能指针,比如说你想传递一个对象到一个函数里,那你就可以使用引用或者普通指针(raw ptr), 这里的引用和普通指针体现的是没有所属权(ownership),也就是说函数本身不负责这个对象的生命周期。只有需要体现所属权(ownership)的创立和变动的时候,采用智能指针。参考
选择条件:
在使用智能指针的时候,优先选用unique_ptr,原因如下:
1.语义简单,即当你不确定使用的指针是不是被分享所有权的时候,默认选unique_ptr独占式所有权,当确定要被分享的时候可以转换成shared_ptr;
2.unique_ptr效率比shared_ptr高,不需要维护引用计数和背后的控制块;
3.unique_ptr用起来更顺畅,选择性更多,可以转换成shared_ptr和通过get和release定制化智能指针(custom smart pointer)。
如果有多个指针指向同一对象的话,你应该使用shared_ptr;
如果一个对象只需要一个智能指针,那你应该是用unique_ptr,它非常适合于返回值类型为unique_ptr的函数
6.1 unique_ptr场景示例:
6.1.1 作为参数
因为不能被拷贝,所以传递裸指针或者引用或者外部不需要了直接转移,但是要注意不能传递值(拷贝)
// 裸指针
#include<iostream>
#include<memory>
void test(int *p)
{*p = 10;
}
int main()
{std::unique_ptr<int> up(new int(42));test(up.get());//传入裸指针作为参数std::cout<<*up<<std::endl;//输出10return 0;
}// 引用
#include<iostream>
#include<memory>
void test(std::unique_ptr<int> &p)
{*p = 10;
}
int main()
{std::unique_ptr<int> up(new int(42));test(up);std::cout<<*up<<std::endl;//输出10return 0;
}// 转移
#include<iostream>
#include<memory>
void test(std::unique_ptr<int> p)
{*p = 10;
}
int main()
{std::unique_ptr<int> up(new int(42));test(std::unique_ptr<int>(up.release()));//test(std::move(up));//这种方式也可以return 0;
}
6.1.2 作为返回值
返回可以用unique_ptr(伪代码),这里可以理解为返回值是拷贝了指向的地方,相当于std::move,获取唯一的所有权
unique_ptr<int> make_init(int n)
{return unique_ptr<int> (new int(n));
}int main()
{
···vector<unique_ptr<int>> vp(size);for(int i = 0; i < vp.size(); i++)vp[i] = make_init(rand() % 1000)
···
}
6.2 shared_ptr场景示例:
6.2.1 作为参数
#include<iostream>
#include<memory>
void func0(std::shared_ptr<int> sp)
{std::cout<<"fun0:"<<sp.use_count()<<std::endl;
}void func1(std::shared_ptr<int> sp)
{std::cout<<"fun1:"<<sp.use_count()<<std::endl;
}void func2(std::shared_ptr<int> &sp)
{std::cout<<"fun1:"<<sp.use_count()<<std::endl;
}int main()
{auto sp = std::make_shared<int>(1024);func0(sp); // 拷贝方式(这种方式unique不可以)func1(sp); // 拷贝方式(这种方式unique不可以)func2(sp); // 引用方式return 0;
}
这里建议传参使用引用,免拷贝。
参考资料
主要参考:https://www.yanbinghu.com/categories/Cpp/
C++11智能指针: https://www.jianshu.com/p/e4919f1c3a28
C++11 shared_ptr智能指针:http://c.biancheng.net/view/7898.html
shared_ptr官方讲解:http://www.cplusplus.com/reference/memory/shared_ptr/shared_ptr/
unique_ptr官方讲解:http://www.cplusplus.com/reference/memory/unique_ptr/unique_ptr/
智能指针的选择:https://blog.csdn.net/qq_22533607/article/details/82318595
相关文章:
智能指针(Newbie Note)
智能指针专题 1.普通指针的问题2.智能指针是什么什么是所有权 3.智能指针三个好处:4.C11提供的智能指针4.1 shared_ptr(共享所有权指针)4.1.1 分配内存4.1.2 成员函数4.1.3 计数情况汇总:4.1.4 示例代码(计数)4.1.5 示例代码(rese…...
2023.11.22 homework
七年级数学 五年级数学 也不知道可以教到几年级,估计很快就教不动了。人生啊。...
存储日志数据并满足安全要求
日志数据是包含有关网络中发生的事件的记录的重要信息,日志数据对于监控网络和了解网络活动、用户操作及其动机至关重要。 由于网络中的每个设备都会生成日志,因此收集的数据量巨大,管理和存储所有这些数据成为一项挑战,日志归档…...
Linux系统管理与服务器安全:构建稳健云数据中心
💂 个人网站:【 海拥】【神级代码资源网站】【办公神器】🤟 基于Web端打造的:👉轻量化工具创作平台💅 想寻找共同学习交流的小伙伴,请点击【全栈技术交流群】 在当今数字化时代,云数据中心已经成…...
用css实现原生form中radio单选框和input输入框的hover样式以及聚焦focus的样式
一.问题描述:用css实现原生form中radio单选框和input的hover已经focus的样式 在实际的开发中,一般公司ui都会给效果图,比如单选按钮radio样式,input输入框hover的时候样式,以及focus的时候样式,等等&#…...
【中国平安社招校招】【内推】【当天内推】
中国平安社招校招内推 通过内推链接即时内推,反馈速度比正常要快(可私信问进度) 开放大量HC(不限岗位和地区,技术、设计、产品、运营、数据等都可内推) Step1:查看相关职位 浏览平安的招聘官网:复制以下地址至浏览器打开。注意以下链接包含…...
CentOS 8最小安装,VM使用这个内存占用小很多
文章目录 一、安装包下载作者使用的安装包 二、安装过程截图三、最小化安装拥有的外部命令四、查看ip(方便ssh连接)五、yum源有问题参考文档 一、安装包下载 CentOS 网站: https://www.centos.org/CentOS 维基: https://wiki.cen…...
【C++ Primer Plus学习记录】递增运算符(++)和递减运算符(--)
递增运算符()和递减运算符(--):前缀版本位于操作数前面,如x;后缀版本位于操作数后面,如x。两个版本对操作数的影响是一样的,但是影响的时间不同。这就像吃饭前买单和吃饭…...
Oracle 数据库中 查询时如何使用日期(时间)作为查询条件
在 Oracle 数据库中,可以使用日期(时间)作为查询条件来筛选数据。 格式化日期的三种方式 方式一: 关键字 DATE 使用关键字DATE, 仅表示日期类型,并不包含时间信息 方式二:关键字TIMESTAMP 使用关键字TI…...
Tomcat 基线安全加固操作
目录 账号管理、认证授权 日志配置 通信协议 设备其他安全要求 账号管理、认证授权 ELK-tomcat-01-01-01 编号 ELK-Tomcat-01-01-01 名称 为不同的管理员分配不同的账号 实施目的 应按照用户分配账号,避免不同用户间共享账号,提高安全性。 问题影响 …...
redis缓存穿透、击穿、雪崩
在我们日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访…...
什么是数字孪生?
数字孪生是指通过数字化技术手段,将现实世界中的实体物理系统或过程与其数字化模型相连接,实现实体物理系统或过程的虚拟仿真、监测、预测和优化等功能的一种技术。数字孪生技术可以将物理系统的运行状态、性能参数、故障信息等实时反馈到数字模型中&…...
【DevOps】Git 图文详解(八):后悔药 - 撤销变更
Git 图文详解(八):后悔药 - 撤销变更 1.后悔指令 🔥2.回退版本 reset3.撤销提交 revert4.checkout / reset / revert 总结 发现写错了要回退怎么办?看看下面几种后悔指令吧! ❓ 还没提交的怎么撤销&#x…...
WPF基础DataGrid控件
WPF DataGrid 是一个用于显示和编辑表格数据的强大控件。它提供了丰富的功能,包括排序、筛选、分组、编辑、选择等,使你能够以类似电子表格的方式呈现和操作数据。 DataGrid 的布局主要由以下部分组成: 列定义 (Columns): DataGrid 列定义了…...
丐版设备互联方案:安卓linux互联局域网投屏,文件共享,共享剪切板
华为,苹果,甚至小米最近也推出了澎湃OS,发现实在是太方便了,当然这些对硬件,系统的要求还是比较高,我用的主力机是小米12pro和ubuntu,win双系统也…...
GZ033 大数据应用开发赛题第08套
2023年全国职业院校技能大赛 赛题第08套 赛项名称: 大数据应用开发 英文名称: Big Data Application Development 赛项组别: 高等职业教育组 赛项编号: GZ033 …...
【SpringMvc】SpringMvc +MyBatis整理
🎄欢迎来到边境矢梦的csdn博文🎄 🎄本文主要梳理 Java 框架 中 SpringMVC的知识点和值得注意的地方 🎄 🌈我是边境矢梦,一个正在为秋招和算法竞赛做准备的学生🌈 🎆喜欢的朋友可以关…...
java springboot测试类鉴定虚拟MVC运行值与预期值是否相同
好 上文java springboot在测试类中构建虚拟MVC环境并发送请求中 我们模拟的MVC环境 发送了一个请求 我们这次需要 对比 预期值和运行值是否一直 这里 我们要用一个 MockMvcResultMatchers 他提供了非常多的校验类型 例如 请求有没有成功 有没有包含请求头信息 等等 这里 我们做…...
UE5的TimeLine的理解
一直以来,我对动画的理解一直是这样的: 所谓动画,就是可导致可视化内容变化的参数和时间的对应关系。 我不能说这个观点现在过时了,只能说自己狭隘了。因为UE的TimeLine的设计理念真让人竖大拇指。 当我第一次看到TimeLine节点的…...
react原理及合成事件原理
文章目录 react的理解react创建组件的三种写法react的工作原理初始化的渲染流程。页面更新的流程。diffing 算法计算更新视图diff策略 react合成事件原理一、React合成事件的概念二、React合成事件的原理三、React合成事件的优势四、React合成事件的使用方法五、总结 react的理…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
