0%

目的

封装算法,使之可以独立于客户变化

示例

文本分行有多种算法

  • 若将程序类包含多种算法的代码,则使程序庞杂难维护
  • 并非任何时候都需要所有的算法
  • 若将算法嵌入客户程序内部,则变更算法将十分困难

  • Composition 维护对 Compositor 对象的一个引用。一旦 Composition 重新格式化它的文本,它就将这个职责转发给它的 Compositor 对象
  • Compositor 的子类实现具体的分行算法

适用性

  • 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
  • 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。
  • 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的 Strategy 类中以代替这些条件语句。

结构

  • Strategy:算法的公共接口,Context 调用该接口
  • ConcreteStrategy:实现具体的策略
  • Context:用一个 ConcreteStrategy 对象来配置,维护一个 Strategy 实例,提供接口让 Strategy 访问其数据

Strategy 和 Context 相互作用以实现选定的算法。

  • 客户仅与 Context 交互,通常创建并传递一个 Concrete-Strategy 对象给该 Context
  • Context 将客户的请求转发给它的 Strategy。
  • 当算法被调用时, Context 可以将该算法所需要的所有数据都传递给该 Strategy。
    • 或者 Context 可以将自身作为一个参数传递给 Strategy 操作,以便 Strategy 在适当时候回调。

效果

  • 降低 Context 的复杂度:将算法和 Context 解耦合,易于切换算法
  • 有利于复用算法
  • 允许客户根据具体的时间/空间需要选择具体算法,但相应地,这意味着需要客户了解不同的 Strategy 之间的差异
  • 消除了大量的条件判断:Context 内嵌多种算法/行为时,通常需要大量条件语句来选择合适的行为 缺陷
  • 冗余参数:不同的 ConcreteStrategy 需要不同的参数集,因此抽象父类定义的接口必须提供这些参数集的并集。
    • 这就意味着有时 Context 会创建和初始化一些永远不会用到的参数
    • 如果存在这样的问题,那么将需要在 Strategy 和 Context 之间进行更紧密的耦合。
  • 对象的数目:Strategy 增加了一个应用中的对象的数目。有时你可以将 Strategy 实现为可供各 Context 共享的无状态的对象来减少这一开销

实现

关于 Strategy 和 Context 的接口

这些接口必须让 ConcreteStrategy 能够有效地访问它所需要的 Context 中的任何数据,实现上有两种方法:

目的

将请求封装为对象,从而可用不同的请求:

  • 对客户行为参数化
  • 对请求排队或记录日志
  • 支持可撤销的操作

示例

有时必须向某对象提交请求,但并不知道关于被请求的操作或请求的接收者的任何信息。

例如,用户界面工具箱包括按钮和菜单这样的对象,它们执行请求响应用户输入。但工具箱不能显式地在按钮或菜单中实现该请求,因为只有使用工具箱的应用知道该由哪个对象做哪个操作。

命令模式通过将请求本身变成一个可存储对象来使工具箱对象可向未指定的应用对象提出请求。

  • 关键是一个抽象的 Command 类,它定义了一个执行操作的接口。
  • Command 子类将接收者作为它的一个实例变量,并实现 Execute 操作

例如,PasteCommand 支持从剪贴板向一个文档(document)粘贴正文。 PasteCommand 的接收者是一个文档对象,该对象是实例化时提供的。Execute 操作将调用该 Document 的 Paste 操作。

MacroCommand:有时一个操作需要多个子操作完成,因此可定义 MacroCommand 执行一个命令序列

  • MacroCommand 没有明确的接收者,而序列中的命令各自定义其接收者。

优势:灵活。因为提交一个请求的对象仅需要知道如何提交它,而不需要知道该请求将会被如何执行

  • 一个应用如果想让一个菜单与一个按钮代表同一项功能,只需让它们共享相应具体 Command 子类的同一个实例即可
  • 可以动态地替换 Command 对象,这可用于实现上下文有关的菜单
  • 可通过将几个命令组成更大的命令的形式来支持命令脚本

