主頁 > 後端開發 > java方法句柄-----1.方法句柄型別、呼叫

java方法句柄-----1.方法句柄型別、呼叫

2020-10-18 21:03:22 後端開發

目錄
  • 方法句柄
    • 1.方法句柄的型別
      • 1.1MethodType類的物件實體的創建
        • 1.1.1 通過指定引數和回傳值的型別來創建MethodType.【顯式地指定回傳值和引數的型別】
        • 1.1.2 通過靜態工廠方法genericMethodType來創建的
        • 1.1.2 通過靜態工廠方法fromMethodDescriptorString來創建的
    • 2 對MethodType類的物件實體的修改
      • 2.1 圍繞回傳值和引數型別的精確修改
      • 2.2 一次性對回傳值和所有引數的型別進行修改
    • 3.方法句柄的呼叫
      • 3.1 通過invokeExact方法實作
      • 3.1 通過invoke方法實作
      • 3.3 通過invokeWithArguments方法實作
    • 4.引數長度可變的方法句柄 --- 簡化方法呼叫時的語法
      • 4.1 MethodHandle的asVarargsCollector方法
      • 4.2 MethodHandle的asCollector方法
      • 4.3MethodHandle的asSpreader方法
      • 4.3MethodHandle的asFixedArity方法
    • 5.引數系結

方法句柄

??方法句柄(method handle)是JSR 292中引入的一個重要概念,它是對Java中方法、構造方法和域的一個強型別的可執行的參考,這也是句柄這個詞的含義所在,通過方法句柄可以直接呼叫該句柄所參考的底層方法,從作用上來說,方法句柄的作用類似于2.2節中提到的反射API中的Method類,但是方法句柄的功能更強大、使用更靈活、性能也更好,實際上,方法句柄和反射API也是可以協同使用的,下面會具體介紹,
在Java標準庫中,方法句柄是由java.lang.invoke.MethodHandle類來表示的,

1.方法句柄的型別

??對于一個方法句柄來說,它的型別完全由它的引數型別和回傳值型別來確定,而與它所參考的底層方法的名稱和所在的類沒有關系,比如參考String類的length方法和Integer類的intValue方法的方法句柄的型別就是一樣的,因為這兩個方法都沒有引數,而且回傳值型別都是int,
??在得到一個方法句柄,即MethodHandle類的物件之后,可以通過其type方法來查看其型別,該方法的回傳值是一個java.lang.invoke.MethodType類的物件,MethodType類的所有物件實體都是不可變的,類似于String類,所有對MethodType類物件的修改,都會產生一個新的MethodType類物件,兩個MethodType類物件是否相等,只取決于它們所包含的引數型別和回傳值型別是否完全一致,

1.1MethodType類的物件實體的創建

??MethodType類的物件實體只能通過MethodType類中的靜態工廠方法來創建,這樣的工廠方法有三類,

1.1.1 通過指定引數和回傳值的型別來創建MethodType.【顯式地指定回傳值和引數的型別】

??這主要是使用methodType方法的多種多載形式,使用這些方法的時候,至少需要指定回傳值型別,而引數型別則可以是0到多個,
??回傳值型別總是出現在methodType方法引數串列的第一個后面緊接著的是0到多個引數的型別,型別都是由Class類的物件來指定的,如果回傳值型別是void,可以用void.class或java.lang.Void.class來宣告,
??代碼清單2-31中給出了使用methodType方法的幾個示例,注意:最后一個methodType方法呼叫中使用了另外一個MethodType的引數型別作為當前MethodType類物件的引數型別

代碼清單2-31 MethodType類中的methodType方法的使用示例

public void generateMethodTypes(){
//String.length()
MethodType mt1=MethodType.methodType(int.class);
//String.concat(String str)
MethodType mt2=MethodType.methodType(String.class, String.class);
//String.getChars(int srcBegin, int srcEnd, char[]dst, int dstBegin)
MethodType mt3=MethodType.methodType(void.class, int.class, int.class, char[].class, int.class);
//String.startsWith(String prefix)
MethodType mt4=MethodType.methodType(boolean.class, mt2);
}

