项目前置知识——不定参以及设计模式

article/2025/6/7 22:48:09

1.C语言不定参宏函数

c语言中,printf就是一个不定参函数,在使用不定参宏函数时,我们使用__VA_ARGS__来解析不定参:

#include <iostream>
#include <cstdarg>#define LOG(fmt/*格式*/, .../*用...表示不定参*/) printf("[%s:%d]"fmt, __FILE__, __LINE__, __VA_ARGS__)int main()
{LOG("%s", "test for Indefinite parameter");LOG("%s-%d", "aaaaaa", 1233456);return 0;
}

运行结果:

[test.cc:9]test for Indefinite parameter
[test.cc:10]aaaaaa-1233456

但是当我们只传入一个格式字符串,而没有后面的不定参,此时该宏就会出错

 LOG("only fmt");

这是因为只传入一个fmt,此时LOG会被替换成为 -> printf("[%s:%d]" "only fmt", "test.cc", "11", );

可以看到,多了一个逗号,此时printf解析时,就会出错。所以,为了避免这个问题,我们需要在使用__VA_ARGS__时前面加上##,##的作用就是在不定参为空时,删除逗号。 

2.C语言不定参函数 

同样,在函数中,用...表示不定参。在函数中,我们使用va_list、va_start、va_arg、va_end来解析不定参。

#include <iostream>
#include <cstdio>
#include <cstdarg>void Print(int count, ...)
{va_list ap;va_start(ap, count);for(int i=0; i<count; i++) {int num = va_arg(ap, int);printf("arg[%d]:%d\n", i+1, num);}va_end(ap);
}int main()
{Print(1, 1);std::cout << std::endl;Print(2, 1, 2);std::cout << std::endl;Print(3, 1, 2, 3);return 0;
}

  • va_list:特殊数据结构,相当于一个指针,用来管理所有的不定参列表;
  • va_start(ap,param):用于初始化va_list列表,ap表示va_list数据结构,param表示不定参前的第一个固定参数。(在调用函数时,要创建函数栈帧,同时会进行参数压栈,va_start的作用就是获取固定参数的压栈位置,让ap指向该固定位置的下一个位置,即不定参的第一个位置)。
  • va_arg(ap, type):用于获取下一个可变参数的值。ap表示不定参数列表,type表示下一个不定参的数据类型。同时,用va_arg之后,ap会自动指向下一个不定参。所以,va_arg在使用前,必须得知不定参的类型。
  • va_end(ap):可变参数列表使用完之后,用该函数进行清理,释放资源。

 使用vasprintf结合不定参列表实现myPrintf

vasprintf:用于将可变参数按照格式化字符串生成动态分配的字符串

#include <iostream>
#include <cstdio>
#include <cstdarg>void myPrintf(const char *fmt, ...)
{va_list ap;va_start(ap, fmt);char *str;int n = vasprintf(&str, fmt, ap);if(n == -1) {std::cerr << "vasprintf error" << std::endl;va_end(ap);return;}va_end(ap);printf("%s", str);free(str);
}int main()
{myPrintf("%d\n", 123456);myPrintf("%s\n", "hahahahaha");myPrintf("[%s:%d]-%s\n", __FILE__, __LINE__, "test my printf");return 0;
}

3.C++不定参函数 

c++不定参函数,在编译时进行递归式包扩展,每一次会将参数包的一个参数提到t中,参数包少一个参数,当参数包为空时,此时就会调用无参Print,此时参数包解析完毕

在解析过程中,使用forward完美转发,确保参数包的参数在解析过程中,属性不变(左值还是左值,右值还是右值)。

#include <iostream>
#include <cstdio>
#include <cstdarg>void Print()
{std::cout << std::endl;
}template <typename T, typename ...Args>
void Print(const T& t, Args &&...args)
{std::cout << t << " ";Print(std::forward<Args>(args)...);
}int main()
{Print("123"); Print("123", 1209); Print("123", "456", "test for c++ args"); return 0;
}

4.设计模式

4.1设计模式的六大原则

0x1.单一职责原则(SRP:Single Responsibility Principle)

  • 定义:一个类应该只有一个引起它变化的原因,也就是说一个类只负责干一件事。避免类和类之间的功能耦合
  • 应用场景:将用户管理(注册、登录)与权限验证分离。文件操作类拆分为读取和写入两个独立类。

