常用设计模式全面总结版(JavaKotlin)

这篇文章主要是针对之前博客的下列文章的总结版本:

  • 《设计模式系列学习笔记》
  • 《Kotlin核心编程》笔记:设计模式
  • 【Android知识笔记】FrameWork中的设计模式

主要为了在学习了 Kotlin 之后,将 Java 的设计模式实现与 Kotin 的实现放在一起做一个对比。

一、创建型模式

单例模式(Java)

Double Check Lock + volatile 版本:

public class Singleton {
    
     /** 使用 volatile 保证对该变量的写入对另一个线程可见 */
    private volatile static Singleton mSingleton = null;
    
    private Singleton() {
   }
    
    public static Singleton getInstance() {
   
        if (mSingleton == null) {
   
            synchronized(Singleton.class) {
   
                if (mSingleton == null) {
   
                    mSingleton = new Singleton();
                }
            }
        }
        return mSingleton;
    }
}

为什么要 Double Check Lockvolatile,因为 mSingleton = new Singleton() 这一句翻译成字节码指令可能对应多条,它不是一次性完成的原子性操作,会分成三个步骤完成:

  1. 给实例分配内存,
  2. 调用构造函数初始化实例,
  3. 给变量赋值操作。

这三个步骤中后面两步可能发生指令重排序,即顺序为1-3-2,从而导致双重锁定失效,另一个线程同时访问就会拿到一个还没来得及初始化完毕的对象实例,出现问题。

volatile 在这里的主要作用是禁止指令重排序。另外 volatile 还可以保证线程可见性(但不保证原子性)。

静态内部类版本:

public class Singleton {
   

    private Singleton(){
   }
    
    /**
     * 静态内部类,内部类的实例与外部类的实例没有绑定关系,
     * 只有被调用到时才会加载,从而实现延迟加载
     */
    private static class SingletonHolder {
   
        /** 静态初始化器,由 JVM 类加载过程来保证线程安全 */
        private static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
   
        return SingletonHolder.instance;
    }
}

注意这个写法,只有getInstance()方法是public的,其他静态内部类及其内部静态变量都是private的。

getInstance方法第一次被调用的时候,会导致JVM 虚拟机加载 SingletonHolder 类,它的静态成员变量得到初始化;从而创建 Singleton 的实例。

补充:JVM加载类的过程:加载 -> 验证 -> 准备 -> 解析 -> 初始化

准备阶段会为SingletonHolder中的静态成员instance赋值为null,在编译时会收集静态赋值语句等组成类构造器<cinit>(),在初始化阶段会执行类构造器来初始化一个类。

触发 JVM 初始化一个类有以下几种情况:

  • 使用new关键字创建对象
  • 访问类的静态成员变量 或 对类的静态成员变量进行赋值
  • 调用类的静态方法
  • 反射调用类时,如 Class.forName()
  • 初始化子类时,会先初始化其父类(如果父类还没有进行过初始化的话)
  • 遇到启动类时,如果一个类被标记为启动类(即包含main方法),虚拟机会先初始化这个主类。
  • 实现带有默认方法的接口的类被初始化时(拥有被default关键字修饰的接口方法的类)
  • 使用 JDK7 新加入的动态语言支持时 MethodHandle

显然当调用Singleton.getInstance() -> SingletonHolder.instance 时,命中上面第 2 条,JVM 就会执行 SingletonHolder 这个类的初始化,然后调用其类构造器<cinit>(),这个时候就会初始化Singleton instance静态成员对象的实例。

在这个过程中, JVM 会保证SingletonHolder这个类的<clinit>()方法被正确的加锁同步,在多线程访问的情况下,只会有一个线程看到这个类的初始化过程,而其他线程是不可见的, 其它线程都需要等待,直到<clinit>()方法执行完毕。所以是线程安全的。

这种方式既保证了线程安全性,又保证了对象的唯一性。并且还是懒加载,只有被调用时才会初始化。

枚举单例:

public enum Singleton {
   
    INSTANCE;
    public void doSomething() {
   
        // todo
    }
}

