主頁 > 後端開發 > Python 的垃圾回識訓制【譯】

Python 的垃圾回識訓制【譯】

2023-01-30 07:03:46 後端開發

幾乎所有的高級編程語言都有自己的垃圾回識訓制,開發者不需要關注記憶體的申請與釋放,Python 也不例外,Python 官方團隊的文章 https://devguide.python.org/internals/garbage-collector 詳細介紹了 Python 中的垃圾回收演算法,本文是這篇文章的譯文,

摘要

CPython 中主要的垃圾回收演算法是參考計數,參考計數顧名思義就是統計每個物件有多少個參考,每個參考可能來自另外一個物件,或者一個全域(或靜態)C 變數,或者 C 語言函式中的區域變數,當一個變數的參考計數變成 0,那么這個物件就會被釋放,如果被釋放的物件包含對其他物件的參考,那么其他物件的參考計數就會相應地減 1,如果其他物件的參考計數在減 1 之后變成 0,這些物件也會級聯地被釋放掉,在 Python 代碼中可以通過 sys.getrefcount 函式獲取一個物件的參考計數(函式的回傳值會比實際的參考計數多 1,因為函式本身也包含一個對目標物件的參考),

>>> x = object()
>>> sys.getrefcount(x)
2
>>> y = x
>>> sys.getrefcount(x)
3
>>> del y
>>> sys.getrefcount(x)
2

參考計數最大的問題就是不能處理回圈參考,下面是一個回圈參考的例子:

>>> container = []
>>> container.append(container)
>>> sys.getrefcount(container)
3
>>> del container