1.1.2 通過靜態工廠方法genericMethodType來創建的

??除了顯式地指定回傳值和引數的型別之外,還可以生成通用的MethodType型別,即回傳值和所有引數的型別都是Object類
??方法genericMethodType有兩種多載形式:
??第一種形式只需要指明方法型別中包含的Object型別的引數個數即可,
??第二種形式可以提供一個額外的引數來說明是否在引數串列的后面添加一個Object[]型別的引數,
??在代碼清單2-32中,mt1有3個型別為Object的引數,而mt2有2個型別為Object的引數和后面的Object[]型別引數,

代碼清單2-32 生成通用MethodType型別的示例

public void generateGenericMethodTypes(){
MethodType mt1=MethodType.genericMethodType(3);
MethodType mt2=MethodType.genericMethodType(2,true);
}

1.1.2 通過靜態工廠方法fromMethodDescriptorString來創建的

??最后介紹的一個工廠方法是比較復雜的fromMethodDescriptorString,這個方法允許開發人員指定方法型別在位元組代碼中的表示形式作為創建MethodType時的引數,這個方法的復雜之處在于位元組代碼中的方法型別格式不是很好理解,
??比如代碼清單2-31中的String.getChars方法的型別在位元組代碼中的表示形式是“(II[CI)V”,不過這種格式比逐個宣告回傳值和引數型別的做法更加簡潔,適合于對Java位元組代碼格式比較熟悉的開發人員,
??在代碼清單2-33中,“(Ljava/lang/String;)Ljava/lang/String;”所表示的方法型別是回傳值和引數型別都是java.lang.String,相當于使用MethodType.methodType(String.class, String.class),
代碼清單2-33 使用方法型別在位元組代碼中的表示形式來創建MethodType

public void generateMethodTypesFromDescriptor(){
ClassLoader cl=this.getClass().getClassLoader();
String descriptor="(Ljava/lang/String;)Ljava/lang/String;";
MethodType mt1=MethodType.fromMethodDescriptorString(descriptor, cl);
}

注意:在使用fromMethodDescriptorString方法的時候,需要指定一個類加載器,該類加載器用來加載方法型別運算式中出現的Java類,如果不指定,默認使用系統類加載器,

2 對MethodType類的物件實體的修改

2.1 圍繞回傳值和引數型別的精確修改

??在通過工廠方法創建出MethodType類的物件實體之后,可以對其進行進一步修改,這些修改都圍繞回傳值和引數型別展開,所有這些修改方法都回傳另外一個新的MethodType物件,

代碼清單2-34 對MethodType中的回傳值和引數型別進行修改的示例

public void changeMethodType(){
//(int, int)String
MethodType mt=MethodType.methodType(String.class, int.class, int.class);
//(int, int, float)String
mt=mt.appendParameterTypes(float.class);
//(int, double, long, int, float)String
mt=mt.insertParameterTypes(1,double.class, long.class);
//(int, double, int, float)String
mt=mt.dropParameterTypes(2,3);
//(int, double, String, float)String
mt=mt.changeParameterType(2,String.class);
//(int, double, String, float)void
mt=mt.changeReturnType(void.class);
}

2.2 一次性對回傳值和所有引數的型別進行修改

??除了上面這幾個精確修改回傳值和引數的型別的方法之外,MethodType還有幾個可以一次性對回傳值和所有引數的型別進行處理的方法,
??代碼清單2-35給出了這幾個方法的使用示例,其中wrap和unwrap用來在基本型別及其包裝型別之間進行轉換generic方法把所有回傳值和引數型別都變成Object型別,而erase只把參考型別變成Object,并不處理基本型別,修改之后的方法型別同樣以注釋的形式給出,

代碼清單2-35 一次性修改MethodType中的回傳值和所有引數的型別的示例

public void wrapAndGeneric(){
//(int, double)Integer
MethodType mt=MethodType.methodType(Integer.class, int.class, double.class);
//(Integer, Double)Integer
MethodType wrapped=mt.wrap();
//(int, double)int
MethodType unwrapped=mt.unwrap();
//(Object, Object)Object
MethodType generic=mt.generic();
//(int, double)Object
MethodType erased=mt.erase();
}

由于每個對MethodType物件進行修改的方法的回傳值都是一個新的MethodType物件,可以很容易地通過方法級聯來簡化代碼,

3.方法句柄的呼叫

??在獲取到了一個方法句柄之后,最直接的使用方法就是呼叫它所參考的底層方法,在這點上,方法句柄的使用類似于反射API中的Method類,但是方法句柄在呼叫時所提供的靈活性是Method類中的invoke方法所不能比的,

3.1 通過invokeExact方法實作

??最直接的呼叫一個方法句柄的做法是通過invokeExact方法實作的,這個方法與直接呼叫底層方法是完全一樣的,
??invokeExact方法引數依次是作為方法接收者的物件呼叫時候的實際引數串列
比如在代碼清單2-36中,這種呼叫方式就相當于直接呼叫"Hello World".substring(1,3)
代碼清單2-36 使用invokeExact方法呼叫方法句柄

public void invokeExact()throws Throwable{
// 1.先獲取String類中substring的方法句柄.
MethodHandles.Lookup lookup=MethodHandles.lookup(); 
MethodType type=MethodType.methodType(String.class, int.class, int.class);
MethodHandle mh=lookup.findVirtual(String.class,"substring",type);
// 2.再通過invokeExact來進行呼叫,
String str=(String)mh.invokeExact("Hello World",1,3);
System.out.println(str);
}

??在這里強調一下靜態方法和一般方法之間的區別靜態方法在呼叫時不需要指定方法的接收物件的,而一般的方法則是需要的,如果方法句柄mh所參考的是java.lang.Math類中的靜態方法min,那么直接通過mh.invokeExact(3,4)就可以呼叫該方法,
??注意:invokeExact方法在呼叫的時候要求嚴格的型別匹配,方法的回傳值型別也是在考慮范圍之內的,代碼清單2-36中的方法句柄所參考的substring方法的回傳值型別是String,因此在使用invokeExact方法進行呼叫時,需要在前面加上強制型別轉換,以宣告回傳值的型別,
??如果去掉這個型別轉換,而直接賦值給一個Object型別的變數,在呼叫的時候會拋出例外,因為invokeExact會認為方法的回傳值型別是Object,如下圖所示:
invokeExact方法呼叫后回傳值型別的嚴格匹配
去掉型別轉換但是不進行賦值操作也是錯誤的,因為invokeExact會認為方法的回傳值型別是void,也不同于方法句柄要求的String型別的回傳值,
invokeExact方法呼叫后回傳值型別的嚴格匹配

3.1 通過invoke方法實作

??與invokeExact所要求的型別精確匹配不同的是,invoke方法允許更加松散的呼叫方式,它會嘗試在呼叫的時候進行回傳值和引數型別的轉換作業,這是通過MethodHandle類的asType方法來完成的,asType方法的作用是把當前的方法句柄適配到新的MethodType上,并產生一個新的方法句柄,當方法句柄在呼叫時的型別與其宣告的型別完全一致的時候,呼叫invoke等同于呼叫invokeExact;否則,invoke會先呼叫asType方法來嘗試適配到呼叫時的型別,如果適配成功,呼叫可以繼續;否則會拋出相關的例外,這種靈活的適配機制,使invoke方法成為在絕大多數情況下都應該使用的方法句柄呼叫方式,
??進行型別適配的基本規則是比對回傳值型別和每個引數的型別是否都可以相互匹配,只要回傳值型別或某個引數的型別無法完成匹配,那么整個適配程序就是失敗的,從待轉換的源型別S到目標型別T匹配成功的基本原則如下:

  • 1)可以通過Java的型別轉換來完成,一般是從子類轉換成父類,介面的實作類轉換成介面,比如從String類轉換到Object類
  • 2)可以通過基本型別的轉換來完成,只能進行型別范圍的擴大,比如從int型別轉換到long型別,
  • 3)可以通過基本型別的自動裝箱和拆箱機制來完成,比如從int型別到Integer型別,
  • 4)如果S有回傳值型別,而T的回傳值是void, S的回傳值會被丟棄,
  • 5)如果S的回傳值是void,而T的回傳值是參考型別,T的回傳值會是null,
  • 6)如果S的回傳值是void,而T的回傳值是基本型別,T的回傳值會是0,
    滿足上面規則時進行兩個方法型別之間的轉換是會成功的,

