idea插件开发-PSI

news2024/11/26 21:40:24

        程序结构接口(Program Structure Interface)简称PSI,PSI是IDEA插件开发最复杂的一块内容,后续会有大量实战来强化理解此处的知识。PSI是IntelliJ 平台中的一个层,负责解析文件并创建语法和语义代码模型,为平台的众多功能提供支持。它主要包含三方面内容:

  • PSI File
  • File View Provider
  • PSI Element

一、idea platform主要的文件类型

        首先先回顾一下idea插件开发涉及的几个主要的文件类型,如下图:

        这里需要注意的是,idea平台对于可解析的文件有两层抽象,第一层是通用抽象即所谓的UAST语法树,而PSI是针对专门语言的抽象。另外一点就是对于XML文件是有专让的语言解析框架,因为PSI主要针对的是可编程的语言,像xml或json这样的不是可编程的语言是不需用到PSI或UAST的,接下来回到正题,看下PSI的主要内容。

二、什么是PSI?

1、PSI File

        PSI文件表现为带root的层级视图树,是为特定编程语言中元素的层次结构,有点类似JAVA中的AST树的结构。PsiFile.java是所有PSI的基类,特定语言根据需要会扩展出自己的Psi类,比如PsiJavaFile表示一个java文件,XmlFile表示一个xml文件。

        PSI 的Scope是项目范围,与之前提到过另两类文件VF和Document不同(即使打开了多个项目,每个文件都由同一个实例表示),所以基于这一点而言一个VF或Document可能存在多个PSI实例。

获取PSI文件

Context

API

Action

AnActionEvent.getData(CommonDataKeys.PSI_FILE)

Document

PsiDocumentManager.getPsiFile()

PSI Element

PsiElement.getContainingFile() (may return null if the PSI element is not contained in a file)

Virtual File

PsiManager.findFile(), PsiUtilCore.toPsiFiles()

File Name

FilenameIndex.getVirtualFilesByName() and locate via PsiManager.findFile() or PsiUtilCore.toPsiFiles()

操作PSI文件

        大多数修改操作是在单个 PSI 元素的级别上执行的,而不是作为一个整体的文件,比如要遍历文件中的元素:

psiFile.accept(new PsiRecursiveElementWalkingVisitor() {
  // visitor implementation ...
});

创建PSI文件

        PSI依赖具体的语言,所以想创建一个PSI用到的API是Language.java,比如:

LanguageParserDefinitions.INSTANCE
    .forLanguage(MyLanguage.INSTANCE)
    .createFile(fileViewProvider);

        PSI和VF一样都是存在于内存中的,可以被GC。

PsiFileFactory.createFileFromText() //创建具有指定内容的内存中 PSI 文件。
PsiDirectory.add() //将 PSI 文件保存到磁盘

监听PSI文件的改动

        使用PsiManager.addPsiTreeChangeListener()可以接收有关项目PSI树的所有更改的通知。或者注册com.intellij.psi.treeChangeListener扩展点,然后实现PsiTreeChangeListener接口,处理PsiTreeChangeEvent事件。对PSI文件进行的任何修改都会反应到Document上。

2、File View Provider

        现实情况是编程语言是可以混合多种语言混写的,这时就需要用到了文件视图提供程序 ( FileViewProvider) ,它可以对单个文件中多个 PSI 树的访问。例如,JSPX 页面中的 Java 代码有一个单独的 PSI 树 ( PsiJavaFile),XML 代码有一个单独的树 ( XmlFile),整个 JSP 有一个单独的树 ( JspFile)。

        每种语言被包装成单独的PSI树,然后在源文件的入口处饮食了一个"outer language elements"的点位符。每个 PSI 树都覆盖了文件的全部内容,并在可以找到不同语言内容的地方包含特殊的“外部语言元素”。

总结一下:一个FileViewProviderinstance对应一个VirtualFile,一个single Document,包含一或多个PsiFileinstance。

