【golang】鏈表(List)

List實現了一個雙向鏈表,而Element則代表了鏈表中元素的結構。

可以把自己生成的Element類型值傳給鏈表嗎?

首先來看List的四種方法。

MoveBefore方法和MoveAfter方法,它們分別用于把給定的元素移動到另一個元素的前面和后面。

MoveToFront方法和MoveToBack方法,分別用于把給定的元素移動到鏈表的最前端和最后端。

在這些方法中,"給定的元素"都是 *Element 類型的,*Element類型是Element類型的指針類型,*Element的值就是元素的指針。

func (l *List) MoveBefore(e, mark *Element)
func (l *List) MoveAfter(e, mark *Element)
func (l *List) MoveToFront(e *Element)
func (l *List) MoveToBack(e *Element)

具體問題是,如果我們自己生成這樣的值,然后把它作為“給定的元素”傳給鏈表的方法,那么會發生什么?鏈表會接受它嗎?

答案:不會接受,這些方法將不會對鏈表做出任何改動。因為我們自己生成的Element值并不在鏈表中,所以也就談不上"在鏈表中移動元素"。更何況鏈表不允許我們把自己生成的Element值插入其中。

問題解析

在List包含的方法中,用于插入新元素的那些方法都只接受interface{}類型的值。這些方法在內部會使用Element值,包裝接收到的新元素。

這些做正是為了避免直接使用我們自己生成的元素,主要原因是避免鏈表的內部關聯,遭到外界破壞,這對于鏈表本身以及我們這些使用者來說都是有益的。

List的方法還有下面這幾種:

Front和Back方法分別用于獲取鏈表中最前端和最后端的元素,

InsertBefore和InsertAfter方法分別用于在指定的元素之前和之后插入新元素。

PushFront和PushBack方法則分別用于在鏈表的最前端和最后端插入新元素。

func (l *List) Front() *Element
func (l *List) Back() *Element
func (l *List) InsertBefore(v interface{}, mark *Element) *Element
func (l *List) InsertAfter(v interface{}, mark *Element) *Element
func (l *List) PushFront(v interface{}) *Element
func (l *List) PushBack(v interface{}) *Element

這些方法都會把一個Element值的指針作為結果返回,它們就是鏈表留給我們的安全"接口"。拿到這些內部元素的指針,我們就可以去調用前面提到的用于移動元素的方法了。

為什么鏈表可以做到開箱即用?

List和Element都是結構體類型。結構體類型有一個特點,那就是它們的零值都會是擁有特定結構,但是沒有任何定制化內容的值,相當于一個空殼。值中的字段也會被分別賦予各自類型的零值。

廣義來講,所謂的零值就是只做了聲明,但還未做初始化的變量被給予的缺省值。每個類型的零值都會依據該類型的特性而被設定。
比如,經過語句var a [2]int聲明的變量a的值,將會是一個包含了兩個0的整數數組。又比如,經過語句var s []int聲明的變量s的值將會是一個[]int類型的、值為nil的切片。

那么經過語句var l list.List聲明的變量l的值將會是什么呢?

這個零值將會是一個長度為0的鏈表。這個鏈表持有的根元素也將會是一個空殼,其中只會包含缺省的內容。
那這樣的鏈表我們可以直接拿來使用嗎?

答案是:可以的。這種稱為"開箱即用"。Go語言標準庫中很多結構體類型的程序實體都做到了開箱即用。這也是在編寫可供別人使用的代碼包(或者說程序庫)時,我們推薦遵守的最佳實踐之一。

那么,語句var l list.List 聲明的鏈表l可以直接使用,這是怎么做到的呢?

關鍵在于它的"延遲初始化"機制。

所謂的延遲初始化,你可以理解為把初始化操作延后,僅在實際需要的時候才進行。延遲初始化的優點在于"延后",它可以分散初始化操作帶來的計算量和存儲空間消耗。

例如,如果我們需要集中聲明非常多的大容量切片的話,那么那時的CPU和內存空間的使用量肯定會有一個激增,并且只有設法讓其中的切片及其底層數組被回收,內存使用量才會有所降低。

如果數組是可以被延遲初始化的,那么計算量和存儲空間的壓力就可以被分散到實際使用它們的時候。這些數組被實際使用的時間越分散,延遲初始化帶來的優勢就會越明顯。

實際上,Go 語言的切片就起到了延遲初始化其底層數組的作用,你可以想一想為什么會這么說的理由。

延遲初始化的缺點恰恰也在于“延后”。你可以想象一下,如果我在調用鏈表的每個方法的時候,它們都需要先去判斷鏈表是否已經被初始化,那這也會是一個計算量上的浪費。在這些方法被非常頻繁地調用的情況下,這種浪費的影響就開始顯現了,程序的性能將會降低。

在這里的鏈表實現中,一些方法是無需對是否初始化做判斷的。比如Front方法和Back方法,一旦發現鏈表的長度為0, 直接返回nil就好了。

