字段临时缓存包装器

前言

在实际开发中,我们有时候存在一种需求,例如对于某个字段,我们希望在某个明确的保存节点前对字段的修改都仅作为缓存保留,最终是否应用这些修改取决于某些条件,比如玩家对游戏设置的修改可能需要玩家明确确认应用修改后才会保存下来,在此之前玩家在游戏界面上的所有修改都是临时的。


本文基于这个需求探索出了一种解决方案“字段临时缓存包装器”,通过创建字段或用于存储字段临时数据的数据结构的副本来实现临时缓存,虽然我们同样可以采用直接声明一个副本字段的方式来达到同样的目的,但是这可能会增加冗余代码,且不利于代码的维护,通过包装器来封装临时缓存的通用逻辑,与具体业务逻辑隔离。

代码

v1.0
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Newtonsoft.Json;

/// <summary>
/// 临时包装器
/// </summary>
/// <typeparam name="T">字段类型</typeparam>
/// <remarks>
/// 该类主要用于创造某个字段的副本作为该字段的临时缓存,避免直接修改源字段。
/// </remarks>
public class TempWrapper<T> : IDisposable
{
    /// <summary>
    /// 是否为值类型
    /// <para>提示:若为true则表示包装字段为值类型,否则为引用类型</para>
    /// </summary>
    public static bool isValueType => _isValueType;

    /// <summary>
    /// 缓存字段
    /// <para>提示:对于值类型而言,该属性涉及拷贝</para>
    /// </summary>
    public T value
    {
        get
        {
            if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");
            return _value;
        }
        set
        {
            if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");
            _value = value;
        }
    }

    /// <summary>
    /// 获取引用
    /// <para>提示:对于值类型而言,该属性直接返回引用从而避免拷贝</para>
    /// </summary>
    public ref T refrence
    {
        get
        {
            if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");
            return ref _value;
        }
    }

    /// <summary>
    /// 是否已经释放
    /// </summary>
    public bool isDisposed => _isDisposed;

    static readonly bool _isValueType = typeof(T).IsValueType;
    static readonly bool _isDisposable = typeof(IDisposable).IsAssignableFrom(typeof(T));
    static readonly object _key = new object();
    T _value;
    bool _isDisposed;

    TempWrapper() { }

    /// <summary>
    /// 包装指定字段并返回包装类
    /// </summary>
    /// <param name="value">待包装字段的引用</param>
    /// <remarks>
    /// <para>提示:采用二进制序列化和反序列化生成字段副本</para>
    /// <para>提示:该方法仅可用于被 <c>Serializable</c> 标记的字段类型</para>
    /// </remarks>
    public static TempWrapper<T> WrapByBinary(ref T value)
    {
        lock (_key)
        {
            try
            {
                TempWrapper<T> wrapper = new TempWrapper<T>();

                if (_isValueType) wrapper._value = value;
                else
                {
                    using (MemoryStream ms = new MemoryStream())
                    {
                        IFormatter formatter = new BinaryFormatter();
                        formatter.Serialize(ms, value);
                        ms.Seek(0, SeekOrigin.Begin);
                        wrapper._value = (T)formatter.Deserialize(ms);
                    }
                }

                return wrapper;
            }
            catch (Exception e)
            {
                throw new InvalidOperationException("Failed to wrap.", e);
            }

        }
    }

    /// <summary>
    /// 包装指定字段并返回包装类
    /// <para>提示:采用JSON序列化和反序列化生成字段副本</para>
    /// </summary>
    /// <param name="value">待包装字段的引用</param>
    public static TempWrapper<T> WrapByJson(ref T value)
    {
        lock (_key)
        {
            try
            {
                TempWrapper<T> wrapper = new TempWrapper<T>();

                if (_isValueType) wrapper._value = value;
                else
                {
                    string jsonStr = JsonConvert.SerializeObject(value);
                    wrapper._value = JsonConvert.DeserializeObject<T>(jsonStr);
                }

                return wrapper;
            }
            catch (Exception e)
            {
                throw new InvalidOperationException("Failed to wrap.", e);
            }
        }
    }

    /// <summary>
    /// 包装生成器所生成的字段并返回包装类
    /// </summary>
    /// <param name="creator">生成器</param>
    public static TempWrapper<T> WrapByCustom(Func<T> creator)
    {
        lock (_key)
        {
            try
            {
                TempWrapper<T> wrapper = new TempWrapper<T>() { _value = creator() };
                return wrapper;
            }
            catch (Exception e)
            {
                throw new InvalidOperationException("Failed to wrap.", e);
            }
        }
    }

