【UE5 C++课程系列笔记】20——共享指针的简单使用

目录

概念

创建共享指针示例

重设共享指针

共享指针内容转移

共享指针和共享引用的转换

判断共享指针的相等性

共享指针访问成员函数

自定义删除器


概念

        共享指针(主要以 TSharedPtr 为例),TSharedPtr 基于引用计数机制来工作,旨在解决对象所有权共享以及确保在合适的时候自动释放对象资源的问题。它允许多个 TSharedPtr 实例指向同一个对象,虚幻引擎会在内部记录该对象被引用的次数(即引用计数),每当有新的 TSharedPtr 通过拷贝构造、赋值等方式开始指向这个对象时,引用计数就会增加;而当一个 TSharedPtr 实例结束其生命周期(比如离开作用域、被重新赋值等情况),引用计数会相应地减少,当引用计数最终降为 0 时,所指向的对象就会被自动销毁,释放其占用的内存等资源。

创建共享指针示例

1. 新建一个actor,这里命名为“SmartActor”,在“SmartActor”中定义一个名为“FSmartPtrStruct”的结构体。通过继承 TSharedFromThis<FSmartPtrStruct>FSmartPtrStruct 类型的对象可以从自身获取到一个指向自己的 TSharedPtr 共享指针

2. 在“SmartPtrActor”中定义一个方法“InitSPStruct”,用于创建共享指针。

3. 实现“InitSPStruct”如下,主要展示了几种不同方式创建和使用共享指针(TSharedPtr )的示例,涉及创建空白共享指针、指向新创建对象的共享指针、从共享引用转换而来的共享指针以及创建线程安全的共享指针。

void ASmartPtrActor::InitSPStruct()
{// 创建一个空白的共享指针TSharedPtr<FSmartPtrStruct> EmptyPointer;// 为新对象创建一个共享指针TSharedPtr<FSmartPtrStruct> NewPointer(new FSmartPtrStruct(100));// 从共享引用创建一个共享指针TSharedRef<FSmartPtrStruct> NewReference(new FSmartPtrStruct(200));TSharedPtr<FSmartPtrStruct> PointerFromReference = NewReference;NewReference->aa = 123;// 创建一个线程安全的共享指针TSharedPtr<FSmartPtrStruct, ESPMode::ThreadSafe> NewThreadsafePointer = MakeShared<FSmartPtrStruct, ESPMode::ThreadSafe>(300);
}

4. 编译后创建基于“SmartPtrActor”的蓝图类“BP_SmartPtrActor”

将“BP_SmartPtrActor”拖入视口

5. 打开“BP_SmartPtrActor”,在BeginPlay时调用方法“InitSPStruct”

6. 运行后可以看到输出日志如下

重设共享指针

        重设共享指针的目的是释放共享指针所指向的对象资源,使共享指针不再持有对相应对象的引用,以此来管理对象的生命周期以及避免悬空指针等问题,确保内存管理的正确性。通常可以通过使用Reset 方法重置共享指针,或通过赋值 nullptr 重置共享指针。

        如下代码所示,通过调用Reset 方法和赋值 nullptr,使 Pointer1、  Pointer2 变为空指针状态,不再持有对任何对象的引用,方便后续对其进行重新赋值等操作,确保内存管理的有序性和对象生命周期的合理控制。

void ASmartPtrActor::ResetSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer1(new FSmartPtrStruct(111));TSharedPtr<FSmartPtrStruct> Pointer2(new FSmartPtrStruct(222));Pointer1.Reset();Pointer2 = nullptr;
}

共享指针内容转移

        可以使用 MoveTemp(或 MoveTempIfPossible)函数将一个共享指针的内容转移到另一个共享指针,转移共享指针(TSharedPtr )所指向对象的所有权,目的是在不进行额外拷贝(避免不必要的资源复制开销)的情况下,重新分配对象的所有权归属,以优化对象生命周期管理和内存使用效率,尤其适用于一些涉及对象传递、赋值等场景中避免多余的构造和析构操作带来的性能损耗。

        如下代码所示,通过 MoveTemp将Pointer1的内容移至Pointer2,在此之后,Pointer1将引用空指针。然后再通过MoveTempIfPossible将Pointer2的内容移至Pointer1,在此之后,Pointer2将引用空指针。

