初识Java 13-2 异常

news2024/11/18 22:51:47

目录

标准Java异常

新特性:更好的NullPointerException报告机制

使用finally执行清理

finally有什么用

在return时使用finally

缺陷:异常丢失

异常的约束

构造器


本笔记参考自: 《On Java 中文版》


标准Java异常

        Throwable类描述了任何可能抛出异常的事物,它有两个常用的子类:

  • Error:表示编译时错误和系统错误。
  • Exception:是一个基本类型,可从任何标准的Java库方法、自定义方法及运行事故中抛出。

        很显然,作为Java程序员,我们会更加关心Exception

        对异常的理解和处理是较为重要的部分(与了解各种异常相比)。基本的思路是,异常的名字代表着所发生的问题。因此,异常的名字应该是浅显易懂的。

    异常并不全部来自java.lang。有些异常是为不同的库设计的,这一点可以从他们的全限定类名或基类看出。

特例:RuntimeException(非检查型异常)

        有这么一组异常,它们总是会被Java自动抛出,而不需要程序员将它们包含在任何异常说明之中。这种异常都被放在了RuntimeException这一基类之下。

    这就是一个继承的完美示例。

||| RuntimeException:表示编程错误,它包括:

  • 无法预料的错误,比如在我们控制之外的null引用。
  • 作为程序员,应该在代码中检查的错误(例如ArrayIndexOutOfBoundsException,这表示我们的数组大小可能出现问题)。

        注意:在一个地方发现的异常,往往也可能成为另一个地方的问题。

        正如之前所说,RuntimeException及其子类不需要异常说明,这类异常也被称为“非检查型异常(它们指出的是bug。若必须检查,代码会变得十分复杂)。不过,尽管我们一般不会捕捉RuntimeException,但我们可以有选择地抛出一个RuntimeException

        若不捕捉这类异常,RuntimeException就可能逐层返回,知道到达main()方法:

【例子:若不捕捉RuntimeException

public class NeverCaught {
    static void f() {
        throw new RuntimeException("来自f()");
    }

    static void g() {
        f();
    }

    public static void main(String[] args) {
        g();
    }
}

        程序执行的结果是:

        若一个RuntimeException没有被捕获,关于这个异常的printStackTrace()会在程序结束时被调用。这就体现出了RuntimeException(及其子类)的特殊性,这类异常不会被要求写入异常说明,它们的输出将被报告给System.err

    只有RuntimeException(及其子类)类型的异常可被忽略,编译器会强制实施对所有检查型异常的处理。

新特性:更好的NullPointerException报告机制

        在JDK 15之前,NullPointerException能够报告的信息并不多。

【例子:NullPointerException的报错】

package exceptions;

class A {
    String s;

    A(String s) {
        this.s = s;
    }
}

class B {
    A a;

    B(A a) {
        this.a = a;
    }
}

class C {
    B b;

    C(B b) {
        this.b = b;
    }
}

public class BetterNullPointerReports {
    public static void main(String[] args) {
        C[] ca = {
                new C(new B(new A(null))),
                new C(new B(null)),
                new C(null),
        };

        for (C c : ca) {
            try {
                System.out.println(c.b.a.s);
            } catch (NullPointerException npe) {
                System.out.println(npe);
            }
        }
    }
}

        若是在JDK 11的环境中运行,会得到如下的结果:

        但若是在JDK 15或更高版本中运行,则会显示:

更多的信息有助于我们理解和处理这些异常。

使用finally执行清理

        我们可能会需要手动进行一些清理操作,这种操作通常是内存恢复之外的动作,因为内存会由垃圾收集器处理。在这种情况下,我们可能会希望:无论try块是否抛出异常,都会有一段代码必须执行。

        为此,可以在所有的异常处理程序的末尾添加一个finally子句:

try {
    // 被守护区域
} catch(A a1) {
    // 情况A的处理程序
} catch(B b1) {
    // 情况B的处理程序
} finally {
    // 不管哪种情况都会执行的活动
}

