主頁 > 後端開發 > 深入探究JVM之類加載與雙親委派機制

深入探究JVM之類加載與雙親委派機制

2020-09-24 05:31:54 後端開發

@

目錄
  • 前言
  • 類的生命周期
    • 加載
    • 驗證
    • 準備
    • 決議
    • 初始化
      • 案例一
      • 案例二
      • 案例三
      • 案例四
  • 類加載器
    • 類加載器和雙親委派模型
    • 破壞雙親委派模型
      • 第一次
      • SPI
      • Tomcat
      • OSGI
  • 總結

前言

前面學習了虛擬機的記憶體結構、物件的分配和創建,但物件所對應的類是怎么加載到虛擬機中來的呢?加載程序中需要做些什么?什么是雙親委派機制以及為什么要打破雙親委派機制?

類的生命周期

在這里插入圖片描述
類的生命周期包含了如上的7個階段,其中驗證準備決議統稱為連接 ,類的加載主要是前五個階段,每個階段基本上保持如上順序開始(僅僅是開始,實際上執行是交叉混合的),只有決議階段不一定,在初始化后也有可能才開始執行決議,這是為了支持動態語言,

加載

加載就是將位元組碼的二進制流轉化為方法區的運行時資料結構,并生成類所物件的Class物件,位元組碼二進制流可以是我們編譯后的class檔案,也可以從網路中獲取,或者運行時動態生成(動態代理)等等,
那什么時候會觸發類加載呢?這個在虛擬機規范中沒有明確定義,只是規定了何時需要執行初始化(稍后詳細分析),

驗證

這個階段很好理解,就是進行必要的校驗,確保加載到記憶體中的位元組碼是符合要求的,主要包含以下四個校驗步驟(了解即可):

  • 檔案格式校驗:這個階段要校驗的東西非常多,主要的有下面這些(實際上遠遠不止)
    • 是否以魔數0xCAFEBABE開頭,
    • 主、次版本號是否在當前Java虛擬機接受范圍之內,
    • 常量池的常量中是否有不被支持的常量型別(檢查常量tag標志),
    • 指向常量的各種索引值中是否有指向不存在的常量或不符合型別的常量,
    • CONSTANT_Utf8_info型的常量中是否有不符合UTF-8編碼的資料,
    • Class檔案中各個部分及檔案本身是否有被洗掉的或附加的其他資訊,
    • ,,,,,,
  • 元資料校驗:對位元組碼描述資訊進行語意分析,
    • 這個類是否有父類(除了java.lang.Object之外,所有的類都應當有父類),
    • 這個類的父類是否繼承了不允許被繼承的類(被final修飾的類),
    • 如果這個類不是抽象類,是否實作了其父類或介面之中要求實作的所有方法,
    • 類中的欄位、方法是否與父類產生矛盾(例如覆寫了父類的final欄位,或者出現不符合規則的方法多載,例如方法引數都一致,但回傳值型別卻不同等),
    • ,,,,,,
  • 位元組碼校驗:確保程式沒有語法和邏輯錯誤,這是整個驗證階段最復雜的一個步驟,
    • 保證任意時刻運算元堆疊的資料型別與指令代碼序列都能配合作業,例如不會出現類似于“在操作堆疊放置了一個 int 型別的資料,使用時卻按 long 型別來加載入本地變數表中”這樣的情況,
    • 保證任何跳轉指令都不會跳轉到方法體以外的位元組碼指令上,
    • 保證方法體中的型別轉換總是有效的,例如可以把-個子類物件賦值給父類資料型別,這是安全的,但是把父類物件賦值給子類資料型別,甚至把物件賦值給與它毫無繼承關系、完全不相干的一個資料型別,則是危險和不合法的,
    • ,,,,,,
  • 符號參考驗證:這個階段發生在符號參考轉為直接參考的時候,即實際上是在決議階段中進行的,
    • 符號參考中通過字串描述的全限定名是否能找到對應的類,
    • 在指定類中是否存在符合方法的欄位描述符及簡單名稱所描述的方法和欄位,
    • 符號參考中的類、欄位、方法的可訪問性( private、 protected. public、 ),
    • 是否可被當前類訪問,
    • ,,,,,,

準備