    /// <summary>
    /// 解包包装器并赋值给指定的字段
    /// </summary>
    /// <remarks>
    /// <para>提示:采用二进制序列化和反序列化解包</para>
    /// <para>提示:该方法仅可用于被 <c>Serializable</c> 标记的字段类型</para>
    /// </remarks>
    public void UnWrapByBinary(ref T value)
    {
        if (_isValueType) value = _value;
        else
        {
            lock (_key)
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    IFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(ms, _value);
                    ms.Seek(0, SeekOrigin.Begin);
                    value = (T)formatter.Deserialize(ms);
                }
            }
        }
    }

    /// <summary>
    /// 解包包装器并赋值给指定的字段
    /// <para>提示:采用JSON序列化和反序列化解包</para>
    /// </summary>
    public void UnwrapByJson(ref T value)
    {
        if (_isValueType) value = _value;
        else
        {
            lock (_key)
            {
                string jsonStr = JsonConvert.SerializeObject(_value);
                value = JsonConvert.DeserializeObject<T>(jsonStr);
            }
        }
    }

    /// <summary>
    /// 释放包装器所包装的字段
    /// <para>提示:当所包装字段实现了IDisposable接口时该方法才有效</para>
    /// </summary>
    public void Dispose()
    {
        if (_isDisposed) return;

        DoDispose(true);
        GC.SuppressFinalize(this);
    }

    void DoDispose(bool disposing)
    {
        if (_isDisposed) return;

        _isDisposed = true;
        if (disposing && _isDisposable && _value is IDisposable ds)
            ds.Dispose();
    }

    ~TempWrapper()
    {
        DoDispose(false);
    }
}
v1.1 
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Newtonsoft.Json;

/// <summary>
/// 临时包装器
/// </summary>
/// <typeparam name="T">字段类型</typeparam>
/// <remarks>
/// 该类主要用于创造某个字段的副本作为该字段的临时缓存,避免直接修改源字段。
/// </remarks>
public class TempWrapper<T> : IDisposable
{
    /// <summary>
    /// 是否为值类型
    /// <para>提示:若为true则表示包装字段为值类型,否则为引用类型</para>
    /// </summary>
    public static bool isValueType => _isValueType;

    /// <summary>
    /// 缓存字段
    /// <para>提示:对于值类型而言,该属性涉及拷贝</para>
    /// </summary>
    public T value
    {
        get
        {
            if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");
            return _value;
        }
        set
        {
            if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");
            _value = value;
        }
    }

    /// <summary>
    /// 获取引用
    /// <para>提示:对于值类型而言,该属性直接返回引用从而避免拷贝</para>
    /// </summary>
    public ref T refrence
    {
        get
        {
            if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");
            return ref _value;
        }
    }

    static readonly bool _isValueType = typeof(T).IsValueType;
    static readonly bool _isDisposable = typeof(IDisposable).IsAssignableFrom(typeof(T));
    static readonly object _key = new object();
    T _value;
    bool _isDisposed;

    TempWrapper() { }

    /// <summary>
    /// 包装指定字段并返回包装类
    /// </summary>
    /// <param name="value">待包装字段的引用</param>
    /// <remarks>
    /// <para>提示:采用二进制序列化和反序列化生成字段副本</para>
    /// <para>提示:该方法仅可用于被 <c>Serializable</c> 标记的字段类型</para>
    /// </remarks>
    public static TempWrapper<T> WrapByBinary(ref T value)
    {
        lock (_key)
        {
            try
            {
                TempWrapper<T> wrapper = new TempWrapper<T>();

                if (_isValueType) wrapper._value = value;
                else
                {
                    using (MemoryStream ms = new MemoryStream())
                    {
                        IFormatter formatter = new BinaryFormatter();
                        formatter.Serialize(ms, value);
                        ms.Seek(0, SeekOrigin.Begin);
                        wrapper._value = (T)formatter.Deserialize(ms);
                    }
                }

                return wrapper;
            }
            catch (Exception e)
            {
                throw new InvalidOperationException("Failed to wrap.", e);
            }

        }
    }