【例子:添加了finally的异常处理】

class ThreeException extends Exception {
}

public class FinallyWorks {
    static int count = 0;

    public static void main(String[] args) {
        while (true) {
            try {
                if (count++ == 0) // 使用后缀++,第一次的结果就是0
                    throw new ThreeException();
                System.out.println("没有异常");
            } catch (ThreeException e) {
                System.out.println("ThreeException");
            } finally {
                System.out.println("在finally子句中");
                if (count == 2) // 跳出循环
                    break;
            }
        }
    }
}

        程序执行的结果是:

        有输出可知,无论是否抛出异常,finally子句都会执行。并且,Java中的异常不会允许我们回退到异常抛出的地方。

finally有什么用

        若语言没有垃圾收集,并且不会自动调用析构函数,那么finally就需要确保内存的释放。但Java并不需要这一功能。对Java而言,finally主要用于清理内存之外的某些东西。

        为了演示finally的作用,首先创建一个需要使用的组件:

public class PnOffSwitch {
    private static Switch sw = new Switch();

    public static void f()
            throws OnOffException1, OnOffException2 {
    }

    public static void main(String[] args) {
        try {
            sw.on();

            // 可能抛出异常的代码...
            f();

            sw.off();
        } catch (OnOffException1 e) {
            System.out.println("OnOffException1");
            sw.off();
        } catch (OnOffException2 e) {
            System.out.println("OnOffException1");
            sw.off();
        }
    }
}

        两个需要使用的异常:

        若不使用finally,一般情况下我们会这样进行异常处理:

public class OnOffSwitch {
    private static Switch sw = new Switch();

    public static void f()
            throws OnOffException1, OnOffException2 {
    }

    public static void main(String[] args) {
        try {
            sw.on();

            // 可能抛出异常的代码...
            f();

            sw.off();
        } catch (OnOffException1 e) {
            System.out.println("OnOffException1");
            sw.off();
        } catch (OnOffException2 e) {
            System.out.println("OnOffException1");
            sw.off();
        }
    }
}

        当程序结束时,我们要求sw处于关闭状态。因此每个catch子句的末尾都添加了sw.off()。但程序还有可能抛出某个没有在这里被捕获的异常,这种情况会导致sw.off()被忽略。因此finally就有了用武之地,可以把try块中的清理工作都放在这里:

【例子:finally的清理】

public class WithFinally {
    static Switch sw = new Switch();

    public static void main(String[] args) {
        try {
            sw.on();

            // 可能抛出异常的代码...
            OnOffSwitch.f();
        } catch (OnOffException1 e) {
            System.out.println("OnOffException1");
        } catch (OnOffException2 e) {
            System.out.println("OnOffException2");
        } finally {
            sw.off(); // 无论何种情况都会执行
        }
    }
}

        即使抛出的异常没有在当前这组catch子句中捕获,在异常处理机制向更高一层进行搜索之前,finally也会执行。

【例子:总是执行的finally

class FourException extends Exception {
}

public class AlwaysFinally {
    public static void main(String[] args) {
        System.out.println("进入第一个try块");
        try {
            System.out.println("进入第二个try块");
            try {
                throw new FourException();
            } finally {
                System.out.println("finally在第二个try块中执行");
            }
        } catch (FourException e) {
            System.out.println("在第一个try块的处理程序中捕获异常FourException");
        } finally {
            System.out.println("finally在第一个try块中执行");
        }
    }
}

        程序执行的结果是:

    涉及breakcontinue语句时,finally也会执行。


return时使用finally

        利用finally子句总会执行的特性,我们可以在一个多返回的方法中保证重要的清理工作能够完成:

【例子:在多返回的方法中使用finally

