很多熟悉Go的程序员们都会说到Go是一门很简单的语言,话虽如此,但实际上Go的简单是基于复杂底层的极简包装。

Go在很多地方均做了“隐式”的转换,这也就导致了很多迷惑点,本文总结了Go开发中几个令人迷惑的地方,如有不当之处请指正。

nil究竟是什么

首先明确一点:nil是值而非类型。nil值只能赋值给slice、map、chan、interface和指针。

在Go中,任何类型都会有一个初始值。数值类型的初始值为0,slice、map、chan、interface和指针类型的初始值为nil,对于nil值的变量,我们可以简化理解为初始状态变量。

但nil在实际使用过程中,仍有不少令人迷惑的地方。

var err error
e := &err
if e != nil {fmt.Printf("&err is not nil:%pn", e)
}
// 输出:&err is not nil:0xc0000301f0

err是一个接口类型的变量,其初始值为nil,然后对err进行取址操作会发现能成功取到地址,这就是Go和C++最大的不同之一。有C++基础的人在刚接触Go的时候,自然而然的会认为nil是个空指针类型值,上面的代码力证在Go中,nil只是一个表示初始状态的值

对于slicemapchaninterface,当值为nil时,不具备可写性。

// 1
var s []int
fmt.Printf("%vn", s[0])
// 输出panic// 2
var c chan int
val := <-c
fmt.Printf("%vn", val)
// 输出panic// 3
var m map[int]int
m[1] = 123
// 输出panic

上面3段代码均会出现panic,对于slicemapchan类型的nil值变量,可以理解为可读不可写,只有通过make(new)创建的对象实例满足可写性。

接口的本质

Go官方文档中表示:interface本身是引用类型,即接口类型本身是指针类型。

type Animal interface {Barking()
}type Cat struct {}func (c *Cat) Barking() {fmt.Printf("Meow~~n")
}type Dog struct{}func (d Dog) Barking() {fmt.Printf("W~W~W~n")
}

Cat和Dog类型都实现了Barking接口,需要注意的是,Cat是以指针接收器方式实现Barking接口,Dog是以值传递方式实现Barking接口。在Go中,当调用接口方法时,会自动对指针进行解引用。下面的代码可以证明这一点:

d := &Dog{}
d.Barking()c := Cat{}
c.Barking()
/* 输出:W~W~W~Meow~~
*/

接口的作为函数参数如何传递?

func AnimalBarking(a Animal) {a.Barking()
}

根据上面这段代码,如何调用AnimalBarking方法呢? 首先明确Animal是引用类型(指针),由于接口会自动对传递的指针进行解引用,所以当接口类型作为函数参数传递时,有以下规则: 当以指针接收器实现接口方法时,传递AnimalBarking的参数必须为对象指针。 当以对象接收器实现接口方法时,传递AnimalBarking的参数既可以是对象指针(指针会自动解引用),也可以是对象实例。

下面的代码合法:

d1 := &Dog{}
AnimalBarking(d1)d2 := Dog{}
AnimalBarking(d2)

很多地方都专门指出:指向接口的指针是无意义的。事实是否真的是这样子?

我们知道在Go中有专门的接口类型,接口类型在runtime中大概是这样:

type iface struct {tab  *itab  // 8bytesdata unsafe.Pointer // 8bytes
}

对于接口而言,可能会存在这样的代码:

type Handler interface {Func()
}type Server struct{}func (s *Server) Func() {fmt.Printf("*Server.Funcn")
}func Func(handler *Handler) {handler.Func()
}

上面的代码在Go1.13下无法通过编译:handler.Func undefined (type *Handler is pointer to interface, not interface)。 这里要清楚,指向结构的指针和指向接口的指针是两回事,接口直接存放了结构的类型信息以及结构指针。在Go中,无法为实现了接口方法的struct生成指向接口的指针并调用接口方法。

但当修改为如下代码则可编译通过:

func Func(handler *Handler) {if handler != nil && (*handler) != nil {(*handler).Func()}
}