获取FileViewProvider

Context

API

PSI File

PsiFile.getViewProvider()

Virtual File

PsiManager.getInstance(project).findViewProvider()

操作FileViewProvider

  • .getLanguages():获取文件中存在 PSI 树的所有语言的集合
  • .getPsi(language):获取特定语言的 PSI 树,例如,要获取 XML 的 PSI 树,使用fileViewProvider.getPsi(XMLLanguage.INSTANCE)
  • .findElementAt(offset, language):在文件中的指定偏移量处查找特定语言的元素

扩展FileViewProvider

        注册com.intellij.fileType.fileViewProviderFactory扩展点,实现FileViewProviderFactory接口的createFileViewProvider()方法获取FileViewProvider。

<extensions defaultExtensionNs="com.intellij">
  <fileType.fileViewProviderFactory
      filetype="$FILE_TYPE$"   
      implementationClass="com.example.MyFileViewProviderFactory"/>
</extensions>
<!--$FILE_TYPE$是指正在创建的文件的类型(例如,“JSF”)-->

3、PSI Element

        在不同层级上操作PSI元素可以获取源代码的内部结构,例如,您可以使用 PSI 元素执行代码分析、代码检查等功做。所有PSI元素的基类实现都是PsiElement。这里需要注意的是单个的PSI文件也是一个PSI Element。这样最后其实形成的是一棵deep树

获取PSI Element

Context

API

Action

AnActionEvent.getData(CommonDataKeys.PSI_ELEMENT)注意:如果当前打开了一个编辑器并且元素是caret的一个引用,将返回解析引用的结果。

PSI File

PsiFile.findElementAt(offset):返回offset处的叶子节点,通常是一个解析器对象。PsiTreeUtil.getParentOfType():查找元素的类型

Reference

PsiReference.resolve()

二、PSI导航

        导航 PSI 的方式主要有三种:自上而下、自下而上和References。在第一个场景中,您有一个 PSI 文件或另一个更高级别的元素(例如,一个方法)。您需要找到所有符合指定条件的元素(例如,所有变量声明)。在第二种情况下,您在 PSI 树中有一个特定的点(例如,插入符号处的元素),需要找出有关其上下文的信息(例如,声明它的元素)。最后,引用允许您从元素的用法(例如,方法调用)导航到声明(被调用的方法)并返回。

1、自上而下的导航

        执行自上而下导航的最常见方法是使用Visitor。要使用Visitor,需要创建一个类(通常是匿名内部类)来扩展基本Visitor类,覆盖处理您感兴趣的元素的方法,并将Visitor实例传递给PsiElement.accept().

        Visitor的基类是特定于语言的。例如,处理 Java 文件中的元素,可以扩展JavaRecursiveElementVisitor和覆盖感兴趣的 Java 元素类型对应的方法。以下代码片段显示了使用Visitor查找所有 Java 局部变量声明,也可以使用PsiClass.getMethods()这样的快捷方法:

file.accept(new JavaRecursiveElementVisitor() {
  @Override
  public void visitLocalVariable(PsiLocalVariable variable) {
    super.visitLocalVariable(variable);
    System.out.println("Found a variable at offset " +
         variable.getTextRange().getStartOffset());
  }
});

PsiTreeUtil包含许多用于 PSI 树导航的通用、独立于语言的函数,其中一些(例如,findChildrenOfType())执行自上而下的导航。

2、自底向上导航

        自下而上导航的起点是 PSI 树中的特定元素(例如,解析引用的结果)或偏移量。如果你有一个偏移量,可以通过调用PsiFile.findElementAt()找到相应的 PSI 元素,此方法返回树最低级别的元素(例如,标识符),如果要确定更广泛的上下文,则需要向上导航树。

        在大多数情况下,自底向上导航是通过调用PsiTreeUtil.getParentOfType(). 此方法在树中向上移动,直到找到指定类型的元素。例如,要查找包含方法,可以调用PsiTreeUtil.getParentOfType(element, PsiMethod.class).

        在某些情况下,您还可以使用特定的导航方法。例如,要查找包含某个方法的类,可以使用PsiMethod.getContainingClass().以下代码片段显示了如何一起使用这些调用:

PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
PsiElement element = psiFile.findElementAt(offset);
PsiMethod containingMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
PsiClass containingClass = containingMethod.getContainingClass();

PsiNavigationDemoAction.java示例代码

public class PsiNavigationDemoAction extends AnAction {

  @Override
  public void actionPerformed(AnActionEvent anActionEvent) {
    Editor editor = anActionEvent.getData(CommonDataKeys.EDITOR);
    PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
      if (editor == null || psiFile == null) {
          return;
      }
    int offset = editor.getCaretModel().getOffset();

    final StringBuilder infoBuilder = new StringBuilder();
    PsiElement element = psiFile.findElementAt(offset);
    infoBuilder.append("Element at caret: ").append(element).append("\n");
    if (element != null) {
      PsiMethod containingMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
      infoBuilder
              .append("Containing method: ")
              .append(containingMethod != null ? containingMethod.getName() : "none")
              .append("\n");
      if (containingMethod != null) {
        PsiClass containingClass = containingMethod.getContainingClass();
        infoBuilder
                .append("Containing class: ")
                .append(containingClass != null ? containingClass.getName() : "none")
                .append("\n");

        infoBuilder.append("Local variables:\n");
        containingMethod.accept(new JavaRecursiveElementVisitor() {
          @Override
          public void visitLocalVariable(PsiLocalVariable variable) {
            super.visitLocalVariable(variable);
            infoBuilder.append(variable.getName()).append("\n");
          }
        });
      }
    }
    Messages.showMessageDialog(anActionEvent.getProject(), infoBuilder.toString(), "PSI Info", null);
  }

  @Override
  public void update(AnActionEvent e) {
    Editor editor = e.getData(CommonDataKeys.EDITOR);
    PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE);
    e.getPresentation().setEnabled(editor != null && psiFile != null);
  }

}

3、References

        PSI 树中的References是一个对象,表示代码中对特定元素的引用的链接。解析引用意味着找到特定用法所引用的声明。比如:

public void hello(String message) {
    System.out.println(message);
}

       jdk会解析为5个引用:class:String、System;field:out;method: println;parameter:println(message)中的message。引用是PsiReference接口的实例。注意,References和 PSI Element不同。引用是由PsiElement.getReferences()创建的,引用的基础 PSI 元素可以从获得PsiReference.getElement()。

定位

        要解析定位References的声明 ,需要调用PsiReference.resolve()方法。了解PsiReference.getElement()和PsiReference.resolve()之间的区别非常重要。前一种方法返回引用的来源,而后者返回其目标。比如在上面的示例中message References,getElement()返回标识符,resolve()返回标识符。

        解析引用的过程与解析不同,并不总是成功。如果当前在 IDE 中打开的代码没有编译,或者其他情况,PsiReference.resolve()返回null是正常的——所有使用引用的代码都要注意处理。

查询

        执行相反方向的导航——从声明到它的用法,可以使用ReferencesSearch,指定要搜索的元素,以及可选的其他参数,例如需要搜索引用的范围。Query允许一次获取所有结果或一个一个地迭代结果。也可以在找到第一个(匹配)结果后立即停止处理。

