仙人指路,引而不發,Go lang1.18入門精煉教程,由白丁入鴻儒,Golang中New和Make函數的使用背景和區別EP16

来源:https://www.cnblogs.com/v3ucn/archive/2022/09/05/16658424.html
-Advertisement-
Play Games

Golang只有二十五個系統保留關鍵字,二十幾個系統內置函數,加起來只有五十個左右需要記住的關鍵字,縱觀編程宇宙,無人能出其右。其中還有一些保留關鍵字屬於“錦上添花”,什麼叫錦上添花?就是從錶面上看,就算沒有,也無傷大雅,不影響業務或者邏輯的實現,比如lambda表達式之類,沒有也無所謂,但在初始化 ...


Golang只有二十五個系統保留關鍵字,二十幾個系統內置函數,加起來只有五十個左右需要記住的關鍵字,縱觀編程宇宙,無人能出其右。其中還有一些保留關鍵字屬於“錦上添花”,什麼叫錦上添花?就是從錶面上看,就算沒有,也無傷大雅,不影響業務或者邏輯的實現,比如lambda表達式之類,沒有也無所謂,但在初始化數據結構的時候,我們無法避免地,會談及兩個內置函數:New和Make。

New函數

假設聲明一個變數:

package main  
  
import "fmt"  
  
func main() {  
  
	var a string  
  
	fmt.Println(a)  
	fmt.Println(&a)  
  
}

系統返回:

 0x14000090210

這裡我們使用var關鍵字聲明瞭一個數據類型是字元串的變數a,然後沒有做任何賦值操作,於是a的預設值變為系統的零值,也就是空,a的記憶體地址已經做好了指向,以便存儲a將來的值。

下麵開始賦值:

package main  
  
import "fmt"  
  
func main() {  
  
	var a string  
	a = "ok"  
	fmt.Println(a)  
	fmt.Println(&a)  
  
}

系統返回:

ok  
0x14000104210

可以看到a的值和記憶體地址都發生了改變,整個初始化過程,我們並沒有使用new函數

下麵我們把數據類型換成指針:

package main  
  
import "fmt"  
  
func main() {  
  
	var a *string  
  
	fmt.Println(a)  
	fmt.Println(&a)  
  
}

系統返回:

<nil>  
0x140000a4018

可以看到由於數據類型換成了指針,零值變成了nil

接著像字元串數據類型一樣進行賦值操作:

package main  
  
import "fmt"  
  
func main() {  
  
	var a *string  
  
	*a = "ok"  
  
	fmt.Println(*a)  
	fmt.Println(&a)  
  
}

系統返回:

panic: runtime error: invalid memory address or nil pointer dereference

是的,空指針異常,為什麼?因為指針是一個引用類型,對於引用類型來說,系統不僅需要我們要聲明它,還要為它分配記憶體空間,否則我們賦值的變數就沒地方放,這裡系統沒法為nil分配記憶體空間,所以沒有記憶體空間就沒法賦值。

而像字元串這種值類型就不會有這種煩惱,因為值類型的聲明不需要我們分配記憶體空間,系統會預設為其分配,為什麼?因為值類型的零值是一個具體的值,而不是nil,比如整形的零值是0,字元串的零值是空,空不是nil,所以就算是空,也可以賦值。

那引用類型就沒法賦值了?

package main  
  
import "fmt"  
  
func main() {  
  
	var a *string  
	a = new(string)  
	*a = "ok"  
  
	fmt.Println(*a)  
	fmt.Println(&a)  
  
}

系統返回:

ok  
0x14000126018

這裡我們使用了new函數,它正是用於分配記憶體,第一個參數接收一個類型而不是一個值,函數返回一個指向該類型記憶體地址的指針,同時把分配的記憶體置為該類型的零值。

換句話說,new函數可以幫我們做之前系統自動為值類型數據類型做的事。

當然,new函數不僅僅能夠為系統的基本類型的引用分配記憶體,也可以為自定義數據類型的引用分配記憶體:

package main  

package main  
  
import "fmt"  
  
func main() {  
  
	type Human struct {  
		name string  
		age  int  
	}  
	var human *Human  
	human = new(Human)  
	human.name = "張三"  
	fmt.Println(*human)  
	fmt.Println(&human)  
  
}  



系統返回:

{張三 0}  
0x1400011c018

這裡我們自定義了一種人類的結構體類型,然後聲明該類型的指針,由於指針是引用類型,所以必須使用new函數為其分配記憶體,然後,才能對該引用的結構體屬性進行賦值。

說白了,new函數就是為瞭解決引用類型的零值問題,nil算不上是真正意義上的零值,所以需要new函數為其“仙人指路”。

Make函數

make函數從功能層面上講,和new函數是一致的,也是用於記憶體的分配,但它只能為切片slice,字典map以及通道channel分配記憶體,並返回一個初始化的值。

這顯然有些矛盾了,既然已經有了new函數,並且new函數可以為引用數據類型分配記憶體,而切片、字典和通道不也是引用類型嗎?

大家既然都是引用類型,為什麼不直接使用new函數呢?

package main  
  
import "fmt"  
  
func main() {  
	a := *new([]int)  
	fmt.Printf("%T, %v\n", a, a == nil)  
  
	b := *new(map[string]int)  
	fmt.Printf("%T, %v\n", b, b == nil)  
  
	c := *new(chan int)  
	fmt.Printf("%T, %v\n", c, c == nil)  
}

程式返回:

[]int, true  
map[string]int, true  
chan int, true

