五、控制流(2)

news2024/9/24 9:17:54

本章概要

  • return
  • break 和 continue
  • 臭名昭著的 goto
  • switch
  • switch 字符串

return

在 Java 中有几个关键字代表无条件分支,这意味无需任何测试即可发生。这些关键字包括 returnbreakcontinue 和跳转到带标签语句的方法,类似于其他语言中的 goto

return 关键字有两方面的作用:1.指定一个方法返回值 (在方法返回类型非 void 的情况下);2.退出当前方法,并返回作用 1 中值。我们可以利用 return 的这些特点来改写上例 IfElse.java 文件中的 test() 方法。代码示例:

// control/TestWithReturn.java
public class TestWithReturn {
    static int test(int testval, int target) {
        if (testval > target) {
          return +1;
        }
        if (testval < target) {
          return -1;
        }
        return 0; // Match
    }

    public static void main(String[] args) {
        System.out.println(test(10, 5));
        System.out.println(test(5, 10));
        System.out.println(test(5, 5));
    }
}

输出结果:

1
-1
0

这里不需要 else,因为该方法执行到 return 就结束了。

如果在方法签名中定义了返回值类型为 void,那么在代码执行结束时会有一个隐式的 return。 也就是说我们不用在总是在方法中显式地包含 return 语句。 注意:如果你的方法声明的返回值类型为非 void 类型,那么则必须确保每个代码路径都返回一个值。

break 和 continue

在任何迭代语句的主体内,都可以使用 breakcontinue 来控制循环的流程。 其中,break 表示跳出当前循环体。而 continue 表示停止本次循环,开始下一次循环。

下例向大家展示 breakcontinueforwhile 循环中的使用。代码示例:

Range.java

public class Range {
    // Produce sequence [start..end) incrementing by step
    public static int[] range(int start, int end, int step) {
        if (step == 0) {
            throw new IllegalArgumentException("Step cannot be zero");
        }
        int sz = Math.max(0, step >= 0 ? (end + step - 1 - start) / step : (end + step + 1 - start) / step);
        int[] result = new int[sz];
        for (int i = 0; i < sz; i++) {
            result[i] = start + (i * step);
        }
        return result;
    }  // Produce a sequence [start..end)

    public static int[] range(int start, int end) {
        return range(start, end, 1);
    }

    // Produce a sequence [0..n)
    public static int[] range(int n) {
        return range(0, n);
    }
}

BreakAndContinue.java

import static BASE0002.Range.range;
// control/BreakAndContinue.java
// Break 和 continue 关键字
public class BreakAndContinue {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) { // [1]
            if (i == 74) {
                break; // 跳出循环
            }
            if (i % 9 != 0) {
                continue; // 下一次循环
            }
            System.out.print(i + " ");
        }
        System.out.println();
        // 使用 for-in 循环:
        for (int i : range(100)) { // [2]
            if (i == 74) {
                break; // 跳出循环
            }
            if (i % 9 != 0) {
                continue; // 下一次循环
            }
            System.out.print(i + " ");
        }
        System.out.println();
        int i = 0;
        //  "无限循环":
        while (true) { // [3]
            i++;
            int j = i * 27;
            if (j == 1269) {
                break; // 跳出循环
            }
            if (i % 10 != 0) {
                continue; // 循环顶部
            }
            System.out.print(i + " ");
        }
    }
}

输出结果:

0 9 18 27 36 45 54 63 72
0 9 18 27 36 45 54 63 72
10 20 30 40

[1] 在这个 for 循环中,i 的值永远不会达到 100,因为一旦 i 等于 74,break 语句就会中断循环。通常,只有在不知道中断条件何时满足时,才需要 break。因为 i 不能被 9 整除,continue 语句就会使循环从头开始。这使 i 递增)。如果能够整除,则将值显示出来。

[2] 使用 for-in 语法,结果相同。

[3] 无限 while 循环。循环内的 break 语句可中止循环。注意,continue 语句可将控制权移回循环的顶部,而不会执行 continue 之后的任何操作。 因此,只有当 i 的值可被 10 整除时才会输出。在输出中,显示值 0,因为 0%9 产生 0。还有一种无限循环的形式: for(;;)。 在编译器看来,它与 while(true) 无异,使用哪种完全取决于你的编程品味。

