右值引用+移动语义

目录

右值引用

引入

介绍 

左值

 左值引用

左值引用的缺陷

引入

缺陷

解决

右值

纯右值

将亡值

右值引用

move函数

介绍

底层实现

参数 -- 通用引用类型

引用折叠

折叠规则:

返回值

remove_reference

移动

引入

介绍

移动构造函数 

介绍

是否抛出异常

noexcept

使用

应用场景

返回值 --  一次深拷贝->移动构造(编译器优化后)

传参 -- 传入右值 

list举例

vector举例

forward

介绍

底层

原理

完美转发 

介绍

示例 

改造我们的list

代码

测试

移动赋值函数 

使用


右值引用

引入

  • 在之前,我们就已经接触了引用的概念
  • 但c++进一步增加了右值引用的概念,所以之前使用的引用就被叫做左值引用(为了与右值引用区分开)
  • 但无论左值引用还是右值引用,都是给对象取别名,只不过对象类型不同

介绍 

左值

  • 左值表示对象的身份,我们可以获取它的地址+可以对它赋值
  • 左值有自己长久的生命周期,要么全局存在,要么随所在的函数存在
  • 以下都是左值:
  • int* p = new int(0);
    int b = 1;
    const int c = 2;
  • 赋值符号的左边 -- 只能是左值
  • 定义时const修饰符后的左值,既可以引用左值,也可以引用右值

 左值引用
  • 左值引用就是给左值取别名,在之前的引用中已经介绍过了
  • int*& rp = p;
    int& rb = b;
    const int& rc = c;
    int& pvalue = *p;
左值引用的缺陷
引入

既然c++11引入了右值引用,说明之前只有左值引用存在时有缺陷

众所周知,用引用作为参数,可以减少拷贝次数,尤其是深拷贝,对需要深拷贝的提升的效率更大

缺陷

但是,如果涉及到临时变量,是绝对不能使用左值引用的:

  • (在之前引用那里就有介绍过,不能传临时变量的引用作为返回值,也不能使用引用接收返回值)
  • 因为可能会出现"悬挂引用"的问题,或者把不该绑定在一起的变量绑定了
  • 所以,一般这里就不会使用引用,而是传值返回,以及用值接收返回值
  • 但就又会出现拷贝的问题
  • 因为传参会先创建一个临时变量,再将临时变量拷贝给ret2,这就会出现2次深拷贝,代价很大
  • 当然,新一点的编译器会做出优化,会将返回值直接拷贝给接收变量,但依然会有1次深拷贝
解决
  • 实际上,我们可以直接将str的资源交给ret2,而不是拷贝一份再给他
  • str只有它自己拥有,不存在什么析构两次的问题,所以直接给没问题
  • 交给ret2后,可以考虑将初始值给str,或者把ret2原先的值给他,然后str被成功析构
  • 皆大欢喜噜~
  • 所以,c++引入了移动语义,补上了左值引用的短板
  • 移动语义的核心就是通过使用右值引用移动构造函数实现资源的有效转移

右值

  • 右值表示对象的值,无法对它取地址,也不能赋值
  • 右值的生命周期很短,可能只在当前行存在
  • 它要么是字面常量,要么是表达式求值过程中/函数返回值时创建的临时对象

右值也分为纯右值和将亡值(将亡值实际上是针对自定义类型存在的)

纯右值
  • 是字面常量/表达式返回值/匿名的临时对象

  • (都是内置类型)

  • double x = 1.1, y = 2.2;//下面都是右值
    10;
    x + y;
    fmin(x, y);
将亡值
  • 指的是 -- 当该右值完成初始化或赋值的任务时,它的资源已经移动给了被初始化者或被赋值者,同时该右值也将会马上被销毁(也就是前面我们举的返回值问题的例子)
  • 而且,只有自定义类型有需要转移的资源(主要指的是需要开空间的资源,因为开空间上限很大,消耗大),而内置类型最大也就8字节,无所谓转不转移
  • 所以,将亡值是针对部分自定义类型的概念
右值引用

右值引用就是对右值的引用,给右值取别名

