【unity框架开发7】对象池的使用,如何封装一个对象池管理器

news2024/10/11 22:19:18

文章目录

  • 什么是对象池?对象池有什么用?
  • 对象池的原理
  • 对象池的实现
    • 1、从对象池获取对象
    • 2、回收对象
    • 3、回收所有对象
    • 4、预先往这个对象池中加载指定数量的游戏对象
    • 5、最终代码
  • 封装对象池管理器
    • 1、对象池管理器代码
    • 2、测试调用
    • 3、生成和回收游戏对象时自动调用的方法
  • 完结

在这里插入图片描述

什么是对象池?对象池有什么用?

频繁创建和销毁对象会造成性能的开销。

创建对象的时候,系统会为这个对象开辟一片新的空间。销毁对象的时候,这个对象会变成内存垃圾,当内存垃圾达到一定程度,就会触发垃圾回收机制(及GC),清理内存垃圾,由于此时在清理垃圾,所以程序有可能就会变卡。

为了改善这个问题,我们就可以使用对象池。使用了它之后,程序的性能就能得到提升不那么容易变卡。

对象池的原理

1、当要创建对象的时候,不直接创建,而是先从对象池里面找,如果对象池里有这种类型的对象,就从对象池中取出来用。如果对象池里面没有这种类型的对象,才创建该对象。
2、当要销毁对象的时候,不直接销毁,而是把这个对象放进对象池里面存着,以便下一次创建对象时使用。

对象池的实现

1、从对象池获取对象

从对象池获取一个对象。如果对象池有,从对象池中取出来用。如果对象池没有,则实例化该对象。

/// <summary>
/// 对象池
/// </summary>
public class ObjectPool : MonoBehaviour
{
    // 这个对象池存储的游戏对象的预制体
    public GameObject prefab;

    // 对象池最多能容纳多少个游戏对象。负数表示可以容纳无数个。
    public int capacity = -1;

    // 从这个对象池中取出并正在使用的游戏对象。
    public List<GameObject> usedGameObjectList = new List<GameObject>();

    // 存在这个对象池中没有被使用的游戏对象。
    public List<GameObject> unUsedGameObjectList = new List<GameObject>();

    // 这个对象池中正在使用和没有被使用的游戏对象的总数
    public int TotalGameObjectCount => usedGameObjectList.Count + unUsedGameObjectList.Count;

    /// <summary>
    /// 从对象池获取一个对象。
    /// 如果对象池有,从对象池中取出来用。
    /// 如果对象池没有,则实例化该对象。
    /// </summary>
    /// <param name="position">位置</param>
    /// <param name="rotation">旋转</param>
    /// <param name="parent">父级</param>
    /// <returns>返回这个对象</returns>
    public GameObject Spawn(Vector3 position, Quaternion rotation, Transform parent = null)
    {
        GameObject go;

        // 如果对象池中有,则从对象池中取出来用。
        if (unUsedGameObjectList.Count > 0)
        {
            go = unUsedGameObjectList[0];
            unUsedGameObjectList.RemoveAt(0);
            usedGameObjectList.Add(go);
            go.transform.localPosition = position;
            go.transform.localRotation = rotation;
            go.transform.SetParent(parent, false);
            go.SetActive(true);
        }
        // 如果对象池中没有,则实例化该对象。
        else
        {
            go = Instantiate(prefab, position, rotation, parent);
            usedGameObjectList.Add(go);
        }

        // 如果该游戏对象身上继承MonoBehaviour的脚本中写了名叫OnSpawn的方法,则会执行它们一次。
        go.SendMessage("OnSpawn", SendMessageOptions.DontRequireReceiver);
        return go;
    }

    
}

2、回收对象

隐藏指定的游戏对象,并把它回收进对象池中。

/// <summary>
/// 回收对象
/// </summary>
/// <param name="go">要回收的对象</param>
public void Despawn(GameObject go)
{
    if (go == null) return;

    // 遍历这个对象池中所有正在使用的游戏对象
    for (int i = 0; i < usedGameObjectList.Count; i++)
    {
        if (usedGameObjectList[i] == go)
        {
            // 如果这个对象池的容量不为负数,且容纳的游戏对象已经满,则把0号的游戏对象剔除掉
            if (capacity >= 0 && usedGameObjectList.Count >= capacity)
            {
                if (unUsedGameObjectList.Count > 0)
                {
                    Destroy(unUsedGameObjectList[0]);
                    unUsedGameObjectList.RemoveAt(0);
                }
            }

            // 把游戏对象回收到对象池中
            unUsedGameObjectList.Add(go);
            usedGameObjectList.RemoveAt(i);
            go.SetActive(false);
            go.transform.SetParent(transform, false);

            // 如果该游戏对象身上继承MonoBehaviour的脚本写了名叫OnDespawn的方法,则在回收的时候,会执行一次。
            go.SendMessage("OnDespawn", SendMessageOptions.DontRequireReceiver);
            return;
        }
    }
}