該階段是為類變數(static)分配記憶體并設定零值,即類只要經過準備階段其中的靜態變數就是可使用的了,但此時類變數的值還不是我們想要的值,需要經過初始化階段才會將我們希望的值賦值給對應的靜態變數,

決議

決議就是將常量池中的符號參考替換為直接參考的程序,符號參考就是一個代號,比如我們的名字,而這里可以理解為就是類的完全限定名直接參考則是對應的具體的人、物,這里就是指目標的記憶體地址,為什么需要符號參考呢?因為類在加載到記憶體之前還沒有分配記憶體地址,因此必然需要一個東西指代它,這個階段包含了類或介面的決議欄位決議類方法決議介面方法決議,在決議的程序中可能會拋出以下例外:

  • java.lang.NoSuchFieldError:找不到欄位
  • java.lang.IllegalAccessError:不具有訪問權限
  • java.lang.NoSuchMethodError:找不到方法

初始化

這是類加載程序中的最后一個步驟,主要是收集類的靜態變數的賦值動作static塊中的陳述句合成<cinit>方法,通過該方法根據我們的意愿為靜態變數賦值以及執行static塊,該方法會被加鎖,確保多執行緒情況下只有一個執行緒能初始化成功,利用該特性可以實作單例模式,虛擬機規定了有且只有遇到以下情況時必須先確保對應類的初始化完成(加載、準備必然在此之前):

  • 遇到new、getstatic、putstatic或invokestatic這四條位元組碼指令時,能夠生成這四條指令的典型Java代碼場景有:
    • 使用new關鍵字實體化物件的時候,
    • 讀取或設定一個型別的靜態欄位(被final修飾、已在編譯期把結果放入常量池的靜態欄位除外)的時候,
    • 呼叫一個型別的靜態方法的時候,
  • 反射呼叫類時,
  • 當初始化類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化,
  • 當虛擬機啟動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類,
  • 當使用JDK 7新加入的動態語言支持時,如果一個java.lang.invoke.MethodHandle實體最后的決議結果為REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四種型別的方法句柄,并且這個方法句柄對應的類沒有進行過初始化,則需要先觸發其初始化,
  • 當一個介面中定義了JDK 8新加入的默認方法(被default關鍵字修飾的介面方法)時,如果有這個介面的實作類發生了初始化,那該介面要在其之前被初始化,

下面分析幾個案例代碼,讀者們可以先思考后再運行代碼看看和自己想的是否一樣,

案例一

先定義如下兩個類:

public class SuperClazz {
	static 	{
		System.out.println("SuperClass init!");
	}
	public static int value=https://www.cnblogs.com/yewy/p/123;
	public static final String HELLOWORLD="hello world";
	public static final int WHAT = value;
}

public class SubClaszz extends SuperClazz {
	static{
		System.out.println("SubClass init!");
	}

}

然后進行下面的呼叫:

public class Initialization {
	public static void main(String[]args){
		Initialization initialization = new Initialization();
		initialization.M1();
	}
	
	public void M1(){
		System.out.println(SubClaszz.value);
	}
}

第一個案例是通過子類去參考父類中的靜態變數,兩個類都會加載和初始化么?列印結果看看:

SuperClass init!
123

可以看到只有父類初始化了,那么父類必然是加載了的,問題就在于子類有沒有被加載呢?可以加上引數:-XX:+TraceClassLoading再執行(該引數的作用就是列印被加載了的類),可以看到子類是被加載了的,所以通過子類參考父類靜態變數,父子類都會被加載,但只有父類會進行初始化
為什么呢?反編譯后可以看到生成了如下指令:

0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic     #6                  // Field ex7/init/SubClaszz.value:I
6: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
9: return

關鍵就是getstatic指令就會觸發類的初始化,但是為什么子類不會初始化呢?因為這個變數是來自于父類的,為了提高效率,所以虛擬機進行了優化,這種情況只需要初始化父類就行了,

案例二

呼叫下面的方法:

	public void M2(){
		SubClaszz[]sca = new SubClaszz[10];
	}

執行后可以發現,使用陣列,不會觸發初始化,但父子類都會被加載

案例三

	public void M3(){
		System.out.println(SuperClazz.HELLOWORLD);
	}

