ArrayList源码全面解析

在这里插入图片描述

一、概述

ArrayList 是 java 集合框架中比较常用的数据结构,继承自 AbstractList,实现了 List 接口。底层采用数组来实现。ArrayList 实现了java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

1.1、底层数据结构

底层采用数组进行数据存储,相当于动态数组。

1.2、特点
  • 动态大小:ArrayList的大小是动态的,可以在运行时添加或删除元素。这意味着您不需要预先确定列表的大小。

  • 数组实现:ArrayList基于数组实现,这使得它具有高效的内部数据结构,可以快速随机访问列表中的元素。但是,如果你需要从列表的中间位置插入或删除元素,这可能会涉及到数组元素的移动,所以可能相对较慢。

  • 可修改:ArrayList是可修改的。这意味着您可以在列表中更改、添加或删除元素。

  • 线程不安全的:ArrayList不是线程安全的。这意味着如果在多个线程同时修改ArrayList时,可能会导致数据的不一致性。如果需要线程安全,可以使用同步块或者考虑使用线程安全的集合类,如CopyOnWriteArrayList。

  • 易于使用:ArrayList提供了许多有用的方法,如add(), remove(), get()等,可以方便地操作列表。同时,它也支持迭代器,可以进行遍历操作。

  • 性能:由于ArrayList内部使用数组进行实现,所以在知道索引的情况下进行读取或写入操作的速度非常快,时间复杂度为O(1)。但是,如果需要从中间位置进行插入或删除操作,那么需要移动数组中的元素,所以时间复杂度为O(n)。

1.3、ArrayList类图

在这里插入图片描述

由源码可得知:
在这里插入图片描述

ArrayList继承了抽象类AbstractList,并实现了ListRandomAccessCloneableSerializable等接口。

在这里插入图片描述

在这里插入图片描述

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

抽象类AbstractList继承了抽象类AbstractCollection,并实现了List接口抽象类AbstractCollection实现了Collection接口Collection接口继承了Iterable接口

  • 实现RandomAccess接口:表明ArrayList支持快速(通常是常量时间)的随机访问。

  • 实现了Cloneable接口,表明它支持克隆。可以调用clone()进行浅拷贝。

  • 实现了Serializable接口,表明它支持序列化。

二、源码解析

2.1、属性解析
    /**
     * 默认的容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 定义了一个空的数组,用于在用户初始化代码的时候传入容量为0时使用
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 定义了一个空数组,用于在默认构造器中,赋值给顶级数组 elementData
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 底层数组,真正存储元素的地方
     */
    transient Object[] elementData;  

    /**
     * 表示集合中ArrayList含有元素的个数
     */
    private int size;
    
    
    /**
     * 标记数组的最大容量
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
    
    /**
     * 记录对 List 操作的次数
     */
    protected transient int modCount = 0;


2.2、构造方法解析

ArrayList默认提供了三种构造器,分别是:

  • 无参构造器
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

解析:

当我们使用无参构造器时,将DEFAULTCAPACITY_EMPTY_ELEMENTDATA变量赋值给elementData,即把elementData初始化为一个空数组。

注意:在这里没有对数组容量进行分配大小;具体给数组分配容量大小是第一次增加元素时才分配的。

  • 指定初始容量的构造器
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

解析:

当传入的容量大小大于0,elementData 赋值为一个容量指定容量为initialCapacity的数组。

当传入的容量等于0,elementData 赋值为 EMPTY_ELEMENTDATA。

其他情况下,抛出异常!

注意:

对比无参构造,与此时的initialCapacity传入0,两种情况有什么不同呢,又有什么作用呢?

// 无参构造
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

// initialCapacity传入0时的赋值
this.elementData = EMPTY_ELEMENTDATA;

这两个变量的作用:用于区分是通过哪个构造函数来进行初始化的。

  • 提供一个Collection集合的构造器
 public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

解析:

将传入的集合转化为数组,赋值给elementData,并计算集合的大小size;

若 size != 0,接着判断保证此刻数组elementData数组的类型和Object[]类型相同,若不同,则拷贝一个Object[]类型的数组赋值给elementData。

若size == 0,将 elementData 赋值为 EMPTY_ELEMENTDATA。

注:若传入的c为null,将会报空指针异常。

2.3、常用方法解析
add方法
// add方法
public boolean add(E e) {
        // 判断数组用不用扩容
        ensureCapacityInternal(size + 1);  
        elementData[size++] = e;
        return true;
}

// ensureCapacityInternal方法
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// calculateCapacity方法
 private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
}

// ensureExplicitCapacity方法
 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
}

// grow方法
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
}

解析:

当我们调用add方法,首先调用ensureCapacityInternal()方法,传入当前元素的个数+1;紧接着调用calculateCapacity方法,进行容量的计算。

 private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
}