3、回收所有对象

把通过这个对象池生成的所有游戏对象全部隐藏并放入对象池中

/// <summary>
/// 回收所有对象,把通过这个对象池生成的所有游戏对象全部隐藏并放入对象池中
/// </summary>
public void DespawnAll()
{
    int count = usedGameObjectList.Count;
    for (int i = 0; i < count; i++)
    {
        Despawn(usedGameObjectList[0]);
    }
}

4、预先往这个对象池中加载指定数量的游戏对象

/// <summary>
/// 预先往这个对象池中加载指定数量的游戏对象
/// </summary>
/// <param name="amount">预先生成游戏对象数量</param>
public void Preload(int amount = 1)
{
    if (prefab == null) return;
    if (amount <= 0) return;

    for (int i = 0; i < amount; i++)
    {
        GameObject go = Instantiate(prefab, Vector3.zero, Quaternion.identity);
        go.SetActive(false);
        go.transform.SetParent(transform, false);
        unUsedGameObjectList.Add(go);
        go.name = prefab.name;
    }
}

5、最终代码

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 对象池
/// </summary>
public class ObjectPool : MonoBehaviour
{
    //这个对象池存储的游戏对象的预制体
    public GameObject prefab;

    //对象池最多能容纳多少个游戏对象。负数表示可以容纳无数个。
    public int capacity = -1;

    //从这个对象池中取出并正在使用的游戏对象
    public List<GameObject> usedGameObjectsList = new List<GameObject>();

    //存在这个对象池中并没有被使用的游戏对象
    public List<GameObject> unUsedGameObjectsList = new List<GameObject>();

    //这个对象池中正在使用和没有被使用的游戏对象的总数
    public int TotalGameObjectsCount { get => usedGameObjectsList.Count + unUsedGameObjectsList.Count; }

    /// <summary>
    /// <para>从对象池获取一个对象</para>
    /// <para>如果对象池中有,则从对象池中取出来用。</para>
    /// <para>如果对象池中没有,则实例化该对象。</para>
    /// </summary>
    /// <param name="position">生成游戏对象的位置</param>
    /// <param name="rotation">生成游戏对象的旋转</param>
    /// <param name="parent">生成的游戏对象的父物体</param>
    /// <returns>返回这个对象</returns>
    public GameObject Spawn(Vector3 position, Quaternion rotation, Transform parent = null)
    {
        GameObject go;

        if (unUsedGameObjectsList.Count > 0)
        {
            go = unUsedGameObjectsList[0];

            unUsedGameObjectsList.RemoveAt(0);

            usedGameObjectsList.Add(go);

            go.transform.localPosition = position;

            go.transform.localRotation = rotation;

            go.transform.SetParent(parent, false);

            go.SetActive(true);
        }
        else
        {
            go = Instantiate(prefab, position, rotation, parent);

            usedGameObjectsList.Add(go);
        }

        //如果该游戏对象身上继承Monobehavior的脚本中写了名叫OnSpawn的方法,则会执行它们。
        go.SendMessage("OnSpawn", SendMessageOptions.DontRequireReceiver);

        return go;
    }

    /// <summary>
    /// <para>隐藏指定的游戏对象,并把它回收进对象池中。</para>
    /// </summary>
    /// <param name="go">要放入对象池的游戏对象</param>
    public void Despawn(GameObject go)
    {
        if (go == null) return;

        //遍历这个对象池中所有正在使用的游戏对象
        for (int i = 0; i < usedGameObjectsList.Count; i++)
        {
            if (usedGameObjectsList[i] == go)
            {
                //如果这个对象池的容量不为负数,且容纳的游戏对象已经满了,则把0号的游戏对象删掉,确保之后新的游戏对象能放入到池子中。
                if (capacity >= 0 && unUsedGameObjectsList.Count >= capacity)
                {
                    if (unUsedGameObjectsList.Count > 0)
                    {
                        Destroy(unUsedGameObjectsList[0]);
                        unUsedGameObjectsList.RemoveAt(0);
                    }
                }

                //把游戏对象放入到对象池中
                unUsedGameObjectsList.Add(go);
                usedGameObjectsList.RemoveAt(i);
				
				//如果该游戏对象身上继承Monobehavior的脚本中写了名叫OnDespawn的方法,则会执行它们。
                go.SendMessage("OnDespawn", SendMessageOptions.DontRequireReceiver);
                
                go.SetActive(false);

                go.transform.SetParent(transform, false);

                return;
            }
        }
    }