处理多个解析引用结果

        最简单的情况下,引用解析为单个元素,如果解析失败,则代码不正确,IDE 需要将其高亮显示为错误。但是,也有情况不同的情况。

        第一种情况是软引用。考虑new File("foo.txt")。如果 IDE 找不到文件“foo.txt”,这并不意味着代码错误 - 也许该文件仅在运行时可用。此类引用需要PsiReference.isSoft()方法返回true,然后可以在检查/注释器中使用以跳过完全突出显示它们或使用较低的严重性。

        第二种情况是多变引用。考虑 JavaScript 程序的情况。JavaScript 是一种动态类型的语言,因此 IDE 不能总是准确地确定在特定位置调用了哪个方法。为了处理这个问题,它提供了一个可以解析为多个可能元素的引用。这些引用实现了PsiPolyVariantReference接口。

        要解析PsiPolyVariantReference引用,可以调用multiResolve()方法,这个方法返回一个ResolveResult对象数组。例如,假设您有多个 Java 方法重载和一个调用,其参数与任何重载都不匹配。在这种情况下,您将取回ResolveResult所有重载的对象,并为所有重载isValidResult()返回。

4、例子

注册plugin.xml

  <actions>
    <action class="org.intellij.sdk.psi.PsiNavigationDemoAction" id="PsiNavigationDemo"
            text="PSI Navigation Demo...">
      <add-to-group group-id="ToolsMenu" anchor="last"/>
    </action>
  </actions>

实现java

public class PsiNavigationDemoAction extends AnAction {

  @Override
  public void actionPerformed(AnActionEvent anActionEvent) {
    Editor editor = anActionEvent.getData(CommonDataKeys.EDITOR);
    PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
      if (editor == null || psiFile == null) {
          return;
      }
    int offset = editor.getCaretModel().getOffset();

    final StringBuilder infoBuilder = new StringBuilder();
    PsiElement element = psiFile.findElementAt(offset);
    infoBuilder.append("Element at caret: ").append(element).append("\n");
    if (element != null) {
      PsiMethod containingMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
      infoBuilder
              .append("Containing method: ")
              .append(containingMethod != null ? containingMethod.getName() : "none")
              .append("\n");
      if (containingMethod != null) {
        PsiClass containingClass = containingMethod.getContainingClass();
        infoBuilder
                .append("Containing class: ")
                .append(containingClass != null ? containingClass.getName() : "none")
                .append("\n");

        infoBuilder.append("Local variables:\n");
        containingMethod.accept(new JavaRecursiveElementVisitor() {
          @Override
          public void visitLocalVariable(PsiLocalVariable variable) {
            super.visitLocalVariable(variable);
            infoBuilder.append(variable.getName()).append("\n");
          }
        });
      }
    }
    Messages.showMessageDialog(anActionEvent.getProject(), infoBuilder.toString(), "PSI Info", null);
  }

  @Override
  public void update(AnActionEvent e) {
    Editor editor = e.getData(CommonDataKeys.EDITOR);
    PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE);
    e.getPresentation().setEnabled(editor != null && psiFile != null);
  }

}

运行测试

三、PSI修改

        PSI(程序结构接口)的最常见操作的方法。是关于使用现有语言(例如 Java)的 PSI。

1、基本操作API

通用操作

  • 如果我知道文件名但不知道路径,如何找到文件?FilenameIndex.getFilesByName()
  • 如何找到使用特定 PSI 元素的位置?ReferencesSearch.search()
  • 如何重命名 PSI 元素?RefactoringFactory.createRename()
  • 如何使虚拟文件的 PSI 重建?FileContentUtil.reparseFiles()

针对java的操作

  • 我如何找到一个类的所有继承者?ClassInheritorsSearch.search()
  • 如何通过限定名称查找课程?JavaPsiFacade.findClass()
  • 如何通过短名称查找班级?PsiShortNamesCache.getClassesByName()
  • 如何找到 Java 类的超类?PsiClass.getSuperClass()
  • 如何获取对 Java 类的包含包的引用?
PsiJavaFile javaFile = (PsiJavaFile)psiClass.getContainingFile(); PsiPackage psiPackage = JavaPsiFacade.getInstance(project) .findPackage(javaFile.getPackageName());

