【C++】右值引用 移动语义

目录

    • 前言
    • 一、右值引用与移动语义
      • 1.1 左值引用和右值引用
      • 1.2 右值引用使用场景和意义
      • 1.3 右值引用引用左值及其一些更深入的使用场景分析
        • 1.3.1 完美转发
    • 二、新的类功能
    • 三、可变参数模板


前言

本篇文章我们继续来聊聊C++11新增的一些语法——右值引用,我们在之前就已经讲过了左值引用,并且左值引用给我们带来了很多的好处直接减少了拷贝操作提高了效率,那么右值引用到底起什么作用呢?下面我们一起来学习吧!!

一、右值引用与移动语义

1.1 左值引用和右值引用

有关引用我们在之前的文章就讲过,它其实就是给别人取别名,所以无论是左值引用还是右值引用都是给别人取别名,只不过取别名的对象不一样罢了,左值引用就是给左值取别名,右值引用就是给右值取别名!!

什么是左值?什么是左值引用?

左值就是一个表示数据的表达式(如变量名或者解引用的指针),说的再通俗一点它是一个变量,标识一块空间,空间中存储着数据。我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

int main()
{// 以下的p、b、c、*p都是左值int* p = new int(0);int b = 1;const int c = 2;// 以下几个是对上面左值的左值引用int*& rp = p;int& rb = b;const int& rc = c;int& pvalue = *p;return 0;
}

什么是右值?什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(临时对象不能左值返回)等等,说的再通俗一点右值就是一个常量,它没有变量去标识,因此也就不能取到它的地址,它只是一个数据!!右值引用就是对右值的引用,给右值取别名。

int main()
{double x = 1.1, y = 2.2;// 以下几个都是常见的右值10;x + y;fmin(x, y);// 以下几个都是对右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);// 下面的语句都会编译报错,左操作数必须为左值//10 = 1;//x + y = 1;//fmin(x, y) = 1;return 0;
}

需要注意的是:右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址。也就是说,不能取字面量 10 的地址,但是 rr1 引用后,可以对 rr1 取地址,也可以修改 rr1。如果不想 rr1 被修改,可以用 const int&& rr1 去引用。注:rr1 和 rr2 都是左值。

在这里插入图片描述

左值可以引用右值吗?右值可以引用左值吗?

int main()
{// 左值引用可以引用右值吗? const的左值引用可以double x = 1.1, y = 2.2;//double& rr1 = x + y;	// 编译报错, x + y是右值, 也是临时对象, 临时对象具有常属性, 需要const保证权限平行const double& rr2 = x + y; // 可以// 右值引用可以引用左值吗?不可以, 可以引用move以后的左值int a = 10;//int&& rr3 = a; // 编译报错, 右值引用不能引用左值int&& rr4 = 10;  // 可以, 10是右值, 对右值取别名int&& rr5 = move(a);  // move(a)的本质是得到一个右值表达式return 0;
}

左值引用与右值引用总结:左值引用只能引用左值,不能引用右值。但是 const 左值引用既可引用左值,也可引用右值。右值引用只能引用右值,不能引用左值,但是右值引用可以 move 以后的左值。

其实上面并不是右值引用的使用场景,因为const左值引用既能做到对左值引用又能做到对右值引用,那么增加右值引用不就是多余的吗!!下面我们继续来看看右值引用的使用场景。

1.2 右值引用使用场景和意义

左值引用解决的问题:

做参数:a. 减少拷贝,提高效率。b. 做输出型参数
做返回值:a. 减少拷贝,提高效率(不能返回临时对象的引用)。

从上述中我们可以知道左值引用的短板就是不能返回临时对象的引用,那么对于自定义类型对象返回时,是不是一定会进行一次深拷贝的操作,所以右值引用设计出来就是为了解决此类问题的。

在讲右值引用前,我们先来了解一组概念:右值有两类,第一类是纯右值,即内置类型右值;第二类是将亡值,即自定义类型右值。右值将亡值的资源可以转移到指定的对象。

在这里插入图片描述

就好比日常生活中,有些重症患者已经到了病入膏肓的情况,它可以将自身的一些器官捐赠给有需要的人身上。右值引用+移动语义最重要的是理解资源转移的过程!

下面我们通过一段代码来进行分析:

namespace bit
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}~string(){delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}string& operator+=(char ch){push_back(ch);return *this;}string operator+(char ch){string tmp(*this);tmp += ch;return tmp;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};bit::string to_string(int value){bool flag = true;if (value < 0){flag = false;value = 0 - value;}bit::string str;while (value > 0){int x = value % 10;value /= 10;str += ('0' + x);}if (flag == false){str += '-';}std::reverse(str.begin(), str.end());return str; }
}int main()
{curry::string ret1 = curry::to_string(1234);return 0;
}