    /// <summary>
    /// 把通过这个对象池生成的所有游戏对象,全部隐藏并放入对象池中
    /// </summary>
    public void DespawnAll()
    {
        int count = usedGameObjectsList.Count;

        for (int i = 1; i <= count; i++)
        {
            Despawn(usedGameObjectsList[0]);
        }

        //清空列表
        usedGameObjectsList.Clear();
    }

    /// <summary>
    /// <para>在这个对象池中预加载指定数量的游戏对象。</para>
    /// </summary>
    /// <param name="amount">要预加载的数量</param>
    public void Preload(int amount = 1)
    {
        if (prefab == null) return;

        if (amount <= 0) return;

        for (int i = 1; i <= amount; i++)
        {
            GameObject go = Instantiate(prefab, Vector3.zero, Quaternion.identity);
            go.SetActive(false);
            go.transform.SetParent(transform, false);
            unUsedGameObjectsList.Add(go);
            go.name = prefab.name;
        }
    }
}

封装对象池管理器

1、对象池管理器代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 对象池管理器
/// </summary>
public class ObjectPoolsManager : Singleton<ObjectPoolsManager>
{
    //所有对象池的父物体
    GameObject poolsParent;

    //所有对象池共同父物体的名字
    readonly string poolsParentName = "ObjectPools";

    //当前所有对象池的列表
    public List<ObjectPool> objectPoolsList = new List<ObjectPool>();

    //键:由对象池生成的并且正在使用的游戏对象
    //值:这个游戏对象所属的对象池
    public Dictionary<GameObject, ObjectPool> objectsDictionary = new Dictionary<GameObject, ObjectPool>();

    /// <summary>
    /// <para>从对象池获取一个对象</para>
    /// <para>如果对象池中有,则从对象池中取出来用。</para>
    /// <para>如果对象池中没有,则实例化该对象。</para>
    /// </summary>
    /// <param name="prefab">要生成的游戏对象的预制体</param>
    /// <param name="position">生成游戏对象的位置。如果没有设置父物体,则是世界坐标。如果设置了父物体,则是局部坐标。</param>
    /// <param name="rotation">生成游戏对象的旋转</param>
    /// <param name="parent">>生成游戏对象的父物体</param>
    /// <returns>返回游戏对象</returns>
    public GameObject Spawn(GameObject prefab, Vector3 position, Quaternion rotation, Transform parent = null)
    {
        if (prefab == null) return null;

        //生成对象池的父物体
        CreatePoolsParentIfNull();

        //通过预制体来查找它所属的对象池
        ObjectPool objectPool = FindPoolByPrefabOrCreatePool(prefab);

        //从对象池获取一个对象
        GameObject go = objectPool.Spawn(position, rotation, parent);

        //把生成的游戏对象与它所属的对象池记录到字典
        objectsDictionary.Add(go, objectPool);

        return go;
    }

    /// <summary>
    /// <para>隐藏指定的游戏对象,并把它回收进对象池中。</para>
    /// </summary>
    /// <param name="go">要放入对象池的游戏对象</param>
    /// <param name="delayTime">延迟多少秒执行</param>
    public void Despawn(GameObject go, float delayTime = 0)
    {
        if (go == null) return;

        //开启协程,延迟执行回收到对象池的逻辑。
        MonoManager.Instance.StartCoroutine(DespawnCoroutine(go, delayTime));
    }
    IEnumerator DespawnCoroutine(GameObject go, float delayTime = 0)
    {
        //等待指定秒数
        if (delayTime > 0)
            yield return CoroutineTool.WaitForSeconds(delayTime);

        //先从象池生成的正在使用的游戏对象的字典中找指定的游戏对象
        if (objectsDictionary.TryGetValue(go, out ObjectPool pool))
        {
            objectsDictionary.Remove(go);

            //把这个游戏对象放入找到的对象池
            pool.Despawn(go);
        }
        else
        {
            //获取这个游戏对象所属的对象池
            pool = FindPoolByUsedGameObject(go);
            if (pool != null)
                pool.Despawn(go);
        }
    }