    /// <summary>
    /// 包装指定字段并返回包装类
    /// <para>提示:采用JSON序列化和反序列化生成字段副本</para>
    /// </summary>
    /// <param name="value">待包装字段的引用</param>
    public static TempWrapper<T> WrapByJson(ref T value)
    {
        lock (_key)
        {
            try
            {
                TempWrapper<T> wrapper = new TempWrapper<T>();

                if (_isValueType) wrapper._value = value;
                else
                {
                    string jsonStr = JsonConvert.SerializeObject(value);
                    wrapper._value = JsonConvert.DeserializeObject<T>(jsonStr);
                }

                return wrapper;
            }
            catch (Exception e)
            {
                throw new InvalidOperationException("Failed to wrap.", e);
            }
        }
    }

    /// <summary>
    /// 包装生成器所生成的字段并返回包装类
    /// </summary>
    /// <param name="creator">生成器</param>
    public static TempWrapper<T> WrapByCustom(Func<T> creator)
    {
        lock (_key)
        {
            try
            {
                TempWrapper<T> wrapper = new TempWrapper<T>() { _value = creator() };
                return wrapper;
            }
            catch (Exception e)
            {
                throw new InvalidOperationException("Failed to wrap.", e);
            }
        }
    }

    /// <summary>
    /// 解包包装器并赋值给指定的字段
    /// </summary>
    /// <remarks>
    /// <para>提示:采用二进制序列化和反序列化解包</para>
    /// <para>提示:该方法仅可用于被 <c>Serializable</c> 标记的字段类型</para>
    /// </remarks>
    public void UnWrapByBinary(ref T value)
    {
        if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");

        if (_isValueType) value = _value;
        else
        {
            using (MemoryStream ms = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(ms, _value);
                ms.Seek(0, SeekOrigin.Begin);
                value = (T)formatter.Deserialize(ms);
            }
        }
    }

    /// <summary>
    /// 解包包装器并赋值给指定的字段
    /// <para>提示:采用JSON序列化和反序列化解包</para>
    /// </summary>
    public void UnwrapByJson(ref T value)
    {
        if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");

        if (_isValueType) value = _value;
        else
        {
            string jsonStr = JsonConvert.SerializeObject(_value);
            value = JsonConvert.DeserializeObject<T>(jsonStr);
        }
    }

    /// <summary>
    /// 释放包装器所包装的字段
    /// <para>提示:当所包装字段实现了IDisposable接口时该方法才有效</para>
    /// </summary>
    public void Dispose()
    {
        if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");

        DoDispose(true);
        GC.SuppressFinalize(this);
    }

    void DoDispose(bool disposing)
    {
        if (_isDisposed) return;

        _isDisposed = true;
        if (disposing && _isDisposable && _value is IDisposable ds)
            ds.Dispose();
    }

    ~TempWrapper()
    {
        DoDispose(false);
    }
}

测试

#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

// TempWrapper测试脚本
public class TempWrapperTest : MonoBehaviour
{
    [SerializeField] int[] intArray;
    [SerializeField] string[] strArray;
    [SerializeField] StructA[] structaArray;
    [SerializeField] ClassA classA;
    [SerializeField] StructA structA;
    [SerializeField] Transform tf;

    [Serializable]
    struct StructA
    {
        public string key;
        public int value;

        public override string ToString()
        {
            return $"(key:{key},value:{value})";
        }
    }

    [Serializable]
    class ClassA
    {
        public string key;
        public StructA structA;

        public override string ToString()
        {
            StringBuilder builder = new StringBuilder("[key:");
            builder.Append(key).Append(",").Append($"StructA:{structA}");
            builder.Append("]");
            return builder.ToString();
        }
    }

    TempWrapper<int[]> intArrayWrapper;
    TempWrapper<string[]> strArrayWrapper;
    TempWrapper<StructA[]> structaArrayWrapper;
    TempWrapper<ClassA> classaWrapper;
    TempWrapper<StructA> structaWrapper;
    TempWrapper<Transform> tfWrapper;

    void Awake()
    {
        if (Application.isPlaying)
        {
            intArrayWrapper = TempWrapper<int[]>.WrapByBinary(ref intArray);
            strArrayWrapper = TempWrapper<string[]>.WrapByJson(ref strArray);
            structaArrayWrapper = TempWrapper<StructA[]>.WrapByBinary(ref structaArray);
            classaWrapper = TempWrapper<ClassA>.WrapByBinary(ref classA);
            structaWrapper = TempWrapper<StructA>.WrapByBinary(ref structA);
            tfWrapper = TempWrapper<Transform>.WrapByCustom(() => Instantiate(tf));
            Instantiate(transform);
        }
    }

