【解释器模式】设计模式系列:构建动态语言解释器与复杂表达式处理(深入理解并实现)

news2024/9/21 10:42:50

文章目录

  • 深入理解并实现解释器模式
    • 1. 引言
      • 1.1 解释器模式的定义
      • 1.2 模式的主要优点和缺点
      • 1.3 适用场景
      • 1.4 实际应用案例简介
    • 2. 解释器模式的基本概念
      • 2.1 模式的核心思想
      • 2.2 模式的角色
      • 2.3 模式的动态行为分析
    • 3. 解释器模式的工作原理
      • 3.1 如何构建表达式树
      • 3.2 如何通过递归遍历树来解析表达式
      • 3.3 示例代码分析
    • 4. 实践案例
      • 4.1 案例背景
      • 4.2 案例实现
      • 4.3 测试与验证
    • 5. 性能考量
      • 5.1 性能瓶颈分析
      • 5.2 优化建议
      • 性能测试对比
    • 6. 解释器模式与其他模式的比较
      • 6.1 与策略模式的比较
      • 6.2 与工厂模式的比较
      • 6.3 与组合模式的比较
    • 7. 高级主题
      • 7.1 动态语言解释器实现
        • 7.1.1 使用反射技术
        • 7.1.2 使用注解处理器
      • 7.2 复杂表达式处理
        • 7.2.1 处理优先级
        • 7.2.2 处理嵌套表达式
    • 8. 总结与展望


深入理解并实现解释器模式


1. 引言

1.1 解释器模式的定义

解释器模式是一种行为型设计模式,它允许您定义一个语言的文法,并创建一个解析器来解释该语言中的句子。这种模式使得我们可以轻松地处理诸如配置文件、简单的查询语言、脚本语言等小型领域特定语言(DSL)。

1.2 模式的主要优点和缺点

1.优点

  • 易于扩展语言: 当需要添加新的语法规则时,可以通过增加新的类来实现,而不需要修改现有代码。
  • 易于改变解释规则: 可以通过改变解释器类的行为来改变解释规则。
  • 高度封装: 解释器模式将解释逻辑封装在各个类中,使得整个系统更加清晰。

2. 缺点

  • 效率问题: 解释器模式可能会导致大量的类产生,尤其是在语言较为复杂的情况下,这可能导致运行时性能下降。
  • 难以调试: 由于解释器模式通常涉及到复杂的递归结构,因此当出现错误时,定位问题可能比较困难。
  • 不适用于频繁变化的语言: 如果语言经常变化,则频繁地添加和修改类将会变得繁琐。

1.3 适用场景

解释器模式适用于以下情况:

  • 当一个应用的一部分功能的实现算法与其使用环境经常改变,可将其定义为一个简单语言,并用解释器模式去解释。
  • 当存在一个需要解释的领域语言时。
  • 当有一个简单文法需要解释执行,且你愿意将该文法表示为一个类层次时。

1.4 实际应用案例简介

假设我们需要开发一个简单的查询语言,用于从数据库中检索数据。这个查询语言可以包含基本的操作如 AND, OR, NOT 以及一些关键字匹配。通过使用解释器模式,我们可以定义一个简单的文法来处理这些查询,并能够轻松地扩展语言以支持更多的操作。


2. 解释器模式的基本概念

2.1 模式的核心思想

解释器模式的核心思想是定义一个语言的文法,并建立一个解释器来解释该语言中的句子。这里的“句子”是指符合文法的一串字符序列。解释器模式的关键在于使用类来表示文法中的规则。

1. 抽象语法树
解释器模式中的表达式通常以抽象语法树的形式组织。这个树的每个节点都是一个表达式,可以是终结符表达式或非终结符表达式。终结符表达式通常代表文法中的最小单位,而非终结符表达式则代表由多个表达式组成的复合表达式。

2. 表达式的解析与执行
解释器模式的核心在于如何解析和执行这些表达式。通常,解析过程涉及构建一个表达式的树形结构,而执行过程则是通过递归地访问树中的各个节点来进行的。