Android源码中的单例:

  • context.getSystemService(name)context的实现类 ContextImpl 中使用静态的Map对象存储ServiceFecther对象(恶汉)
  • 线程内的单例ThreadLocal,如 ChoreographerLooper 中都使用 ThreadLocal 绑定当前线程,同一个 ThreadLocal 对象在同一个线程内返回的对象是唯一的。
  • 进程内的单例:如 ActivityManager 类的 getService() 方法返回的 IActivityManagerSingleton 单例在进程内唯一。
  • 进程间的单例ServiceManager,不同进程之间内存是隔离的,在进程间共享的单例其实是由binder驱动来保证不同的进程共用同一个对象的。对所有的进程而言,ServiceManager对应同一个binder句柄(通过 0Handle句柄引用),binder句柄处理完不同进程的请求后会转发给ServiceManager所在的进程。

Q:怎样在应用里做一个跨进程的单例?

  • 单例对象类必须实现Parcelable接口,成员变量除了基本数据类型外都必须实现Parcelable接口
  • 通过 AIDL 接口, 拿到远端进程的IBinder对象,再进行 Binder 调用远端接口的业务方法,将单例对象作为参数发布给远端进程
  • 远端进程在业务方法中将接受到单例对象保存下来使用

单例模式(kotlin)

在 kotlin 中 object 类是天生的单例模式。

object Singleton {
   
    var x: Int = 2 
    fun y() {
    }
}

这个代码翻译成 Java 字节码后发现它其实就是恶汉模式的单例

如果是一个普通类想生成单例,可以使用伴生对象 companion object 来生成:

class Singleton {
   
    companion object {
   
        var x: Int = 2
        fun y() {
    }
    }
}

如果要实现 Java 中静态内部类版本的单例模式,可以像下面这样写:

class Singleton private constructor() {
   

    private object Holder {
   
        val INSTANCE = Singleton()
    }
    
    companion object {
   
        val instance = Holder.INSTANCE
    }
}

object不能定义构造函数,但可以定义init块:

object Singleton {
   
    var x: Int = 2 
    fun y(){
    }

    init {
   
        //object不能定义构造函数,但可以定义init块
    }
}

如果要在Java中使用kotlin的单例,最好在成员属性和成员方法上分别加上 @JvmField@JvmStatic注解:

object Singleton {
   
    @JvmField var x: Int = 2  
    @JvmStatic fun y() {
    } 
}
  • @JvmField 的作用是生成java的静态成员 不会生成gettersetter方法。
  • @JvmStatic 的作用是生成java的静态方法。

object修饰的类内部方法相当于静态方法,但是伪静态,也就是内部会生成一个静态的成员对象,对类的方法调用实际上是调用的内部静态成员对象的方法,只有在方法上添加@JvmStatic才会真正的生成静态方法。

普通kotlin类(非单例)中使用使用JvmFieldJvmStatic

class Foo {
   
    //普通类中使用JvmField和JvmStatic
    companion object {
   
        @JvmField var x: Int = 2
        @JvmStatic fun y(){
     }
    }
}

注意,加的注解@JvmField@JvmStatic只针对java平台的可用,其他平台并不可行。

单例的object类仍可以继承类:

object Singleton: Runnable{
   
    override fun run() {
   

    }
}

工厂方法模式(Java)

  • 定义了一个创建对象的接口,但是由子类来决定要实例化的是哪一个类。工厂方法使类的实例化延迟到子类中。
public abstract class Product {
   
    public void method1() {
   
    }
    public abstract void method();
}
public class ConcreteProductA extends Product {
   
    @Override
    public void method2() {
   
        //ProductA的业务处理
    }
}
public class ConcreteProductB extends Product {
   
    @Override
    public void method2() {
   
        //ProductB的业务处理
    }
}
public abstract class Factory {
   
    /** 
     * 此方法必须返回一个Product类型的对象
     */
    public abstract <T extends Product> T createProduct(Class<T> c);
}
public class ConcreteFactory extends Factory {
    
    @Override
    public <T extends Product> T createProduct(Class<T> c) {
   
        //创建Product的逻辑,这里是直接调用Class的newInstance方法
        Product product = null;
        try {
   
            product = (Product) Class.forName(c.getName()).newInstance();
        } catch (Exception e) {
   
            e.printStackTrace();
        }
        return (T)product;
    }
}
public class Client {
   
    public static void main(String[] args) {
   
        Creator creator = new ConcreteCreator();
        Product productA = creator.createProduct(ConcreteProductA.class);
        Product productB = creator.createProduct(ConcreteProductB.class);
        ......
    }
}

简单工厂模式:如果实际当中只需要一个工厂类,那么就不需要抽象的工厂基类,可以把创建产品的方法改为静态方法

