仙人指路,引而不发,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中New和Make函数的使用背景和区别EP16

简介: Golang只有二十五个系统保留关键字,二十几个系统内置函数,加起来只有五十个左右需要记住的关键字,纵观编程宇宙,无人能出其右。其中还有一些保留关键字属于“锦上添花”,什么叫锦上添花?就是从表面上看,就算没有,也无伤大雅,不影响业务或者逻辑的实现,比如lambda表达式之类,没有也无所谓,但在初始化数据结构的时候,我们无法避免地,会谈及两个内置函数:New和Make。

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函数作用是为类型申请内存空间,并返回指向内存地址的指针。

相关文章
|
1月前
|
Unix 程序员 编译器
第六章 Golang函数
第六章 Golang函数
28 0
|
19小时前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello(&quot;Alice&quot;)`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
10 1
|
23小时前
|
SQL 关系型数据库 MySQL
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
|
1天前
|
Go 开发者
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
【4月更文挑战第21天】本文介绍了Go语言中的流程控制语句,包括`if`、`switch`和`for`循环。`if`语句支持简洁的语法和初始化语句,但需注意比较运算符的使用。`switch`语句提供多分支匹配,可省略`break`,同时支持不带表达式的形式。`for`循环有多种形式,如基本循环和`for-range`遍历,遍历时修改原集合可能导致未定义行为。理解并避免易错点能提高代码质量和稳定性。通过实践代码示例,可以更好地掌握Go语言的流程控制。
9 3
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
|
1天前
|
Go
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
【4月更文挑战第21天】Go语言函数是代码组织的基本单元,用于封装可重用逻辑。本文介绍了函数定义(包括基本形式、命名、参数列表和多返回值)、调用以及匿名函数与闭包。在函数定义时,注意参数命名和注释,避免参数顺序混淆。在调用时,要检查并处理多返回值中的错误。理解闭包原理,小心处理外部变量引用,以提升代码质量和可维护性。通过实践和示例,能更好地掌握Go语言函数。
14 1
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
|
2天前
|
Go
Golang深入浅出之-Go数据类型详解:整型、浮点型与布尔型
【4月更文挑战第20天】Go语言基础数据类型包括整型(有符号和无符号,如`int8`、`uint32`)、浮点型(`float32`、`float64`)和布尔型(`true`、`false`)。理解它们的范围和特性,以及注意溢出、精度损失、类型转换等问题,是编写高效Go代码的关键。例如,整型溢出可能导致模运算,浮点数比较可能有精度误差,布尔型不应用于数值计算。了解这些易错点,能帮助写出更健壮的代码。
17 0
|
2天前
|
Go
Golang深入浅出之-Go语言基础语法:变量声明与赋值
【4月更文挑战第20天】本文介绍了Go语言中变量声明与赋值的基础知识,包括使用`var`关键字和简短声明`:=`的方式,以及多变量声明与赋值。强调了变量作用域、遮蔽、初始化与零值的重要性,并提醒读者注意类型推断时的一致性。了解这些概念有助于避免常见错误,提高编程技能和面试表现。
18 0
|
1月前
|
存储 Go
|
1月前
|
Go 开发工具 git
编程笔记 GOLANG基础 003 Go语言开发环境搭建
编程笔记 GOLANG基础 003 Go语言开发环境搭建
|
1月前
|
存储 Java Go
编程笔记 GOLANG基础 002 Go语言简介
编程笔记 GOLANG基础 002 Go语言简介