int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
  • 右值引用本身应该属于右值,但是,如果它是右值,后面我们的移动操作就无法实现,因为需要改变右值引用绑定的右值
  • 所以,编译器强制认为右值引用是左值,然后就可以通过右值引用为右值赋值
  • 右值原本不能取地址,但给右值取别名后,会导致右值被存储到特定位置,就可以取到该位置的地址,也就可以实现对右值赋值

当我们学习了右值/右值引用的概念了,看待很多函数的参数/返回值就可以换一种方式了 


move函数

介绍

  • 是 C++ 标准库中的一个函数模板,用于将左值转换为右值引用
  • 头文件<utility>
  • 所以右值引用也可以引用move后的左值
  • 但注意,move本身不对左值做修改
  • 一旦拿右值接收了move(左值),这个左值的资源就已经被转移了,不能再使用这个移后源对象

底层实现

这是g++下的move函数

参数 -- 通用引用类型

  • 可以看出来,它是用模板参数&&作为参数
  • T&&是通用引用的形式,它是同时具备左值引用和右值引用性质的引用类型
  • T 不仅可以是左值引用,也可以是右值引用,具体取决于传递给函数的参数的类型
  • 但是,为什么可以实现这样的功能呢?又为什么一定要写成T&&的形式呢?
  • 这就需要介绍一下引用折叠

引用折叠
  • 用于确定在涉及到引用的类型推断和引用性质时的行为
  • 引用折叠主要用于处理 通用引用 和 模板参数的类型推断
  • 折叠也就是有多个引用进行组合,而我们的引用类型有两种,所以有4种组合
  • 但我们实际见不到这样的引用类型,这些形式可以算是一种中间状态
  • 我们得到的引用折叠后的结果 -- 要么是左值引用,要么是右值引用
  • 折叠规则:
  • 如果任一引用为左值引用,则结果为左值引用;否则为右值引用
  • 比如:
  • 根据规则,只要双方有一方是左值引用,就为左值
  • 所以最后结果是:
  • 同时,这也符合我们的预期,不同类型的实参传进来,形参就是他们对应类型的引用

  • auto实际上也是模板T的一种形式,他们是等价的
  • auto && a = 1; //通用引用,可以接收右值
    int b = 1;
    auto && c = b;//通用引用,也可以接收左值

返回值

可以看到,它使用了remove_reference这个模板类

remove_reference

  • 从名字和代码就能看出来,它可以去除引用返回基本类型
  • 所以,这个返回值实际上是先去除掉参数的引用,然后转换为右值引用后返回
  • 所以,它的代码也可以被写成(以int为例):

以上几个函数的详细介绍来源于 -- https://avdancedu.com/a39d51f9/

讲的特别好,我哭死 

移动

引入

将亡值那里有说过,内置类型的拷贝都是小事情,但如果一旦涉及到资源的分配问题,拷贝的消耗可能会很大

对应的也就是类里的构造/赋值/插入函数,这些都是拷贝的重灾区

如果可以为这些成员函数引入移动操作,将大大提高效率 

介绍

在C++中,"移动" 是指将资源(如动态分配的内存、文件句柄、对象等)的所有权从一个对象转移到另一个对象,而不进行不必要的数据复制

移动构造函数 

介绍

  • 接受一个右值引用参数,用于实现资源的移动(因为只有右值的资源是可以被移动的,左值是不可以轻易拿资源的)
  • 在移动构造函数内部,资源的所有权被转移到新对象,同时原对象进入有效但未定义的状态,以确保原对象不再持有资源,防止原对象被析构后出现问题
  • void swap(myvector<T> &v)  //交换资源{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endOfStorage, v._endOfStorage);} 
    myvector(myvector<T> &&v) noexcept: _start(nullptr), _finish(nullptr), _endOfStorage(nullptr){cout << "移动构造" << endl;swap(v);}
    

是否抛出异常

  • 因为移动构造只是交换资源,而没有分配资源,那么一般移动操作是不会抛出异常的
  • 又因为标准库会为某个函数可能会抛出异常而做出处理
  • 并且防止真的出现异常,导致移动构造执行一半跳走了(可能在分配空间的过程中,也使用了移动操作),让源对象和新对象都处于中间状态,这样可能会导致很多问题

  • 为了避免这些问题,所以需要我们[显式告诉标准库,移动函数是没有问题的]
  • 这样就不会中止掉移动操作,也减少了标准库额外的操作
  • 而告诉标准库它不会产生异常,就需要一个关键字"noexcept"