从Go设计角度出发的。Go里面的interface是个结构,保存了类型信息和数据拷贝,所谓的无意义我更多是认为Go设计上的理念,因为nil值的存在且又是指针,那么问题就回落到有无办法一步判断指针和值是否合法。显然这种代码有违Go的设计初衷,但指向接口的指针这种东西确实存在于Go中,对此的解释在这里:https://golang.org/doc/faq#pointer_to_interface

【指向接口的指针是无意义的】这句话确实有点绝对,但确实是存在且意义并不大的东西。

关于接口的延申阅读:Go interface

defer机制

在Go中提供defer这样优雅的函数退出后“收尾”操作,但很多人会忽略defer机制中的一点:defer在声明时引用到的变量就已被“固定”下来。下面的代码:

var ErrNotFound error = errors.New("Not found")
func TestDefer1() error {var err errordefer fmt.Printf("TestDefer1 err: %vn", err)// ...err = ErrNotFoundreturn err
}/* 输出:TestDefer1 err: <nil>
*/

当defer声明func时,情况不一样了:

func TestDefer2() error {var err errordefer func() {fmt.Printf("TestDefer2 err: %vn", err)}()// ...err = ErrNotFoundreturn err
}/* 输出:TestDefer2 err: Not found
*/

所以:defer在声明语句时引用到的变量就已被实时编译

读写chan是否应该加锁

先说答案:不需要。具体原因可以从runtime/chan.go中知道。chan的原始struct如下:

type hchan struct {qcount   uint           // total data in the queuedataqsiz uint           // size of the circular queuebuf      unsafe.Pointer // points to an array of dataqsiz elementselemsize uint16closed   uint32elemtype *_type // element typesendx    uint   // send indexrecvx    uint   // receive indexrecvq    waitq  // list of recv waiterssendq    waitq  // list of send waiters// lock protects all fields in hchan, as well as several// fields in sudogs blocked on this channel.//// Do not change another G's status while holding this lock// (in particular, do not ready a G), as this can deadlock// with stack shrinking.lock mutex
}

chanstruct定义上来看,有lock字段,再来看看chan的读写实现(简化代码):

func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {// ...lock(&c.lock)// ...unlock(&c.lock)// ...
}func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {// ...lock(&c.lock)// ...unlock(&c.lock)// ...
}

chan的实现源代码看到,其读写内部均加了锁,实际上在关闭chan时内部也是加锁了,所以实际应用中,多个coroutine同时读写chan时不需要加锁。

