MyBatis源码分析の配置文件解析

news2025/3/16 7:09:16

文章目录

  • 前言
  • 一、SqlSessionFactoryBuilder
    • 1.1、XMLConfigBuilder
    • 1.2、parse
  • 二、mappers标签的解析
    • 2.1、cacheElement
      • 2.1.1、缓存策略
    • 2.2、buildStatementFromContext
      • 2.2.1、sql的解析


前言

  本篇主要介绍MyBatis源码中的配置文件解析部分。MyBatis是对于传统JDBC的封装,屏蔽了传统JDBC与数据库进行交互,组装参数,获取查询结果并自己封装成对象的繁琐过程。
  原生MyBatis首先需要配置mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <properties resource="jdbc.properties"/>
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

  并且指定数据源jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

  创建数据库访问层接口:

public interface UserMapper {
    List<User> selectAll();

    User selectById(int id);

    void insert(User user);

    void update(User user);

    void delete(int id);
}

  以及对应的xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mybatis.mapper.UserMapper">
    <cache/>
    <resultMap id="userResultMap" type="com.example.mybatis.entity.User">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
    </resultMap>

    <select id="selectAll" resultMap="userResultMap">
        SELECT * FROM users
    </select>

    <select id="selectById" resultMap="userResultMap" parameterType="int">
        SELECT * FROM users WHERE id = #{id}
    </select>

    <insert id="insert" parameterType="com.example.mybatis.entity.User">
        INSERT INTO users (name, age) VALUES (#{name}, #{age})
    </insert>

    <update id="update" parameterType="com.example.mybatis.entity.User">
        UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}
    </update>

    <delete id="delete" parameterType="int">
        DELETE FROM users WHERE id = #{id}
    </delete>
</mapper>

  mybatis-config.xml常见的标签:

标签作用
<settings>控制 MyBatis 全局行为(缓存、懒加载、日志等)
<typeAliases>设置类型别名,简化 Mapper XML 中类名书写
<typeHandlers>自定义类型转换器(Java类型 ↔ JDBC类型)
<plugins>注册插件(如分页插件、SQL打印等)
<objectFactory>自定义对象创建逻辑
<environments>配置数据库环境及事务管理
<mappers>注册 Mapper 映射文件或 Mapper 接口

  原生MyBatis的使用,其中读取配置文件并进行解析,主要体现在SqlSessionFactoryBuilderbuild方法中:

public class Main {
    public static void main(String[] args) throws Exception {
        //将xml构筑成configuration配置类
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        //解析xml,注册成SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

        try (SqlSession session = sqlSessionFactory.openSession()) {

            User user = session.selectOne("com.example.mybatis.mapper.UserMapper.selectById", 1);

            System.out.println(user);
        }
    }
}

一、SqlSessionFactoryBuilder

1.1、XMLConfigBuilder

  在调用SqlSessionFactoryBuilderbuild方法时,首先会去创建一个XMLConfigBuilder,目的是构建一个XML配置文件解析器对象。
在这里插入图片描述  其中的核心代码,这段代码的作用是注册别名,将配置文件中的 “JDBC”、"POOLED"等关键词和实际的类型进行绑定。
在这里插入图片描述

别名实际类用途
"JDBC"JdbcTransactionFactoryJDBC事务管理器(默认事务方式)
"MANAGED"ManagedTransactionFactory受容器管理的事务(如 Spring)
"JNDI"JndiDataSourceFactory从 JNDI 获取数据源
"POOLED"PooledDataSourceFactory数据库连接池(MyBatis 内置)
"UNPOOLED"UnpooledDataSourceFactory不使用连接池的数据源
"PERPETUAL"PerpetualCache永久缓存
"FIFO"FifoCache先进先出缓存
"LRU"LruCache最近最少使用缓存
"SOFT"SoftCache基于 SoftReference 的缓存
"WEAK"WeakCache基于 WeakReference 的缓存
"DB_VENDOR"VendorDatabaseIdProvider根据数据库类型自动切换 SQL
"XML"XMLLanguageDriverMyBatis 默认的 XML SQL 语言驱动器
"RAW"RawLanguageDriver原生 SQL 写法语言驱动器
"SLF4J"Slf4jImpl使用 SLF4J 的日志输出
"COMMONS_LOGGING"JakartaCommonsLoggingImpl使用 Commons Logging 日志
"LOG4J"Log4jImpl使用 Log4j 日志
"LOG4J2"Log4j2Impl使用 Log4j2 日志
"JDK_LOGGING"Jdk14LoggingImpl使用 JDK 内建日志
"STDOUT_LOGGING"StdOutImpl输出日志到控制台
"NO_LOGGING"NoLoggingImpl不输出日志
"CGLIB"CglibProxyFactory使用 CGLIB 动态代理
"JAVASSIST"JavassistProxyFactory使用 Javassist 动态代理