2.2 模式的角色

1. AbstractExpression (抽象表达式)

  • 定义了一个接口,规定了所有表达式共有的方法,比如 interpret(Context context)。这是解释器模式的核心接口。
  • 是解释器模式的核心接口,所有的表达式类都必须实现这个接口。

2. TerminalExpression (终结符表达式)

  • 实现了AbstractExpression接口,负责解释文法中的终结符号。终结符表达式通常对应于文法中的叶子节点。
  • 终结符表达式通常对应于文法中的叶子节点。

3. NonterminalExpression (非终结符表达式)

  • 同样实现了AbstractExpression接口,负责解释文法中的非终结符号。
  • 非终结符表达式通常对应于文法中的内部节点,它们可以包含其他表达式。

4. Context (上下文)

  • 包含了解释器所需的外部信息,供解释器使用。
  • 通常,上下文中存储着解释器解释表达式所需要的数据。

5. Client (客户端)

  • 使用解释器模式的客户端负责构建表达式树,并调用解释器来解释这个树。

在这里插入图片描述

2.3 模式的动态行为分析

解释器模式的动态行为主要包括构建表达式树和遍历解释树两个阶段。构建表达式树通常是通过客户端进行的,而遍历解释树则是在解释器类中完成的。

  • 构建表达式树:客户端根据需要解释的文本构建出表达式树,通常是由一系列的非终结符表达式和终结符表达式组成。
  • 遍历解释树:解释器类通过递归地访问表达式树的各个节点,来解析并执行表达式。

3. 解释器模式的工作原理

3.1 如何构建表达式树

构建表达式树的过程通常是基于给定的文法进行的。首先,需要定义文法来描述要解析的语言。然后,根据输入字符串,按照文法的规则,构建相应的表达式对象。这些对象构成了表达式树。

3.2 如何通过递归遍历树来解析表达式

解析表达式树的过程通常是递归的。从根节点开始,递归地访问每个子节点,直到访问到叶子节点。在访问每个节点时,调用 interpret() 方法来处理该节点。这个方法通常包含了具体的解析逻辑。

3.3 示例代码分析

以下是一个简单的解释器模式的Java代码示例,用于解析一个简单的布尔表达式。

// Abstract Expression
interface Expression {
    boolean interpret(String context);
}

// Terminal Expressions
class TerminalExpression implements Expression {
    private String data;

    public TerminalExpression(String data) {
        this.data = data;
    }

    @Override
    public boolean interpret(String context) {
        // Implement the logic for terminal expressions
        return context.contains(data);
    }
}

// Non-Terminal Expressions
class OrExpression implements Expression {
    private Expression expr1;
    private Expression expr2;

    public OrExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }

    @Override
    public boolean interpret(String context) {
        return expr1.interpret(context) || expr2.interpret(context);
    }
}

// Client
public class InterpreterClient {
    public static void main(String[] args) {
        Expression andExpr = new AndExpression(
            new TerminalExpression("John"),
            new TerminalExpression("Doe")
        );
        Expression orExpr = new OrExpression(
            new TerminalExpression("Jane"),
            new TerminalExpression("Doe")
        );

        System.out.println(andExpr.interpret("John Doe"));
        System.out.println(orExpr.interpret("Jane Doe"));
    }
}

在这个示例中,我们定义了一个简单的文法来处理名字是否包含 “John” 和 “Doe” 的布尔表达式。AndExpressionOrExpression 分别实现了 &&|| 的逻辑。


4. 实践案例

4.1 案例背景

1. 需求分析
假设我们正在开发一个简单的搜索引擎,需要处理用户输入的查询字符串。查询字符串可以包含关键词、逻辑运算符(如 AND、OR、NOT)以及括号来改变优先级。

2. 设计决策
为了处理这些查询,我们决定使用解释器模式来定义一个简单的查询语言,并实现一个解析器来解析这些查询。