我们先来看看没有C++11之前未新增移动拷贝的情况:

在这里插入图片描述

从上图我们也可以看到编译器优化后只调用了一次深拷贝操作,下面当我们在自定义类型中添加了移动构造后,看看编译器会做什么处理:

在这里插入图片描述

从上图我们可以看到当新增移动拷贝函数之后,编译器识别为右值类型就会优先去调用移动拷贝函数,相较于深拷贝移动拷贝只是将资源进行了转移——得到那块空间的起始地址就能访问那块空间的所有资源!!避免了拷贝的操作,移动拷贝实际并不是真正的拷贝,其实就可以理解为改变了接收资源的对象!!所以右值引用在这种情况下提供了非常大的价值!!如果返回的对象是一个数组、一棵树呢?是不是极大程度上减少了拷贝,提高了效率!!移动语义包括移动构造移动赋值两个成员函数!!

注:右值引用并不是直接作为返回值起作用的,右值引用返回临时对象跟左值引用返回对象的情况是一样的,临时对象都是会被销毁的!!不能直接返回临时对象的左右值引用!!


我们通过监视窗口来继续看看移动构造的具象过程:

int main()
{curry::string s1("hello world");curry::string ret1 = s1;curry::string ret2 = (s1 + '1');curry::string ret3 = move(s1); // move操作可以理解为将左值转变为右值返回 -- 是一个表达式return 0;
}

在这里插入图片描述

通过上图我们可以发现move操作是有风险的,它能将一个对象的资源转交给另一个对象,转移完之后这个对象就悬空了,我们不能对这个悬空的对象做操作!!当你不想用个资源了之后可以这样做,如果后续你还要用到它就不能这么干了!!

我们来看看下面这种情况:

在这里插入图片描述


C++11后,STL 中的容器都是增加了移动构造和移动赋值的。

在这里插入图片描述

STL 容器的插入接口函数也增加了右值引用版本

在这里插入图片描述

在这里插入图片描述


总结:

  • 右值引用使用的场景:自定义类型临时对象返回或自定义类型对象作为右值进行传参时,此时编译器会优先进行移动拷贝间接减少拷贝,将右值资源直接进行转移!!
  • 左右值引用区别:左值引用是直接减少拷贝,而右值引用是间接减少拷贝,编译器识别出是左值还是右值,如果是右值则不再进行深拷贝,直接进行移动拷贝转移资源提高效率!!

1.3 右值引用引用左值及其一些更深入的使用场景分析

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
// forward _Arg as movable
return ((typename remove_reference<_Ty>::type&&)_Arg);
}
1.3.1 完美转发

模板中的&& 万能引用

// 下面的 Fun都是重载函数, 因为引用也属于类型
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }// 万能引用:既能引用左值,也能引用右值
// 引用折叠
template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}

模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值,这是大佬规定的语法。

我们输出一下结果看看是否已经根据类型实现了万能引用:

在这里插入图片描述

我们发现结果并不是我们想象的那样:左值引用就调用左值引用对应的函数,右值引用就调用右值引用的函数??

我们继续进行分析,在之前的例子中我们说过右值是不能取地址的例如10,但是右值引用过后int&& rr1 = 10;这时候你会发现rr1此时成为了一个变量,既然是变量就有空间(地址),所以此时的rr1就充当了左值!!好,我们继续来看一个例子:

在这里插入图片描述

结论:模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发!!


完美转发:保持变量的属性,防止提早转变成左值属性,导致调用的接口函数不一致。

在这里插入图片描述

注:完美转发操作在容器中的各种函数中十分常见,因为C++11提供了右值引用+移动语义,并且在某些场景下我们需要一直转发才能防止变量属性提早转变(退化为左值)!!

我们来看看完美转发的场景:

