C++之移动语义与智能指针

目录

移动语义

1、几个基本概念的理解

2、复制控制语义的函数

3、移动控制语义的函数

3.1、移动构造函数:

3.2、移动赋值函数

4.区别

5、std::move函数

6.代码演示:

资源管理与智能指针

一、C语言中的问题

二、C++的解决办法(RAII技术):

三、四种智能指针

1、auto_ptr.cc

2、unique_ptr

3、shared_ptr

4、weak_ptr

四、为智能指针定制删除器


移动语义

1、几个基本概念的理解

左值、右值、 const 左值引用、右值引用?
可以取地址的是左值,不能取地址的就是右值。区分左值与右值的是能不能取地址。右值可能存在寄存器,也可能存在于栈上(短暂存在栈上)
右值包括:临时对象、匿名对象、字面值常量
const 左值引用可以绑定到左值与右值上面,称为万能引用正因如此,也就无法区分传进来的参数是左值还是右值。

左值引用:可以绑定到左值,但是不能绑定到右值。
const左值引用:既可以绑定到左值也可以绑定到右值。(正因如此,才将拷贝构造函数写成const左值引用)
右值引用:可以绑定到右值,但是不能绑定到左值。(正因如此,还能有移动语义的函数)
所以可以区分出传进来的参数到底是左值还是右值,进而可以区分.

右值引用到底是左值还是右值?

这个与右值引用本身有没有名字有关,如果是 int &&rref = 10 ,右值引用本身就是左值,因为有名字。如果右值引用本身没有名字,那右值引用就是右值,如右值引用作为函数返回值。

什么是右值引用:

2、复制控制语义的函数

3、移动控制语义的函数

将内存的所有权从一个对象转移到另外一个对象,高效的移动用来替换效率低下的复制,对象的移动语义需要实现移动构造函数(move constructor)和移动赋值运算符(move assignment operator)。

3.1、移动构造函数:

String(String &&rhs)
: _pstr(rhs._pstr)
{cout << "String(String &&)" << endl;rhs._pstr = nullptr;
}

3.2、移动赋值函数

String &operator=(String &&rhs)
{cout << "String &operator=(String &&)" << endl;if(this != &rhs)//1、自移动{delete [] _pstr;//2、释放左操作数_pstr = nullptr;_pstr = rhs._pstr;//3、浅拷贝rhs._pstr = nullptr;}return *this;//4、返回*this
}

3.3举例

template <typename T>
class DynamicArray
{
public:explicit DynamicArray(int size) :m_size{ size }, m_array{ new T[size] }{cout << "Constructor: dynamic array is created!\n";}virtual ~DynamicArray(){delete[] m_array;cout << "Destructor: dynamic array is destroyed!\n";}// 拷贝构造函数DynamicArray(const DynamicArray& rhs) :m_size{ rhs.m_size }{m_array = new T[m_size];for (int i = 0; i < m_size; ++i){m_array[i] = rhs.m_array[i];}cout << "Copy constructor: dynamic array is created!\n";}// 拷贝赋值运算符DynamicArray& operator=(const DynamicArray& rhs){cout << "Copy assignment operator is called\n";if (this == &rhs){return *this;}delete[] m_array;m_size = rhs.m_size;m_array = new T[m_size];for (int i = 0; i < m_size; ++i){m_array[i] = rhs.m_array[i];}return *this;}// 移动构造函数DynamicArray(DynamicArray&& rhs) :m_size{ rhs.m_size }, m_array{ rhs.m_array }{rhs.m_size = 0;rhs.m_array = nullptr;cout << "Move constructor: dynamic array is moved!\n";}// 移动赋值操作符DynamicArray& operator=(DynamicArray&& rhs){cout << "Move assignment operator is called\n";if (this == &rhs){return *this;}delete[] m_array;m_size = rhs.m_size;m_array = rhs.m_array;rhs.m_size = 0;rhs.m_array = nullptr;return *this;}
private:T* m_array;int m_size;
};int main()
{DynamicArray<int> arr1(10);DynamicArray<int> arr2(move(arr1));system("pause");return 0;
}

4.区别

5、std::move函数

将左值转换为右值,在内部其实上是做了一个强制转换, static_cast<T &&>(lvaule) 。将左值转换为右值后,左值就不能直接使用了,如果还想继续使用,必须重新赋值。
std::move()作用于内置类型没有任何作用,内置类型本身是左值还是右值,经过std::move()后不会改变。

