当前位置: 首页 > news >正文

c++进阶--智能指针

大家好,今天我们来学习一下c++中的智能指针部分。

智能指针的使⽤及其原理

1. 智能指针的使⽤场景分析
下⾯程序中我们可以看到,new了以后,我们也delete了,但是因为抛异常导,后⾯的delete没有得到执⾏,所以就内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛出,但是因为new本⾝也可能抛异常,连续的两个new和下⾯的Divide都可能会抛异常,让我们处理起来很⿇烦。智能指针放到这样的场景⾥⾯就让问题简单多了。
double Divide(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Divide by zero condition!";}else{return (double)a / (double)b;}
}void Func()
{// 这⾥可以看到如果发⽣除0错误抛出异常,另外下⾯的array和array2没有得到释放。// 所以这⾥捕获异常后并不处理异常,异常还是交给外⾯处理,这⾥捕获了再重新抛出去。// 但是如果array2new的时候抛异常呢,就还需要套⼀层捕获释放逻辑,这⾥更好解决⽅案// 是智能指针,否则代码太戳了int* array1 = new int[10];int* array2 = new int[10]; // 抛异常呢try{int len, time;cin >> len >> time;cout << Divide(len, time) << endl;}catch (...){cout << "delete []" << array1 << endl;cout << "delete []" << array2 << endl;delete[] array1;delete[] array2;throw; // 异常重新抛出,捕获到什么抛出什么}// ...cout << "delete []" << array1 << endl;delete[] array1;cout << "delete []" << array2 << endl;delete[] array2;
}int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}catch (const exception& e){cout << e.what() << endl;}catch (...){cout << "未知异常" << endl;}return 0;
}

 我们在上节异常最后时说到,当我们抛出异常后,那些在函数调用链中申请的资源有可能不会被释放,这时需要catch( . . . )来捕获异常,将资源释放掉之后再将异常重新抛出。

此时我们在func中申请了两个数组,但new也是可以抛出异常的,若在申请array1时抛出异常,则不用管,若是在申请array2时抛出异常,则需要将array1的资源进行释放,所以要对array2再进行一次捕获异常判断,在申请的资源很多时这种方法明显是不可取的,所以要使用更好的方法。

2. RAII和智能指针的设计思路
1. RAII是Resource Acquisition Is Initialization的缩写,他是⼀种管理资源的类的设计思想,本质是⼀种利⽤对象⽣命周期来管理获取到的动态资源,避免资源泄漏,这⾥的资源可以是内存、⽂件指针、⽹络连接、互斥锁等等。RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问,资源在对象的⽣命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题。
2. 智能指针类除了满⾜RAII的设计思路,还要⽅便资源的访问,所以智能指针类还会想迭代器类⼀样,重载 operator*/operator->/operator[] 等运算符,⽅便访问资源。
template<class T>
class SmartPtr
{
public:// RAIISmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout << "delete[] " << _ptr << endl;delete[] _ptr;}// 重载运算符,模拟指针的⾏为,⽅便访问资源T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t i){return _ptr[i];}private:T* _ptr;
};double Divide(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Divide by zero condition!";}else{return (double)a / (double)b;}
}void Func()
{// 这⾥使⽤RAII的智能指针类管理new出来的数组以后,程序简单多了SmartPtr<int> sp1 = new int[10];SmartPtr<int> sp2 = new int[10];for (size_t i = 0; i < 10; i++){sp1[i] = sp2[i] = i;}int len, time;cin >> len >> time;cout << Divide(len, time) << endl;
}int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}catch (const exception& e){cout << e.what() << endl;}catch (...){cout << "未知异常" << endl;}return 0;
}

 智能指针就是通过使用一个模拟指针的类来进行资源的申请,因为类中的资源在出作用域的时候会调用析构函数,申请的资源会释放。

由于智能指针模拟的是指针的行为,所以要支持 * 和 - > [ ] 这些运算符重载。