public class MultipleReturns {
    public static void f(int i) {
        System.out.println("初始化后执行清理");
        try {
            System.out.println("Point 1");
            if (i == 1)
                return;

            System.out.println("Point 2");
            if (i == 2)
                return;

            System.out.println("Point 3");
            if (i == 3)
                return;

            System.out.println("结束");
            return;
        } finally {
            System.out.println("执行清理");
        }
    }

    public static void main(String[] args) {
        for (int i = 1; i <= 4; i++){
            f(i);
            System.out.println();
        }
    }
}

        程序执行的结果是:


缺陷:异常丢失

        在一些特殊的finally子句的使用中,可能发生异常丢失的情况:

【例子:特殊子句中的异常丢失】

class VeryImportantException extends Exception {
    @Override
    public String toString() {
        return "这是一个很重要的异常,它不应该被忽略";
    }
}

class HoHumException extends Exception {
    @Override
    public String toString() {
        return "一个不重要的异常";
    }
}

public class LostMessage {
    void f() throws VeryImportantException {
        throw new VeryImportantException();
    }

    void dispose() throws HoHumException {
        throw new HoHumException();
    }

    public static void main(String[] args) {
        try {
            LostMessage lm = new LostMessage();
            try {
                lm.f();
            } finally {
                lm.dispose();
            }
        } catch (VeryImportantException |
                 HoHumException e) {
            System.out.println(e);
        }
    }
}

        程序执行的结果是:

        原本应该被捕获的VeryImportantExceptionfinally子句中的HoHumException取代了。这是十分严重的问题,因为它意味着一个异常可能会完全丢失(并且难以察觉)。

    C++会将在第一个异常处理前抛出第二个异常视为严重的编程错误。

        目前Java还未修复这一问题。为了处理这一麻烦,我们需要将任何可能抛出异常的方法(就是上面的dispose)包在另一个try-catch子句中。

        还有一种会丢失异常的方式,就是在finally子句中执行return

【例子:另一种异常丢失】

public class ExceptionSilencer {
    public static void main(String[] args) {
        try {
            throw new RuntimeException();
        }finally {
            return; // 在finally中使用return,会阻止任何的异常报错
        }
    }
}

        若运行程序,会发现并无任何报错

异常的约束

        存在着这样一个约束:在重写一个方法时,只能抛出 ①该方法的基类版本中说明的异常,或者是 ②以原有异常为基类派生而出的异常。

    这一约束指向一个概念:能够配合基类工作的代码,可以自动配合从这个基类派生出的其他类的对象进行工作,异常也不会例外。

【例子:异常的各种约束】

class BaseballException extends Exception {
}

class Foul extends BaseballException {
}

class Strike extends BaseballException {
}

abstract class Inning {
    Inning() throws BaseballException {
    }

    public void event() throws BaseballException {
    }

    public abstract void atBat() throws Strike, Foul;

    public void walk() { // 该方法没有抛出检查型异常
    }
}

class StormException extends Exception {
}

class RainedOut extends StormException {
}

class PopFoul extends Foul {
}

interface Storm {
    void event() throws RainedOut;

    void rainHard() throws RainedOut;
}

public class StormyInning extends Inning implements Storm {
    // 子类构造器可以有新的异常,但在处理这些异常时还需要考虑基类的异常
    public StormyInning()
            throws RainedOut, BaseballException {
    }

    public StormyInning(String s)
            throws BaseballException {
    }

    // 普通的方法在重写时,必须遵守基类方法的约定
    // 1. 访问权限
    // 2. 不能擅自添加异常
    // void walk() throws PopFoul{}

    // event()方法已经存在于基类当中,接口无法增加其的异常
    // public void event() throws RainedOut {}

    // 若是基类中不存在的方法,则可以自行添加声明:
    @Override
    public void rainHard() throws RainedOut {
    }

    // 即使基类版本会抛出异常,其子类版本也可以选择不进行异常抛出:
    @Override
    public void event() {
    }

    // 若是重写的方法,可以抛出其基类版本所说明的异常的子类:
    @Override
    public void atBat() throws PopFoul { // PopFoul是Foul的子类
    }

