golang快速入门-1

bytemode · · 795 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。
## 压缩包安装 分别对应mac、linux、windows的golang包,基本选择amd64位的 go1.x.darwin-amd64.tar.gz go1.x.linux-amd64.tar.gz go1.x.windows-amd64.zip ### linux安装 1. 解压之后/usr/local/go存在bin src doc等目录 tar -C /usr/local -xzf go1.9.linux-amd64.tar.gz 2. 设置环境变量 /etc/profile是针对所有用户都有效的;$HOME/.profile是针对当前用户有效的 source ~/.profile命令即可生效 ``` wget https://studygolang.com/dl/golang/go1.13.5.linux-amd64.tar.gz tar -C /usr/local -zxvf go1.13.5.linux-amd64.tar.gz vim /etc/profile export GOROOT=/usr/local/go export GOPATH=/src/gopath export GOBIN=$GOPATH/bin export PATH=$PATH:$GOROOT/bin export PATH=$PATH:$GOPATH/bin export GOPROXY=https://goproxy.io,https://goproxy.cn,direct source /etc/profile go version ``` ### mac安装和linux相同 ### windows设置环境变量就可以 ### 创建项目 ``` cd /src/goauto mkdir /src/goauto/testgo go mod init testgo ``` ## 构建执行 main.go ``` package main func main(){ } ``` 包含packate main和main函数即可编译为可执行文件。 编译运行 `go run --work main.go` 它使用一个临时目录来构建程序,执行完然后清理掉临时目录 ## 关于文档 `godoc -http=:6060` `http://localhost:6060` godoc可以本地启动文档服务 ## 包和文件 Go语言中的包和其他语言的库或模块的概念类似,目的都是为了支持模块化、封装、单独编译和代码重 用。一个包的源代码保存在一个或多个以.go为文件后缀名的源文件中。 当你想去命名一个包的时候,可以通过 package 关键字,提供一个值,而不是完整的层次结构。当你想去导入一个包的时候,你需要指定完整路径。 如果一个字段名以一个小写字母开始,只有包内的代码可以访问他们 如果一个名字是大写字母开头的,那么该名字是导出的(包外部可见) 每个文件都可以包含多个init初始化函数 ``` func init() { } ``` 在每个文件中的init初始 化函数,在程序开始执行时按照它们声明的顺序被自动调用。 每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。 初始化工作是自下而上进行的, main包最后被初始化.在main函数执行之前,所有依然的包都已经完成初始化工作了. import导入包的用法: ``` import "github.com/tidwall/gjson" //通过包名gjson调用导出接口 import json "github.com/tidwall/gjson" //通过别名json调用gjson import . "github.com/tidwall/gjson" //.符号表示,对包gjson的导出接口的调用直接省略包名 import _ "github.com/tidwall/gjson" //_ 仅仅会初始化gjson,如初始化全局变量,调用init函数 ``` ## 作用域 一个声明语句将程序中的实体和一个名字关联,比如一个函数或一个变量。声明语句的作用域是指源代 码中可以有效使用这个名字的范围。 不要将作用域和生命周期混为一谈. 任何在在函数外部(也就是包级语法域)声明 的名字可以在同一个包的任何源文件中访问的。对于导入的包,例如tempconv导入的fmt包,则是对应源 文件级的作用域,因此只能在当前的文件中访问导入的fmt包,当前包的其它源文件无法访问在当前源文 件导入的包 在包级别,声明的顺序并不会影响作用域范围,因此一个先声明的可以引用它自身或者是引用后面的一 个声明,这可以让我们定义一些相互嵌套或递归的类型或函数。 ## 关键字 内建常量: true false iota nil 内建类型: int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex128 complex64 bool byte rune string error 内建函数: make len cap new append copy close delete complex real imag panic recover ## 常量 常量表达式的值在编译期计算,而不是在运行期。每种常量的潜在类型都是基础类型:boolean、string或数字 ``` const x, y int = 1, 2 // 多常量初始化 const s = "Hello, KeKe!" // 类型推断 const ( B = 1 << (10*iota) //iota在const关键字出现时将被重置为0,const中每新增一行常量声明将使iota计数一次 KB MB GB TB PB ) ``` ## 变量 Go的基本类型有: * bool * string * int int8 int16 int32 int64 * uint uint8 uint16 uint32 uint64 uintptr * byte // uint8 的别名 * rune // int32 的别名 代表一个Unicode码点 * float32 float64 * complex64 complex128 变量的显式声明的语法一般是: `var 变量名字 类型 = 表达式` 通常情况下“类型”或“= 表达式”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串,接口或引用类型(包括slice、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。 简短声明变量,它以“名字 := 表达式”形式声明变量,变量的类型根据表达式来自动推导.广泛用于大部分的局部变量的声明和初始化. 多个变量赋值的时候,只要其中有一个变量是新的,就可以使用:=. go 支持多个变量同时赋值(使用 = 或者 :=). ``` var a int = 1 var b = 2 var c int d := 3 d++ d-- var a,b,c,d = 1,2,3.14,4 power := 1000 name, power := "Goku", 9000 ``` var形式的声明语 句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。 对于在包一级声明的变量来说,它们 的生命周期和整个程序的运行周期是一致的。而相比之下,在局部变量的声明周期则是动态的 ## 类型 类型 变量或表达式的类型定义了对应存储值的属性特征,类型声明语句一般出现在包一级,因此如果新创建的类型名字的首字符大写,则在外部包也可以使用。 type 类型名字 底层类型 `type Precision float64 #精确度` ### 整型 Go语言同时提供了有符号和无符号类型的整数运算。 有符号整形数类型: ``` int8,长度:1字节, 取值范围:(-128 ~ 127) int16,长度:2字节,取值范围:(-32768 ~ 32767) int32,长度:4字节,取值范围:(-2,147,483,648 ~ 2,147,483,647) int64.长度:8字节,取值范围:(-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807) ``` 无符号整形数类型: ``` uint8,长度:1字节, 取值范围:(0 ~ 255) uint16,长度:2字节,取值范围:(0 ~ 65535) uint32,长度:4字节,取值范围:(0 ~ 4,294,967,295) uint64.长度:8字节,取值范围:(0 ~ 18,446,744,073,709,551,615) ``` 字符是 UTF-8 编码的 Unicode 字符,Unicode 为每一个字符而非字形定义唯一的码值(即一个整数),例如 字符a 在 unicode 字符表是第 97 个字符,所以其对应的数值就是 97,也就是说对于Go语言处理字符时,97 和 a 都是指的是字符a,而 Go 语言将使用数值指代字符时,将这样的数值称呼为 rune 类型。 rune类型是 Unicode 字符类型,和 int32 类型等价,通常用于表示一个 Unicode 码点。rune 和 int32 可以互换使用。 一个Unicode代码点通常由"U+"和一个以十六进制表示法表示的整数表示,例如英文字母'A'的Unicode代码点为"U+0041"。 此外rune类型的值需要由单引号"'"包裹 #### 整形运算 ``` & 位运算 AND 与 只有两个数都是1结果才为1 | 位运算 OR 或 两个数有一个是1 结果就是1 ^ 位运算 XOR 非 一元运算符是按位取反 二元运算符是异或 相同为0不同为1 &^ 位清空 (AND NOT) 位清零 相同位清零,相异的位保留 << 左移 右边空出的位用0填补 >> 右移 左边空出的位用0或者1填补。正数用0填补,负数用1填 ``` ### 浮点型 浮点型。float32 精确到小数点后 7 位,float64 精确到小数点后 15 位 ### 布尔型 在Go语言中,布尔值的类型为 bool,值是 true 或 false,布尔可以做3种逻辑运算,&&(逻辑且),||(逻辑或),!(逻辑非),布尔类型的值不支持其他类型的转换. ### 字符串 字符串值是不可变的.原生表示法和解释型表示法。原生表示法,需用用反引号"`"把字符序列包起来,如果用解释型表示法,则需要用双引号"""包裹字符序列 ``` var str1 string = "abc\n" //对于\n会转义 var str2 string = 'abc\n' //不会对特使字符转义 ``` ## 复合数据类型 ### 数组 每个数组元素都是完全相同的类型,数组的每个元素都被初始化为元素类型对应的零值,数组可以直接进行比较,当数组内的元素都一样的时候表示两个数组相等。 ``` var arr1 [3]int = [3]int{1, 2, 3} //数组的长度位置出现的是“...”省略号,则表示数组的长度是根据初始化值的 个数来计算 arr2 := [...]int{1, 2, 3} fmt.Printf("%T\n", arr2) // "[3]int" arr3 := [3]int{1, 2, 3} //数组遍历 value是一份复制 range时重新赋值 不能修改数组 for index, value := range arr3 { fmt.Println(idnex, value) } //使用下标索引到数组进行修改 for index:= range arr3 { arr3[index] = 1 } for i:=0; i < len(arr3); i++ { arr[i] = 1 } ``` 数组可以作为函数的参数传入,但由于数组在作为参数的时候,其实是进行了拷贝,这样在函数内部改变数组的值,是不影响到外面的数组的值得。 如果想要改变就只能使用指针,在函数内部改变的数组的值,也会改变外面的数组的值. ``` func ArrIsArgs(arr *[4]int){ arr[0] = 20 } m := [...]int{1, 2, 3, 4} ArrIsArgs(&m) ``` 通常这样的情况下都是用切片来解决,而不是用数组。 这里的* 和&的区别: & 是取地址符号 , 即取得某个变量的地址 , 如 ; &a *是指针运算符 , 可以表示一个变量是指针类型 , 也可以表示一个指针变量所指向的存储单元 , 也就是这个地址所存储的值. 字符串和字节数组转换: ``` stra := "the spice must flow" byts := []byte(stra) strb := string(byts) ``` 数组是值类型。将一个数组赋值给另一个会赋值所有元素。 特别是,如果传递一个数组给函数,它将接收这个数组的副本,而不是指针。 数组的长度也是其类型的一部分。 [10]int 和 [20]int 是不同的两种类型。 ``` func Sum(a *[3]float64) (sum float64) { for _, v := range *a { sum += v } return } array := [...]float64{7.0, 8.5, 9.1} x := Sum(&array) // Note the explicit address-of operator ``` ### Slice lice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已. 支持make、 append、 copy、len、 cap、range、[index]、 切片操作. 一个Slice由三部分组成:指针,长度和容量。内置的len和cap函数可以分别返回slice的长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量 ``` arrVar := [4]int{1, 2, 3 } sliceVar := arrVar[1:3] //基于数组创建 索引1到索引2 左闭右开区间 var p *[]int = new([]int) //分配slice结构内存 var m []int = make([]int, 100) //m指向一个新分配的有100个整数的数组 ``` ``` new 分配;make 初始化 new(T) 返回 *T 指向一个零值 T make(T) 返回初始化后的 T make([]T, len) make([]T, len, cap) // same as make([]T, cap)[:len] ``` 主流的创建切片的方式`make([]int)` ``` slice1 := make([]int,5)//创建一个元素个数5的slice,cap也是5 slice2 := make([]int,5,10)//创建一个元素个数5的slice,cap是10 slice3 := []int{1,2,3,4,5}//创建一个元素个数为5的slice,cap是5 var slice []int //创建一个空的slice,cap和len都是0 var s []int // len(s) == 0, s == nil s = nil // len(s) == 0, s == nil s = []int(nil) // len(s) == 0, s == nil s = []int{} // len(s) == 0, s != nil ``` 此外Slice切片还有append函数用于向slice追加元素.虽然Slice是可以动态扩展的。但Slice的动态扩展是有代价的,也就是说如果在确定大小的前提下,最好是设置好slice的cap大小. append的底层原理就是当slice的容量满了的时候,重新建立一块内存,然后将原来的数据拷贝到新建的内存。所以说容量的扩充是存在内存的建立和复制的. copy函数的第一个参数是要复制的目标slice,第二个参数是源slice,目标和源的位置顺序和dst = src赋值语句是一致的. ``` var x []int x = append(x, 1) x = append(x, 2, 3) x = append(x, 4, 5, 6) x = append(x, x...) // append the slice x fmt.Println(x) // "[1 2 3 4 5 6 1 2 3 4 5 6]" slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中 copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置 var stack interface stack = append(stack, v) // push v top := stack[len(stack)-1] // top of stack stack = stack[:len(stack)-1] // pop //delete func remove(slice []int, i int) []int { copy(slice[i:], slice[i+1:]) return slice[:len(slice)-1] } type LinesOfText [][]byte text := LinesOfText{ []byte("Now is the time"), []byte("for all good gophers"), []byte("to bring some fun to the party."), } // 分配底层切片. picture := make([][]uint8, YSize) // y每一行的大小 //循环遍历每一行 for i := range picture { picture[i] = make([]uint8, XSize) } ``` ### Map 映射Map是一个存储键值对的无序集合, 支持make、delete、len 、range、Value-OK操作 ``` var m map[string] string // 声明 key 是string value 是string类型 m := make(map[string]int) // 创建 key 是string value 是int m := map[string]int{ "keke": 001, "jame": 002,} m := make(map[string]int) m["kek"] = 001 m["sss"] = 002 power, exists := m["vegeta"] delete(m, "sss") //删除m["sss"] ``` map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作 ``` _ = &ages["keke"] // compile error: cannot take address of map element ``` 遍历的顺序是随机的,每一次遍历的顺序都不相同,map类型的零值是nil,也就是没有引用任何映射Map ``` import "sort" var ages map[string]int fmt.Println(ages == nil) // "true" fmt.Println(len(ages) == 0) // "true" ages = make(map[string]int) for k, v := range ages { //在每次迭代时设置name和age变量,它们对应下一个键/值对 fmt.Printf("%s\t%d\n", k, v) } var names []string for name := range ages { names = append(names, name) } sort.Strings(names) for _,value := range names { fmt.Printf("%s\t%d\n", name, ages[name]) } ``` ### 结构体 通过type定义一个新的数据类型,然后是新的数据类型名称Rectangle,最后是struct关键字,表示这个高级数据类型是结构体类型。 ``` type Rectangle struct { Width float64 Length float64 } // 构造器 func NewRectangle(width float64, length float64) *Rectangle{ return &Rectangle( Width: width, Length: length, ) } func(r *Rectangle) Super(){ s.Width += 100 } func(r Rectangle) SuperEx(){ s.Width += 100 } var r Rectangle r.width = 100 r.length = 200 var r = Rectangle{width: 100, length: 200} var r = Rectangle{100, 200} test := Rectangle{ width:100, length:200, //此处逗号是必须的 } var r = new(Rectangle) //创建一个结构体指针Rectangle r.width = 100 r.length = 200 //new(X) 的结果与 &X{} 相同 第二种可读性比较好 r1 := new(Rectangle) r2 := &Rectangle{} ``` 结构体参数传递方式,Go函数的参数传递方式是值传递,这句话对结构体也是适用的 通常有人就会有疑问觉得不知道啥时候使用指针,啥时候不使用指针,其实呢使不使用指针,取决于你是否试图在函数内部改变传递进来的参数的值。 一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身(该限制同样适应于数组)。但是S类型的结构体可以包含*S指针类型的成员,这可以让我们创建递归的数据结构,比如链表和树结构等。 结构体成员名字是以大写字母开头的,那么该成员就是导出的,就是说,在其他包中可以进行读写.小写字母开头是当前包的私有的,函数定义也是类似的. ``` type tree struct { value int left, right *tree } t := &tree{ value:1, left:&tree{ value: 2, left: nil, right: nil, }, right: nil, } ``` #### 结构体嵌套 ``` type Point struct { X, Y int } type Circle struct { Center Point Radius int } type Wheel struct { Circle Circle Spokes int } var w Wheel w.Circle.Center.X = 8 w.Circle.Center.Y = 8 w.Circle.Radius = 5 w.Spokes = 20 ``` 匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针.直接访问叶子属性而不需要给出完整的路径. struct不仅仅能够将struct作为匿名字段,自定义类型、内置类型都可以作为匿名字段. ``` type Circle struct { Point // 匿名字段,struct Radius int } type Wheel struct { Circle // 匿名字段,struct Spokes int } var w Wheel w.X = 8 // equivalent to w.Circle.Point.X = 8 w.Y = 8 // equivalent to w.Circle.Point.Y = 8 w.Radius = 5 // equivalent to w.Circle.Radius = 5 w.Spokes = 20 ``` #### 结构体TAG 在Golang中结构体和数据库表的映射关系的建立是通过struct Tag来实现的 ``` type User struct { Name string `json:"name"` //这引号里面的就是tag Passwd int `json:"passwd"` } func main() { user := &User{"keke", 123456} s := reflect.TypeOf(user).Elem() //通过反射获取type定义 for i := 0; i < s.NumField(); i++ { fmt.Println(s.Field(i).Tag.Get("json")) //将tag输出出来 } } ``` ### interface 只要某个类型拥有该接口的所有方法签名,就算实现该接口,无需显示声明实现了那个接口 struct包含interface之后,并不需要实现interface的接口,也能成为 interface接口类 接口查询 if sameValue, ok := sameInterface.(SameType); ok {} ``` type TypeCalculator interface { TypeCal() string } type Worker struct { Type int Name string } type Student struct { Name string } func (w Worker) TypeCal() string { return w.Name +"是蓝翔毕业的员工" } func (s Student) TypeCal() string { return s.Name + "还在蓝翔学挖掘机炒菜" } func main() { worker := Worker{Type:0, Name:"小华"} student := Student{Name:"小明"} workers := []TypeCalculator{worker, student} for _, v := range workers { fmt.Println(v.TypeCal()) } } //运行效果 //小华是蓝翔毕业的员工 //小明还在蓝翔学挖掘机炒菜 type newEr interface { New() } type testInterface interface { newEr Done() <-chan struct{} } type kkTest struct { testInterface } func NewTest() newEr { return kkTest{} } func main() { kk := NewTest() i,ok := kk.(testInterface) fmt.Println(i,ok) } ``` ### JSON Go中提供的处理json的标准包是 encoding/json,主要使用的是以下两个方法: ``` // 序列化 结构体=> json func Marshal(v interface{}) ([]byte, error) // 反序列化 json=>结构体 func Unmarshal(data []byte, v interface{}) error ``` 针对JSON的输出,我们在定义struct tag的时候需要注意的几点是: - 字段的tag是"-",那么这个字段不会输出到JSON - tag中带有自定义名称,那么这个自定义名称会出现在JSON的字段名中 - tag中如果带有"omitempty"选项,那么如果该字段值为空,就不会输出到JSON串中 - 如果字段类型是bool, string, int, int64等,而tag中带有",string"选项,那么这个字段在输出到JSON的时候会把该字段对应的值转换成JSON字符串 ``` type Server struct { // ID 不会导出到JSON中 ID int `json:"-"` // ServerName2 的值会进行二次JSON编码 ServerName string `json:"serverName"` ServerName2 string `json:"serverName2,string"` // 如果 ServerIP 为空,则不输出到JSON串中 ServerIP string `json:"serverIP,omitempty"` } ``` Marshal函数只有在转换成功的时候才会返回数据,但是我们应该注意下: - JSON对象只支持string作为key,所以要编码一个map,那么必须是map[string]T这种类型(T是Go语言中任意的类型) - Channel, complex和function是不能被编码成JSON的 - 嵌套的数据是不能编码的,不然会让JSON编码进入死循环 - 指针在编码的时候会输出指针指向的内容,而空指针会输出null - 解析到interface 解析到interface 在Go中Interface{}可以用来存储任意数据类型的对象,这种数据结构正好用于存储解析的未知结构的json数据的结果。JSON包中采用map[string]interface{}和[]interface{}结构来存储任意的JSON对象和数组。Go类型和JSON类型的对应关系如下: ``` bool 代表 JSON booleans, loat64 代表 JSON numbers, string 代表 JSON strings, nil 代表 JSON null. ``` b := []byte(`{"Name":"Ande","Age":10,"Hobby":"Football" 解析到接口中 ``` var f interface{} err := json.Unmarshal(b, &f) ``` 在这个接口f里面存储了一个map类型,他们的key是string,值存储在空的interface{}里 ``` f = map[string]interface{}{ "Name": "Ande", "Age": , "Hobby":"Football" } ``` 通过断言的方式我们把结构体强制转换数据类型: ``` m := f.(map[string]interface{}) //断言方式实现类型转化 ``` 通过断言之后,我们就可以通过来访问里面的数据: ``` for k, v := range m { switch vv := v.(type) { //取出type v.(type) case string: fmt.Println(k, "is string", vv) case int: fmt.Println(k, "is int", vv) case float64: fmt.Println(k,"is float64",vv) case []interface{}: fmt.Println(k, "is an array:") for i, u := range vv { fmt.Println(i, u) } default: fmt.Println(k, "is of a type I don't know how to handle") } } ``` ## 函数 type Add func(a int, b int) int 它可以用在任何地方 -- 作为字段类型,参数或者返回值 ``` type Add func(a int, b int) int func process(add Add) int{ return add(1, 2) } fmt.Print(process(func(a int, b int)int{ return a + b })) ``` - 普通的带有名字的函数. - 匿名函数或者lambda函数. - 方法(Methods). 函数可以有多个返回值和参数, ...是变参,表示可以接受多个参数 ``` func patent(a,b,c ...int) (int, int, int) ``` GO中包内的函数,类型和变量的对外可见性(可访问性)由函数名,类型和变量标示符首字母决定,大写对外可见,小写对外不可见 概括来说: 公有函数的名字以大写字母开头;私有函数的名字以小写字母开头. 函数调用 ``` package.Function(arg1, arg2, …, argn) ``` 包名.函数名 函数被调用的时候,这些实参将被复制然后传递给被调用函数。函数本身也可以作为参数传递. ### 内置函数 | 名称 | 说明 | | ---- | ------------------------------- | |close|用于关闭管道通信channel| |len、cap |len 用于返回某个类型的长度或数量(字符串、数组、切片、map 和管道);cap 是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)| |new、make |new 和 make 均是用于分配内存:new 用于值类型和用户定义的类型,如自定义结构,内建函数new分配了零值填充的元素类型的内存空间,并且返回其地址,一个指针类型的值。make 用于内置引用类型(切片、map 和管道)创建一个指定元素类型、长度和容量的slice。容量部分可以省略,在这种情况下,容量将等于长度。。它们的用法就像是函数,但是将类型作为参数:new(type)、make(type)。new(T) 分配类型 T 的零值并返回其地址,也就是指向类型 T 的指针)。它也可以被用于基本类型:v := new(int)。make(T) 返回类型 T 的初始化之后的值,因此它比 new 进行更多的工作,new() 是一个函数,不要忘记它的括号| |copy、append|copy函数用于复制,copy返回拷贝的长度,会自动取最短的长度进行拷贝(min(len(src), len(dst))),append函数用于向slice追加元素| |panic、recover|两者均用于错误处理机制,使用panic抛出异常,抛出异常后将立即停止当前函数的执行并运行所有被defer的函数,然后将panic抛向上一层,直至程序carsh。recover的作用是捕获并返回panic提交的错误对象、调用panic抛出一个值、该值可以通过调用recover函数进行捕获。主要的区别是,即使当前goroutine处于panic状态,或当前goroutine中存在活动紧急情况,恢复调用仍可能无法检索这些活动紧急情况抛出的值。| |print、println|底层打印函数| |complex、real imag|用于创建和操作复数,imag返回complex的实部,real返回complex的虚部| |delete|从map中删除key对应的value| ### 递归函数 ``` func processing(n int) (res int) { if n <= 1 { res = 1 } else { res = processing(n-1) + processing(n-2) } return } ``` ### 匿名函数 匿名函数结构: ``` func() { //func body }() //花括号后加()表示函数调用,此处声明时为指定参数列表, ``` 如: ``` fun(a,b int) { fmt.Println(a+b) }(1,2) ``` ### 闭包函数 闭包函数=匿名函数+环境变量。 ``` func f(i int) func() int { return func() int { i++ return i } } ``` ``` 1 := f(0) c2 := f(0) c1() // reference to i, i = 0, return 1 c2() // reference to another i, i = 0, return 1 ``` goroutine常用的匿名函数 ``` go func(x, y int64) { z := x+y fmt.Println("the reuslt value:",z) }(1, 2) ``` ### defer函数 如果有多个defer表达式,调用顺序类似于栈,越后面的defer表达式越先被调用。 return A这一条语句并不是一条原子指令! 返回值 = A 调用defer函数 空的return ``` func f() (result int) { defer func() { //defer被插入到return之前执行.对result++ result++ }() return 0 //return语句不是一条原子调用,return xxx其实是赋值+ret指令 } ``` f() 的返回值是1, defer函数对result返回值进行了++ ``` func f() (r int) { t := 5 defer func() { t = t + 5 }() return t // r = t,之后 fefer中并未对r进行操作返回值一直都是5 } ``` f()的返回值是5 ``` func f() (r int) { defer func(r int) { r = r + 5 }(r) return 1 //r = 1, defer函数值值传递并未修改r } ``` f()返回值是1 ### panic recover recover 函数用来获取 panic 函数的参数信息,只能在延时调用 defer 语句调用的函数中直接调用才能生效,如果在 defer 语句中也调用 panic 函数,则只有最后一个被调用的 panic 函数的参数会被 recover 函数获取到。如果 goroutine 没有 panic,那调用 recover 函数会返回 nil。 ``` func Parse(input string) (s *Syntax, err error) { defer func() { if p := recover(); p != nil { err = fmt.Errorf("internal error: %v", p) } }() // ...parser... } ``` ### 方法 Golang方法的是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量,因此Golang方法是一种特殊类型的函数 接收者类型可以是(几乎)任何类型,不仅仅是结构体类型:任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名类型。但是接收者不能是一个接口类型,因为接口是一个抽象定义,但是方法却是具体实现 最后接收者不能是一个指针类型,但是它可以是任何其他允许类型的指针. 当调用一个函数时,会对其每一个参数值进行拷贝,如果一个函数需要更新一个变量,或者函数的其中一个参数实在太大我们希望能够避免进行这种默认的拷贝,这种情况下我们就需要用到指针 ``` type Path []Point func (path Path) Distance() float { } func(path *Path) Distance() float { } ``` 不管你的method的receiver是指针类型还是非指针类型,都是可以通过指针/非指针类型进行调用的,编译器会帮你做类型转换。 在声明一个method的receiver该是指针还是非指针类型时,你需要考虑两方面的内部,第一方面是这个对象本身是不是特别大,如果声明为非指针变量时,调用会产生一次拷贝;第二方面是如果你用指针类型作为receiver,那么你一定要注意,这种指针类型指向的始终是一块内存地址,就算你对其进行了拷贝。 ## 反射 反射可以在运行时检查类型和变量,例如它的大小、方法和 动态 的调用这些方法. reflect包有两个数据类型,一个是Type,一个是Value。它定义了两个重要的类型,Type和Value. Type就是定义的类型的一个数据类型,Value是值的类型, TypeOf和ValueOf是获取Type和Value的方法。 ### 反射可以将“接口类型变量”转换为“反射类型对象”. 反射类型指的是reflect.Type和reflect.Value,将接口类型变量转换为反射类型变量,是通过reflect包的TypeOf和ValueOf实现的。 ``` var p int = 10 v1 := reflect.ValueOf(p)//返回Value类型对象,值为10 t1 := reflect.TypeOf(p)//返回Type类型对象,值为int v2 := reflect.ValueOf(&p)//返回Value类型对象,值为&p,变量p的地址 t2 := reflect.TypeOf(&p)//返回Type类型对象,值为*int ``` ### 反射可以将“反射类型对象”转换为“接口类型变量” 这里根据一个 reflect.Value类型的变量,我们可以使用Interface方法恢复其接口类型的值。事实上,这个方法会把 type和value信息打包并填充到一个接口变量中,然后返回. ``` var a int = 10 v1 := reflect.ValueOf(&a) //返回Value类型对象,值为&a,变量a的地址 t1 := reflect.TypeOf(&a) //返回Type类型对象,值为*int v2 := v1.Interface() //返回空接口变量 v3 := v2.(*int) //类型断言,断定v1中type=*int 10 ``` ### 如果要修改反射类型对象,其值必须是“addressable” 在上面第一种反射定律将“接口类型变量”转换为“反射类型对象”我们可以知道,反射对象包含了接口变量中存储的值以及类型。如果反射对象中包含的值是原始值,那么可以通过反射对象修改原始值,如果反射对象中包含的值不是原始值(反射对象包含的是副本值或指向原始值的地址),那么该反射对象是不可以修改的。 Elem函数作用是用来获取原始值对应的反射对象,Elem返回接口v包含的值或指针v指向的值 `func (v Value) Elem() Value` ## goroutine ### sync.Mutex ``` var lock sync.Mutex go func(){ lock.Lock() defer lock.Unlock() }() ``` ## 语法 ``` // 比较两个字节型切片,返回一个整数 // 按字典顺序. // 如果a == b,结果为0;如果a < b,结果为-1;如果a > b,结果为+1 func Compare(a, b []byte) int { for i := 0; i < len(a) && i < len(b); i++ { switch { case a[i] > b[i]: return 1 case a[i] < b[i]: return -1 } } switch { case len(a) > len(b): return 1 case len(a) < len(b): return -1 } return 0 } //文件读取 func Contents(filename string) (string, error) { f, err := os.Open(filename) if err != nil { return "", err } defer f.Close() var result []byte buf := make([]byte, 100) for { n, err := f.Read(buf[0:]) result = append(result, buf[0:n]...) // append稍后讨论。 if err != nil { if err == io.EOF { break } return "", err } } return string(result), nil } ``` ## new分配内存 new 关键字,它不负责 初始化 内存,只负责将其初始为零值. new(T) ,将类型 T 分配一个零值,并且返回其地址,一个类型为 * T 的值。 在 Go 的术语中,其返回一个指向新分配的类型为 T 的指针,这个指针指向的内容的值为零。 当 new 将这段内存置为零值, 当你初始化一个数据结构结构后,不需要进一步的初始化其中每一个类型就可以使用了, 因为已经被初始化为了零值。 当你你用 new 来初始化一个数据结构后,可以直接使用。 ``` type SyncedBuffer struct { lock sync.Mutex buffer bytes.Buffer } p := new(SyncedBuffer) // type *SyncedBuffer var v SyncedBuffer // type SyncedBuffer ``` ## make 内置函数 make(T, args) 的用途与 new(T) 不同。它只创建切片、映射和通道,并返回类型为 T(而不是 *T)的 initialized(非 zeroed)值。 区别的原因是,这三种类型在封面下表示对数据结构的引用,这些数据结构必须在使用前初始化。 make 只能用于创建映射、切片和通道,并且不返回指针。如果想获取一个显式的指针, 你可以使用 new 分配内存,或者显式地获取一个变量的地址。 ``` c := make(chan int) s := make([]int, 10, 100) m := make(map[string]bool) s2 := []int{1, 2, 3} m2 := map[string]bool{ "ann":true, "joe":false, } ```

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:701969077

795 次点击  
加入收藏 微博
上一篇:Golang学习笔记
下一篇:Golang逃逸分析
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传