let's see how it's possible to use the invoke() with a boxed argument:

 @Test
    public void givenReplaceMethodHandle_whenInvoked_thenCorrectlyReplaced() throws Throwable {
        MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
        MethodType mt = MethodType.methodType(String.class, char.class, char.class);
        MethodHandle replaceMH = publicLookup.findVirtual(String.class, "replace", mt);
        String replacedString = (String) replaceMH.invoke("jovo", Character.valueOf('o'), 'a');
        String replacedString3 = (String) replaceMH.invoke("jovo",  'o', 'a');
        String replacedString2 = (String) replaceMH.invoke("jovo",  new Character('o'), 'a');
        String replacedString4 = (String) replaceMH.invokeExact("jovo",  'o', 'a');
        String replacedString5 = (String) replaceMH.invokeExact("jovo",  new Character('o'), 'a'); //不能使用包裝類,報錯
        assertEquals("java", replacedString);
    }

In this case, the replaceMH requires char arguments, the invoke() performs an unboxing on the Character argument before its execution.通過MethodHandle類的asType方法嘗試在呼叫的時候進行引數型別的轉換作業
invokeExact方法呼叫后回傳值型別的嚴格匹配

3.3 通過invokeWithArguments方法實作

??最后一種呼叫方式是使用invokeWithArguments,該方法在呼叫時可以指定任意多個Object型別的引數,完整的呼叫方式是首先根據傳入的實際引數的個數.

    1. 通過MethodType的genericMethodType方法得到一個回傳值和引數型別都是Object的新方法型別,
    1. 再把原始的方法句柄通過asType轉換后得到一個新的方法句柄
    1. 最后通過新方法句柄的invokeExact方法來完成呼叫

