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.hqwc.cn/news/564655.html

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

相关文章

就业班 第二阶段 2401--3.19 day4 主从复制

一、MySQL-Replication&#xff08;主从复制&#xff09; 1.1、MySQL Replication 主从复制&#xff08;也称 AB 复制&#xff09;允许将来自一个MySQL数据库服务器&#xff08;主服务器&#xff09;的数据复制到一个或多个MySQL数据库服务器&#xff08;从服务器&#xff09;…

【项目设计】基于MVC的负载均衡式的在线OJ

项目代码&#xff08;可直接下载运行&#xff09; 一、项目的相关背景 学习编程的小伙伴&#xff0c;大家对力扣、牛客或其他在线编程的网站一定都不陌生&#xff0c;这些编程网站除了提供了在线编程&#xff0c;还有其他的一些功能。我们这个项目只是做出能够在线编程的功能。…

Priority Queue实现栈和队列

在排序算法中&#xff0c;堆排序利用了完全二叉树形式的堆结构&#xff0c;结合了插入排序与合并排序的优点&#xff0c;能够以 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)的时间完成排序。优先级队列是可基于堆结构进行实现的一种数据结构&#xff0c;在计算机系统中可以用来记录…

[AIGC] SQL中的数据添加和操作:数据类型介绍

SQL&#xff08;结构化查询语言&#xff09;作为一种强大的数据库查询和操作工具&#xff0c;它能够完成从简单查询到复杂数据操作的各种任务。在这篇文章中&#xff0c;我们主要讨论如何在SQL中添加&#xff08;插入&#xff09;数据&#xff0c;以及在数据操作过程中&#xf…

安装MQTTfx,并且模拟MQTT订阅发布

&#xff08;一&#xff09;软件安装 下面给出了mqttfx官网的链接&#xff0c;可以使用官网进行下载软件&#xff0c;不过因为一些可知的原因&#xff0c;可能无法打开&#xff0c;那么你可以使用我提供的百度网盘进行下载安装。 官网链接&#xff1a;Softblade GmbH - Home …

h5增强表单---数据列表和属性

h5增强表单---数据列表 下拉列表 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</ti…

Agent Workflows(智能体工作流)

1.目前使用LLM的方式 目前&#xff0c;我们主要在零样本模式下使用大型语言模型&#xff08;LLM&#xff09;&#xff0c;通过提供提示&#xff08;prompt&#xff09;&#xff0c;模型逐词&#xff08;token&#xff09;地构建最终的输出内容&#xff0c;其间并未实施任何操作…

【新手教程】mmselfsup训练教程及常见报错处理

mmselfsup教程 1.安装mmselfsup2.了解文件结构与配置3.训练常见报错1.报错&#xff1a;FileNotFoundError: [Errno 2] No such file or directory:data/imagenet/train/./train/n04311004/images/n04311004_194.JPEG2.报错&#xff1a;报错ImportError: /mmcv/_ext.cpython-38-…

IT运维服务规范标准与实施细则

一、 总则 本部分规定了 IT 运维服务支撑系统的应用需求&#xff0c;包括 IT 运维服务模型与模式、 IT 运维服务管理体系、以及 IT 运维服务和管理能力评估与提升途径。 二、 参考标准 下列文件中的条款通过本部分的引用而成为本部分的条款。凡是注日期的引用文件&#xff0c…

由浅到深认识Java语言(7):方法(函数)

该文章Github地址&#xff1a;https://github.com/AntonyCheng/java-notes 在此介绍一下作者开源的SpringBoot项目初始化模板&#xff08;Github仓库地址&#xff1a;https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址&#xff1a;https://blog.c…

C语言——sizeof与strlen的对比

一.sizeof 我们在学习操作符的时候&#xff0c;就了解到了sizeof操作符&#xff0c;它的作用是求参数所占内存空间的大小&#xff0c;单位是字节。如果参数是一个类型&#xff0c;那就返回参数所占的字节数。 #include <stdio.h>int main() {int a 10;size_t b sizeo…

GEE入门及进阶教程|在 Earth Engine 中过滤图像集合

Earth Engine API 为 ImageCollection 类型提供了一组过滤器&#xff0c;过滤器可以根据空间、时间或属性特征来限制 ImageCollection&#xff0c;即可将图像从 ImageCollection 中分离出来以进行检查或操作。 图1 1 Earth Engine 中应用于图像集合的过滤、映射…