当前位置: 首页 > article >正文

java基础(2) 面向对象编程-java核心类

面向对象

面向对象对应的就是面向过程,
面向过程就是一步一步去操作,你需要知道每一步的步骤。
面向对象的编程以对象为核心,通过定义类描述实体及其行为,并且支持继承、封装和多态等特性

面向对象基础

面向对象编程,是一种通过对象的方式,把现实世界映射到计算机模型的一种编程方法。
现实世界中,我们定义了“人”这种抽象概念,而具体的人则是“小明”、“小红”、“小军”等一个个具体的人。所以,“人”可以定义为一个类(class),而具体的人则是实例(instance):
在这里插入图片描述

class Test {
    private int field1;
    public String field2;
    // 构造函数
 	public Test(String field2, int field1) {
        this.field1 = field1;
        this.field2 = field2;
    }

    public int getField1(){
        return this.field1;
    }

    public  void setField1(int val){
        if(val < 0 || val > 100){
            // 抛出错误
            throw new IllegalArgumentException("invalid age value");
        }
        // 赋值
        this.field1 = val;
    }
};

public class Main {
	public static void main(String[] args){
		Test t = new Test();
        t.setField1(2);
        System.out.println(t.getField1());
        System.out.println(t.field2);
	}
}

写法同js类型,但是需要指定Test类型。一般字段使用private修饰符修饰,表示私有属性,外部无法访问,但是可以通过设置公共的方法去获取和设置值。
java类的构造函数,是public 类名(){},跟js的constructor(){}还是有点区别的。并且java可以写多个构造方法。除此之外,构造方法之间还能相互调用。

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name) {
        this(name, 18); // 调用另一个构造方法Person(String, int)
    }

    public Person() {
        this("Unnamed"); // 调用另一个构造方法Person(String)
    }
}
方法重载

如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法。
方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。

 public  void setField1(int val){
        if(val < 0 || val > 100){
            // 抛出错误
            throw new IllegalArgumentException("invalid age value");
        }
        // 赋值
        this.field1 = val;
    }
    public  void setField1(int val, boolean isTrue){
        if(val < 0 || val > 100){
            // 抛出错误
            throw new IllegalArgumentException("invalid age value");
        }
        // 赋值
        this.field1 = val;
    }
   

比如String.indexOf()方法,就提供了多种调用方法。

举个例子,String类提供了多个重载方法indexOf(),可以查找子串:
int indexOf(int ch):根据字符的Unicode码查找;
int indexOf(String str):根据字符串查找;
int indexOf(int ch, int fromIndex):根据字符查找,但指定起始位置;
int indexOf(String str, int fromIndex)根据字符串查找,但指定起始位置。
继承

所有的类都默认继承与object,只有object例外,他没有父类。
java的继承同js差不多,只有一点区别,这里不多详述。

多态

子类可以重写父类的方法。这里需要注意,只有方法名,参数,返回值相同,才算是重写。加上@Overide装饰器可以方便编译器检查报错。

class Person {
    public void run() {
        System.out.println("Person.run");
    }
}
class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法;
没运行之前不知道运行的是父类的方法还是子类的方法。

如果父类不想子类继承某个方法,可以用final修饰方法。

class Person {
    protected String name;
    public final String hello() {
        return "Hello, " + name;
    }
}

用final修饰的类不允许被继承
用final修饰的属性,不允许被重新赋值

抽象类

如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法:

 abstract class Person {
    public abstract void run();
}

包含抽象方法的类不允许被实例化。抽象类强迫子类必须实现他的抽象方法。
用法跟ts类似,这里不多详述

面向抽象编程的本质就是:
  • 上层代码只定义规范(例如:abstract class Person);
  • 不需要子类就可以实现业务逻辑(正常编译);
  • 具体的业务逻辑由不同的子类实现,调用者并不关心。
接口

没有字段的,且全部方位都是抽象方法的抽象类,就可以改写为接口。

abstract class Person {
    public abstract void run();
    public abstract String getName();
}
//改写为
interface Person {
    void run();
    String getName();
}

