【C++学习】C++11新特性(第二节)—— 右值引用与移动语义超详解

在这里插入图片描述

文章目录

  • 文章简介
  • 二.右值引用
      • 1.什么是左值,什么是右值?什么是左值引用,什么是右值引用?
      • 2.左值引用与右值引用比较
  • 三.右值引用使用场景和意义
      • 1.左值引用的使用场景:
      • 2.左值引用的短板:
      • 3.右值引用与移动构造
      • 4.有了右值引用STL库的变化
      • 5.模板中的&& 万能引用
      • 6.完美转发
      • 7.新的类功能
        • 1.默认成员函数
        • 2.强制生成默认函数的关键字default:
        • 3.禁止生成默认函数的关键字delete:

文章简介

本篇文章续上篇文章C++11特性的讲解,这篇主要讲解C++11的新特性:右值引用与移动语义。
本篇文章会涵盖: 左值与右值的区别,怎么去区分左值与右值。右值引用的概念,为什么要增加右值引用,右值引用的作用,解决了什么问题, 右值引用与左值引用的比较,右值引用的使用场景,右值引用的注意事项,完美转发的使用,C++11后容器新增的两个默认函数等等

二.右值引用

1.什么是左值,什么是右值?什么是左值引用,什么是右值引用?

  1. 左值:左值就是那些可以出现在赋值符号左边的东西,它标识了一个可以存储结果值的地点。例如:一些变量名或解引用的指针 ,左值可以取地址的,一般可以对它赋值,一般可以修改(除const 修饰),可以出现在赋值运算符的左边,右值不能出现在赋值运算符的左边。左值引用就是对左值的引用,给左值取别名。

程序在编译时,编译器会为每个变量分配一个地址(左值),这个地址在编译是即可知。

int& func(int& x)       //func函数的返回值也是左值
{                       //因为func函数返回的是x,x是其他变量的别名,当func函数结束时,x没有被销毁,是有地址的
	x*=3;
	return x;
}

int main()
{
	//一些左值的例子
	int _a = 10;  
	int* p = &_a;
	const int _b = 10;
	//_b = 20;   //不能修改
	//他们都是可以取地址的。

	//左值引用
	int& pa = _a;
	int*& pp = p;
	const int& pb = _b;
	int& ret = func(_a);
	return 0;
}
  1. 右值:右值就是那些可以出现在赋值符号右边的东西,它必须具有一个特定的值。
    右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。

与左值相反,变量中存储的那个值(右值),只有在运行时才可知,且只有要用到变量中存储的值时,编译器才会发出指令从指定的地址读入变量的值,并将它存于寄存器中。也就是说,右值就是一个数字或一个字面值或一个常量或一个式子等,它并不标识任何位置。

int func()     //传值返回,函数结束后,局部变量a就销毁了,就没有地址
{              //所以是右值
	int a = 10;
	return a;
}
int main()
{
	int x = 10;
	int y = 10;

	10;      //字面量
	x + y;   //式子
	func();  //返回值是右值

	//右值引用
	int&& _a = 10;
	int&& _x = func();    
	int&& add = x + y; 

	return 0;
}

注意:右值是不能取地址的,如字面量10是没有地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说_a是可以取地址的。

总结:
左值可以取地址,右值不能取地址。不能以是否能修改判断是左值还是右值,因为左值加了const也不能被修改。

2.左值引用与右值引用比较

左值引用总结:

  1. 左值引用一般只能引用左值,不能引用右值。
  2. 但是const左值引用既可引用左值,也可引用右值。
int main()
{
	// 左值引用只能引用左值,不能引用右值。
	int a = 10;
	int& ra1 = a;
	// ra为a的别名
	//int& ra2 = 10;   // 编译失败,因为10是右值
	
	// const左值引用既可引用左值,也可引用右值。
	const int& ra3 = 10;
	const int& ra4 = a;

	return 0;
}

右值引用总结:

  1. 右值引用一般只能引用右值,不能引用左值。
  2. 但是右值引用可以move以后的左值。

关键字move
move可以将右值转为左值,但是不能将左值转为右值。move(x)不是将x变为右值,是move()后的返回值是左值,x本身属性没有改变。

int main()
{
 // 右值引用只能右值,不能引用左值。
 int&& r1 = 10;
 
 // error C2440: “初始化”: 无法从“int”转换为“int &&”
 // message : 无法将左值绑定到右值引用
 int a = 10;
 int&& r2 = a;
 
 // 右值引用可以用move,move以后右值可以变为左值
 int&& r3 = std::move(a);
 return 0;
}

三.右值引用使用场景和意义

前面所学了左值引用,左值引用解决了传值传参:利用左值引用会减少在传值传参要进行拷贝的问题。函数返回值的问题(函数结束该变量(对象)没有别销毁可以用左值引用返回,也可以减少拷贝)