4.2 案例实现

1. 类的设计与定义
我们需要定义以下类:

  • AbstractExpression: 接口,定义了解析逻辑。
  • TerminalExpression: 实现了关键词匹配。
  • NonterminalExpression: 实现了逻辑运算符的处理。
  • Context: 存储查询字符串。
  • Client: 构建表达式树并调用解析方法。

2. Java代码示例
下面是一个简单的实现示例:

// Abstract Expression
interface Expression {
    boolean interpret(String context);
}

// Terminal Expressions
class KeywordExpression implements Expression {
    private String keyword;

    public KeywordExpression(String keyword) {
        this.keyword = keyword;
    }

    @Override
    public boolean interpret(String context) {
        return context.contains(keyword);
    }
}

// Non-Terminal Expressions
class AndExpression implements Expression {
    private Expression expr1;
    private Expression expr2;

    public AndExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }

    @Override
    public boolean interpret(String context) {
        return expr1.interpret(context) && expr2.interpret(context);
    }
}

// Client
public class SearchEngineClient {
    public static void main(String[] args) {
        Expression searchQuery = new AndExpression(
            new KeywordExpression("Java"),
            new KeywordExpression("programming")
        );

        System.out.println(searchQuery.interpret("Java programming tutorials"));
    }
}

4.3 测试与验证

测试案例

public class SearchEngineTest {
    @Test
    public void testSimpleSearch() {
        Expression searchQuery = new KeywordExpression("Java");
        assertTrue(searchQuery.interpret("Java programming tutorials"));
    }

    @Test
    public void testComplexSearch() {
        Expression searchQuery = new AndExpression(
            new KeywordExpression("Java"),
            new KeywordExpression("programming")
        );
        assertTrue(searchQuery.interpret("Java programming tutorials"));
        assertFalse(searchQuery.interpret("Python programming tutorials"));
    }
}

5. 性能考量

5.1 性能瓶颈分析

解释器模式的主要性能瓶颈来自于大量的类实例化以及递归调用。特别是在处理复杂的文法时,可能会创建大量的表达式类实例,这会导致内存消耗增加和运行速度减慢。

  • 类实例化: 对于每种不同的表达式类型,都需要创建相应的类实例。如果表达式树很大或者文法很复杂,这可能导致大量的类实例化。
  • 递归调用: 在解析表达式树时,通常采用递归的方式进行访问。递归调用可能会导致栈溢出或其他性能问题,尤其是在表达式树很深的情况下。

5.2 优化建议

为了提高解释器模式的性能,可以采取以下几种策略:

  1. 缓存表达式: 对于重复出现的表达式,可以考虑使用缓存机制来减少类的实例化次数。例如,可以使用哈希表来存储已经创建过的表达式实例。
  2. 避免不必要的递归: 尽量减少递归深度,可以通过优化文法或者使用迭代的方式来替代递归。
  3. 使用静态方法: 如果表达式的计算逻辑比较简单,可以考虑将 interpret() 方法改为静态方法,从而避免每次调用时都要创建一个新的对象。
  4. 预编译表达式: 对于固定的表达式,可以在程序启动时就将其编译成中间代码或者直接的执行代码,这样在运行时可以直接执行,无需重新解析。
  5. 使用代理模式: 在某些情况下,可以使用代理模式来包装解释器类,代理类可以预先执行一些初始化操作,减少运行时的开销。

性能测试对比

为了评估解释器模式的性能,可以进行基准测试来比较不同实现方式的性能差异。例如,可以比较以下几种情况:

  • 未优化的解释器: 不使用任何优化措施的标准解释器模式实现。
  • 使用缓存的解释器: 在解释器中加入缓存机制。
  • 预编译的解释器: 将常见的表达式预编译成中间代码或执行代码。

测试可以用相同的输入数据集来执行,记录并比较每个版本的执行时间和内存使用情况。


6. 解释器模式与其他模式的比较

6.1 与策略模式的比较