    void OnDestroy()
    {
        intArrayWrapper.Dispose();
        strArrayWrapper.Dispose();
        structaArrayWrapper.Dispose();
        classaWrapper.Dispose();
        structaWrapper.Dispose();
        tfWrapper.Dispose();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            PrintWrapper();
            PrintHashCode();
            PrintWrapperHashCode();
        }

        if (Input.GetKeyDown(KeyCode.W))
        {
            WriteWrapper();
            UnWrap();
        }
    }

    void PrintWrapper()
    {
        intArrayWrapper.value.LogC("IntArrayWrapper:");
        strArrayWrapper.value.LogC("StrArrayWrapper:");
        structaArrayWrapper.value.LogC(s => $"[key:{s.key},value:{s.value}]", "StructaArrayWrapper:");
        LogUtility.Log("ClassAWrapper:" + classaWrapper.value);
        LogUtility.Log("StructAWrapper:" + structaWrapper.value);
        LogUtility.Log("TfWrapper:" + tfWrapper.value.position);
    }

    void PrintHashCode()
    {
        LogUtility.Log("IntArray:" + intArray.GetHashCode());
        LogUtility.Log("StrArray:" + strArray.GetHashCode());
        LogUtility.Log("StructaArray:" + structaArray.GetHashCode());
        LogUtility.Log("ClassA:" + classA.GetHashCode());
        LogUtility.Log("StructA:" + structA.GetHashCode());
        LogUtility.Log("Tf:" + tf.GetHashCode());
    }

    void PrintWrapperHashCode()
    {
        LogUtility.Log("IntArrayWrapper:" + intArrayWrapper.value.GetHashCode());
        LogUtility.Log("StrArrayWrapper:" + strArrayWrapper.value.GetHashCode());
        LogUtility.Log("StructaArrayWrapper:" + structaArrayWrapper.value.GetHashCode());
        LogUtility.Log("ClassAWrapper:" + classaWrapper.value.GetHashCode());
        LogUtility.Log("StructAWrapper:" + structaWrapper.value.GetHashCode());
        LogUtility.Log("TfWrapper:" + tfWrapper.value.GetHashCode());
    }

    void WriteWrapper()
    {
        List<int> ints = new List<int>(intArrayWrapper.value) { 99, 100 };
        intArrayWrapper.value = ints.ToArray();

        List<string> strs = new List<string>(strArrayWrapper.value) { "D", "E" };
        strArrayWrapper.value = strs.ToArray();

        List<StructA> strcutAs = new List<StructA>(structaArrayWrapper.value)
        {
            new StructA { key = "D", value = 99 },
            new StructA { key = "E", value = 100 }
        };
        structaArrayWrapper.value = strcutAs.ToArray();

        structaWrapper.refrence.key = "E";
        structaWrapper.refrence.value = 1000;

        classaWrapper.value.key = "DE";
        classaWrapper.value.structA.key = "D";
        classaWrapper.value.structA.value = 999;

        tfWrapper.value.position = Vector3.zero;
    }

    void UnWrap()
    {
        intArrayWrapper.UnWrapByBinary(ref intArray);
        strArrayWrapper.UnwrapByJson(ref strArray);
        structaArrayWrapper.UnWrapByBinary(ref structaArray);
        structaWrapper.UnWrapByBinary(ref structA);
        classaWrapper.UnWrapByBinary(ref classA);
    }
}
#endif
v1.0 
用例ID用例名称前者测试预期结果是否通过
1简单值类型数组可缓存通过
2不可变引用类型数组可缓存通过
3复合值类型数组可缓存通过
4自定义引用类型可缓存通过
5自定义值类型可缓存通过
6Unity对象可缓存通过
v1.1
用例ID用例名称前者测试预期结果是否通过
1简单值类型数组可缓存通过
2不可变引用类型数组可缓存通过
3复合值类型数组可缓存通过
4自定义引用类型可缓存通过
5自定义值类型可缓存通过
6Unity对象可缓存通过

分析

字段临时缓存包装器有三种包装字段的方式,分别是WrapByBinary、WrapByJson和WrapByCustom,三种方式各有优缺点,择优而用。WrapByBinary采用二进制序列化和反序列化生成字段副本,该方法仅可用于被 Serializable 标记的字段类型。WrapByJson采用JSON序列化和反序列化生成字段副本,它虽然比前者包装范围更广,但是不可避免可能会依赖第三方用于JSON序列化和反序列化的库。WrapByCustom则是对前两种方式的补充,当前两种方式都不适用时,则可以自定义包装方式,例如对于Unity对象来说,需要通过Instantiate方法创建对象副本,这个时候就只能用自定义的方法进行包装。


