主頁 > 後端開發 > 我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不復返

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不復返

2021-08-04 07:39:15 後端開發

大家好,我是二哥呀,

Java 位元組碼指令是 JVM 體系中非常難啃的一塊硬骨頭,我估計有些讀者會有這樣的疑惑,“Java 位元組碼難學嗎?我能不能學會啊?”

講良心話,不是我謙虛,一開始學 Java 位元組碼和 Java 虛擬機方面的知識我也感覺頭大!但硬著頭皮學了一陣子之后,突然就開竅了,覺得好有意思,尤其是明白了 Java 代碼在底層竟然是這樣執行的時候,感覺既膨脹又飄飄然,渾身上下散發著自信的光芒!

我在 CSDN 共輸出了 100 多篇 Java 方面的文章,總字數超過 30 萬字, 內容風趣幽默、通俗易懂,識訓了很多初學者的認可和支持,內容包括 Java 語法、Java 集合框架、Java 并發編程、Java 虛擬機等核心內容

為了幫助更多的 Java 初學者,我“一怒之下”就把這些文章重新整理并開源到了 GitHub,起名《教妹學 Java》,聽起來是不是就很有趣?

GitHub 開源地址(歡迎 star):https://github.com/itwanger/jmx-java

Java 官方的虛擬機 Hotspot 是基于堆疊的,而不是基于暫存器的,

基于堆疊的優點是可移植性更好、指令更短、實作起來簡單,但不能隨機訪問堆疊中的元素,完成相同功能所需要的指令數也比暫存器的要多,需要頻繁的入堆疊和出堆疊,

基于暫存器的優點是速度快,有利于程式運行速度的優化,但運算元需要顯式指定,指令也比較長,

Java 位元組碼由操作碼和操作陣列成,

  • 操作碼(Opcode):一個位元組長度(0-255,意味著指令集的操作碼總數不可能超過 256 條),代表著某種特定的操作含義,
  • 運算元(Operands):零個或者多個,緊跟在操作碼之后,代表此操作需要的引數,

由于 Java 虛擬機是基于堆疊而不是暫存器的結構,所以大多數指令都只有一個操作碼,比如 aload_0(將區域變數表中下標為 0 的資料壓入運算元堆疊中)就只有操作碼沒有運算元,而 invokespecial #1(呼叫成員方法或者構造方法,并傳遞常量池中下標為 1 的常量)就是由操作碼和操作陣列成的,

01、加載與存盤指令

加載(load)和存盤(store)相關的指令是使用最頻繁的指令,用于將資料從堆疊幀的區域變數表和運算元堆疊之間來回傳遞,

1)將區域變數表中的變數壓入運算元堆疊中

  • xload_(x 為 i、l、f、d、a,n 默認為 0 到 3),表示將第 n 個區域變數壓入運算元堆疊中,
  • xload(x 為 i、l、f、d、a),通過指定引數的形式,將區域變數壓入運算元堆疊中,當使用這個指令時,表示區域變數的數量可能超過了 4 個

解釋一下,

x 為操作碼助記符,表明是哪一種資料型別,見下表所示,

像 arraylength 指令,沒有操作碼助記符,它沒有代表資料型別的特殊字符,但運算元只能是一個陣列型別的物件,

大部分的指令都不支持 byte、short 和 char,甚至沒有任何指令支持 boolean 型別,編譯器會將 byte 和 short 型別的資料帶符號擴展(Sign-Extend)為 int 型別,將 boolean 和 char 零位擴展(Zero-Extend)為 int 型別,

舉例來說,

private void load(int age, String name, long birthday, boolean sex) {
    System.out.println(age + name + birthday + sex);
}

通過 jclasslib 看一下 load() 方法(4 個引數)的位元組碼指令,

  • iload_1:將區域變數表中下標為 1 的 int 變數壓入運算元堆疊中,
  • aload_2:將區域變數表中下標為 2 的參考資料型別變數(此時為 String)壓入運算元堆疊中,
  • lload_3:將區域變數表中下標為 3 的 long 型變數壓入運算元堆疊中,
  • iload 5:將區域變數表中下標為 5 的 int 變數(實際為 boolean)壓入運算元堆疊中,

通過查看區域變數表就能關聯上了,

2)將常量池中的常量壓入運算元堆疊中

根據資料型別和入堆疊內容的不同,此類又可以細分為 const 系列、push 系列和 Idc 指令,

const 系列,用于特殊的常量入堆疊,要入堆疊的常量隱含在指令本身,