    /// <summary>
    /// 把所有通过对象池生成的对象全部隐藏,并回收进对象池中。
    /// </summary>
    public void DespawnAll()
    {
        for (int i = 0; i < objectPoolsList.Count; i++)
        {
            objectPoolsList[i].DespawnAll();
        }
        objectsDictionary.Clear();
    }

    /// <summary>
    /// <para>在对象池中预加载指定数量的游戏对象。</para>
    /// </summary>
    /// <param name="prefab">游戏对象的预制体</param>
    /// <param name="amount">要预加载的数量</param>
    public void Preload(GameObject prefab, int amount = 1)
    {
        if (prefab == null) return;
        if (amount <= 0) return;

        //通过预制体来查找它所属的对象池
        ObjectPool pool = FindPoolByPrefabOrCreatePool(prefab);

        //预加载指定数量的游戏对象
        pool.Preload(amount);
    }

    /// <summary>
    /// <para>返回指定的预制体所属的对象池的容量。</para>
    /// <para>如果该对象池不存在,则会创建它,然后返回-1。</para>
    /// </summary>
    /// <param name="prefab">预制体</param>
    public int GetCapacity(GameObject prefab)
    {
        ObjectPool pool = FindPoolByPrefabOrCreatePool(prefab);

        return pool.capacity;
    }

    /// <summary>
    /// <para>设置指定的预制体所属的对象池的容量。</para>
    /// <para>如果该对象池不存在,则会创建它,然后再设置它的容量。</para>
    /// </summary>
    /// <param name="prefab">预制体</param>
    /// <param name="capacity">要设置的容量。如果设置为负数,则表示这个对象池可以容纳无数个游戏对象。</param>
    public void SetCapacity(GameObject prefab, int capacity = -1)
    {
        ObjectPool pool = FindPoolByPrefabOrCreatePool(prefab);

        pool.capacity = capacity;
    }

    /// <summary>
    ///生成对象池的父物体
    /// </summary>
    void CreatePoolsParentIfNull()
    {
        if (poolsParent == null)
        {
            //清空列表和字典,避免上一个场景的数据的影响。
            objectPoolsList.Clear();
            objectsDictionary.Clear();

            //生成一个空物体
            poolsParent = new GameObject(poolsParentName);
        }
    }

    /// <summary>
    /// <para>查找指定的预制体所属的对象池</para>
    /// </summary>
    /// <param name="prefab">预制体</param>
    ObjectPool FindPoolByPrefab(GameObject prefab)
    {
        if (prefab == null) return null;

        for (int i = 0; i < objectPoolsList.Count; i++)
        {
            if (objectPoolsList[i].prefab == prefab)
                return objectPoolsList[i];
        }
        return null;
    }

    /// <summary>
    /// <para>查找指定的游戏对象所属的对象池</para>
    /// </summary>
    /// <param name="go">要查找的游戏对象</param>
    ObjectPool FindPoolByUsedGameObject(GameObject go)
    {
        if (go == null) return null;

        for (int i = 0; i < objectPoolsList.Count; i++)
        {
            ObjectPool pool = objectPoolsList[i];

            //遍历每一个对象池正在使用的所有游戏对象
            for (int j = 0; j < pool.usedGameObjectsList.Count; j++)
            {
                if (pool.usedGameObjectsList[j] == go)
                    return pool;
            }
        }

        return null;
    }

    /// <summary>
    /// <para>通过预制体来查找它所属的对象池</para>
    /// </summary>
    /// <param name="prefab">预制体</param>
    ObjectPool FindPoolByPrefabOrCreatePool(GameObject prefab)
    {
        //生成对象池的父物体
        CreatePoolsParentIfNull();

        //查找并返回该预制体所属的对象池
        ObjectPool objectPool = FindPoolByPrefab(prefab);

        //如果该对象池不存在,则创建一个。
        if (objectPool == null)
        {
            //创建一个对象池
            objectPool = new GameObject($"ObjectPool-{prefab.name}").AddComponent<ObjectPool>();

            //设置这个对象池所管理的预制体
            objectPool.prefab = prefab;

            //把生成的对象池放到父物体中,方便管理。
            objectPool.transform.SetParent(poolsParent.transform);

            //记录这个对象池
            objectPoolsList.Add(objectPool);
        }

        return objectPool;
    }
}

2、测试调用