public class HumanFactory {
   
    @Override
    public static <T extends Human> T createHuman(Class<T> c) {
   
        Human human = null;
        try {
   
            human = (Human) Class.forName(c.getName()).newInstance();
        } catch (Exception e) {
   
            e.printStackTrace();
        }
        return (T) human;
    }
}

多个工厂类:在实际当中如果初始化创建对象的过程比较复杂,比如不同的产品可能会设置不同的参数,这个时候创建产品的方法不能共用,所以这时就需要为每一个产品类创建一个工厂类。

可生成单例模式的工厂类:既然工厂方法是用来生产对象的,那么就可以对工厂方法做简单的修改,只返回一个对象,就变成了单例模式。

public class Singleton {
   
    //不允许通过new产生对象
    private Singleton(){
   
    }
    public void doSomething() {
   
        //业务处理
    }
}

public class SingletonFactory {
   
    private static Singleton singleton;

    static {
   
        try {
   
            Class<?> clazz = Class.forName(Singleton.class.getName());
            //获取无参的构造函数
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            //设置无参的构造函数可访问
            constructor.setAccessible(true);
            //创建一个实例对象
            singleton = (Singleton) constructor.newInstance();
        } catch (Exception e) {
   
            e.printStackTrace();
        }
    }
    
    public static Singleton getSingleton() {
   
        return singleton;
    }
}

可复用缓存的工厂类:如果创建对象的过程比较复杂,或者非常耗时,可以在工厂类内部对已创建的对象进行缓存,以备下次使用。

public class ProductFactory {
   
    private static final Map<String, Product> productMap = new HashMap<>();
    public static synchronized Product createProduct(String type) {
   
        Product product = null;
        //如果缓存中有该对象实例直接使用
        if (productMap.containsKey(type)) {
   
            product = productMap.get(type);
        } else {
   
            if (type.equals("ProductA")) {
   
                product = new ConcreteProductA();
            } else {
   
                product = new ConcreteProductB();
            }
            //把创建的对象缓存起来
            productMap.put(type, product);
        }
        return product;
    }
}

Android源码中的工厂方法模式:

  • 集合类的iterator()返回的是一个新的迭代器对象,其实就是一个工厂方法。
  • ActivityonCreate()方法某种角度上可以看作是一个用于创建ContentView的工厂方法,该ContentView用于填充PhoneWindowDecorView的内容区。

抽象工厂模式(Java)

  • 为创建一组相关或依赖的对象提供一个接口,而无需指定它们的具体类。

在这里插入图片描述

/**
* 抽象工厂
*/
public interface AbstractFactory {
   
    /**创建A产品家族的产品*/
    public AbstractProductA createProductA();
    /**创建B产品家族的产品*/
    public AbstractProductB createProductB();
}
public class ConcreteFactory1 implements AbstractFactory {
   

    @Override
    public AbstractProductA createProductA() {
   
        //创建一个来自A产品家族的产品
        return new ProductA1();
    }

    @Override
    public AbstractProductB createProductB() {
   
        //创建一个来自B产品家族的产品
        return new ProductB1();
    }
}
public class ConcreteFactory2 implements AbstractFactory {
   

    @Override
    public AbstractProductA createProductA() {
   
        //创建一个来自A产品家族的产品
        return new ProductA2();
    }

    @Override
    public AbstractProductB createProductB() {
   
        //创建一个来自B产品家族的产品
        return new ProductB2();
    }
}
/**
* 产品A家族
*/
public abstract class AbstractProductA {
   
    /**每个产品共有的方法*/
    public void shareMethod() {
   
    }
    /**每个产品不同实现的方法*/
    public abstract void doSomething();
}
public class ProductA1 extends AbstractProductA {
   
    @Override
    public void doSomething() {
   
        System.out.println("产品A1的实现方法");
    }
}
public class ProductA2 extends AbstractProductA {
   
    @Override
    public void doSomething() {
   
        System.out.println("产品A2的实现方法");
    }
}
/**
* 产品B家族
*/
public abstract class AbstractProductB {
   
    /**每个产品共有的方法*/
    public void shareMethod() {
   
    }
    /**每个产品不同实现的方法*/
    public abstract void doSomething();
}
public class ProductB1 extends AbstractProductB {
   