适用性

  • 抽象出待执行的动作以参数化某对象(Command 模式是回调机制的一个面向对象的替代品)
  • 在不同的时刻指定、排列和执行请求。一个 Command 对象可以有一个与初始请求无关的生存期。
  • 支持取消操作:Command 的 Excute 操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。
  • 支持修改日志:当系统崩溃后,可从磁盘中重新读入记录下来的命令并用 Execute 操作重新执行它们。(Command 接口中需添加装载操作和存储操作)
  • 用构建在原语操作上的高层操作构造一个系统:在支持事务(transaction)的信息系统中,一个事务封装了对数据的一组变动。
    • Command 模式提供了对事务进行建模的方法。

结构

  • Command:操作的接口
  • ConcreteCommand:将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现 Execute
  • Invoker(示例中的MenuItem):要求该命令执行这个请求
  • Receiver(示例中的 Document):知道如何实施与执行一个请求相关的操作

工作流程

用途

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

示例

将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一致性

视图 View 依赖数据 Data 一个目标 Subject/publish 可以有任意数目的依赖它的观察者 Observer/subscribe。一旦目标的状态发生改变,所有的观察者都得到通知。 收到通知后,每个观察者都将查询目标以使其状态与目标的状态同步。

适用性

  • 一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中,以使它们可以各自独立地改变和复用。
  • 对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。
  • 一个对象必须通知其他对象,而它又不能假定其他对象是谁。换言之,你不希望这些对象是紧密耦合的。

结构

  • Subject(目标)
    • 目标知道它的观察者。可以有任意多个观察者观察同一个目标。
    • 状态改变时,通知自身的观察者
    • 提供注册和删除观察者对象的接口。
  • ConcreteSubject(具体目标):
    • 为观察者提供获取/设置状态的接口
  • Observer(观察者):为那些在目标发生改变时需要获得通知的对象定义一个更新接口。
  • ConcreteObserver:
    • 维护一个指向 ConcreteSubject 对象的引用
    • 存储状态,这些状态与 ConcreteSubject 的状态保持一致
    • 实现 Observer 的更新接口,以使自身状态与目标的状态保持一致。

交互时序

  • 当 ConcreteSubject 发生任何可能导致其观察者与其本身状态不一致的改变时,它将通知它的各个观察者。
  • 在得到一个具体目标的改变通知后,ConcreteObserver 对象可向目标对象查询信息。ConcreteObserver 使用这些信息使它的状态与目标对象的状态一致。

注意

  • 观察者 setState 后不会立即更新自身状态,而是等到被 notify 后再更新
  • Notify 不总是由目标对象调用,它也可被一个观察者或其他对象调用。

效果

Publisher 和 Subscriber 彼此独立,允许增加新的观察者而无需对现有的 Publisher/Subscriber 做任何修改

  • 低耦合:一个目标所知道的仅仅是它有一系列观察者,每个都符合抽象的 Observer 类的简单接口,而不知道其具体类型
    • 一个处于较低层次的目标对象可与一个处于较高层次的观察者通信并通知它,反之则不行:
      • 如果目标和观察者混在一块,那么得到的对象要么横贯两个层次(违反了层次性),要么必须放在这两层的某一层中(这可能会损害层次抽象)。
  • 支持广播通信
  • 意外的更新:
    • 在目标上一个看似无害的的操作可能会引起一系列对观察者以及依赖于这些观察者的对象的更新
    • 如果依赖准则的定义或维护不当,常常会引起错误的更新,这种错误通常很难捕捉。

实现

创建目标到其观察者之间的映射

动态规划问题

这类问题的求解思路是:

  • 先尝试有 Cache 的暴力搜索,列出递推式
  • 将上述搜索转化为使用 dp 数组的方法
  • 尝试压缩 dp 数组维度

70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