相同点:

  • 两者都可以用来封装算法或行为,并且可以根据需要在运行时选择不同的算法或行为。
  • 两者都遵循面向对象的设计原则,使得代码更加灵活和可维护。

不同点:

  • 解释器模式主要用于解析和执行简单的语言或文法,而策略模式主要用于封装一组可互换的算法。
  • 解释器模式侧重于定义文法和解析表达式,而策略模式侧重于定义算法族并使它们可以相互替换。

6.2 与工厂模式的比较

相同点:

  • 两种模式都可以用于创建对象。
  • 它们都遵循面向对象的原则,提高了代码的灵活性和可扩展性。

不同点:

  • 解释器模式关注的是如何解析和执行一个语言的句子,而工厂模式关注的是如何创建对象而不暴露创建逻辑。
  • 工厂模式提供了一种创建对象的统一接口,而解释器模式提供了一种定义语言文法并解释这些句子的方法。

6.3 与组合模式的比较

相同点:

  • 两种模式都可以用来构建树形结构。
  • 它们都可以用来处理递归结构的问题。

不同点:

  • 解释器模式用于解析语言的文法,而组合模式用于构建和操作树形结构的组件。
  • 组合模式关注于如何让单个对象和组合对象具有一致的行为,而解释器模式关注于如何解析和执行文法中的句子。

7. 高级主题

7.1 动态语言解释器实现

7.1.1 使用反射技术

反射技术可以用来动态地创建对象和调用方法,这对于构建动态语言解释器非常有用。通过反射,我们可以根据输入的字符串动态地创建表达式类的对象,并调用它们的方法。

示例代码:

public class DynamicInterpreter {
    public static Expression createExpression(String className, String... params) throws Exception {
        Class<?> clazz = Class.forName(className);
        Constructor<?> constructor = clazz.getConstructor(String[].class);
        return (Expression) constructor.newInstance(new Object[]{params});
    }

    public static void main(String[] args) throws Exception {
        Expression expr = createExpression("com.example.AndExpression", new String[]{"com.example.KeywordExpression", "Java"}, new String[]{"com.example.KeywordExpression", "programming"});
        String context = "Java programming tutorials";
        System.out.println(expr.interpret(context));
    }
}
7.1.2 使用注解处理器

注解处理器可以在编译期间生成代码,这对于创建解释器非常有用,因为它可以自动地生成表达式类,从而简化代码。

示例代码:

@Retention(RetentionPolicy.RUNTIME)
@interface Expression {
    String value();
}

@Expression(value = "com.example.KeywordExpression")
public class JavaKeywordExpression extends KeywordExpression {
    public JavaKeywordExpression() {
        super("Java");
    }
}

@Expression(value = "com.example.KeywordExpression")
public class ProgrammingKeywordExpression extends KeywordExpression {
    public ProgrammingKeywordExpression() {
        super("programming");
    }
}

public class AnnotationProcessorInterpreter {
    public static Expression createExpression(String annotationValue) throws Exception {
        Class<?> clazz = Class.forName(annotationValue);
        Expression annotation = clazz.getAnnotation(Expression.class);
        if (annotation != null) {
            return (Expression) clazz.getDeclaredConstructor().newInstance();
        }
        throw new IllegalArgumentException("No expression found with annotation: " + annotationValue);
    }

    public static void main(String[] args) throws Exception {
        Expression expr = new AndExpression(createExpression("com.example.JavaKeywordExpression"), createExpression("com.example.ProgrammingKeywordExpression"));
        String context = "Java programming tutorials";
        System.out.println(expr.interpret(context));
    }
}

7.2 复杂表达式处理

7.2.1 处理优先级

处理优先级时,通常需要考虑括号和其他运算符的优先级。可以通过调整表达式树的构建方式来处理优先级问题,确保优先级高的运算符先被处理。

示例代码:

class OrExpression extends NonterminalExpression {
    private Expression expr1;
    private Expression expr2;

