【C++随笔02】左值和右值

【C++随笔02】左值和右值

  • 一、左值和右值
    • 1、字面理解——左值、右值
    • 2、字面理解的问题
    • 3、左值、右值
    • 4、左值的特征
    • 5、 右值的特征
    • 6、x++和++x是左值还是右值
    • 7、复合例子
    • 8、通常字面量都是一个右值,除字符串字面量以外:
  • 二、左值引用和右值引用
  • 三、左值引用
    • 1、常量左值引用
    • 2、制构造函数和复制赋值运算符函数——左值引用
  • 四、右值引用
    • 1、移动语义和完美转发
    • 2、移动语义
    • 3、完美转发
      • 3.1、简介、demo
      • 3.2、问题:c++中的完美转发(std::forward)存在的意义?
  • 简单总结

一、左值和右值

1、字面理解——左值、右值

最简单的字面理解,表达式等号左边的值为左值,等号右边的值为右值,比如

int x = 1; 
int y = 3; 
int z = x + y;

以上面的代码为例,

  1. x是左值,1是右值;
  2. y是左值,3是右值;
  3. z是左值,x+y的结果是右值。(注意,是结果)

2、字面理解的问题

在第一行代码中我们判断a是一个左值,它却在第二行变成了右值,所以这就是问题,要准确地区分左值和右值还是应该理解其内在含义。

3、左值、右值

在C++中

  • 左值一般是指一个指向特定内存的具有名称的值(具名对象),它有一个相对稳定的内存地址,并且有一段较长的生命周期。
  • 右值则是不指向稳定内存地址的匿名值(不具名对象)

简单来说,左值是一个有内存地址的表达式,而右值是一个没有内存地址的表达式。

基于这一特征,我们可以用取地址符&来判断左值和右值,能取到内存地址的值为左值,否则为右值。还是以上面的代码为例,因为&a和&b都是符合语法规则的,所以a和b都是左值。

4、左值的特征

左值具有以下特征:

  • 左值在内存中有唯一的地址。
  • 左值可以出现在赋值操作符的左边。
  • 左值可以被取地址操作符(&)获取其内存地址。
  • 左值可以作为函数参数或返回值。
int x = 10; // x是一个左值
int* ptr = &x; // 获取x的地址并赋给指针ptr

5、 右值的特征

右值具有以下特征:

  • 右值没有内存地址。
  • 右值不能作为赋值操作符的左边。
  • 右值不能被取地址操作符获取其内存地址。
int y = 20; // 20是一个右值
int z = x + y; // 表达式x + y的结果是一个右值

6、x++和++x是左值还是右值

int *p = &x++; // 编译失败 
int *q = &++x; // 编译成功
  • x++是右值,因为在后置++操作中编译器首先会生成一份x值的临时复制,然后才对x递增,最后返回临时复制内容。
  • ++x则不同,它是直接对x递增后马上返回其自身,所以++x是一个左值。

7、复合例子

int x = 1;
int get_val()
{
 return x;
}
void set_val(int val)
{
 x = val;
}
int main() 
{
 x++;
 ++x;
 int y = get_val();
 set_val(6);
}
  • get_val函数,该函数返回了一个全局变量x,虽然很明显变量x是一个左值,但是它经过函数返回以后变成了一个右值。

原因和x++类似,在函数返回的时候编译器并不会返回x本身,而是返回x的临时复制,所以int * p = &get_val();也会编译失败。

  • set_val函数,该函数接受一个参数并且将参数的值赋值到x中。在main函数中set_val(6);实参6是一个右值,但是进入函数之后形参val却变成了一个左值。

我们可以对val使用取地址符.

  • 左值到右值的隐式转换
    左值可以被隐式地转换为右值,例如在某些表达式中,需要右值而传入一个左值参数时,编译器会自动进行转换。
void printValue(int value) 
{
    cout << value << endl;
}

int x = 10;
printValue(x); // 编译器将x隐式地转换为右值传递给函数

8、通常字面量都是一个右值,除字符串字面量以外:

int x = 1;
set_val(6);
auto p = &“hello world”;

编译器会将字符串字面量存储到程序的数据段中,程序加载的时候也会为其开辟内存空间(rodata),所以我们可以使用取地址符&来获取字符串字面量的内存地址。

  • 给大家写一个有编译错误的例子,大家调调