//或者PsiUtil.getPackageName()
  • 如何找到覆盖特定方法的方法?OverridingMethodsSearch.search()
  • 如何检查 JVM 库是否存在?可以使用来JavaLibraryUtil类的方法:hasLibraryClass()通过已知的库类 FQN 检查存在hasLibraryJar()使用 Maven 坐标(例如,io.micronaut:micronaut-core)。

2、操作注意事项

        PSI 是源代码的读/写表示,作为与源文件结构相对应的元素树。您可以通过添加、替换和删除PSI 元素来修改 PSI 。可以使用PsiElement类的PsiElement.add()、PsiElement.delete()和PsiElement.replace()等方法在单个操作中处理多个元素,或者指定树中需要添加元素的确切位置,与文档操作一样,PSI 修改需要包装在一个Action中。

创建新的PSI

        添加到树中或替换现有 PSI 元素的 PSI 元素通常是从文本创建的。在最一般的情况下,PsiFileFactory.createFileFromText()方法一般用于创建一个新文件,其中包含您需要添加到树中的代码构造或用作现有元素的替换、遍历生成的树以找到特定的部分,然后将该元素传递给add()or replace()。大多数语言都提供工厂方法,可以更轻松地创建特定的代码结构。例子:

        PsiJavaParserFacade类包含诸如createMethodFromText()之类的方法,它根据给定的文本创建 Java 方法。SimpleElementFactory.createProperty()创建简单语言属性对于小的代码版本,可以以文本形式编写或从现有文件中获取的代码片段截取,然后传递给createFromText(),对于较大的代码片断,最好要分几步进行:

  • 从文本创建替换树片段,为用户代码片段留下占位符;
  • 用用户代码片段替换占位符;
  • 用替换树替换原始源文件中的元素。

        这可确保保留用户代码的格式,并且修改不会引入任何不需要的空白更改。正如 IntelliJ 平台 API 中的其他地方一样,传递给的文本createFileFromText()和其他createFromText()方法必须仅用作\n行分隔符。可参考ComparingStringReferencesInspection示例:

// binaryExpression holds a PSI expression of the form "x == y", which needs to be replaced with "x.equals(y)"
PsiBinaryExpression binaryExpression = (PsiBinaryExpression) descriptor.getPsiElement();
IElementType opSign = binaryExpression.getOperationTokenType();
PsiExpression lExpr = binaryExpression.getLOperand();
PsiExpression rExpr = binaryExpression.getROperand();

// Step 1: Create a replacement fragment from text, with "a" and "b" as placeholders
PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiMethodCallExpression equalsCall =
    (PsiMethodCallExpression) factory.createExpressionFromText("a.equals(b)", null);

// Step 2: replace "a" and "b" with elements from the original file
equalsCall.getMethodExpression().getQualifierExpression().replace(lExpr);
equalsCall.getArgumentList().getExpressions()[0].replace(rExpr);

// Step 3: replace a larger element in the original file with the replacement tree
PsiExpression result = (PsiExpression) binaryExpression.replace(equalsCall);

保持树结构的一致性

        PSI 修改方法不会限制您构建结果树结构的方式。例如,在使用 Java 类时,您可以将语句添加for为元素的直接子元素,即使 Java 解析器永远不会生成表示方法主体的PsiMethod这种结构(语句for将始终是 的子元素)。PsiCodeBlock产生不正确树结构的修改可能看起来有效,但它们稍后会导致问题和异常。因此,您始终需要确保使用 PSI 修改操作构建的结构与解析器在解析您创建的代码时生成的结构相同。为确保您不会引入不一致,可以使用PsiTestUtil.checkFileStructure()为修改 PSI 的操作调用测试。此方法可确保您构建的结构与解析器生成的结构相同。

