当前位置: 首页 > news >正文

C++11新特性② | 左值、左值引用、右值与右值引用

目录

1、引言

2、值类别及相关概念

3、左值、右值

4、左值引用、右值引用

5、移动语义

5.1、为什么需要移动语义

5.2、移动语义定义

5.3、转移构造函数

5.4、转移赋值函数

6、标准库函数 std::move

7、完美转发 std::forward


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html       C++11新特性很重要,作为C++开发人员很有必要去学习,不仅笔试面试时会涉及到,开源代码中会大规模的使用。以很多视频会议及直播软件都在使用的开源WebRTC项目为例,WebRTC代码中大篇幅地使用了C++11及以上的新特性,要读懂其源码,必须要了解这些C++的新特性。所以,接下来一段时间我将结合工作实践,给大家详细讲解一下C++11的新特性,以供借鉴或参考。

1、引言

       C++11引入了对象移动的概念,是一种移动而非拷贝对象的能力,移动对象可以有效地提高程序的性能。

       为了支持移动操作,C++11引入了一种新的引用类型 - 右值引用(rvalue references)。所谓右值引用,是必须要绑定到右值的引用,通过&&操作符获得右值引用。今天就来详细讲讲左值、左值引用、右值、右值引用相关的内容。

2、值类别及相关概念

       在C++11中,值类别主要分为左值、左值引用、右值与右值引用。左值引用是绑定到左值的应用,右值引用则是绑定到右值的引用。当右值引用T&&出现在模板函数的参数中,T是模板类型,T&&则是万能引用。万能引用可以接收左值参数,也可以接收右值参数。然后在推导函数参数类型时,可能会发生引用折叠。还会涉及到标准库函数std::move和std::forward。

3、左值、右值

       在C语言中,我们常常会提起左值(lvalue)、右值(rvalue)这样的称呼。一个最为典型的判别方法就是,在赋值表达式中,出现在等号左边的就是“左值”,而在等号右边的,则称为“右值”。如:

int b = 1;
int c = 2;
int a = a + b;

在这个赋值表达式中,a就是一个左值,而b + c则是一个右值。

       不过C++中还有一个被广泛认同的说法,那就是可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值。那么这个加法赋值表达式中,&a是允许的操作,但&(b + c)这样的操作则不会通过编译。因此a是一个左值,(b + c)是一个右值。相对于左值,右值表示字面常量、表达式、函数的非引用返回值等。

       这里总结一下,什么是左值:

1)能被赋值(充分非必要条件),左值不一定能被赋值,比如const变量,智能在初始化时赋初值,后续不能赋值
2)能取址(充分非必要条件),左值不一定能取址,比如C语言中的register变量:register int i= 3,C++11已经取消了对register的支持,编译时会忽略。再比如C语言中的位域变量是不能取址的:
struct St{ int m:3;} St st; st.m = 3; 指定int型成员m占3个字节。
3)可以初始化左值引用(必要不充分条件),左值可以初始化左值引用,比如:int m = 3; int& n = m;,右值不能初始化左值引用,比如:const int& m = 3; 因为3是右值,不能初始化左值引用,所以编译会报错。
4)字面量属于纯右值,比如1,2,3等立即数就属于字面量,注意,常量字符串“xyz”是左值,可以对该字符串进行取址,即&"xyz"。
5)将亡值可以偷其中的资源。函数的返回值,则是纯右值,返回右值引用,比如std::move函数。

4、左值引用、右值引用

       左值引用是对一个左值进行引用的类型,右值引用则是对一个右值进行引用的类型。左值引用和右值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。

        左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。

        左值引用示例:

int &a = 2;       // 左值引用绑定到右值,编译失败, err
int b = 2;        // 非常量左值
const int &c = b; // 常量左值引用绑定到非常量左值,编译通过, ok
const int d = 2;  // 常量左值
const int &e = c; // 常量左值引用绑定到常量左值,编译通过, ok
const int &b = 2; // 常量左值引用绑定到右值,编程通过, ok

“const 类型 &”为 “万能”的引用类型,它可以接受非常量左值、常量左值、右值对其进行初始化;

       右值引用,使用&&表示:

int && r1 = 22;
int x = 5;
int y = 8;
int && r2 = x + y;
T && a = ReturnRvalue();

       通常情况下,右值引用是不能够绑定到任何的左值的:

int c;
int && d = c; //err

       下面看一个测试示例:

void process_value(int & i) //参数为左值引用
{cout << "LValue processed: " << i << endl;
}void process_value(int && i) //参数为右值引用
{cout << "RValue processed: " << i << endl;
}int main()
{int a = 0;process_value(a); //LValue processed: 0process_value(1); //RValue processed: 1return 0;
}

5、移动语义

5.1、为什么需要移动语义

       右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。

       转移语义是和拷贝语义相对的,可以类比文件的剪切与拷贝,当我们将文件从一个目录拷贝到另一个目录时,速度比剪切慢很多。通过转移语义,临时对象中的资源能够转移其它的对象里。

5.2、移动语义定义

       在现有的 C++ 机制中,我们可以定义拷贝构造函数和赋值函数。要实现转移语义,需要定义转移构造函数,还可以定义转移赋值操作符。对于右值的拷贝和赋值会调用转移构造函数和转移赋值操作符。

       如果转移构造函数和转移拷贝操作符没有定义,那么就遵循现有的机制,拷贝构造函数和赋值操作符会被调用。普通的函数和操作符也可以利用右值引用操作符实现转移语义。

5.3、转移构造函数

       先来看个转移构造函数的实例:

class MyString
{
public:MyString(const char *tmp = "abc"){//普通构造函数len = strlen(tmp);  //长度str = new char[len+1]; //堆区申请空间strcpy(str, tmp); //拷贝内容cout << "普通构造函数 str = " << str << endl;}MyString(const MyString &tmp){//拷贝构造函数len = tmp.len;str = new char[len + 1];strcpy(str, tmp.str);cout << "拷贝构造函数 tmp.str = " << tmp.str << endl;}//移动构造函数//参数是非const的右值引用MyString(MyString && t){str = t.str; //拷贝地址,没有重新申请内存len = t.len;//原来指针置空t.str = NULL;cout << "移动构造函数" << endl;}MyString &operator= (const MyString &tmp){//赋值运算符重载函数if(&tmp == this){return *this;}//先释放原来的内存len = 0;delete []str;//重新申请内容len = tmp.len;str = new char[len + 1];strcpy(str, tmp.str);cout << "赋值运算符重载函数 tmp.str = " << tmp.str << endl;return *this;}~MyString(){//析构函数cout << "析构函数: ";if(str != NULL){cout << "已操作delete, str =  " << str;delete []str;str = NULL;len = 0;}cout << endl;}private:char *str = NULL;int len = 0;
};MyString func() //返回普通对象,不是引用
{MyString obj("mike");return obj;
}int main()
{MyString &&tmp = func(); //右值引用接收return 0;
}


和拷贝构造函数类似,有几点需要注意:

1)参数(右值)的符号必须是右值引用符号,即“&&”。
2)参数(右值)不可以是常量,因为我们需要修改右值。
3)参数(右值)的资源链接和标记必须修改,否则,右值的析构函数就会释放资源,转移到新对象的资源也就无效了。

       有了右值引用和转移语义,我们在设计和实现类时,对于需要动态申请大量资源的类,应该设计转移构造函数和转移赋值函数,以提高应用程序的效率。 

5.4、转移赋值函数

         直接看转移赋值函数的实例:

class MyString
{
public:MyString(const char *tmp = "abc"){//普通构造函数len = strlen(tmp);  //长度str = new char[len+1]; //堆区申请空间strcpy(str, tmp); //拷贝内容cout << "普通构造函数 str = " << str << endl;}MyString(const MyString &tmp){//拷贝构造函数len = tmp.len;str = new char[len + 1];strcpy(str, tmp.str);cout << "拷贝构造函数 tmp.str = " << tmp.str << endl;}//移动构造函数//参数是非const的右值引用MyString(MyString && t){str = t.str; //拷贝地址,没有重新申请内存len = t.len;//原来指针置空t.str = NULL;cout << "移动构造函数" << endl;}MyString &operator= (const MyString &tmp){//赋值运算符重载函数if(&tmp == this){return *this;}//先释放原来的内存len = 0;delete []str;//重新申请内容len = tmp.len;str = new char[len + 1];strcpy(str, tmp.str);cout << "赋值运算符重载函数 tmp.str = " << tmp.str << endl;return *this;}//移动赋值函数//参数为非const的右值引用MyString &operator=(MyString &&tmp){if(&tmp == this){return *this;}//先释放原来的内存len = 0;delete []str;//无需重新申请堆区空间len = tmp.len;str = tmp.str; //地址赋值tmp.str = NULL;cout << "移动赋值函数\n";return *this;}~MyString(){//析构函数cout << "析构函数: ";if(str != NULL){cout << "已操作delete, str =  " << str;delete []str;str = NULL;len = 0;}cout << endl;}private:char *str = NULL;int len = 0;
};MyString func() //返回普通对象,不是引用
{MyString obj("mike");return obj;
}int main()
{MyString tmp("abc"); //实例化一个对象tmp = func();return 0;
}

6、标准库函数 std::move

       编译器只对右值引用才能调用转移构造函数和转移赋值函数,而所有命名对象都只能是左值引用,如果已知一个命名对象不再被使用而想对它调用转移构造函数和转移赋值函数,也就是把一个左值引用当做右值引用来使用,怎么做呢?标准库提供了函数 std::move,这个函数以非常简单的方式将左值引用转换为右值引用。

int a;
int &&r1 = a;              // 编译失败
int &&r2 = std::move(a);      // 编译通过

7、完美转发 std::forward

        完美转发适用于这样的场景:需要将一组参数原封不动的传递给另一个函数。“原封不动”不仅仅是参数的值不变,在 C++ 中,除了参数值之外,还有一下两组属性:左值/右值和 const/non-const。完美转发就是在参数传递过程中,所有这些属性和参数值都不能改变,同时,而不产生额外的开销,就好像转发者不存在一样。在泛型函数中,这样的需求非常普遍。

       下面举例说明:

#include <iostream>
using namespace std;template <typename T> void process_value(T & val)
{cout << "T &" << endl;
}template <typename T> void process_value(const T & val)
{cout << "const T &" << endl;
}
//函数 forward_value 是一个泛型函数,它将一个参数传递给另一个函数 process_value
template <typename T> void forward_value(const T& val)
{process_value(val);
}template <typename T> void forward_value(T& val)
{process_value(val);
}int main()
{int a = 0;const int &b = 1;//函数 forward_value 为每一个参数必须重载两种类型,T& 和 const T&forward_value(a); // T&forward_value(b); // const T &forward_value(2); // const T&return 0;
}

        对于一个参数就要重载两次,也就是函数重载的次数和参数的个数是一个正比的关系。这个函数的定义次数对于程序员来说,是非常低效的。

        那C++11是如何解决完美转发的问题的呢?实际上,C++11是通过引入一条所谓“引用折叠”(reference collapsing)的新语言规则,并结合新的模板推导规则来完成完美转发。

typedef const int T;
typedef T & TR;
TR &v = 1; //在C++11中,一旦出现了这样的表达式,就会发生引用折叠,即将复杂的未知表达式折叠为已知的简单表达式