\begin{cases} f(n)=f(n-2)+f(n-1)\\ f(<=0)=0,f(1)=1,f(2)=2 \end{cases}

746. 使用最小花费爬楼梯

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 输入:cost = [10,15,20] 输出:15 解释:你将从下标为 1 的台阶开始。

  • 支付 15 ,向上爬两个台阶,到达楼梯顶部。 总花费为 15 。

设 $dp[i]$ 为走到位置 i 所需的最小 cost:

简介

Lombok 是一个 Java 库,用于简化 Java 类的代码,特别是那些需要大量样板代码的类。通过注解的方式,Lombok 自动生成常见的代码结构,例如 getter、setter、构造函数、toString、hashCode 和 equals 方法等,从而大大减少了代码冗余,提高了代码的可读性和可维护性。

Lombok 的优势

  • 性能:Lombok 是通过编译时注解处理器生成字节码的,它不会影响运行时性能,但你需要在编译时支持 Lombok。
  • 减少冗余代码:生成大量样板代码,避免手动编写 getter/setter、toString、equals、hashCode 等方法。
  • 提高开发效率:减少代码的编写量,专注于业务逻辑。
  • 提高可维护性:避免由于手动编写冗余代码而产生的潜在错误。

引入

注意 Scope

1
2
3
4
5
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<scope>annotationProcessor</scope>
</dependency>

在编译插件中引入注解处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    <build>
        <plugins>
            <!-- 其他插件 -->

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
<!--                <version>3.8.1</version> -->
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                    <compilerArgs>
                    </compilerArgs>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

若在 Spring 环境下使用,需在打包插件中排除

什么是 SpringMVC

SpringMVC 是处于 Web 层的框架,是对 Servlet 进行了封装。,所以其主要的作用就是用来接收前端发过来的请求和数据然后经过处理并将处理的结果响应给前端,所以如何处理请求和响应是 SpringMVC 中非常重要的一块内容。

什么是 MVC:

  • 早期的 WEB 服务器:将后端服务器Servlet拆分成三层,分别是 webservicedao

    • web层主要由servlet来处理,负责页面请求和数据的收集以及响应结果给前端
    • service层主要负责业务逻辑的处理
    • dao层主要负责数据的增删改查操作
  • 后端渲染+MVC 模式:将 web 层设计为 controllerviewModel

    • controller负责请求和数据的接收,接收后将其转发给service进行业务处理
    • service根据需要会调用dao对数据进行增删改查
    • dao把数据处理完后将结果交给service,service再交给controller
    • controller根据需求组装成Model和View,Model和View组合起来生成页面转发给前端浏览器
  • 前端渲染+MVC 模式:后端不再渲染 View

    • controller 接收请求和数据,转发给业务层
    • 再将业务层响应编码并转发给前端

FirstExample

POM

导入 spring-webmvc 和 javax.servlet-api 的引用

  • 注意 servlet 的 scope 需要限制为 provide(编译和测试时有效)。因为 tomcat 中已有 servlet-api 包,若导入的这个包运行时也有效则会发生冲突
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>6.2.1</version>
</dependency>

配置类

主配置类:启用包扫描

框架

框架的基本特点:

  • 框架(Framework),是基于基础技术之上,从众多业务中抽取出的通用解决方案;
  • 框架是一个半成品,使用框架规定的语法开发可以提高开发效率,可以用简单的代码就能完成复杂的基础业务;
  • 框架内部使用大量的设计模式、算法、底层代码操作技术,如反射、内省、xml 解析、注解解析等;
  • 框架一般都具备扩展性;
  • 有了框架,我们可以将精力尽可能的投入在纯业务开发上而不用去费心技术实现以及一些辅助业务。

Java 中常用的框架:可以分为基础框架和服务框架:

  • 基础框架:完成基本业务操作的框架,如 MyBatis、Spring、SpringMVC、Struts2、Hibernate 等
  • 服务框架:特定领域的框架,一般还可以对外提供服务框架,如 MQ、ES、NacOs 等

