เอกสารแนะนำให้ใช้หลายย่อหน้าเพื่ออธิบายความแตกต่างระหว่างnew()
และmake()
แต่ในทางปฏิบัติคุณสามารถสร้างวัตถุภายในขอบเขตภายในและส่งคืนได้
ทำไมคุณถึงใช้ผู้จัดสรรคู่
เอกสารแนะนำให้ใช้หลายย่อหน้าเพื่ออธิบายความแตกต่างระหว่างnew()
และmake()
แต่ในทางปฏิบัติคุณสามารถสร้างวัตถุภายในขอบเขตภายในและส่งคืนได้
ทำไมคุณถึงใช้ผู้จัดสรรคู่
คำตอบ:
สิ่งที่คุณสามารถทำได้ด้วยmake
สิ่งที่คุณไม่สามารถทำได้:
มันเป็นเรื่องเล็ก ๆ น้อย ๆ new
ยากที่จะปรับ สิ่งสำคัญที่ทำให้ง่ายขึ้นคือการสร้างพอยน์เตอร์สำหรับประเภทที่ไม่ใช่คอมโพสิต ทั้งสองฟังก์ชั่นด้านล่างเทียบเท่า มีเพียงข้อสรุปเล็ก ๆ น้อย ๆ :
func newInt1() *int { return new(int) }
func newInt2() *int {
var i int
return &i
}
m := map[string]int{}
แทนที่จะเป็นm := make(map[string]int)
อะไร ไม่จำเป็นต้อง preallocate ขนาดเช่นกัน
Go มีวิธีการจัดสรรหน่วยความจำหลายวิธีและการกำหนดค่าเริ่มต้น:
&T{...}
, &someLocalVar
, new
,make
การจัดสรรสามารถเกิดขึ้นได้เมื่อสร้างตัวอักษรผสม
new
สามารถใช้เพื่อจัดสรรค่าเช่นจำนวนเต็ม&int
เป็นสิ่งผิดกฎหมาย:
new(Point)
&Point{} // OK
&Point{2, 3} // Combines allocation and initialization
new(int)
&int // Illegal
// Works, but it is less convenient to write than new(int)
var i int
&i
ความแตกต่างระหว่างnew
และmake
สามารถมองเห็นได้โดยดูตัวอย่างต่อไปนี้:
p := new(chan int) // p has type: *chan int
c := make(chan int) // c has type: chan int
Suppose Go ไม่มีnew
และmake
มีฟังก์ชั่นในNEW
ตัว จากนั้นโค้ดตัวอย่างจะมีลักษณะดังนี้:
p := NEW(*chan int) // * is mandatory
c := NEW(chan int)
The *
จะเป็นข้อบังคับดังนั้น:
new(int) --> NEW(*int)
new(Point) --> NEW(*Point)
new(chan int) --> NEW(*chan int)
make([]int, 10) --> NEW([]int, 10)
new(Point) // Illegal
new(int) // Illegal
ใช่กลมกลืนnew
และmake
เป็นหนึ่งในตัวฟังก์ชั่นเป็นไปได้ อย่างไรก็ตามมีความเป็นไปได้ที่ฟังก์ชั่นในตัวเดียวจะทำให้เกิดความสับสนมากขึ้นในหมู่นักเขียนโปรแกรม Go คนใหม่กว่ามีฟังก์ชั่นในตัวสองตัว
เมื่อพิจารณาจากจุดทั้งหมดข้างต้นแล้วจะมีความเหมาะสมมากกว่าnew
และmake
จะแยกออกจากกัน
int
สร้างขึ้น
make(Point)
และmake(int)
ในช่วงที่ผ่านมา 2 สาย?
make
ฟังก์ชั่นการจัดสรรและเริ่มต้นวัตถุประเภทชิ้นแผนที่หรือจังเท่านั้น เช่นnew
อาร์กิวเมนต์แรกเป็นประเภท แต่ก็สามารถโต้แย้งขนาดที่สอง ไม่เหมือนกับใหม่ประเภทส่งคืนของ make จะเหมือนกับชนิดของอาร์กิวเมนต์ไม่ใช่ตัวชี้ไปที่ และค่าที่จัดสรรจะเริ่มต้นได้ (ไม่ได้ตั้งค่าเป็นศูนย์เหมือนใหม่) เหตุผลก็คือชิ้นส่วนแผนที่และจังเป็นโครงสร้างข้อมูล พวกเขาจะต้องเริ่มต้นมิฉะนั้นพวกเขาจะไม่สามารถใช้งานได้ นี่คือเหตุผลใหม่ () และทำให้ () ต้องแตกต่างกัน
ตัวอย่างต่อไปนี้จาก Effective Go ทำให้ชัดเจน:
p *[]int = new([]int) // *p = nil, which makes p useless
v []int = make([]int, 100) // creates v structure that has pointer to an array, length field, and capacity field. So, v is immediately usable
new([]int)
มันเพิ่งจัดสรรหน่วยความจำสำหรับ [] int แต่ไม่เริ่มต้นดังนั้นมันก็แค่ส่งกลับnil
; ไม่ใช่ตัวชี้ไปยังหน่วยความจำเนื่องจากไม่สามารถใช้งานได้ make([]int)
จัดสรรและเริ่มต้นเพื่อให้สามารถใช้งานได้จากนั้นส่งคืนที่อยู่
new(T)
- จัดสรรหน่วยความจำและชุดให้ค่าเป็นศูนย์สำหรับประเภทT ..
..that คือ0
สำหรับint , ""
สำหรับสตริงและnil
ประเภทอ้างอิง ( ชิ้น , แผนที่ , จัง )
โปรดทราบว่าประเภทที่อ้างอิงเป็นเพียงตัวชี้ไปยังโครงสร้างข้อมูลพื้นฐานบางอย่างซึ่งจะไม่ถูกสร้างขึ้นโดยnew(T)
ตัวอย่าง: ในกรณีของการแบ่งอาร์เรย์ต้นแบบจะไม่ถูกสร้างขึ้นดังนั้นnew([]int)
จะส่งคืนตัวชี้ไปที่อะไร
make(T)
- จัดสรรหน่วยความจำสำหรับชนิดข้อมูลที่อ้างอิง ( ส่วนแบ่ง , แผนที่ , จัง ) และเริ่มต้นโครงสร้างข้อมูลพื้นฐานของพวกเขา
ตัวอย่าง: ในกรณีของslice อาร์เรย์พื้นฐานจะถูกสร้างขึ้นตามความยาวและความจุที่ระบุไว้
โปรดจำไว้ว่าไม่เหมือนกับ C อาร์เรย์นั้นเป็นชนิดดั้งเดิมใน Go!
ที่ถูกกล่าวว่า:
make(T)
พฤติกรรมเช่นไวยากรณ์ตัวอักษรผสม
new(T)
ทำตัวเหมือนvar
(เมื่อตัวแปรไม่ได้เริ่มต้น)
func main() {
fmt.Println("-- MAKE --")
a := make([]int, 0)
aPtr := &a
fmt.Println("pointer == nil :", *aPtr == nil)
fmt.Printf("pointer value: %p\n\n", *aPtr)
fmt.Println("-- COMPOSITE LITERAL --")
b := []int{}
bPtr := &b
fmt.Println("pointer == nil :", *bPtr == nil)
fmt.Printf("pointer value: %p\n\n", *bPtr)
fmt.Println("-- NEW --")
cPtr := new([]int)
fmt.Println("pointer == nil :", *cPtr == nil)
fmt.Printf("pointer value: %p\n\n", *cPtr)
fmt.Println("-- VAR (not initialized) --")
var d []int
dPtr := &d
fmt.Println("pointer == nil :", *dPtr == nil)
fmt.Printf("pointer value: %p\n", *dPtr)
}
เรียกใช้โปรแกรม
-- MAKE --
pointer == nil : false
pointer value: 0x118eff0 # address to underlying array
-- COMPOSITE LITERAL --
pointer == nil : false
pointer value: 0x118eff0 # address to underlying array
-- NEW --
pointer == nil : true
pointer value: 0x0
-- VAR (not initialized) --
pointer == nil : true
pointer value: 0x0
อ่านเพิ่มเติม:
https://golang.org/doc/effective_go.html#allocation_new
https://golang.org/doc/effective_go.html#allocation_make
คุณต้องmake()
สร้างช่องและแผนที่ (และชิ้นส่วน แต่สามารถสร้างได้จากอาร์เรย์ด้วย) ไม่มีทางเลือกอื่นในการทำสิ่งเหล่านี้ดังนั้นคุณจึงไม่สามารถลบออกmake()
จากพจนานุกรมของคุณ
สำหรับnew()
ฉันไม่รู้เหตุผลใด ๆ เลยว่าทำไมคุณถึงต้องการเมื่อคุณสามารถใช้ไวยากรณ์ของ struct มันมีความหมายเชิงความหมายที่ไม่ซ้ำกันซึ่งก็คือ "สร้างและส่งกลับโครงสร้างที่มีเขตข้อมูลทั้งหมดเริ่มต้นให้เป็นค่าศูนย์ของพวกเขา" ซึ่งจะมีประโยชน์
นอกเหนือจากทุกอย่างที่อธิบายไว้ในEffective Goความแตกต่างที่สำคัญระหว่างnew(T)
และ&T{}
คือการจัดสรรฮีปอย่างชัดเจน อย่างไรก็ตามควรสังเกตว่านี่ขึ้นอยู่กับการใช้งานและอาจมีการเปลี่ยนแปลงได้
เมื่อเปรียบเทียบmake
กับnew
ความรู้สึกเล็กน้อยขณะที่ทั้งสองทำหน้าที่ต่างกันโดยสิ้นเชิง แต่นี่จะอธิบายรายละเอียดในบทความที่เชื่อมโยง
&T{}
ดำเนินการจัดสรรฮีปอย่างชัดเจนคือ AFAIK ไม่ได้ยึดตามข้อกำหนดใด ๆ อันที่จริงผมเชื่อว่าการวิเคราะห์การหลบหนีอยู่แล้วการรักษา T * new(T)
เช่นในกองเมื่อใดก็ตามที่เป็นไปได้ในทางเดียวกันว่าเช่นเดียวกับ
ใหม่ (T): มันจะส่งคืนตัวชี้เพื่อพิมพ์ค่า T ประเภท * T มันจัดสรรและศูนย์หน่วยความจำ ใหม่ (T) เทียบเท่ากับ & T {}
ทำให้ (T): มันส่งกลับค่าเริ่มต้นของประเภท Tมันจะจัดสรรและเริ่มต้นหน่วยความจำ มันใช้สำหรับชิ้นแผนที่และช่อง