又比如,在用于刪除元素、移動元素,以及一些用于插入元素的方法中,只要判斷一下傳入的元素中指向所屬鏈表的指針,是否與當前鏈表的指針相等就可以了。

如果不相等,就一定說明傳入的元素不是這個鏈表中的,后續的操作就不用做了。反之,就一定說明這個鏈表已經被初始化了。

原因在于,鏈表的PushFront方法、PushBack方法、PushBackList方法以及PushFrontList方法總會先判斷鏈表的狀態,并在必要時進行初始化,這就是延遲初始化。

而且,我們在向一個空的鏈表中添加新元素的時候,肯定會調用這四個方法中的一個,這時新元素中指向所屬鏈表的指針,一定會被設定為當前鏈表的指針。所以,指針相等是鏈表已經初始化的充分必要條件。

List利用了自身以及Element在結構上的特點,巧妙地平衡了延遲初始化的優缺點,使得鏈表可以開箱即用,并且在性能上可以達到最優。

Ring和List的區別在哪兒?

container/ring包中的Ring類型實現的是一個循環鏈表,也就是俗稱的環。其實List在內部就是一個循環鏈表。它的根元素永遠不會持有任何實際的元素值,而該元素的存在就是為了連接這個循環鏈表的首尾兩端。

所以也可以說,List的零值是一個只包含了根元素,但不包含任何實際元素值的空鏈表。那么,既然Ring和List在本質上都是循環鏈表,那它們到底有什么不同呢?

1.Ring類型的數據結構僅由它自身即可代表,而List類型則需要由它以及Element類型聯合表示。這是表示方式上的不同,也是結構復雜度上的不同。

2.一個Ring類型的值嚴格來講,只代表了其所屬的循環鏈表中的一個元素,而一個List類型的值則代表了一個完整的鏈表。這是表示維度上的不同。

3.在創建并初始化一個Ring值的時候,我們可以指定它包含的元素的數量,但是對于一個List值來說卻不能這樣做(也沒有必要這樣做)。循環鏈表一旦被創建,其長度是不可變的。這是兩個代碼包中的New函數在功能上的不同,也是兩個類型在初始化值方面的第一個不同。

4.僅通過var r ring.Ring語句聲明的r將會是一個長度為1的循環鏈表,而List類型的零值則是也給長度為0的鏈表。別忘了List中的根元素不會持有實際元素值,因此計算長度時不會包含它。這是兩個類型在初始化值方面的第二個不同。

5.Ring值的Len方法的算法復雜度是 O(N) 的,而List值的Len方法的算法復雜度則是O(1) 的。這是兩者在性能方面最顯而易見的差別。

文章學習自郝林老師的《Go語言36講》

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/40425.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/40425.shtml
英文地址,請注明出處:http://en.pswp.cn/news/40425.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

十種排序算法(附動圖)

排序算法 一、基本介紹 ? 排序算法比較基礎,但是設計到很多計算機科學的想法,如下: ? 1、比較和非比較的策略 ? 2、迭代和遞歸的實現 ? 3、分而治之思想 ? 4、最佳、最差、平均情況時間復雜度分析 ? 5、隨機算法 二、排序算法的分類 …

RabbitMq-1基礎概念

RabbitMq-----分布式中的一種通信手段 1. MQ的基本概念(message queue,消息隊列) mq:消息隊列,存儲消息的中間件 分布式系統通信的兩種方式:直接遠程調用,借助第三方完成間接通信 消息的發送方是生產者&#xff0c…

面試熱題(二叉樹的鋸齒形層次遍歷)