在這個例子中,container 物件包含一個對自己的參考,所以即使我們移除了一個參考(變數 containercontainer 物件的參考計數也不會變成 0,因為 container 物件內部仍然有一個對自身的參考,因此如果僅僅通過簡單的參考計數,container 物件永遠不會被釋放,鑒于此,當物件不可達的時候(譯注:當代碼中沒有對實際物件的參考時),我們需要額外的機制來清除這些不可達物件間的回圈參考,這個額外的機制就是回圈垃圾收集器,通常簡稱為垃圾收集器(Garbage Collector,GC),雖然參考計數也是一種垃圾回收演算法,

記憶體布局和物件結構

一般的 Python 物件在 C 語言中的結構體表示如下

object -----> +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ \
              |                    ob_refcnt                  | |
              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | PyObject_HEAD
              |                    *ob_type                   | |
              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /
              |                      ...                      |

為了支持垃圾回收,在一般 Python 物件的記憶體布局前面加了一些額外的資訊

              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ \
              |                    *_gc_next                  | |
              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | PyGC_Head
              |                    *_gc_prev                  | |
object -----> +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /
              |                    ob_refcnt                  | \
              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | PyObject_HEAD
              |                    *ob_type                   | |
              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /
              |                      ...                      |

通過這種方式,object 可以被看做一般的 Python 物件,當需要垃圾回收相關的資訊時可以通過簡單的型別轉換來訪問前面的欄位:((PyGC_Head *)(the_object)-1)

在后面的章節 優化:通過復用欄位來節省記憶體 會介紹這兩個欄位通常用來串聯起被垃圾收集器管理的物件構成的雙向鏈表( 每個鏈表對應垃圾回收中的一個分代,更多細節在優化:分代回收 中有介紹),但是在不需要鏈表結構的時候這兩個欄位會被用作其他功能來減少記憶體的使用,

使用雙向鏈表是因為其能高效地支持垃圾回收對鏈表的一些高頻操作,所有被垃圾收集器管理的物件被劃分成一些不相交的集合,每個集合都在各自的雙向鏈表中,不同的集合表示不同的分代,物件所處的分代反映了其在垃圾回收中存活了多久,每次垃圾回收的時候,每個分代中的物件會進一步劃分成可達物件和不可達物件,雙向鏈表的一些操作比如移動物件、添加物件、完全洗掉一個物件(垃圾收集器管理的物件一般情況下會在兩次垃圾回收之間通過參考計數系統回收)、合并鏈表等,都會在常量時間復雜度完成,并且雙向鏈表支持在遍歷的時候添加和洗掉物件,垃圾收集器在運行的時候這也是一個非常頻繁的操作,

為了支持物件的垃圾回收,Python 的 C 介面提供了一些 API 來分配、釋放、初始化、添加和移除被垃圾收集器維護的物件,這些 API 的詳情參見 https://docs.python.org/3/c-api/gcsupport.html,

除了上面的物件結構之外,對于支持垃圾回收的物件的型別物件必須要在 tp_flags 欄位中設定 Py_TPFLAGS_HAVE_GC 標記,并且實作 tp_traverse 句柄,另外這些型別物件還要實作 tp_clear 句柄,除非能夠證明僅僅通過該型別的物件不會形成回圈參考或者支持垃圾回收的物件是不可變型別,

回圈參考的識別

CPython 中識別回圈參考的演算法在 gc 模塊中實作,垃圾收集器只關注清除容器型別的物件(也就是那些能夠包含對其他物件的參考的物件),比如陣列、字典、串列、自定義類實體和擴展模塊中的類等等,雖然回圈參考并不常見, 但是 CPython 解釋器自身也會由于一些內部參考的存在形成回圈參考,下面是一些典型場景

  • 例外物件 exception 會包含堆疊跟蹤物件 traceback,堆疊跟蹤物件包含堆疊幀的串列,這些堆疊幀最終又會包含例外物件,
  • 模塊級別的函式會參考模塊的字典 dict(用于決議全域變數),模塊字典反過來又會包含模塊級別的函式,
  • 類的實體物件會參考其所屬的類物件,類物件會參考其所在的模塊,模塊會包含模塊內的所有物件(可能還會包含其他的模塊)從而最侄訓參考到最初的類實體物件,
  • 當要表示圖這類資料結構的時候,很容易產生對自身的參考,

如果想要正確釋放不可達的物件,第一步就是要識別出不可達物件,在識別回圈參考的函式中維護了兩個雙向鏈表:一個鏈表包含所有待掃描的物件,另一個鏈表包含暫時不可達的物件,

為了理解演算法的原理,我們看一下下面的示例,其中 A 參考了一個回圈鏈表,另外還有一個不可達的自我參考的物件:

>>> import gc
>>> class Link:
...     def __init__(self, next_link=None):
...         self.next_link = next_link

>>> link_3 = Link()
>>> link_2 = Link(link_3)
>>> link_1 = Link(link_2)
>>> link_3.next_link = link_1
>>> A = link_1
>>> del link_1, link_2, link_3

>>> link_4 = Link()
>>> link_4.next_link = link_4
>>> del link_4

# 回收不可達的 Link 物件 (和它的字典 __dict__),
>>> gc.collect()
2

當垃圾收集器開始作業的時候,會將所有要掃描的容器物件放在第一個鏈表中,這樣做是為了將所有不可達物件移除,因為正常情況下大多數物件都是可達的,所以移除不可達物件會涉及更少的指標操作,因此更高效,

演算法開始的時候會為所有支持垃圾回收的物件另外初始化一個參考計數欄位(下圖中的 gc_ref),初始值設定為物件實際的參考計數,因為演算法在識別回圈參考的計算中會修改參考計數,通過使用一個另外的欄位 gc_ref 解釋器就不會修改物件真正的參考計數,

垃圾收集器會遍歷第一個串列中的所有容器物件,每遍歷一個物件都會將其所參考的所有的其他物件的 gc_ref 欄位減 1,為了找到容器物件所參考的其他物件需要呼叫容器類的 tp_traverse 方法(通過 C API 實作或者由超類繼承),在遍歷完之后,只有那些被外部變數參考的物件的 gc_ref 的值才會大于 0,

需要注意的是即使 gc_ref == 0 也不能說明物件就是不可達的,因為被外部變數參考的物件(gc_ref > 0)仍然可能參考它們,比如在我們的例子中,link_2 物件在第一次遍歷之后 gc_ref == 0,但是 link_1 物件仍然會參考 link_2,而 link_1 是從外部可達的,為了找到那些真正不可達的物件,垃圾收集器需要重新遍歷容器物件,這次遍歷的時候會將 gc_ref == 0 的物件標記為暫時不可達并且移動到暫時不可達的鏈表中,下圖描述了垃圾收集器在處理了 link_3link_4 物件但是還沒處理 link_1link_2 物件時的狀態,

垃圾收集器接下來會處理 link_1 物件,由于 gc_ref == 1,垃圾收集器不會對其做特殊處理因為知道其可達(并且已經在可達物件鏈表中),

當垃圾收集器遇到可達物件時(gc_ref > 0),會通過 tp_traverse 找到其所參考的其他物件,并且將這些物件移動到可達物件鏈表(它們最初所在的鏈表)的末尾,同時設定 gc_ref 欄位為 1,link_2link_3 物件就會被這樣處理,因為它們都被 link_1 參考,在上圖所示狀態之后,垃圾收集器會檢查被 link_1 參考的物件從而知道 link_3 是可達的,所以將其移回到原來的鏈表中并且設定 gc_ref 欄位值為 1,那么垃圾收集器下次遇到 link_3 的時候就知道它是可達的,為了避免重復處理同一個物件,垃圾收集器在處理被可達物件參考的物件的時候會標記它們已經被訪問過(通過清除 PREV_MASK_COLLECTING 標記),

需要注意的是那些開始被標記為暫時性不可達后來又被移回到可達物件鏈表的物件,會再次被垃圾收集器訪問到,因為按照演算法邏輯現在被這些物件參考的其他物件也要被處理,第二次遍歷實際上是對這些物件間參考關系構成的圖的廣度優先搜索,在第二遍遍歷結束后,垃圾收集器就可以確定現在還留在暫時性不可達物件鏈表中的物件是真的不可達,因此可以被回收掉,

值得注意的是,整個演算法中沒有遞回操作,也不需要相對于物件數量、指標數量或參考鏈長度的線性記憶體空間,除了臨時的 C 變數占用 O(1) 的常量空間外,物件本身的欄位都已經包含了演算法需要的所有資料,

為什么選擇移動不可達的物件

因為大多數物件都是可達物件,因此移動不可達物件看起來很合理,但是真正的原因并非想象的那么簡單,

假設我們依次創建了 A、B 和 C 三個物件,那么它們在第一代(譯注:這里的第一代指的是分代回收)中的順序就是創建順序,如果 B 參考 A,C 參考 B,并且 C 有外部參考,那么在垃圾回收演算法的第一輪遍歷之后 A、B 和 C 的 gc_ref 值分別為 0、0 和 1,因為只有 C 是外部可達物件,

在演算法的第二輪遍歷中,會先訪問 A,因為 gc_ref 為 0 會被移動到暫時性不可達物件鏈表中,B 也一樣,當訪問到 C 的時候會將 B 移回到可達物件鏈表中,當再次訪問 B 的時候 A 也會被移回到可達物件鏈表中,

本可以不用移動,A 和 B 卻來回移動了兩次,為什么要這么做呢?如果演算法直接移動可達物件的話,那么只用將 A、B 和 C 分別移動一次即可,這么做的關鍵是在垃圾回收結束的時候這幾個物件在鏈表中的順序依次為 C、B 和 A,與它們最初的創建順序相反,在后續的垃圾回收中,它們不再需要做任何移動,因為大多數物件之間都沒有回圈參考,這樣做只會在第一次垃圾回收的時候開銷比較大,在后續的垃圾回收中能節省很多移動的開銷,

銷毀不可達物件

一旦垃圾收集器確定了最終的不可達物件串列,就開始銷毀這些物件,銷毀的大體程序如下

  1. 處理并且清除弱參考(如果有的話),如果要銷毀的不可達物件有弱參考的回呼,那么需要處理回呼函式,這個處理程序需要特別小心,因為一個小小的錯誤就可能讓狀態不一致的物件被復活或者被回呼函式所呼叫的 Python 函式參考,如果弱參考物件本身也是不可達的(弱參考和其參考的物件在不可達的回圈參考中),那么這個弱參考物件需要馬上被清理并且不用呼叫回呼函式,如果不馬上清理的話,那么在后來呼叫 tp_clear 的時候會造成嚴重后果,當弱參考和其參考的物件都不可達的時候,那么兩者都會被銷毀,因此可以先銷毀弱參考,這個時候其參考的物件還存在,所以可以忽略弱參考的回呼,
  2. 如果物件有老版本的終結器(tp_del)需要將其移到 gc.garbage 串列中,
  3. 呼叫不可達物件的終結器(tp_finalize函式)并且標記這些物件已終結,避免物件被復活后或者在其他物件的終結器已經移除該物件的情況下重復呼叫 tp_finalize
  4. 處理被復活的物件,如果有些不可達物件在上一步被復活,垃圾收集器需要重新計算出最終的不可達物件,
  5. 對于每個最終的不可達物件,呼叫 tp_clear 來打破回圈參考使每個物件的參考計數都變成 0,從而觸發基于參考計數的銷毀邏輯,

優化:分代回收

為了避免每次垃圾回收的時候耗時太久,垃圾收集器使用了一個常用的優化:分代回收,分代回收有個前提假設,認為大多數物件的生命周期都很短,會在創建后很快就被回收,這個假設與現實中很多 Python 程式的情況一致,因為很多臨時物件會被很快地創建和銷毀,存活越久的物件越不容易因為不可達而被回收,

為了充分利用這一點,所有容器物件都會被分成三代中的某一代,每個新建的容器物件都處于第一代(generation 0),上面描述的垃圾回收演算法只在某一個具體的分代中進行,那些沒有被回收的物件會進入下一代(generation 1),這一代中的物件相對于上一代執行垃圾回收的次數會更少,如果物件在新一代中仍然沒有被回收就會移動到最后一代(generation 2),在最后一代中執行垃圾回收的次數是最少的,

垃圾收集器會記錄在上次垃圾回收之后新增物件與銷毀物件數量之差,也就是凈新增物件數量,如果超過了 threshold_0 垃圾收集器就會執行,最初的時候垃圾回收只在 generation 0 中執行,如果 generation 1 在上次執行垃圾回收之后, generation 0 中執行垃圾回收的次數超過了 threshold_1 那么就會再次在 generation 1 中執行垃圾回收,對于 generation 2 的處理稍微復雜點,在第三代中的垃圾回收 中單獨介紹,前面說的閾值 threshold_0threshold_1 可以通過函式 gc.get_threshold 查看(譯注:原文這里描述有誤,我在 issue https://github.com/python/devguide/issues/1027 中提出并被作者采納):

>>> import gc
>>> gc.get_threshold()
(700, 10, 10)

每個分代中的物件可以通過函式 gc.get_objects(generation=NUM) 查看,另外可以通過呼叫函式 gc.collect(generation=NUM) 指定在哪個分代中執行垃圾回收,

>>> import gc
>>> class MyObj:
...     pass
...

# 為了更容易觀察年輕代中的物件需要將第一代和第二代中的物件都移動到第三代
>>> gc.collect()
0 # 譯注:不同版本執行的時候的結果不一樣

# 創建回圈參考
>>> x = MyObj()
>>> x.self = x

# x 最初在第一代中
>>> gc.get_objects(generation=0)
[..., <__main__.MyObj object at 0x7fbcc12a3400>, ...]

# 在第一代中執行垃圾回收之后,x 就移動到了第二代
>>> gc.collect(generation=0)
0
>>> gc.get_objects(generation=0)
[] # 譯注:在互動模式下,每次鍵入的代碼都會經過編譯再執行,所以輸出中還有編譯程序生成的一些被垃圾收集器管理的物件
>>> gc.get_objects(generation=1)
[..., <__main__.MyObj object at 0x7fbcc12a3400>, ...]

第三代中的垃圾回收

在上面提到的各種閾值的基礎之上,只有當比率 long_lived_pending / long_lived_total 的值高于一個給定值(硬編碼為 25%)的時候,才會在第三代中進行一次全量回收,因為盡管對于非全量的回收(比如創建很多被垃圾收集器管理的物件并且都放進一個串列中,那么垃圾回收的時間復雜度不是線性的,而是 O(N2),(譯注:這里的時間復雜度不是上面說的一次垃圾回收的時間與分代中物件數量的關系,而是指在創建很多物件到串列中這種程式模式下,所有垃圾回收的總耗時與創建的物件數量的關系,詳情見 https://mail.python.org/pipermail/python-dev/2008-June/080579.html)),如果使用上面的比率,就會變成攤還之后的線性復雜度(這種做法可以總結為:盡管隨著創建的物件越來越多全量垃圾回識訓變得越來越慢,但是全量垃圾回收的次數也會變得越來越少),

優化:通過復用欄位來節省記憶體

為了節省記憶體,支持垃圾回收的物件中的用于鏈表的兩個指標也會被用作其他用途,這種常見的優化叫做胖指標或標記指標:在指標中存盤額外的資料,能夠這樣做也是利用了記憶體尋址的特性,大多數架構會將資料的記憶體同資料的大小對齊,通常是一個字或多個字對齊,對齊之后就會使得指標的最低幾位不會被使用,而這些低位就可以用作標記或存盤其他資訊 —— 通常作為位域(每一位都是一個獨立的標記)—— 只要程式在使用指標尋址前屏蔽掉這些位域即可,例如在 32 位架構上,一個字大小是 32 位 4 位元組,所以字對齊的地址都是 4 的倍數,也就是以 00 結尾,因此最低 2 位可以用作他用;類似的在 64 位結構上,一個字大小是 64 位 8 位元組,字對齊的地址以 000 結尾,最低 3 位可以用來保存其他資料,

CPython 的垃圾收集器的實作就使用了記憶體布局和物件結構 中描述的 PyGC_Head 結構體中的兩個胖指標:

  • _gc_prev 欄位正常情況下用于指向雙向鏈表中的前一個元素,低 2 位會用來保存 PREV_MASK_COLLECTING_PyGC_PREV_MASK_FINALIZED 標記,在沒有進行垃圾回收的時候只有表示物件是否被釋放的標記 _PyGC_PREV_MASK_FINALIZED 會被使用,在垃圾回收期間,_gc_prev 會被臨時用來保存參考計數 gc_ref,在此期間垃圾收集器維護的物件鏈表只能當做單鏈表使用,直到 _gc_prev 恢復原來的值,
  • _gc_next 欄位用于指向雙向鏈表中的下一個元素,垃圾回收演算法在檢測回圈參考時,它的最低位會用來保存表示物件是否暫時性不可達的標記 NEXT_MASK_UNREACHABLE,垃圾回收演算法中使用雙向鏈表使得大多數操作都能在常量時間復雜度內完成,但是無法高效地判斷一個物件是在可達鏈表中還是在暫時性不可達鏈表中,由于有標記 NEXT_MASK_UNREACHABLE 的存在,當需要判斷物件在哪個鏈表中的時候只用檢查該標記即可,

注意事項

因為胖指標或標記指標保存了其他資料,所以不能直接用來尋址,必須在清除這些其他資料之后才能得到真正的地址,尤其需要注意那些直接操作鏈表的函式,因為這些函式經常會假設鏈表中的指標狀態一致,

優化:延遲管理容器物件

有些容器物件不會產生回圈參考,所以垃圾收集器沒必要管理它們,解除對這些物件的管理會提高垃圾收集器的性能,但是判斷一個物件是否可以解除管理也是有成本的,因此必須要權衡一下成本和由此給垃圾收集器帶來的收益,有兩個可能的時機可以解除對容器物件的管理:

  1. 容器物件創建的時候
  2. 垃圾回收的時候

大的原則是原子型別不需要被垃圾收集器管理,非原子型別(容器、用戶自定義物件等)需要,也有一些針對特定型別的優化避免垃圾收集器在垃圾回收時對一些簡單物件做不必要的檢查,下面是一些對內置型別延遲管理的例子:

  • 只包含不可變物件(整數、字串或者只包含不可變物件的元組)的元組不需要被管理,解釋器會創建大量的元組,其中很多在垃圾回收之前就銷毀了,因此沒必要在創建時就對符合條件的元組解除管理,因此除了空元組之外的所有元組在創建時都會被垃圾收集器管理,直到垃圾回收的時候才會確定是否有存活的元組需要解除管理,如果一個元組中的所有元素都沒有被垃圾收集器管理,那么元組本身也可以解除管理,每個分代中的垃圾回收都會對元組做是否需要解除管理的檢查,因此一個元組可能在多次檢查之后才會被解除管理,
  • 只包含不可變物件的字典也不需要被垃圾收集器管理,字典是在創建時解除管理的,如果另一個被管理的物件加入到字典中(無論是作為鍵還是值),那么垃圾收集器都會重新管理字典,另外,在全量垃圾回收(所有分代中)時,垃圾收集器也會檢查字典中的內容是否都沒有被管理,如果滿足條件也會對字典解除管理,

垃圾回收模塊提供了 Python 函式 is_tracked(obj) 回傳物件當前是否被垃圾收集器管理,當然后續垃圾回收的時候可能會改變管理狀態,

>>> gc.is_tracked(0)
False
>>> gc.is_tracked("a")
False
>>> gc.is_tracked([])
True
>>> gc.is_tracked({})
False
>>> gc.is_tracked({"a": 1})
False
>>> gc.is_tracked({"a": []})
True

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

標籤:其他

上一篇:Python工具箱系列(二十三)

下一篇:Java 如何高亮 Excel 中低于或高于平均值的單元格

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