#include <iostream>

int add1(int& x)
{
    return x+1;
}

int main() {
    int a = add1(1);

    std::cout << a << std::endl;
    return 0;
}

二、左值引用和右值引用

void processValue(int& value) {
    // 对左值进行处理
}

void processValue(int&& value) {
    // 对右值进行处理
}
  • 左值引用和右值引用在C++11中,引入了右值引用的概念。左值引用(L-value references)用于绑定到左值,右值引用(R-value references)用于绑定到右值。

  • 左值引用的声明使用单个&符号:

int x = 10;
int& lvalueRef = x; // 左值引用绑定到左值x
  • 右值引用的声明使用两个&&符号:
int y = 20;
int&& rvalueRef = y + 30; // 右值引用绑定到右值表达式的结果

引用可以方便地对变量进行修改或者将其传递给函数。
右值引用在移动语义(Move Semantics)和完美转发(Perfect Forwarding)中具有重要的作用,可以提高性能和代码效率。

三、左值引用

1、常量左值引用

常量左值引用的特性显得更加有趣,它除了能引用左值,还能够引用右值,比如:

int &x1 = 7; // 编译错误
const int &x = 11; // 编译成功

在上面的代码中,第一行代码会编译报错,因为int&无法绑定一个int类型的右值,但是第二行代码却可以编译成功。请注意,虽然在结果上const int &x = 11和const int x = 11是一样的,但是从语法上来说,前者是被引用了,所以语句结束后11的生命周期被延长,而后者当语句结束后右值11应该被销毁。虽然常量左值引用可以引用右值的这个特性在赋值表达式中看不出什么实用价值,但是在函数形参列表中却有着巨大的作用。一个典型的例子就是复制构造函数和复制赋值运算符函数,

2、制构造函数和复制赋值运算符函数——左值引用

当我们使用左值引用时,通常会涉及到复制构造函数和复制赋值运算符函数。复制构造函数用于创建一个新对象,并将其初始化为已存在的对象的副本。复制赋值运算符函数用于将一个已存在的对象的值复制给另一个已存在的对象。

以下是一个简单的示例程序,演示了左值引用、复制构造函数和复制赋值运算符函数的使用:

#include <iostream>

class MyObject {
private:
    int data;

public:
    MyObject(int d) : data(d) {
        std::cout << "Constructor called with value: " << d << std::endl;
    }

    MyObject(const MyObject& other) : data(other.data) {
        std::cout << "Copy constructor called. Copied value: " << data << std::endl;
    }

    MyObject& operator=(const MyObject& other) {
        if (this != &other) {
             data = other.data;
             std::cout << "Copy assignment operator called. Copied value: " << data << std::endl;
        }
        return *this;
    }

    void printData() const {
        std::cout << "Data: " << data << std::endl;
    }
};

int main() {
    MyObject obj1(42);                      // 调用构造函数
    MyObject obj2(obj1);                    // 调用复制构造函数
    MyObject obj3 = obj1;                   // 调用复制构造函数

    MyObject obj4(55);                      // 调用构造函数
    obj4 = obj1;                            // 调用复制赋值运算符函数

    obj1.printData();                        // 输出: Data: 42
    obj2.printData();                        // 输出: Data: 42
    obj3.printData();                        // 输出: Data: 42
    obj4.printData();                        // 输出: Data: 42

    return 0;
}

输出

Constructor called with value: 42
Copy constructor called. Copied value: 42
Copy constructor called. Copied value: 42
Constructor called with value: 55
Copy assignment operator called. Copied value: 42
Data: 42
Data: 42
Data: 42
Data: 42

在上述示例中,我们首先定义了一个名为 MyObject 的类,该类具有一个带有整数参数的构造函数、一个复制构造函数和一个复制赋值运算符函数。然后我们创建了几个 MyObject 类型的对象,并通过不同方式进行初始化和赋值。

在 main() 函数中,我们创建了 obj1,并使用拷贝构造函数将其值分别复制给 obj2 和 obj3。接下来,我们创建了 obj4,然后使用赋值运算符将 obj1 的值复制给 obj4。