3. C++标准库智能指针的使⽤
1. C++标准库中的智能指针都在<memory>这个头⽂件下⾯,我们包含<memory>就可以是使⽤了,智能指针有好⼏种,除了weak_ptr他们都符合RAII和像指针⼀样访问的⾏为,原理上⽽⾔主要是解决智能指针拷⻉时的思路不同。
2. auto_ptr是C++98时设计出来的智能指针,他的特点是拷⻉时把被拷⻉对象的资源的管理权转移给拷⻉对象,这是⼀个⾮常糟糕的设计,因为他会到被拷⻉对象悬空,访问报错的问题,C++11设计出新的智能指针后,强烈建议不要使⽤auto_ptr。其他C++11出来之前很多公司也是明令禁⽌使⽤这个智能指针的。
3. unique_ptr是C++11设计出来的智能指针,他的名字翻译出来是唯⼀指针,他的特点的不⽀持拷⻉,只⽀持移动。如果不需要拷⻉的场景就⾮常建议使⽤他。
4. shared_ptr是C++11设计出来的智能指针,他的名字翻译出来是共享指针,他的特点是⽀持拷⻉,也⽀持移动。如果需要拷⻉的场景就需要使⽤他了。底层是⽤引⽤计数的⽅式实现的。
5. weak_ptr是C++11设计出来的智能指针,他的名字翻译出来是弱指针,他完全不同于上⾯的智能指针,他不⽀持RAII,也就意味着不能⽤它直接管理资源,weak_ptr的产⽣本质是要解决shared_ptr的⼀个循环引⽤导致内存泄漏的问题。具体细节下⾯我们再细讲。
6. 智能指针析构时默认是进⾏delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。智能指针⽀持在构造时给⼀个删除器,所谓删除器本质就是⼀个可调⽤对象,这个可调⽤对象中实现你想要的释放资源的⽅式,当构造智能指针时,给了定制的删除器,在智能指针析构时就会调⽤删除器去释放资源。因为new[]经常使⽤,所以为了简洁⼀点,unique_ptr和shared_ptr都特化了⼀份[]的版本,使⽤时 unique_ptr<Date[]> up1(new Date[5]);shared_ptr<Date[]> sp1(new Date[5]); 就可以管理new []的资源。
7. template <class T, class... Args> shared_ptr<T> make_shared (Args&&... args);
8. shared_ptr 除了⽀持⽤指向资源的指针构造,还⽀持 make_shared ⽤初始化资源对象的值直接构造。
9. shared_ptr 和 unique_ptr 都⽀持了operator bool的类型转换,如果智能指针对象是⼀个
空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。
10. shared_ptr 和 unique_ptr 都得构造函数都使⽤explicit 修饰,防⽌普通指针隐式类型转换
成智能指针对象。
struct Date
{int _year;int _month;int _day;Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){}~Date(){cout << "~Date()" << endl;}
};int main()
{auto_ptr<Date> ap1(new Date);// 拷⻉时,管理权限转移,被拷⻉对象ap1悬空auto_ptr<Date> ap2(ap1);// 空指针访问,ap1对象已经悬空//ap1->_year++;unique_ptr<Date> up1(new Date);// 不⽀持拷⻉//unique_ptr<Date> up2(up1);// ⽀持移动,但是移动后up1也悬空,所以使⽤移动要谨慎unique_ptr<Date> up3(move(up1));shared_ptr<Date> sp1(new Date);// ⽀持拷⻉shared_ptr<Date> sp2(sp1);shared_ptr<Date> sp3(sp2);cout << sp1.use_count() << endl;sp1->_year++;cout << sp1->_year << endl;cout << sp2->_year << endl;cout << sp3->_year << endl;// ⽀持移动,但是移动后sp1也悬空,所以使⽤移动要谨慎shared_ptr<Date> sp4(move(sp1));return 0;
}

auto_ptr 使用时可以进行拷贝,但拷贝是将权限直接转交给另一个指针,在拷贝之后它对资源就没有权限了。

unique_ptr 使用时不能进行拷贝,但能进行移动,同理,在移动后它也会失去对资源的权限。

shared_ptr 使用时可以进行拷贝也可以进行移动,拷贝之后它不会失去对资源的权限,而在移动后会失去对资源的权限。

下面我们再来看看智能指针删除时的用法:

template<class T>
void DeleteArrayFunc(T* ptr)
{delete[] ptr;
}template<class T>
class DeleteArray
{
public:void operator()(T* ptr){delete[] ptr;}
};class Fclose
{
public:void operator()(FILE* ptr){cout << "fclose:" << ptr << endl;fclose(ptr);}
};int main()
{// 这样实现程序会崩溃// unique_ptr<Date> up1(new Date[10]);// shared_ptr<Date> sp1(new Date[10]);// 解决⽅案1// 因为new[]经常使⽤,所以unique_ptr和shared_ptr// 实现了⼀个特化版本,这个特化版本析构时⽤的delete[]unique_ptr<Date[]> up1(new Date[5]);shared_ptr<Date[]> sp1(new Date[5]);// 解决⽅案2// 仿函数对象做删除器//unique_ptr<Date, DeleteArray<Date>> up2(new Date[5], DeleteArray<Date>());// unique_ptr和shared_ptr⽀持删除器的⽅式有所不同// unique_ptr是在类模板参数⽀持的,shared_ptr是构造函数参数⽀持的// 这⾥没有使⽤相同的⽅式还是挺坑的// 使⽤仿函数unique_ptr可以不在构造函数传递,因为仿函数类型构造的对象直接就可以调⽤// 但是下⾯的函数指针和lambda的类型不可以unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);shared_ptr<Date> sp2(new Date[5], DeleteArray<Date>());// 函数指针做删除器unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);// lambda表达式做删除器auto delArrOBJ = [](Date* ptr) {delete[] ptr; };unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);shared_ptr<Date> sp4(new Date[5], delArrOBJ);// 实现其他资源管理的删除器shared_ptr<FILE> sp5(fopen("Test.cpp", "r"), Fclose());shared_ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) {cout << "fclose:" << ptr << endl;fclose(ptr);});return 0;
}