給你二叉樹的根節點 root ,返回其節點值的 鋸齒形層序遍歷 。(即先從左往右,再從右往左進行下一層遍歷,以此類推,層與層之間交替進行) 輸入:root [3,9,20,null,null,15,7] 輸出:[[3…

MySQL數據庫-字符串函數詳解

前言 MySQL數據庫提供了多種不同類型的函數,用于處理字符串、日期、數值等數據類型,以及實現條件、聚合等操作,下面我們主要介紹字符串函數 CONCAT() 函數 CONCAT() 可用于將多個字符串連接在一起。 示例: SELECT CONCAT(Hell…

C++ STL stack queue

目錄 一.stack 介紹 二.stack 使用 三.stack 模擬實現 普通版本: 適配器版本: 四.queue的介紹 五. queue使用 六.queue模擬實現 七.deque介紹 1.容器適配器 2.deque的簡單介紹 3.deque的缺陷 4.為什么選擇deque作為stack和queue的底層默認容…

System.Text.Encoding不同字符編碼之間進行轉換

System.Text.Encoding 是 C# 中用于處理字符編碼和字符串與字節之間轉換的類。它提供了各種靜態方法和屬性,用于在不同字符編碼之間進行轉換,以及將字符串轉換為字節數組或反之。 在處理多語言文本、文件、網絡通信以及其他字符數據的場景中&#xff0c…

Spring Boot 獲取前端參數

Spring Boot 獲取前端參數 在開發 Web 應用程序時,前端參數是非常重要的。Spring Boot 提供了多種方法來獲取前端參數,本文將介紹其中的一些常用方法。 1. 使用 RequestParam 注解 RequestParam 注解是 Spring MVC 提供的一種常用方式,用于…

C++ 函數

函數是一組一起執行一個任務的語句。每個 C 程序都至少有一個函數,即主函數 main() ,所有簡單的程序都可以定義其他額外的函數。 您可以把代碼劃分到不同的函數中。如何劃分代碼到不同的函數中是由您來決定的,但在邏輯上,劃分通常…

pycharm調整最大堆發揮最大

python程序運行時,怎么提高效率,設置pycharm最大堆過程如下; 一、進入設置pycharm最大堆; 二、進入設置pycharm最大堆; 如果8g設置為6g左右,占75%左右最佳

5個實用的 Vue 技巧

在這篇文章中,我們將探討五個實用的 Vue 技巧,這些技巧可以使你日常使用 Vue 編程更高效、更富有成效。無論你是Vue的初學者還是經驗豐富的開發者,這些技巧都能幫助你編寫更清晰、更簡潔、更有效的代碼。那么,讓我們開始吧。 1. …

9.1 C++ STL 排序、算數與集合

C STL(Standard Template Library)是C標準庫中的一個重要組成部分,提供了豐富的模板函數和容器,用于處理各種數據結構和算法。在STL中,排序、算數和集合算法是常用的功能,可以幫助我們對數據進行排序、統計…

【JVM】JVM中的分代回收

文章目錄 分代收集算法什么是分代分代收集算法-工作機制MinorGC、 Mixed GC 、 FullGC的區別是什么 分代收集算法 什么是分代 在java8時,堆被分為了兩份: 新生代和老年代【1:2】 其中: 對于新生代,內部又被分為了三…

eclipse常用設置

1、調整編輯頁面字體大小 窗口 (Window)- 首選項(Preferences)- 常規(General)- 外觀 (Appearence)- 顏色與字體 (Colors And Fonts),在右邊的對話框里選擇 Java - Java Editor Text Font,點擊出現的修改&…

【ARM 嵌入式 編譯系列 3.3 -- gcc 動態庫與靜態庫的鏈接方法介紹】

文章目錄 1.1 GCC 鏈接器 LD 介紹1.1.1 GCC 鏈接器 LD 常用參數介紹1.2 動態庫和靜態庫介紹1.2.1 動態庫和靜態庫優缺點1.2.2 庫文件鏈接方式1.2.3 ldd 工具介紹1.2.4 靜態庫鏈接時搜索路徑順序1.2.5 動態庫鏈接時、執行時搜索路徑順序1.2.6 頭文件搜索路徑1.2.7 有關環境變量上…

Neo4j之Aggregation基礎

在 Neo4j 中,聚合(Aggregation)是對數據進行計算、匯總和統計的過程。以下是一些使用聚合函數的常見例子,以及它們的解釋: 計算節點數量: MATCH (p:Person) RETURN count(p) AS totalPersons;這個查詢會計…

Socks5代理在多線程爬蟲中的應用

在進行爬蟲開發過程中,我們常常需要處理大量的數據,并執行多任務并發操作。然而,頻繁的請求可能會引起目標網站的反爬機制,導致IP封禁或限制訪問。為了規避這些限制,我們可以借助Socks5代理的強大功能,通過…

Nginx反向代理技巧

跨域 作為一個前端開發者來說不可避免的問題就是跨域,那什么是跨域呢? 跨域:指的是瀏覽器不能執行其他網站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器對javascript施加的安全限制。瀏覽器的同源策略是指協議,域名…

2011-2021年數字普惠金融指數Bartik工具變量法(含原始數據和Bartik工具變量法代碼)

2011-2021年數字普惠金融指數Bartik工具變量法(含原始數據和Bartik工具變量法代碼) 1、時間:2011-2020(省級、城市),2014-2020(區縣) 2、原始數據來源:北大金融研究中心…

npm的鏡像源和代理的查看和修改

一、鏡像源 查詢當前鏡像源 npm get registry 設置為淘寶鏡像 npm config set registry http://registry.npm.taobao.org/ 設置回默認的官方鏡像 npm config set registry https://registry.npmjs.org/ 設置electron為淘寶鏡像 npm config set ELECTRON_MIRROR "h…

Redis對象類型和結構、內存回收、對象共享

對象類型和結構 在Redis中,無論是鍵key還是值value都是一個對象,每次對Redis數據庫創建一個新的鍵值對時,就至少會創建兩個對象。 常見的對象類型有: 字符串列表哈希集合有序集合 這些對象在Redis中統一用一個結構體redisObjec…