push 系列,主要包括 bipush 和 sipush,前者接收 8 位整數作為引數,后者接收 16 位整數,

Idc 指令,當 const 和 push 不能滿足的時候,萬能的 Idc 指令就上場了,它接收一個 8 位的引數,指向常量池中的索引,

  • Idc_w:接收兩個 8 位數,索引范圍更大,
  • 如果引數是 long 或者 double,使用 Idc2_w 指令,

舉例來說,

public void pushConstLdc() {
    // 范圍 [-1,5]
    int iconst = -1;
    // 范圍 [-128,127]
    int bipush = 127;
    // 范圍 [-32768,32767]
    int sipush= 32767;
    // 其他 int
    int ldc = 32768;
    String aconst = null;
    String IdcString = "沉默王二";
}

通過 jclasslib 看一下 pushConstLdc() 方法的位元組碼指令,

  • iconst_m1:將 -1 入堆疊,范圍 [-1,5],
  • bipush 127:將 127 入堆疊,范圍 [-128,127],
  • sipush 32767:將 32767 入堆疊,范圍 [-32768,32767],
  • ldc #6 <32768>:將常量池中下標為 6 的常量 32768 入堆疊,
  • aconst_null:將 null 入堆疊,
  • ldc #7 <沉默王二>:將常量池中下標為 7 的常量“沉默王二”入堆疊,

3)將堆疊頂的資料出堆疊并裝入區域變數表中

主要是用來給區域變數賦值,這類指令主要以 store 的形式存在,

  • xstore_(x 為 i、l、f、d、a,n 默認為 0 到 3)
  • xstore(x 為 i、l、f、d、a)

明白了 xload_ 和 xload,再看 xstore_ 和 xstore 就會輕松得多,作用反了一下而已,

大家來想一個問題,為什么要有 xstore_ 和 xload_ 呢?它們的作用和 xstore n、xload n 不是一樣的嗎?

xstore_ 和 xstore n 的區別在于,前者相當于只有操作碼,占用 1 個位元組;后者相當于由操作碼和操作陣列成,操作碼占 1 個位元組,運算元占 2 個位元組,一共占 3 個位元組,

由于區域變數表中前幾個位置總是非常常用,雖然 xstore_<n>xload_<n> 增加了指令數量,但位元組碼的體積變小了!

舉例來說,

public void store(int age, String name) {
    int temp = age + 2;
    String str = name;
}

通過 jclasslib 看一下 store() 方法的位元組碼指令,

  • istore_3:從運算元中彈出一個整數,并把它賦值給區域變數表中索引為 3 的變數,
  • astore 4:從運算元中彈出一個參考資料型別,并把它賦值給區域變數表中索引為 4 的變數,

通過查看區域變數表就能關聯上了,

02、算術指令

算術指令用于對兩個運算元堆疊上的值進行某種特定運算,并把結果重新壓入運算元堆疊,可以分為兩類:整型資料的運算指令和浮點資料的運算指令,

需要注意的是,資料運算可能會導致溢位,比如兩個很大的正整數相加,很可能會得到一個負數,但 Java 虛擬機規范中并沒有對這種情況給出具體結果,因此程式是不會顯式報錯的,所以,大家在開發程序中,如果涉及到較大的資料進行加法、乘法運算的時候,一定要注意!

當發生溢位時,將會使用有符號的無窮大 Infinity 來表示;如果某個操作結果沒有明確的數學定義的話,將會使用 NaN 值來表示,而且所有使用 NaN 作為運算元的算術操作,結果都會回傳 NaN,

舉例來說,

public void infinityNaN() {
    int i = 10;
    double j = i / 0.0;
    System.out.println(j); // Infinity

    double d1 = 0.0;
    double d2 = d1 / 0.0;
    System.out.println(d2); // NaN
}
  • 任何一個非零的數除以浮點數 0(注意不是 int 型別),可以想象結果是無窮大 Infinity 的,
  • 把這個非零的數換成 0 的時候,結果又不太好定義,就用 NaN 值來表示,

Java 虛擬機提供了兩種運算模式

  • 向最接近數舍入:在進行浮點數運算時,所有的結果都必須舍入到一個適當的精度,不是特別精確的結果必須舍入為可被表示的最接近的精確值,如果有兩種可表示的形式與該值接近,將優先選擇最低有效位為零的(類似四舍五入),
  • 向零舍入:將浮點數轉換為整數時,采用該模式,該模式將在目標數值型別中選擇一個最接近但是不大于原值的數字作為最精確的舍入結果(類似取整),