控制反转原理

为何需要 IOC,什么是 IOC

  1. 接口和实现,层与层紧密耦合(例如业务层 new 一个数据库对象,后续更换数据库不便)
  2. 通用的事务功能耦合在业务代码中:例如通用的日志功能耦合在业务代码中

解决思路:程序代码中不要手动 new 对象,第三方根据要求为程序提供需要的 Bean 对象的代理对象

IOC 控制反转:不由应用程序直接创建对象,创建对象的控制权转到外部,而由第三方创建对象 DI 依赖注入:指将对象 B 注入到对象 A 之中(例如 A 持有 B 的引用) Aspect Oriented Programing:面向切面编程,主要通过 Proxy 实现

SpringIOC

在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean,离开了 SpringIOC 容器这些 bean 只是普通的 Java 对象

org.springframework.context.ApplicationContext 接口表示Spring IOC容器,负责实例化、配置和组装Bean。

  • 容器通过读取配置元数据获取有关组件的指令,以实例化、配置和组装 Bean,Spring IOC容器本身与配置元数据完全分离
  • 配置元数据可以表示为带注释的组件类、带有工厂方法的配置类或外部XML文件或Groovy脚本
  • 大多数应用场景中,不需要显式用户代码来实例化 Spring IoC 容器的一个或多个实例。
    • 在 Spring Boot 场景中,应用程序上下文是根据常见的设置约定隐式引导的

使用 SpringIOC 实现 IoC+DI

分为三步,实现代码只定义接口,实现通过配置元数据绑定:

AOP 概念和原理

什么是 AOP

AOP(Aspect Oriented Programming)面向切面编程:一种编程范式,在不改原有代码的前提下对其进行增强

术语概念

连接点(Join Point):连接点是程序执行过程中可以插入切面的具体位置,如方法调用或异常抛出。Spring AOP 只支持方法级别的连接点。 切入点(Pointcut):切入点定义了在哪些连接点上执行通知。切入点表达式用于匹配连接点。 通知(Advice):通知定义了切面实际要执行的动作。通知定义在通知类中 切面(Aspect):描述通知与切入点的对应关系。

代理(Proxy):SpringAOP 的核心本质是采用代理模式实现的

Spring AOP 最小单元

添加依赖:spring-context 和 aspectjweaver

  • Java-中间件-Maven依赖版本
  1. 在 Spring 注解类添加: @EnableAspectJAutoProxy 注解
  2. 创建切面类,使用 @Aspect 注解标识
  3. 在切面类中定义切入点 Pointcut 方法,并添加 @Pointcut 注解
  4. 在切面类中定义通知方法,并用 @Before@After@Around 等注解绑定到切入点。这些通知会在切入点执行前/后/前后执行

切面类:例如统计方法执行时间的切面类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
@Aspect
public class MyAdvice {

    @Pointcut("execution(* com.itheima.dao.BookDao.*d*(..))")
    private void pt(){}

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("------------------------------");
        Long startTime = System.currentTimeMillis();
        for (int i = 0 ; i<10000 ; i++) {
            //调用原始操作
            pjp.proceed();
        }
        Long endTime = System.currentTimeMillis();
        Long totalTime = endTime-startTime;
        System.out.println("执行万次消耗时间:" + totalTime + "ms");
        return null;
    }

}

涉及的注解如下:

线程

Java语言内置了多线程支持。当Java程序启动的时候,实际上是启动了一个JVM进程,然后,JVM启动主线程来执行 main() 方法。

创建线程

总体有两类方法

继承 Thread

方法一:从Thread派生一个自定义类,然后覆写run()方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 多线程
public class Main {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start(); // 启动新线程
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("start new thread!");
    }
}

执行上述代码,注意到start()方法会在内部自动调用实例的run()方法。

Vscode 配置文件

vscode 使用的最基本的两个配置文件是tasks.jsonlaunch.json文件(这两个文件一般都是在 vscode 当前打开的文件夹下的 .vscode 文件夹中,没有可以手动创建,只要位置正确就可以生效)。