在calculateCapacity 方法中,有个判断逻辑:elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个也正是我们使用默认构造器创建的。若为默认的构造器创建的话,把minCapacity(也就是我们的size+1)和DEFAULT_CAPACITY(默认容量是10)比较,取较大者返回,否则的话,就返回minCapacity。

该方法的作用:通过无参构造创建的 ArrayList 在第一次 add 时进行初始容量的设置。因为只要不相等,返回的还是minCapacity,与传入的相同;

因此通过无参构造创建的ArrayList,第一次add的时候,创始容量的大小为DEFAULT_CAPACITY(默认容量是10)。

接下来调用ensureExplicitCapacity 方法:

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
}

进行判断:如果minCapacity大于底层数组的长度,则需要调用grow(minCapacity)方法进行扩容,否则的话,回到add()方法中,ensureCapacityInternal(size + 1)什么也不做。

过程解析

  • 当我们要 add 进第1个元素到 ArrayList 时,elementData.length 为0 (因为还是一个空的 list),因为执行了 ensureCapacityInternal() 方法 ,所以 minCapacity 此时为10。此时,minCapacity - elementData.length > 0 成立,所以会进入 grow(minCapacity) 方法。
  • 当add第2个元素时,minCapacity 为2,此时elementData.length(容量)在添加第一个元素后扩容成 10 了。此时,minCapacity - elementData.length > 0 不成立,所以不会进入 (执行)grow(minCapacity) 方法。
  • 添加第3、4···到第10个元素时,依然不会执行grow方法,数组容量都为10。

直到添加第11个元素,minCapacity(为11)比elementData.length(为10)要大;进入grow方法进行扩容。

接下来进入grow方法:

// grow方法
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
}

解析:

int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);

oldCapacity为旧容量, newCapacity是扩容后的容量,即oldCapacity + (oldCapacity >> 1),大概是oldCapacity的1.5倍。

oldCapacity为偶数就是1.5倍;否则是1.5倍左右,因为如果是奇数的话会丢掉小数。

接下来进行判断:

第一个判断:如果新容量小于我们设置的minCapacity的话,就把minCapacity赋值给newCapacity。

第二个判断:判断newCapacity是否大于MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8),(MAX_ARRAY_SIZE代表了所能设置的数组的最大容量,如果超过这个值,可能导致内存溢出的错误(OutOfMemoryError),当然,如果超过的话,会将数组的容量设置为INTEGER.MAX_VALUE,否则新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8)。

过程解析

  • 当add第1个元素时,oldCapacity 为0,经比较后第一个if判断成立,newCapacity 赋值为minCapacity(minCapacity = 10)。但是第二个if判断不会成立,即newCapacity 不比 MAX_ARRAY_SIZE大,则不会进入 hugeCapacity 方法。数组容量为10,add方法中 return true,size增为1。
  • 当add第11个元素进入grow方法时,newCapacity为15,比minCapacity(为11)大,第一个if判断不成立。新容量没有大于数组最大size,不会进入hugeCapacity方法。数组容量扩为15,add方法中return true,size增为11。
  • 以此类推… …

总结:

  • 如果 ArrayList 是通过无参构造创建的,那么初始化时并不会初始化数组的大小,只是把数组标记为通过无参构造初始化的;然后在第一次加入数据时,初始化数组大小为10。

  • 如果超过了最大容量则需要扩容,扩容后的数组大小大概为原数组的1.5倍;扩容需要判断是否超过允许的最大的长度,并做相关的处理。

addAll方法
public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
}

解析:

addAll方法与add方法基本相似

首先将传入的Collection转化为数组,然后将其容量numNew 加上ArrayList的大小,进行判断容量够不够,最后进行数组的拷贝,把传入的所有元素添加到底层数组elementData中,然后把ArrayList中元素个数的size重新计算。

最后如果传入的参数的元素不为空,则返回true,表示把元素添加了进去,如果为空的话,说明没有添加元素,则返回false。

获取ArrayList的大小
// 获取元素大小
public int size() {
    return size;
}

直接返回size即可,因为属性size代表了ArrayList的大小。

获取元素的位置

获取元素第一次出现的索引位置使用indexOf方法;获取元素最后一次出现的索引位置使用lastIndexOf方法。

indexOf方法

// 获取元素第一次出现的索引位置
public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

解析:

当参数是null的时候,循环遍历elementData 判断其位置,直接返回;

当参数不为null的时候,循环遍历elementData 判断其位置,直接返回;

如果没有找到则返回-1。

lastIndexOf方法