template<class T>
struct ListNode
{ListNode* _next = nullptr;ListNode* _prev = nullptr;T _data;
};template<class T>
class List
{typedef ListNode<T> Node;
public:List(){_head = new Node;_head->_next = _head;_head->_prev = _head;}void push_back(T&& x){//Insert(_head, x);Insert(_head, std::forward<T>(x));}void push_back(const T& x){Insert(_head, x);}void Insert(Node* pos, T&& x){Node* prev = pos->_prev;Node* newnode = new Node;newnode->_data = std::forward<T>(x); // 关键位置// prev newnode posprev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}void Insert(Node* pos, const T& x){Node* prev = pos->_prev;Node* newnode = new Node;newnode->_data = x; // 关键位置// prev newnode posprev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}
private:Node* _head;
};int main()
{List<curry::string> lt;lt.push_back("1111");lt.push_back(curry::string("hello"));return 0;
}

在这里插入图片描述

我们来分析一下为什么会是这样的结果:

在这里插入图片描述

我们来看看每个接口函数间不一直进行转发会发生什么情况:

在这里插入图片描述

我们发现当insert函数中的x未进行转发,导致提前退化为左值故调用赋值重载!!所以我们在某些场景下一定要小心将数据一直转发下去!!

二、新的类功能

原来 C++ 类中,有 6 个默认成员函数:构造函数、析构函数、拷贝构造函数,赋值运算符重载、取地址重载和 const 取地址重载。重要的是前 4 个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。C++11 新增了两个:移动构造函数和移动赋值运算符重载。拷贝构造函数和赋值运算符重载是针对左值的拷贝,而移动构造和移动赋值时针是右值的拷贝。不需要深拷贝的类,也就不需要自己写移动构造和移动赋值。拷贝对象需要深拷贝时,自己写移动构造和移动赋值。比如:string、vector 和 list 等。

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行浅拷贝,对于自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行浅拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用赋值运算符重载。
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。如果没有移动构造和移动赋值,才会去调用拷贝构造和赋值运算符重载。

类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化。这个在类和对象就讲了,这里就不再细讲了。

强制生成默认函数的关键字 default

C++11 可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用 default 关键字显示指定移动构造生成。

禁止生成默认函数的关键字 delete

如果能想要限制某些默认函数的生成,在 C++98 中,可以将该函数设置成 private,这样只要其他人想要调用就会报错。在 C++11 中更简单,只需在该函数声明加上 = delete 即可,该语法指示编译器不生成对应函数的默认版本,称 = delete 修饰的函数为删除函数。

继承和多态中的 final 与 override 关键字

final 可以修饰一个类,表示这个类不能被继承;也能修饰一个虚函数,表示这个虚函数不能被重写。override 修饰子类的虚函数,如果子类的虚函数没有完成重写,就会编译报错。

三、可变参数模板

在这里插入图片描述
其实在 C 语言中我们就已经接触过可变参数了,只是我们并没有深入的去研究过,上述printf的参数列表中... 就代表任意多个参数,这里我们就不去研究C语言中的可变参数模板到底是如何提取出参数的了,我们来看下C++是如何来操作的!!

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比
C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改
进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。现
阶段呢,我们掌握一些基础的可变参数模板特性就够我们用了。

下面就是一个基本可变参数的函数模板

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

上面的参数 args 前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为参数包,它里面包含了 0 到 N(N>=0)个模版参数。我们无法直接获取参数包 args 中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用 args[i] 这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。

计算可变参数的个数

// 可变参数的模板
template <class ...Args>
void ShowList(Args... args)
{cout << sizeof...(args) << endl;
}int main()
{string str("hello");ShowList();ShowList(1);ShowList(1, 'A');ShowList(1, 'A', str);return 0;
}

在这里插入图片描述

通过上图我们能准确的知道每次调用showList有多少参数,但是我们该如何展开其中的参数包获取参数包的值呢?

递归函数方式展开参数包

template <class T>
void ShowList(const T& t)
{cout << t << endl;
}template <class T, class ...Args>
void ShowList(T value, Args... args)
{cout << value << " ";ShowList(args...);
}int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

此时传递了两个模板参数,第一个模板参数接收的是传过来的值,第二个接收的是剩余的参数包,打印完value之后再继续递归展开剩余的参数包,直至参数包的个数为0递归结束!!

在这里插入图片描述

逗号表达式展开参数包
这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的,printarg
不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。

这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。
expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行
printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列
表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0),
(printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…
(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)
打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在
数组构造的过程展开参数包。

template <class T>
void PrintArg(T t)
{cout << t << " ";
}template <class ...Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(args), 0)... };cout << endl;
}int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

在这里插入图片描述


STL 容器中的 empalce 相关接口函数

在这里插入图片描述

我们可以看到C++11在很多容器中都新增了emplace相关的接口函数,并且emplace接口函数中使用了可变模板参数的万能引用,那么相对于push_back()、insert等接口emplace有什么优势的地方吗?我们来看一组例子:

int main()
{list<curry::string> list;curry::string s1("1111");// 插入左值  -- 没区别list.push_back(s1);list.emplace_back(s1);cout << endl;// 插入move()curry::string s2("22222");list.push_back(move(s1));list.emplace_back(move(s2));cout << endl;// 直接插入右值 -- 开始有区别//list.push_back("3333");//list.emplace_back("3333");return 0;
}