    public static void main(String[] args) {
        try {
            StormyInning si = new StormyInning();
            si.atBat();
        } catch (PopFoul e) {
            System.out.println("Pop Foul(一次违规的挥棒)");
        } catch (RainedOut e) {
            System.out.println("Rained out(下雨了)");
        } catch (BaseballException e) { // 这里,派生的si.atBat()不会抛出Strike异常
            System.out.println("通用的baseball(棒球)异常");
        }

        try {
            // 若向上转型,情况会有所不同
            Inning i = new StormyInning();
            i.atBat();
            // 此时,就必须捕获来自基类版本的异常
        } catch (Strike e) {
            System.out.println("Strike(发生碰撞)");
        } catch (Foul e) {
            System.out.println("Foul(犯规)");
        } catch (RainedOut e) {
            System.out.println("Rained out(下雨了)");
        } catch (BaseballException e) {
            System.out.println("通用的baseball(棒球)异常");
        }
    }
}

        先观察Inning类:

        该类的构造器和event()方法都有异常列表,这就向编译器说明它们会抛出异常,但实际上并没有。这种做法是合法的,因为编译器会要求用户捕获任何可能在event()的重写版本中添加的异常(这也适用于抽象方法)。

        StormyInning继承了Inning类和Storm接口,其中event()方法即存在于Inning中,也存在于Storm中。注意,这个event()方法不能改变Inning中的event()方法的异常说明:

假设这种语法能够成立,那么在我们使用基类的时候,就难以判断是否捕获了正确的异常。

    构造器由于其多样的调用形式,在异常的约束方面也不同于一般的方法。有上述例子可以发现,构造器可以抛出任何异常。也因此,子类构造器必须在其异常说明中声明基类构造器提到的异常

        子类构造器不能捕获基类构造器抛出的异常。

        再看StormyInning类中的walk()方法:

这个方法之所以无法编译,就是因为其抛出了一个Inning.walk()没有抛出的异常。

        而从StormyInning类中的event()方法中可以发现:

即使基类方法抛出异常,其子类也可以选择不进行异常抛出。因为这不会破坏基类版本会抛出异常的情况。

        最后需要提一点,在main()中,我们首先定义了一个StormInning对象:

StormyInning si = new StormyInning();

此时,编译器会强制要求我们处理StormInning类声明会抛出的异常。但是,若我们将其向上转型为Inning

Inning i = new StormyInning();

则编译器会强制我们捕获基类声明会抛出的异常。

    异常说明不是方法类型的一部分,因此不能依赖方法说明作为重载方法的依据。

        注意:在继承和重写的过程中,“异常说明”可以缩小,但是不能扩大——这就和继承过程中的规则恰恰相反。

构造器

        我们需要常常怀疑:当发生异常时,程序是否正确地进行了清理。

        构造器的存在会使得对象有一个安全的起始状态。但是,构造器可能会执行某些操作,比如打开文件,这种操作需要通过特殊的清理方法处理。若构造器内抛出了异常,这些清理行为可能就不会正确执行了。

        finally可能是一个办法。但是,由于finally每次都会执行清理代码,若构造器半途而废,finally就可能会把构造器还未成功构建的部分也一并清理。