我把所有的算術指令列一下:

  • 加法指令:iadd、ladd、fadd、dadd
  • 減法指令:isub、lsub、fsub、dsub
  • 乘法指令:imul、lmul、fmul、dmul
  • 除法指令:idiv、ldiv、fdiv、ddiv
  • 求余指令:irem、lrem、frem、drem
  • 自增指令:iinc

舉例來說,

public void calculate(int age) {
    int add = age + 1;
    int sub = age - 1;
    int mul = age * 2;
    int div = age / 3;
    int rem = age % 4;
    age++;
    age--;
}

通過 jclasslib 看一下 calculate() 方法的位元組碼指令,

  • iadd,加法
  • isub,減法
  • imul,乘法
  • idiv,除法
  • irem,取余
  • iinc,自增的時候 +1,自減的時候 -1

03、型別轉換指令

可以分為兩種:

1)寬化,小型別向大型別轉換,比如 int–>long–>float–>double,對應的指令有:i2l、i2f、i2d、l2f、l2d、f2d,

  • 從 int 到 long,或者從 int 到 double,是不會有精度丟失的;
  • 從 int、long 到 float,或者 long 到 double 時,可能會發生精度丟失;
  • 從 byte、char 和 short 到 int 的寬化型別轉換實際上是隱式發生的,這樣可以減少位元組碼指令,畢竟位元組碼指令只有 256 個,占一個位元組,

2)窄化,大型別向小型別轉換,比如從 int 型別到 byte、short 或者 char,對應的指令有:i2b、i2s、i2c;從 long 到 int,對應的指令有:l2i;從 float 到 int 或者 long,對應的指令有:f2i、f2l;從 double 到 int、long 或者 float,對應的指令有:d2i、d2l、d2f,

  • 窄化很可能會發生精度丟失,畢竟是不同的數量級;
  • 但 Java 虛擬機并不會因此拋出運行時例外,

舉例來說,

public void updown() {
    int i = 10;
    double d = i;
    
    float f = 10f;
    long ong = (long)f;
}

通過 jclasslib 看一下 updown() 方法的位元組碼指令,

  • i2d,int 寬化為 double
  • f2l, float 窄化為 long

04、物件的創建和訪問指令

Java 是一門面向物件的編程語言,那么 Java 虛擬機是如何從位元組碼層面進行支持的呢?

1)創建指令

陣列也是一種物件,但它創建的位元組碼指令和普通的物件不同,創建陣列的指令有三種:

  • newarray:創建基本資料型別的陣列
  • anewarray:創建參考型別的陣列
  • multianewarray:創建多維陣列

普通物件的創建指令只有一個,就是 new,它會接收一個運算元,指向常量池中的一個索引,表示要創建的型別,

舉例來說,

public void newObject() {
    String name = new String("沉默王二");
    File file = new File("無愁河的浪蕩漢子.book");
    int [] ages = {};
}

通過 jclasslib 看一下 newObject() 方法的位元組碼指令,

  • new #13 <java/lang/String>,創建一個 String 物件,
  • new #15 <java/io/File>,創建一個 File 物件,
  • newarray 10 (int),創建一個 int 型別的陣列,

2)欄位訪問指令

欄位可以分為兩類,一類是成員變數,一類是靜態變數(static 關鍵字修飾的),所以欄位訪問指令可以分為兩類:

  • 訪問靜態變數:getstatic、putstatic,
  • 訪問成員變數:getfield、putfield,需要創建物件后才能訪問,

舉例來說,

public class Writer {
    private String name;
    static String mark = "作者";

    public static void main(String[] args) {
        print(mark);
        Writer w = new Writer();
        print(w.name);
    }

    public static void print(String arg) {
        System.out.println(arg);
    }
}

通過 jclasslib 看一下 main() 方法的位元組碼指令,

  • getstatic #2 <com/itwanger/jvm/Writer.mark>,訪問靜態變數 mark
  • getfield #6 <com/itwanger/jvm/Writer.name>,訪問成員變數 name

05、方法呼叫和回傳指令

方法呼叫指令有 5 個,分別用于不同的場景:

  • invokevirtual:用于呼叫物件的成員方法,根據物件的實際型別進行分派,支持多型,
  • invokeinterface:用于呼叫介面方法,會在運行時搜索由特定物件實作的介面方法進行呼叫,
  • invokespecial:用于呼叫一些需要特殊處理的方法,包括構造方法、私有方法和父類方法,
  • invokestatic:用于呼叫靜態方法,
  • invokedynamic:用于在運行時動態決議出呼叫點限定符所參考的方法,并執行,