new和delete在申请和释放资源时对于数组有不同的写法,对于代码中给出的shared_ptr<Date> sp1(new Date[10]);这样写是不行的,因为申请的是Date类型的数组,在释放时应该用delete[ ]来进行释放,但智能指针默认是delete删除,不能释放数组资源。

在这里给出了两种方法,一是智能指针模板提供了一个特化版本,专门用于数组的申请和释放;二是使用仿函数对象做删除器,我们可以提供一个删除方法,这样也能释放数组的资源。但对于unique_ptr和shared_ptr两种智能指针的写法不同。

对于unique_ptr 是把删除器写道模板参数列表里,二对于shared_ptr 是把删除器写道构造函数里,在构造的时候传入。

4. 智能指针的原理
1. 下⾯我们模拟实现了auto_ptr和unique_ptr的核⼼功能,这两个智能指针的实现⽐较简单,⼤家了解⼀下原理即可。auto_ptr的思路是拷⻉时转移资源管理权给被拷⻉对象,这种思路是不被认可的,也不建议使⽤。unique_ptr的思路是不⽀持拷⻉。
2. ⼤家重点要看看shared_ptr是如何设计的,尤其是引⽤计数的设计,主要这⾥⼀份资源就需要⼀个引⽤计数,所以引⽤计数才⽤静态成员的⽅式是⽆法实现的,要使⽤堆上动态开辟的⽅式,构造智能指针对象时来⼀份资源,就要new⼀个引⽤计数出来。多个shared_ptr指向资源时就++引⽤计数,shared_ptr对象析构时就--引⽤计数,引⽤计数减到0时代表当前析构的shared_ptr是最后⼀个管理资源的对象,则析构资源。

对于引用计数,这里不能使用int类型的变量作为引用计数,当进行拷贝时两个智能指针的引用计数都会+1,在析构的时候引用计数都-1也不能使引用计数为0,不能释放资源。

同理,引用计数也不能为静态成员变量,类的静态成员变量是属于类的所有对象的,当我们申请了多个资源时,引用计数就不对等了,所以也不行。

在这里,我们把引用计数也动态申请,在智能指针中存放它的地址,当进行拷贝时,引用计数+1,当析构时,引用计数-1,当引用计数为0时,再把资源释放。