noexcept

是C++11引入的关键字,用于指示函数是否会抛出异常

它用来标记和控制函数的异常行为,从而增强代码的可靠性和性能

使用
  • 可以附加在函数声明的尾部(声明和定义都要添加),表示该函数不会抛出异常:
  • 检查表达式是否会引发异常,返回一个 bool 值,指示表达式是否会引发异常:

应用场景

返回值 --  一次深拷贝->移动构造(编译器优化后)
bit::myvector<int> func_move()
{cout << "func_move" << endl;auto it = {1, 2, 3, 4};myvector<int> tmp(it);return tmp;
}
void test7()
{bit::myvector<int> s1(func_move());
}
  • 当该函数返回时,需要先深拷贝一个临时对象
  • 然后这个临时对象作为函数返回值(它是将亡值,当这个返回值完成拷贝工作后,就没了),调用移动构造初始化ret1
  • 但编译器做出了优化,跳过那个中间状态,直接对str进行移动构造,也就是直接将str识别成将亡值
  • 编译器将tmp(也就是图中的str)的资源直接给[接收返回值的对象],使tmp成为有效但未定义的状态,之后随着func_move的结束而销毁
  • (深拷贝是构造tmp时的),这样就是减少了一次深拷贝
传参 -- 传入右值 
list举例
    list<bit::mystring> s1;bit::mystring arr("1234");s1.push_back(arr);cout<<endl;s1.push_back("243");

这里使用库中的list作为例子(方便看),而string是我们自己模拟实现的

当我们直接传入右值时

  • 如果没有移动构造,那么就会在构造list的结点的时候进行一次深拷贝
  • 可以看到,无论传入左值还是右值,都是两次深拷贝:(第一次是字符串构造string,第二次是string构造list的结点)
  • 如果有移动构造,那传入右值时,那个[被右值构造出来的string临时对象]就会直接将资源交给list结点
  • 可以看到,传入右值时会减少一次深拷贝
vector举例
void test8(){vector<bit::mystring> s1;bit::mystring arr("1234");s1.push_back(arr);cout<<endl;s1.push_back("243");
}

但是,如果用vector的话,会在移动构造的下面,还会调用一次深拷贝:

为啥呢?按理说,移动资源后就应该没事了啊,怎么还会有一次深拷贝

让我们看看实际调用了哪些函数吧:

  • 构建出string临时对象后,转到vector的pushback函数,这里识别到了string是个右值,匹配进了接收右值的pushback:
  • (注意,这里把右值引用参数move了之后才传到下一个函数!!!因为前面有介绍,右值引用被编译器强制认为是左值,而这里为了延续它的右值属性,就得用move,否则根本调不到移动版本的构造)
  • 然后进入emplace_back函数,这里将传入的右值引用放进forward函数里面:
  • 这里我们可以猜测,他可能和move有类似的功能,否则为什么要对传进来的参数进行处理呢
  • 然后进了一堆函数(不再贴图了,太多了太多了)  
  • 最后在这里进入移动构造:
  • 然后后面又经过一系列操作,在和上面的很相似的函数中,却进入了拷贝构造:
  • (是传入的第一个模板参数不同导致的吗,不太懂,先把这个问题搁这吧,我一时半会也不会知道为啥)

反正我们可以从上面的例子中知道,右值引用可以让传参时减少深拷贝次数

总之,我们先看看里面函数调用时,出现的forward函数吧(次数频率可高,基本函数参数里都有它)

forward

介绍

是C++标准库中的一个函数模板,用于实现完美转发的关键工具

允许在函数模板中将参数以原始的值类别(左值或右值)传递给其他函数,而不会改变它们的值类别

底层

这是g++下,forward的实现情况

  • 它重载了两个函数,可以看到,都是用remove_reference去除掉引用属性后,分别让他们接收左值/左值引用 和 右值
  • 返回的是同一句代码,但实际效果却不同(因为使用了通用引用,而通用引用可以引用折叠)