最后,我们调用各个对象的成员函数 printData() 来打印它们的数据值。你可以看到,obj2、obj3 和 obj4 的数据值都与 obj1 相同,这表明复制构造函数和复制赋值运算符函数成功地将一个对象的值复制给了另一个对象。

四、右值引用

顾名思义,右值引用是一种引用右值且只能引用右值的方法。在语法方面右值引用可以对比左值引用,在左值引用声明中,需要在类型后添加&,而右值引用则是在类型后添加&&,例如:

int i = 0;
int &j = i; // 左值引用
int &&k = 11; // 右值引用

在上面的代码中,k是一个右值引用,如果试图用k引用变量i,则会引起编译错误。右值引用的特点之一是可以延长右值的生命周期。

1、移动语义和完美转发

右值引用在移动语义和完美转发中起着重要的作用。

  • 移动语义:移动语义是指通过右值引用将资源(如动态分配的内存、文件句柄等)从一个对象转移到另一个对象,而不是进行深拷贝。这样可以避免不必要的内存分配和释放,提高程序性能。

  • 完美转发:完美转发是指将一个函数中的参数以原样传递给另一个函数,包括参数的左值或右值属性信息。通过使用右值引用和模板,可以实现完美转发,避免了不必要的拷贝。

2、移动语义

当涉及到移动语义和完美转发时,我们需要先了解一些基本概念和问题。在C++中,对象的拷贝构造函数(Copy Constructor)和拷贝赋值运算符(Copy Assignment Operator)会对资源进行拷贝操作,这可能导致内存分配和释放的开销。在某些情况下,我们希望能够高效地转移资源的所有权而不是进行深拷贝,这就引入了移动语义和完美转发。

移动语义(Move Semantics)
移动语义是指通过右值引用将资源(如动态分配的内存、文件句柄等)从一个对象转移到另一个对象,而不是进行深拷贝。移动语义可以大大提高程序性能,因为它避免了不必要的内存分配和释放。

移动语义的实现依赖于右值引用。右值引用(R-value Reference)通过双个&&符号进行声明,并且可以绑定到右值。通过移动构造函数(Move Constructor)和移动赋值运算符(Move Assignment Operator),可以使用移动语义来实现资源的高效转移。

简单来说,移动语义允许我们从一个临时的右值对象或者一个将要被销毁的对象中“窃取”资源,然后将其传递给新对象,而无需进行资源的复制操作。

示例代码:

#include <iostream>

class MyObject {
private:
    int data;

public:
    MyObject(int d = 0) : data(d) {
        std::cout << "Constructor called with value: " << d << std::endl;
    }

    MyObject(const MyObject& other) : data(other.data) {
        std::cout << "Copy constructor called. Copied value: " << data << std::endl;
    }

    MyObject& operator=(const MyObject& other) {
        if (this != &other) {
            data = other.data;
            std::cout << "Copy assignment operator called. Copied value: " << data << std::endl;
        }
        return *this;
    }

    MyObject(MyObject&& other) noexcept : data(other.data) {
        std::cout << "Move constructor called. Moved value: " << data << std::endl;
        other.data = 0;  // 清空原对象的值
    }

    MyObject& operator=(MyObject&& other) noexcept {
        if (this != &other) {
            data = other.data;
            std::cout << "Move assignment operator called. Moved value: " << data << std::endl;
            other.data = 0;  // 清空原对象的值
        }
        return *this;
    }

    void printData() const {
        std::cout << "Data: " << data << std::endl;
    }
};

int main() {
    MyObject obj1(42);                      // 调用构造函数
    MyObject obj2(obj1);                    // 调用复制构造函数
    MyObject obj3 = obj1;                   // 调用复制构造函数

    MyObject obj4(55);                      // 调用构造函数
    obj4 = obj1;                            // 调用复制赋值运算符函数

    MyObject obj5(std::move(obj1));         // 调用移动构造函数
    MyObject obj6 = std::move(obj2);        // 调用移动赋值构造函数

    obj1.printData();                        // 输出: Data: 42
    obj2.printData();                        // 输出: Data: 0
    obj3.printData();                        // 输出: Data: 42
    obj4.printData();                        // 输出: Data: 42
    obj5.printData();                        // 输出: Data: 42
    obj6.printData();                        // 输出: Data: 42

    return 0;
}