參考常量不會觸發類的加載和初始化,因為常量在編譯后就已經存在當前class的常量池,

案例四

	public void M4(){
		System.out.println(SubClaszz.WHAT);
	}

通過常量去參考其它的靜態變數會發生什么呢?這個和案例一結果是一樣的,

類加載器

類加載器和雙親委派模型

在我們平時開發中,確定一個類需要通過完全限定名,而不能簡單的通過名字,因為在不同的路徑下我們是可以定義同名的類的,那么在虛擬機中又是怎么區分類的呢?在虛擬機中需要類加載器+完全限定名一起來指定一個類的唯一性,即相同限定名的類若由兩個不同的類加載器加載,那虛擬機就不會把它們當做一個類,從這里我們可以看出類加載器一定是有多個的,那么不同的類加載器是怎么組織的?它們又分別需要加載哪些類呢?
在這里插入圖片描述
從虛擬角度看,只有兩種型別的類加載器:啟動類加載器(BootstrapClassLoader)非啟動類加載器,前者是C++實作,屬于虛擬機的一部分,后者則是由Java實作的,獨立于虛擬機的外部,并且全部繼承自抽象類java.lang.ClassLoader,
但從Java本身來看,一直保持著三層類加載器雙親委派的結構,當然除了Java本身提供的三層類加載器,我們還可以自定義實作類加載器,如上圖,上面三個就是原生的類加載器,每一個都是下一個類加載器的父加載器,注意這里都是采用組合而非繼承,當開始加載類時,首先交給父加載器加載,父加載器加載了子加載器就不用再加載了,而若是父加載器加載不了,就會交給子加載器加載,這就是雙親委派機制,這就好比作業中遇到了無法處理的事,你會去請示直接領導,直接領導處理不了,再找上層領導,然后上層領導覺得這是個小事,不用他親自動手,就讓你的直接領導去做,接著他又交給你去做等等,下面來看看每個類加載器的具體作用:

  • BootstrapClassLoader:啟動類加載器,顧名思義,這個類加載器主要負責加載JDK lib包,以及-Xbootclasspath引數指定的目錄,并且虛擬機對檔案名進行了限定,也就是說即使我們自己寫個jar放入到上述目錄,也不會被加載,由于該類加載器是C++使用,所以我們的Java程式中無法直接參考,呼叫java.lang.ClassLoader.getClassLoader()方法時默認回傳的是null,
  • ExtClassLoader:擴展類加載器,主要負責加載JDK lib/ext包,以及被系統變數java.ext.dirs指向的所有類別庫,這個類別庫可以存放我們自己寫的通用jar,
  • AppClassLoader:應用程式類加載器,負責加載用戶classpath上的所有類,它是java.lang.ClassLoader.getSystemClassLoader()的回傳值,也是我們程式的默認類加載器(如果我們沒有自定義類加載器的話),

通過這三個類加載以及雙親委派機制,一個顯而易見的好處就是,不同的類隨它的類加載器天然具有了加載優先級,像Object、String等等這些核心類別庫自然就會在我們的應用程式類之前被加載,使得程式更安全,不會出現錯誤,Spring的父子容器也是這樣的一個設計,通過下面這段代碼可以看到每個類所對應的類加載器:

public class ClassLoader {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader()); //啟動類加載器
        System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());//拓展類加載器
        System.out.println(ClassLoader.class.getClassLoader());//應用程式類加載器
    }
}

輸出:

null
sun.misc.Launcher$ExtClassLoader@4b67cf4d
sun.misc.Launcher$AppClassLoader@14dad5dc

破壞雙親委派模型

剛剛我舉了作業中的一個例子來說明雙親委派機制,但現實中我們不需要事事都去請示領導,同樣類加載器也不是完全遵循雙親委派機制,在必要的時候是可以打破這個規則的,下面列舉四個破壞的情況,在此之前我們需要先了解下雙親 委派的代碼實作原理,在java.lang.ClassLoader類中有一個loadClass以及findClass方法:

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

從上面可以看到首先是呼叫parent去加載類,沒有加載到才呼叫自身的findClass方法去加載,也就是說用戶在實作自定義類加載器的時候需要覆寫的是fiindClass而不是loadClass,這樣才能滿足雙親委派模型
下面具體來看看破壞雙親委派的幾個場景,