    @Override
    public void doSomething() {
   
        System.out.println("产品B1的实现方法");
    }
}
public class ProductB2 extends AbstractProductB {
   
    @Override
    public void doSomething() {
   
        System.out.println("产品B2的实现方法");
    }
}
public class Client {
   
    public static void main(String[] args) {
   
        //创建两个工厂
        AbstractFactory factory1 = new ConcreteFactory1();
        AbstractFactory factory2 = new ConcreteFactory2();

        //使用factory1来创建一组产品,它们来自不同的产品家族
        AbstractProductA productA1 = factory1.createProductA();
        AbstractProductB productB1 = factory1.createProductB();

        //使用factory2来创建一组产品,它们来自不同的产品家族
        AbstractProductA productA2 = factory2.createProductA();
        AbstractProductB productB2 = factory2.createProductB();

        //do something...
    }
}

抽象工厂模式其实就是每个工厂类是为生产某一类相关性很强的产品类族专门特供的版本

在这里插入图片描述

工厂方法模式(Kotlin)

用单例代替工厂类

我们已经知道 Kotlin 支持用 object 来实现 Java 中的单例模式。所以我们可以使用一个 object 单例来代替一个工厂类。

object ComputerFactory {
    // 用 object 代替 class
    fun produce(type: ComputerType): Computer {
   
        return when (type) {
   
            ComputerType.PC -> PC()
            ComputerType.Server -> Server()
        }
    }
}
fun main() {
   
    val compter = ComputerFactory.produce(ComputerType.PC)
    println(compter.cpu)
}

我们可以通过operator操作符重载invoke方法来代替produce,从而进一步简化表达:

object ComputerFactory {
      
    operator fun invoke(type: ComputerType): Computer {
   
        return when (type) {
   
            ComputerType.PC -> PC()
            ComputerType.Server -> Server()
        }
    }
}
fun main() {
   
    val compter = ComputerFactory(ComputerType.PC)
    println(compter.cpu)
}

伴生对象创建静态工厂方法

interface Computer {
   
    val cpu: String
    companion object {
   
        operator fun invoke(type: ComputerType): Computer {
   
            return when (type) {
   
                ComputerType.PC -> PC()
                ComputerType.Server -> Server()
            }
        }
    }
}

class PC(override val cpu: String = "Core") : Computer
class Server(override val cpu: String = "Xeon") : Computer

enum class ComputerType {
    PC, Server }

fun main() {
   
    val compter = Computer(ComputerType.PC)
    println(compter.cpu)
}

这样就可以在一个接口类中添加伴生对象的方式来创建静态工厂方法。

伴生对象也可以指定名字:

interface Computer {
   
    val cpu: String
    companion object Factory {
   
        operator fun invoke(type: ComputerType): Computer {
   
            return when (type) {
   
                ComputerType.PC -> PC()
                ComputerType.Server -> Server()
            }
        }
    }
}

fun main() {
   
    val compter = Computer.Factory(ComputerType.PC)
    println(compter.cpu)
}

为伴生对象添加扩展方法

主要是针对三方类库中无法直接修改源码的类。

fun Computer.Companion.fromCPU(cpu: String): ComputerType? = when(cpu) {
   
    "Core" -> ComputerType.PC
    "Xeon" -> ComputerType.Server
    else -> null
}

指定伴生对象名字的写法:

fun Computer.Factory.fromCPU(cpu: String): ComputerType? = when(cpu) {
   
    "Core" -> ComputerType.PC
    "Xeon" -> ComputerType.Server
    else -> null
}

调用:

fun main() {
   
    val type = Computer.fromCPU("Core")
    println(type)
}

抽象工厂模式(Kotlin)

Kotlin 中的可以使用内联函数 inline + reified 关键字来简化抽象工厂模式:

class Dell: Computer {
    }
class Asus: Computer {
    }
class Acer: Computer {
    }

class DellFactory: AbstractFactory() {
   
    override fun produce() = Dell()
}
class AsusFactory: AbstractFactory() {
   
    override fun produce() = Asus()
}
class AcerFactory: AbstractFactory() {
   
    override fun produce() = Acer()
}

abstract class AbstractFactory {
   

    abstract fun produce(): Computer
    
    companion object {
   
        inline operator fun <reified T : Computer> invoke(): AbstractFactory = when(T::class) {
   
            Dell::class -> DellFactory()
            Asus::class -> AsusFactory()
            Acer::class -> AcerFactory()
            else -> throw IllegalArgumentException()
        }
    }
}