原理
  • 第一个
  • 它接收左值/左值引用,T的类型都是T&,这样返回值处类型就是 T& && ,折叠后就是T&,和传进来的左值是一样的属性

  • 第二个
  • 接收右值,T的类型是T,那这里的返回值类型就不需要折叠,直接是T&&,而这里直接返回一个右值引用,编译器会将函数返回的右值引用认为是一个右值
  • 这样我们就得到了一个右值,那下一个函数得到的就是右值了,而不是左值(因为编译器强制将右值引用识别成左值)
  • 而forward正是为了解决这个问题

完美转发 

介绍
  • 完美转发(Perfect Forwarding)是C++中的一个重要概念,它允许函数将其参数以原始的值类别(左值或右值)和常量性 传递给其他函数,同时保持参数的特性
  • 完美转发通常用于泛型编程,特别是在模板和泛型函数中
  • 核心就是标准库中的forward函数和通用引用,以确保在参数传递过程中保持参数的原始性质

示例 

template <typename T>
void process(T&& arg) {// 使用std::forward确保完美转发参数some_function(std::forward<T>(arg));
}void some_function(int& x) {std::cout << "Lvalue reference: " << x << std::endl;
}void some_function(int&& x) {std::cout << "Rvalue reference: " << x << std::endl;
}int main() {int value = 42;process(value);       // 调用some_function(int&)process(123);         // 调用some_function(int&&)
}
改造我们的list
  • 还记得前面,我们用库中的vector配合着我们自己实现的string,验证了移动构造的好处吗
  • 我们可以从调用函数的参数发现,库中是将右值的属性一直保持着的,否则调不到移动版本的构造函数
  • 现在我们也试着用完美转发,实现库中的功能
代码
// List的节点类template <class T>struct ListNode // struct默认公有(因为不会有人去访问结点成员的){typedef ListNode<T> *PNode;// ListNode(const T &val)//     : _ppre(nullptr), _pnext(nullptr), _val(val){};// ListNode(T &&val)  //注意,这里有两个重载函数时,不能都有缺省值,所以这里不设置缺省值了//     : _ppre(nullptr), _pnext(nullptr), _val(forward<T>(val)){};// 我们也可以用通用引用,来实现不同类型的参数,调用不同的构造template <class Data>ListNode(Data &&val): _ppre(nullptr), _pnext(nullptr), _val(forward<Data>(val)){};PNode _ppre;PNode _pnext;T _val;};void push_back(const T &val){insert(end(), val);}void push_back(T &&val) // 移动构造,如果这里传入一个右值{insert(end(), std::forward<T>(val)); // 需要我们保持它的右值属性}void push_front(const T &val){insert(begin(), val);}void push_front(T &&val) // 移动构造,如果这里传入一个右值{insert(begin(), std::forward<T>(val)); // 需要我们保持它的右值属性}// 在pos位置前插入值为val的节点iterator insert(iterator pos, const T &val){PNode cur = pos._pNode;PNode pre = cur->_ppre;PNode newnode = new Node(val);newnode->_pnext = cur;pre->_pnext = newnode;cur->_ppre = newnode;newnode->_ppre = pre;_size++;return newnode;}iterator insert(iterator pos, T &&val) // 继承push函数给的右值{PNode cur = pos._pNode;PNode pre = cur->_ppre;PNode newnode = new Node(std::forward<T>(val)); // 要保持它的右值属性newnode->_pnext = cur;pre->_pnext = newnode;cur->_ppre = newnode;newnode->_ppre = pre;_size++;return newnode;}//不要忘了,这里把构造的缺省值去掉了,那头结点就得用默认构造初始化void CreateHead(){_pHead = new Node(T()); // 因为去掉了缺省值,所以这里给个默认构造_pHead->_pnext = _pHead;_pHead->_ppre = _pHead;_size = 0;}
测试
void test9()
{bit::mylist<bit::mystring> l;l.push_back("123");cout << endl;bit::mystring arr("q34");l.push_back(arr);
}