1.2、parse

  真正解析配置文件的是利用上一步构造出的XMLConfigBuilderparse方法,首先会进行判断,如果已经解析过,则抛出异常,不会重复解析:
在这里插入图片描述  否则就将标记设置为true。并且执行parseConfiguration方法,从根节点进行解析:
  每一行都对应了一个 <mybatis-config.xml> 中的标签,逐步填充 Configuration 对象内容:
在这里插入图片描述

/**
 * 解析 <configuration> 根节点的各个子标签,并将配置信息填充到 Configuration 对象中
 */
private void parseConfiguration(XNode root) {
  try {
    // 【1】先解析 <properties> 标签(必须最优先解析),以便后续标签中的占位符 ${} 能被正确替换
    propertiesElement(root.evalNode("properties"));

    // 【2】解析 <settings> 标签,将其内容转换为 Properties 对象
    Properties settings = settingsAsProperties(root.evalNode("settings"));

    // 【3】解析 settings 中的 vfsImpl 属性(如果配置了自定义 VFS 实现类)
    loadCustomVfs(settings);

    // 【4】解析 settings 中的 logImpl 属性(设置日志实现类,如 LOG4J、STDOUT_LOGGING 等)
    loadCustomLogImpl(settings);

    // 【5】解析 <typeAliases> 标签,注册用户自定义的别名或包扫描别名
    typeAliasesElement(root.evalNode("typeAliases"));

    // 【6】解析 <plugins> 标签,注册 MyBatis 插件(如分页插件、SQL 拦截器等)
    pluginElement(root.evalNode("plugins"));

    // 【7】解析 <objectFactory> 标签,设置自定义对象工厂(用于实例化结果对象)
    objectFactoryElement(root.evalNode("objectFactory"));

    // 【8】解析 <objectWrapperFactory> 标签,自定义对象包装器(封装结果对象属性访问行为)
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

    // 【9】解析 <reflectorFactory> 标签,自定义反射器工厂(高级反射行为控制)
    reflectorFactoryElement(root.evalNode("reflectorFactory"));

    // 【10】将 <settings> 中的配置项应用到 Configuration 对象中
    settingsElement(settings);

    // 【11】解析 <environments> 标签,注册事务管理器和数据源配置(必须在 objectFactory 之后执行)
    environmentsElement(root.evalNode("environments"));

    // 【12】解析 <databaseIdProvider> 标签,支持数据库厂商识别(如区分 MySQL、Oracle)
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));

    // 【13】解析 <typeHandlers> 标签,注册自定义类型处理器(TypeHandler)
    typeHandlerElement(root.evalNode("typeHandlers"));

    // 【14】解析 <mappers> 标签,加载 Mapper 映射器(包括 XML 和接口方式)
    mapperElement(root.evalNode("mappers"));

  } catch (Exception e) {
    // 如果解析过程中发生异常,则封装为 BuilderException 抛出
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

  当解析完成后,会得到一个configuration对象,其中就包含了配置文件中的各种值。相当于此时的xml配置文件已经转化为了configuration对象。最后还会将其再次包装成SqlSessionFactory,后续会利用SqlSessionFactory进行sql相关逻辑的执行。
在这里插入图片描述  其中最关键的是mappers标签的解析。

二、mappers标签的解析

  mapperElement方法,首先会拿到mappers根标签,然后进行解析。

/**
 * 解析 <mappers> 标签,支持三种加载方式:package、resource/url、class
 */
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    // 遍历 <mappers> 下的所有子节点(可能是 <package> 或 <mapper>)
    for (XNode child : parent.getChildren()) {

      // 情况1:<package name="com.xxx.mapper"/>,批量注册包下所有 Mapper 接口
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        // 自动扫描指定包下的所有接口,并注册到 Configuration 中
        configuration.addMappers(mapperPackage);

      } else {
        // 情况2~4:单个 <mapper> 节点,通过 resource/url/class 指定加载方式
        String resource = child.getStringAttribute("resource"); // 从 classpath 中加载 Mapper XML
        String url = child.getStringAttribute("url");           // 从网络路径加载 Mapper XML
        String mapperClass = child.getStringAttribute("class"); // 直接加载 Mapper 接口类

        // 情况2:只指定 resource,加载 Mapper XML 文件
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource); // 设置错误上下文信息
          try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(
              inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse(); // 解析 Mapper XML,注册语句映射
          }

        // 情况3:只指定 url,加载远程 Mapper XML 文件
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          try (InputStream inputStream = Resources.getUrlAsStream(url)) {
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(
              inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse(); // 同样调用解析逻辑
          }

        // 情况4:只指定 class,注册 Mapper 接口类(无 XML 时适用)
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface); // 注册接口类到 MapperRegistry

        // 情况5:配置冲突,三种方式只能选一种,否则抛异常
        } else {
          throw new BuilderException(
            "A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

  案例中对应的是情况2,首先会注册一个mapper解析器,然后调用其parse方法对案例中UserMapper.xml进行解析,在该方法中,首先会进行判断,如果已经进行过解析,则不会重复解析。
在这里插入图片描述  解析的核心方法在于configurationElement,同样是对于xml中的各种标签再次分类解析:
在这里插入图片描述  这里重点看一下cacheElement以及buildStatementFromContext

2.1、cacheElement

  cacheElement和Mybatis的二级缓存有关。简单的说,Mybatis有两级缓存:

  • 一级缓存是SqlSession 级别的,并且默认开启。
  • 二级缓存是Mapper 映射级别,默认不开启,如果需要,应该在某个mapper.xml中使用cache标签开启。

  cacheElement方法正是解析mapper.xml中的cache标签:

/**
 * 解析 <cache> 标签,构建二级缓存对象并注册到 Configuration 中。
 */
private void cacheElement(XNode context) {
  // 1. 判断 <cache> 标签是否存在
  if (context != null) {

    // 2. 解析缓存类型(默认是 PERPETUAL,即 PerpetualCache)
    String type = context.getStringAttribute("type", "PERPETUAL");
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);

    // 3. 解析缓存淘汰策略(默认是 LRU,即最近最少使用)
    String eviction = context.getStringAttribute("eviction", "LRU");
    Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);

    // 4. 缓存刷新间隔(可选):指定自动清空缓存的时间(毫秒)
    Long flushInterval = context.getLongAttribute("flushInterval");

    // 5. 缓存大小(可选):最大缓存对象个数
    Integer size = context.getIntAttribute("size");

    // 6. 是否为读写缓存(readOnly=false 表示使用序列化;true 表示共享引用)
    //    readWrite = true 表示开启对象副本,确保线程安全
    boolean readWrite = !context.getBooleanAttribute("readOnly", false);

    // 7. 是否阻塞:当缓存正在被其他线程刷新时,是否阻塞等待
    boolean blocking = context.getBooleanAttribute("blocking", false);

    // 8. 获取 <cache> 中配置的其他 <property> 子节点
    Properties props = context.getChildrenAsProperties();

    // 9. 构建缓存并注册到 Configuration,封装为 MapperBuilderAssistant.useNewCache()
    builderAssistant.useNewCache(
      typeClass,          // 缓存类型类(如 PerpetualCache)
      evictionClass,      // 淘汰策略类(如 LruCache)
      flushInterval,      // 缓存刷新间隔
      size,               // 缓存容量
      readWrite,          // 是否使用读写模式
      blocking,           // 是否阻塞模式
      props               // 自定义属性
    );
  }
}

  在useNewCache中,最终会调用CacheBuilderbuild方法:
在这里插入图片描述   build方法中运用到了装饰器模式,所有的Cache都实现了一个共同的父类Cache。
  在**cache = newCacheDecoratorInstance(decorator, cache);这一行代码中,传入LruCache和当前的Cache实例(PERPETUAL),将PERPETUAL包装到LRU中:(LruCache的delegate属性,指向的是传入的PerpetualCache实例)
在这里插入图片描述
在这里插入图片描述  然后继续执行到
cache = setStandardDecorators(cache);**这一行代码,会继续进行装饰器的包装:
在这里插入图片描述  setStandardDecorators方法,对于Cache实例层层包装,赋值给各自的delegate属性:
在这里插入图片描述  包装完成的层次:SynchronizedCache线程同步缓存区->LoggingCache统计命中率以及打印日志->SerializedCache序列化->LruCache最少使用->PerpetualCache默认。
在这里插入图片描述

2.1.1、缓存策略

  默认的PerpetualCache,使用的是HashMap进行存储。
在这里插入图片描述
  而LruCache,为了实现最近最少使用的机制,使用了LinkedHashMap的数据结构,并且重写了它的removeEldestEntry方法,关键在于,LinkedHashMap构造时第三个参数为 true 表示按访问顺序排列:
在这里插入图片描述

LruCache cache = new LruCache(new PerpetualCache("myCache"));
cache.setSize(3);