    public OrExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }

    @Override
    public boolean interpret(String context) {
        return expr1.interpret(context) || expr2.interpret(context);
    }
}

class AndExpression extends NonterminalExpression {
    private Expression expr1;
    private Expression expr2;

    public AndExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }

    @Override
    public boolean interpret(String context) {
        return expr1.interpret(context) && expr2.interpret(context);
    }
}

public class ComplexExpressionInterpreter {
    public static Expression parse(String input) {
        // 假设我们已经有了一个解析器来处理优先级
        // 这里仅做演示
        String[] tokens = input.split(" ");
        Stack<Expression> stack = new Stack<>();
        for (String token : tokens) {
            switch (token) {
                case "AND":
                    Expression expr2 = stack.pop();
                    Expression expr1 = stack.pop();
                    stack.push(new AndExpression(expr1, expr2));
                    break;
                case "OR":
                    expr2 = stack.pop();
                    expr1 = stack.pop();
                    stack.push(new OrExpression(expr1, expr2));
                    break;
                default:
                    stack.push(new KeywordExpression(token));
            }
        }
        return stack.pop();
    }

    public static void main(String[] args) {
        Expression expr = parse("Java AND programming OR tutorials");
        String context = "Java programming tutorials";
        System.out.println(expr.interpret(context));
    }
}
7.2.2 处理嵌套表达式

处理嵌套表达式时,需要考虑如何正确地构建表达式树,确保括号内的表达式作为一个整体被处理。

示例代码:

public class NestedExpressionInterpreter {
    public static Expression parse(String input) {
        // 假设我们已经有了一个解析器来处理嵌套表达式
        // 这里仅做演示
        String[] tokens = input.split(" ");
        Stack<Expression> stack = new Stack<>();
        for (String token : tokens) {
            switch (token) {
                case "(":
                    // 开始一个新的嵌套表达式
                    stack.push(null);
                    break;
                case ")":
                    // 结束一个嵌套表达式
                    List<Expression> nestedExprs = new ArrayList<>();
                    while (stack.peek() != null) {
                        nestedExprs.add(stack.pop());
                    }
                    stack.pop(); // 移除 '('
                    Expression nestedExpr = new AndExpression(nestedExprs.get(0), nestedExprs.get(1)); // 假设只有两个嵌套表达式
                    stack.push(nestedExpr);
                    break;
                case "AND":
                case "OR":
                    Expression expr2 = stack.pop();
                    Expression expr1 = stack.pop();
                    stack.push(new AndExpression(expr1, expr2));
                    break;
                default:
                    stack.push(new KeywordExpression(token));
            }
        }
        return stack.pop();
    }

    public static void main(String[] args) {
        Expression expr = parse("(Java AND programming) OR tutorials");
        String context = "Java programming tutorials";
        System.out.println(expr.interpret(context));
    }
}

8. 总结与展望

1. 解释器模式的应用前景
随着领域特定语言(DSLs)的流行,解释器模式在各种应用中变得越来越重要。它可以用于处理各种类型的查询语言、配置文件解析、小型编程语言等。

2. 未来可能的发展方向

  • 更高效的实现: 随着技术的进步,可能会出现更高效的方式来实现解释器模式,例如使用字节码生成技术来减少运行时开销。
  • 自动化工具: 自动化工具可以帮助开发者更容易地生成解释器,减少手动编码的工作量。
  • 结合机器学习: 在某些领域,解释器模式可以与机器学习技术相结合,用于动态生成和优化解释器。

本文详细介绍了23种设计模式的基础知识,帮助读者快速掌握设计模式的核心概念,并找到适合实际应用的具体模式:
【设计模式入门】设计模式全解析:23种经典模式介绍与评级指南(设计师必备)

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

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

相关文章

可达鸭举牌网页版本在线生成源码html5

源码介绍 可达鸭举牌网页版本&#xff0c;在线生成源码&#xff0c;点击分享即可制作DIY自己的举牌文字网页&#xff0c;需要GIF动图的自行用GIF图片录制工具录制下来。 PS:上传到服务器运行或者本地nginx运行&#xff0c;不要双击index.html&#xff0c;如果本地双击HTML&…