這個方法相對于invokeExact和invoke的優勢在于,它可以通過Java反射API被正常獲取和呼叫,而invokeExact和invoke不可以這樣,它可以作為反射API和方法句柄之間的橋梁,

MethodType mt = MethodType.methodType(List.class, Object[].class);

MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt);
 
List<Integer> list = (List<Integer>) asList.invokeWithArguments(1,2);
 
assertThat(Arrays.asList(1,2), is(list));

methodHandle類中的invokeWithArguments方法

    public Object invokeWithArguments(Object... arguments) throws Throwable {
        MethodType invocationType = MethodType.genericMethodType(arguments == null ? 0 : arguments.length);
        return invocationType.invokers().spreadInvoker(0).invokeExact(asType(invocationType), arguments);
    }

4.引數長度可變的方法句柄 --- 簡化方法呼叫時的語法

??在方法句柄中,所參考的底層方法中包含長度可變的引數是一種比較特殊的情況,雖然最后一個長度可變的引數實際上是一個陣列,但是仍然可以簡化方法呼叫時的語法,對于這種特殊的情況,方法句柄也提供了相關的處理能力,主要是一些轉換的方法,允許在可變長度的引數和陣列型別的引數之間互相轉換,以方便開發人員根據需求選擇最適合的呼叫語法.

4.1 MethodHandle的asVarargsCollector方法

??MethodHandle中第一個與長度可變引數相關的方法是asVarargsCollector,它的作用是把原始的方法句柄中的最后一個陣列型別的引數轉換成對應型別的可變長度引數
??如代碼清單2-37所示,方法normalMethod的最后一個引數是int型別的陣列,參考它的方法句柄在通過asVarargsCollector方法轉換之后,得到的新方法句柄在呼叫時就可以使用長度可變引數的語法格式,而不需要使用原始的陣列形式,在實際的呼叫中,int型別的引數3、4和5組成的陣列被傳入到了normalMethod的引數arg3中,
代碼清單2-37 asVarargsCollector方法的使用示例