返回的包装器提供了一些属性和方法,可用于判断是否为值类型、缓存的字段和缓存字段的引用(值类型),提供了针对WrapByBinary和WrapByJson包装方法的解包方法,还提供了显式释放包装器的方法。对于包装值类型时,我们可以通过获取缓存字段的引用来避免拷贝,解包方法用于将临时缓存的数据重新写入被包装字段中。通过显式释放包装器可以保证那些使用了非托管资源的类型(实现了IDisposable接口)进行资源的释放工作,从而避免内存泄漏等问题。


但是该包装器存在一些不可避免的限制,若所包装字段越复杂,其性能损耗越高,这是不可避免的。而对于复杂类型,建议自定义一个数据结构作为临时缓存的包装类型。


注意:不要使用包装器去包装其声明所在的类,特别是对于自定义包装逻辑要避免无限递归,否则会导致栈溢出或内存泄漏问题。示例代码如下:

// 该方法将导致栈溢出
public class A
{
    TempWrapper<A> wrapper;

    public A()
    {
        wrapper = TempWrapper<A>.WrapByCustom(() => new A());
    }
}

// 该方法将导致内存泄漏
public class B:Monobehaviour
{
    TempWrapper<Transform> wrapper;
    
    void Awake()
    {
        // Instantiate方法去克隆当前组件对象的Transform组件就会导致无限递归
        wrapper = TempWrapper<Transform>.WrapByCustom(() => Instantiate(transform));
    }
}

版本改进

版本号改进内容
v1.1

1.实例方法不使用线程锁,仅对静态方法使用线程锁;

2.手动执行Dispose方法释放包装器后,所有对公开实例成员的访问都将触发异常;

3.删除IsDisposed属性;

............

系列文章

......

如果这篇文章对你有帮助,请给作者点个赞吧!

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

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

相关文章

Android Automotive(一)

目录 什么是Android Automotive Android Automotive & Android Android Automotive 与 Android Auto 什么是Android Automotive Android Automotive 是一个基础的 Android 平台&#xff0c;它能够运行预装的车载信息娱乐系统&#xff08;IVI&#xff09;应用程序&#…

分治算法(3)_快速选择_数组中的第K个最大元素

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 分治算法(3)_快速排序_数组中的第K个最大元素 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#…

51单片机的自动制冷系统【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块温度传感器继电器LED、按键和蜂鸣器等模块构成。适用于车载便携式自动制冷系统、冰箱制冷、温度控制等相似项目。 可实现功能: 1、LCD1602实时显示当前温度 2、温度传感器DS18B20采集温度 3、按键可设置温度的阈…

双向数据库迁移工具:轻松实现 MySQL 与 SQLite 数据互导

项目概述与作用 该项目的核心是实现 MySQL 和 SQLite 两种数据库之间的数据迁移工具。它能够轻松地将 MySQL 数据库中的数据导出为 SQLite 数据库文件&#xff0c;反过来也可以将 SQLite 数据库中的数据上传到 MySQL 数据库中。这个双向迁移工具非常适用于&#xff1a; 数据库备…

日期类的实现(C++)

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 所属栏目&#xff1a;C知识点的补充_Jason_from_China的博客-CSDN博客 前言 日期类是六个成员函数学习的总结和拓展&#xff0c;是实践的体现 创建文件 构造函…

