内容简介:前言: 大家好,我是asong,这是我的第二篇原创文章。上一文介绍了切片、变量声明、defer三个知识点(回顾上文,关注公众号即可进行阅读),这一文将继续介绍其他Go语言特性,废话不多说,直接上干货。在Go语言中只有一种参数传递的规则,那就是值拷贝,其包含两种含义:我们在使用过程中会发现有时明明是值拷贝的地方,结果却修改了变量的内容,有以下两种情况:
前言: 大家好,我是asong,这是我的第二篇原创文章。上一文介绍了切片、变量声明、defer三个知识点(回顾上文,关注公众号即可进行阅读),这一文将继续介绍其他 Go 语言特性,废话不多说,直接上干货。
1. 指针和引用
在Go语言中只有一种参数传递的规则,那就是值拷贝,其包含两种含义:
- 函数参数传递时使用的值拷贝
- 实例赋值给接口变量,接口对实例的引用是值拷贝
我们在使用过程中会发现有时明明是值拷贝的地方,结果却修改了变量的内容,有以下两种情况:
- 直接传递的是指针。指针传递同样是值拷贝,但指针和指针副本的值指向的地址是同一个地方,所以能修改实参
- 参数是复合数据类型,这些符合数据类型内部有指针类型的元素,此时参数的值拷贝并不影响指针的指向。
在Go语言中,复合类型chan、map、slice、interface内部都是通过指针指向具体的数据,这些类型的变量在作为函数参数传递时,实际上相当于指针的副本。我们可以通过查看源码,看一看他们的底层数据结构:
- map的底层数据结构:
//src/runtime/map.go1.14 // A header for a Go map. type hmap struct { // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go. // Make sure this stays in sync with the compiler's definition. count int // # live cells == size of map. Must be first (used by len() builtin) flags uint8 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details hash0 uint32 // hash seed buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) extra *mapextra // optional fields } 复制代码
通过源码我们可以分析,其通过buckets指针来间接引用map中的存储结构。 2. slice的底层数据结构:
//src/reflect/value.go1.14 // sliceHeader is a safe version of SliceHeader used within this package. type sliceHeader struct { Data unsafe.Pointer Len int Cap int } 复制代码
slice则采用uinptr指针指向底层存放数据的数组。 3. interface的底层数据结构如下:
//src/reflect/value.go1.14 // nonEmptyInterface is the header for an interface value with methods. type nonEmptyInterface struct { // see ../runtime/iface.go:/Itab itab *struct { ityp *rtype // static interface type typ *rtype // dynamic concrete type hash uint32 // copy of typ.hash _ [4]byte fun [100000]unsafe.Pointer // method table } word unsafe.Pointer } // emptyInterface is the header for an interface{} value. type emptyInterface struct { typ *rtype word unsafe.Pointer } 复制代码
我们可以看到接口内部通过一个指针指向实例值或地址的副本。 4. chan的底层数据结构如下:
//src/runtime/chan.go1.14 type hchan struct { qcount uint // total data in the queue dataqsiz uint // size of the circular queue buf unsafe.Pointer // points to an array of dataqsiz elements elemsize uint16 closed uint32 elemtype *_type // element type sendx uint // send index recvx uint // receive index recvq waitq // list of recv waiters sendq 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 } 复制代码
通过源码我们可以看出,通道元素的存放地址由buf指针确定,chan内部的数据也是间接通过指针访问的。
2. 函数
Go语言支持匿名函数,其函数名和匿名函数字面量的值有3层含义:
-
类型信息,表明其数据类型是函数类型
-
函数名代表函数的执行代码的起始位置
-
可以通过函数名进行函数调用,函数调用格式为 func_name(param_list)。在底层执行层面包含以下4部分内容。
-
准备好参数
-
修改PC值,跳转到函数代码起始位置开始执行
-
复制值到函数的返回值栈区
-
通过RET返回到函数调用的下一条指令处继续执行。
2). 函数的方法设计 我们在开发时,有时内部会实现两个"同名"的函数或方法,一个首字母大写,用于导出API供外部调用;一个首字母小写,用于实现具体逻辑。一般首字母大写的函数调用首字母小写的函数,同时包装一些功能;首字母小写的函数负责更多的底层细节。 大部分情况下我们不需要两个同名且只是首字母大小写不同的函数,只有在函数逻辑很复杂,而且函数在包的内、外部都被调用的情况下,才考虑拆分为两个函数进行实现。一方面减少单个函数的复杂性,另一方面进行调用隔离。
这种编程方法在database/sql库中体现较明显,有兴趣的可以查看这一部分的源码。 3) 多值返回函数设计 Go语言支持多值返回函数,这里不对多值返回函数基础使用进行介绍,这里只介绍多值返回函数的推荐编程风格方法。 多值返回函数里如果有error或bool类型的返回值,则应该将error或bool作为最后一个返回值。这是一种编程风格,没有对错。Go标准库的写法也遵循这样的规则。当大多数人都使用、遵循这种方法时,如果有人不遵循这种"潜规则",则写出的代码会让别人读起来就会很别扭。所以推荐你们开发时这样进行书写。示例如下:
func testBool() (int ,bool){} func testError() (int,error){} 复制代码
3. 代码风格
Go作为新世纪开发的一门语言,其作者在代码干净上有了近乎苛刻的要求,有如下几方面的体现: 1) 编译器不能通过未使用的局部变量。 2)"import"未使用的包同样通不过编译。 3)所有的控制结构、函数和方法定义的"i"放到行尾,而不能另起一行。 4)提供go fmt工具格式化代码,使所有的代码风格保持统一。 Go支持使用comma,ok表达式 常见的几个comma,ok 表达式如下。
1. 读取chan值读取已经关闭的通道,不会阻塞,也不会引起panic,而是一直返回该通道的零值。若判断通道是否已经关闭有两种方法:一种是读取通道的comma,ok 表达式,如果通道已经关闭,则ok的返回值是false,另一种就是通过range循环迭代。看下面的示例:
import "fmt" func main() { c := make(chan int) go func() { c <- 1 c <- 2 close(c) }() for{ v,ok := <-c if ok{ fmt.Println(v) }else { break } } /* for v := range c{ fmt.Println(v) } */ } 复制代码
- 获取map值 获取map中不存在键的值不会发生异常,而是会返回值类型的零值,如果想确定map中是否存在key,则可以使用获取map值的comma,ok语法。示例如下:
import "fmt" func main() { m := make(map[string]string) v,ok := m["test"] //通过ok进行判断 if !ok{ fmt.Println("m[test] is nil") }else { fmt.Println("m[test] =",v) } } 复制代码
- 类型断言 类型断言,是Go语言中一个难点。有一点难理解。这一文将不详细介绍用法,后面将会专门写一篇文章进行详细的介绍。 接口断言通常可以使用comma,ok语句来确定接口是否绑定某个实例类型,或者判断接口绑定的实例类型是否实现另一个接口。可以看src/net/http/request.go中部分代码如下:
858 rc, ok := body.(io.ReadCloser) 1191 if _, ok := r.Body.(*maxBytesReader); !ok { 复制代码
好啦,本文到此结束啦,基本对Go语言基于其他语言的不同做了一个介绍,因为我也是一个新手,理解的还不是很到位,也在努力学习中,有错误或者有需要更改的地方,请联系我,非常感谢。同时再一次推荐我的公众号:Golang梦工厂,我会不断发表关于Golang方面的知识,面试、个人理解等多个方面,一定对你受益匪浅的。公众号搜索:Golang梦工厂,或者直接扫描下方二维码即可。
欢迎关注我们的微信公众号,每天学习Go知识
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 掌握这些 Go 语言特性,你的水平将提高 N 个档次(二)
- 掌握这些 Go 语言特性,你的水平将提高 N 个档次(一)
- [译] 做好这几件事,代码质量可以提升一个档次
- 引起相变的无序结构域(IDRs)怎么预测?跟踪热点,提升文章档次!
- 如何优雅地向别人介绍高端大气上档次的Git
- 『互联网架构』软件架构-redis特性和集群特性(中)(49)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Linux二进制分析
[美]瑞安 奥尼尔 / 棣琦 / 人民邮电出版社 / 2017-12-1 / CNY 59.00
二进制分析属于信息安全业界逆向工程中的一种技术,通过利用可执行的机器代码(二进制)来分析应用程序的控制结构和运行方式,有助于信息安全从业人员更好地分析各种漏洞、病毒以及恶意软件,从而找到相应的解决方案。 本书是一本剖析Linux ELF工作机制的图书,共分为9章,其内容涵盖了Linux环境和相关工具、ELF二进制格式、Linux进程追踪、ELF病毒技术、Linux二进制保护、Linux中的E......一起来看看 《Linux二进制分析》 这本书的介绍吧!