public class Varargs {
    public void normalMethod(String arg1,int arg2,int[]arg3){
        System.out.println(arg3);  // args 
    }
    @Test
    public void asVarargsCollector()throws Throwable{
        MethodHandles.Lookup lookup=MethodHandles.lookup();
        MethodHandle mh=lookup.findVirtual(Varargs.class,"normalMethod", MethodType.methodType(void.class, String.class,
                int.class, int[].class));
        mh = mh.asVarargsCollector(int[].class);
        mh.invoke(this,"Hello",2,1,4,5,7,8);
    }
}

asVarargsCollector方法的使用

4.2 MethodHandle的asCollector方法

??第二個方法asCollector的作用與asVarargsCollector類似,不同的是該方法只會把指定數量的引數;收集到原始方法句柄所對應的底層方法的陣列型別引數中,而不像asVarargsCollector那樣可以收集任意數量的引數,
??如代碼清單2-38所示,還是以參考normalMethod的方法句柄為例,asCollector方法呼叫時的指定引數為2,即只有2個引數會被收集到整數型別陣列中,在實際的呼叫中,int型別的引數3和4組成的陣列被傳入到了normalMethod的引數args中,

代碼清單2-38 asCollector方法的使用示例

public class Varargs {
    public void normalMethod(String arg1,int arg2,int[]arg3){
        System.out.println(arg3);
    }
    @Test
    public void asCollector()throws Throwable{
        MethodHandles.Lookup lookup=MethodHandles.lookup();
        MethodHandle mh=lookup.findVirtual(Varargs.class,"normalMethod", MethodType.methodType(void.class, String.class,
                int.class, int[].class));
        mh = mh.asCollector(int[].class,2);
        mh.invoke(this,"Hello",2,1,4);
//        mh.invoke(this,"Hello",2,1,4,5,7,8); // 報錯了指定最后一個入引陣列的長度為2
    }
}

asCollector方法的使用

4.3MethodHandle的asSpreader方法

??上面的兩個方法把陣列型別引數轉換為長度可變的引數,自然還有與之對應的執行反方向轉換的方法,
??代碼清單2-39給出的asSpreader方法就把長度可變的引數轉換成陣列型別的引數,轉換之后的新方法句柄在呼叫時使用陣列作為引數,而陣列中的元素會被按順序分配給原始方法句柄中的各個引數,在實際的呼叫中,toBeSpreaded方法所接受到的引數arg2、arg3和arg4的值分別是3、4和5,

代碼清單2-39 asSpreader方法的使用示例

    public void toBeSpreaded (String arg1,int arg2,int arg3,int arg4){

        
    }

    public void asSpreader()throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandle mh = lookup.findVirtual(Varargs.class, "toBeSpreaded", MethodType.methodType(void.class, String.class,
                int.class, int.class, int.class));
        mh = mh.asSpreader(int[].class, 3);
        mh.invoke(this, "Hello", new int[]{3, 4, 5});
    }
}

4.3MethodHandle的asFixedArity方法

??最后一個方法asFixedArity是把引數長度可變的方法轉換成引數長度不變的方法,經過這樣的轉換之后,最后一個長度可變的引數實際上就變成了對應的陣列型別,在呼叫方法句柄的時候,就只能使用陣列來進行引數傳遞,
??如代碼清單2-40所示,asFixedArity會把參考引數長度可變方法varargsMethod的原始方法句柄轉換成固定長度引數的方法句柄,

代碼清單2-40 asFixedArity方法的使用示例

 public void varargsMethod(String arg1,int...args){

    }

    public void asFixedArity()throws Throwable{
        MethodHandles.Lookup lookup=MethodHandles.lookup();
        MethodHandle mh=lookup.findVirtual(Varargs.class,"varargsMethod",MethodType.methodType(void.class, String.class,
                int[].class));
        mh=mh.asFixedArity();
        mh.invoke(this,"Hello",new int[]{2,4});
    }