void ASmartPtrActor::MoveSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer1(new FSmartPtrStruct(111));TSharedPtr<FSmartPtrStruct> Pointer2;Pointer2 = MoveTemp(Pointer1);  //将Pointer1的内容移至Pointer2,在此之后,Pointer1将引用空指针Pointer1 = MoveTempIfPossible(Pointer2);  //将Pointer2的内容移至Pointer1,在此之后,Pointer2将引用空指针
}

运行结果如下,一开始Pointer1有内容,Pointer2无内容,经过第43行代码后Pointer1为空指针,Pointer1的内容移至Pointer2。

经第44行代码后,Pointer2为空指针,Pointer2的内容又移回Pointer1。

共享指针和共享引用的转换

        从共享指针转换为共享引用可以使用 ToSharedRef 函数,但这个转换有一个重要前提,那就是共享指针必须是有效的,即它当前指向一个实际存在的对象(也就是其内部所管理对象的引用计数大于 0)。而共享引用可以直接隐式转换为共享指针。

        如下代码所示,展示了共享引用(TSharedRef )和共享指针(TSharedPtr )之间相互转换。

        第49行代码创建了一个 TSharedRef 类型的共享引用 MySharedReference,它通过 new 关键字创建一个 FSmartPtrStruct 类型的对象,并将其传递给 TSharedRef 的构造函数,使得 MySharedReference 指向这个新创建的对象。TSharedRef 表示对对象的一种强引用,它在创建时必须绑定一个有效的对象,并且在对象的整个生命周期内,只要有 TSharedRef 指向它,该对象就不会被销毁(通过虚幻引擎内部的引用计数机制来管理,这里对象的引用计数初始为 1,因为只有这一个共享引用指向它)。

        第50行代码实现了从共享引用 MySharedReference 到共享指针 MySharedPointer 的转换。当进行此转换时,会将所指向对象的引用计数加 1(因为又多了一个共享指针来管理这个对象),现在对象的引用计数变为 2MySharedPointer 同样指向 MySharedReference 所指向的那个 FSmartPtrStruct 对象,后续可以通过 MySharedPointer 来访问对象的成员变量、调用成员函数等操作,就像使用普通指针一样,只要对象的引用计数大于 0,即还有共享指针或共享引用指向它,对象就是有效的。

        第51~54行代码首先通过 MySharedPointer.IsValid() 来检查共享指针 MySharedPointer 是否有效,当条件成立后,尝试从共享指针 MySharedPointer 再转换回共享引用 BackReference

        第55行代码通过调用 MySharedPointer.Reset() 函数对共享指针进行重置操作,将 MySharedPointer 所指向的当前对象释放掉。

        第56行代码是一个错误的操作,因为第55行代码已经通过 Reset 函数将 MySharedPointer 重置为空指针了,此时再尝试调用 ToSharedRef 函数从这个空指针转换为共享引用是不合法的,会导致程序出现未定义行为从而引发运行时错误。ToSharedRef 函数要求其调用的共享指针必须是有效的,指向一个实际存在的对象,才能进行转换操作。

判断共享指针的相等性

        如下代码所示,主要用于测试两个共享指针(TSharedPtr )是否指向同一个对象,通过使用重载的 == 运算符来进行相等性判断。在如下代码中,首先创建了 Pointer1 并通过 MakeShareable(new FSmartPtrStruct()) 让它指向一个新创建的 FSmartPtrStruct 类型的对象。然后使用赋值语句 Pointer2 = Pointer1;,这样 Pointer2 就获得了与 Pointer1 相同的指向,它们都指向了最初由 Pointer1 指向的那个对象。此时,该对象的引用计数会从 1(仅有 Pointer1 指向时)变为 2Pointer1 == Pointer2 的比较结果为 true。

void ASmartPtrActor::EqualSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer1;TSharedPtr<FSmartPtrStruct> Pointer2;Pointer1 = MakeShareable(new FSmartPtrStruct(100));;Pointer2 = Pointer1;if (Pointer1 == Pointer2){UE_LOG(LogTemp, Warning, TEXT("Pointer1=Pointer2"));}
}