【例子:文件的打开与清理】

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class InputFile {
    private BufferedReader in;

    public InputFile(String fname) throws Exception {
        try {
            in = new BufferedReader(new FileReader(fname));
            // 剩下的部分也可能抛出异常
        } catch (FileNotFoundException e) {
            System.out.println("无法打开[" + fname + "]");
            // 因为没有打开,所以不用关闭文件
            throw e;
        } catch (Exception e) {
            // 除上述异常,其他异常情况都需要关闭文件
            try {
                in.close(); // 对于可能抛出异常的close()方法,也需要使用try进行处理
            } catch (IOException e2) {
                System.out.println("in.close() 执行失败");
            }

            throw e; // 重新抛出异常
        } finally {
            // 不应该在这里进行文件关闭
        }
    }

    public String getLine() {
        String s;
        try {
            s = in.readLine();
        } catch (IOException e) { // 在方法内部直接处理异常
            throw new RuntimeException("readline() 失败");
        }
        return s;
    }

    public void dispose() {
        try {
            in.close();
            System.out.println("dispose() 执行成功");
        } catch (IOException e) {
            throw new RuntimeException("in.close() 失败");
        }
    }
}

        除了捕获FileNotFoundException(文件未发现)的catch子句外,其他catch子句都应该关闭文件(此时文件已经被打开)。

        比较好的做法是,在执行完异常处理后再次抛出异常。因为构造器的调用已经失败,那么我们当然不会希望构造器的调用者认为构造器顺利完成了任务。

        就像之前所说,在构造器中,finally绝对不适合调用close()来进行文件关闭。因为这种做法会导致文件在构造器调用完毕后就被关闭,这很明显与我们的期望相违背。

    上述的代码中,有些方法会抛出异常,而有的方法会在内部直接处理异常。这种对异常处理的设计需要仔细考虑。

        现在必须提醒一点,Java存在着一个缺点:除了内存的清理,其他清理都不会自动发生。这就导致Java必须告诉客户程序员,那些要让他们自己处理。

        下面的例子演示了如何清理可能抛出异常的类:

【例子:嵌套的try块】

public class Cleanup {
    public static void main(String[] args) {
        try {
            InputFile in = new InputFile("Cleanup.java");
            try {
                String s;
                int i = 1;
                while ((s = in.getLine()) != null)
                    ; // 一行一行进行数据处理
            } catch (Exception e) {
                System.out.println("在main()中捕获异常");
                e.printStackTrace(System.out);
            } finally {
                in.dispose();
            }
        } catch (Exception e) {
            System.out.println("InputFile对象构造失败");
        }
    }
}

        程序执行的结果是:

        InputFile对象的构造存在于外层的try块之中。若构造器执行失败,程序不会向下进入下一个try块中,而是直接来到外层的try块对应的catch子句内。

    这里体现了一种清理惯用法:在创建了一个需要清理的对象后,直接跟一个try-finally块。

【例子:清理惯用法】

class NeedsCleanup {
    // 该构造不会失败
    private static long counter = 1;
    private final long id = counter++;

    public void dispose() {
        System.out.println("需要清理 id:" + id);
    }
}

class ConstructionException extends Exception {
}

class NeedsCleanup2 extends NeedsCleanup {
    // 该构造可能失败
    NeedsCleanup2() throws ConstructionException {
    }
}

public class CleanupIdiom {
    public static void main(String[] args) {
        // 直接的处理方式:在需要清理的对象后紧跟一个try-finally块
        NeedsCleanup nc1 = new NeedsCleanup();
        try {
            // ...
        } finally {
            nc1.dispose();
        }

        // 可以将不会失败的构造对象组织在一起:
        NeedsCleanup nc2 = new NeedsCleanup();
        NeedsCleanup nc3 = new NeedsCleanup();
        try {
            // ...
        } finally {
            // 释放顺序与构造顺序相反
            nc3.dispose();
            nc2.dispose();
        }

        // 若构造可能失败,就需要确保每个对象的清理
        try {
            NeedsCleanup2 nc4 = new NeedsCleanup2();
            try {
                NeedsCleanup2 nc5 = new NeedsCleanup2();
                try {
                    // ...
                } finally {
                    nc5.dispose();
                }
            } catch (ConstructionException e) {
                // 处理nc5可能报出的异常
                System.out.println(e);
            } finally {
                nc4.dispose();
            }
        } catch (ConstructionException e) {
            // 处理nc4
            System.out.println(e);
        }
    }
}

        程序执行的结果是:

        在nc4nc5的构造处理的过程中,可以发现try-catch导致的麻烦:为了处理每一个对象,我们需要为它们分别设定try-catch的处理。

    为了处理异常,我们需要尽可能考虑所有可能性,并确保它们能被处理。

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

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