接口定义的所有方法默认都是public abstract的
而当一个具体的类想去实现一个接口的时候,就需要使用implements,而非extends。
一个类只能继承于一个类,但是一个类可以implements多个接口。

在这里插入图片描述
接口之间也可以通过extends去继承。
接口还可以定义一个非抽象方法default,这个不强求类去实现,目的是为了:
当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。

静态字段和静态方法

实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。举个例子:

class Person {
    public String name;
    public int age;
    // 定义静态字段number:
    public static int number;
}

对于静态字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例;
在这里插入图片描述
可以把静态字段立即为类自己的属性,一般通过类.字段去获取,而不是通过实例.字段去获取。

同理,静态方法也一致,属于类,可以直接通过类.方法名去调用,但是内部无法访问this。
静态方法经常用于工具类。例如:
Arrays.sort()
Math.random()

在Java中,我们使用package来解决名字冲突。
Java定义了一种名字空间,称之为包:package。一个类总是属于某个包,类名(比如Person)只是一个简写,真正的完整类名是包名.类名。
类似于ts的namespace,防止命名冲突。
JDK的Arrays类存放在包java.util下面,因此,完整类名是java.util.Arrays。
包的命名跟文件存放位置也有关。
在这里插入图片描述

导入其他包

  • 直接写完整的包名: lin.TestClass
  • 先在当前包import lin.TestClass ,就可以直接用了new TestClass
  • 也可以直接import lin.*,他会将lin包的class引入。
  • 默认引入java.lang的包,比如String, 这些。
作用域

比如public、protected、private,这些修饰符,用来限定作用域的范围。
publci的类,可以被其他包访问。public的属性和方法,也可以被其他类访问。
非publci的类,外部无法访问,就跟js没有export一样
private定义的方法和属性,无法在类外被访问。
由于Java支持嵌套类,如果一个类内部还定义了嵌套类,那么,嵌套类拥有访问private的权限:

public class Main {
    public static void main(String[] args) {
        Inner i = new Inner();
        i.hi();
    }

    // private方法:
    private static void hello() {
        System.out.println("private hello!");
    }

    // 静态内部类:
    static class Inner {
        public void hi() {
            Main.hello();
        }
    }
}

private定义的字段和方法,其继承类中无法使用,procted修饰的方法和字段,可以在子类中使用,但同样不可以被实例调用。

package权限(包作用域)
最后,包作用域是指一个类允许访问同一个package的没有private修饰的class,以及没有protected、private修饰的字段和方法。
final

  • 用final修饰class可以阻止被继承:
  • 用final修饰method可以阻止被子类覆写
  • 用final修饰字段可以阻止被重新赋值
  • 用final修饰局部变量可以阻止被重新赋值
内部类

在这里插入图片描述

也是嵌套类。

classpath和jar

classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class
现在我们假设classpath是.;C:\work\project1\bin;C:\shared,当JVM在加载abc.xyz.Hello这个类时,会依次查找:

  • <当前目录>\abc\xyz\Hello.class
  • C:\work\project1\bin\abc\xyz\Hello.class
  • C:\shared\abc\xyz\Hello.class

在系统环境变量中设置classpath环境变量,不推荐;
在启动JVM时设置classpath变量,推荐。
启动jvm的时候🌽定义
java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello

jar包

如果有很多.class文件,散落在各层目录中,肯定不便于管理。如果能把目录打一个包,变成一个文件,就方便多了。

jar包就是用来干这个事的,它可以把package组织的目录层级,以及各个目录下的所有文件(包括.class文件和其他文件)都打成一个jar文件,这样一来,无论是备份,还是发给客户,就简单多了。

jar包相当于目录,可以包含很多.class文件,方便下载和使用;

MANIFEST.MF文件可以提供jar包的信息,如Main-Class,这样可以直接运行jar包。

java核心类

字符串

实际上字符串在String内部是通过一个char[]数组表示的,因此,按下面的写法也是可以的:

String s2 = new String(new char[] {'H', 'e', 'l', 'l', 'o', '!'});

Java字符串的一个重要特点就是字符串不可变。这种不可变性是通过内部的private final char[]字段,以及没有任何修改char[]的方法实现的。
不可变不是说变量s2不可变,s2可以重新赋值一个新的String对象,只不过原有的String对象不会改变。

字符串比较

java拥有一个字符串常量池,如果多个字符串具有相同的值,那么他们将共享存储在常量池中的同一个实例,也就是说
String s = "hello",这个hello,实际上是存放在常量池中,s存放的值是hello的地址,类似于js的对象,只不过js的字符串是基础类型而不是引用类型。
当我们想要比较两个字符串是否相同时,要特别注意,我们实际上是想比较字符串的内容是否相同。必须使用equals()方法而不能用==
因为==实际上比较的是内存地址,

 		String s1 = "hello";
        String s2 = "HELLO".toLowerCase();
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));

==返回false,只有equals才返回true。

其他字符串方法同js类似

"Hello".indexOf("l"); // 2
"Hello".lastIndexOf("l"); // 3
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); // true
"Hello".substring(2); // "llo"
"Hello".substring(2, 4); "ll"
"  \tHello\r\n ".trim(); // "Hello" 返回的Hello是新的String对象

"".isEmpty(); // true,因为字符串长度为0
"  ".isEmpty(); // false,因为字符串长度不为0
"  \n".isBlank(); // true,因为只包含空白字符
" Hello ".isBlank(); // false,因为包含非空白字符

String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"


//字符串占位 
String s = "Hi %s, your score is %d!";
System.out.println(s.formatted("Alice", 80));
System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5));

字符串转换

要把任意基本类型或引用类型转换为字符串,可以使用静态方法valueOf()。这是一个重载方法,编译器会根据参数自动选择合适的方法。

String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 类似java.lang.Object@636be97c

//int
int n1 = Integer.parseInt("123"); // 123
int n2 = Integer.parseInt("ff", 16); // 按十六进制转换,255

// boolean
boolean b1 = Boolean.parseBoolean("true"); // true
boolean b2 = Boolean.parseBoolean("FALSE"); // false

// char 和 String 互转
char[] cs = "Hello".toCharArray(); // String -> char[]
String s = new String(cs); // char[] -> String


byte[] b1 = "Hello".getBytes(); // 按系统默认编码转换,不推荐
byte[] b2 = "Hello".getBytes("UTF-8"); // 按UTF-8编码转换
byte[] b2 = "Hello".getBytes("GBK"); // 按GBK编码转换
byte[] b3 = "Hello".getBytes(StandardCharsets.UTF_8); // 按UTF-8编码转换

byte[] b = ...
String s1 = new String(b, "GBK"); // 按GBK转换
String s2 = new String(b, StandardCharsets.UTF_8); // 按UTF-8转换
小结
  • Java字符串String是不可变对象;
  • 字符串操作不改变原字符串内容,而是返回新字符串;
  • 常用的字符串操作:提取子串、查找、替换、大小写转换等;
  • Java使用Unicode编码表示String和char;
  • 转换编码就是将String和byte[]转换,需要指定编码;
  • 转换为byte[]时,始终优先考虑UTF-8编码。
StringBuilder

Java编译器对String做了特殊处理,使得我们可以直接用+拼接字符串
但是

String s = "";
for (int i = 0; i < 1000; i++) {
    s = s + "," + i;
}

上面循环代码,每次循环都会创建新的String对象,而老的String对象则被丢弃,造成内存浪费,所以java提供StringBuilder来高校拼接String;
Java标准库提供了StringBuilder,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder中新增字符时,不会创建新的临时对象:

StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
    sb.append(',').append(i);
}
String s = sb.toString();

注意:对于普通的字符串+操作,并不需要我们将其改写为StringBuilder,因为Java编译器在编译时就自动把多个连续的+操作编码为StringConcatFactory的操作。在运行期,StringConcatFactory会自动把字符串连接操作优化为数组复制或者StringBuilder操作。