6.代码演示:

#include <string.h>
#include <iostream>using std::cout;
using std::endl;class String
{
public:String(): _pstr(nullptr)/* : _pstr(new char[1]()) */{cout << "String()" << endl;}String(const char *pstr): _pstr(new char[strlen(pstr) + 1]()){cout << "String(const char *)" << endl;strcpy(_pstr, pstr);}//将拷贝构造函数和赋值运算符函数称为具有复制控制语义的函数String(const String &rhs): _pstr(new char[strlen(rhs._pstr) + 1]()){cout << "String(const String &)" << endl;strcpy(_pstr, rhs._pstr);}String &operator=(const String &rhs){cout << "String &operator=(const String &)" << endl;if (this != &rhs)//1、自复制{delete[] _pstr;//2、释放左操作数_pstr = nullptr;//3、深拷贝_pstr = new char[strlen(rhs._pstr) + 1]();strcpy(_pstr, rhs._pstr);}//4、返回*thisreturn *this;}//将移动构造函数和移动赋值运算符函数称为具有移动语义的函数////具有移动语义的函数优先于具有复制控制语义的函数执行////复制控制语义的函数编译器会自动生成,但是具有移动语义的//函数编译器是不会自动生成的,必须要手写////移动构造函数优先于拷贝构造函数执行的(优先级)//移动构造函数//String s3 = String("world");String(String &&rhs):_pstr(rhs._pstr){cout << "String(string &&)" << endl;rhs._pstr = nullptr;}//移动赋值运算符函数优先于赋值运算符函数执行的(优先级)//移动赋值运算符函数(移动赋值函数)//s4 = String("wuhan")//s4 = std::move(s4)//s4 = std::move(s5)String &operator=(String &&rhs){cout << "String &operator=(String &&)" << endl;if (this != &rhs)//1、自移动{delete[] _pstr;//2、释放左操作数_pstr = nullptr;_pstr = rhs._pstr;//3、浅拷贝rhs._pstr = nullptr;}return *this;//4、返回*this}~String(){cout << "~String()" << endl;if (_pstr){delete[] _pstr;_pstr = nullptr;}}friend std::ostream &operator<<(std::ostream &os, const String &rhs);
private:char *_pstr;
};std::ostream &operator<<(std::ostream &os, const String &rhs)
{if (rhs._pstr){os << rhs._pstr;}return os;
}void test()
{String s1("hello");cout << "s1 = " << s1 << endl;cout << endl;String s2 = s1;cout << "s1 = " << s1 << endl;cout << "s2 = " << s2 << endl;cout << endl;//   C++    C   C风格转换为C++风格//   过渡String s3 = "world";//String("world"),临时对象/匿名对象,cout << "s3 = " << s3 << endl;/* &"world";//文字常量区,左值 *//* String("world");//右值 */cout << endl;String s4("wangdao");cout << "s4 = " << s4 << endl;cout << endl;s4 = String("wuhan");cout << "s4 = " << s4 << endl;//左右操作数是两个不一样对象/* String("wuhan") = String("wuhan"); */cout << endl;cout << "000000" << endl;//std::move可以将左值转换为右值,实质上没有做任何移动,只是//在底层做了强制转换static_cast<T &&>(lvalue)//如果以后不想使用某个左值,可以使用std::move将其转换为//右值,以后就不再使用了s4 = std::move(s4);cout << "s4 = " << s4 << endl;cout << "11111" << endl;s2 = std::move(s1);cout << "s1 = " << s1 << endl;cout << "2222" << endl;}
int main()
{test();return 0;
}

运行结果:

#include <iostream>
#include <string>using std::cout;
using std::endl;
using std::string;void test()
{int a = 10;int b = 20;int *pflag = &a;string s1("hello");string s2("world");&a;//左值&b;//左值&pflag;//左值&*pflag;//左值&s1;//左值&s2;//左值(a + b);//右值,不能取地址(s1 + s2);//右值,不能取地址//const左值引用既可以绑定到左值,也可以绑定到右值const int &ref = a;const int &ref2 = 10;&ref;&ref2;//C++11之前是不能识别右值的,C++11之后新增语法可以识别右值//右值引用可以绑定到右值,能识别右值,但是右值引用不能//识别左值,绑定不了左值int &&rref = 10;//右值引用/* int &&rref2 = a;//error *///右值引用是左值还是右值?//所以,右值引用既可以是左值也可以是右值&rref;//右值引用在此处是左值//右值引用在作为函数参数的时候,体现出来的是左值的含义
}//右值引用可以是右值吗?
//右值引用作为函数返回类型的时候,是右值
int &&func()
{return 10;
}int main(int argc, char **argv)
{test();/* &func();//error, func是右值, */return 0;
}

资源管理与智能指针

一、C语言中的问题

C语言在对资源管理的时候,比如文件指针,由于分支较多,或者由于写代码的人与维护的人不一致,导致分支没有写的那么完善,从而导致文件指针没有释放,所以可以使用C++的方式管理文件指针。

#include <iostream>
#include <string>using std::cout;
using std::endl;
using std::string;class SafaFile
{
public://在构造时初始化资源,或者说托管资源SafaFile(FILE *fp): _fp(fp){cout << "SafaFile(FILE *)" << endl;}//提供若干访问资源的方法void write(const string &msg){fwrite(msg.c_str(), 1, msg.size(), _fp);}//void  read();//在析构时候释放资源~SafaFile(){cout << "~SafaFile()" << endl;if (_fp){fclose(_fp);//如果不关掉,就表明文件没有关闭cout << "fclose(_fp)" << endl;}}private:FILE *_fp;
};int main(int argc, char **argv)
{string msg = "hello,world";SafaFile sf(fopen("test.txt", "a+"));//sf是栈对象,//其实就是利用栈对象sf的生命周期管理文件指针的资源sf.write(msg);/* SafaFile sf2 = sf;//error */return 0;
}

二、C++的解决办法(RAII技术):

资源管理 RAII 技术,利用对象的生命周期管理程序资源(包括内存、文件句柄、锁等)的技术,因为对象在离开作用域的时候,会自动调用析构函数。
关键:要保证资源的释放顺序与获取顺序严格相反。正好是析构函数与构造函数的作用。
RAII常见特征
1、在构造时初始化资源,或者托管资源。
2、析构时释放资源。
3、一般不允许复制或者赋值(值语义-对象语义)
4、提供若干访问资源的方法。

区分:

值语义:可以进行复制与赋值。
对象语义:不能进行复制与赋值(世界上一般没有两个重复的人)

一般使用两种方法达到要求:

(1)、将拷贝构造函数和赋值运算符函数设置为私有的就 ok 。
(2)、将拷贝构造函数和赋值运算符函数使用=delete.

#include <iostream>using std::cout;
using std::endl;class Point
{
public:Point(int ix = 0, int iy = 0): _ix(ix), _iy(iy){cout << "Point(int = 0, int = 0)" << endl;}void print() const{cout << "(" << _ix<< ", " << _iy<< ")" << endl;}~Point(){cout << "~Point()" << endl;}
private:int _ix;int _iy;
};template<typename T>
class RAII
{
public://在构造函数中初始化资源RAII(T *data): _data(data){cout << "RAII(T *)" << endl;}//在析构函数中释放资源~RAII(){cout << "~RAII()" << endl;if (_data){delete _data;//假如指针是new出来_data = nullptr;}}//提供若干访问资源的方法T *operator->(){return _data;}T &operator*(){return *_data;}T *get() const{return _data;}//重置数据成员_datavoid reset(T *data){if (_data){delete _data;_data = nullptr;}_data = data;}//不允许复制或者赋值//C++11的写法=deleteRAII(const RAII &rhs) = delete;RAII &operator=(const RAII &rhs) = delete;//C++98(传统C++方法)设置为私有的
private:/* RAII(const RAII &rhs); *//* RAII &operator=(const RAII &rhs); */
private:T *_data;
};int main(int argc, char **argv)
{/* RAII<int> pInt(new int(10)); *///pt本身不是指针,但是具备指针的功能(智能指针)RAII<Point> pt(new Point(1, 2));//pt栈对象pt->print();(*pt).print();/* pt.operator->()->print();//ok *//* RAII<Point> pt2 = pt;//拷贝构造函数,error */RAII<Point> pt3(new Point(3, 4));/* pt3 = pt;//赋值运算符函数,error */return 0;
}

运行结果:

三、四种智能指针

智能指针的两篇优秀文章:

【C++】智能指针详解_c++智能指针-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_53268869/article/details/124551345?spm=1001.2014.3001.5506c++11之智能指针_智能指针c++11-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_56673429/article/details/124837626?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171124459316800186573033%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171124459316800186573033&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-8-124837626-null-null.nonecase&utm_term=C%2B%2B%E4%B9%8B%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88&spm=1018.2226.3001.4450

RAII的对象就有智能指针的雏形。其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源.

智能指针不是指针,是一个管理指针的类,用来存储指向动态分配(堆空间)对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源.

1、auto_ptr.cc

void test()
{int *pt = new int(10);auto_ptr<int> ap(pt);cout << "*ap = " << *ap << endl;cout << "ap.get() = " << ap.get() << endl;cout << "pt = " << pt  << endl;cout << endl << endl;auto_ptr<int> ap2(ap);//表面上执行拷贝构造函数,但是在底层已经发生了所有权(资源的)
的转移//该智能指针存在缺陷cout << "*ap2 = " << *ap2 << endl;cout << "*ap = " << *ap << endl;//core dump
}

具体的内部实现:
 

template<class T>class auto_ptr{public:auto_ptr(T* ptr=nullptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr; //管理权转移}auto_ptr<T>& operator = (auto_ptr<T>& ap){if (this != *ap) {delete _ptr;_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}~SmartPtr(){if (_ptr)delete _ptr;}T& operator *(){return *_ptr;}T* operator ->(){return _ptr;}private:T* _ptr;};

2、unique_ptr

void test()
{unique_ptr<int> up(new int(10));cout << "*up = " << *up << endl;cout << "up.get() = " << up.get() << endl;//获取托管的指针的值cout << endl << endl;/* unique_ptr<int> up2(up);//error,独享资源的所有权,不能进行复制 */cout << endl << endl;unique_ptr<int> up3(new int(10));/* up3 = up;//error,不能进行赋值操作 */ cout << endl << endl;unique_ptr<int> up4(std::move(up));//通过移动语义转移up的所有权cout << "*up4 = " << *up4 << endl;cout << "up4.get() = " << up4.get() << endl;cout << endl << endl;unique_ptr<Point> up5(new Point(3, 4));//通过移动语义转移up的所有权vector<unique_ptr<Point>> numbers;numbers.push_back(unique_ptr<Point>(new Point(1, 2)));numbers.push_back(std::move(up5));
}

具体的内部实现:

template<class T>class unique_ptr{public:unique_ptr(T* ptr = nullptr):_ptr(ptr){}//防拷贝unique_ptr(unique_ptr<T>& ap) = delete;unique_ptr<T>& operator = (unique_ptr<T>& ap) = delete;~SmartPtr(){if (_ptr)delete _ptr;}T& operator *(){return *_ptr;}T* operator ->(){return _ptr;}private:T* _ptr;};

几个注意点:

1.无法进行复制、赋值操作std::unique_ptr<int> ap(new int(88 );std::unique_ptr<int> one (ap) ; // 会出错std::unique_ptr<int> two = one; //会出错2.可以进行移动构造和移动赋值操作unique_ptr<int> GetVal( ){unique_ptr<int> up(new int(88 );return up;}unique_ptr<int> uPtr = GetVal();   //ok实际上上面的的操作有点类似于如下操作unique_ptr<int> up(new int(88 );unique_ptr<int> uPtr2 = std::move(up) ; //这里是显式的所有权转移. 把up所指的内存转给uPtr2了,而up不再拥有该内存.3.可做为容器元素unique_ptr<int> sp(new int(88));vector<unique_ptr<int> > vec;vec.push_back(std::move(sp));//vec.push_back( sp ); 这样不行,会报错的.//cout<<*sp<<endl;但这个也同样出错,说明sp添加到容器中之后,它自身报废了.

3、shared_ptr

#include <iostream>
using namespace std;
#include <string>
#include <memory>class Test
{
public:Test() : m_num(0){cout << "construct Test..." << endl;}Test(int x) : m_num(0){cout << "construct Test, x = " << x << endl;}Test(string str) : m_num(0){cout << "construct Test, str = " << str << endl;}~Test(){cout << "destruct Test..." << endl;}void setValue(int v){this->m_num = v;}void print(){cout << "m_num: " << this->m_num << endl;}private:int m_num;
};int main()
{/*--------------------------  一,初始化智能指针shared_ptr  ------------------------------*///1.通过构造函数初始化shared_ptr<int> ptr1(new int(3));cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;//2.通过移动和拷贝构造函数初始化shared_ptr<int> ptr2 = move(ptr1);//此时ptr1的空间不再使用cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;shared_ptr<int> ptr3 = ptr2;cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;//3.通过 std::make_shared初始化shared_ptr<int> ptr4 = make_shared<int>(8);shared_ptr<Test> ptr5 = make_shared<Test>(7);shared_ptr<Test> ptr6 = make_shared<Test>("GOOD LUCKLY!");//4.通过reset初始化ptr6.reset(); //重置ptr6, ptr6的引用基数为0cout << "ptr6管理的内存引用计数: " << ptr6.use_count() << endl;ptr5.reset(new Test("hello"));cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;cout << endl;cout << endl;/*--------------------------  二,共享智能指针shared_ptr的使用  ------------------------------*///1.方法一Test* t = ptr5.get();cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;t->setValue(1000);t->print();//2.方法二ptr5->setValue(7777);ptr5->print();printf("\n\n");/*------------------------------------  三,指定删除器  -----------------------------------*///1.简单举例shared_ptr<Test> ppp(new Test(100), [](Test* t) {//释放内存cout << "Test对象的内存被释放了......." << endl;delete t;});printf("----------------------------------------------------------------------\n");//2.如果是数组类型的地址,就需要自己写指定删除器,否则内存无法全部释放//shared_ptr<Test> p1(new Test[5], [](Test* t) {//    delete[]t;//    });//3.也可以使用c++给我们提供的 默认删除器函数(函数模板)shared_ptr<Test> p2(new Test[3], default_delete<Test[]>());//4.c++11以后可以这样写 也可以自动释放内存shared_ptr<Test[]> p3(new Test[3]);return 0;
}

具体的内部实现:

template<class T>class shared_ptr{public:shared_ptr(T*ptr =nullptr):_ptr(ptr),_pcount(new int(1)){}//拷贝构造shared_ptr(const T& sp)_ptr(sp._ptr),_pcount(sp._pcount){++(*_pcount);}//赋值拷贝shared_ptr<T>& operator = (shared_ptr<T>& sp){if (_ptr != sp._ptr) {if (--(*_pcount) == 0){delete _pcount;delete _ptr;}_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}T& operator *(){return *_ptr;}T* operator ->(){return _ptr;}~shared_ptr(){if (--(*_pcount) == 0 && _ptr) {delete _pcount;delete _ptr;}}private:T* _ptr;int* _pcount;};

指针存在的问题:

举例:
 

问题:循环引用--无法释放对象,造成内存泄露
#include <iostream>
#include <memory>class Parent;
class Child;typedef std::shared_ptr<Parent> parent_ptr;
typedef std::shared_ptr<Child> child_ptr;class Child
{
public:Child() {   std::cout << "Child..." << std::endl;   }~Child() {  std::cout << "~Child..." << std::endl;  }parent_ptr parent_;
};class Parent
{
public:Parent() {  std::cout << "Parent..." << std::endl;  }~Parent() { std::cout << "~Parent..." << std::endl; }child_ptr child_;
};int main(void)
{parent_ptr parent(new Parent);child_ptr child(new Child);parent->child_ = child;//parent.operator->()->child_ = child;child->parent_ = parent;return 0;
}

4、weak_ptr

使用:

#include <iostream>
#include <memory>
using namespace std;int main() 
{shared_ptr<int> sp(new int);weak_ptr<int> wp1;weak_ptr<int> wp2(wp1);weak_ptr<int> wp3(sp);weak_ptr<int> wp4;wp4 = sp;weak_ptr<int> wp5;wp5 = wp3;return 0;
}

内部实现:

template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(shared_ptr<T>& sp):_ptr(sp.get()),_pcount(sp.use_count()){}weak_ptr(weak_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){}weak_ptr& operator = (shared_ptr<T>& sp){_ptr = sp.get();_pcount = sp.use_count();return *this;}weak_ptr& operator = (weak_ptr<T>& sp){_ptr = sp._ptr;_pcount = sp._pcount;return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int use_count(){return *_pcount;}private:T* _ptr;int* _pcount;};

解决循环引用问题:
 

#include <iostream>
#include <memory>class X
{
public:X() { std::cout << "construct X" << std::endl; }~X() { std::cout << "~destruct X" << std::endl; }void Fun(){std::cout << "Fun() " << std::endl;}
};
int main()
{std::weak_ptr<X> p;{std::shared_ptr<X> p2(new X);std::cout << p2.use_count() << std::endl;p = p2;std::cout << "after p = p2" << std::endl;std::cout << p2.use_count() << std::endl;std::shared_ptr<X> p3 = p.lock();//提升成功if (!p3){std::cout << "object is destroyed" << std::endl;}else{p3->Fun();std::cout << p3.use_count() << std::endl;}}//new X 已经被释放了std::shared_ptr<X> p4 = p.lock();//提升失败if (!p4)std::cout << "object is destroyed 2" << std::endl;elsep4->Fun();return 0;
}

四、为智能指针定制删除器

很多时候我们都用new来申请空间,用delete来释放。库中实现的各种智能指针,默认也都是用delete来释放空间,但是若我们采用malloc申请的空间或是用fopen打开的文件,这时我们的智能指针就无法来处理,因此我们需要为智能指针定制删除器,提供一个可以自由选择析构的接口,这样,我们的智能指针就可以处理不同形式开辟的空间以及可以管理文件指针。
自定义智能指针的方式有两种,函数指针与仿函数(函数对象)

函数指针的形式:

template<class T>
void Free(T* p)
{if (p)free(p);
}
template<class T>
void Del(T* p)
{if (p)delete p;
}
void FClose(FILE* pf)
{if (pf)fclose(pf);
}
//定义函数指针的类型
typedef void(*DP)(void*);
template<class T>
class SharedPtr
{
public:SharedPtr(T* ptr = NULL ,DP dp=Del):_ptr(ptr), _pCount(NULL), _dp(dp){if (_ptr != NULL){_pCount = new int(1);}}
private:void Release(){if (_ptr&&0==--GetRef()){//delete _ptr;_dp(_ptr); delete _pCount;}}int& GetRef()
{return *_pCount;
}
private:T* _ptr;int* _pCount;DP _dp;
};

删除器的使用:

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

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

相关文章

2024年产品品牌化深度分析:消费者心理与品牌化、产品质量的权衡

随着市场竞争的加剧和消费者需求的多样化&#xff0c;产品品牌化已经成为企业不可或缺的战略选择。在2024年&#xff0c;当消费者面对众多商品时&#xff0c;品牌化与产品质量之间的权衡成为了消费者决策的重要因素。那么&#xff0c;在消费者心理中&#xff0c;品牌化重要还是…

cadence中run pspice运行仿真 光标搜索Search Command

cadence中run pspice运行仿真 光标搜索Search Command 在cadence进行波形分析时&#xff0c;如果可以随时找到对应的点分析十分方便。 也就是cadence中的光标搜索&#xff08;Search Command&#xff09;功能 但是需要输入正确形式才能使用 官方说明&#xff1a;PSpice User…

chatGPT中文在线版本(亲测可用

ChatGPT是一个先进的自然语言处理模型&#xff0c;由OpenAI开发。它通过深度学习技术训练而成&#xff0c;可以进行对话、回答问题等多种自然语言处理任务。对于学生、开发者、研究人员和任何对人工智能感兴趣的人来说&#xff0c;这是一个非常有用的工具。 最近找到一个国内可…

Linux 服务升级:Nginx 热升级 与 平滑回退

目录 一、实验 1.环境 2.Kali Linux 使用nmap扫描CentOS 3.Kali Linux 远程CentOS 4.Kali Linux 使用openvas 扫描 CentOS 5.Nginx 热升级 6.Nginx 平滑回退 二、问题 1.kill命令的信号有哪些 2.平滑升级与回退的信号 一、实验 1.环境 &#xff08;1&#xff09;主机…

鸿蒙网络开发学习:【ylong_http】

简介 ylong_http 构建了完整的 HTTP 能力&#xff0c;支持用户使用 HTTP 能力完成通信场景的需求。 ylong_http 使用 Rust 编写&#xff0c;为 OpenHarmony 的 Rust 能力构筑提供支持。 ylong_http 在 OpenHarmony 中的位置 ylong_http 向 OpenHarmony 系统服务层中的网络协…

Adaptive Object Detection with Dual Multi-Label Prediction

gradient reversal layer (GRL) 辅助信息 作者未提供代码

蓝桥杯需要掌握的几个案例(C/C++)

文章目录 蓝桥杯C/C组的重点主要包括以下几个方面&#xff1a;以下是一些在蓝桥杯C/C组比赛中可能会涉及到的重要案例类型&#xff1a;1. **排序算法案例**&#xff1a;2. **查找算法案例**&#xff1a;3. **数据结构案例**&#xff1a;4. **动态规划案例**&#xff1a;5. **图…

java 高级面试题(借鉴)(下)

雪花算法原理 第1位符号位固定为0&#xff0c;41位时间戳&#xff0c;10位workId&#xff0c;12位序列号&#xff0c;位数可以有不同实现。 优点&#xff1a;每个毫秒值包含的ID值很多&#xff0c;不够可以变动位数来增加&#xff0c;性能佳&#xff08;依赖workId的实现…

数据结构面试题

1、数据结构三要素&#xff1f; 逻辑结构、物理结构、数据运算 2、数组和链表的区别&#xff1f; 数组的特点&#xff1a; 数组是将元素在内存中连续存放&#xff0c;由于每个元素占用内存相同&#xff0c;可以通过下标迅速访问数组中任何元素。数组的插入数据和删除数据效率低…

v77.递归

理解&#xff1a; 函数直接或者间接地调用自身&#xff1b;并且有边界条件。 1&#xff1a; #include <stdio.h> int main() {int result fun(3);printf("%d",result);return 0 ; } int fun(int num) {if(num 1)return num;return num fun(num-1); }思路…

raise PyAutoGUIException! ! !

在了解pyautogui时&#xff0c;你是否遇到过这样的情况&#xff1a; y pyautogui.locateOnScreen(kk.png) print(y) 在信心满满下输入完成后选择直接运行&#xff0c;结果却是抛出异常的尴尬。 raise PyAutoGUIException( pyautogui.PyAutoGUIException: PyAutoGUI was unable…

一文详解Rust中的字符串

有人可能会说&#xff0c;字符串这么简单还用介绍&#xff1f;但是很多人学习rust受到的第一个暴击就来自这浓眉大眼、看似毫无难度的字符串。 请看下面的例子。 fn main() {let my_name "World!";greet(my_name); }fn greet(name: String) {println!("Hello…

[leetcode] 240. 搜索二维矩阵 II

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,…

LeetCode讲解算法2-数据结构[栈和队列](Python版)

文章目录 一、栈1.1 栈的定义1.2 栈的实现分析步骤1.3 栈的应用匹配圆括号匹配符号模2除法&#xff08;十进制转二进制&#xff09;进制转换 二、队列2.1 单向队列2.2 双端队列2.3 队列的应用验证回文串滑动窗口最大值 一、栈 1.1 栈的定义 栈是一种线性数据结构&#xff0c;栈…

机器人路径规划:基于鳑鲏鱼优化算法(BFO)的机器人路径规划(提供MATLAB代码)

一、机器人路径规划介绍 移动机器人&#xff08;Mobile robot&#xff0c;MR&#xff09;的路径规划是 移动机器人研究的重要分支之&#xff0c;是对其进行控制的基础。根据环境信息的已知程度不同&#xff0c;路径规划分为基于环境信息已知的全局路径规划和基于环境信息未知或…

如何在C语言中使用命令行参数

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…

Negative Sampling with Adaptive DenoisingMixup for Knowledge Graph Embedding

摘要 知识图嵌入(Knowledge graph embedding, KGE)的目的是通过对比正负三元组&#xff0c;将知识图中的实体和关系映射到一个低维、密集的向量空间中。在kge的训练过程中&#xff0c;由于kge只包含正三元组&#xff0c;因此负采样对于找到高质量的负三元组至关重要。大多数现…

如何申请代码签名证书

代码签名证书也是数字证书的一种&#xff0c;其主要作用是对可执行脚本、软件代码和内容进行数字签名的数字证书。代码签名证书用于验证开发者身份真实性、保护代码的完整性。用户下载软件时&#xff0c;能通过数字签名验证软件来源&#xff0c;确认软件、代码没有被非法篡改或…

有道翻译实现接口加密解密

文章目录 目标简单逆向分析源码深度逆向分析参考文献目标 实现对网易有道 sign 等参数的加密 及 返回的密文数据解密实现 简单逆向分析 首先在右上角提前登录好账号信息。 输入中文:你好 要求翻译成:英文 全局搜索:你好 或 hello,结果没有发现什么。 切换 Fetch/XHR …

关于YOLOv9项目的使用说明。

​ 专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;助力高效涨点&#xff01;&#xff01;&#xff01; 使用说明 1. 下载解压 首先&#xff0c;在进群之后&#xff0c;使用群公告中的百度云链接进行下载。 下载完成后解压打开&#xff0c;会得到一个…