cache.put("A", 1);  // A
cache.put("B", 2);  // A B
cache.put("C", 3);  // A B C
cache.get("A");     // B C A (A 被访问过,移到尾部)
cache.put("D", 4);  // C A D(B 被淘汰)

  SynchronizedCache,每个方法上通过加synchronized保证线程安全:
在这里插入图片描述   LoggingCache,会记录日志,以及统计缓存命中次数:
在这里插入图片描述

2.2、buildStatementFromContext

  buildStatementFromContext是用来解析 select、insert、update、delete 标签中sql语句的方法,首先会解析出这些节点,然后进行循环,获取到XMLStatementBuilder后,执行parseStatementNode方法:
在这里插入图片描述  在parseStatementNode方法中有几个关键点,这一段代码会判断当前的标签是否为select,如果是select标签,则不会清除一级缓存(增删改会清除),以及判断是否使用二级缓存(默认 select 使用)
在这里插入图片描述

2.2.1、sql的解析

  真正执行解析sql的是下图中的代码:
在这里插入图片描述  同样地会先去构建一个XMLScriptBuilder,然后调用其parseScriptNode方法进行解析:
在这里插入图片描述  在parseScriptNode方法中,首先会解析 SQL 标签中的所有子标签,然后去进行判断:

  • 包含动态 SQL(即是否包含 if、choose、${} 等动态节点)构建 DynamicSqlSource(运行时动态拼接 SQL)
  • 不包含动态 SQL(即是否包含 if、choose、${} 等动态节点)构建 RawSqlSource(直接编译成静态 SQL,提升效率)

在这里插入图片描述  MixedSqlNode对象,实现了SqlNode接口,SqlNode是所有动态 SQL节点的统一接口,而MixedSqlNode代表了 一整个 SQL 脚本块,比如select标签中所有内容就会变成一个 MixedSqlNode。

SqlNode 接口

├── MixedSqlNode // 组合节点
├── StaticTextSqlNode // 静态文本节点:普通 SQL 字符串
├── TextSqlNode // 动态文本节点:包含 ${}
├── IfSqlNode // if 标签
├── ChooseSqlNode // choose/when/otherwise
├── ForEachSqlNode // foreach
├── WhereSqlNode // where
├── TrimSqlNode // trim
├── SetSqlNode // set
└── BindSqlNode // bind

  用一个案例说明,假如我在mapper.xml中定义了如下的sql语句:

<select id="findUser" parameterType="map" resultType="User">
  SELECT * FROM user
  <where>
    <if test="name != null">
      AND name = #{name}
    </if>
    <if test="age != null">
      AND age = #{age}
    </if>
  </where>
</select>

  则生成的结构如下:

MixedSqlNode
├── StaticTextSqlNode(“SELECT * FROM user”)
└── WhereSqlNode
└── MixedSqlNode
├── IfSqlNode(test=“name != null”) → TextSqlNode(“AND name = #{name}”)
└── IfSqlNode(test=“age != null”) → TextSqlNode(“AND age = #{age}”)


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

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

相关文章

python爬虫笔记(一)

文章目录 html基础标签和下划线无序列表和有序列表表格加边框 html的属性a标签&#xff08;网站&#xff09;target属性换行线和水平分割线 图片设置宽高width&#xff0c;height html区块——块元素与行内元素块元素与行内元素块元素举例行内元素举例 表单from标签type属性pla…

DC-6靶机详解

一、主机发现 arp-scan -l靶机ip为192.168.55.159 二、端口扫描、目录枚举、指纹识别、 2.1端口扫描 nmap 192.168.55.159发现没有开放特殊端口 看来信息收集的重点要放在网页中了 2.2目录枚举 dirb http://192.168.55.1592.3指纹识别 nmap 192.168.55.159 -sV -sC -O …

STM32-SPI通信外设

目录 一&#xff1a;SPI外设简介 SPI框图​编辑 SPI逻辑 ​编辑 主模式全双工连续传输 ​编辑 非连续传输 二&#xff1a;硬件SPI读写W25Q64 1.接线&#xff1a; 2. 代码 SPI外设的初始化 生成时序 一&#xff1a;SPI外设简介 STM32内部集成了硬件SPI收发电路&#…

远程控制中的云电脑是什么意思?1分钟学会用

很多常用我们ToDesk远程控制的朋友们或许会注意到无论是在PC端还是移动端中都出现有【云电脑】【来云电脑爽玩-新用户免费1小时】这些词句等信息。那么这究竟是代表什么意思呐&#xff1f;云电脑是什么又怎么用呐&#xff1f;为什么要增加云电脑&#xff1f;以下小编就为大家科…

网络爬虫【简介】

我叫补三补四&#xff0c;很高兴见到大家&#xff0c;欢迎一起学习交流和进步 今天来讲一讲视图 一、网络爬虫的定义 网络爬虫&#xff08;Web Crawler&#xff09;&#xff0c;又称为网络蜘蛛、网络机器人等&#xff0c;是一种按照一定规则自动抓取互联网信息的程序或脚本。它…

2024华东师范大学计算机复试上机真题

2024华东师范大学计算机复试机试真题 2023华东师范大学计算机复试机试真题 2022华东师范大学计算机复试机试真题 2024华东师范大学计算机复试上机真题 2023华东师范大学计算机复试上机真题 2022华东师范大学计算机复试上机真题 在线评测&#xff1a;传动门&#xff1a;pgcode…

14.使用各种读写包操作 Excel 文件:辅助模块

一 各种读写包 这些是 pandas 在底层使用的各种读写包。无须安装 pandas&#xff0c;直接使用这些读写包就能够读写 Excel 工作簿。可以尽可能地使用 pandas 来解决这类问题&#xff0c;只在 pandas 没有提供你所需要的功能时才用到读写包。 表中没有 xlwings &#xff0c;因为…

Python数据分析之数据可视化

Python 数据分析重点知识点 本系列不同其他的知识点讲解&#xff0c;力求通过例子让新同学学习用法&#xff0c;帮助老同学快速回忆知识点 可视化系列&#xff1a; Python基础数据分析工具数据处理与分析数据可视化机器学习基础 四、数据可视化 图表类型与选择 根据数据特…

1、操作系统引论

一、操作系统 会使用linux系统 建议大家先学会linux的基础指令&#xff0c;可以看菜鸟教程网站进行学习。 1、各种定义 操作系统定义 管理计算机的 硬件 和软件资源&#xff0c; 能对各类作业进行调度&#xff0c;方便用户使用计算机的程序集合。操作系统运行在内核态&#xf…

HarmonyOS NEXT - 网络请求问题(http)

HTTP&#xff08;HyperText Transfer Protocal&#xff0c;超文本传输协议&#xff09;是一种用于传输超媒体文档&#xff08;如HTML&#xff09;的应用层协议&#xff0c;它是客户端和服务器之间通信的基础&#xff1b;无论是获取数据、提交表单、上传文件&#xff0c;HTTP都扮…

告别旧版本,功能全面升级!

小伙伴们&#xff0c;今天来给大家唠唠一款超经典的软件——格式工厂&#xff01;相信很多人都不陌生吧&#xff1f;它可是早期超多人用的视频格式转换工具呢&#xff01;但随着软件行业的发展&#xff0c;它慢慢被其他工具代替了&#xff0c;像万兴、小丸、AME这些新宠儿一出现…

Obsidian Copilot:打造你的专属 AI 笔记助手

Obsidian Copilot作为一款非常受欢迎的Obsidian插件&#xff0c;不仅极大地提升了用户的笔记管理和信息检索效率&#xff0c;还通过其多样化的AI功能为用户带来了前所未有的便捷体验。本文将详细介绍Obsidian Copilot的核心特点、使用方法及个人体验分享。 核心特点 Obsidian…

VPC4-通达oa-docker逃逸-shiro反序列化-hash传递-CrackMapExec喷射-历史ptt攻击-进程注入

由于本人是菜鸡&#xff0c;不会免杀&#xff0c;所有免杀的部分就直接跳过了 &#xff08;hhh) 靶场地址&#xff1a; 链接: https://pan.baidu.com/s/1Fh1Zg79n1yjCPe6rrQ2apA 提取码: qiag 第一台ubuntu(docker逃逸&#xff0c;shiro反序列化&#xff09; fscan扫到一…

C++类与对象——拷贝构造与运算符重载

拷贝构造函数和赋值运算符重载就是C类默认六个函数之二。 拷贝构造函数&#xff1a; 如果⼀个构造函数的第⼀个参数是自身类类型的引用&#xff0c;且任何额外的参数都有默认值&#xff0c;则此构造函数 也叫做拷贝构造函数&#xff0c;也就是说拷贝构造是⼀个特殊的构造函数…

疗养院管理系统设计与实现(代码+数据库+LW)

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装疗养院管理系统软件来发挥其高效地信息处理的作用&#xf…

2024年12月CCF-GESP编程能力等级认证C++编程四级真题解析

四级真题的难度: 一、总体难度评价 CCF-GESP编程能力等级认证C++四级真题的难度通常被认为相对较高。它不仅要求考生具备扎实的C++编程基础,还需要考生掌握一定的算法和数据结构知识,以及良好的问题解决能力。 二、具体难度分析 ‌理论知识考察‌: 单选题和判断题中,会涉…

MySQL开发陷阱与最佳实践:第1章:MySQL开发基础概述-1.1 MySQL简介与应用场景

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 MySQL开发陷阱与最佳实践&#xff1a;第1章&#xff1a;MySQL开发基础概述-1.1 MySQL简介与应用场景1.1.1 MySQL的发展历程与市场地位1.1.2 MySQL的核心特性与技术优势1.1.2…

使用GitHub Actions实现Git推送自动部署到服务器

将网站一键部署到服务器的方案很多&#xff0c;比如纯Shell脚本结合SSH、Jenkins等工具。本文将介绍如何利用GitHub Actions这一免费且轻量的CI/CD工具&#xff0c;实现代码推送后自动部署到云服务器。 之前一直在使用github的工作流&#xff0c;确实是一个比较好用的工具。 我…

PyTorch 系列教程:探索自然语言处理应用

本文旨在介绍如何使用PyTorch进行自然语言处理&#xff08;NLP&#xff09;的基础知识&#xff0c;包括必要的库、概念以及实际代码示例。通过阅读本文&#xff0c;您将能够开始您的NLP之旅。 1. 理解PyTorch PyTorch是一个开源的机器学习库&#xff0c;基于Torch库&#xff0…

3.14-1列表

列表 一.列表的介绍和定义 1 .列表 类型: <class list> 2.符号:[] 3.定义列表: 方式1:[] 通过[] 来定义 list[1,2,3,4,6] print(type(list)) #<class list> 方式2: 通过list 转换 str2"12345" print(type(str2)) #<class str> list2lis…