仙人指路,引而不发,Golang入门教程New和Make函数区别EP16
yuyutoo 2024-10-11 21:41 1 浏览 0 评论
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函数作用是为类型申请内存空间,并返回指向内存地址的指针。
相关推荐
- jQuery VS AngularJS 你更钟爱哪个?
-
在这一次的Web开发教程中,我会尽力解答有关于jQuery和AngularJS的两个非常常见的问题,即jQuery和AngularJS之间的区别是什么?也就是说jQueryVSAngularJS?...
- Jquery实时校验,指定长度的「负小数」,小数位未满末尾补0
-
在可以输入【负小数】的输入框获取到焦点时,移除千位分隔符,在输入数据时,实时校验输入内容是否正确,失去焦点后,添加千位分隔符格式化数字。同时小数位未满时末尾补0。HTML代码...
- 如何在pbootCMS前台调用自定义表单?pbootCMS自定义调用代码示例
-
要在pbootCMS前台调用自定义表单,您需要在后台创建表单并为其添加字段,然后在前台模板文件中添加相关代码,如提交按钮和表单验证代码。您还可以自定义表单数据的存储位置、添加文件上传字段、日期选择器、...
- 编程技巧:Jquery实时验证,指定长度的「负小数」
-
为了保障【负小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【负小数】的方法。HTML代码<inputtype="text"class="forc...
- 一篇文章带你用jquery mobile设计颜色拾取器
-
【一、项目背景】现实生活中,我们经常会遇到配色的问题,这个时候去百度一下RGB表。而RGB表只提供相对于的颜色的RGB值而没有可以验证的模块。我们可以通过jquerymobile去设计颜色的拾取器...
- 编程技巧:Jquery实时验证,指定长度的「正小数」
-
为了保障【正小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【正小数】的方法。HTML做成方法<inputtype="text"class="fo...
- jquery.validate检查数组全部验证
-
问题:html中有多个name[],每个参数都要进行验证是否为空,这个时候直接用required:true话,不能全部验证,只要这个数组中有一个有值就可以通过的。解决方法使用addmethod...
- Vue进阶(幺叁肆):npm查看包版本信息
-
第一种方式npmviewjqueryversions这种方式可以查看npm服务器上所有的...
- layui中使用lay-verify进行条件校验
-
一、layui的校验很简单,主要有以下步骤:1.在form表单内加上class="layui-form"2.在提交按钮上加上lay-submit3.在想要校验的标签,加上lay-...
- jQuery是什么?如何使用? jquery是什么功能组件
-
jQuery于2006年1月由JohnResig在BarCampNYC首次发布。它目前由TimmyWilson领导,并由一组开发人员维护。jQuery是一个JavaScript库,它简化了客户...
- django框架的表单form的理解和用法-9
-
表单呈现...
- jquery对上传文件的检测判断 jquery实现文件上传
-
总体思路:在前端使用jquery对上传文件做部分初步的判断,验证通过的文件利用ajaxFileUpload上传到服务器端,并将文件的存储路径保存到数据库。<asp:FileUploadI...
- Nodejs之MEAN栈开发(四)-- form验证及图片上传
-
这一节增加推荐图书的提交和删除功能,来学习node的form提交以及node的图片上传功能。开始之前需要源码同学可以先在git上fork:https://github.com/stoneniqiu/R...
- 大数据开发基础之JAVA jquery 大数据java实战
-
上一篇我们讲解了JAVAscript的基础知识、特点及基本语法以及组成及基本用途,本期就给大家带来了JAVAweb的第二个知识点jquery,大数据开发基础之JAVAjquery,这是本篇文章的主要...
- 推荐四个开源的jQuery可视化表单设计器
-
jquery开源在线表单拖拉设计器formBuilder(推荐)jQueryformBuilder是一个开源的WEB在线html表单设计器,开发人员可以通过拖拉实现一个可视化的表单。支持表单常用控件...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)