template<class T>
class auto_ptr
{
public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& sp):_ptr(sp._ptr){// 管理权转移sp._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& ap){// 检测是否为⾃⼰给⾃⼰赋值if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;// 转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = NULL;}return *this;}~auto_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针⼀样使⽤T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};template<class T>
class unique_ptr
{
public:explicit unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针⼀样使⽤T& operator*(){return *_ptr;}T* operator->(){return _ptr;}unique_ptr(const unique_ptr<T>&sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>&sp) = delete;unique_ptr(unique_ptr<T> && sp):_ptr(sp._ptr){sp._ptr = nullptr;}unique_ptr<T>& operator=(unique_ptr<T> && sp){delete _ptr;_ptr = sp._ptr;sp._ptr = nullptr;}private:T* _ptr;
};template<class T>
class shared_ptr
{
public:explicit shared_ptr(T* ptr = nullptr): _ptr(ptr), _pcount(new int(1)){}template<class D>shared_ptr(T* ptr, D del): _ptr(ptr), _pcount(new int(1)), _del(del){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount), _del(sp._del){++(*_pcount);}void release(){if (--(*_pcount) == 0){// 最后⼀个管理的对象,释放资源_del(_ptr);delete _pcount;_ptr = nullptr;_pcount = nullptr;}}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);_del = sp._del;}return *this;}~shared_ptr(){release();}T* get() const{return _ptr;}int use_count() const{return *_pcount;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;//atomic<int>* _pcount;function<void(T*)> _del = [](T* ptr) {delete ptr; };
};// 需要注意的是我们这⾥实现的shared_ptr和weak_ptr都是以最简洁的⽅式实现的,
// 只能满⾜基本的功能,这⾥的weak_ptr lock等功能是⽆法实现的,想要实现就要
// 把shared_ptr和weak_ptr⼀起改了,把引⽤计数拿出来放到⼀个单独类型,shared_ptr
// 和weak_ptr都要存储指向这个类的对象才能实现,有兴趣可以去翻翻源代码
template<class T>
class weak_ptr
{
public:weak_ptr(){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}private:T* _ptr = nullptr;
};int main()
{auto_ptr<Date> ap1(new Date);// 拷⻉时,管理权限转移,被拷⻉对象ap1悬空auto_ptr<Date> ap2(ap1);// 空指针访问,ap1对象已经悬空//ap1->_year++;unique_ptr<Date> up1(new Date);// 不⽀持拷⻉//unique_ptr<Date> up2(up1);// ⽀持移动,但是移动后up1也悬空,所以使⽤移动要谨慎unique_ptr<Date> up3(move(up1));shared_ptr<Date> sp1(new Date);// ⽀持拷⻉shared_ptr<Date> sp2(sp1);shared_ptr<Date> sp3(sp2);cout << sp1.use_count() << endl;sp1->_year++;cout << sp1->_year << endl;cout << sp2->_year << endl;cout << sp3->_year << endl;return 0;
}
5. shared_ptr和weak_ptr
5.1 shared_ptr循环引⽤问题
shared_ptr⼤多数情况下管理资源⾮常合适,⽀持RAII,也⽀持拷⻉。但是在循环引⽤的场景下会导致资源没得到释放内存泄漏,所以我们要认识循环引⽤的场景和资源没释放的原因,并且学会使⽤weak_ptr解决这种问题。
如下图所述场景,n1和n2析构后,管理两个节点的引⽤计数减到1
1. 右边的节点什么时候释放呢,左边节点中的_next管着呢,_next析构后,右边的节点就释放了。
2. _next什么时候析构呢,_next是左边节点的的成员,左边节点释放,_next就析构了。
3. 左边节点什么时候释放呢,左边节点由右边节点中的_prev管着呢,_prev析构后,左边的节点就释放了。
4. _prev什么时候析构呢,_prev是右边节点的成员,右边节点释放,_prev就析构了。
⾄此逻辑上成功形成回旋镖似的循环引⽤,谁都不会释放就形成了循环引⽤,导致内存泄漏
把ListNode结构体中的_next和_prev改成weak_ptr,weak_ptr绑定到shared_ptr时不会增加它的引⽤计数,_next和_prev不参与资源释放管理逻辑,就成功打破了循环引⽤,解决了这⾥的问题

 

struct ListNode
{int _data;std::shared_ptr<ListNode> _next;std::shared_ptr<ListNode> _prev;// 这⾥改成weak_ptr,当n1->_next = n2;绑定shared_ptr时// 不增加n2的引⽤计数,不参与资源释放的管理,就不会形成循环引⽤了/*std::weak_ptr<ListNode> _next;std::weak_ptr<ListNode> _prev;*/~ListNode(){cout << "~ListNode()" << endl;}
};int main()
{// 循环引⽤ -- 内存泄露std::shared_ptr<ListNode> n1(new ListNode);std::shared_ptr<ListNode> n2(new ListNode);cout << n1.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;// weak_ptr不⽀持管理资源,不⽀持RAII// weak_ptr是专⻔绑定shared_ptr,不增加他的引⽤计数,作为⼀些场景的辅助管理//std::weak_ptr<ListNode> wp(new ListNode);return 0;
}
5.2 weak_ptr
1. weak_ptr不⽀持RAII,也不⽀持访问资源,所以我们看⽂档发现weak_ptr构造时不⽀持绑定到资源,只⽀持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引⽤计数,那么就可以解决上述的循环引⽤问题。
2. weak_ptr也没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的shared_ptr已经释放了资源,那么他去访问资源就是很危险的。weak_ptr⽀持expired检查指向的资源是否过期,use_count也可获取shared_ptr的引⽤计数,weak_ptr想访问资源时,可以调⽤lock返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是⼀个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。
int main()
{std::shared_ptr<string> sp1(new string("111111"));std::shared_ptr<string> sp2(sp1);std::weak_ptr<string> wp = sp1;cout << wp.expired() << endl;cout << wp.use_count() << endl;// sp1和sp2都指向了其他资源,则weak_ptr就过期了sp1 = make_shared<string>("222222");cout << wp.expired() << endl;cout << wp.use_count() << endl;sp2 = make_shared<string>("333333");cout << wp.expired() << endl;cout << wp.use_count() << endl;wp = sp1;//std::shared_ptr<string> sp3 = wp.lock();auto sp3 = wp.lock();cout << wp.expired() << endl;cout << wp.use_count() << endl;*sp3 += "###";cout << *sp1 << endl;return 0;
}
6. shared_ptr的线程安全问题
1. shared_ptr的引⽤计数对象在堆上,如果多个shared_ptr对象在多个线程中,进⾏shared_ptr的拷⻉析构时会访问修改引⽤计数,就会存在线程安全问题,所以shared_ptr引⽤计数是需要加锁或者原⼦操作保证线程安全的。
2. shared_ptr指向的对象也是有线程安全的问题的,但是这个对象的线程安全问题不归shared_ptr管,它也管不了,应该有外层使⽤shared_ptr的⼈进⾏线程安全的控制。
3. 下⾯的程序会崩溃或者A资源没释放,bit::shared_ptr引⽤计数从int*改成atomic<int>*就可以保证引⽤计数的线程安全问题,或者使⽤互斥锁加锁也可以。
struct AA
{int _a1 = 0;int _a2 = 0;~AA(){cout << "~AA()" << endl;}
};
int main()
{bit::shared_ptr<AA> p(new AA);const size_t n = 100000;mutex mtx;auto func = [&](){for (size_t i = 0; i < n; ++i){// 这⾥智能指针拷⻉会++计数bit::shared_ptr<AA> copy(p);{unique_lock<mutex> lk(mtx);copy->_a1++;copy->_a2++;}}};thread t1(func);thread t2(func);t1.join();t2.join();cout << p->_a1 << endl;cout << p->_a2 << endl;cout << p.use_count() << endl;return 0;
}

 

相关文章:

c++进阶--智能指针

大家好&#xff0c;今天我们来学习一下c中的智能指针部分。 智能指针的使⽤及其原理 1. 智能指针的使⽤场景分析 下⾯程序中我们可以看到&#xff0c;new了以后&#xff0c;我们也delete了&#xff0c;但是因为抛异常导&#xff0c;后⾯的delete没有得到执⾏&#xff0c;所以…...

五种常用的web加密算法

文章目录 五种常用Web加密算法实战及原理详解1. AES (高级加密标准)原理详解应用场景实战代码&#xff08;Node.js&#xff09; 2. RSA (非对称加密)原理详解应用场景实战代码&#xff08;Node.js&#xff09; 3. SHA-256 (安全哈希算法)原理详解应用场景实战代码&#xff08;浏…...

LeetCode 题目 「二叉树的右视图」 中,如何从「中间存储」到「一步到位」实现代码的优化?

背景简介 在 LeetCode 的经典题目 「二叉树的右视图」 中&#xff0c;我们需要返回从右侧看一棵二叉树时所能看到的节点集合。每一层我们只能看到最右边的那个节点。 最初&#xff0c;我采用了一个常规思路&#xff1a;层序遍历 每层单独保存节点值 最后提取每层最后一个节…...

MySQL——存储过程、索引

一、存储过程 1、存储过程使用的场景 例如&#xff1a;有一个购物网站&#xff0c;要验证查询商品的性能&#xff0c;测试之前肯定要准备大量的测试数据&#xff0c;如果是通过 执行 insert 语句一条一条进行插入&#xff0c;效率很低。这种情况下&#xff0c;写一个存储过程…...

【项目管理】第9章 项目范围管理

相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 (一)知识总览 项目管理知识域 知识点: (项目管理概论、立项管理、十大知识域、配置与变更管理、绩效域) 对应:第6章-第19章 (二)知识笔记 第9章 项目范围管理 1.管理基础 1.1 产品范围…...

无人机隐身技术难点要点!

全频段雷达隐身 频段覆盖挑战&#xff1a;传统隐身材料&#xff08;如铁氧体、掺杂半导体&#xff09;多针对特定频段&#xff08;如X波段&#xff09;&#xff0c;难以应对米波至毫米波的宽频段探测。 低频段突破&#xff1a;低频雷达&#xff08;如米波雷达&#xff09;波长…...

gerrit配置及使用git-lfs

gerrit服务器端配置 下载git-lfs插件 登录Dashboard [Jenkins] (gerritforge.com)&#xff0c;下载对应版本的插件 配置gerrit 将下载的lfs.jar插件放到${GERRIT_SITE}/plugins/下面为所有仓库启用git-lfs 此步骤需要修改 All-projects 仓库配置&#xff0c;步骤如下 1、克隆仓…...

基于DNS的负载均衡和反向代理负载均衡

基于 DNS 的负载均衡和反向代理负载均衡有一些相似之处&#xff0c;但实际上它们存在诸多区别&#xff0c;主要体现在以下几个方面&#xff1a; 工作原理 DNS 负载均衡&#xff1a;通过在 DNS 服务器中为同一主机名配置多个 IP 地址&#xff0c;DNS 服务器根据一定的算法&…...

Windows10 ssh无输出 sshd服务启动失败 1067报错 公钥无法认证链接 解决办法

背景描述 最近突然发现windows 10的ssh服务好像挂了&#xff0c;在系统设置-可选功能那里反复重新安装还是报错。命令行输入ssh按回车无输出&#xff08;正常情况下应该输出一堆参数说明&#xff09;&#xff0c;但是Get-Command ssh 又可以找到system32下的ssh程序。任务管理…...

【图书管理系统】深入解析基于 MyBatis 数据持久化操作:全栈开发图书管理系统:查询图书属性接口(注解实现)、修改图书属性接口(XML 实现)

查询图书属性接口 约定前后端交互接口 约定前后端交互接口&#xff0c;进入修改页面&#xff0c;需要显示当前图书的信息&#xff1b; 请求 /book/queryBookById?bookId25 参数 无 响应 { "id": 25, "bookName": "图书21", "…...

消息队列(IPC技术)

目录 一、Linux 中主要的进程间通信方式如下&#xff1a; 二、消息队列函数 &#xff08;1&#xff09;msgget函数 功能概述 函数原型 参数解释 返回值 示例 结果 问题 (2) msgsnd函数 功能概述 函数原型 参数说明 返回值 示例 结果 &#xff08;3&#xff0…...

分支语句和循环语句

什么是语句&#xff1f; C语言中由一个分号;隔开的就是一条语句。 比如&#xff1a; printf("haha");12;分支语句 if语句 if语句的语法结构: if(表达式)语句;if(表达式)语句1; else语句2;//多分支 if(表达式1)语句1; else if(表达式2)语句2; else语句3;在C语言…...

MySQL基础 [八] - 事务

目录 前言 什么是事务 事务的版本支持 事务的提交方式 事务的相关演示 并行事务引发的问题 脏读 dirty read 不可重复读 non-repeatable read 幻读 phantom read 事务的隔离级别 查看与设置隔离级别 读未提交&#xff08;Read Uncommitted&#xff09; 读提交&…...

深入理解Java反射

反射(Reflection)是Java语言的一个强大特性&#xff0c;它允许程序在运行时动态地获取类的信息并操作类或对象的属性、方法和构造器。就是在获取运行时的java字节码文件&#xff0c;通过各种方法去创建对象&#xff0c;反射是Java被视为动态语言的关键特性之一。 反射其实就是…...

【UE】渐变框材质

效果 步骤 新建一个材质&#xff0c;这里命名为“M_GlowingBorder”&#xff0c;打开“M_GlowingBorder”后&#xff0c;设置材质域为“用户界面”&#xff0c;混合模式为“半透明” 添加如下节点&#xff1a; 代码&#xff1a; Begin Object Class/Script/UnrealEd.Materia…...

2025年第十八届“认证杯”数学中国数学建模网络挑战赛【ABCD题】思路分析

首先&#xff0c;需要理解用户的需求。问题1需要数学模型来确定小行星的相对距离&#xff0c;而问题2需要预测短期轨道并计算特定时间的观测角度。这两个问题都需要结合天文学和数学建模的知识&#xff0c;涉及到轨道力学和几何定位的方法。 接下来&#xff0c;查阅提供的搜索…...

JavaScript 性能优化:突破瓶颈的实战指南

一、引言​ 在现代 Web 应用和 Node.js 服务端开发中&#xff0c;JavaScript 已成为核心编程语言。随着应用复杂度提升&#xff0c;性能问题愈发凸显。高延迟、卡顿甚至崩溃等现象&#xff0c;不仅影响用户体验&#xff0c;还可能导致业务流失。深入理解 JavaScript 性能瓶颈并…...

HarmonyOS:组件布局保存至相册

一&#xff0c;需求背景 有这样一个需求&#xff0c;将页面上的某个自定义组件以图片的形式保存至相册。 二&#xff0c;需求拆解 根据需求分析&#xff0c;可将需求拆解成两步&#xff1a; 1&#xff0c;将组件转换成图片资源&#xff1b; 2&#xff0c;将图片保存到相册…...

【langchain库名解析】

目录 一、from langchain_openai import ChatOpenAI 1. 核心功能 2. 典型使用场景 场景 1&#xff1a;直接生成对话回复 场景 3&#xff1a;流式输出&#xff08;逐词显示结果&#xff09; 3. 与其他 LangChain 组件的协同 结合提示模板&#xff08;PromptTemplate&#…...

629SJBH图书管理系统设计与实现

一、 绪论 &#xff08;一&#xff09;课题的提出、现状及研究意义 图书馆是文献情报中心&#xff0c;是为教学和科研服务的学术性机构。它履行搜集、加工、存贮和传播知识信息的职能&#xff0c;与各系资料室互为补充&#xff0c;共同承担为教学和科研提供文献情报资料保障的…...

2025 年“认证杯”数学中国数学建模网络挑战赛 A题 小行星轨迹预测

近地小行星&#xff08; Near Earth Asteroids, NEAs &#xff09;是轨道相对接近地球的小行 星&#xff0c;它的正式定义为椭圆轨道的近日距不大于 1.3 天文单位&#xff08; AU &#xff09;的小行星。 其中轨道与地球轨道最近距离小于 0.05A 且直径大于 140 米的小行星被…...

PhotoShop学习09

1.弯曲钢笔工具 PhotoShop提供了弯曲钢笔工具可以直观地创建路径&#xff0c;只需要对分段推拉就能够进行修改。弯曲港币工具位于工具面板中的钢笔工具里&#xff0c;它的快捷键为P。 在使用前&#xff0c;可以把填充和描边选为空颜色&#xff0c;并打开路径选项&#xff0c;勾…...

远程管理命令:关机和重启

关机/重启 序号命令对应英文作用01shutdown 选项 时间shutdown关机 / 重新启动 一、shutdown shutdown 命令可以安全关闭 或者 重新启动系统。 选项含义-r重新启动 提示&#xff1a; 不指定选项和参数&#xff0c;默认表示 1 分钟之后 关闭电脑远程维护服务器时&#xff0…...

用Perl和HTTP::Tiny库的爬虫

HTTP::Tiny是Perl的一个轻量级HTTP客户端&#xff0c;适合简单的请求&#xff0c;但不像LWP那样功能全面&#xff0c;不过对于基本需求应该足够了。 首先&#xff0c;我需要熟悉HTTP::Tiny的基本用法。比如如何发起GET请求&#xff0c;设置user-agent&#xff0c;处理响应。用…...

MPP 架构解析:原理、核心优势与对比指南

一、引言&#xff1a;大数据时代的数据处理挑战 全球数据量正以指数级增长。据 Statista 统计&#xff0c;2010 年全球数据量仅 2ZB&#xff0c;2025 年预计达 175ZB。企业面临的核心挑战已从“如何存储数据”转向“如何快速分析数据”。传统架构在处理海量数据时暴露明显瓶颈…...

2025.04.10-拼多多春招笔试第三题

📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 03. 数字重排最大化问题 问题描述 LYA是一位专业的数字设计师。她手中有两个数字序列 s 1 s_1...

前端-vue2核心

官网网址Vue2 安装 — Vue.js 搭建环境 第一种方式&#xff08;刚开是接触Vue&#xff09; 我们看官网&#xff0c;可以直接在script引入vue版本。这里有两个版本&#xff0c;开发版和生产版本。我们两个都下载。 然后创建一个项目&#xff0c;将下载的生产版本和开发版本粘…...

基于springboot的“协同过滤算法的高考择校推荐系统”的设计与实现(源码+数据库+文档+PPT)

基于springboot的“协同过滤算法的高考择校推荐系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;springboot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 局部E-R图 系统…...

制作前的关键筹备:考试考核系统之核心要点

明确系统使用目的​ 制作考试考核系统前&#xff0c;企业需明确系统使用目的&#xff0c;这是开发基石&#xff0c;不同目的决定系统功能特性。用于员工培训考核时&#xff0c;系统要与培训内容结合&#xff0c;能生成相应考题&#xff0c;检验员工知识掌握程度&#xff0c;具备…...

【动手学深度学习】现代卷积神经网络:ALexNet

【动手学深度学习】现代卷积神经网络&#xff1a;ALexNet 1&#xff0c;ALexNet简介2&#xff0c;AlexNet和LeNet的对比3&#xff0c; AlexNet模型详细设计4&#xff0c;AlexNet采用ReLU激活函数4.1&#xff0c;ReLU激活函数4.2&#xff0c;sigmoid激活函数4.3&#xff0c;为什…...

Linux自启动脚本 systemctl

1.编写好脚本 #!/bin/bash /home/china/Linux/code/a.out2. 创建 Systemd 服务文件 sudo gedit /etc/systemd/system/my_script.service3.编写服务配置 将以下内容写入文件&#xff08;根据需求修改字段&#xff09;&#xff1a; [Unit] DescriptionMy Custom Shell Script…...

2024年KBS SCI1区TOP:信息增益比子特征分组赋能粒子群算法ISPSO,深度解析+性能实测

目录 1.摘要2.信息度量3.改进策略4.结果展示5.参考文献6.代码获取 1.摘要 特征选择是机器学习中的关键预处理步骤&#xff0c;广泛应用于实际问题。尽管粒子群算法&#xff08;PSO&#xff09;因其强大的全局搜索能力被广泛用于特征选择&#xff0c;但要开发一种高效的PSO方法…...

餐饮厨房开源监控安全系统的智能革命

面对日益严格的合规要求和消费者对卫生的信任危机&#xff0c;传统人工监督已力不从心&#xff1a;卫生死角难发现、违规操作难追溯、安全隐患防不胜防。如何让后厨更透明、更安全、更可信&#xff1f;餐饮厨房视频安全系统横空出世&#xff01;这套系统融合实时监控与AI技术&a…...

Ansys Electronics 变压器 ACT

你好&#xff0c; 在本博客中&#xff0c;我将讨论如何使用 Ansys 电子变压器 ACT 自动快速地设计电力电子电感器或变压器。我将逐步介绍设计和创建电力电子变压器示例的步骤&#xff0c;该变压器为同心组件&#xff0c;双绕组&#xff0c;采用正弦电压激励&#xff0c;并应用…...

Redis与Lua原子操作深度解析及案例分析

一、Redis原子操作概述 Redis作为高性能的键值存储系统&#xff0c;其原子性操作是保证数据一致性的核心机制。在Redis中&#xff0c;原子性指的是一个操作要么完全执行&#xff0c;要么完全不执行&#xff0c;不会出现部分执行的情况。 Redis原子性的实现原理 单线程模型&a…...

Shell 脚本开发从入门到实战

第1章&#xff1a;什么是 Shell 与 Shell 脚本&#xff1f; 一、Shell 是什么&#xff1f; Shell 是一个命令解释器&#xff0c;是你在 Linux 里敲命令的地方。你平时用的命令如 cd、ls、echo&#xff0c;其实都由 Shell 来解析执行。最常见的 Shell 是 Bash&#xff0c;绝大…...

宇视设备视频平台EasyCVR打造智慧酒店安防体系,筑牢安全防线

一、需求背景 酒店作为人员流动频繁的场所&#xff0c;对安全保障与隐私保护有着极高的要求。为切实维护酒店内部公共区域的安全秩序&#xff0c;24小时不间断视频监控成为必要举措。通常情况下&#xff0c;酒店需在本地部署视频监控系统以供查看&#xff0c;部分连锁酒店还希…...

深度解读分销小程序商城源码系统:从搭建到运营的关键指南​​​​

在移动互联网浪潮的席卷下&#xff0c;电商领域持续变革与创新。分销小程序商城凭借其独特优势&#xff0c;如依托社交平台流量、便捷的购物体验、高效的分销推广模式等&#xff0c;成为众多企业和创业者开展线上业务的热门选择。深入了解分销小程序商城源码系统&#xff0c;从…...

BeeWorks:打造安全可控的企业内网即时通讯平台

在数字化办公时代&#xff0c;企业对即时通讯工具的需求日益增长&#xff0c;尤其是对数据安全和隐私保护有严格要求的行业&#xff0c;如金融、政府、医疗等。BeeWorks 作为一款专注于内网部署的即时通讯软件&#xff0c;凭借其卓越的安全性、稳定性、丰富的功能以及全面的信创…...

微信小程序开发:废品回收小程序-功能清单

用户端&#xff1a;便捷体验&#xff0c;触手可及 废品百科与估价指南&#xff1a;平台以直观的方式展示各类废品的分类标准与实时市场价格&#xff0c;让用户轻松掌握废品价值&#xff0c;决策更从容。 一键预约&#xff0c;轻松回收&#xff1a;用户只需轻触屏幕&#xff0c…...

【Grok 大模型深度解析】第一期:技术溯源与核心突破

一、Grok的技术基因:从Transformer到混合架构的演进 1.1 Transformer架构的局限性 2017年Google提出的Transformer架构彻底改变了自然语言处理领域,其自注意力机制(Self-Attention)在长序列建模上表现优异。然而,随着模型规模的增大,传统Transformer暴露出以下问题: 计…...

性能比拼: Redis vs Memcached

本内容是对知名性能评测博主 Anton Putra Redis vs Memcached Performance Benchmark 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准 在本视频中&#xff0c;我们将对比 Redis 和 Memcached。我会介绍一些功能上的不同&#xff0c;但主要关注 性能。 首先&#xf…...

Mujoco xml actuator

actuator general&#xff08;通用执行器&#xff09;motor&#xff08;电机执行器&#xff09;position&#xff08;位置伺服&#xff09;velocity&#xff08;速度伺服&#xff09;intvelocity&#xff08;积分速度伺服&#xff09;damper&#xff08;主动阻尼器&#xff09;…...

Mybatis Plus分页查询返回total为0问题

概述 最近开发公司新项目&#xff0c;使用 Mybatis Plus 分页&#xff0c;发现总数和总页数为0&#xff0c;在此记录问题和解决方案。 添加 MybatisPlusConfig /*** author: lanys* version: 1.0* 创建时间&#xff1a;2025年4月9日 14:24:40* Description: MybatisPlus分页…...

多卡分布式训练:torchrun --nproc_per_node=5

多卡分布式训练:torchrun --nproc_per_node=5 1. torchrun 实现规则 torchrun 是 PyTorch 提供的用于启动分布式训练作业的实用工具,它基于 torch.distributed 包,核心目标是简化多进程分布式训练的启动和管理。以下是其主要实现规则: 进程启动 多进程创建:torchrun 会…...

网络层-IP地址计算

例1&#xff1a;IP地址二进制与十进制互转 题目&#xff1a; 将二进制IP 11000000.10101000.00000001.00001010 转换为点分十进制。将IP地址 172.16.254.1 转换为二进制格式。 答案与解析&#xff1a; 转换步骤&#xff1a; 每个8位二进制转为十进制&#xff1a; 11000000 →…...

BeagleBone Black笔记

目录 参考资料开机led控制GPIO输入输出插网线联网安装gcc编译工具镜像备份验证备份完整性将内存卡插入目标BBBboot启动开关 参考资料 链接: BeagleBone Black使用&#xff08;一&#xff09;&#xff1a;狗板简介 链接: 使用Beaglebone Black的IO口 开机 直接用usb连接到电脑…...

【25软考网工笔记】第一章 计算机网络概述

目录 一、计算机网络发展与分类 1. 计算机网络形成和发展 1&#xff09;ICT 2&#xff09;计算机网络的发展 3&#xff09;我国互联网发展 2. 计算机网络分类 1&#xff09;通信子网和资源子网 2&#xff09;PAN、LAN、MAN、WAN 3&#xff09;其他分类方式 3. 计算机…...

Soybean Admin 配置vite兼容低版本浏览器、安卓电视浏览器(飞视浏览器)

环境 window10 pnpm 8.15.4 node 8.15.4 vite 5.1.4 soybean admin: 1.0.0 native-ui: 2.38.0 小米电视 MIUI TV版本&#xff1a;MiTV OS 2.7.1886(稳定版) 飞视浏览器&#xff1a;https://www.fenxm.com/1220.html在小米电视安装飞视浏览器可以去小红书查安装教程&#xff1a…...

MicroPython 开发ESP32应用教程 之 I2S、INMP441音频录制、MAX98357A音频播放、SD卡读写

本课程我们讲解Micropython for ESP32 的i2s及其应用&#xff0c;比如INMP441音频录制、MAX98357A音频播放等&#xff0c;还有SD卡的读写。 一、硬件准备 1、支持micropython的ESP32S3开发板 2、INMP441数字全向麦克风模块 3、MAX98357A音频播放模块 4、SD卡模块 5、面包板及…...