相关文章

项目生命周期

阶段 项目经理或组织可以将每一个项目划分为若干个阶段&#xff0c;以便于有效地进行管理控制&#xff0c;并实施该项目组织的日常运作联系起来。 项目划分为四个阶段&#xff1a;概念、计划、实施、结束 生命期 项目阶段合在一起称为项目生命期&#xff0c;项目生命期确定了将…

Go流程控制与快乐路径原则

Go流程控制与快乐路径原则 文章目录 Go流程控制与快乐路径原则一、流程控制基本介绍二、if 语句2.1 if 语句介绍2.2 单分支结构的 if 语句形式2.3 Go 的 if 语句的特点2.3.1 分支代码块左大括号与if同行2.3.2 条件表达式不需要括号 三、操作符3.1 逻辑操作符3.2 操作符的优先级…

【k8s】ingress-nginx通过header路由到不同后端

K8S中ingress-nginx通过header路由到不同后端 背景 公司使用ingress-nginx作为网关的项目&#xff0c;需要在相同域名、uri&#xff0c;根据header将请求转发到不同的后端中在稳定发布的情况下&#xff0c;ingress-nginx是没有语法直接支持根据header做转发的。但是这个可以利…

ubuntu配置yolov5环境

本文硬件平台为工控机&#xff0c;系统环境为ubuntu18 配置yolov5步骤 1.下载pytorch和torchvision软件包 由于在线安装容易出现安装失败&#xff0c;所以本文使用的是本地安装。本文是基于miniconda安装的&#xff0c;miniconda安装参考之前的博客&#xff1a;ubuntu中安装m…

ssm+vue的台球厅管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的台球厅管理系统(有报告)。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring S…

差模电感和共模电感的差别

一、初步了解差模、共模的概念 超链接&#xff0c;点击鼠标打开&#xff1a;X电容和Y电容&#xff1b;差模与共模初认识 二、差模和共模电感的二者区别 共模电感和差模电感&#xff0c;是电路中常用的滤波电感、EMI器件&#xff0c;两者经常以环形电感线圈的方式存在。 首先…

理解http中cookie!C/C++实现网络的HTTP cookie

HTTP嗅探&#xff08;HTTP Sniffing&#xff09;是一种网络监控技术&#xff0c;通过截获并分析网络上传输的HTTP数据包来获取敏感信息或进行攻击。其中&#xff0c;嗅探器&#xff08;Sniffer&#xff09;是一种用于嗅探HTTP流量的工具。 在HTTP嗅探中&#xff0c;cookie是一…

Python一步到位实现图像转PDF自动化处理详解

什么是 img2pdf 库&#xff1f; img2pdf 是一个 Python 库&#xff0c;它可以让你轻松地把多张图像转换为 PDF 文件。它支持多种图像格式&#xff0c;如 JPG, PNG, GIF, BMP 等&#xff0c;并且可以自动调整图像的大小和方向&#xff0c;以适应 PDF 的页面大小和方向。它还可以…

跨域问题-笔记

这里写目录标题 一、什么是跨域&#xff1a;二、跨域问题解决思路&#xff1a;1.从浏览器入手2.从域名入手3.从jsonp入手4.从代理入手 一、什么是跨域&#xff1a; 跨域指的是不同服务器之间不能相互访问各自的资源或者数据&#xff0c;这出于一个策略——“同源策略”&#x…

【力扣】2578. 最小和分割

【力扣】2578. 最小和分割 文章目录 【力扣】2578. 最小和分割1. 题目介绍2. 思路3. 解题代码4. 疑问&#xff1f;5. Danger参考 1. 题目介绍 给你一个正整数 num &#xff0c;请你将它分割成两个非负整数 num1 和 num2 &#xff0c;满足&#xff1a; num1 和 num2 直接连起来…