Constructor called with value: 42
Copy constructor called. Copied value: 42
Copy constructor called. Copied value: 42
Constructor called with value: 55
Copy assignment operator called. Copied value: 42
Move constructor called. Moved value: 42
Move constructor called. Moved value: 42
Data: 0
Data: 0
Data: 42
Data: 42
Data: 42
Data: 42

  • obj1和obj2为0,这证明了移动构造函数被调用,并且资源成功被转移。

3、完美转发

3.1、简介、demo

完美转发(perfect forwarding)是C++11引入的概念,用于在函数模板中将参数按原样传递给另一个函数,同时保留其值类别(lvalue或rvalue)。它可以在保持精确性的同时避免不必要的复制或移动操作,提高代码的效率。

完美转发通常与转发引用类型参数(如模板中的万能引用)一起使用,以实现泛型编程中的参数传递。在函数模板中,我们可以使用 std::forward 函数来进行完美转发。

下面是一个使用完美转发的示例:

#include <iostream>
#include <utility>

// 定义一个接收传入参数的函数
void process(int& i) {
    std::cout << "Lvalue: " << i << std::endl;
    i = 100;  // 修改传入的左值参数
}

void process(int&& i) {
    std::cout << "Rvalue: " << i << std::endl;
}

// 将参数转发到 process 函数
template <typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

int main() {
    int a = 42;

    wrapper(a);        // 传递左值
    wrapper(123);      // 传递右值

    std::cout << "a: " << a << std::endl;

    int b = 42;
    process(b);
    process(123);
    std::cout << "b: " << a << std::endl;

    return 0;
}

输出
Lvalue: 42
Rvalue: 123
a: 100
Lvalue: 42
Rvalue: 123
b: 100

在上述示例中,我们定义了两个重载的 process 函数,一个接受左值引用参数,一个接受右值引用参数。然后,我们创建了一个模板函数 wrapper,该函数接受一个通用引用类型的参数 T&& arg。

在 wrapper 函数内部,我们通过调用 std::forward 来进行完美转发,将 arg 参数原封不动地传递给 process 函数。std::forward 根据参数的值类别(lvalue还是rvalue)将参数作为对应类型的引用进行转发。

在 main 函数中,我们先传递了一个左值 x 给 wrapper 函数,然后传递了一个右值 123。程序会根据参数的值类别,选择调用合适的 process 函数,并输出相应的结果。

使用完美转发可以避免不必要的拷贝和移动操作,提高代码的效率和性能。

3.2、问题:c++中的完美转发(std::forward)存在的意义?

  • 我们从上面的demo中可以看到,既然不使用forward也可以达到类似的效果,那为何还要使用完美转发(std::forward)呢?

  • 说这个问题前,我们先把上面的一个demo改下

#include <iostream>

class MyObject {
private:
    int data;

public:
    MyObject(int d = 0) : data(d) {
        std::cout << "Constructor called with value: " << d << std::endl;
    }

    MyObject(const MyObject& other) : data(other.data) {
        std::cout << "Copy constructor called. Copied value: " << data << std::endl;
    }

    MyObject& operator=(const MyObject& other) {
        if (this != &other) {
            data = other.data;
            std::cout << "Copy assignment operator called. Copied value: " << data << std::endl;
        }
        return *this;
    }

    MyObject(MyObject&& other) noexcept : data(other.data) {
        std::cout << "Move constructor called. Moved value: " << data << std::endl;
        other.data = 0;  // 清空原对象的值
    }

    MyObject& operator=(MyObject&& other) noexcept {
        if (this != &other) {
            data = other.data;
            std::cout << "Move assignment operator called. Moved value: " << data << std::endl;
            other.data = 0;  // 清空原对象的值
        }
        return *this;
    }

    void printData() const {
        std::cout << "Data: " << data << std::endl;
    }
};

template<typename T>
T* createObject(T&& t)
{
    return new MyObject(t);
}

int main() {
    MyObject* newMyObject = createObject(std::move(MyObject(110)));

    return 0;
}
  • 大家猜猜看,上面应该输出什么?应该调用移动构造函数的,对吧,实际情况输出如下,调用的却是拷贝构造函数。

Constructor called with value: 110
Copy constructor called. Copied value: 110

  • 是不是很好奇,为啥调用的是拷贝构造函数,而非我们之前想象的移动构造函数呢?
    我们把createObject做下简单的更改,增加std::forward,