小结

  • StringBuilder是可变对象,用来高效拼接字符串;
  • StringBuilder可以支持链式操作,实现链式操作的关键是返回实例本身;
StringJoiner

类似用分隔符拼接数组的需求很常见,所以Java标准库还提供了一个StringJoiner来干这个事:

public class Main {
    public static void main(String[] args) {
        String[] names = {"Bob", "Alice", "Grace"};
        var sj = new StringJoiner(", ", "Hello ", "!");
        for (String name : names) {
            sj.add(name);
        }
        System.out.println(sj.toString());
    }
}

第一个参数指定拼接的字符串,第二个参数指定开头,第三个参数指定结尾。
String还提供了一个静态方法join(),这个方法在内部使用了StringJoiner来拼接字符串,在不需要指定“开头”和“结尾”的时候

String[] names = {"Bob", "Alice", "Grace"};
var s = String.join(", ", names);
包装类型

在这里插入图片描述
Integer类实现,

public class Integer {
    private int value;

    public Integer(int value) {
        this.value = value;
    }

    public int intValue() {
        return this.value;
    }
}

在这里插入图片描述

有点像js中,string对应的String, number对应的Numer,
int 和Integer可以互相转换

int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();

Integer n = 100; // 编译器自动使用Integer.valueOf(int)
int x = n; // 编译器自动使用Integer.intValue()

所有的包装类都是不变类,

public final class Integer {
    private final int value;
}

init在初始化赋值之后,将不能再修改;
对两个Integer实例进行比较要特别注意:绝对不能用==比较,因为Integer是引用类型,必须使用equals()比较

public class Main {
    public static void main(String[] args) {
        Integer x = 127;
        Integer y = 127;
        Integer m = 99999;
        Integer n = 99999;
        System.out.println("x == y: " + (x==y)); // true
        System.out.println("m == n: " + (m==n)); // false
        System.out.println("x.equals(y): " + x.equals(y)); // true
        System.out.println("m.equals(n): " + m.equals(n)); // true
    }
}

值较小的Integer比较是相等的,这是因为,在Integer内部,为了优化性能,对于较小的,Integer.initValue总是返回同一个实例,这也导致==为true。

Integer x = 127
//等同于
Integer x = Integer.valueOf(127)

Integer.valueOf()就是静态工厂方法,它尽可能地返回缓存的实例以节省内存。
创建新对象时,优先选用静态工厂方法而不是new操作符。

进制转换

Integer本身还提供了很多方法,就跟js的Number.xxx一样,

  • parseInt 将其他类型转为Integer类型
int x1 = Integer.parseInt("100"); // 100
int x2 = Integer.parseInt("100", 16); // 256,因为按16进制解析

System.out.println(Integer.toString(100)); // "100",表示为10进制
System.out.println(Integer.toString(100, 36)); // "2s",表示为36进制
System.out.println(Integer.toHexString(100)); // "64",表示为16进制
System.out.println(Integer.toOctalString(100)); // "144",表示为8进制
System.out.println(Integer.toBinaryString(100)); // "1100100",表示为2进制

Java的包装类型还定义了一些有用的静态变量

// boolean只有两个值true/false,其包装类型只需要引用Boolean提供的静态字段:
Boolean t = Boolean.TRUE;
Boolean f = Boolean.FALSE;
// int可表示的最大/最小值:
int max = Integer.MAX_VALUE; // 2147483647 类似于js的x`
int min = Integer.MIN_VALUE; // -2147483648
// long类型占用的bit和byte数量:
int sizeOfLong = Long.SIZE; // 64 (bits)
int bytesOfLong = Long.BYTES; // 8 (bytes)

所有的整数和浮点数的包装类型都继承Number,因此,可以非常方便地直接通过包装类型获取各种基本类型:

// 向上转型为Number:
Number num = new Integer(999);
// 获取byte, int, long, float, double:
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();
JavaBean

符合以下这种

// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)