5.引數系結

??在前面介紹過,如果方法句柄在呼叫時參考的底層方法不是靜態的,呼叫的第一個引數應該是該方法呼叫的接收者,這個引數的值一般在呼叫時指定,也可以事先進行系結,通過MethodHandle的bindTo方法可以預先系結底層方法的呼叫接收者,在實際呼叫的時候,只需要傳入實際引數即可,不需要再指定方法的接收者,
??代碼清單2-41給出了對參考String類的length方法的方法句柄的兩種呼叫方式:

  • 第一種沒有進行系結,呼叫時需要傳入length方法的接收者;
  • 第二種方法預先系結了一個String類的物件,因此呼叫時不需要再指定,

代碼清單2-41 引數系結的基本用法

public void bindTo()throws Throwable{
MethodHandles.Lookup lookup=MethodHandles.lookup();
MethodHandle mh=lookup.findVirtual(String.class,"length",MethodType.methodType(int.class));
int len=(int)mh.invoke("Hello");//值為5
mh=mh.bindTo("Hello World");
len=(int)mh.invoke();//值為11
}

??優點:這種預先系結引數的方式的靈活性在于它允許開發人員只公開某個方法,而不公開該方法所在的物件,開發人員只需要找到對應的方法句柄,并把適合的物件系結到方法句柄上,客戶代碼就可以只獲取到方法本身,而不會知道包含此方法的物件,系結之后的方法句柄本身就可以在任何地方直接運行,

??實際上,MethodHandle的bindTo方法只是系結方法句柄的第一個引數而已,并不要求這個引數一定表示方法呼叫的接收者,對于一個MethodHandle,可以多次使用bindTo方法來為其中的多個引數系結值,代碼清單2-42給出了多次系結的一個示例,方法句柄所參考的底層方法是String類中的indexOf方法,同時為方法句柄的前兩個引數分別系結了具體的值,
代碼清單2-42 多次引數系結的示例

@Test
    public void multipleBindTo()throws Throwable{
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandle mh = lookup.findVirtual(String.class,"indexOf",MethodType.methodType(
        int.class, String.class, int.class));
        mh = mh.bindTo("Hello").bindTo("l");
        int index = "Hello".indexOf('l',2); 
        assertEquals(index, mh.invoke(2)); // true

}

??需要注意的是,在進行引數系結的時候,只能對參考型別的引數進行系結,無法為int和float這樣的基本型別系結值,對于包含基本型別引數的方法句柄,可以先使用wrap方法把方法型別中的基本型別轉換成對應的包裝類,再通過方法句柄的asType將其轉換成新的句柄,轉換之后的新句柄就可以通過bindTo來進行系結,如代碼清單2-43所示,

代碼清單2-43 基本型別引數的系結方式

    @Test
    public void multipleBindTo()throws Throwable{
        MethodHandles.Lookup lookup = MethodHandles.lookup();
//        MethodHandle mh = lookup.findVirtual(String.class,"indexOf",MethodType.methodType(
//        int.class, String.class, int.class));
//        mh = mh.bindTo("Hello").bindTo("l");
//        int index = "Hello".indexOf('l',2);
//        assertEquals(index, mh.invoke(2));
        MethodHandle mh=lookup.findVirtual(String.class,"substring",MethodType.methodType(String.class, int.class,
        int.class));
        mh=mh.asType(mh.type().wrap());
        mh=mh.bindTo("Hello World").bindTo(3);
        String  str = "Hello World".substring(3,5);
        System.out.println(mh.invoke(5));//值為“lo”
        assertEquals(str, mh.invoke(5));
    }

參考:
?? https://www.baeldung.com/java-method-handles

參考資料:《java程式員修煉之道》、《深入理解java7核心技術與最佳實踐》

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

標籤:Java

上一篇:這 30 個常用的 Maven 命令你必須熟悉!

下一篇:Spring Boot加密組態檔特殊內容

標籤雲
其他(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