移动赋值函数 

       myvector<T> &operator=(myvector<T>&& v) noexcept{cout << "移动赋值" << endl;swap(v);return *this;}

和移动拷贝非常像,都是转移资源,也都需要加上noexcept关键字

使用

bit::myvector<int> func_move()
{cout << "func_move" << endl;auto it = { 1, 2, 3, 4 };bit::myvector<int> tmp(it); //深拷贝return tmp; 
}void test8() {bit::myvector<int> s2;s2 = func_move();
}

  • (这里因为s2的赋值和定义不在一行,所以没有优化)
  • 因为要返回tmp赋值给s2,所以要先创建临时变量作为函数返回值
  • 但和上面一样,编译器优化后,直接将tmp认为是将亡值,且有移动构造,所以使用移动构造构建临时对象
  • 然后将这个临时对象使用[=重载]赋值给s2
  • 而因为临时对象也是将亡值(匿名对象,且马上就要被销毁) ,且有移动赋值的存在,所以直接调用移动版本的,把临时对象的资源交给s2

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

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

相关文章

华为数通方向HCIP-DataCom H12-831题库(单选题:261-280)

第261题 某网络通过部署1S-IS实现全网与通,若在一台IS-IS路由器的某接口下配置命令isis timer holding multiplier 5 level-2,则以下关于该场景的描述,正确的是哪一项? A、该接口Level-2邻居保持时间为5秒 B、该接口Level-1邻居保持时间为30秒 C、该接口为点对点链路接口 …

柔性数组的使用及注意事项

1.柔性数组在结构体当中,并且在结构体的最后面. 2.结构体中除了柔型数组外至少还要有一个其他成员. 3.sizeof()返回结构体的大小不包含柔性数组的大小. 4.malloc 例:struct sdshdr16 *p malloc(sizeof (struct sdshdr16) 32); // 32 为柔性数组的大小 5.free 例: fre…

大语言模型在推荐系统的实践应用

本文从应用视角出发&#xff0c;尝试把大语言模型中的一些长处放在推荐系统中。 01 背景和问题 传统的推荐模型网络参数效果较小(不包括embedding参数)&#xff0c;训练和推理的时间、空间开销较小&#xff0c;也能充分利用用户-物品的协同信号。但是它的缺陷是只能利用数据…

问题记录2 域名解析问题

上线部署时遇到内网域名解析问题&#xff1a; 内网域名为xxx.cn&#xff0c;在ip为yyy的服务器上&#xff0c;ping&#xff1a;xxx.cn 首先在服务器&#xff1a;yyy /etc/hosts查找缓存记录 cat /etc/hosts 127.0.0.1 VM-4-2-centos VM-4-2-centos 127.0.0.1 localhost.local…

使用UniApp实现视频数组自动下载与播放功能:一步步指导

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

类加载的过程总结以及双亲委派模型[JVM]

类加载过程 类一共有七个生命周期:加载->验证->准备->解析->初始化->使用->卸载 加载&#xff08;加载字节码文件&#xff0c;生成.class对象&#xff09; 加载是类加载的第一个阶段。 加载阶段的任务是在类文件从磁盘加载到内存中&#xff0c;通常是从cl…

Aroid问题笔记 - ViewPager嵌套RecyclerView,降低ViewPager灵敏度

点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&…

【网络协议】聊聊DHCP和PXE 工作原理

DHCP 动态主机配置协议 对于每个主机来说&#xff0c;只要连接了网络&#xff0c;那么就会配置一个IP地址&#xff0c;那么这个IP地址&#xff0c;如果是手动配置的话&#xff0c;对于公司内部的人员来说都要找IT进行配置&#xff0c;这个太浪费人力物力了&#xff0c;所以解决…

React18入门(第四篇)——React中的4种CSS使用方式,CSS Module、CSS-in-Js详解

文章目录 一、普通方式使用CSS1.1 元素内联 style1.2 引入 CSS 文件1.3 类名插件 -- Classnames1.4 注意事项 二、CSS Module2.1 普通 CSS 的问题2.2 CSS Module 的特点2.3 简单使用 三、使用 sass3.1 sass 简介3.2 使用 四、CSS-in-JS4.1 CSS-in-JS 简介4.2 CSS-in-JS 常用工具…