雖然new函數也可以為切片、字典和通道分配記憶體,但沒有意義,因為它分配以後的地址還是nil:

  
package main  
  
import "fmt"  
  
func main() {  
	a := *new([]int)  
	fmt.Printf("%T, %v\n", a, a == nil)  
  
	b := *new(map[string]int)  
	fmt.Printf("%T, %v\n", b, b == nil)  
  
	c := *new(chan int)  
	fmt.Printf("%T, %v\n", c, c == nil)  
  
	b["123"] = 123  
  
	fmt.Println(b)  
}

這裡使用new函數初始化以後,為字典變數b賦值,系統報錯:

panic: assignment to entry in nil map

提示無法為nil的字典賦值,所以這就是make函數存在的意義:

  
package main  
  
import "fmt"  
  
func main() {  
	a := *new([]int)  
	fmt.Printf("%T, %v\n", a, a == nil)  
  
	b := make(map[string]int)  
	fmt.Printf("%T, %v\n", b, b == nil)  
  
	c := *new(chan int)  
	fmt.Printf("%T, %v\n", c, c == nil)  
  
	b["123"] = 123  
  
	fmt.Println(b)  
}

這裡字典b使用make函數進行初始化之後,就可以為b進行賦值了。

程式返回:

[]int, true  
map[string]int, false  
chan int, true  
map[123:123]

這也是make和new的區別,make可以為這三種類型分配記憶體,並且設置好其對應基本數據類型的零值,所以只要記住切片、字典和通道聲明後需要賦值的時候,需要使用make函數為其先分配記憶體空間。

不用New或者Make會怎麼樣

有人會說,為什麼非得糾結分配記憶體的問題?用海象操作符不就可以直接賦值了嗎?

// example1.go  
package main  
  
import "fmt"  
  
func main() {  
  
	a := map[int]string{}  
	fmt.Printf("%T, %v\n", a, a == nil)  
  
	a[1] = "ok"  
  
	fmt.Println(a)  
	  
}

程式返回:

map[int]string, false  
map[1:ok]

沒錯,就算沒用make函數,我們也可以“人為”的給字典分配記憶體,因為海象操作符其實是聲明加賦值的連貫操作,後面的空字典就是在為變數申請記憶體空間。

但為什麼系統還要保留new和make函數呢?事實上,這是一個分配記憶體的時機問題,聲明之後,沒有任何規定必須要立刻賦值,賦值後的變數會消耗系統的記憶體資源,所以聲明以後並不分配記憶體,而是在適當的時候再分配,這也是new和make的意義所在,所謂千石之弓,引而不發,就是這個道理。

結語

new和make函數都可以為引用類型分配記憶體,起到“仙人指路”的作用,變數聲明後“引而不發”就是使用它們的時機,make函數作用於創建 slice、map 和 channel 等內置的數據結構,而 new函數作用是為類型申請記憶體空間,並返回指向記憶體地址的指針。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前文再續,上一回我們完成了用戶管理模塊的CURD(增刪改查)功能,功能層面,無甚大觀,但有一個結構性的缺陷顯而易見,那就是項目結構過度耦合,項目的耦合性(Coupling),也叫耦合度,進而言之,模塊之間的關係,是對項目結構中各模塊間相互聯繫緊密程度的一種量化。耦合的強弱取決於模塊間調用的複雜性、調 ...
  • 非同步編程在 Rust 中的地位非常高,很多 crate 尤其是多IO操作的都使用了 async/await. 首先弄清楚非同步編程的幾個基本概念: Future Future 代表一個可在未來某個時候獲取返回值的 task,為了獲取這個 task 的執行狀況,Future 提供了一個函數用於判斷該 t ...
  • 引入依賴 <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.5.5</version> </dependency> 基礎創建方式 Cac ...
  • 序言 每逢佳節倍思親,想買個東西給家裡,結果發現手速不夠,網速不夠快,沒有時間下單等等各種原因導致最後想買的東西售罄了… 甚至跟你一起搶購的可能是腳本,太真實了! 今天就給大家分享一個python版搶購月餅的腳本,我們要用魔法打敗魔法!話不多說,直接開搞! 準備工作 今天要用的是一個測試工具的庫:S ...
  • 摘要:經常有朋友問,學 Python 面向對象時,翻閱別人代碼,會發現一個 super() 函數,那這個函數的作用到底是什麼? 本文分享自華為雲社區《Python 中的 super 函數怎麼學,怎麼解?》,作者:夢想橡皮擦。 實戰場景 經常有朋友問,學 Python 面向對象時,翻閱別人代碼,會發現 ...
  • 日誌對程式的重要性不言而喻,輕巧、簡單、無需費腦,程式代碼中隨處可見,幫助我們排查定位一個有一個問題問題。但看似不起眼的日誌,卻隱藏著各式各樣的“坑”,如果使用不當,不僅不能幫助我們,反而會成為服務“殺手”。 本文主要介紹生產環境日誌使用不當導致的“坑”及避坑指北,高併發系統下尤為明顯。同時提供一套... ...
  • IO流 筆記目錄:(https://www.cnblogs.com/wenjie2000/p/16378441.html) IO流體系圖 文件 什麼是文件 文件.對我們並不陌生,文件是==保存數據的地方==,比如大家經常使用的word文檔,txt文件.excel文件...都是文件。它既可以保存一張圖 ...
  • django的基本操作 1.django的安裝 (1)在ubuntu上的安裝 sudo pip3 install django==2.1.12(版本號) 檢查安裝是否成功: sudo pip3 freeze|grep -i ‘Django' 有輸出django==2.2.12 表示安裝成功 (2)在 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...