Python 入门

目录 1 Python介绍1.1 特点1.2 什么时候不应该用Python1.3 Python解释器 2 IDLE开发环境使用入门2.1 IDLE 两种模式2.2 IDLE常用快捷键 3 程序基本格式4 图形化程序设计5 绘制奥运五环 声明&#xff1a;本文作为自己的学习笔记&#xff0c;欢迎大家于本人学习交流&#xff0c;转…

联邦学习综述二

联邦学习漫画 联邦学习漫画链接: https://federated.withgoogle.com/ Federated Analytics: Collaborative Data Science without Data Collection 博客链接: https://blog.research.google/2020/05/federated-analytics-collaborative-data.html 本篇博客介绍了联邦分析&a…

JTS:10 Crosses

这里写目录标题 版本点与线点与面线与面线与线 版本 org.locationtech.jts:jts-core:1.19.0 链接: github public class GeometryCrosses {private final GeometryFactory geometryFactory new GeometryFactory();private static final Logger LOGGER LoggerFactory.getLog…

掌握 Web3 的关键工具:9大宝藏APP助你玩转区块链

Web3世界充满了无限机遇&#xff0c;但要掌握它&#xff0c;您需要合适的工具&#xfffd;&#xfffd;&#xfffd;。今天&#xff0c;我将为您介绍9款Web3必备APP&#xff0c;涵盖钱包、DEX、和工具三大类别。而且&#xff0c;我要特别强烈推荐一个强大的钱包——Bitget Wall…

CAN 通信-底层

本文主要以rockchip的rk3568平台基础&#xff0c;介绍can 控制器、硬件电路和底层驱动。 rk3568 CAN 控制器 概览 CAN(控制器区域网络)总线是一种稳健的车载总线标准,它允许微控制器和设备在没有主机计算机的应用中相互通信。它是一个基于消息的协议,最初是为了在汽车中多路…

Godot快速精通-从看懂英文文档开始-翻译插件

视频教程地址&#xff1a;https://www.bilibili.com/video/BV1t8411q7hw/ 大家好&#xff0c;我今天要和大家分享的是如何快速精通Godot&#xff0c;众所周知&#xff0c;一般一个开源项目都会有一个文档&#xff0c;对于有一定基础或者是理解能力强的同学&#xff0c;看文档比…

Qt QPair

QPair 文章目录 QPair 摘要QPairQPair 特点代码示例QPair 与 QMap 区别 关键字&#xff1a; Qt、 QPair、 QMap、 键值、 容器 摘要 今天在观摩小伙伴撸代码的时候&#xff0c;突然听到了QPair自己使用Qt开发这么就&#xff0c;竟然都不知道&#xff0c;所以趁没有被人发…

关于网络协议的若干问题(二)

1、网络号、IP 地址、子网掩码和广播地址的先后关系是什么&#xff1f; 答&#xff1a;当在一个数据中心或者一个办公室规划一个网络的时候&#xff0c;首先是网络管理员规划网段&#xff0c;一般是根据将来要容纳的机器数量来规划&#xff0c;一旦定了&#xff0c;以后就不好…

在 centos7 上安装Docker

1、检查linux内核 Docker 运行在 CentOS 7 上&#xff0c;要求系统为64位、系统内核版本为 3.10 以上。 Docker 运行在 CentOS-6.5 或更高的版本的 CentOS 上&#xff0c;要求系统为64位、系统内核版本为 2.6.32-431 或者更高版本。 uname -r 2、使用 root 权限登录 Centos…

商业化之路怎么走,一家开源分布式数据库厂商的答案|爱分析调研

01 商业化是衡量开源项目成功与否的重要维度之一 中国开源软件商业化公司的涌起以及资本对开源的持续关注&#xff0c;正打破人们对开源与商业化“互斥”的传统印象&#xff0c;展现出两者关系的真正本质&#xff0c;即开源和商业化可以相互促进、相互融合&#xff0c;协同发展…