读写方法名分别以get和set开头,并且后接大写字母开头的字段名Xyz,因此两个读写方法名分别是getXyz()和setXyz()。
类似于js的defineProperty的get set方法。

JavaBean是一种符合命名规范的class,它通过getter和setter来定义属性;
属性是一种通用的叫法,并非Java语法规定;
可以利用IDE快速生成getter和setter;
使用Introspector.getBeanInfo()可以获取属性列表。

枚举类

在Java中,我们可以通过static final来定义常量

public class Weekday {
    public static final int SUN = 0;
    public static final int MON = 1;
    public static final int TUE = 2;
    public static final int WED = 3;
    public static final int THU = 4;
    public static final int FRI = 5;
    public static final int SAT = 6;
}

enum
为了让编译器能自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,我们可以使用enum来定义枚举类:

public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if (day == Weekday.SAT || day == Weekday.SUN) {
            System.out.println("Work at home!");
        } else {
            System.out.println("Work at office!");
        }
    }
}

enum Weekday {
    SUN, MON, TUE, WED, THU, FRI, SAT;
}

有点类型于ts的enum,

  • 不同类型的枚举,不能相互赋值。
  • 不同类型的值,不可以直接==
 Test1 tt = Test1.TEST_1;
tt = HAHA.HAHA_1;
int test = 1;
test == Test1.TEST_1;


enum Test1 { TEST_1, TEST_2 };
enum HAHA {HAHA_1, HAHA_2};

enum实际上创建的也是一个class

  • 定义的enum类型总是继承自java.lang.Enum,且无法被继承;
  • 只能定义出enum的实例,而无法通过new操作符创建enum的实例;
  • 定义的每个实例都是引用类型的唯一实例;
  • 可以将enum类型用于switch语句。
public enum Color {
    RED, GREEN, BLUE;
}
//实际上

public final class Color extends Enum { // 继承自Enum,标记为final class
    // 每个实例均为全局唯一:
    public static final Color RED = new Color();
    public static final Color GREEN = new Color();
    public static final Color BLUE = new Color();
    // private构造方法,确保外部无法调用new操作符:
    private Color() {}
}

每个实例都是全局唯一
编译后的enum类和普通class并没有任何区别。但是我们自己无法按定义普通class那样来定义enum,必须使用enum关键字,这是Java语法规定的。

因为enum是一个class,每个枚举的值都是class实例,因此,这些实例有一些方法:name()

String s = Weekday.SUN.name(); // "SUN"
int n = Weekday.MON.ordinal(); // 1 返回定义的常量的顺序,从0开始计数

因为enum实际上定义的也是类,所以可以根据构造函数,添加每个实例对应的属性。比如

enum Weekday {
    MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6), SUN(0);

    public final int dayValue;

    private Weekday(int dayValue) {
        this.dayValue = dayValue;
    }
}

 public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if (day.dayValue == 6 || day.dayValue == 0) {
            System.out.println("Work at home!");
        } else {
            System.out.println("Work at office!");
        }
    }

MON(1)相当于执行new操作,将1传入作为dataValue的值,这样就可以通过.dataValue去获取值。
再比如

enum Weekday {
    MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日");

    public final int dayValue;
    private final String chinese;

    private Weekday(int dayValue, String chinese) {
        this.dayValue = dayValue;
        this.chinese = chinese;
    }

    @Override
    public String toString() {
        return this.chinese;
    }
}

还可以多用其他属性,然后重新toString方法。name属于final,无法被重写。
小结

  • Java使用enum定义枚举类型,它被编译器编译为final class Xxx extends Enum { … };
  • 通过name()获取常量定义的字符串,注意不要使用toString();
  • 通过ordinal()返回常量定义的顺序(无实质意义);
  • 可以为enum编写构造方法、字段和方法
  • enum的构造方法要声明为private,字段强烈建议声明为final;
  • enum适合用在switch语句中。
BigInteger BigDecimal

整数最大是long类型,超过了的话,java.math.BigInteger就是用来表示任意大小的整数。BigInteger内部用一个int[]数组来模拟一个非常大的整数:

BigInteger bi = new BigInteger("1234567890");
System.out.println(bi.pow(5)); // 2867971860299718107233761438093672048294900000

和BigInteger类似,BigDecimal可以表示一个任意大小且精度完全准确的浮点数。
实际上一个BigDecimal是通过一个BigInteger和一个scale来表示的,即BigInteger表示一个完整的整数,而scale表示小数位数

public class BigDecimal extends Number implements Comparable<BigDecimal> {
    private final BigInteger intVal;
    private final int scale;
}
常用工具类

Math
顾名思义,Math类就是用来进行数学计算的,它提供了大量的静态方法来便于我们实现数学计算:

Math.abs(-13) //13 绝对值
Math.max(13,9) //13 最大
Math.min() //最小
Math.pow(2, 10); // 2的10次方=1024
double pi = Math.PI; // 3.14159...
double e = Math.E; // 2.7182818...
Math.sin(Math.PI / 6); // sin(π/6) = 0.5
Math.random(); // 0.53907... 每次都不一样

Java标准库还提供了一个StrictMath,它提供了和Math几乎一模一样的方法。这两个类的区别在于,由于浮点数计算存在误差,不同的平台(例如x86和ARM)计算的结果可能不一致(指误差不同),因此,StrictMath保证所有平台计算结果都是完全相同的,而Math会尽量针对平台优化计算速度,所以,绝大多数情况下,使用Math就足够了。
·
Random
生成伪随机数

Random r = new Random();
r.nextInt(); // 2071575453,每次都不一样
r.nextInt(10); // 5,生成一个[0,10)之间的int
r.nextLong(); // 8811649292570369305,每次都不一样
r.nextFloat(); // 0.54335...生成一个[0,1)之间的float
r.nextDouble(); // 0.3716...生成一个[0,1)之间的double

SecureRandom
有伪随机数,就有真随机数。实际上真正的真随机数只能通过量子力学原理来获取,而我们想要的是一个不可预测的安全的随机数,SecureRandom就是用来创建安全的随机数的:

SecureRandom sr = new SecureRandom();
System.out.println(sr.nextInt(100));

SecureRandom的安全性是通过操作系统提供的安全的随机种子来生成随机数。这个种子是通过CPU的热噪声、读写磁盘的字节、网络流量等各种随机事件产生的“熵”。


http://www.kler.cn/a/232871.html

相关文章:

  • 国家网络安全法律法规
  • JWT深度解析:Java Web中的安全传输与身份验证
  • 文件夹被占用了无法删除怎么办?强制粉碎文件夹你可以这样操作
  • C++模板特化实战:在使用开源库boost::geometry::index::rtree时,用特化来让其支持自己的数据类型
  • 使用elementUI实现表格行拖拽改变顺序,无需引入外部库
  • DNS面临的4大类共计11小类安全风险及防御措施
  • pytest+allure批量执行测试用例
  • Linux操作系统基础(三):虚拟机与Linux系统安装
  • MATLAB环境下用于提取冲击信号的几种解卷积方法
  • 致我的2023年——个人学年总结
  • 32I2C通信协议
  • android 音频调试技巧
  • 25、数据结构/二叉树相关练习20240207
  • vue项目开发vscode配置
  • 《学成在线》微服务实战项目实操笔记系列(P1~P83)【上】
  • FastAPI使用ORJSONResponse作为默认的响应类型
  • MyBatis之动态代理实现增删改查以及MyBatis-config.xml中读取DB信息文件和SQL中JavaBean别名配置
  • 极值图论基础
  • VScode为什么选择了Electron,而不是QT?
  • Leecode之环形链表
  • c#进程(Process)常用方法
  • Linux运用fork函数创建进程
  • Ubuntu22.04 gnome-builder gnome C 应用程序习练笔记(一)
  • 教你用C++开发 身份证号码日期提取工具
  • 除夕快乐(前端小烟花)
  • 【C++ 二分】电脑游戏