template<typename T>
T* createObject(T&& t)
{
    return new MyObject(std::forward<T>(t));
}
  • 结果输出如下

Constructor called with value: 110
Move constructor called. Moved value: 110

这时,才真正的如我们所想,真的调用了移动构造函数。

  • 分析:经历了模版参数t这一次转发,t右值的属性被改变为了左值。

简单总结

  • 左值引用用于引用左值并允许修改,
  • 右值引用用于引用右值并具有移动语义和完美转发的特性。

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

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

相关文章

VR防地质灾害安全教育:增强自然灾害知识,提高自我保护意识

VR防地质灾害安全教育系统是一种虚拟仿真技术&#xff0c;可以通过虚拟现实技术模拟地震、泥石流、滑坡等地质灾害的发生和应对过程&#xff0c;帮助人们提高应对突发自然灾害的能力。这种系统的优势在于可以增强自然灾害知识&#xff0c;提高自我保护意识&#xff0c;锻炼人们…

4. 池化层相关概念

4.1 池化层原理 ① 最大池化层有时也被称为下采样。 ② dilation为空洞卷积&#xff0c;如下图所示。 ③ Ceil_model为当超出区域时&#xff0c;只取最左上角的值。 ④ 池化使得数据由5 * 5 变为3 * 3,甚至1 * 1的&#xff0c;这样导致计算的参数会大大减小。例如1080P的电…

R语言实现网状Meta分析(1)

#R语言实现网状Meta library(gemtc) help(package"gemtc") data<-gemtc::smoking #注意按照实例格式编写数据 net<-mtc.network(data$data.ab) #网状图 plot(net,mode"circle",displaylabelsT,boxed.labelF) summary(net) #网状model model<-mtc…

wazuh环境配置和漏洞复现

1.wazuh配置 虚拟机 &#xff08;OVA&#xff09; - 替代安装 (wazuh.com)在官方网页安装ova文件 打开VMware选择打开虚拟机&#xff0c;把下载好的ova文件放入在设置网络改为NAT模式 账号:wazuh-user 密码:wazuh ip a 查看ip 启动小皮 远程连接 账号admin …

温故知新之:代理模式,静态代理和动态代理(JDK动态代理)

0、前言 代理模式可以在不修改被代理对象的基础上&#xff0c;通过扩展代理类&#xff0c;进行一些功能的附加与增强。 1、静态代理 静态代理是一种代理模式的实现方式&#xff0c;它在编译期间就已经确定了代理对象&#xff0c;需要为每一个被代理对象创建一个代理类。静态代…

机器学习资料汇总

一 卷积 原来卷积是这么计算的 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/268179286 最核心的部分是要知道&#xff0c;通道数和输出特征图无关&#xff0c;是卷积核的个数&#xff0c;决定了输出特征图的个数

详解 SpringMVC 的 @RequestMapping 注解

文章目录 1、RequestMapping注解的功能2、RequestMapping注解的位置3、RequestMapping注解的value属性4、RequestMapping注解的method属性5、RequestMapping注解的params属性&#xff08;了解&#xff09;6、RequestMapping注解的headers属性&#xff08;了解&#xff09;7、Sp…

ArcGIS Pro怎么解决道路压盖问题

在默认情况下&#xff0c;道路可能会存在低等级道路将高等级道路压盖、在道路连接处不连通的情况&#xff0c;这些问题都可以在ArcGIS Pro内解决&#xff0c;这里为大家介绍一下处理方法&#xff0c;希望能对你有所帮助。 道路分级 在符号系统内&#xff0c;选择唯一值&#x…

集成易点易动管理系统连接更多应用

场景描述&#xff1a; 基于易点易动开放平台能力&#xff0c;无代码集成易点易动与多个应用互通互连&#xff0c;实现固定资产管理数字化、智能化。通过Aboter可搭建业务自动化流程&#xff0c;实现多个应用之间的数据连接。 开放能力&#xff1a; 消息推送&#xff1a; 新…

前端js实现获取指定元素(top,lef,right,bottom)到视窗的距离 ;getBoundingClientRect()获取

