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

New函数

假设声明一个变量:

  1. package main
  2. import "fmt"
  3. func main() {
  4. var a string
  5. fmt.Println(a)
  6. fmt.Println(&a)
  7. }

系统返回:

  1. 0x14000090210

这里我们使用var关键字声明了一个数据类型是字符串的变量a,然后没有做任何赋值操作,于是a的默认值变为系统的零值,也就是空,a的内存地址已经做好了指向,以便存储a将来的值。

下面开始赋值:

  1. package main
  2. import "fmt"
  3. func main() {
  4. var a string
  5. a = "ok"
  6. fmt.Println(a)
  7. fmt.Println(&a)
  8. }

系统返回:

  1. ok
  2. 0x14000104210

可以看到a的值和内存地址都发生了改变,整个初始化过程,我们并没有使用new函数

下面我们把数据类型换成指针:

  1. package main
  2. import "fmt"
  3. func main() {
  4. var a *string
  5. fmt.Println(a)
  6. fmt.Println(&a)
  7. }

系统返回:

  1. <nil>
  2. 0x140000a4018

可以看到由于数据类型换成了指针,零值变成了nil

接着像字符串数据类型一样进行赋值操作:

  1. package main
  2. import "fmt"
  3. func main() {
  4. var a *string
  5. *a = "ok"
  6. fmt.Println(*a)
  7. fmt.Println(&a)
  8. }

系统返回:

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

是的,空指针异常,为什么?因为指针是一个引用类型,对于引用类型来说,系统不仅需要我们要声明它,还要为它分配内存空间,否则我们赋值的变量就没地方放,这里系统没法为nil分配内存空间,所以没有内存空间就没法赋值。

而像字符串这种值类型就不会有这种烦恼,因为值类型的声明不需要我们分配内存空间,系统会默认为其分配,为什么?因为值类型的零值是一个具体的值,而不是nil,比如整形的零值是0,字符串的零值是空,空不是nil,所以就算是空,也可以赋值。

那引用类型就没法赋值了?

  1. package main
  2. import "fmt"
  3. func main() {
  4. var a *string
  5. a = new(string)
  6. *a = "ok"
  7. fmt.Println(*a)
  8. fmt.Println(&a)
  9. }

系统返回:

  1. ok
  2. 0x14000126018

这里我们使用了new函数,它正是用于分配内存,第一个参数接收一个类型而不是一个值,函数返回一个指向该类型内存地址的指针,同时把分配的内存置为该类型的零值。

换句话说,new函数可以帮我们做之前系统自动为值类型数据类型做的事。

当然,new函数不仅仅能够为系统的基本类型的引用分配内存,也可以为自定义数据类型的引用分配内存:

  1. package main
  2. package main
  3. import "fmt"
  4. func main() {
  5. type Human struct {
  6. name string
  7. age int
  8. }
  9. var human *Human
  10. human = new(Human)
  11. human.name = "张三"
  12. fmt.Println(*human)
  13. fmt.Println(&human)
  14. }

系统返回:

  1. {张三 0}
  2. 0x1400011c018

这里我们自定义了一种人类的结构体类型,然后声明该类型的指针,由于指针是引用类型,所以必须使用new函数为其分配内存,然后,才能对该引用的结构体属性进行赋值。

说白了,new函数就是为了解决引用类型的零值问题,nil算不上是真正意义上的零值,所以需要new函数为其“仙人指路”。

Make函数

make函数从功能层面上讲,和new函数是一致的,也是用于内存的分配,但它只能为切片slice,字典map以及通道channel分配内存,并返回一个初始化的值。

这显然有些矛盾了,既然已经有了new函数,并且new函数可以为引用数据类型分配内存,而切片、字典和通道不也是引用类型吗?

大家既然都是引用类型,为什么不直接使用new函数呢?

  1. package main
  2. import "fmt"
  3. func main() {
  4. a := *new([]int)
  5. fmt.Printf("%T, %v\n", a, a == nil)
  6. b := *new(map[string]int)
  7. fmt.Printf("%T, %v\n", b, b == nil)
  8. c := *new(chan int)
  9. fmt.Printf("%T, %v\n", c, c == nil)
  10. }