fun main() {
    
    val dellFactory = AbstractFactory<Dell>()
    val dell = dellFactory.produce()
    println(dell)
}

建造者模式(Java)

  • 封装一个产品的构建过程,并允许按步骤构造。
  • 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
public class Robot {
   
    private final String code;
    private final String battery;
    private final Integer height;
    private final Integer weight;

    private Robot(String code, String battery, Integer height, Integer weight) {
   
        this.code = code;
        this.battery = battery;
        this.height = height;
        this.weight = weight;
    }

    public static class Builder {
   
        private final String code;
        private String battery;
        private Integer height;
        private Integer weight;

        public Builder(String code) {
   
            this.code = code;
        }

        public Builder setBattery(String battery) {
   
            this.battery = battery;
            return this;
        }

        public Builder setHeight(int height) {
   
            this.height = height;
            return this;
        }

        public Builder setWeight(int weight) {
   
            this.weight = weight;
            return this;
        }

  

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

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

相关文章

CentOS 7 实战指南:文件操作命令详解

写在前面 想要快速掌握 CentOS 7 系统下的文件操作技巧吗&#xff1f;不用担心&#xff01;我为你准备了一篇详细的技术文章&#xff0c;涵盖了各种常用的文件操作命令。无论您是初学者还是有一定经验的用户&#xff0c;这篇文章都能帮助您加深对 CentOS 7 文件操作的理解&…

【LeetCode每日一题】1154. 一年中的第几天(直接计算+调用库函数)

2023-12-31 文章目录 [1154. 一年中的第几天](https://leetcode.cn/problems/day-of-the-year/)方法一&#xff1a;直接计算思路&#xff1a; 方法二&#xff1a;调用库函数思路 1154. 一年中的第几天 方法一&#xff1a;直接计算 思路&#xff1a; 1.根据所给的字符串&#…

第二节 linux操作系统安装与配置

一&#xff1a;Vmware虚拟机安装与使用   ①VMware是一个虚拟PC的软件&#xff0c;可以在现有的操作系统上虚拟出一个新的硬件环境&#xff0c;相当于模拟出一台新的PC &#xff0c;以此来实现在一台机器上真正同时运行多个独立的操作系统。   ②VMware主要特点&#xff1a…

[LitCTF 2023]Vim yyds

[LitCTF 2023]Vim yyds wp 题目页面如下&#xff1a; 搜索一番&#xff0c;没有发现任何信息。题目描述中说到了源码泄露&#xff0c;那么先进行目录扫描。 dirsearch 目录扫描 命令&#xff1a; dirsearch -u "http://node4.anna.nssctf.cn:28588/"返回结果&…

PTA——计算火车运行时间

本题要求根据火车的出发时间和达到时间&#xff0c;编写程序计算整个旅途所用的时间。 输入格式&#xff1a; 输入在一行中给出2个4位正整数&#xff0c;其间以空格分隔&#xff0c;分别表示火车的出发时间和到达时间。每个时间的格式为2位小时数&#xff08;00-23&#xff0…

最简单的基于 SDL2 的音频播放器

最简单的基于 SDL2 的音频播放器 最简单的基于 SDL2 的音频播放器正文工程文件下载 参考雷霄骅博士的文章&#xff0c;链接&#xff1a;最简单的基于FFMPEGSDL的音频播放器&#xff1a;拆分-解码器和播放器 最简单的基于 SDL2 的音频播放器 正文 SDL2 音频播放器实现了播放 …

抖音详情API:开发环境搭建与工具选择

随着短视频的流行&#xff0c;抖音已经成为了一个备受欢迎的社交媒体平台。对于开发人员而言&#xff0c;利用抖音详情API开发定制化的抖音应用具有巨大的潜力。本文将为你详细介绍开发抖音应用的开发环境搭建与工具选择&#xff0c;帮助你顺利地开始开发工作。 一、开发环境搭…

Github项目推荐-vocal-separate

项目地址 vocal-separate: 项目简述 这是一个音乐和人声分离的项目&#xff0c;基于python开发。有图形化操作界面&#xff0c;看起来还不错。 项目截图

Centos7安装Docker和Docker-Compose

环境 操作系统&#xff1a;Centos 7.9 root环境 Docker安装 卸载原先的Docker环境 如果你先前的操作系统安装了Docker环境&#xff0c;请卸载 Docker 相关的软件包&#xff0c;没有则忽略这一步。 yum remove docker \docker-client \docker-client-latest \docker-common \doc…

计数排序 CountingSort

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序&#xff0c;计数排序要求输入的数据必须是有确定范围的整数。 动画演示 : 案例代码 : (此代码有些瑕疵 , 不能处理负数) public static void main(String[] args) {//int[…

【Web API系列】使用异步剪贴板API(async clipboard)的图像的编程复制和粘贴

文章目录 前言一、将数据写入剪切板1. WriteText()2. Write()3. 监听复制事件 二、从剪切板读取数据1.readText()2.read()3. 处理粘贴的文件4. 监听读剪切板事件 三、申请权限政策集成 四、功能检测五、处理多个 MIME 类型 前言 访问系统剪贴板的传统方法是通过 document.exec…

WAZUH的安装、设置代理

wazuh安装 wazu的安装分为以下两种方式 官方文档&#xff1a;https://wazuh.com/blog/detecting-common-linux-persistence-techniques-with-wazuh/ 1、自定义安装 这种方式就是一步一步的安装 直接参考官方文档&#xff1a; 这里就不详细介绍了&#xff0c;本次主要介绍的…

vue3全网最全教程-----(4)

目录 6. 组件通信 6.1. 【props】 6.2. 【自定义事件】 6.3. 【mitt】 6.4.【v-model】 6.5.【$attrs 】 6.6. 【refs、parent】 6.7. 【provide、inject】 6.8. 【pinia】 6.9. 【slot】 1. 默认插槽 2. 具名插槽 3. 作用域插槽 7. 其它 API 7.1.【shallowRef 与…

Obsidian笔记软件无公网远程同步数据到群辉Webdav

文章目录 1. 群晖开启Webdav服务2. 群晖安装Cpolar3. 配置Webdav远程地址4. Obsidian 安装Remotely Save5. Obsidian远程连接Webdav6. 固定Cpolar公网地址7. PC和移动端笔记同步演示 Obsidian是一款笔记软件&#xff0c;它基于Markdown&#xff0c;支持Windows、macOS、iOS和An…

程序员绩效考核表就是这么简单

大家好&#xff0c;今天给大伙分享一份程序员的绩效考核表&#xff0c;仅供参考&#xff01; 《程序员绩效考核表》

EasyExcel详解(结合官方文档)

EasyExcel 零、前言 文章是根据官方文档&#xff0c;加上自己的测试运行总结出来的&#xff0c;目前只总结的EasyExcel读的部分&#xff0c;写的部分还未完结&#xff0c;后续会更新1、官方文档 https://easyexcel.opensource.alibaba.com/2、EasyExcel的maven依赖 <!--…

Git 常用命令知识笔记

Git 仓库数据结构 Git 仓库由一个个的 commit 组成某些 commit 上会有一些 branch 指向它们&#xff0c;这些 branch 的本质是引用有一个特殊的引用叫做 HEAD&#xff0c;它始终指向当前的位置&#xff0c;这个位置可以是 commit&#xff0c;也可以是 branch staging area 暂存…

Linux:进程退出 与 进程等待wait,waitpid

1.进程退出 1.1 进程退出介绍 进程退出场景 代码运行完毕&#xff0c;结果正确代码运行完毕&#xff0c;结果不正确代码异常终止 进程常见退出方法&#xff1a; 1. 正常终止&#xff08;可以通过 echo $? 查看进程退出码&#xff09;&#xff1a; 从main return调用exit…

为什么广西建筑模板在行业中备受推崇?

广西建筑模板以其卓越的品质和性能&#xff0c;在建筑行业中享有极高的声誉。在众多优秀生产商中&#xff0c;能强优品木业作为广西建筑模板的四大品牌之一&#xff0c;以25年的专业生产经验&#xff0c;成为业界的佼佼者。 高质量原材料 广西的气候和土壤条件非常适合木材生…

dev express 15.2图表绘制性能问题(dotnet绘图表)

dev express 15.2 绘制曲线 前端代码 <dxc:ChartControl Grid.Row"1"><dxc:XYDiagram2D EnableAxisXNavigation"True"><dxc:LineSeries2D x:Name"series" CrosshairLabelPattern"{}{A} : {V:F2}"/></dxc:XYDi…
最新文章