        C++11中的引用折叠规则:

TR的类型定义

声明v的类型

v的实际类型

T &

TR

T &

T &

TR &

T &

T &

TR &&

T &

T &&

TR

T &&

T &&

TR &

T &

T &&

TR &&

T &&

注意,一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用。       C++11中,std::forward可以保存参数的左值或右值特性:

#include <iostream>
using namespace std;template <typename T> void process_value(T & val)
{cout << "T &" << endl;
}template <typename T> void process_value(T && val)
{cout << "T &&" << endl;
}template <typename T> void process_value(const T & val)
{cout << "const T &" << endl;
}template <typename T> void process_value(const T && val)
{cout << "const T &&" << endl;
}//函数 forward_value 是一个泛型函数,它将一个参数传递给另一个函数 process_value
template <typename T> void forward_value(T && val) //参数为右值引用
{process_value( std::forward<T>(val) );//C++11中,std::forward可以保存参数的左值或右值特性
}int main()
{int a = 0;const int &b = 1;forward_value(a); // T &forward_value(b); // const T &forward_value(2); // T &&forward_value( std::move(b) ); // const T &&return 0;
}

相关文章:

C++11新特性② | 左值、左值引用、右值与右值引用

目录 1、引言 2、值类别及相关概念 3、左值、右值 4、左值引用、右值引用 5、移动语义 5.1、为什么需要移动语义 5.2、移动语义定义 5.3、转移构造函数 5.4、转移赋值函数 6、标准库函数 std::move 7、完美转发 std::forward VC常用功能开发汇总&#xff08;专栏文章…...

Python Opencv实践 - Harris角点检测

参考资料&#xff1a;https://blog.csdn.net/wsp_1138886114/article/details/90415190 import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/chinease_tower.jpg", cv.IMREAD_COLOR) plt.imshow(img[:,:,::-1])#…...

el-upload上传图片到七牛云或阿里云

&#xff08;1&#xff09;绑定上传地址&#xff0c;上传数据对象 <el-upload class"upload-demo" :action"uploadUrl" :data"uploadData":on-success"handleSuccess" :file-list"[]" :show-file-list"false"…...

Web jQuery—选择器、样式和效果

jQuery 选择器、样式和效果 代码下载 jQuery 介绍 JavaScript库&#xff1a;即 library&#xff0c;是一个封装好的特定的集合&#xff08;方法和函数&#xff09;。从封装一大堆函数的角度理解库&#xff0c;就是在这个库中&#xff0c;封装了很多预先定义好的函数在里面&a…...

Java和Kotlin的Field在继承中的不同表现

Kotlin是一个宣称与Java兼容性较好的语言&#xff0c;但在接触后发现一些技术还是有“概念上”的冲突&#xff0c;本文就记录下两者对象的Field&#xff08;中文的说法有字段、域、属性、成员变量&#xff0c;下文若出现这些表达&#xff0c;指的都是这个东西&#xff09;在继承…...

MySQL 子查询

文章目录 1.简介2.优势3.分类3.1 标量子查询3.2 行子查询3.3 列子查询IN 操作符ALL 操作符ANY/SOME 操作符 3.4 表子查询 4.关联子查询5.EXISTS 和 NOT EXISTS6.横向派生表7.附录参考文献 1.简介 子查询是另一个语句中的 SELECT 语句。 子查询也称为内查询&#xff08;Inner …...

Ubuntu离线或在线安装CMake

首先下载适用于Ubuntu的CMake安装包&#xff0c;可以去官网下载&#xff0c;也可以通过下面的命令下载&#xff08;需要联网&#xff09;&#xff1a; wget https://cmake.org/files/v3.22/cmake-3.22.1.tar.gz将下载的安装包进行解压&#xff1a; tar -xvzf cmake-3.22.1.ta…...

后端面试话术集锦第 十七 篇:MySQL面试话术

这是后端面试集锦第十七篇博文——MySQL面试话术❗❗❗ 1. 解释一下单列索引和联合索引 单列索引是指在表的某一列上创建索引。 联合索引是在多个列上联合创建索引。 单列索引可以出现在where条件的任何位置,而联合索引需要按照一定的顺序来写。在多条件查询的时候,联合索引…...

< 文件资源管理器 > 和 < 此电脑 > 有什么区别?

“文件资源管理器”和 “此电脑” 的区别 1. 文件和文件夹管理&#xff1a;2. 访问存储设备&#xff1a;3. 搜索功能&#xff1a;4. 视图和排序选项&#xff1a;5. 快速访问&#xff1a; 主要的区别1. 界面和用途&#xff1a;2. 显示内容&#xff1a;3. 导航&#xff1a; 在Win…...

线上问诊:可视化展示

系列文章目录 线上问诊&#xff1a;业务数据采集 线上问诊&#xff1a;数仓数据同步 线上问诊&#xff1a;数仓开发(一) 线上问诊&#xff1a;数仓开发(二) 线上问诊&#xff1a;数仓开发(三) 线上问诊&#xff1a;可视化展示 文章目录 系列文章目录前言一、全流程调度1.生产新…...

如何选择合适的HTTP代理服务器

HTTP代理服务器是一种常见的网络代理方式&#xff0c;它可以帮助用户隐藏自己的IP地址&#xff0c;保护个人隐私和安全。然而&#xff0c;选择合适的HTTP代理服务器并不容易&#xff0c;需要考虑多个因素。本文将介绍如何选择合适的HTTP代理服务器。 了解代理服务器的类型 HTT…...

Car Window Control Reset

大众汽车窗口自动升降失效&#xff0c;重置&#xff1a; 扣住5秒&#xff0c;重启汽车&#xff0c;试一下车钥匙&#xff0c;和再重试这个按钮&#xff0c;扣一下试一试...

序列号序列号

主板序列号 string str;str bios.GetSystemSerialNumber(); //wentai//str1 bios.GetSystemECSerialNumber();//CLogHelp::ITCLog(str1);LocalSN str.c_str();str bios.GetSystemVersion();LocalMode str.c_str();string str1;str1 bios.GetSystemSerialNumber();CLogHe…...

SSM(Spring-Mybatis-SpringMVC)

文章目录 1. 介绍1.1 概念介绍 2 SSM整合框架3. SSM功能模块开发4 测试4.1 业务层接口测试4.2 表现层接口测试 5.优化 -表现层数据封装6.异常处理 1. 介绍 1.1 概念介绍 SSM项目是指基于SpringSpringMVCMyBatis框架搭建的Java Web项目。 Spring是负责管理和组织项目的IOC容器和…...

MySQL——数据的删除以及MySQL中的约束

删除数据 删除表中的一行数据&#xff0c;也必须加上 WHERE条件&#xff0c;否则整列的数据都会被删除。删除语句&#xff1a; delete from 表名 where 条件; 他会将所有的符合条件的数据删除&#xff0c;如果不写条件&#xff0c;则表中的数据全部删除&#xff1a; 如果不添…...

静态路由 网络实验

静态路由 网络实验 拓扑图初步配置R1 ip 配置R2 ip 配置R3 ip 配置查看当前的路由表信息查看路由表信息配置静态路由测试 拓扑图 需求&#xff1a;实现 ip 192.168.1.1 到 192.168.2.1 的通信。 初步配置 R1 ip 配置 system-view sysname R1 undo info-center enable # 忽略…...

数据结构-01 数据结构基本概念,算法时间复杂度,空间复杂度

0 数据结构概述 四门课的关系 1 绪论 数据对象、数据元素、数据项关系 1.1 数据结构的基本概念 1.2 算法和算法评价 小练习 空间复杂度中的递归调用 n只是传入 n也是数组&#xff0c;计算存储数组flag的空间大小...

【Redis】Bitmap 使用及应用场景

前言&#xff1a;bitmap 占用空间小&#xff0c;查询效率高&#xff0c;在一些场景中使用 bitmap 是一个很好的选择。 一、bitmap 相关命令 SETBIT - 设置指定位置的比特值&#xff0c;可以设为 1 或 0 例如 SETBIT key 10 1&#xff0c;将在 key 对应的 bitmap 中第10位设置为…...

C#,数值计算——用于积分函数与方法的Stiel类的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { public class Stiel { public class pp : UniVarRealValueFun, RealValueFun { public Stiel st { get; set; } null; public pp() { } public doubl…...

报错:为什么数组明明有内容但打印的length是0

文章目录 一、问题二、分析三、解决1.将异步改为同步2.设置延迟 一、问题 在日常开发中&#xff0c;for 循环遍历调用接口&#xff0c;并将接口返回的值进行拼接&#xff0c;即push到一个新的数组中&#xff0c;但是在for循环内部是可以拿到这个新的数组&#xff0c;而for循环…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...

关于easyexcel动态下拉选问题处理

前些日子突然碰到一个问题&#xff0c;说是客户的导入文件模版想支持部分导入内容的下拉选&#xff0c;于是我就找了easyexcel官网寻找解决方案&#xff0c;并没有找到合适的方案&#xff0c;没办法只能自己动手并分享出来&#xff0c;针对Java生成Excel下拉菜单时因选项过多导…...