空格和导入

        使用 PSI 修改函数时,您永远不应从文本中创建单独的空白节点(空格或换行符)。相反,所有空白修改都由格式化程序执行,它遵循用户选择的代码样式设置。reformat(PsiElement)格式化会在每个命令结束时自动执行,如果需要,您也可以使用类中的方法手动执行CodeStyleManager。

        此外,在使用 Java 代码(或使用具有类似导入机制的其他语言的代码,例如 Groovy 或 Python)时,您永远不应该手动创建导入。相反,您应该将完全限定的名称插入到您正在生成的代码中,然后调用(或您正在使用的语言的等效 API)shortenClassReferences()中的方法。JavaCodeStyleManager这确保导入是根据用户的代码样式设置创建的,并插入到文件的正确位置。

结合PSI 和Document

        在某些情况下,您需要执行 PSI 修改,然后通过 PSI 对刚刚修改的文档执行操作(例如,启动实时模板)。要完成基于 PSI 的后处理(例如格式化)并将更改提交到文档,请调用doPostponedOperationsAndUnblockDocument()实例PsiDocumentManager。

 四、Element Patterns

        元素模式提供了一种通用的方式来给指定的对象设置格式条件,一般用来检查 PSI 元素是否匹配特定结构。类似使用正则表达式测试字符串的与正则表达式是否匹配一样,元素模式用于对 PSI 元素的嵌套结构设置条件。在 IntelliJ 平台有两个application使用了这种技术:

  • 指定在为自定义语言实现完成贡献者时应在何处进行自动完成。
  • 指定通过PSI 参考贡献者提供进一步参考的 PSI 元素。

        在使用时建议使用 IntelliJ 平台提供的高级模式类,而不要直接扩展ElementPattern:

Class

Main Contents

Notable Examples

StandardPatterns

字符串和字符模式的工厂;与、或、非等逻辑运算

LogbackReferenceContributor, RegExpCompletionContributor

PlatformPatterns

PSI、IElement 和 VirtualFile 模式的工厂

FxmlReferencesContributor, PyDataclassCompletionContributor

PsiElementPattern

PSI 模式;检查孩子、父母或邻近的叶子

XmlCompletionContributor

CollectionPattern

过滤和检查模式集合;主要用于为其他高级模式类提供功能

PsiElementPattern

TreeElementPattern

专门用于检查 (PSI) 树结构的模式

PyMetaClassCompletionContributor

StringPattern

检查字符串是否匹配、是否具有特定长度、是否具有特定的开头或结尾,或者是否是字符串集合中的一个

AbstractGradleCompletionContributor

CharPattern

检查字符是否为空格、数字或 Java 标识符部分

CompletionUtil

IntelliJ 平台中的一些内置语言实现了它们自己的模式类,并且可以提供额外的示例:

  • XmlPatterns为 XML 属性、值、实体和文本提供模式。
  • PythonPatterns为 Python 提供文字、字符串、参数和函数/方法参数的模式。
  • DomPatterns构建XmlPatterns并充当包装器,为DOM-API提供更多模式。

1、例子

PsiElementPattern.Capture<PsiElement> AFTER_COMMA_OR_BRACKET_IN_ARRAY =
  psiElement().
  afterLeaf("[", ",").
  withSuperParent(2, JsonArray.class).
  andNot(
    psiElement().
    withParent(JsonStringLiteral.class)
  );

上述代码主要完成以下功能:

  1. 出现在左括号或逗号之后,通过对相邻叶元素施加限制来表示。
  2. 作为JsonArray二级父级,指示 PSI 元素必须位于 JSON 数组内。
  3. 没有 aJsonStringLiteral作为父级,这可以防止数组中带有方括号或逗号的字符串给出误报匹配的情况。

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

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

相关文章

Linux 导入MySQL数据库(四)

文章目录 一、导出数据库二、导入数据库&#xff08;方法一&#xff09;1. 通过FinalShell连接服务器&#xff0c;登录mysql&#xff1a;2. 新建数据库3. 使用新建的数据库4. 对数据库进行编码设置5. 从路径中导入 SQL 文件数据 三、导入数据库&#xff08;方法二&#xff09;【…