【3】AT32F437 OpenHarmony轻量系统第一个程序:点灯

在搭建好AT32F437 OpenHarmony 轻量系统之后&#xff0c;当然要尝试点一下灯了。 编写点灯程序 笔者在适配OpenHarmony轻量系统的时候&#xff0c;只对源码的device和vendor目录进行了修改&#xff0c;AT32的app目录笔者放置在了vendor/tree/master/artery/AT-START-F437/app…

什么是网络安全?网络安全防范技术包括哪些?

一、引言 在当今数字化的时代&#xff0c;网络已经成为人们生活和工作中不可或缺的一部分。然而&#xff0c;随着网络的普及和应用的广泛&#xff0c;网络安全问题也日益凸显。从个人隐私泄露到企业关键信息被盗&#xff0c;从网络欺诈到大规模的网络攻击&#xff0c;网络安全…

在国产芯片上实现YOLOv5/v8图像AI识别-【2.5】yolov8使用C++部署在RK3588更多内容见视频

本专栏主要是提供一种国产化图像识别的解决方案&#xff0c;专栏中实现了YOLOv5/v8在国产化芯片上的使用部署&#xff0c;并可以实现网页端实时查看。根据自己的具体需求可以直接产品化部署使用。 B站配套视频&#xff1a;https://www.bilibili.com/video/BV1or421T74f 背景…

ubuntu 安装两个nginx实例时的坑,非默认nginx实例配置修改总也不生效的问题

一、问题 由于工作需求xx云服务器上安装了两个nginx实例&#xff0c;突然有一天需要在非默认nginx上增加一个子站点&#xff0c;根据网上教程和原来的记录修改vi nginx.conf 后保存载总也不生效&#xff1f; 怎么破&#xff1f; 二、过程记录 假如&#xff1a;非默认nginx安装在…

HanLP分词的使用与注意事项

1 概述 HanLP是一个自然语言处理工具包&#xff0c;它提供的主要功能如下&#xff1a; 分词转化为拼音繁转简、简转繁提取关键词提取短语提取词语自动摘要依存文法分析 下面将介绍其分词功能的使用。 2 依赖 下面是依赖的jar包。 <dependency><groupId>com.ha…

使用SSH协议远程连接Ubuntu

1.切换到root用户 sudo -i 2.安装openssh-server apt update apt install openssh-server 3.启动ssh服务 service ssh start 4.查看ssh状态 &#xff08;q键: 退出&#xff09; service ssh status 5.检查ssh服务是否启动成功 ps -e | grep ssh 6.开机自启动 systemctl enable …

基于STM32F103的FreeRTOS系列(九)·任务创建函数的使用·静态方法和动态方法

目录 1. 前期准备 1.1 中断文件修改 1.2 SysTick文件修改 1.3 任务创建函数API 2. 任务创建&#xff08;静态方法&#xff09; 2.1 创建两个任务函数 2.2 静态创建开始任务函数 2.3 创建开始任务的任务函数 2.4 补充 2.5 代码 3. 任务创建&#xff08;动…

【python基础】—利用pandas读取或写入mysql表数据

文章目录 一、read_sql()二、to_sql()三、连接数据库方式—MySQL1、用sqlalchemy包构建数据库链接2、用DBAPI构建数据库链接 四、容易遇到的问题 一、read_sql() 功能 将 SQL 查询/数据库表读入 DataFrame。 语法 读取数据库&#xff08;通过SQL语句或表名&#xff09; pand…

【书生大模型实战营(暑假场)闯关材料】基础岛:第4关 InternLM + LlamaIndex RAG 实践

基础任务 (完成此任务即完成闯关) 任务要求&#xff1a; 基于 LlamaIndex 构建自己的 RAG 知识库&#xff0c;寻找一个问题 A 在使用 LlamaIndex之前InternLM2-Chat-1.8B模型不会回答&#xff0c;借助 LlamaIndex 后 InternLM2-Chat-1.8B模型具备回答 A 的能力&#xff0c;截图…