在这里插入图片描述

下面我们来看看区别在哪?

在这里插入图片描述

我们可以发现push_back比emplace_back多调用了一次移动构造将右值资源进行了转移,那么为何emplace只会调用一次构造呢?原因是"3333"直接被当做const char*类型的参数包传下去,最终直接去调用const char*的构造函数,而在push_back中"3333"要隐式类型转换为string类型的对象,先去会去调用构造函数,然后它为右值类型会与移动构造进行匹配,所以它比emplace会多调用一次移动构造,但其实理论上俩者的效率都差不大多,因为需要深拷贝的类移动构造并不会消耗太多的时间!!


下面我们来看看浅拷贝类emplace_back与push_back的效率问题:

class Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){cout << "Date(const Date& d)" << endl;}Date& operator=(const Date& d){cout << "Date& operator=(const Date& d))" << endl;return *this;}private:int _year;int _month;int _day;
};int main()
{// 没有区别list<Date> list;Date d1(2024, 5, 18);list.push_back(d1);list.emplace_back(d1);cout << "-----------------" << endl;Date d2(2024, 5, 18);list.push_back(move(d1));list.emplace_back(move(d2));cout << "-----------------" << endl;// 有区别list.push_back(Date(2024, 5, 18));list.push_back({ 2024, 5, 18 });cout << "-----------------" << endl;list.emplace_back(Date(2024, 5, 18));list.emplace_back(2024, 5, 18);return 0;
}

注:由于我们实现的是浅拷贝类,所以移动构造与拷贝构造都是浅拷贝并无其他区别,这里我们就不去实现移动拷贝函数了。
在这里插入图片描述

通过上图我们知道emplace少调用了一次拷贝构造,当这个浅拷贝的类非常大时是不是就减少了大量的拷贝提高了效率,所以从这个角度新增emplace接口还是有一定价值的!!

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

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

相关文章

民国漫画杂志《时代漫画》第22期.PDF

时代漫画22.PDF: https://url03.ctfile.com/f/1779803-1248634856-2c7010?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!

AIGC 006-textual-inversion使用文本反转实现个性化文本到图像生成!

AIGC 006-textual-inversion使用文本反转实现个性化文本到图像生成&#xff01; 文章目录 0 论文工作1 论文方法2 效果 0 论文工作 这篇论文 (An Image is Worth One Word: Personalizing Text-to-Image Generation using Textual Inversion) 提出了一种新颖的技术&#xff0c…

Modal.method() 不显示头部的问题

ant-design中的Modal组件有两种用法&#xff1a; 第一种是用标签&#xff1a;<a-modal></a-modal> 第二种是用Api&#xff1a;Modal.info、Modal.warning、Modal.confirm...... 一开始项目中这两种用法是混用的&#xff0c;后面UI改造&#xff0c;需要统一样式&…

Shell字符串变量

目标 能够使用字符串的3种方式 掌握Shell字符串拼接 掌握shell字符串截取的常用格式 能够定义Shell索引数组和关联数组 能够使用内置命令alias,echo,read,exit,declare操作 掌握Shell的运算符操作 Shell字符串变量 介绍 字符串&#xff08;String&#xff09;就是一系…

使用LabVIEW时遇到VISA属性错误 -1073807331的解决方案

在LabVIEW或VeriStand中使用VISA属性时&#xff0c;可能会遇到错误 -1073807331。这一错误的具体描述如下&#xff1a; 解决方案 导致VISA属性出现此错误的原因主要有以下四种&#xff1a; 属性不被使用的串行总线支持 示例 A.1&#xff1a;Is Port Connected VISA属性仅支持由…

React(四)memo、useCallback、useMemo Hook

目录 (一)memo API 1.先想一个情景 2.用法 (1)props传入普通数据类型的情况 (2)props传入对象的情况 (3)props传入函数的情况 (4)使用自定义比较函数 3.什么时候使用memo&#xff1f; (二)useMemo Hook 1.用法 2.useMemo实现组件记忆化 3.useMemo实现函数记忆化 …

如何停止 iPad 和 iPhone 之间共享短信,独立接收和发送消息

概括 在当今高度互联的数字世界中&#xff0c;Apple 设备之间的无缝连接性提供了极大的便利&#xff0c;尤其是在消息同步方面。iPhone 和 iPad 用户通常可以享受到设备间短信的自动同步功能&#xff0c;这意味着无论是在哪个设备上&#xff0c;用户都可以接收和回复消息。然而…