臭名昭著的 goto

goto 关键字 很早就在程序设计语言中出现。事实上,goto 起源于汇编(assembly language)语言中的程序控制:“若条件 A 成立,则跳到这里;否则跳到那里”。如果你读过由编译器编译后的代码,你会发现在其程序控制中充斥了大量的跳转。较之汇编产生的代码直接运行在硬件 CPU 中,Java 也会产生自己的“汇编代码”(字节码),只不过它是运行在 Java 虚拟机里的(Java Virtual Machine)。

一个源码级别跳转的 goto,为何招致名誉扫地呢?若程序总是从一处跳转到另一处,还有什么办法能识别代码的控制流程呢?随着 _Edsger Dijkstra_发表著名的 “Goto 有害” 论(Goto considered harmful)以后,goto 便从此失宠。甚至有人建议将它从关键字中剔除。

正如上述提及的经典情况,我们不应走向两个极端。问题不在 goto,而在于过度使用 goto。在极少数情况下,goto 实际上是控制流程的最佳方式。

尽管 goto 仍是 Java 的一个保留字,但其并未被正式启用。可以说, Java 中并不支持 goto。然而,在 breakcontinue 这两个关键字的身上,我们仍能看出一些 goto 的影子。它们并不属于一次跳转,而是中断循环语句的一种方法。之所以把它们纳入 goto 问题中一起讨论,是由于它们使用了相同的机制:标签。

“标签”是后面跟一个冒号的标识符。代码示例:

label1:

对 Java 来说,唯一用到标签的地方是在循环语句之前。进一步说,它实际需要紧靠在循环语句的前方 —— 在标签和循环之间置入任何语句都是不明智的。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另一个循环或者一个开关。这是由于 breakcontinue 关键字通常只中断当前循环,但若搭配标签一起使用,它们就会中断并跳转到标签所在的地方开始执行。代码示例:

label1:
outer-iteration { 
  inner-iteration {
  // ...
  break; // [1] 
  // ...
  continue; // [2] 
  // ...
  continue label1; // [3] 
  // ...
  break label1; // [4] 
  } 
}

[1] break 中断内部循环,并在外部循环结束。

[2] continue 移回内部循环的起始处。但在条件 3 中,continue label1 却同时中断内部循环以及外部循环,并移至 label1 处。

[3] 随后,它实际是继续循环,但却从外部循环开始。

[4] break label1 也会中断所有循环,并回到 label1 处,但并不重新进入循环。也就是说,它实际是完全中止了两个循环。

下面是 for 循环的一个例子:

// control/LabeledFor.java
// 搭配“标签 break”的 for 循环中使用 break 和 continue
public class LabeledFor {
    public static void main(String[] args) {
        int i = 0;
        outer:
        // 此处不允许存在执行语句
        for (; true; ) { // 无限循环
            inner:
            // 此处不允许存在执行语句
            for (; i < 10; i++) {
                System.out.println("i = " + i);
                if (i == 2) {
                    System.out.println("continue");
                    continue;
                }
                if (i == 3) {
                    System.out.println("break");
                    i++; // 否则 i 永远无法获得自增
                    // 获得自增
                    break;
                }
                if (i == 7) {
                    System.out.println("continue outer");
                    i++;  // 否则 i 永远无法获得自增
                    // 获得自增
                    continue outer;
                }
                if (i == 8) {
                    System.out.println("break outer");
                    break outer;
                }
                for (int k = 0; k < 5; k++) {
                    if (k == 3) {
                        System.out.println("continue inner");
                        continue inner;
                    }
                }
            }
        }
        // 在此处无法 break 或 continue 标签
    }
}

输出结果:

在这里插入图片描述

注意 break 会中断 for 循环,而且在抵达 for 循环的末尾之前,递增表达式不会执行。由于 break 跳过了递增表达式,所以递增会在 i==3 的情况下直接执行。在 i==7 的情况下,continue outer 语句也会到达循环顶部,而且也会跳过递增,所以它也是直接递增的。