为uni-cloud(Dcloud国产之辉)声明!

目录 uni-cloud的介绍 uni-cloud与uni-app的关系 uni-cloud与云原生的关系 uni-cloud的开发优点 uni-cloud与HBuilder X结合的优越性 uni-cloud高效解决"高并发" uni-cloud与阿里云、腾讯云完美结合 uni-cloud背后庞大的插件市场 美中不足 加油&#xff01…

chatgpt赋能python:Python代码保存:如何保存你的Python代码?

Python代码保存&#xff1a;如何保存你的Python代码&#xff1f; Python被广泛认为是学习编程的入门语言之一&#xff0c;因为它易于学习和使用&#xff0c;并且拥有大量的库和框架来处理各种任务。 在编写Python代码时&#xff0c;你可能会像大多数编程任务一样&#xff0c;…

实践项目三: 校园兼职平台(合作重构版)

项目说明 1 据了解&#xff0c;目前在校大学生80%以上有做兼职的需求&#xff0c;兼职打工已经不仅仅是经济困难的学生赚取生活费用的途径。调查显示&#xff0c;全球经济危机对就业产生冲击&#xff0c;用人单位对人员的社会实践能力要求提高&#xff0c;大学期间必要的社会实…

Java学习笔记(StringJoiner和集合)

StringJoiner StringJoiner与StringBuilder一样&#xff0c;也可以看成是一个容器&#xff0c;创建之后的内容是可变的 作用&#xff1a;提高字符串的操作效率&#xff0c;而且代码编写特别简洁&#xff0c;但是目前市场上很少有人用 构造方法&#xff1a; 方法名 说明 pub…

四、HAL_驱动机械按键

1、开发环境。 (1)KeilMDK&#xff1a;V5.38.0.0 (2)STM32CubeMX&#xff1a;V6.8.1 (3)MCU&#xff1a;STM32F407ZGT6 2、机械按键简介 (1)按键内部是机械结构&#xff0c;也就是内部是没有电路的。按键按下内部引脚导通&#xff0c;松开内部断开。 3、实验目的&原理…

Git、Github、Gitee的区别

⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;Git 目录 1、Git2、Gitee3、GitHub 什么是版本管理&#xff1f;   版本管理是管理各个不同的版本&#xff0c;出了问题可以及时回滚。 1、Git Git是一个分布式版本控制系统&#xff0c;用于跟踪和管理代码的变化。它是…

开源代码分享(2)—综合能源系统零碳优化调度

参考文献&#xff1a; Optimal dispatch of zero-carbon-emission micro Energy Internet integrated with non-supplementary fired compressed air energy storage system | SGEPRI Journals & Magazine | IEEE Xplore 1.引言 全球能源危机和环境污染的双重压力促使能量…

mysql 删表引出的问题

背景 将测试环境的表同步到另外一个数据库服务器中&#xff0c;但有些表里面数据巨大&#xff0c;&#xff08;其实不同步该表的数据就行&#xff0c;当时没想太多&#xff09;&#xff0c;几千万的数据&#xff01;&#xff01; 步骤 1. 既然已经把数据同步过来的话&#x…

chatgpt赋能python:Python怎么从1加到100

Python怎么从1加到100 Python是一种面向对象的编程语言&#xff0c;随着人工智能和大数据技术的流行&#xff0c;Python也变得越来越受欢迎。Python有很多优点&#xff0c;其中之一就是易于学习和使用。在这篇文章中&#xff0c;我们将介绍如何用Python从1加到100。 前置知识…

还在为浏览量焦虑吗?为何不用R语言来做归因分析找出痛点