0x2. 开闭原则(OCP:Open/Closed Principle) 

  • 定义软件实体应当对扩展开放,对修改关闭。即软件中的类、模块、函数等应该可以在不修改原有代码的基础上进行功能扩展。即通过抽象和多态实现扩展,而非修改原有代码。
  • 应用场景:使用接口 / 抽象类定义规范,通过子类扩展功能。

0x3. 里氏替换原则(LSP:Liskov Substitution Principle)

  • 定义:子类可以替换其父类且不影响程序的正确性。本质上就是多态的应用,子类重写父类的虚方法,可以用父类指针指向子类对象。
  • 应用场景: 在使用继承机制构建类层次结构时,需要遵循里氏替换原则。比如在一个图形编辑软件中,有一个图形基类,用于定义图形的基本属性和操作,如移动、缩放。圆形、矩形等具体图形类继承自这个基类。当需要对图形进行统一操作(如批量移动图形)时,就可以利用基类的引用指向不同子类的实例来实现,而不需要关心具体是哪种图形。

0x4.依赖倒置原则(DIP:Dependency Inversion Principle)

  • 定义:高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。什么意思呢?就是说,类与类之间的交互,都通过它们的抽象基类来交互,而不通过具体的某一个类。这样我们就可以在高层模块中进行底层模块的替换。
  • 应用场景:比如我们正在开发游戏,有两种武器(剑,弓),两种角色(剑士,弓箭手)。而我们在实现角色类时,不要将角色与武器绑死,而是在角色内部使用基类指向武器,我们根据传入武器的类型,来判断到底是什么角色。

0x5.接口隔离原则(ISP:Interface Segregation Principle)

  • 定义: 客户端不应该依赖它不需要的接口。实现思路:将本应该设计到一个基类中的虚函数接口,重新定义为一个一个的子项(类,该类中只有一个虚函数成员接口),再想实现具体类时,可以根据具体需要的功能,继承不同的子项类。
  • 应用场景:有一个打印机接口,其中包含了打印、复印、扫描等多个方法。但有些打印机(如小型便携式打印机)可能只支持打印功能。按照接口隔离原则,应该将这些功能分成不同的接口,如打印接口、复印接口、扫描接口,这样小型打印机就可以只实现打印接口,避免了实现不需要的接口方法。

 0x6.迪米特法则(LoD:Law of Demeter,最少知识原则)

  • 定义:一个对象应该对其他对象有最少的了解。
  • 应用场景:在一个学生成绩管理系统中,学生类和成绩类是直接相关的,成绩类和课程类也是直接相关的。但是学生类不应该直接操作课程类,而应该通过成绩类来进行间接操作。这样可以降低对象之间的耦合度。

5.单例模式 

单例模式,确保一个类只能实例化出一个实例,并提供一个全局访问点来获取该实例。

而创建单例的方式有两种:饿汉模式和懒汉模式。

5.1饿汉模式创建单例

在程序启动时就会创建出一个实例。但因为不管我们是否使用该实例,都会在程序启动时创建,所以饿汉模式可能会影响程序的启动速度。

所以饿汉模式一般适用于实例创建开销小,或需要在程序启动时立即初始化的情况。

class SinglEton
{
public:static SinglEton& getInstance() { return _eton; }private:// 构造函数私有化,并且避免拷贝对象,所以删除拷贝构造和赋值SinglEton(){}SinglEton(const SinglEton& se) = delete;SinglEton operator=(const SinglEton& se) = delete;
private:static SinglEton _eton;
};// 立即实例化单例对象
SinglEton SinglEton::_eton;

饿汉模式创建单例是线程安全的,多线程进入getInstance获取的都是同一个单例,因为单例早已经创建好了。

2.懒汉模式创建单例

在第一次使用单例时,才开始创建实例。懒汉模式其实是一种延迟初始化的思想。

适用于单例对象构造特别耗时或者耗费济 源(加载插件、加载⽹络资源等)的情况。

2.1double-check懒汉单例

class LazySingleton
{
public:static LazySingleton* getInstance(){if(!_instance) {std::lock_guard<std::mutex> lock(_mutex);if(!_instance) {_instance = new LazySingleton();}}return _instance;}
private:LazySingleton(){}LazySingleton(const LazySingleton &lse) = delete;LazySingleton operator=(const LazySingleton &lse) = delete;
private:static LazySingleton* _instance;static std::mutex _mutex;
};// 静态成员初始化
LazySingleton* LazySingleton::_instance = nullptr;
std::mutex LazySingleton::_mutex;