舉例來說,

public class InvokeExamples {
    private void run() {
        List ls = new ArrayList();
        ls.add("難頂");

        ArrayList als = new ArrayList();
        als.add("學不動了");
    }

    public static void print() {
        System.out.println("invokestatic");
    }

    public static void main(String[] args) {
        print();
        InvokeExamples invoke = new InvokeExamples();
        invoke.run();
    }
}

我們用 javap -c InvokeExamples.class 來反編譯一下,

Compiled from "InvokeExamples.java"
public class com.itwanger.jvm.InvokeExamples {
  public com.itwanger.jvm.InvokeExamples();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  private void run();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #4                  // String 難頂
      11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      16: pop
      17: new           #2                  // class java/util/ArrayList
      20: dup
      21: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
      24: astore_2
      25: aload_2
      26: ldc           #6                  // String 學不動了
      28: invokevirtual #7                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
      31: pop
      32: return

  public static void print();
    Code:
       0: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #9                  // String invokestatic
       5: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #11                 // Method print:()V
       3: new           #12                 // class com/itwanger/jvm/InvokeExamples
       6: dup
       7: invokespecial #13                 // Method "<init>":()V
      10: astore_1
      11: aload_1
      12: invokevirtual #14                 // Method run:()V
      15: return
}

InvokeExamples 類有 4 個方法,包括預設的構造方法在內,

1)InvokeExamples() 構造方法中

預設的構造方法內部會呼叫超類 Object 的初始化構造方法:

`invokespecial #1 // Method java/lang/Object."<init>":()V`

2)成員方法 run()

invokeinterface #5,  2  // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z

由于 ls 變數的參考型別為介面 List,所以 ls.add() 呼叫的是 invokeinterface 指令,等運行時再確定是不是介面 List 的實作物件 ArrayList 的 add() 方法,

invokevirtual #7 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z

由于 als 變數的參考型別已經確定為 ArrayList,所以 als.add() 方法呼叫的是 invokevirtual 指令,

3)main() 方法中

invokestatic  #11 // Method print:()V

print() 方法是靜態的,所以呼叫的是 invokestatic 指令,

方法回傳指令根據方法的回傳值型別進行區分,常見的回傳指令見下圖,

06、運算元堆疊管理指令

常見的運算元堆疊管理指令有 pop、dup 和 swap,

  • 將一個或兩個元素從堆疊頂彈出,并且直接廢棄,比如 pop,pop2;
  • 復制堆疊頂的一個或兩個數值并將其重新壓入堆疊頂,比如 dup,dup2,dup_×1,dup2_×1,dup_×2,dup2_×2;
  • 將堆疊最頂端的兩個槽中的數值交換位置,比如 swap,

這些指令不需要指明資料型別,因為是按照位置壓入和彈出的,

舉例來說,

public class Dup {
    int age;
    public int incAndGet() {
        return ++age;
    }
}

通過 jclasslib 看一下 incAndGet() 方法的位元組碼指令,

  • aload_0:將 this 入堆疊,
  • dup:復制堆疊頂的 this,
  • getfield #2:將常量池中下標為 2 的常量加載到堆疊上,同時將一個 this 出堆疊,
  • iconst_1:將常量 1 入堆疊,
  • iadd:將堆疊頂的兩個值相加后出堆疊,并將結果放回堆疊上,
  • dup_x1:復制堆疊頂的元素,并將其插入 this 下面,
  • putfield #2: 將堆疊頂的兩個元素出堆疊,并將其賦值給欄位 age,
  • ireturn:將堆疊頂的元素出堆疊回傳,

07、控制轉移指令

控制轉移指令包括:

  • 比較指令,比較堆疊頂的兩個元素的大小,并將比較結果入堆疊,
  • 條件跳轉指令,通常和比較指令一塊使用,在條件跳轉指令執行前,一般先用比較指令進行堆疊頂元素的比較,然后進行條件跳轉,
  • 比較條件轉指令,類似于比較指令和條件跳轉指令的結合體,它將比較和跳轉兩個步驟合二為一,
  • 多條件分支跳轉指令,專為 switch-case 陳述句設計的,
  • 無條件跳轉指令,目前主要是 goto 指令,

1)比較指令

比較指令有:dcmpg,dcmpl、fcmpg、fcmpl、lcmp,指令的第一個字母代表的含義分別是 double、float、long,注意,沒有 int 型別,