launch.json: 这个配置文件是告诉vscode如何来启动调试你的代码程序的,这其中包括你的程序在哪个位置,你用什么工具来调试,调试的时候需要给调试工具传什么参数等。
tasks.json: 这个配置文件是用来执行你预定的任务的,比如说你修改了你的代码,调试之前,肯定要重新生成新的程序后再调试,那么你就可以配置它告诉vscode怎么重新生成这个新的程序。(task.json不是必须文件,比如python调试,可以不用提前编译)

vscode就是先跑 tasks.json 任务,再跑 launch.json。

说明: vscode 调用任务是根据lable标签识别的(文章后面有说明)。

1
"lable": "startRun"

环境变量

  • ${workspaceFolder}:在 Visual Studio Code 中打开的文件夹的完整路径
  • ${workspaceFolderBasename}:在Visual Studio Code中打开的文件夹名
  • ${file}:当前打开的文件的完整路径
  • ${relativeFile}:当前打开的文件的相对workspaceFolder路径
  • ${relativeFileDirname}:当前打开的文件的文件夹的相对workspaceFolder路径
  • ${fileBasenameNoExtension}:当前打开的文件的文件名,不包含扩展名
  • ${fileDirname}:当前打开的文件的文件夹的完整路径
  • ${fileExtname}:当前打开的文件的扩展名
  • ${cwd}:Task启动时的工作目录
  • ${lineNumber}:当前光标的所在的行号
  • ${selectedText}:当前打开的文件中选中的文本
  • ${execPath}:Visual Studio Code可知行文件的完整路径
  • ${defaultBuildTask}:默认的Build Task的名字
  • ${env:Name}:引用环境变量
  • ${config:Name}:可以引用Visual Studio Code的设置项
  • ${input:variableID}:传入输入变量

Launch

以 C++编译为例:

  • 指定 Linux,Windows 属性,设置每个平台运行的指令
  • cwd 指定工作目录
  • preLaunchTask 指定预载任务
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "DefaultTest",
            "type": "lldb", 
            "request": "launch",
            "program": "${workspaceFolder}/build/${workspaceRootFolderName}_test",
            "osx": {
                "name": "TestOSX",
                "type": "lldb", 
                "request": "launch",
                "program": "${workspaceFolder}/build/${workspaceRootFolderName}_test",
            },
            "linux": {
                "name": "TestLinux",
                "type": "cppdbg",
                "request": "launch",
                "program": "${workspaceFolder}/build/${workspaceRootFolderName}_test",
                "MIMode": "gdb",
                "setupCommands": [
                    {
                        "description": "gdb print",
                        "text": "-enable-pretty-printing",
                        "ignoreFailures": true
                    }
                ],
            },
            "windows": {
                "name": "TestWindows",
                "type": "cppvsdbg",
                "request": "launch",
                "program": "${workspaceFolder}/build/Debug/${workspaceRootFolderName}_test"
            },
            "args": [], 
            "cwd": "${workspaceFolder}",
            "preLaunchTask": "CmakeBuild",
        },
    ]
}

Task

1. Task 中一些主要的属性及相应语义:

  • label:在用户界面上展示的 Task 标签
  • type:Task的类型,分为shell和process两种
    • shell:作为Shell命令运行
    • process:作为一个进程运行
  • command:真正执行的命令
  • windows:Windows中的特定属性。相应的属性会在Windows中覆盖默认的属性定义。
  • group:定义Task属于哪一组
  • presentation:定义用户界面如何处理Task的输出
  • options:定义cwd(当前工作目录)、env(环境变量)和shell的值
  • runOptions:定义Task何时运行及如何运行。