程序返回:

  1. []int, true
  2. map[string]int, true
  3. chan int, true

虽然new函数也可以为切片、字典和通道分配内存,但没有意义,因为它分配以后的地址还是nil:


  1. package main
  2. import "fmt"
  3. func main() {
  4. a := *new([]int)
  5. fmt.Printf("%T, %v\n", a, a == nil)
  6. b := *new(map[string]int)
  7. fmt.Printf("%T, %v\n", b, b == nil)
  8. c := *new(chan int)
  9. fmt.Printf("%T, %v\n", c, c == nil)
  10. b["123"] = 123
  11. fmt.Println(b)
  12. }

这里使用new函数初始化以后,为字典变量b赋值,系统报错:

  1. panic: assignment to entry in nil map

提示无法为nil的字典赋值,所以这就是make函数存在的意义:


  1. package main
  2. import "fmt"
  3. func main() {
  4. a := *new([]int)
  5. fmt.Printf("%T, %v\n", a, a == nil)
  6. b := make(map[string]int)
  7. fmt.Printf("%T, %v\n", b, b == nil)
  8. c := *new(chan int)
  9. fmt.Printf("%T, %v\n", c, c == nil)
  10. b["123"] = 123
  11. fmt.Println(b)
  12. }

这里字典b使用make函数进行初始化之后,就可以为b进行赋值了。

程序返回:

  1. []int, true
  2. map[string]int, false
  3. chan int, true
  4. map[123:123]

这也是make和new的区别,make可以为这三种类型分配内存,并且设置好其对应基本数据类型的零值,所以只要记住切片、字典和通道声明后需要赋值的时候,需要使用make函数为其先分配内存空间。

不用New或者Make会怎么样

有人会说,为什么非得纠结分配内存的问题?用海象操作符不就可以直接赋值了吗?

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

程序返回:

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

没错,就算没用make函数,我们也可以“人为”的给字典分配内存,因为海象操作符其实是声明加赋值的连贯操作,后面的空字典就是在为变量申请内存空间。

但为什么系统还要保留new和make函数呢?事实上,这是一个分配内存的时机问题,声明之后,没有任何规定必须要立刻赋值,赋值后的变量会消耗系统的内存资源,所以声明以后并不分配内存,而是在适当的时候再分配,这也是new和make的意义所在,所谓千石之弓,引而不发,就是这个道理。

结语