go 定义一个结构体并赋初始值_Go中必须谈论的四个迷点相关推荐

  1. ACMNO.42 C语言-第几天 定义一个结构体变量(包括年、月、日)。计算该日在本年中是第几天,注意闰年问题。利用结构体的在最下面

    题目描述 定义一个结构体变量(包括年.月.日).计算该日在本年中是第几天,注意闰年问题. 输入 年月日 输出 当年第几天 样例输入 2000 12 31 样例输出 366 来源/分类 C语言 题目截图 ...

  2.  一个复数可以用实部和虚部两部分组成,a1 = 1.2 + 3.4i,其中1.2是实部,3.4是虚部。定义一个结构体ComplexNumber,包含imaginary和real两个成员变量,能够表示

    题目原文     一个复数可以用实部和虚部两部分组成,a1 = 1.2 + 3.4i,其中1.2是实部,3.4是虚部.定义一个结构体ComplexNumber,包含imaginary和real两个成 ...

  3. MDK keil中在定义一个结构体的时候加点后面不出现结构体当中变量的提示

    MDK keil中在定义一个结构体的时候加点后面不出现结构体当中变量的提示 解决办法: 1.确保已经将顶层头文件stm32f10x.h添加至当前你所写的文件内: 2.检查你自己写的文件是否已经加载到工 ...

  4. 定义一个结构体student,存储学生的学号、名字、性别和年龄,读入每个学生的所有信息,保存在结构体中,并输出。

    题目描述 定义一个结构体student,存储学生的学号.名字.性别和年龄,读入每个学生的所有信息,保存在结构体中,并输出.结构体student的定义如下: struct student { int n ...

  5. 定义一个结构体变量(包括年、月、日)。计算该日在本年中是第几天,注意闰年 问题。 写一个函数days,实现上述计算。由主函数将年、月、日传递给days函数,计算后将 日子数传回主函数输出

    /*定义一个结构体变量(包括年.月.日).计算该日在本年中是第几天,注意闰年 问题. 写一个函数days,实现上述计算.由主函数将年.月.日传递给days函数,计算后将 日子数传回主函数输出*/#in ...

  6. 定义一个结构体变量(包括年、月、日)。计算该日在本年中是第几天?注意闰年问题

    定义一个结构体变量(包括年.月.日).计算该日在本年中是第几天?注意闰年问题 #include<stdio.h>struct Date{int year;int month;int day ...

  7. 题9.1:定义一个结构体变量(包括年、月、日)。计算该日在本年中是第几天, 注意闰年问 题。

    题目 本题是谭浩强<C程序设计课后习题>题9.1. 题目: 1.定义一个结构体变量(包括年.月.日).计算该日在本年中是第几天, 注意闰年问 题. 以下是本篇文章正文内容,欢迎朋友们进行指 ...

  8. 定义一个结构体类型Point,包含数据成员x和y,它们是平面坐标系下的坐标点(x,y),求两点间的距离

    题目描述:定义一个结构体类型Point,包含数据成员x和y,它们是平面坐标系下的坐标点(x,y).编写如下函数: (1)struct Point Input():在函数中输入一个坐标点的值,并返回该值 ...

  9. 定义一个结构体变量(包括年、月、日)。计算该日在本年中是第几天,注意闰年问题

    定义一个结构体变量(包括年.月.日) 计算该日在本年中是第几天,注意闰年问题 代码如下: #include<stdio.h> struct daliy {int year;int mont ...

最新文章

  1. AI战场,李彦宏马化腾马云都在频频刷脸,周鸿祎和他的360在想啥呢?
  2. 检查MySQL主从数据一致性
  3. 【C#】C#实现鼠标滚轮的图像居中缩放
  4. BGP 路由属性 公认可选 LOCAL_PREF
  5. AcWing479.加分二叉树(区间DP)题解
  6. 漫画:从打牌到 map-reduce 工作原理解析
  7. 美团点评架构再调整,王兴凭什么同时杠上阿里滴滴饿了么
  8. revit API 实现可停靠窗口
  9. sqlserver2005安装(附加sqlserver2005 和 sqlserver2005 sp4补丁,完整安装包)
  10. 机器学习常用小代码块
  11. 服务器运维技术-02 Linux基本使用
  12. 最近三年收藏网站,做一次云备份
  13. 天大的本事,顶不上一张会说话的嘴
  14. 解决页面下载文件,资源不存在页面出现空白的问题。
  15. 绝对布局(AbsoluteLayout)的简单使用
  16. 王牌流量爆刷器 流量提升工具 网站刷新 增加浏览量 王牌软件
  17. 【Windows编程学习笔记】1:实现学生信息管理系统的简易页面
  18. 计算机学习网址【快乐学习,持续更新。。。】
  19. 微信群裂变有哪些技巧?这款社群裂变工具不要说你还不知道!
  20. 基于S3C2410平台移植Linux 2.6内核指南

热门文章

  1. 子数组的最大累加和问题
  2. 论文笔记:Spatial-Temporal Map Vehicle Trajectory Detection Using Dynamic Mode Decomposition and Res-UNe
  3. python函数整理
  4. LeetCode-二分查找-278. 第一个错误的版本
  5. Cracer渗透视频课程笔记——基础知识(2)
  6. 算法工程师和算法框架开发,谁会代表未来?
  7. 提高开源项目逼格-为你的github项目添加Travis CI
  8. python2 'str' object has no attribute 'decode'
  9. ZooKeeper Recipes and Solutions
  10. jodd-servlet工具集锦