1.左值引用的使用场景:

做参数和做返回值都可以提高效率。

void func1(map<string, string> dict1)
{}
void func2(const map<string,string>& dict2)
{}

int main()
{
	map<string, string> dict;   //假设dict很大
	func1(dict);    //会调用map的拷贝构造,用dict构造dict1
	func2(dict);    //dict2直接是dict的别名,不用拷贝构造

    // string operator+=(char ch) 传值返回存在深拷贝
    // string& operator+=(char ch) 传左值引用没有拷贝提高了效率
    s1 += '!';
    
	return 0;
}

2.左值引用的短板:

但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。传值返回会导致2次拷贝构造,如果编译器优化了,就是一个拷贝构造。

例如:

namespace XX
{
	class string
	{
	public:
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			cout << "构造" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}
		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "拷贝构造" << endl;
			string tmp(s._str);
			swap(tmp);
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "赋值拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; 
	};
}

XX::string func(const XX::string& s)
{
	XX::string str(s);

	return str;
}
int main()
{
	XX::string s1("hello");
	XX::string ret1 = func(s1);
	
	return 0;
}

如图:
在这里插入图片描述

解析:
上面示例代码中,func()函数的返回值是一个右值,会用这个值去构造ret1,因为XX::里面string类的拷贝构造的参数是const string& ,是一个const 左值引用,所以不仅可以引用左值,也可以引用右值。这里就会拷贝构造,是一个深拷贝。

3.右值引用与移动构造

上面的场景,有没有可以不用拷贝就能解决的办法呢?

可以用右值引用和移动语义解决上述问题:
在XX::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。

// 拷贝构造
string(const string& s)
	:_str(nullptr)
{
	cout << "拷贝构造" << endl;
	string tmp(s._str);
	swap(tmp);
}
//移动构造
string(string&& s)
	:_str(nullptr)
{
	cout << "移动构造" << endl;   //打印只是方便观察
	swap(s);
}

移动构造原理:

先用str去拷贝构造一个临时对象(右值),用临时对象又去拷贝构造s1,拷贝完成后,临时对象再被销毁。
移动构造就是利用了临时对象反正就会被销毁的特点,直接将s1与临时对象交换,然后临时对象再把s1析构掉。
所以重载了一个构造函数(移动构造),移动构造的参数为右值引用,所以临时对象构造s1的时候,编译器会走更匹配的移动构造,而不会走拷贝构造。

如下图解:

编译器不优化前: 需要做一次拷贝。
在这里插入图片描述
编译器优化后: 不用拷贝构造
在这里插入图片描述

编译器优化后可以理解为:返回值str本身是一个左值,经过编译器处理后,可以猜测编译器可能将它move变成了右值,然后根据优先匹配更合适的原则,直接走移动构造,减少拷贝。

除了有移动拷贝,还有移动赋值

// 赋值重载
string& operator=(const string& s)
{
	cout << "赋值拷贝" << endl;
	string tmp(s);
	swap(tmp);
	return *this;
}
// 移动赋值
string& operator=(string&& s)
{
	cout << " 移动赋值" << endl;
	swap(s);
	return *this;
}

当我们将上面的代码改成两个语句时:

XX::string func(const XX::string& s)
{
	XX::string str(s);

	return str;
}
int main()
{
	XX::string s1("hello");
	XX::string ret1 ;
	ret1 = func(s1);
	
	return 0;
}

这里编译器就不会优化了,只有是同一条语句连续构造或赋值时,编译器才会优化处理。
这里运行后,我们看到调用了一次移动构造和一次移动赋值。因为这里是用一个已经存在的对象接收。
XX::func() 函数中会先用str构造生成一个临时对象,但是我们可以看到,编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后在把这个临时对象做为XX::func()函数调用的返回值赋值给ret1,这里调用的移动赋值。

4.有了右值引用STL库的变化

STL中的容器都是增加了移动构造和移动赋值:列举几个常见的容器:
在这里插入图片描述

void push_back (value_type&& val);
int main()
{
 list<bit::string> lt;
 bit::string s1("1111");
// 这里调用的是拷贝构造
 lt.push_back(s1);
 
// 下面调用都是移动构造
 lt.push_back("2222");
 lt.push_back(std::move(s1));
 return 0;
}

5.模板中的&& 万能引用

按我们的理解,根据编译器匹配原则,如果要写一个函数的参数既可以接受左值,也可以接受右值的话,就得写4个重载。但实际上不用,只需要写一个即可。

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; }

但实际上不需要,只需要写一个即可,如下:

void Fun(T&& x)     //这一个可以推演出上面的4个

解析:
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,也就是一个右值,被引用后属性就变为了左值我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发。
注意:也就是右值被右值引用过后,该引用就变成了左值属性。