new和make函数都可以为引用类型分配内存,起到“仙人指路”的作用,变量声明后“引而不发”就是使用它们的时机,make函数作用于创建 slice、map 和 channel 等内置的数据结构,而 new函数作用是为类型申请内存空间,并返回指向内存地址的指针。

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

  1. 延宕执行,妙用无穷,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中defer关键字延迟调用机制使用EP17

    先行定义,延后执行.不得不佩服Go lang设计者天才的设计,事实上,defer关键字就相当于Python中的try{ ...}except{ ...}finally{...}结构设计中的finall ...

  2. 清源正本,鉴往知来,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中引用类型是否进行引用传递EP18

    开篇明义,Go lang中从来就不存在所谓的"引用传递",从来就只有一种变量传递方式,那就是值传递.因为引用传递的前提是存在"引用变量",但是Go lang中从 ...

  3. 你有对象类,我有结构体,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang结构体(struct)的使用EP06

    再续前文,在面向对象层面,Python做到了超神:万物皆为对象,而Ruby,则干脆就是神:飞花摘叶皆可对象.二者都提供对象类操作以及继承的方式为面向对象张目,但Go lang显然有一些特立独行,因为它 ...

  4. 百亿数据百亿花, 库若恒河沙复沙,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang数据库操作实践EP12

    Golang可以通过Gorm包来操作数据库,所谓ORM,即Object Relational Mapping(数据关系映射),说白了就是通过模式化的语法来操作数据库的行对象或者表对象,对比相对灵活繁复 ...

  5. 层次分明井然有条,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang包管理机制(package)EP10

    Go lang使用包(package)这种概念元素来统筹代码,所有代码功能上的可调用性都定义在包这个级别,如果我们需要调用依赖,那就"导包"就行了,无论是内部的还是外部的,使用im ...

  6. 兔起鹘落全端涵盖,Go lang1.18入门精炼教程,由白丁入鸿儒,全平台(Sublime 4)Go lang开发环境搭建EP00

    Go lang,为并发而生的静态语言,源于C语言又不拘泥于性能,高效却不流于古板,Python灵活,略输性能,Java严谨,稍逊风骚.君不见各大厂牌均纷纷使用Go lang对自己的高并发业务进行重构, ...

  7. 化整为零优化重用,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang函数的定义和使用EP07

    函数是基于功能或者逻辑进行聚合的可复用的代码块.将一些复杂的.冗长的代码抽离封装成多个代码片段,即函数,有助于提高代码逻辑的可读性和可维护性.不同于Python,由于 Go lang是编译型语言,编译 ...

  8. 因势而变,因时而动,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang泛型(generic)的使用EP15

    事实上,泛型才是Go lang1.18最具特色的所在,但为什么我们一定要拖到后面才去探讨泛型?类比的话,我们可以想象一下给小学一年级的学生讲王勃的千古名篇<滕王阁序>,小学生有多大的概率可 ...

  9. 巨细靡遗流程控制,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang流程结构详解EP09

    流程结构就是指程序逻辑到底怎么执行,进而言之,程序执行逻辑的顺序.众所周知,程序整体都是自上由下执行的,但有的时候,又不仅仅是从上往下执行那么简单,大体上,Go lang程序的流程控制结构一共有三种: ...

随机推荐

  1. BUUCTF-qr

    qr 签到题

  2. 关于Vue Element组件el-checkbox与el-select默认选中值的几点注意事项

    el-select 示例: 代码: <el-select v-model="doc.zhic" placeholder="请选择"> <el- ...

  3. 你真的懂Python命名吗?

    转载请注明出处️ 作者:测试蔡坨坨 原文链接:caituotuo.top/7417a7f0.html 大家好,我是测试蔡坨坨. 今天,我们来聊一下Python命名那些事儿. 名为万物之始,万物始于无名 ...

  4. jenkins自动触发构建

    1. 安装jenkins cat /etc/yum.repos.d/jenkins.repo [jenkins] name=Jenkins baseurl=http://pkg.jenkins.io/ ...

  5. .NET混合开发解决方案24 WebView2对比CefSharp的超强优势

    系列目录     [已更新最新开发文章,点击查看详细] WebView2控件应用详解系列博客 .NET桌面程序集成Web网页开发的十种解决方案 .NET混合开发解决方案1 WebView2简介 .NE ...

  6. python——5行代码采集3000+上市公司信息

    毕业季也到了找工作的季节了,很多小伙伴都会一家一家的公司去看,这得多浪费时间啊.今天用Python教大家怎么采集公司的信息,相信大家会很喜欢这个教程的,nice! 基本环境配置 版本:Python3 ...

  7. NC14893 栈和排序

    NC14893 栈和排序 题目 题目描述 给你一个1->n的排列和一个栈,入栈顺序给定 你要在不打乱入栈顺序的情况下,对数组进行从大到小排序 当无法完全排序时,请输出字典序最大的出栈序列 输入描 ...

  8. 015(Power string)(哈希表)

    题目:http://ybt.ssoier.cn:8088/problem_show.php?pid=1457 题目思路: 思路就是预设子串的长度,从1开始,而后一段一段试 试到一个对不上的就打回 如果 ...

  9. 一款 IDEA 插件帮你优雅转化 DTO、VO、BO、PO、DO

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.承认你优秀很难 很多码农,把路走窄了 捧一个,喷一个,很多码农都不会多一点思路看问题,总是 ...

  10. Android刷第三方Recovery &获取root权限

    一.基础环境 Make sure your computer has working adb and fastboot. Setup instructions can be found here. E ...