如果没有 break outer 语句,就没有办法在一个内部循环里找到出外部循环的路径。这是由于 break 本身只能中断最内层的循环(对于 continue 同样如此)。 当然,若想在中断循环的同时退出方法,简单地用一个 return 即可。

下面这个例子向大家展示了带标签的 break 以及 continue 语句在 while 循环中的用法:

// control/LabeledWhile.java
// 带标签的 break 和 conitue 在 while 循环中的使用
public class LabeledWhile {
    public static void main(String[] args) {
        int i = 0;
        outer:
        while (true) {
            System.out.println("Outer while loop");
            while (true) {
                i++;
                System.out.println("i = " + i);
                if (i == 1) {
                    System.out.println("continue");
                    continue;
                }
                if (i == 3) {
                    System.out.println("continue outer");
                    continue outer;
                }
                if (i == 5) {
                    System.out.println("break");
                    break;
                }
                if (i == 7) {
                    System.out.println("break outer");
                    break outer;
                }
            }
        }
    }
}

输出结果:

在这里插入图片描述

同样的规则亦适用于 while

  1. 简单的一个 continue 会退回最内层循环的开头(顶部),并继续执行。
  2. 带有标签的 continue 会到达标签的位置,并重新进入紧接在那个标签后面的循环。
  3. break 会中断当前循环,并移离当前标签的末尾。
  4. 带标签的 break 会中断当前循环,并移离由那个标签指示的循环的末尾。

大家要记住的重点是:在 Java 里需要使用标签的唯一理由就是因为有循环嵌套存在,而且想从多层嵌套中 breakcontinue

breakcontinue 标签在编码中的使用频率相对较低 (此前的语言中很少使用或没有先例),所以我们很少在代码里看到它们。

Dijkstra“Goto 有害” 论文中,他最反对的就是标签,而非 goto。他观察到 BUG 的数量似乎随着程序中标签的数量而增加。标签和 goto 使得程序难以分析。但是,Java 标签不会造成这方面的问题,因为它们的应用场景受到限制,无法用于以临时方式传输控制。由此也引出了一个有趣的情形:对语言能力的限制,反而使它这项特性更加有价值。

switch

switch 有时也被划归为一种选择语句。根据整数表达式的值,switch 语句可以从一系列代码中选出一段去执行。它的格式如下:

switch(integral-selector) {
	case integral-value1 : statement; break;
	case integral-value2 : statement;	break;
	case integral-value3 : statement;	break;
	case integral-value4 : statement;	break;
	case integral-value5 : statement;	break;
	// ...
	default: statement;
}

其中,integral-selector (整数选择因子)是一个能够产生整数值的表达式,switch 能够将这个表达式的结果与每个 integral-value (整数值)相比较。若发现相符的,就执行对应的语句(简单或复合语句,其中并不需要括号)。若没有发现相符的,就执行 default 语句。

在上面的定义中,大家会注意到每个 case 均以一个 break 结尾。这样可使执行流程跳转至 switch 主体的末尾。这是构建 switch 语句的一种传统方式,但 break 是可选的。若省略 break, 会继续执行后面的 case 语句的代码,直到遇到一个 break 为止。通常我们不想出现这种情况,但对有经验的程序员来说,也许能够善加利用。注意最后的 default 语句没有 break,因为执行流程已到了 break 的跳转目的地。当然,如果考虑到编程风格方面的原因,完全可以在 default 语句的末尾放置一个 break,尽管它并没有任何实际的作用。

switch 语句是一种实现多路选择的干净利落的一种方式(比如从一系列执行路径中挑选一个)。但它要求使用一个选择因子,并且必须是 intchar 那样的整数值。例如,假若将一个字串或者浮点数作为选择因子使用,那么它们在 switch 语句里是不会工作的。对于非整数类型(Java 7 以上版本中的 String 型除外),则必须使用一系列 if 语句。 在后面中,我们将会了解到枚举类型被用来搭配 switch 工作,并优雅地解决了这种限制。

下面这个例子可随机生成字母,并判断它们是元音还是辅音字母:

import java.util.*;
// control/VowelsAndConsonants.java
// switch 执行语句的演示
public class VowelsAndConsonants {
    public static void main(String[] args) {
        Random rand = new Random(47);
        for (int i = 0; i < 100; i++) {
            int c = rand.nextInt(26) + 'a';
            System.out.print((char) c + ", " + c + ": ");
            switch (c) {
                case 'a':
                case 'e':
                case 'i':
                case 'o':
                case 'u':
                    System.out.println("vowel");
                    break;
                case 'y':
                case 'w':
                    System.out.println("Sometimes vowel");
                    break;
                default:
                    System.out.println("consonant");
            }
        }
    }
}

输出结果:

y, 121: Sometimes vowel
n, 110: consonant
z, 122: consonant
b, 98: consonant
r, 114: consonant
n, 110: consonant
y, 121: Sometimes vowel
g, 103: consonant
c, 99: consonant
f, 102: consonant
o, 111: vowel
w, 119: Sometimes vowel
z, 122: consonant
  ...

由于 Random.nextInt(26) 会产生 0 到 25 之间的一个值,所以在其上加上一个偏移量 a,即可产生小写字母。在 case 语句中,使用单引号引起的字符也会产生用于比较的整数值。

请注意 case 语句能够堆叠在一起,为一段代码形成多重匹配,即只要符合多种条件中的一种,就执行那段特别的代码。这时也应该注意将 break 语句置于特定 case 的末尾,否则控制流程会继续往下执行,处理后面的 case。在下面的语句中:

int c = rand.nextInt(26) + 'a';

此处 Random.nextInt() 将产生 0~25 之间的一个随机 int 值,它将被加到 a 上。这表示 a 将自动被转换为 int 以执行加法。为了把 c 当作字符打印,必须将其转型为 char;否则,将会输出整数。

switch 字符串

Java 7 增加了在字符串上 switch 的用法。 下例展示了从一组 String 中选择可能值的传统方法,以及新式方法:

// control/StringSwitch.java
public class StringSwitch {
    public static void main(String[] args) {
        String color = "red";
        // 老的方式: 使用 if-then 判断
        if ("red".equals(color)) {
            System.out.println("RED");
        } else if ("green".equals(color)) {
            System.out.println("GREEN");
        } else if ("blue".equals(color)) {
            System.out.println("BLUE");
        } else if ("yellow".equals(color)) {
            System.out.println("YELLOW");
        } else {
            System.out.println("Unknown");
        }
        // 新的方法: 字符串搭配 switch
        switch (color) {
            case "red":
                System.out.println("RED");
                break;
            case "green":
                System.out.println("GREEN");
                break;
            case "blue":
                System.out.println("BLUE");
                break;
            case "yellow":
                System.out.println("YELLOW");
                break;
            default:
                System.out.println("Unknown");
                break;
        }
    }
}

输出结果:

RED
RED

一旦理解了 switch,你会明白这其实就是一个逻辑扩展的语法糖。新的编码方式能使得结果更清晰,更易于理解和维护。

作为 switch 字符串的第二个例子,我们重新访问 Math.random()。 它是否产生从 0 到 1 的值,包括还是不包括值 1 呢?在数学术语中,它属于 (0,1)、[0,1)、(0,1]、[0,1] 中的哪种呢?(方括号表示“包括”,而括号表示“不包括”)

下面是一个可能提供答案的测试程序。 所有命令行参数都作为 String 对象传递,因此我们可以 switch 参数来决定要做什么。 那么问题来了:如果用户不提供参数 ,索引到 args 的数组就会导致程序失败。 解决这个问题,我们需要预先检查数组的长度,若长度为 0,则使用空字符串 "" 替代;否则,选择 args 数组中的第一个元素:

TimedAbort.java

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class TimedAbort {
    private volatile boolean restart = true;

    public TimedAbort(double t, String msg) {
        CompletableFuture.runAsync(() -> {
            try {
                while (restart) {
                    restart = false;
                    TimeUnit.MILLISECONDS
                            .sleep((int) (1000 * t));
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(msg);
            System.exit(0);
        });
    }

    public TimedAbort(double t) {
        this(t, "TimedAbort " + t);
    }

    public void restart() {
        restart = true;
    }
}
// control/RandomBounds.java
// Math.random() 会产生 0.0 和 1.0 吗?
// {java RandomBounds lower}
public class RandomBounds {
    public static void main(String[] args) {
        new TimedAbort(3);
        switch (args.length == 0 ? "" : args[0]) {
            case "lower":
                while (Math.random() != 0.0) {
                    ; // 保持重试
                }
                System.out.println("Produced 0.0!");
                break;
            case "upper":
                while (Math.random() != 1.0) {
                    ; // 保持重试
                }
                System.out.println("Produced 1.0!");
                break;
            default:
                System.out.println("Usage:");
                System.out.println("\tRandomBounds lower");
                System.out.println("\tRandomBounds upper");
                System.exit(1);
        }
    }
}

要运行该程序,请键入以下任一命令:

java RandomBounds lower 
// 或者
java RandomBounds upper

TimedAbort 类可使程序在三秒后中止。从结果来看,似乎 Math.random() 产生的随机值里不包含 0.0 或 1.0。 这就是该测试容易混淆的地方:若要考虑 0 至 1 之间所有不同 double 数值的可能性,那么这个测试的耗费的时间可能超出一个人的寿命了。 这里我们直接给出正确的结果:Math.random() 的结果集范围包含 0.0 ,不包含 1.0。 在数学术语中,可用 [0,1) 来表示。由此可知,我们必须小心分析实验并了解它们的局限性。

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

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

相关文章

Spring Cloud Alibaba - Nacos源码分析(三)

目录 一、Nacos客户端服务订阅的事件机制 1、监听事件的注册 2、ServiceInfo处理 serviceInfoHolder.processServiceInfo 一、Nacos客户端服务订阅的事件机制 Nacos客户端订阅的核心流程&#xff1a;Nacos客户端通过一个定时任务&#xff0c;每6秒从注册中心获取实例列表&…

华为nat64配置

1.前期环境准备 环境拓扑 拓扑分为两个区域,左边为trust区域,使用IPv4地址互访,右边为untrust区域,使用IPv6地址互访 2.接口地址配置 pc1地址配置 pc2地址配置 FW接口配置 (1)首先进入防火墙配置界面 注:防火墙初始账号密码为user:admin,pwd:Admin@123,进入之后…

目标检测之3维合成

现在有一系列的图片&#xff0c;图片之间可以按照z轴方向进行排列。图片经过了目标检测&#xff0c;输出了一系列的检测框&#xff0c;现在的需求是将检测框按类别进行合成&#xff0c;以在3维上生成检测结果。 思路&#xff1a;将图片按照z轴方向排列&#xff0c;以z轴索引作…

机器学习:提取问题答案

模型BERT 任务&#xff1a;提取问题和答案 问题的起始位置和结束位置。 数据集 数据集 DRCDODSQA 先分词&#xff0c;然后tokenize 文章长度是不同的&#xff0c;bert的token的长度有限制&#xff0c;一般是512&#xff0c; self-attention的计算量是 O ( n 2 ) O(n^2) O(n…

Netty学习(三)

文章目录 三. Netty 进阶1. 粘包与半包1.1 粘包现象服务端代码客户端代码 1.2 半包现象服务端代码客户端代码 1.3 现象分析粘包半包缘由滑动窗口MSS 限制Nagle 算法 1.4 解决方案方法1&#xff0c;短链接方法2&#xff0c;固定长度方法3&#xff0c;固定分隔符方法4&#xff0c…

Stable Diffusion 开源模型 SDXL 1.0 发布

关于 SDXL 模型&#xff0c;之前写过两篇&#xff1a; Stable Diffusion即将发布全新版本Stable Diffusion XL 带来哪些新东西&#xff1f; 一晃四个月的时间过去了&#xff0c;Stability AI 团队终于发布了 SDXL 1.0。当然在这中间发布过几个中间版本&#xff0c;分别是 SDXL …

c++ 类

类的引入 c 语言的结构体只能定义变量 但是 c的结构体除了定义变量之外&#xff0c;还可以定义函数。 感受感受&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1//我们声明一个结构体 struct Stack {// c可以把函数写在结构体中//叫成员函数:// 如下&#xff1a;//c的写法&am…

【Git】分支管理-创建切换合并删除分支冲突

文章目录 分支管理创建分支切换分支合并分支删除分支分支冲突 分支管理 在版本库当中有一个head指针&#xff0c;指向master分支。master存储的是最新一次提交的commit id&#xff08;版本号&#xff09; >对应的是版本库当中对象库的一个对象的索引 在版本回退⾥&#xff…

【MySQL 基于Amoeba读写分离】

目录 一、读写分离是什么&#xff1f; 二、常见的MySQL读写分离方案 1.基于程序代码内部实现 2.基于中间代理层实现 3.Amoeba 三、分离步骤 1.在主机Amoeba上安装java环境 2.安装并配置Amoeba 3.配置Amoeba读写分离&#xff0c;两个Slave读负载均衡 4.测试 4.1 在Cl…

大数据Flink(五十三):Flink流处理特性、发展历史以及Flink的优势

文章目录 Flink流处理特性、发展历史以及Flink的优势 一、Flink流处理特性 二、发展历史

javascript数据类型详解

文章和代码已经归档至【Github仓库&#xff1a;https://github.com/timerring/front-end-tutorial 】或者公众号【AIShareLab】回复 javascript 也可获取。 文章目录 数据类型数据类型的分类基本数据类型Number数字型进制数字型范围三个特殊值IsNaN () String字符串转义符字符串…

matplotlib绘图中可选标记

文章目录 简介所有可用的绘图标记绘图函数标记绘制 简介 前面的博客简要介绍了matplotlib中的绘图标记&#xff0c;并列举出了部分可用标记点的类型&#xff0c;并画了个图作为示例&#xff0c;如下图下表所示。本文则将所有标记点的类型均绘制一遍 字符类型字符类型字符类型…

C++ | 红黑树以及map与set的封装

目录 前言 一、红黑树 1、红黑树的基本概念 2、红黑树相关特性 3、红黑树结点的定义 4、红黑树的查找 5、红黑树的插入 6、二叉树的拷贝构造与析构 7、红黑树的检测 8、红黑树总结 二、map与set的封装 1、红黑树的结点 2、红黑树迭代器 3、set的封装 4、map的封…

error:0308010C:digital envelope routines::unsupported(Vue2报错)

原因:node.js版本过高&#xff0c; 解决方案&#xff0c;在终端输入以下命令 set NODE_OPTIONS--openssl-legacy-provider 然后再package.json里面添加一行 "dev_t": "set NODE_OPTIONS\"--openssl-legacy-provider\" & npm run dev\n" 然后…

又一个产业即将被中国超越,韩国急了,将提供1.2万亿支持

日前韩媒报道指韩国计划推出1.2万亿韩元的OLED面板产业资助计划&#xff0c;希望帮助韩国两大OLED面板企业三星和LGD巩固OLED面板的技术领先优势&#xff0c;主要是因为它们正面临中国面板厂商的狙击&#xff0c;即将被超越。 据悉韩国推出的1.2万亿韩元资助计划&#xff0c;其…

在EF Core中为数据表按列加密存储

假设有User表 public class User : Entity<int> {public int Id { get; set; }public string UserName { get; set; }public string Name { get; set; }public string IdentificationNumber { get; set; } }其中有身份证号码IdentificationNumber列&#xff0c;需要加密…

计算机网络知识点汇总(持续更新)

文章目录 第一章 概述1.1 计算机网络在信息时代的作用信息服务基础设施我国互联网发展状况 1.2 因特网概述网络、互联网、因特网的基本概述因特网发展的三个阶段因特网的标准化工作 1.3 三种交换方式电路交换分组交换报文交换 1.4 计算机网络的定义和分类定义分类按交换技术按使…

【雕爷学编程】Arduino动手做(175)---机智云ESP8266开发板模块4

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

互联网广告投放算法是怎么回事?这本书给你答案

目录 内容简介 作者简介 读者对象 书本目录 文末自购链接 广告平台的建设和完善是一项长期工程。例如&#xff0c;谷歌早于2003年通过收购Applied Semantics开展Google AdSense 项目&#xff0c;而直到20年后的今天&#xff0c;谷歌展示广告平台仍在持续创新和提升。广告平…

QT编写的串口助手

QT编写的串口助手 提前的知识 创建UI界面工程 找帮助文档 添加串口的宏