public class PoolTest : MonoBehaviour
{
    private void OnGUI()
    {
        if (GUI.Button(new Rect(0, 0, 150, 70), "生成游戏对象"))
        {
            GameObject prefab = ResourcesManager.Instance.Load<GameObject>("Prefabs/Cube");

            GameObject go = ObjectPoolsManager.Instance.Spawn(prefab, transform.position, Quaternion.identity, transform);

            go.GetComponent<Rigidbody>().AddForce(Vector3.forward * 1000f);//加力

            //5秒后回收
            ObjectPoolsManager.Instance.Despawn(go, 5f);
        }
    }
}

效果
在这里插入图片描述

3、生成和回收游戏对象时自动调用的方法

在预制体添加脚本,书写生成和回收游戏对象时自动调用的方法,可以在上面书写对象初始化方法,重置对象参数

public class Cube : MonoBehaviour {
    //生成游戏对象时自动调用
    void OnSpawn(){
        Debug.Log("生成游戏对象");
    }

    //回收游戏对象时自动调用
    void OnDespawn(){
        Debug.Log("回收游戏对象");
        
        //重置参数
        transform.position = Vector3.zero;
        GetComponent<Rigidbody>().velocity = Vector3.zero;
    }
}

效果
在这里插入图片描述

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

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

相关文章

LLM基础常见面试题

#############【持续更新】############## LLM基础常见面试题 简单介绍一下大语言模型【LLMs】&#xff1f; 大模型&#xff1a;一般指1亿以上参数的模型&#xff0c;但是这个标准一直在升级&#xff0c;目前万亿参数以上的模型也有了。大语言模型&#xff08;Large Languag…

在 Windows 11 安卓子系统中安装 APK 的操作指南

这个软件好像不可以在纯android系统中使用&#xff08;不知道是缺了什么&#xff09;&#xff0c;其他对于android的虚拟机要不缺少必要功能组件&#xff0c;要不性能过于低下。本方法致力于在带有谷歌框架WSA中运行该APK 在 Windows 11 安卓子系统中安装 APK 的操作指南 本指…

渗透测试系列之靶机渗透

Helpline是一个困难的靶机&#xff0c;知识点涉及EFS解密和ME SDP的多个漏洞&#xff0c;包括XXE漏洞、LFI\任意文件下载漏洞、身份认证绕过漏洞以及远程代码执行漏洞等. 通关思维导图 0x01 侦查 端口探测 首先使用nmap进行端口扫描 nmap -Pn -p- -sV -sC -A 10.10.10.13…

Obsidian复制代码块代码正确方式

最近在用obsidain做笔记&#xff0c;整体使用下来感觉不错&#xff0c;尤其是强大的检索功能&#xff0c;但是刚才使用其内置的复制代码块功能时&#xff0c;却被恶心到了。 现有背景是&#xff1a;我有一个pydantic的 code block&#xff0c;编辑器处于编辑模式如下&#xff…

Visual Studio的实用调试技巧总结

对于很多学习编程的老铁们来说&#xff0c;是不是也像下面这张图一样写代码呢&#xff1f; 那当我们这样编写代码的时候遇到了问题&#xff1f;大家又是怎么排查问题的呢&#xff1f;是不是也像下面这张图一样&#xff0c;毫无目的的一遍遍尝试呢&#xff1f; 这篇文章我就以 V…

【C语言】深入理解指针(二)(上)

本篇博客将讲解的知识&#xff1a; &#xff08;1&#xff09;指针的使用和传址调用 &#xff08;2&#xff09;数组名的理解 1、指针的使用和传址调用 &#xff08;1&#xff09;strlen 的模拟实现 库函数strlen的功能是求字符串的长度&#xff0c;统计的是字符串中‘\0’之…

onnx代码解读

一、定义 torch.jit.trace 相关代码解读onnx 内部实现 3 查看是否为aten 算子aten 算子实现torch.autograd.Functions 算子实现自定义算子实现查找未实现的节点一次性发现所有的未实现 aten 算子 二、实现 torch.jit.trace 相关代码解读 1. torch.jit.script() : 将其转换为…

数据库的基本概念、安装MySQL及基础运用

目录 一、数据库的基本概念 1. 使用数据库的必要性 2. 数据&#xff08;Data&#xff09; 3. 表 4. 数据库 5. 数据库管理系统&#xff08;DBMS&#xff09; 6. 数据库管理系统DBMS的优点 7. 使用数据库的必要性总结 8. 访问数据库的流程 二、数据库发展及基本功能 1.…