// 获取元素最后一次出现的索引位置
public int lastIndexOf(Object o) {
    if (o == null) {
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

解析:

与indexOf方法类似,只不过遍历elementData是从后向前遍历的。

判断是否包含某个元素contains
// 判断是否包含某个元素
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

解析:

直接调用上面的indexof()方法即可,若该方法返回大于0,则存在该元素;否则不存在。

给某个位置设置元素
// 给某个位置设置元素
public E set(int index, E element) {
    // 检查索引位置是否越界
    rangeCheck(index);

    // 查看底层elementData数组的某个元素
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

解析:

首先检查,传入的索引位置是否越界,越界抛出IndexOutOfBoundsException异常,然后获取elementData数组该位置的元素,接着将新元素放入该位置,最后返回该位置的旧值。

在指定位置的地方添加新的元素
public void add(int index, E element) {
    // 范围检查
    rangeCheckForAdd(index);

    // 增加容量
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

解析:

首先检查一下参数索引位置是否符合,其次通过ensureCapacityInternal(size + 1)判断是否需要增加容量,如果需要则扩容,否则不需要则什么也不用做。

然后进行数组数组elementData元素的移动,最后把要添加的元素添加到指定的位置,然后进行元素个数size的累加。

移除指定位置的元素
// 移除指定位置的元素
public E remove(int index) {
    // 范围检查
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

解析:

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

首先,rangeCheck方法对传入的索引参数范围检查,接着获取旧值赋值给变量oldValue;

在移动元素的时候,仍然选择进行数组的拷贝。首先计算需要移动的元素个数(size - index - 1);如果移动的元素的个数大于0,下面进行数组的拷贝(其实就是起到了移动元素的功能);否则的话直接在elementData[–size]处设置为空就可以了。记住最后需要返回移除了的值。

移除指定值的元素
// 移除指定值的元素
public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

// fastRemove方法
 private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

解析:

该方法对传入的参数进行是否为null的判断,两种情况下,对elementData数组进行循环遍历判断,是否与传入的值相等,相等的话调用fastRemove方法 进行元素的移除操作。

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

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

相关文章

fiddler设置手机端抓包看这篇文章就足够了,轻松解决!

fiddler设置手机端抓包 安卓手机抓包 第一步&#xff1a;配置电脑和安卓的相关设置 1、手机和fiddler位于同一个局域网内&#xff1b;首先从fiddler处获取到ip地址和端口号&#xff1a; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; &#xff0c;点…

10年资深码农,聊聊程序员的35岁危机

程序员的一天&#xff0c;日常都在做什么&#xff1f; 很多外行以及初入 IT 行业的从业者&#xff0c;往往只看到了高薪、加班、敲代码的标签&#xff0c;那程序员的日常究竟是什么样的&#xff1f; 当代程序员的一天&#xff1a;聊天、开会、写代码 哪种技术最热门与能拿到高…

Shell编程基础 – for循环

Shell编程基础 – for循环 Shell Scripting Essentials - for Loop 大多数编程语言都有循环的概念和语句。如果想重复一个任务数十次&#xff0c;无论是输入数十次&#xff0c;还是输出数十次&#xff0c;对用户来说都不现实。 因此&#xff0c;我们考虑如何用好Bash Shell编…

(Spring学习06)Spring之循环依赖底层源码解析

什么是循环依赖&#xff1f; 很简单&#xff0c;就是A对象依赖了B对象&#xff0c;B对象依赖了A对象。 比如&#xff1a; // A依赖了B class A{public B b; }// B依赖了A class B{public A a; }那么循环依赖是个问题吗&#xff1f; 如果不考虑Spring&#xff0c;循环依赖并不…

浅谈STL中的分配器

分配器是STL中的六大部件之一&#xff0c;是各大容器能正常运作的关键&#xff0c;但是对于用户而言确是透明的&#xff0c;它似乎更像是一个幕后英雄&#xff0c;永远也不会走到舞台上来&#xff0c;观众几乎看不到它的身影&#xff0c;但是它又如此的重要。作为用户&#xff…

计算机缺少vcruntime140_1.dll的5个解决方法,轻松解决dll缺失问题

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“缺少vcruntime140_1.dll”。这个错误提示通常出现在运行某些程序或游戏时&#xff0c;这个错误通常会导致某些应用程序无法正常运行。那么&#xff0c;如何解决缺少vcruntime140_1.dll的问…

Vue框架学习笔记——事件处理:v-on指令+methods

文章目录 前文提要事件处理的解析过程&#xff0c;v-on:事件名样例代码如下&#xff1a;效果展示图片&#xff1a;v-on:事件名"响应函数"v-on简写形式响应函数添加响应函数传参占位符"$event"注意事项 前文提要 本人仅做个人学习记录&#xff0c;如有错误…

深度学习框架配置

目录 1. 配置cuda环境 1.1. 安装cuda和cudnn 1.1.1. 显卡驱动配置 1.1.2. 下载安装cuda 1.1.3. 下载cudnn&#xff0c;将解压后文件复制到cuda目录下 1.2. 验证是否安装成功 2. 配置conda环境 2.1. 安装anaconda 2.2. conda换源 2.3. 创建conda环境 2.4. pip换源 3.…

Linux以nohup方式运行jar包

1、在需要运行的jar包同级目录下建立启动脚本文件&#xff1a; 文件内容&#xff1a; #! /bin/bash #注意&#xff1a;必须有&让其后台执行&#xff0c;否则没有pid生成 jar包路径为绝对路径 nohup java -jar /usr/local/testDemo/jdkDemo-0.0.1-SNAPSHOT.jar >/us…

MX6ULL学习笔记 (一)交叉工具链的安装

前言&#xff1a; ARM 裸机、Uboot 移植、Linux 移植这些都需要在 Ubuntu 下进行编译&#xff0c;编译就需要编译 器&#xff0c;Ubuntu 自带的 gcc 编译器是针对 X86 架构的&#xff01;而我们现在要编译的是 ARM 架构的代码&#xff0c;因为我们编译的代码是需要烧写到ARM板子…

nacos集群开箱搭建-linux版本搭建

原创/朱季谦 nacos是一款易于构建云原生应用的动态服务发现、配置管理和服务管理平台&#xff0c;简单而言&#xff0c;它可以实现类似zookeeper做注册中心的功能&#xff0c;也就是可以在springcloud领域替代Eureka、consul等角色&#xff0c;同时&#xff0c;还可以充当spri…

吴恩达《机器学习》10-6-10-7:学习曲线、决定下一步做什么

一、学习曲线 1. 学习曲线概述 学习曲线将训练集误差和交叉验证集误差作为训练集实例数量&#xff08;m&#xff09;的函数绘制而成。这意味着从较少的数据开始&#xff0c;逐渐增加训练集的实例数量。该方法的核心思想在于&#xff0c;当训练较少数据时&#xff0c;模型可能…

MVVM 模式与 MVC 模式:构建高效应用的选择

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

3.2 Windows驱动开发:内核CR3切换读写内存

CR3是一种控制寄存器&#xff0c;它是CPU中的一个专用寄存器&#xff0c;用于存储当前进程的页目录表的物理地址。在x86体系结构中&#xff0c;虚拟地址的翻译过程需要借助页表来完成。页表是由页目录表和页表组成的&#xff0c;页目录表存储了页表的物理地址&#xff0c;而页表…

数字阅读用户规模持续增长 5.3亿人享受数字化阅读便利

近日,鲁迅长孙周令飞在接受采访时表示,自己“现在90%的时间刷视频,10%的时间看书”,引发网友热议。不少网友表示,鲁迅的孙子都花90%的时间刷视频,难怪现在没人看书了,其实这并不奇怪,也并不表明没人看书,而是读屏与读书并重的时代,纸质阅读与数字阅读共同构成了日常的阅读模式。…

机器学习常用距离度量方法

机器学习常用距离度量方法 前言一、前期准备二、距离度量方法1. 欧氏距离2.曼哈顿距离3.切比雪夫距离4. 闵可夫斯基距离 总结 前言 机器学习中往往通过度量来研究不同样本或数据集之间的差异性&#xff0c;合适的度量方式可以显著提高算法的准确率&#xff0c;因此在接下来的内…

js逆向-某赞滑块

声明 本文仅供学习参考&#xff0c;如有侵权可私信本人删除&#xff0c;请勿用于其他途径&#xff0c;违者后果自负&#xff01; 如果觉得文章对你有所帮助&#xff0c;可以给博主点击关注和收藏哦&#xff01; 前言 目标网站&#xff1a;aHR0cHM6Ly9hY2NvdW50LnlvdXphbi5j…

科普 | 隧道代理IP,简化操作提升安全性

随着数字化时代的深入发展&#xff0c;企业对网络数据的依赖日益增强。在这样的背景下&#xff0c;隧道代理IP正在以其独特的优势改变传统的网络代理模式&#xff0c;为企业级数据采集领域带来革命性的变革。 隧道代理IP技术简介 隧道代理IP通过云端服务器实现自动化的HTTP代理…

检验科LIS系统源码,LIS系统,检验数据分析,生成检验报告

检验科LIS系统源码&#xff0c;全套LIS系统商业项目源码 LIS是HIS系统的一个重要的组成部分&#xff0c;其主要功能是将检验的实验仪器传出的检验数据经分析&#xff0c;生成检验报告&#xff0c;通过网络存储在数据库中&#xff0c;这样医生能够方便、及时的看到患者的检验结果…

96.STL-遍历算法 transform

目录 transform 语法&#xff1a; 功能描述&#xff1a; 函数原型&#xff1a; 代码示例&#xff1a; transform 是 C 标准模板库&#xff08;STL&#xff09;中的一个算法&#xff0c;用于对一个范围内的元素进行转换并将结果存储到另一个范围。以下是简要解释和一个示例…