2. 控制 Task 在运行时的输出行为:

  • reveal:控制集成终端是否显示
    • always:集成终端总是会在Task启动时显示
    • never:集成终端不会主动显示
    • silent:当输出不是错误和警告时,集成终端才会显示
  • focus:控制集成终端在显示时是否取得焦点
  • echo:控制被执行的命令是否在集成终端中输出
  • showReuseMessage:控制是否显示显示“Terminal will be reused by tasks,press any key to close it”提示信息
  • panel:控制不同的Task在运行时是否共享同一个集成终端
    • shared:共享集成终端
    • dedicated:Task会有一个专用的集成终端
    • new:每次运行的Task都会创建一个新的集成终端
  • clear:控制Task在运行前,是否清除集成终端的输出
  • group: 控制 Task 是否在同一个集成终端中运行

3. 控制 Task 在运行时的运行行为

  • reevaluateOnRerun:在执行 Rerun Last Task 命令时,控制是否重新计算变量
  • runOn:指定何时运行Task
    • default:只有在运行RunTask命令时,才会触发运行
    • foderOpen:当包含这个tasks.json文件夹被打开时,便会触发运行

示例

  • 指定 dependson 属性,设置前置任务
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
{
    "tasks": [
        {
            // 任务一: 创建 build 文件夹
            "type": "shell",
            "label": "CreateBuildDir", // lable 标记任务名称
            "command": "mkdir",  // 命令
            // 传给上面命令的参数,这里是传给 Unix 系统的参数,windows下稍有不用,下边有
            "args": [
                "-p",
                "build"
            ], 
            "windows": {
                "options": {
                    "shell": {
                        "executable": "powershell.exe"
                    }
                },
                "args": [   // 对于windows系统,传的参数
                    "-Force",
                    "build"
                ]
            },
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "problemMatcher": [
                "$gcc"
            ],
        },
        // 任务二: Cmake
        // 在 build 文件夹中调用 cmake 进行项目配置
        // 如果想配置比如 release 还是 debug 可以添加参数或者在
        // CMakeLists.txt中设置也行
        {
            "type": "shell",
            "label": "cmakeRun", // 给这个任务起个名字
			// 这里的cmake,用我后面小程序创建的结果填的是全路径,
			// 命令写全路径,则路径中不能包含带空格
			// 如果你添加了环境变量,那么直接填写命令即可,也不会有
			// 路径是否包含空格的问题(下面的命令同理)
            "command": "cmake",
            "args": [
            	"-DCMAKE_MAKE_PROGRAM=E:\\Resource\\mingw64\\bin\\mingw32-make.exe", // MinGW目录下bin目录下的mingw32-make.exe
                "-G",
                // 不使用-G "Unix Makefiles" 参数可能会编译成了VS用的工程文件
                // 之所以三个斜杠,是因为vscode终端自己还要转义一次
                // 2021-01-21更新:我在32位的win7上发现,vscode自己又不转义了
                // 所以如果以下三个斜杠不行的话,大家手动改成一个斜杠就好,即\"Unix Makefiles\"
                // 后面我给的小程序默认写的是3个
                "\\\"Unix Makefiles\\\"",  
                "../"  // ../ 表示build文件夹的上级目录,CMakeLists.txt就放在上级目录中
            ],
            "options": {
                "cwd": "${workspaceFolder}/build"
            },
            "dependsOn":[
                "CreateBuildDir"  // 表示在 创建目录 任务结束后进行
            ]
        },
        // 任务三: make编译
        {
            "type": "shell",
            "label": "makeRun",
            "command": "mingw32-make",  // 这个也是MinGW目录下bin目录下的mingw32-make.exe,如果添加了环境变量,这里直接写mingw32-make.exe
            "args": [],
            "options": {
                "cwd": "${workspaceFolder}/build"
            }, // 注意这里是编译到了项目文件夹下的 build 文件夹里面,这里就解释了
            // 为什么 launch.json 中 program 路径要那么设置了。
            "dependsOn":[
                "cmakeRun"  // 表示在Cmake任务结束后进行
            ]
        },
    ],
    "version": "2.0.0"
}