SQL-约束篇

在数据库设计中&#xff0c;约束是确保数据完整性和准确性的关键元素。约束可以限制表中数据的类型、范围和关系&#xff0c;从而维护数据的一致性和可靠性。 1. 主键约束 (Primary Key) 主键约束用于唯一标识表中的每一行数据。一个表只能有一个主键&#xff0c;主键字段的值…

计算机毕业设计SpringBoot-VUE-python-nodeJS铁路列车安全管理-评估报告-铁路局-客运-货运-行车-站段-天气情况

1 引言 1.1 项目开发的背景 我国的普铁历史悠久&#xff0c;从19世纪至今已有百来年的历史。新中国以来&#xff0c;铁路的发展速度令人惊叹。但随着发展速度的增快&#xff0c;与之不对应的则是运营安全管理系统的落后。但这些来&#xff0c;我国铁路对于安全的重视程度已经…

代码随想录算法训练营第十六天(二叉树 四)

力扣题部分: 513.找树左下角的值 题目链接:. - 力扣&#xff08;LeetCode&#xff09; 题面: 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 思路(层序遍历): 应该是这道题最简单的方法了&#xff0…

超文本文档HTML

简单的个人网站 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>欢迎来到我的主页</title> &…

【HarmonyOS】鸿蒙应用蓝牙功能实现 (二)

【HarmonyOS】鸿蒙应用蓝牙功能实现 &#xff08;二&#xff09; 前言 蓝牙一般分为传统蓝牙(BR/EDR)&#xff0c;低功耗蓝牙(BLE)两种。 鸿蒙将蓝牙的功能模块分的非常细。 基本上我们会用到access进行蓝牙状态的开启和关闭&#xff0c;以及状态查询。 在使用connection进…

BUUCTF PWN wp--warmup_csaw_2016

第一步 先checksec一下&#xff08;没有启用NX保护、PIE、完整的RELRO和栈保护&#xff0c;还有具有RWX权限的内存段。&#xff09; 分析一下这个文件的保护机制&#xff1a; Arch: amd64-64-little 这表示该可执行文件是为64位的AMD64架构编译的&#xff0c;并且使用的是小…

科大讯飞飞凡计划面经(2024年秋招新出炉)

7月笔完一个多月约的面试 —————————————- 一面8.16 不问项目&#xff0c;上来就是八股轰炸 c11新特性 shared_ptr是线程安全的吗&#xff0c;不安全的话怎么实现线程安全的 stl容器中频繁查找用什么&#xff0c;频繁增删用什么 vector中间插入元素会发生什么&…

NVIDIA Isaac Lab 入门教程(一)

系列文章目录 前言 Isaac Lab 是一个用于机器人学习的统一模块化框架&#xff0c;旨在简化机器人研究中的常见工作流程&#xff08;如 RL、从演示中学习和运动规划&#xff09;。它建立在英伟达 Isaac Sim 的基础上&#xff0c;利用最新的仿真功能实现逼真的场景和快速高效的仿…

74、docker容器编译安装lnmp

一、docker部署lnmp l linux n nginx 1.22 172.111.0.10 docker–nginx m mysql 8.0.30 172.111.0.20 docker–mysql p php 8.1.27 172.111.0.30 docker–php docker&#xff1a;单节点部署&#xff0c;在一台机器上部署&#xff0c;跨了机器容器无法通信&#xff0c;做高…

git 如何生成sshkey公钥

打开git客户端 输入 ssh-keygen -t rsa -b 4096 -C "xxxxxxexample.com" 然后根据提示按enter 或者y 直到出现下图所示 打开 c盘的路径下的文件&#xff0c;/c/Users/18159/.ssh/id_rsa.pub 将id_rsa.pub中的公钥贴到git 网站上的SSH keys即可