共享指针访问成员函数

        这里介绍3种访问成员函数/变量的方式:

                1. 通过 -> 运算符访问成员函数;

                2. 通过 Get 函数获取原始指针后访问成员函数

                3. 通过解引用操作符 * 访问成员函数

        解引用操作的本质是获取共享指针所指向对象的实际内存地址对应的内容,也就是通过共享指针找到对应的对象实例,然后可以对该对象进行操作,比如访问成员变量、调用成员函数等。但需要注意的是,在进行解引用操作之前,必须确保共享指针是有效的,即它指向一个实际存在的对象,否则对空的共享指针进行解引用会导致程序出现未定义行为从而导致程序崩溃等严重后果。

        除了通过解引用操作访问成员变量外,还可以在确保共享指针有效的条件下,直接通过共享指针来访问所指向对象的成员变量、成员函数等。

        如下代码所示,主要展示了通过共享指针(TSharedPtr )对其所指向对象的成员函数PrintAA进行访问的几种不同方式。

        第74行代码通过 -> 运算符访问成员函数-> 运算符会先对 Pointer 进行解引用操作,找到其所指向的 FSmartPtrStruct 对象,然后调用该对象的 PrintAA 成员函数。

        第75行代码通过 Get 函数获取原始指针后访问成员函数Get 函数是 TSharedPtr 提供的一个成员函数,它的作用是返回共享指针所指向对象的原始指针(也就是类似普通指针的形式)。在这行代码中,Pointer.Get() 会获取到 Pointer 所指向的 FSmartPtrStruct 对象的原始指针,再通过 -> 运算符调用 PrintAA 成员函数。

        第76行代码通过解引用操作符 * 访问成员函数。使用 * 运算符对共享指针 Pointer 进行解引用,得到其所指向的 FSmartPtrStruct 对象本身(相当于将共享指针转换为普通对象的引用形式),然后再通过 . 运算符来调用 PrintAA 成员函数。这种方式与使用 -> 运算符的功能是等价的,只是语法形式上有所不同,不过在实际编程中,使用 -> 运算符更加简洁直观,所以通常更推荐使用 Pointer->PrintAA(); 的形式。

  PrintAA()如下:

        代码执行结果如下,可以看到3种方式都可以成功访问成员函数。

自定义删除器

        自定义删除器是一个很有用的特性,它允许你指定在智能指针释放其所管理对象时执行的特定清理逻辑。

        自定义删除器的意义:

        在如下代码中,使用 TSharedPtr (共享指针)并为其指定自定义删除器(以 lambda 表达式的形式)的用法。其目的是在共享指针所管理的对象生命周期结束(即引用计数变为 0 时),执行自定义的清理逻辑。

        通过 new FSmartPtrStruct(100) 创建了一个 FSmartPtrStruct 类型的对象,这个对象的构造函数会被调用,输出相应的构造日志信息,并且其成员变量 aa 的初始值被设置为 100。然后将这个新创建的对象传递给 TSharedPtr 的构造函数,使得 NewPointer 共享指针指向这个对象,此时该对象的引用计数初始为 1,由 NewPointer 通过虚幻引擎内部的引用计数机制来管理其生命周期。同时,在 TSharedPtr 的构造函数中还传入了一个 lambda 表达式作为自定义删除器。这个 lambda 表达式接收一个 FSmartPtrStruct* 类型的指针参数 Obj,代表了共享指针所管理的对象指针。

        执行结果如下,从打印结果可以看到,虽然执行了FSmartPtrStruct的构造函数,却没有执行析构。

“SmartPtrActor”完整代码:

// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SmartPtrActor.generated.h"struct FSmartPtrStruct :public TSharedFromThis<FSmartPtrStruct>
{FSmartPtrStruct(int32 Inaa):aa(Inaa){UE_LOG(LogTemp, Warning, TEXT("FSmartPtrStruct Construction  aa = %d"), aa);}~FSmartPtrStruct(){UE_LOG(LogTemp, Warning, TEXT("FSmartPtrStruct Destruction  aa = %d"), aa);}void PrintAA() {UE_LOG(LogTemp, Warning, TEXT("aa = %d"), aa);}int32 aa = -1;
};UCLASS()
class STUDY_API ASmartPtrActor : public AActor
{GENERATED_BODY()public:	// Sets default values for this actor's propertiesASmartPtrActor();UFUNCTION(BlueprintCallable)void InitSPStruct();UFUNCTION(BlueprintCallable)void ResetSPStruct();UFUNCTION(BlueprintCallable)void MoveSPStruct();UFUNCTION(BlueprintCallable)void ConvertSPStruct();UFUNCTION(BlueprintCallable)void EqualSPStruct();UFUNCTION(BlueprintCallable)void AccessSPStruct();UFUNCTION(BlueprintCallable)void DeleteStruct();protected:// Called when the game starts or when spawnedvirtual void BeginPlay() override;public:	// Called every framevirtual void Tick(float DeltaTime) override;};
// Fill out your copyright notice in the Description page of Project Settings.#include "SmartPointer/SmartPtrActor.h"// Sets default values
ASmartPtrActor::ASmartPtrActor()
{// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.PrimaryActorTick.bCanEverTick = true;}void ASmartPtrActor::InitSPStruct()
{// 创建一个空白的共享指针TSharedPtr<FSmartPtrStruct> EmptyPointer;// 为新对象创建一个共享指针TSharedPtr<FSmartPtrStruct> NewPointer(new FSmartPtrStruct(100));// 从共享引用创建一个共享指针TSharedRef<FSmartPtrStruct> NewReference(new FSmartPtrStruct(200));TSharedPtr<FSmartPtrStruct> PointerFromReference = NewReference;NewReference->aa = 123;// 创建一个线程安全的共享指针TSharedPtr<FSmartPtrStruct, ESPMode::ThreadSafe> NewThreadsafePointer = MakeShared<FSmartPtrStruct, ESPMode::ThreadSafe>(300);
}void ASmartPtrActor::ResetSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer1(new FSmartPtrStruct(111));TSharedPtr<FSmartPtrStruct> Pointer2(new FSmartPtrStruct(222));Pointer1.Reset();Pointer2 = nullptr;
}void ASmartPtrActor::MoveSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer1(new FSmartPtrStruct(111));TSharedPtr<FSmartPtrStruct> Pointer2;Pointer2 = MoveTemp(Pointer1);  //将Pointer1的内容移至Pointer2,在此之后,Pointer1将引用空指针Pointer1 = MoveTempIfPossible(Pointer2);  //将Pointer2的内容移至Pointer1,在此之后,Pointer2将引用空指针
}void ASmartPtrActor::ConvertSPStruct()
{TSharedRef<FSmartPtrStruct> MySharedReference(new FSmartPtrStruct(100));TSharedPtr<FSmartPtrStruct> MySharedPointer = MySharedReference;if (MySharedPointer.IsValid()){TSharedRef<FSmartPtrStruct> BackReference = MySharedPointer.ToSharedRef();}/*MySharedPointer.Reset();TSharedRef<FSmartPtrStruct> BackReferenceNULL = MySharedPointer.ToSharedRef();*/
}void ASmartPtrActor::EqualSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer1;TSharedPtr<FSmartPtrStruct> Pointer2;Pointer1 = MakeShareable(new FSmartPtrStruct(100));;Pointer2 = Pointer1;if (Pointer1 == Pointer2){UE_LOG(LogTemp, Warning, TEXT("Pointer1=Pointer2"));}
}void ASmartPtrActor::AccessSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer(new FSmartPtrStruct(111));Pointer->PrintAA();Pointer.Get()->PrintAA();(*Pointer).PrintAA();
}void ASmartPtrActor::DeleteStruct()
{TSharedPtr<FSmartPtrStruct> NewPointer(new FSmartPtrStruct(100), [](FSmartPtrStruct* Obj) {UE_LOG(LogTemp, Warning, TEXT("aa = %d"), Obj->aa);});
}// Called when the game starts or when spawned
void ASmartPtrActor::BeginPlay()
{Super::BeginPlay();}// Called every frame
void ASmartPtrActor::Tick(float DeltaTime)
{Super::Tick(DeltaTime);}

官方文档地址:

https://dev.epicgames.com/documentation/zh-cn/unreal-engine/shared-pointers-in-unreal-engine?application_version=5.3

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

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

相关文章

flux中的缓存

1. cache&#xff0c;onBackpressureBuffer。都是缓存。cache可以将hot流的数据缓存起来。onBackpressureBuffer也是缓存&#xff0c;但是当下游消费者的处理速度比上游生产者慢时&#xff0c;上游生产的数据会被暂时存储在缓冲区中&#xff0c;防止丢失。 2. Flux.range 默认…

Ubuntu网络连接问题(笔记本更换wifi后,虚拟机连不上网络)

1、笔记本更换wifi后&#xff0c;虚拟机的IP地址变了&#xff0c;然后就连不上网络了&#xff08;主机笔记本连接wifi正常上网&#xff09; 2、修改子网地址&#xff08;按照ubutun的ip设置子网掩码&#xff09; 3、Ubuntu已经显示网络连接正常了&#xff0c;但是就是无法上网&…

如何在 Ubuntu 22.04 上安装 Cassandra NoSQL 数据库教程

简介 本教程将向你介绍如何在 Ubuntu 22.04 上安装 Cassandra NoSQL 数据库。 Apache Cassandra 是一个分布式的 NoSQL 数据库&#xff0c;旨在处理跨多个普通服务器的大量数据&#xff0c;并提供高可用性&#xff0c;没有单点故障。Apache Cassandra 是一个高度可扩展的分布…

Spring MVC实战指南:构建高效Web应用的架构与技巧(三)

响应数据和结果视图(7种) 返回值分类 创建web.xml&#xff08;spring、过滤器解决乱码、配置控制器dispatcherServlet、加载springmvc.xml文件、配置启动加载&#xff09;创建springmvc.xml文件 <!--配置了内容&#xff0c;启动Tomcat服务器的时候&#xff0c;就会被加载--…

oscp备考 oscp系列——Kioptix Level 1靶场 古老的 Apache Vuln

目录 前言 1. 主机发现 2. 端口扫描 3. 指纹识别 4. 目录扫描 5. 漏洞搜索和利用 前言 oscp备考&#xff0c;oscp系列——Kioptix Level 1靶场 Kioptix Level 1难度为简单靶场&#xff0c;主要考察 nmap的使用已经是否会看输出&#xff0c;以及是否会通过应用查找对应漏…

Linux下编译安装PETSc

本文记录在Linux下编译安装PETSc的流程。 零、环境 操作系统Ubuntu 22.04.4 LTSVS Code1.92.1Git2.34.1GCC11.4.0CMake3.22.1oneAPI2024.2.1 一、安装依赖 1.1 安装oneAPI 参见&#xff1a;Get the Intel oneAPI Base Toolkit , Get the Intel oneAPI HPC Toolkit 1.2 安…

深入Android架构(从线程到AIDL)_11 线程之间的通信架构

目录 5、 线程之间的通信架构 认识Looper与Handler对象 主线程丢信息给自己 子线程丢信息给主线程 替子线程诞生Looper与MQ 5、 线程之间的通信架构 认识Looper与Handler对象 当主线程诞生时&#xff0c;就会去执行一个代码循环(Looper)&#xff0c;以便持续监视它的信息…

【中间件】docker+kafka单节点部署---zookeeper模式

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言消息中间件介绍1. KRaft模式2. zookeeper模式2.1. 单节点部署安装验证 前言 最近生产环境上准备部署ELFK日志监控&#xff0c;先在测试环境部署单节点kafka验证…

AI-Talk开发板之超拟人

一、说明 运行duomotai_ap sdk下的LLM_chat例程&#xff0c;实现开发板和超拟人大模型进行语音交互&#xff0c;支持单轮和多轮交互。 二、SDK更新 v2.3.0及以上的SDK版本才支持超拟人&#xff0c;如果当前SDK在v2.3.o以下&#xff0c;需要更新SDK。在SDK目录(duomotai_ap)下…

2024年, Milvus 社区的那些事