int main()
{
	int&& x = 10; //右值被右值引用过后,该引用就变成了左值属性
	x++;   //可以修改
	cout << &x << endl;  //可以取地址

	const int&& y = 10;   
	//y++;        //加了const后,这个引用就不能被修改了
	cout << &y << endl;  //可以取地址

	//右值引用的底层是一个指针,实际上是将10拷贝到一个区域(相当于在栈上开了一个临时空间把10存起来)
	//然后右值引用就是指向了这块空间

	return 0;
}

右值引用后属性改变的例子:

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; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
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;
 }
//运行结果:
//左值引用
//左值引用
//左值引用
//const 左值引用
//const 左值引用

为什么属性会改变?

//移动构造
string(string&& s)
	:_str(nullptr)
{
	cout << "移动构造" << endl;   //打印只是方便观察
	swap(s);
}

根据上面的代码进行分析:
如果右值引用过后,属性还是右值的话,那么s的属性为右值,则不能改变,就不能交换资源了。

6.完美转发

std::forward 完美转发在传参的过程中保留对象原生类型属性
forward作用的对象,如果是左值,则属性不变,如果是右值,右值引用过后,变成左值,然后forward将它变为右值。

举个例子:

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(forward<T>(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;
}
//运行结果:
//右值引用
//左值引用
//右值引用
//const 左值引用
//const 右值引用

7.新的类功能

1.默认成员函数

原来C++类中,有6个默认成员函数:

  1. 构造函数

  2. 析构函数

  3. 拷贝构造函数

  4. 拷贝赋值重载

  5. 取地址重载

  6. const 取地址重载
    最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。C++11 新增了两个:移动构造函数和移动赋值运算符重载。
    针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

    • 如果你没有自己实现移动构造函数,且没有实现 析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
    • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。
    • 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似) 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
2.强制生成默认函数的关键字default:

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

class string
{
	string()=default;   //强制生成默认构造
} 
3.禁止生成默认函数的关键字delete:

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

class string
{
	string()=delete;   //强制生成默认构造
} 

补充如果想要一个类,不让实例化,还有什么办法?

将构造函数声明放在类里面,且私有,不给定义。
这样类外面不能访问类私有,就访问不到构造函数,就无法实例化。类里面只有构造函数的声明,没有定义,也无法实例化。

🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈
本章完~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity之Unity面试题(五)

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之Unity面试题&#xff08;五&#xff09; TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取…

LVM逻辑卷

LVM逻辑卷 一.逻辑卷简介 LVM 是 Logical Volume Manager 的简称&#xff0c;译为中文就是逻辑卷管理。它是 Linux 下对硬盘分区的一种管理机制。LVM 适合于管理大存储设备&#xff0c;并允许用户动态调整文件系统的大小。此外&#xff0c;LVM 的快照功能可以帮助我们快速备份…

【AI大模型应用开发】【LangChain系列】 LangChain框架介绍,实现LangChain的Hello World

AI时代&#xff0c;相信大家或多或少都听过 LangChain 的大名。通俗的说&#xff0c;LangChain是一个面向大模型的开发框架&#xff08;SDK&#xff09;。 目前 LangChain 仍在快速迭代中&#xff0c;所以在使用中要时刻关注你所使用的版本和接口变更。 0. 认识LangChain框架 从…

Kafka 消费端消费重试和死信队列

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Spring-Kafka 提供消费重试的机制。当消息消费失败的时候,Spring-Kafka …

【QT】pro文件里添加又删除LIBS不影响运行的原因

我发现个问题啊&#xff0c;如果运行项目&#xff0c;发现报错&#xff0c;缺少某dll&#xff0c;接着你在pro文件里加上win32:LIBS -lOpengl32&#xff08;举个例子&#xff09;&#xff0c;接着可以运行了&#xff0c;接着把这行删掉&#xff0c;再运行&#xff0c;仍然可以…

中介者模式【行为模式C++】

1.简介 中介者模式是一种行为设计模式&#xff0c; 能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互&#xff0c; 迫使它们通过一个中介者对象进行合作。 亦称&#xff1a; 调解人、控制器、Intermediary、Controller、Mediator 2.示例 中介者模式在…

计算机组成原理(IO,输入输出)

1、“821.2016T1(1)”&#xff0c;表示821真题&#xff0c;2016年的题&#xff0c;T1是 选择题/填空题/大题 的第一题&#xff0c;其他类似标记也是相通 2、个人小白总结自用&#xff0c;不一定适用于其他人&#xff0c;请自行甄别 3、有任何疑问&#xff0c;欢迎私信探讨&…

uniapp开发h5端使用video播放mp4格式视频黑屏,但有音频播放解决方案

mp4格式视频有一些谷歌播放视频黑屏&#xff0c;搜狗浏览器可以正常播放 可能和视频的编码格式有关&#xff0c;谷歌只支持h.264编码格式的视频播放 将mp4编码格式修改为h.264即可 转换方法&#xff1a; 如果是自己手动上传文件可以手动转换 如果是后端接口调取的地址就需…