宠物空气净化器怎么选?希喂、霍尼韦尔、美的宠物哪款除毛好?

身为养宠五年的资深铲屎官&#xff0c;最近收到了很多新手养宠朋友关于宠物空气净化器的挑选疑问。宠物空气净化器作为宠物领域目前最火热的产品&#xff0c;谈论度一直很高&#xff0c;评价也褒贬不一。双十一购物节又即将到来&#xff0c;大家都想赶上这一波优惠活动。 铺天盖…

Automa插件之js脚本小技巧:零依赖的日期时间格式化,亲测好用!

背景 在使用 Automa 插件自动下载文件时,有时候需要根据当前时间重新命名文件,如果是时间戳的话倒是也可以防重复文件命名,只不过那样的话,没有了时间可读性. 所以需要日期时间格式化,分享一个一直在用的纯 js 格式化日期脚本,可实现简单的日期格式化. 文末附完整代码,直接复制…

时序约束进阶四:set_input_delay和set_output_delay详解

目录 一、前言 二、set_input_delay/set_output_delay 2.1 延时约束 2.2 约束设置界面 2.3 示例工程 2.4 Delay Value 2.5 Delay value is relative to clock edge 2.6 Delay value already includes latencies of the specified clock edge 2.7 Rise/Fall 2.8 Max/M…

教育部白名单赛事到底是什么?大家为什么那么重视它?

近年来&#xff0c;随着素质教育的推广和升学竞争的加剧&#xff0c;白名单赛事这一概念变得越来越热门。所谓的白名单赛事&#xff0c;是指经过教育部批准并公布的竞赛名单。这些比赛不仅具备权威性和高含金量&#xff0c;还受到各大中小学、重点高中和高校的广泛认可。在升学…

文件句柄泄漏排查及方法总结

如果只是怀疑文件句柄泄漏&#xff0c;可以通过Process Explorer 找到对应进程&#xff0c;双击点开查看performance中的handles变化即可&#xff0c;然后结合I/O项变化进行大致分析。 ——当然对于程序员而言&#xff0c;不光是要发现问题&#xff0c;还要定位问题。 针对li…

Qt 自绘开关按钮以及设计器中的提升为用法

文章目录 自绘按钮实现概要效果图代码 提升为用法介绍步骤 总结 自绘按钮实现 概要 当我们需要一个开关样式的QPushbutton&#xff0c;没有图片的话&#xff0c;我们可以采用自绘的形式实现。且使用QtDesinger中提升为Promote to的功能加入界面中&#xff0c;而不是使用代码的…

C++入门基础知识107—【关于C++continue 语句】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于C continue 语句的相关内容&#xff01;…

初始爬虫13(js逆向)

为了解决网页端的动态加载&#xff0c;加密设置等&#xff0c;所以需要js逆向操作。 JavaScript逆向可以分为三大部分&#xff1a;寻找入口&#xff0c;调试分析和模拟执行。 1.chrome在爬虫中的作用 1.1preserve log的使用 默认情况下&#xff0c;页面发生跳转之后&#xf…

基于html的大模型调试页面

效果1 源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>API Call Example</title><st…

C++面向对象--------继承篇

目录 一.继承&#xff08;重点&#xff09; 1.1 概念 1.2 构造函数 1.2.1 派生类与基类的构造函数关系 1.2.2 解决方案 1.2.2.1 补充基类的无参构造函数 1.2.2.2 手动在派生类中调用基类构造函数 1.2.2.2.1 透传构造 1.2.2.2.2 委托构造 1.2.2.2.3 继承构造 1.3 对象…

为什么SEO是一个不断学习和适应的过程?

SEO并不是一成不变的&#xff0c;它是一个需要不断学习和适应的过程。谷歌的算法经常更新&#xff0c;用户的搜索行为也在不断变化&#xff0c;这使得SEO策略必须与时俱进&#xff0c;才能保持有效性。企业需要认识到&#xff0c;SEO是一项长期的投资&#xff0c;需要持续的关注…

Spring WebFlux 响应式概述(1)

1、响应式编程概述 1.1、响应式编程介绍 1.1.1、为什么需要响应式 传统的命令式编程在面对当前的需求时的一些限制。在应用负载较高时&#xff0c;要求应用需要有更高的可用性&#xff0c;并提供低的延迟时间。 1、Thread per Request 模型 比如使用Servlet开发的单体应用&a…