随着跨年钟声响起&#xff0c;2024 年告一段落。这一年&#xff0c;Milvus GitHub Stars 正式突破 3 万大关&#xff0c;Docker 下载量突破6700w 次&#xff0c;达到一个新的里程碑&#xff0c;在开源向量数据库领域继续引领前行。在这遥遥领先的数据背后&#xff0c;不妨让我们…

docker中使用Volume完成数据共享

情景概述 在一个docker中&#xff0c;部署两个MySQL容器&#xff0c;假如它们的数据都存储在自己容器内部的data目录中。这样的存储方式会有以下问题&#xff1a; 1.无法保证两个MySQL容器中的数据同步。 2.容器删除后&#xff0c;数据就会丢失。 基于以上问题&#xff0c;容…

【期末复习】二、进程管理

1.进程的内存结构🍊 程序加载到内存之后就变成了一个进程,进程在内存当中的一个结构有: 文本段(text section):存放程序代码 栈(stack):存放局部变量和函数返回地址 数据段(data section):存放全局变量和静态变量(static) 堆(heap):程序运行时的动态内存分…

Type-C多口适配器:高效充电与连接解决方案

在科技飞速发展的今天&#xff0c;我们的生活已经离不开各种各样的电子设备&#xff0c;如智能手机、平板电脑、智能手表和无线耳机等。这些设备不仅丰富了我们的数字生活&#xff0c;也带来了更多的充电需求。传统的单一充电口已经难以满足现代人对于便捷性和效率的追求&#…

UCAS 24秋网络认证技术 CH10 SSL 复习

TLS字段、参数含义要了解每个消息是什么意思 基本方式只验证服务端&#xff0c;服务端有证书&#xff0c;变形方式加上验证客户端TLS1.3区别 协商过程 背景 Record层使用的各种加密算法参数&#xff0c;均由Handshake协议协商获得。 具体过程 随机数交换 Client/Server相互…

支付宝商家转账到账户余额,支持多商户管理

大家好&#xff0c;我是小悟 转账到支付宝账户是一种通过 API 完成单笔转账的功能&#xff0c;支付宝商家可以向其他支付宝账户进行单笔转账。 商家只需输入另一个正确的支付宝账号&#xff0c;即可将资金从本企业支付宝账户转账至另一个支付宝账户。 该产品适用行业较广&am…

基于Django的旅游信息管理系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 随着我国经济的高速发展与人们生活水平的日益提高&#xff0c;人们对生活质量的追求也多种多样。尤其在人们生活节奏不断加快的当下&#xff0c;人们更趋向于足不出户解决生活上的问题&#xff0c;线上管理系统展现了其蓬勃生命力和广阔的前景。与此同时&#xff0c;随着…

常见的框架漏洞复现

1.Thinkphp Thinkphp5x远程命令执行及getshell 搭建靶场 cd vulhub/thinkphp/5-rce docker-compose up -d 首页 漏洞根本源于 thinkphp/library/think/Request.php 中method方法可以进行变量覆盖&#xff0c;通过覆盖类的核心属性filter导致rce&#xff0c;其攻击点较为多&…

UE5中实现右键开镜效果

右键之后添加时间轴&#xff0c;然后设置视野即可。Set Field Of View 时间轴设置&#xff0c;第一个点设置0,90度&#xff0c;因为默认的就是90度 第二个点看武器的类型或者倍境来设置&#xff0c;时间就是开镜时间&#xff0c;值越小开镜速度越快&#xff0c;第二个值就是视野…

关于华硕Armoury Crate(奥创中心)安装程序失败、卡进度条问题解决方案

关于华硕Armoury Crate(奥创中心&#xff09;安装失败解决方案 清理旧版本文件 如果之前安装过Armoury Crate&#xff0c;可能有残留文件导致冲突&#xff1a; 利用官方的卸载工具&#xff0c;卸载旧版本&#xff1a; https://www.asus.com.cn/supportonly/armoury%20crate/…

iOS18 上的 Genmoji

在 WWDC 2025 期间&#xff0c;一种名为Genmoji的新型表情符号问世。 许多用户可能仍然不确定 Genmoji 是什么、它与传统表情符号有何不同、如何使用它以及如何恢复丢失的数据。因此&#xff0c;在本文中&#xff0c;我们将介绍您需要了解的有关 iOS 18 上的 Genmoji 的所有信…