第一次检查,如果不为空,直接返回,提高程序性能(实例创建好后,以后的线程就不再需要加锁在访问了)

第二次检查,此时已加锁,再次判断是否为空(因为在加锁期间,可能已经有线程创建了单例)。

2.2 Meyers 单例(C++11 及以后)

class MeyersSingleton {
private:MeyersSingleton() = default;~MeyersSingleton() = default;MeyersSingleton(const MeyersSingleton&) = delete;MeyersSingleton& operator=(const MeyersSingleton&) = delete;public:static MeyersSingleton& getInstance() {static MeyersSingleton instance;  // 线程安全的局部静态变量return instance;}
};

之所以能够这么创建单例,是因为c++11以后,局部静态变量的初始化是线程安全的,编译器会保证即使在多线程环境下,也只会初始化一次。每次调用 getInstance() 方法时,都会返回同一个 instance 对象的引用。

6.工厂模式 

工厂模式是一种创建型设计模式,它将对象的创建和使用分离,通过工厂类来负责创建对象。这种模式可以提高代码的可维护性和可扩展性。

6.1简单工厂模式

简单工厂模式是工厂模式的基础形式,它定义一个工厂类来创建产品对象。根据指定的类型,创建出指定类型的产品。适用于产品种类较少且不会频繁变化的场景。

但是简单工厂模式不满足6大原则中的开闭原则,当有了新产品,我们需要在原有代码上进行修改。

#include <iostream>
#include <memory>// 抽象产品类
class Product {
public:virtual void operation() const = 0;virtual ~Product() = default;
};// 具体产品类A
class ConcreteProductA : public Product {
public:void operation() const override {std::cout << "ConcreteProductA operation" << std::endl;}
};// 具体产品类B
class ConcreteProductB : public Product {
public:void operation() const override {std::cout << "ConcreteProductB operation" << std::endl;}
};// 简单工厂类
class SimpleFactory {
public:static std::unique_ptr<Product> createProduct(char type) {switch (type) {case 'A':return std::make_unique<ConcreteProductA>();case 'B':return std::make_unique<ConcreteProductB>();default:throw std::invalid_argument("Invalid product type");}}
};

6.2工厂方法模式

由抽象工厂类,派生出具体工厂类,每一个工厂负责生产一个专门的产品。便于扩展新的产品种类,符合开闭原则。

#include <iostream>
#include <memory>// 抽象产品类
class Product {
public:virtual void operation() const = 0;virtual ~Product() = default;
};// 具体产品类
class ConcreteProductA : public Product {
public:void operation() const override {std::cout << "ConcreteProductA operation" << std::endl;}
};class ConcreteProductB : public Product {
public:void operation() const override {std::cout << "ConcreteProductB operation" << std::endl;}
};// 抽象工厂类
class Factory {
public:virtual std::unique_ptr<Product> createProduct() const = 0;virtual ~Factory() = default;
};// 具体工厂类
class ConcreteFactoryA : public Factory {
public:std::unique_ptr<Product> createProduct() const override {return std::make_unique<ConcreteProductA>();}
};class ConcreteFactoryB : public Factory {
public:std::unique_ptr<Product> createProduct() const override {return std::make_unique<ConcreteProductB>();}
};

6.3抽象工厂模式

在产品类型较多的情况下,由抽象工厂类派生出具体的工厂类,这些工厂类都有相同的功能,只不过产生的是不同商家的产品。适用于产品组合较多且需要一起使用的情况。

下面以一个开发游戏的例子来说明:

// 武器抽象类
class Weapon {
public:virtual void attack() const = 0;virtual ~Weapon() = default;
};// 防具抽象类
class Armor {
public:virtual void defend() const = 0;virtual ~Armor() = default;
};
// 现代武器
class ModernGun : public Weapon {
public:void attack() const override {std::cout << "使用现代枪械射击!" << std::endl;}
};// 现代防具
class ModernVest : public Armor {
public:void defend() const override {std::cout << "使用防弹衣防御!" << std::endl;}
};// 中世纪武器
class MedievalSword : public Weapon {
public:void attack() const override {std::cout << "使用中世纪长剑挥砍!" << std::endl;}
};// 中世纪防具
class MedievalPlate : public Armor {
public:void defend() const override {std::cout << "使用全身板甲防御!" << std::endl;}
};// 未来武器
class FutureLaser : public Weapon {
public:void attack() const override {std::cout << "使用激光武器射击!" << std::endl;}
};// 未来防具
class FutureShield : public Armor {
public:void defend() const override {std::cout << "使用能量护盾防御!" << std::endl;}
};
// 装备工厂抽象类
class EquipmentFactory {
public:virtual std::unique_ptr<Weapon> createWeapon() const = 0;virtual std::unique_ptr<Armor> createArmor() const = 0;virtual ~EquipmentFactory() = default;
};
// 现代装备工厂
class ModernEquipmentFactory : public EquipmentFactory {
public:std::unique_ptr<Weapon> createWeapon() const override {return std::make_unique<ModernGun>();}std::unique_ptr<Armor> createArmor() const override {return std::make_unique<ModernVest>();}
};// 中世纪装备工厂
class MedievalEquipmentFactory : public EquipmentFactory {
public:std::unique_ptr<Weapon> createWeapon() const override {return std::make_unique<MedievalSword>();}std::unique_ptr<Armor> createArmor() const override {return std::make_unique<MedievalPlate>();}
};// 未来装备工厂
class FutureEquipmentFactory : public EquipmentFactory {
public:std::unique_ptr<Weapon> createWeapon() const override {return std::make_unique<FutureLaser>();}std::unique_ptr<Armor> createArmor() const override {return std::make_unique<FutureShield>();}
};

7.建造者模式

建造者模式(Builder Pattern)是一种创建型设计模式,它允许我们分步骤构建复杂对象,并且允许相同的构建过程创建不同的表示。它将构建过程与其表示分离,使相同的构建过程可以创建不同的表示。简单来说,就是创建一个对象需要很多部件,建造者模式允许我们先创建出这些部件,再有这些部件创造出一个我们所需要的对象

建者造模式的结构