【JVM】对象内存布局

对象内存布局 文章目录 对象内存布局1. 对象的内存布局2. 对象标记(Mark Word)3. 类元信息(类型指针)4. 实例数据和对象填充 1. 对象的内存布局 在Hotspot虚拟机里&#xff0c;对象在堆内存中的存储布局可以划分为三个部分&#xff1a;对象头(Header)、实例数据(Instance Data…

SpringBoot面试题5:SpringBoot Starter的工作原理是什么?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:SpringBoot Starter的工作原理是什么? Spring Boot Starter 是一种便捷的方式来为 Spring Boot 应用程序引入一组特定功能的依赖项。它简化了项目…

Kotlin注释

一、设置注释样式 按需配置 二、单行多行注释 fun main() {// 单行注释println("单行注释") //单行注释/** 多行注释* */println("多行注释") }

ELK + Filebeat 分布式日志管理平台部署

ELK Filebeat 分布式日志管理平台部署 1、前言1.1日志分析的作用1.2需要收集的日志1.3完整日志系统的基本特征 2、ELK概述2.1ELK简介2.2为什么要用ELK?2.3ELK的组件 3、ELK组件详解3.1Logstash3.1.1简介3.1.2Logstash命令常用选项3.1.3Logstash 的输入和输出流3.1.4Logstash配…

密码学三 btc 钱包 节点 挖矿 51%攻击 双花攻击

03-BTC-数据结构_哔哩哔哩_bilibili 哈希指针并解释 比特币的每个区块都包含一个区块头和区块体两部分。 在区块头中,有一个字段是用于存储前一个区块的哈希值,我们把这个存储前一个区块哈希值的字段称为“哈希指针”。 这个哈希指针的作用是将本区块指向前一个区块,连接起整…

CentOS有IP地址,连接不上Xshell或使用Xshell时突然断开

问题原因&#xff1a;未在电脑主机的网络中进行IP地址配置 解决办法&#xff1a; 1.打开控制面板&#xff0c;选择‘网络与共享中心’ 2.选择“更改适配器设置” 3.右键点击以太网3“属性” 4.选择协议版本4&#xff0c;点击属性 5.IP地址填写CentOS的IP地址&#xff1a;192.…

Epoch、批量大小、迭代次数

梯度下降 它是 机器学习中使用的迭代 优化算法&#xff0c;用于找到最佳结果&#xff08;曲线的最小值&#xff09;。 坡度 是指 斜坡的倾斜度或倾斜度 梯度下降有一个称为 学习率的参数。 正如您在上图&#xff08;左&#xff09;中看到的&#xff0c;最初步长较大&#…

SpringBoot基础详解

目录 SpringBoot自动配置 基于条件的自动配置 调整自动配置的顺序 纷杂的SpringBoot Starter 手写简单spring-boot-starter示例 SpringBoot自动配置 用一句话说自动配置&#xff1a;EnableAutoConfiguration借助SpringFactoriesLoader将标准了Configuration的JavaConfig类…

微信小程序中如何使用fontawesome6的免费图标

一、官网下载fontawesome6 Download Font Awesome Free or Pro | Font Awesome 二、使用transfer编码成Base64 transfer打开官网&#xff1a;Online font-face generator — Transfonter 首先先把刚刚下载的fontawesome6解压&#xff0c;将文件夹中的字体上传&#xff08;点…

java入参为对象的(非基本数据类型int/float等)修改属性会影响原始对象

ApiOperation("登录接口")RequestMapping(value "/login", method RequestMethod.POST)public Result<JSONObject> login(RequestBody SysLoginModel sysLoginModel){Result<JSONObject> result new Result<JSONObject>();// by wang…

禁用和开启笔记本电脑的键盘功能,最快的方式

笔记本键盘通常较小&#xff0c;按键很不方便&#xff0c;当我们外接了键盘时就不需要再使用自带的键盘了&#xff0c;而且午睡的时候&#xff0c;总是担心碰到笔记本的键盘&#xff0c;可能会删掉我们的代码什么的&#xff0c;所以就想着怎么禁用掉&#xff0c;下面是操作步骤…