一、引言 大家好&#xff0c;我是一名博客作者&#xff0c;同时也是一个有着浏览量焦虑症的患者。每次发一篇新的博客文章&#xff0c;我总是不停地刷新页面&#xff0c;看看有多少人来访问、阅读和留言。当发现访问量不如自己预期时&#xff0c;我就会有一种被冷落、被忽视的…

【DFT】MBIST (1) MBIST基础

MBSIT基础 1. 存储器测试2. 存储器结构3. 存储器故障模型3.1 固定故障(SAF)3.2 转换故障(TF)3.3 耦合故障(CF)3.4 桥接和状态耦合故障 4. 功能测试方法4.1 March 测试算法4.2 March-C 算法4.3 MATS 算法4.4 其他的 March 测试 5. MBSIT方法5.1 简单的 March MBIST1. 简单的Marc…

灵动超值系列FTHR-G0140开发板

文章目录 引言MM32G0140微控制器FTHR-G0140电路板MM32G0140最小核心系统供电系统可编程按键和小灯扩展插座 MindSDK软件开发平台 引言 2023年上半年的一些活动现场&#xff08;包括但不限于4月在苏州的全国高校电子信息类专业教学论坛、5月和6月在同济大学、四川大学、南京大学…

Vue3+Vite+TypeScript常用项目模块详解

目录 1.Vue3ViteTypeScript 概述 1.1 vue3 1.1.1 Vue3 概述 1.1.2 vue3的现状与发展趋势 1.2 Vite 1.2.1 现实问题 1.2 搭建vite项目 1.3 TypeScript 1.3.1 TypeScript 定义 1.3.2 TypeScript 基本数据类型 1.3.3 TypeScript语法简单介绍 2. 项目配置简单概述 2.…

chatgpt赋能python:如何在Python中二次运行同一个命令语句

如何在Python中二次运行同一个命令语句 如果您是一个熟练的Python开发者&#xff0c;一定会遇到必须二次运行同一个命令语句的情况。在本文中&#xff0c;我们将探讨Python中的几种方法来实现这一目标。 方法1&#xff1a;使用Python Shell Python Shell是Python解释器的一个…

R 语言学习笔记

1. 基础语法 赋值 a 10; b <- 10;# 表示流向&#xff0c;数据流向变量&#xff0c;也可以写成10 -> b创建不规则向量 不用纠结什么是向量&#xff0c;就当作一个容器&#xff0c;数据类型要相同 a c("我","爱","沛")创建一定规则的向…

编译原理 | 课程设计 — 语法分析

第1关&#xff1a;使用C/C语言编写PL/0编译程序的语法分析程序 1、任务描述 基于第二章的词法分析程序&#xff0c;使用C/C语言编写PL/0编译程序的语法分析程序。 2、编程要求 完成上述编程任务&#xff0c;将C/C语言源程序复制粘贴到右侧代码编辑器&#xff0c;点击“评测”按…

bthclsbthclsbthcls

Sql简单查询 创建数据库/表 进入数据库&#xff1a;mysql -uroot -p123456 支持中文字符&#xff1a; Set character_set_databaseutf8; Set character_set_serverutf8; 1.创建数据库 create database demo; use demo; 2.创建数据表 create table score( id int primar…

Day_48堆排序

目录 一. 关于堆排序 1. 堆的定义 二. 堆排序的实现 1. 堆排序的思路 2. 堆排序的问题分析 3. 堆排序的具体实施 4. 效率分析 三. 堆排序的代码实现 1. 堆排序 2. 调整堆&#xff08;核心代码&#xff09; 四. 代码展示 五. 数据测试 六. 总结 一. 关于堆排序 1. 堆的定义…

Shell脚本学习记录

shell教程 第一个shell脚本 打开文本编辑器(可以使用 vi/vim 命令来创建文件)&#xff0c;新建一个文件 test.sh&#xff0c;扩展名为 sh&#xff08;sh代表shell&#xff09;。 #!/bin/bash echo "Hello World !" #! 是一个约定的标记&#xff0c;它告诉系统这个…