getBoundingClientRect()获取元素位置&#xff0c;这个方法没有参数 该函数返回一个Object对象&#xff0c;该对象有6个属性&#xff1a;top,lef,right,bottom,width,height&#xff1b; <div id"box"></div>var objectdocument.getElementById(box); …

计算机组成原理(主存储器的基本组成、 运算器的基本组成、 控制器的基本组成、完成一条指令的三个阶段)

主存储器的基本组成&#xff1a; 这个是读数据操作图&#xff1a; 读入数据与菜鸟驿站的取货流程差不多&#xff1a; 写入数据的过程与读入数据类似&#xff1a; 1、cpu 指明想要写入到那个位置&#xff08;写到MAR中&#xff09; 2、想要写入的数据会放到MDR中 3、c…

Visual Studio中Linux开发头文件intellisense问题的解决办法

文章目录 前言个人环境 SSH到WSL复制文件后记 前言 最近在用我心爱的Visual Studio配合WSL2做一些Linux开发&#xff0c;但是有一个问题&#xff0c;就是当我#include <sys/socket.h>&#xff0c;会提示找不到文件 我尝试了各种姿势&#xff0c;包括修改CMakeSettings.…

Hbase-技术文档-java.net.UnknownHostException: 不知道这样的主机。 (e64682f1b276)

问题描述&#xff1a; 在使用spring-boot操作habse的时候&#xff0c;在对habse进行操作的时候出现这个问题。。 报错信息如下&#xff1a; 第一段报错&#xff1a; 第二段报错&#xff1a; java.net.UnknownHostException: e64682f1b276 问题定位解读&#xff1a; 错误 ja…

FanoutExchange广播(扇形)交换机

目录 一、简介 二、代码展示 父pom文件 pom文件 配置文件 config 生产者 消费者 测试 结果 一、简介 扇型&#xff08;广播&#xff09;交换机&#xff0c;这个交换机没有路由键概念&#xff0c;就算你绑了路由键也是无视的。 这个交换机在接收到消息后&#xff0c;…

Windows安装6s模型

官网给出了详细的安装步骤 第一步&#xff1a;安装编译器 安装GnuWin32&#xff0c;按照提示安装&#xff0c;安装到你想安装的地方&#xff0c;记住目录。 安装G77&#xff0c;下载链接里面的Fort99.zip&#xff0c;将G77文件夹提取到C盘根目录。 将这两个目录的bin目录添加…

LibreOffice新一代的办公软件for Mac/Windows免费版

LibreOffice是一款免费、开源的办公软件套件&#xff0c;可在多个操作系统上运行&#xff0c;包括Windows、Mac和Linux。它提供了一系列功能强大的办公工具&#xff0c;包括文档处理、电子表格、演示文稿、数据库管理等。 LibreOffice的界面简洁直观&#xff0c;与其他流行的办…

python+django+协同过滤算法-基于爬虫的个性化书籍推荐系统(包含报告+源码+开题)

为了提高个性化书籍推荐信息管理的效率&#xff1b;充分利用现有资源&#xff1b;减少不必要的人力、物力和财政支出来实现管理人员更充分掌握个性化书籍推荐信息的管理&#xff1b;开发设计专用系统--基于爬虫的个性化书籍推荐系统来进行管理个性化书籍推荐信息&#xff0c;以…

MacOS软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 MacOS是一种由苹果公司开发的操作系统&#xff0c;专门用于苹果公司的计算机硬件。它被广泛用于创意和专业应用程序&#xff0c;如图像设计、音频和视频编辑等。以下是关于MacOS的详细介绍。 1、MacOS的历史和演变 MacOS最初于…

node.js安装好后测试报错解决

node.js的版本是18.X.X node.js安装好后&#xff0c;执行命令&#xff1a; npm install express -g 报错&#xff01;&#xff01;&#xff01; 解决办法&#xff1a; 看报错是由于权限不够&#xff0c; 所以打开cmd时&#xff0c;以管理员的方式打开 然后再执行命令就OK了…

单片机TVS/ESD二极管防护

TVS 瞬态电压抑制二极管Transient Voltage Suppressor ESD 静电释放二极管 Electro-Static discharge 这两种本质上都是二极管。都是利用了二极管正向导通、反向截止的特性。二极管在反向截止截止条件下&#xff0c;如果电压继续增大&#xff0c;将会引发雪崩&#xff0c;使得…