對于 double 和 float 來說,由于 NaN 的存在,有兩個版本的比較指令,拿 float 來說,有 fcmpg 和 fcmpl,區別在于,如果遇到 NaN,fcmpg 會將 1 壓入堆疊,fcmpl 會將 -1 壓入堆疊,

舉例來說,

public void lcmp(long a, long b) {
    if(a > b){}
}

通過 jclasslib 看一下 lcmp() 方法的位元組碼指令,

lcmp 用于兩個 long 型的資料進行比較,

2)條件跳轉指令

這些指令都會接收兩個位元組的運算元,它們的統一含義是,彈出堆疊頂元素,測驗它是否滿足某一條件,滿足的話,跳轉到對應位置,

對于 long、float 和 double 型別的條件分支比較,會先執行比較指令回傳一個整形值到運算元堆疊中后再執行 int 型別的條件跳轉指令,

對于 boolean、byte、char、short,以及 int,則直接使用條件跳轉指令來完成,

舉例來說,

public void fi() {
    int a = 0;
    if (a == 0) {
        a = 10;
    } else {
        a = 20;
    }
}

通過 jclasslib 看一下 fi() 方法的位元組碼指令,

3 ifne 12 (+9) 的意思是,如果堆疊頂的元素不等于 0,跳轉到第 12(3+9)行 12 bipush 20

3)比較條件轉指令

前綴“if_”后,以字符“i”開頭的指令針對 int 型整數進行操作,以字符“a”開頭的指令表示物件的比較,

舉例來說,

public void compare() {
    int i = 10;
    int j = 20;
    System.out.println(i > j);
}

通過 jclasslib 看一下 compare() 方法的位元組碼指令,

11 if_icmple 18 (+7) 的意思是,如果堆疊頂的兩個 int 型別的數值比較的話,如果前者小于后者時跳轉到第 18 行(11+7),

4)多條件分支跳轉指令

主要有 tableswitch 和 lookupswitch,前者要求多個條件分支值是連續的,它內部只存放起始值和終止值,以及若干個跳轉偏移量,通過給定的運算元 index,可以立即定位到跳轉偏移量位置,因此效率比較高;后者內部存放著各個離散的 case-offset 對,每次執行都要搜索全部的 case-offset 對,找到匹配的 case 值,并根據對應的 offset 計算跳轉地址,因此效率較低,

舉例來說,

public void switchTest(int select) {
    int num;
    switch (select) {
        case 1:
            num = 10;
            break;
        case 2:
        case 3:
            num = 30;
            break;
        default:
            num = 40;
    }
}

通過 jclasslib 看一下 switchTest() 方法的位元組碼指令,

case 2 的時候沒有 break,所以 case 2 和 case 3 是連續的,用的是 tableswitch,如果等于 1,跳轉到 28 行;如果等于 2 和 3,跳轉到 34 行,如果是 default,跳轉到 40 行,

5)無條件跳轉指令

goto 指令接收兩個位元組的運算元,共同組成一個帶符號的整數,用于指定指令的偏移量,指令執行的目的就是跳轉到偏移量給定的位置處,

前面的例子里都出現了 goto 的身影,也很好理解,如果指令的偏移量特別大,超出了兩個位元組的范圍,可以使用指令 goto_w,接收 4 個位元組的運算元,


巨人的肩膀:

https://segmentfault.com/a/1190000037628881

除了以上這些指令,還有例外處理指令和同步控制指令,我打算吊一吊大家的胃口,大家可以期待一波~~

(騷操作)

路漫漫其修遠兮,吾將上下而求索

想要走得更遠,Java 位元組碼這塊就必須得硬碰硬地吃透,希望二哥的這些分享可以幫助到大家~

叨逼叨

二哥在 CSDN 上寫了很多 Java 方面的系列文章,有 Java 核心語法、Java 集合框架、Java IO、Java 并發編程、Java 虛擬機等,也算是體系完整了,

為了能幫助到更多的 Java 初學者,二哥把自己連載的《教妹學Java》開源到了 GitHub,盡管只整理了 50 篇,發現字數已經來到了 10 萬+,內容更是沒得說,通俗易懂、風趣幽默、圖文并茂

GitHub 開源地址(歡迎 star):https://github.com/itwanger/jmx-java

如果有幫助的話,還請給二哥點個贊,這將是我繼續分享下去的最強動力!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/291635.html

標籤:java

上一篇:大學生一個暑假學會5個神仙賺錢技能 | 你學會了幾個?記得收藏喲

下一篇:幾百行代碼寫個Mybatis,原理搞的透透的!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more