深入理解python列表与字典:数据结构的选择与性能差异

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、列表与字典&#xff1a;基础数据结构的对比 二、列表&#xff1a;逐个遍历的查找方式 …

【JAVA】接口

前面我们说了说抽象类相关内容&#xff0c;这篇我们主要聊聊接口相关内容&#xff0c;这部分很重要&#xff0c;大家引起关注。 1. 接口 1.1 接口的概念 接口就是公共的行为规范标准&#xff0c;大家在实现时&#xff0c;只要符合规范标准&#xff0c;就可以通用。在Java中&am…

C语言 数组——向函数传递数组

目录 把数组传给函数&#xff08;Passing Arrays to Functions&#xff09; 向函数传递一维数组 向函数传递二维数组 数组在学生成绩管理中的应用 例&#xff1a;计算每个学生的平均分 把数组传给函数&#xff08;Passing Arrays to Functions&#xff09; 向函数传递一维…

gnocchi学习小结

背景 总结gnocchi 4.4版本gnocchi-metricd工作流程 入口 gnocchi.cli.metricd metricd stop after processing metric默认为0&#xff0c;调servicemanager run MetricdServiceManager __init__ 服务逻辑封装到MetricdServiceManager初始化中 主要由MetricProcessor, Met…

基于Vue的前端自定义询问弹框与输入弹框组件的设计与实践

基于Vue的前端自定义询问弹框与输入弹框组件的设计与实践 摘要 随着技术的不断进步&#xff0c;前端开发面临越来越多的挑战&#xff0c;其中之一就是如何有效管理复杂的业务逻辑和用户体验。传统的整块应用开发方式在面对频繁的功能变更和用户体验优化时&#xff0c;往往显得…

python数据分析-CO2排放分析

导入所需要的package import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import datetime %matplotlib inline plt.rcParams[font.sans-serif] [KaiTi] #中文 plt.rcParams[axes.unicode_minus] False #负号 数据清洗…

SSH 远程登录系统和远程拷贝

文章目录 目录 文章目录 前言 一.SSH的基本用法 SSH基本用法&#xff1a; SSH无密码登录 二.SSH安全设置 三.SSH限制用户 前言 很多时候服务器并没有服务器&#xff0c;我们也不能每次都通过控制台去管理服务器&#xff0c;这时候就需要远程登录&#xff0c;相比于Telnet&a…

京东应届生公司内网说了一句‘什么时候被pdd收购‘,结果惨遭辞退

京东应届生公司内网说了一句’什么时候被pdd收购’&#xff0c;结果惨遭公司开除 这个事最近在圈子讨论比较多 前二天&#xff0c;有一个上海交大毕业的应届生&#xff0c;在京东实习了9个月&#xff0c;好不容易转正12天后&#xff0c;只因在内网说了一句话&#xff0c;就被…

upload-labs 21关解析

目录 一、代码审计 二、实践 三、总结 一、代码审计 $is_upload false; $msg null; if(!empty($_FILES[upload_file])){//检查MIME$allow_type array(image/jpeg,image/png,image/gif);if(!in_array($_FILES[upload_file][type],$allow_type)){$msg "禁止上传该类型…

Quartus Cyclone I II III IVE 器件型号

玩耍了一个 EP2 型号的开发板&#xff0c;发现 安装的quartus13 没有Cyclone II 型号&#xff0c;经过探索发现了是版本不对。 https://www.intel.com/content/www/us/en/software-kit/711920/intel-quartus-ii-subscription-edition-design-software-version-13-0sp1-for-win…

行业分析---造车新势力之蔚来汽车

1 前言 在之前的博客中&#xff0c;笔者分析了苹果《行业分析---我眼中的Apple Inc.》&#xff0c;苹果已经成为世界级的公司。随后也分析了电动汽车公司特斯拉《行业分析---马斯克的Tesla》&#xff0c;特斯拉也在不断成长。目前能分析的新能源汽车公司不多&#xff0c;小米汽…

Minecraft服务器如何搭建

Minecraft这是原版英文名称&#xff0c;在中国大陆被译为《我的世界》&#xff0c;这款游戏很火爆。台湾的很多小伙伴也在玩&#xff0c;其译名为《我的创世神》。现在这款游戏在国内已经被网易代理了。因为这款游戏开源&#xff0c;所以任何人都可以搭建服务器端&#xff0c;如…

机器人支持回调接口配置(详细教程)

大家伙&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。 一、前言 今天&#xff0c;给大家介绍一下&#xff0c;如何在机器人中配置回调地址和接口编写。很多时候我们可能有这样的场景&#xff0c;收到消息后&#xff0c;想自己处理一下消息的内…