  1. 产品(Product):要创建的复杂对象。

  2. 抽象建造者(Builder):声明创建产品各部件的抽象接口。

  3. 具体建造者(Concrete Builder):实现抽象建造者接口,构建并装配产品部件。

  4. 指挥者(Director):使用具体建造者来构建产品,它不依赖于产品具体类。

#include <iostream>
#include <memory>
#include <vector>
#include <string>// 产品类:汽车
class Car {
private:std::string engine;std::string chassis;std::vector<std::string> wheels;std::string body;std::string interior;public:void setEngine(const std::string& eng) { engine = eng; }void setChassis(const std::string& chs) { chassis = chs; }void addWheel(const std::string& wl) { wheels.push_back(wl); }void setBody(const std::string& bd) { body = bd; }void setInterior(const std::string& intr) { interior = intr; }void display() const {std::cout << "Car Details:\n";std::cout << "Engine: " << engine << "\n";std::cout << "Chassis: " << chassis << "\n";std::cout << "Wheels: ";for (const auto& wheel : wheels) {std::cout << wheel << " ";}std::cout << "\nBody: " << body << "\n";std::cout << "Interior: " << interior << "\n";}
};// 抽象建造者
class CarBuilder {
public:virtual void buildEngine() = 0;virtual void buildChassis() = 0;virtual void buildWheels() = 0;virtual void buildBody() = 0;virtual void buildInterior() = 0;virtual Car getResult() = 0;
};// 具体建造者:SUV建造者
class SUVBuilder : public CarBuilder {
private:Car car;public:void buildEngine() override {car.setEngine("Powerful V6 Engine");}void buildChassis() override {car.setChassis("Heavy-Duty Chassis");}void buildWheels() override {car.addWheel("All-Terrain Wheel");car.addWheel("All-Terrain Wheel");car.addWheel("All-Terrain Wheel");car.addWheel("All-Terrain Wheel");}void buildBody() override {car.setBody("Robust SUV Body");}void buildInterior() override {car.setInterior("Luxurious SUV Interior");}Car getResult() override {return car;}
};// 具体建造者:轿车建造者
class SedanBuilder : public CarBuilder {
private:Car car;public:void buildEngine() override {car.setEngine("Efficient I4 Engine");}void buildChassis() override {car.setChassis("Standard Chassis");}void buildWheels() override {car.addWheel("Regular Wheel");car.addWheel("Regular Wheel");car.addWheel("Regular Wheel");car.addWheel("Regular Wheel");}void buildBody() override {car.setBody("Sleek Sedan Body");}void buildInterior() override {car.setInterior("Comfortable Sedan Interior");}Car getResult() override {return car;}
};// 指挥者
class Director {
public:void constructCar(CarBuilder& builder) {builder.buildEngine();builder.buildChassis();builder.buildWheels();builder.buildBody();builder.buildInterior();}
};

在建造者模式中,指挥者类是用来组装所有部件来构建对象的类,因为有些对象的创建是有顺序的。


http://www.hkcw.cn/article/gnCLjrfmVm.shtml

相关文章

redis哨兵与集群部署

目录 一.哨兵模式部署 1.设置Redis哨兵模式配置文件的属组以及属主&#xff08;所有节点操作&#xff09; 2.修改Redis哨兵模式的配置文件&#xff08;所有节点操作&#xff09; 3.准备server文件启动&#xff0c;先启主再启从&#xff08;所有节点操作&#xff09; 4.验证…

飞腾D2000,麒麟系统V10,docker,ubuntu1804,小白入门喂饭级教程

#下载docker Index of linux/static/stable/ 根据电脑的CPU类型选择&#xff1a; Intel和AMD选x86_64飞腾D2000选aarch64 #选择较新的版本 #在包含下载的docker-XX.X.X.tgz的文件夹中右键->打开终端 # 解压安装包&#xff08;根据实际下载的文件&#xff09; tar -zxvf …

torch.nn中的各种组件

Content 线性层 (Linear Layers)**核心原理&#xff1a;线性变换****关键组件****示例代码****示例1&#xff1a;基础线性层 (nn.Linear)****示例2&#xff1a;双线性层 (nn.Bilinear)** **应用场景** 非线性激活函数 (Non-linear Activations)**常用非线性激活函数****1. ReLU…

高性能分布式消息队列系统(二)

上一篇博客将C进行实现消息队列的用到的核心技术以及环境配置进行了详细的说明&#xff0c;这一篇博客进行记录消息队列进行实现的核心模块的设计 五、项目的需求分析 5.1、项目框架的概念性理解 5.1.1、消息队列的设计和生产消费者模型的关系 在现代系统架构中&#xff0c;…

AI健康小屋+微高压氧舱:科技如何重构我们的健康防线?

目前&#xff0c;随着科技和社会的不断发展&#xff0c;人们的生活水平和方式有了翻天覆地的变化。 从吃饱穿暖到吃好喝好再到健康生活&#xff0c;观念也在逐渐发生改变。 尤其是在21世纪&#xff0c;大家对健康越来越重视&#xff0c;这就不得不提AI健康小屋和氧舱。 一、A…

Azure DevOps Server 2022.2 补丁(Patch 5)

微软Azure DevOps Server的产品组在4月8日发布了2022.2 的第5个补丁。下载路径为&#xff1a;https://aka.ms/devops2022.2patch5 这个补丁的主要功能是修改了代理(Agent)二进制安装文件的下载路径&#xff1b;之前&#xff0c;微软使用这个CND(域名为vstsagentpackage.azuree…

Rag技术----项目博客(六)

RAG 定义&#xff1a;检索增强生成&#xff08;Retrieval Augmented Generation&#xff09;&#xff0c;简称 RAG&#xff0c;已经成为当前最火热的LLM应用方案。 目的&#xff1a;通过提供相关领域数据库通过问题检索信息&#xff0c;将相关信息合并到Prompt中&#xff0c;…

BioID技术:揭示铁死亡机制中Caspase-2蛋白相互作用网络

铁死亡&#xff08;Ferroptosis&#xff09;是一种铁依赖的非凋亡形式的细胞死亡&#xff0c;其发生与细胞内氧化应激失衡以及抗氧化防御途径受损密切相关。随着研究的深入&#xff0c;学界逐渐认识到蛋白质相互作用在铁死亡调控中扮演着关键角色。铁死亡作为一种新型的细胞死亡…

机器学习——随机森林算法

随机森林算法是一种强大的树集成算法&#xff0c;比使用单个决策树效果要好得多。 以下是生成树集成的方法&#xff1a;假设有一个大小为m的训练集&#xff0c;然后对于b1到B&#xff0c;所以执行B次&#xff0c;可以使用有放回抽样来创建一个大小为m的训练集。所以如果有10个…

快速排序(Quick Sort)算法详解(递归与非递归)

引言 在计算机科学中&#xff0c;排序算法是最基础且重要的算法之一。快速排序&#xff08;Quick Sort&#xff09;作为一种高效的排序算法&#xff0c;在实际应用中被广泛使用。平均时间复杂度为 (O(n log n))&#xff0c;最坏情况下为 (O(n^2))。本文将详细介绍快速排序算法…

8.RV1126-OPENCV 视频中添加LOGO

一.视频中添加 LOGO 图像大体流程 首先初始化VI,VENC模块并使能&#xff0c;然后创建两个线程&#xff1a;1.把LOGO灰度化&#xff0c;然后获取VI原始数据&#xff0c;其次把VI数据Mat化并创建一个感兴趣区域&#xff0c;最后把LOGO放感兴趣区域里并把数据发送给VENC。2.专门获…

Linux 下 ChromeDriver 安装

个人博客地址&#xff1a;Linux 下 ChromeDriver 安装 | 一张假钞的真实世界 Selenium 是一个用于 Web 应用程序测试的工具。可以通过它驱动浏览器执行特定的操作&#xff0c;如点击、下滑、资源加载与渲染等。该工具在爬虫开发中也非常有帮助。Selenium 需要通过浏览器驱动操…

C++学者给您讲数学之——数列

C学者为您解析数列基础 数列的概念 **数列&#xff08;sequence of number&#xff09;**是以正整数集&#xff08;或其有限子集&#xff09;为定义域的有序数集。数列中的每个数称为该数列的项&#xff0c;其中&#xff1a; 第一位称为第1项&#xff08;首项&#xff09; 第…

【Harmony OS】数据存储

目录 数据存储概述 首选项数据存储 关系型数据库 数据存储概述 • 数据存储 是为了解决应用数据持久化问题&#xff0c;使得数据能够存储在外存中&#xff0c;达到保存或共享目的。 • 鸿蒙应用数据存储包括 本地数据存储 和 分布式数据存储 。 • 本地数据存储 为应用…

程序员健康防护指南

深度学习欢迎访问&#xff1a;通义灵码2.5qwen3——节假日抢票不用愁&#xff0c;基于12306-MCP实现个人火车票智能查询小助手&#xff01;-CSDN博客 一、视觉系统防护工程 1. 数字眼疲劳综合征防控 蓝光管理&#xff1a;使用经认证的防蓝光眼镜可过滤45%有害蓝光&#xff0c;…

CSS 平铺+自动换行效果

先上效果图 样式 <template><div class"activity-questions"><h1>活动题库</h1><div v-if"loading" class"loading">加载中...</div><div v-else><div v-if"questions.length 0" clas…

苏超火了 “苏大强”的作业怎么抄 全网热潮背后的足球盛宴

“比赛第一,友谊第十四”是这里的原则。近日,江苏省首届城市足球联赛“苏超”火出圈。“苏超”由江苏省体育局与江苏省各设区市政府联合主办,13个设区市各派一队参加。联赛打破了准入的边界,队伍中既有职业球员也有个体工商户、大学生和高中生等业余球员。尽管球员水平与中…

ck-editor5的研究 (7):自定义配置 CKeditor5 的 toolbar 工具栏

文章目录 一、前言二、实现步骤1. 第一步: 搭建目录结构2. 第二步:配置toolbar工具栏的步骤(2-1). 配置粗体和斜体(2-2). 配置链接和标题+正文(2-3). 配置列表和引用(2-4). 配置自动格式化3. 第三步:更多工具三、测试效果和细节四、总结一、前言 在前面的文章中,我们已经对…

Skydel25.4发布:解锁自定义星座,增强C波段与干扰模拟能力

在GNSS模拟技术持续迭代的浪潮中&#xff0c;Skydel迈出创新一步&#xff0c;正式发布25.4.0版本及后续修复版本25.4.1。本次更新的核心突破在于引入了强大的自定义星座功能&#xff0c;赋予用户前所未有的自由度&#xff0c;可创建包含多达400颗卫星的专属星座&#xff0c;突破…