第一次

第一次破壞是在雙親委派模型出現之前, 因為該模型是在JDK1.2之后才引入的,那么在此之前,抽象類java.lang.ClassLoader就已經存在了,用戶自定義的類加載器都會去覆寫該類中的loadClass方法,所以雙親委派模型出現后,就無法避免用戶覆寫該方法,因此新增了findClass引導用戶去覆寫該方法實作自己的類加載邏輯,

SPI

第二次破壞是由于這個模型本身缺陷導致的,因為該模型保證了類的加載優先級,但是有些介面是Java定義在核心類別庫中,但具體的服務實作是由用戶提供的,這時候就不得不破壞該模型才能實作,典型的就是Java中的SPI機制(對SPI不了解的讀者可以翻閱我之前的文章或是其它資料,這里不進行闡述),J
DBC的驅動加載就是SPI實作的,所以直接看到java.sql.DriverManager類,該類中有一個靜態初始化塊:

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

    private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

主要看ServiceLoader.load方法,這個就是通過SPI去加載我們引入java.sql.Driver實作類(比如引入mysql的驅動包就是com.mysql.cj.jdbc.Driver):

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

這個方法主要是從當前執行緒中獲取類加載器,然后通過這個類加載器去加載驅動實作類(這個叫執行緒背景關系類加載器,我們也可以使用這個技巧去打破雙親委派),那這里會獲取到哪一個類加載器呢?具體的設定是在sun.misc.Launcher類的構造器中:

    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

可以看到設定的就是AppClassLoader,你可能會有點疑惑,這個類加載器加載類的時候不也是先呼叫父類加載器加載么,怎么就打破雙親委派了呢?其實打破雙親委派指的就是類的層次結構,延伸意思就是類的加載優先級,這里本應該是在加載核心類別庫的時候卻提前將我們應用程式中的類別庫給加載到虛擬機中來了,

Tomcat

在這里插入圖片描述
上圖是Tomcat類加載的類圖,前面三個不用說,CommonClassLoaderCatalinaClassLoaderSharedClassLoaderWebAppClassLoaderJspClassLoader則是Tomcat自己實作的類加載器,分別加載common包server包shared包WebApp/WEB-INF/lib包以及JSP檔案,前面三個在tomcat 6之后已經合并到根目錄下的lib目錄下,而WebAppClassLoader則是每一個應用程式對應一個,JspClassLoader是每一個JSP檔案都會對應一個,并且這兩個類加載器都沒有父類加載器,這也就違背了雙親委派模型,
為什么每個應用程式需要單獨的WebAppClassLoader實體?因為每個應用程式需要彼此隔離,假如在兩個應用中定義了一樣的類(完全限定名),如果遵循雙親委派那就只會存在一份了,另外不同的應用還有可能依賴同一個類別庫的不同版本,這也需要隔離,所以每一個應用程式都會對應一個WebAppClassLoader,它們共享的類別庫可以讓SharedClassLoader加載,另外這些類加載加載的類對Tomcat本身來說也是隔離的(CatalinaClassLoader加載的),
為什么每個JSP檔案需要對應單獨的一個JspClassLoader實體?這是由于JSP是支持運行時修改的,修改后會丟棄掉之前編譯生成的class,并重新生成一個JspClassLoader實體去加載新的class,
以上就是Tomcat為什么要打破雙親委派模型的原因,

OSGI

OSGI是用于實作模塊熱部署,像Eclipse的插件系統就是利用OSGI實作的,這個技術非常復雜同時使用的也越來越少了,感興趣的讀者可自行查閱資料學習,這里不再進行闡述,

總結

類加載的程序讓我們了解到一個類是如何被加載到記憶體中,需要經過哪些階段;而類加載器和雙親委派模型則是告訴我們應該怎么去加載類、類的加載優先級是怎樣的,其中的設計思想我們也可以學習借鑒;最后需要深刻理解的是為什么需要打破雙親委派,在遇到相應的場景時應該怎么做,

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

標籤:Java

上一篇:牛逼!OpenJDK 原始碼要遷移到 GitHub 了!

下一篇:這16道Redis最常見面試問題,你能回答上來幾個?

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