【leetcode面试经典150题】36. 旋转图像(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…

【Ansible自动化运维】Ansible入门基础信息【安装配置、常用命令与模块】

介绍安装配置注意事项yum安装验证安装配置host配置主机清单配置主控端被控端 常用模块命令组成command模块shell模块copy模块script模块 日志信息最后 介绍 Ansible 是一个开源 IT 自动化引擎&#xff0c;可自动执行供应、配置管理、应用程序部署、编排和许多其他 IT 流程。它可…

MySQL分库分表的方式有哪些

目录 一、为什么要分库分表 二、什么是分库分表 三、分库分表的几种方式 1.垂直拆分 2. 水平拆分 四、分库分表带来的问题 五、分库分表技术如何选型 一、为什么要分库分表 如果一个网站业务快速发展&#xff0c;那这个网站流量也会增加&#xff0c;数据的压力也会随之而…

护眼台灯哪个牌子好?护眼台灯十大排名,看看业内人怎么选!

眼镜已成为许多人日常生活中不可或缺的一部分&#xff0c;但戴眼镜并不总是方便的。现在许多家长也越来越关注孩子的视力问题&#xff0c;有些学生的书桌上已经放置上了护眼台灯。这种台灯提供柔和的光线&#xff0c;有助于改善照明环境&#xff0c;保护眼睛健康。然而&#xf…

使用jQuery实现购物界面的动态效果

实现功能&#xff1a;&#xff08;购物车以表格的格式展示&#xff09; 1 全选框和复选框之间的联动关系&#xff1a; 点击全选&#xff0c;所有复选框checked状态为true 点击复选框&#xff0c;全选框状态实时更新 2 点击删除按钮&#xff0c;删除对应的行 3 点击删除所选…

VUE3的有关知识

学习vue3的原因 在vue2当中的组件的实例,都是data一块,computed一块,当我们去找某一变量相关的则十分麻烦,vue3是组合式API,vue2是选项式, vue3的优点: 1)组合式更易维护 2)更快的速度 3)更小的体积 4)更好的响应式proxy 使用vue3相关脚手架创建项目 步骤: 1)node -v node版…

【CVE-2010-2883】进行钓鱼攻击的研究

最近作业中研究APT攻击&#xff0c;了解到2011年前后披露的LURID-APT&#xff0c;其中敌手利用了各种版本的文件查看器的漏洞实现攻击。CVE-2010-2883就是其中被利用的一个adobe reader的漏洞。特此复现&#xff0c;更好的研究和防范APT攻击。 本文仅仅是对相关漏洞利用的学习…

PyCharm Pro 2024:卓越的Python编辑开发工具,适用于Mac与Windows平台

PyCharm Pro 2024是一款专为Python开发者设计的强大编辑开发工具&#xff0c;无论是Mac还是Windows用户&#xff0c;都能从中受益良多。该软件凭借其出色的性能、丰富的功能和卓越的用户体验&#xff0c;成为Python编程界的翘楚。 作为一款高效的Python编辑器&#xff0c;PyCh…

02-结构化程式与自定义函数

视频教程&#xff1a;b站视频【MATLAB教程_台大郭彦甫&#xff08;14课&#xff09;原视频补档】https://www.bilibili.com/video/BV1GJ41137UH/?share_sourcecopy_web&vd_sourc*ed6b9f96888e9c85118cb40c164875dfc 官网教程&#xff1a; MATLAB 快速入门 - MathWorks 中…

React 使用 three.js 加载 gltf 3D模型 | three.js 入门

系列文章 示例项目(gitcode)&#xff1a;https://gitcode.com/qq_41456316/simple-react-three-demo 文章目录 系列文章前言一、three.js是什么&#xff1f;二、使用 React 和 three.js 加载 glTF 3D 模型的步骤步骤 1&#xff1a;创建 React 应用步骤 2&#xff1a;安装 thre…

使用QT 开发不规则窗体

使用QT 开发不规则窗体 不规则窗体贴图法的不规则窗体创建UI模板创建一个父类创建业务窗体main函数直接调用user_dialog创建QSS文件 完整的QT工程 不规则窗体 QT中开发不规则窗体有两种方法&#xff1a;&#xff08;1&#xff09;第一种方法&#xff0c;使用QWidget::setMask函…

川土微高性能模拟芯片系列产品介绍和应用

一、公司简介 上海川土微电子有限公司是一家成立于2016年的专注于高端模拟芯片研发设计与销售的高科技公司&#xff0c;产品涵盖隔离与接口、驱动与电源、高性能模拟三大产品线以及μMiC战略产品&#xff08; micro-Module in Chip&#xff09;。目前产品已广泛应用于工业控制…