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.rhkb.cn/news/289863.html

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

相关文章

<el-table>设置一列为固定字段,其他列为循环生成

<el-table :data"tableData" style"width: 100%"><el-table-columnprop"name"label"固定字段名":formatter"formatter"></el-table-column><el-table-columnv-for"(item, index) in wordsColumns…

echarts 3D示例 echart, echarts-gl

echarts官网有很多的炫酷的3D模型 来尝试实现下&#xff0c;使用原本的柱状图或者折线图代码创建echarts示例,使用cdn的方式引入echarts <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewp…

XUbuntu22.04之激活Linux最新Typora版本(二百二十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

element-ui 自定义点击图标/文本/按钮触发el-date-picker时间组件,不使用插槽

天梦星服务平台 (tmxkj.top)https://tmxkj.top/#/ 1. 图片预览 2.上代码 2.1html <el-button class"hide_input" size"small"><svg t"1711608996149" class"icon" viewBox"0 0 1024 1024" version"1.1"…

macOS 13 Ventura (苹果最新系统) v13.6.6正式版

macOS 13 Ventura是苹果电脑的全新操作系统&#xff0c;它为用户带来了众多引人注目的新功能和改进。该系统加强了FaceTime和视频通话的体验&#xff0c;同时优化了邮件、Safari浏览器和日历等内置应用程序&#xff0c;使其更加流畅、快速和安全。特别值得一提的是&#xff0c;…

原型链-(前端面试 2024 版)

来讲一讲原型链 原型链只存在于函数之中 四个规则 1、引用类型&#xff0c;都具有对象特性&#xff0c;即可自由扩展属性。 2、引用类型&#xff0c;都有一个隐式原型 __proto__ 属性&#xff0c;属性值是一个普通的对象。 3、引用类型&#xff0c;隐式原型 __proto__ 的属…

二维双指针,滑动窗口

二维双指针 思路&#xff1a;考虑暴力做法&#xff0c;我们统计前缀和&#xff0c;然后枚举以 ( x 1 , y 1 ) (x_1,y_1) (x1​,y1​), ( x 2 , y 2 ) (x_2,y_2) (x2​,y2​)为左上&#xff0c;右下顶点的矩阵有多少是合法的&#xff0c;那么&#xff0c;这样的时间复杂度为 n 4…

纯分享万岳外卖跑腿系统客户端源码uniapp目录结构示意图

系统买的是商业版&#xff0c;使用非常不错有三端uniapp开源代码&#xff0c;自从上次分享uniapp后有些网友让我分享下各个端的uniapp下的各个目录结构说明 我就截图说以下吧&#xff0c;

怎样去保证 Redis 缓存与数据库双写一致性?

解决方案 那么我们这里列出来所有策略&#xff0c;并且讨论他们优劣性。 先更新数据库&#xff0c;后更新缓存先更新数据库&#xff0c;后删除缓存先更新缓存&#xff0c;后更新数据库先删除缓存&#xff0c;后更新数据库 先更新数据库&#xff0c;后更新缓存 这种方法是不推…

基于单片机技术的门禁系统硬件设计研究

摘要:门禁系统在工业领域的应用十分广泛,如何利用单片机技术对门禁系统中的硬件进行管理与控制已经成为相关单位十分重要的研究课题之一。因此,文章设计了一套基于单片机技术的门禁系统硬件方案,旨在充分发挥单片机设备在自动化控制方面的优势,提高门禁系统的自动化水平。…

Uibot6.0 (RPA财务机器人师资培训第5天 ) 报销汇总机器人案例实战

训练网站&#xff1a;泓江科技 (lessonplan.cn)https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981https…

企业网站建设的方法的相关问题的解决办法的问题

现在市场上比较大的公司都建立了自己的企业网站&#xff0c;比如华为、小米等&#xff0c;在他们的企业网站中&#xff0c;可以充分展示自己产品的优势&#xff0c;介绍公司的优质服务。 这都是让顾客改变购买想法的重要因素。 现在互联网发达了&#xff0c;很多人在购买产品的…

Linux内核之最核心数据结构之一:struct file(三十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

使用脚本自动同步时间(在 Windows 7/8/10/11 中)

你可以使用使用 w32tm 命令的批处理脚本来同步 Windows上的时间。 这是一个用于同步时间的简单批处理脚本&#xff1a; echo off echo 正在同步时间... w32tm /resync echo 时间同步完成。将以上代码保存在扩展名为.bat的文本文件中&#xff0c;例如sync_time.bat。 然后&…

推动制药行业数字化转型:基于超融合架构的MES一体机解决方案

随着中国对信息化重视程度的不断加深&#xff0c;制药行业作为国民经济的重要支柱之一&#xff0c;也在积极寻求通过数字化手段提升产业效率与产品质量。自党的十六大提出“以信息化带动工业化”的战略以来&#xff0c;制药业的这一转型探索尤为迫切。 在现代制药生产中&#…

Svg Flow Editor 原生svg流程图编辑器(四)

系列文章 Svg Flow Editor 原生svg流程图编辑器&#xff08;一&#xff09; Svg Flow Editor 原生svg流程图编辑器&#xff08;二&#xff09; Svg Flow Editor 原生svg流程图编辑器&#xff08;三&#xff09; Svg Flow Editor 原生svg流程图编辑器&#xff08;四&#xf…

实时数仓之实时数仓架构(Hudi)

目前比较流行的实时数仓架构有两类&#xff0c;其中一类是以FlinkDoris为核心的实时数仓架构方案&#xff1b;另一类是以湖仓一体架构为核心的实时数仓架构方案。本文针对FlinkHudi湖仓一体架构进行介绍&#xff0c;这套架构的特点是可以基于一套数据完全实现Lambda架构。实时数…

LockSupport与线程中断机制

中断机制是个协商机制 Interrupt(): 将中断状态设置为true Interrupted():&#xff08;静态方法&#xff09; 1.返回当前线程的中断状态 2.将中断状态清零并设置为false is Interrupted(): 判断当前线程是否被中断 如何停止中断运行中的线程&#xff1f; 一个线程不应该由…

电脑关机速度很慢怎么解决?

给电脑关机&#xff0c;总是要很久才完全关闭。这是因为计算机运行了太长时间&#xff0c;并且打开的程序太多&#xff0c;则关闭时间超过十秒钟&#xff0c;这是正常的现象。还有就是计算机升级或补丁程序更新也将导致计算机缓慢关闭。此时&#xff0c;建议耐心等待关闭完成。…

Redis、Mysql双写情况下,如何保证数据一致

Redis、Mysql双写情况下&#xff0c;如何保证数据一致 场景谈谈数据一致性三个经典的缓存模式Cache-Aside Pattern读流程写流程 Read-Through/Write-Through&#xff08;读写穿透&#xff09;Write behind &#xff08;异步缓存写入&#xff09; 操作缓存的时候&#xff0c;删除…