右值引用+移动语义

news2025/3/15 15:02:00

目录

右值引用

引入

介绍 

左值

 左值引用

左值引用的缺陷

引入

缺陷

解决

右值

纯右值

将亡值

右值引用

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.coloradmin.cn/o/1103678.html

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

相关文章

SpringBoot+SpringSecurity项目的创建

首先创建一个springboot项目&#xff0c;注意springboot要求jdk版本在17以上 等项目构建完成之后&#xff0c;在pom文件中加入SpringSecurity的依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-securi…

Unity之ShaderGraph如何实现边缘光效果

前言 游戏中最常用的一个效果就是边缘光了&#xff0c;既可以做物体的高亮效果&#xff0c;也可以给人物皮肤表面添加光泽。如下图所示&#xff1a; 实现原理 边缘光的原理就是Fresnel节点的底层逻辑。 Fresnel&#xff1a;菲涅尔效应是根据视角不同表面上反射率不同的效应…

MIT6.5830 Lab0-Go tutorial实验记录(三)

MIT6.5830 Lab0-Go tutorial实验记录&#xff08;三&#xff09; – WhiteNights Site 标签&#xff1a;Golang 在前面两次实验记录的铺垫&#xff0c;是时候完成第一项任务了。 实验步骤 补全handlers.go中缺失的代码 先来看第一个部分&#xff0c;从RidershipDB获取图像数据…

老师如何发布考试成绩?

成绩查询页面是什么&#xff1f;如何用各种代码、Excel来实现让学生自助查询成绩&#xff1f; 作为老师&#xff0c;发布考试成绩是教学过程中的一个重要环节。传统的做法是&#xff0c;老师手动计算每个学生的分数&#xff0c;然后将成绩单打印出来并逐个发放给学生。这种方式…

RISCV——RV32I指令集详述

一、指令编码 6种指令格式 40条基本整数指令 二、指令实现 load/store 参考链接&#xff1a;riscv load/store

【mysql】关于mysql的数据结构特点 索引特点

文章目录 二叉树红黑树 b treehash结构b tree索引存放特点myisamInnoDB 最左原则主键相关知识点缓存池淘汰机制 前言&#xff1a;翻自己博客 发现缺少mysql数据结构和索引相关内容 两年前整理的mysql知识点 一直存在于博主的笔记本里面 &#xff08;是的 纸质的那种笔记本 不是…

python:代码加密流水线

通过以下五步&#xff0c;将python代码加密&#xff0c;且只能在指定电脑上运行。 三层加密。只加密核心函数。将参数暴露在外。都可以实现。 文章目录 一、获取机器码二、加密机器码三、将加密机器码写入ism函数中四、加密代码&#xff08;可有可无&#xff09;1.使用[https:…

图像特征之SIFT

SIFT介绍 尺度不变特征转换即SIFT (Scale-invariant feature transform)是一种计算机视觉的算法。它用来侦测与描述影像中的局部性特征&#xff0c;它在空间尺度中寻找极值点&#xff0c;并提取出其位置、尺度、旋转不变量&#xff0c;此算法由 David Lowe在1999年所发表&#…

学习mapster的基本用法

正在学习的开源博客项目Blog .NET Core中采用mapster实现对象映射&#xff0c;个人理解对象映射框架主要用于不同类型间的数据转换&#xff0c;比起个人实现的定制化的类型对类型的转换代码&#xff0c;采用对象映射框架更便捷&#xff0c;同时也能支撑各式各样的对象映射场景。…

List集合拆分为多个List

list 拆分 目录概述需求&#xff1a; 设计思路实现思路分析1.list 拆分是2.用stream的parallel实现list的分批处理3.使用stream的parallel实现list的分批处理 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full bus…

【LeetCode】33. 搜索旋转排序数组

1 问题 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nums…

【Godot引擎开发】算是重要点吧,好像只能算是入门教程

博主&#xff1a;_LJaXi 专栏&#xff1a; Godot | 横版游戏开发 Godot 物体规律移动内置虚函数浮点计算浮点数计算数组APIInput单例与自定义单例节点NodeSprite2DArea2DCollisionShape2DKinematicBody2DRigidBody2D Pong游戏场景安排玩家1玩家2小球记分系统主体框架节点选择文…

ansible基础

一&#xff0c;ansible简述&#xff1a; 是一款自动化运维工具&#xff0c;通过ssh对目标主机进行配置、应用部署、任务执行、编排调度等操作。它简化了复杂的环境管理和自动化任务,提高了工作效率和一致性。同时ansibie的剧木&#xff08;playbooks〉可使用YAML语言进行编写。…

三分钟快速实现MQTT网关与三菱系列PLC远程通讯

MQTT协议网关串口连接三菱FX3U操作说明V1.3 目录 一. 使用流程 二. 准备工作 2.1 需要准备如下物品 2.2 LF220网关准备工作 2.3 PLC准备工作 2.4 电脑准备工作 2.5 MQTT服务器 三. MQTT网关登陆平台配置步骤 3.1 登录 3.2 网关概况 3.3 MQTT连接配置 3…

C语言练习题-指针-(编写一个函数,接受一个整型数组和数组的长度作为参数,将数组中的元素按逆序存放)

文章目录 前言题目题目1简单的代码框架 题目2测试用例 题目3测试用例 参考答案题目1答案1解析 答案2解析 题目2答案1答案2 题目3答案1答案2 其他文章 前言 本篇文章的题目为C的基础练习题&#xff0c;指针部分。做这些习题之前&#xff0c;你需要确保已经学习了指针的知识。 本…

session 反序列化

原理详解 ctfshow 新手杯 剪刀石头布 这里我们可以发现服务器使用的处理器为php_serialize&#xff0c;与当前页面处理器不同&#xff0c;在反序列化的时候会造成一些问题。同时cleanup配置没开&#xff0c;关闭了session自动清理&#xff0c;所以我们不需要进行条件竞争。并…

pointnet和pointnet++点云分割和分类

目录 1. pointnet 1.1 点云数据的特点 1.2 模型功能 1.3 网络结构 1.3.1 分类网络 1.3.2 分割网络 2. pointnet 2.1 模型 2.2 sampling layer组件 2.3 grouping layer 2.4 pointnet 1. pointnet 1.1 点云数据的特点 &#xff08;1&#xff09;无序性&#xff1a…

深度学习——批量规范化(Batch Normalization)

深度学习——批量规范化&#xff08;Batch Normalization&#xff09; 文章目录 前言一、训练深层网络二、批量规范化层2.1. 全连接层2.2. 卷积层2.3. 预测过程中的批量规范化 三、从零实现四、使用批量规范化层的LeNet五、简洁实现六、小结总结 前言 训练深层神经网络是十分困…

三极管从入门到精通

文章目录 摘要1 基础1.1 PN结1.2 三极管 2 三极管模拟电路知识2.1 I-V特性曲线2.2 极限参数解释2.3 基本共射极放大电路2.4 小信号模型2.5 用小信号模型分析基本共射极放大电路 3 三极管实际模拟电路应用图3.1 共射极放大电路3.1.1 基本共射极放大电路3.1.2 基极分压式射极偏置…

深度学习——使用kaggle中的GPU资源

文章目录 前言一、使用流程二、数据集加载总结 前言 之前都是使用CPU来进行模型训练&#xff0c;对于一些小模型还好&#xff0c;等神经网络越来越深&#xff0c;所需计算资源也越来越大&#xff0c;时间耗费也越来越多&#xff0c;这时我们需要使用GPU来进行加速。本章将介绍…