[C#]使用onnxruntime部署yolov11-onnx实例分割模型

【官方框架地址】 https://github.com/ultralytics/ultralytics.git 【算法介绍】 在C#中使用ONNX Runtime部署YOLOv11-ONNX实例分割模型&#xff0c;涉及到模型的加载、数据预处理、模型推理和后处理几个关键步骤。 首先&#xff0c;需要确保已经安装了ONNX Runtime的NuGe…

whisper 实现语音识别 ASR - python 实现

语音识别&#xff08;Speech Recognition&#xff09;&#xff0c;同时称为自动语音识别&#xff08;英语&#xff1a;Automatic Speech Recognition, ASR&#xff09;&#xff0c;将语音音频转换为文字的技术。 whisper是一个通用的语音识别模型&#xff0c;由OpenAI公司开发。…

【Spring】“请求“ 之后端传参重命名,传递数组、集合,@PathVariable,@RequestPart

1. 后端传参重命名&#xff08;后端参数映射&#xff09; 某些特殊情况下&#xff0c;前端传递的参数 key 和我们后端接收的 key 可以不一致&#xff0c;比如前端传了一个 time 给后端&#xff0c;而后端是使用 createtime 字段来接收的&#xff0c;这样就会出现参数接收不到的…

【新人系列】Python 入门(一):介绍及环境搭建

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12801353.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Python 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…

Python数据分析-远程办公与心理健康分析

一、研究背景 随着信息技术的飞速发展和全球化的推进&#xff0c;远程工作&#xff08;Remote Work&#xff09;成为越来越多企业和员工的选择。尤其是在2020年新冠疫情&#xff08;COVID-19&#xff09;爆发后&#xff0c;全球范围内的封锁措施使得远程工作模式迅速普及。根据…

【AIGC】ChatGPT提示词Prompt高效编写模式:结构化Prompt、提示词生成器与单样本/少样本提示

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;结构化Prompt (Structured Prompt)组成元素应用实例优势结论 &#x1f4af;提示词生成器 (Prompt Creator)如何工作应用实例优势结论 &#x1f4af;单样本/少样本提示 (O…

(贪心) 反悔贪心之反悔堆

文章目录 ⭐例题&#x1f6a9;题意与思路 ⭐返回贪心&#x1f6a9;原理&#xff08;反悔池&#xff09;&#x1f6a9;落实到题&#x1f6a9;AC code ⭐练习题⭐END&#x1f31f;交流方式 ⭐例题 经典例题&#xff1a; 871. 最低加油次数 &#x1f6a9;题意与思路 题意&#xf…

Microsoft 更新 Copilot AI,未來將能使用語音並看到你瀏覽的網頁

不過受到 Recall 事件的影響&#xff0c;更新的推出將更緩慢謹慎。 Microsoft 也同步對其網頁版及行動版的 Copilot AI 進行大改版。這主要是為網頁版換上了一個較為簡單乾淨的介面&#xff0c;並增加了一些新的功能&#xff0c;像是 Copilot Voice 能讓你與 AI 助手進行對話式…

IDEA:增加类注释模板和方法注释模板

文章目录 概要配置类注释模板配置方法模版 概要 配置类注释和方法注释 配置类注释模板 点击setting->Editor->File and Code Templates&#xff0c;然后找到Class&#xff0c;如下图&#xff1a; 注意勾掉Reformat according to style&#xff0c;否则会格式化。 注…

51单片机的水位检测系统【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块水位传感器继电器LED、按键和蜂鸣器等模块构成。适用于水位监测、水位控制、水位检测相似项目。 可实现功能: 1、LCD1602实时显示水位高度 2、水位传感器采集水位高度 3、按键可设置水位的下限 4、按键可手动加…

指针(7)

目录 1. sizeof和strlen的对⽐ 1.1 sizeof 1.2 strlen sizeof 和 strlen 总结&#xff1a; 2. 数组和指针 2.1 ⼀维数组 2.2 字符数组 1. sizeof和strlen的对⽐ 1.1 sizeof 计算的是使⽤类型创建的变量所占内存空间的⼤⼩。sizeof不在乎你里面放的什么。sizieof是操作符…

设计模式~~~

简单工厂模式(静态工厂模式) 工厂方法模式 抽象工厂角色 具体工厂角色

王者农药更新版

GPIO简介 STM32开发板有5组GPIO引脚&#xff0c;分别是GPIOA,GPIOB,GPIOC,GPIOD,GPIOE&#xff0c;每组GPIO有16个引脚。 每个引脚都有4个位来配置其端口&#xff0c;可以配置出不同的输入\输出模式。 1、普通推挽输出&#xff08;GPIO_Mode_Out_PP&#xff09;: 使用场合&…

在不支持WSL2的Windows环境下安装Redis并添加环境变量的方法

如果系统版本支持 WSL 2 可跳过本教程。使用官网提供的教程即可 官网教程 查看是否支持 WSL 2 如果不支持或者觉得麻烦可以按照下面的方式安装 下载 点击打开下载地址 下载 zip 文件即可 安装 将下载的 zip 文件解压到自己想要解压的地方即可。&#xff08;注意&#x…

sqli-labs less-17密码重置报错注入

密码重置报错植入 来到首页面我们看到页面提示【password reset】&#xff0c;说明这是更改密码的注入&#xff0c;也就是说我们知道一个账户名&#xff0c;修改他的密码&#xff0c;所以我们可以在passwd处进行注入。 闭合方式 添加单引号 有报错 可以知道闭合方式为单引号…