网站优化说明,网页设计模板html代码 div,网络营销五个特点,成都网页制作培训机构Go学习笔记
1.基础
Go程序设计的一些规则
Go之所以会那么简洁#xff0c;是因为它有一些默认的行为#xff1a;
大写字母开头的变量是可导出的#xff0c;也就是其它包可以读取 的#xff0c;是公用变量#xff1b;小写字母开头的就是不可导出的#xff0c;是私有变量…Go学习笔记
1.基础
Go程序设计的一些规则
Go之所以会那么简洁是因为它有一些默认的行为
大写字母开头的变量是可导出的也就是其它包可以读取 的是公用变量小写字母开头的就是不可导出的是私有变量。大写字母开头的函数也是一样相当于class 中的带public关键词的公有函数小写字母开头的就是有private关键词的私有函数。
for
Go里面最强大的一个控制逻辑就是for它即可以用来循环读取数据又可以当作while来控制逻辑还能迭代操 作。它的语法如下
for expression1; expression2; expression3 {//...
}expression1、expression2和expression3都是表达式其中expression1和expression3是变量声明或者 函数调用返回值之类的expression2是用来条件判断expression1在循环开始之前调用expression3在每 轮循环结束之时调用。
一个例子比上面讲那么多更有用那么我们看看下面的例子吧
package main
import fmt
func main() {sum : 0;for index:0; index 10 ; index {sum index}fmt.Println(sum is equal to , sum)
}
// 输出sum is equal to 45有些时候需要进行多个赋值操作由于Go里面没有,操作那么可以使用平行赋值i, j i1, j-1
有些时候如果我们忽略expression1和expression3
sum : 1
for ; sum 1000; {sum sum
}其中;也可以省略那么就变成如下的代码了是不是似曾相识对这就是while的功能。
sum : 1
for sum 1000 {sum sum
}在循环里面有两个关键操作break和continue ,break操作是跳出当前循环continue是跳过本次循环。当嵌套 过深的时候break可以配合标签使用即跳转至标签所指定的位置详细参考如下例子
for index : 10; index0; index-- {if index 5{break // 或者continue}fmt.Println(index)
}
// break打印出来10、9、8、7、6
// continue打印出来10、9、8、7、6、4、3、2、1break和continue还可以跟着标号用来跳到多重循环中的外层循环
for配合range可以用于读取slice和map的数据
for k,v:range map {fmt.Println(maps key:,k)fmt.Println(maps val:,v)
}由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用_来丢弃 不需要的返回值 例如
for _, v : range map{fmt.Println(maps val:, v)
}switch
有些时候你需要写很多的if-else来实现一些逻辑处理这个时候代码看上去就很丑很冗长而且也不易于以后的维 护这个时候switch就能很好的解决这个问题。它的语法如下
switch sExpr {case expr1:some instructionscase expr2:some other instructionscase expr3:some other instructionsdefault:other code
}sExpr和expr1、expr2、expr3的类型必须一致。Go的switch非常灵活表达式不必是常量或整数执行的过程 从上至下直到找到匹配项而如果switch没有表达式它会匹配true。
i : 10
switch i {case 1:fmt.Println(i is equal to 1)case 2, 3, 4:fmt.Println(i is equal to 2, 3 or 4)case 10:fmt.Println(i is equal to 10)default:fmt.Println(All I know is that i is an integer)
}在第5行中我们把很多值聚合在了一个case里面同时Go里面switch默认相当于每个case最后带有break匹 配成功后不会自动向下执行其他case而是跳出整个switch, 但是可以使用fallthrough强制执行后面的case代 码。
integer : 6
switch integer {case 4:fmt.Println(The integer was 4)fallthroughcase 5:fmt.Println(The integer was 5)fallthroughcase 6:fmt.Println(The integer was 6)fallthroughcase 7:fmt.Println(The integer was 7)fallthroughcase 8:fmt.Println(The integer was 8)fallthroughdefault:fmt.Println(default case)
}上面的程序将输出
The integer was 6
The integer was 7
The integer was 8
default case字符串
我们在上一节中讲过Go中的字符串都是采用UTF-8字符集编码。字符串是用一对双引号“”或反引号 括 起来定义它的类型是string。
//示例代码
var frenchHello string // 声明变量为字符串的一般方法
var emptyString string // 声明了一个字符串变量初始化为空字符串
func test() {no, yes, maybe : no, yes, maybe // 简短声明同时声明多个变量japaneseHello : Ohaiou // 同上frenchHello Bonjour // 常规赋值
}在Go中字符串是不可变的例如下面的代码编译时会报错
var s string hello
s[0] c 但如果真的想要修改怎么办呢下面的代码可以实现
s : hello
c : []byte(s) // 将字符串 s 转换为 []byte 类型
c[0] c
s2 : string(c) // 再转换回 string 类型
fmt.Printf(%s\n, s2)Go中可以使用操作符来连接两个字符串
s : hello,
m : world
a : s m
fmt.Printf(%s\n, a)修改字符串也可写为
s : hello
s c s[1:] // 字符串虽不能更改但可进行切片操作
fmt.Printf(%s\n, s)如果要声明一个多行的字符串怎么办可以通过来声明
m : hello
world
// 括起的字符串为Raw字符串即字符串在代码中的形式就是打印时的形式它没有字符转义换行也将原样输出。slice
在很多应用场景中数组并不能满足我们的需求。在初始定义数组时我们并不知道需要多大的数组因此我们就需 要“动态数组”。在Go里面这种数据结构叫slice
slice并不是真正意义上的动态数组而是一个引用类型。slice总是指向一个底层arrayslice的声明也可以像 array一样只是不需要长度。
对于slice有几个有用的内置函数
len 获取slice的长度 cap 获取slice的最大容量append 向slice里面追加一个或者多个元素然后返回一个和slice一样类型的slicecopy 函数copy从源slice的src中复制元素到目标dst并且返回复制的元素的个数
注append函数会改变slice所引用的数组的内容从而影响到引用同一数组的其它slice。 但当slice中没有剩余空间即(cap-len) 0时此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间而原 数组的内容将保持不变其它引用此数组的slice则不受影响。
package mainimport fmtfunc main() {nums : []int{1, 2, 3}nums append(nums, 4) // 添加元素nums append(nums, 5) // 添加元素fmt.Printf(nums的长度是, len(nums))fmt.Printf(nums的最大容量是, cap(nums))//nums[0] 5fmt.Println(nums)for _, num : range nums {fmt.Println(num)}//fmt.Println(nums[0:3])// 切片// 删除索引为2个元素本质是取索引为2的元素前面的和索引为三开始的nums append(nums[:2], nums[3:]...)fmt.Println(nums) // 切片
}map
package maintype student struct {id string
}func main() {var map1 make(map[string]student, 10)map1[123] student{123}map1[124] student{124}map1[125] student{19999}println(len(map1))delete(map1, 126)println(len(map1))student, ok : map1[125]if ok {println(student.id)}
}
make、new操作
make用于内建类型map、slice 和channel的内存分配。
new用于各种类型的内存分配。 内建函数new本质上说跟其它语言中的同名函数功能一样new(T)分配了零值填充的T类型的内存空间并且返回其 地址即一个*T类型的值。用Go的术语说它返回了一个指针指向新分配的类型T的零值。有一点非常重要 new返回指针。
内建函数make(T, args)与new(T)有着不同的功能make只能创建slice、map和channel并且返回一个有初 始值(非零)的T类型而不是*T。本质来讲导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被 初始化。例如一个slice是一个包含指向数据内部array的指针、长度和容量的三项描述符在这些项目被初始化之前slice为nil。对于slice、map和channel来说make初始化了内部的数据结构填充适当的值。
make返回初始化后的非零值。
错误类型
Go内置有一个error类型专门用来处理错误信息Go的package里面还专门有一个包errors来处理错误
err : errors.New(emit macho dwarf: elf header corrupted)
if err ! nil {fmt.Print(err)
}iota枚举
Go里面有一个关键字iota这个关键字用来声明enum的时候采用它默认开始值是0每调用一次加1
const(x iota // x 0y iota // y 1z iota // z 2w // 常量声明省略值时默认和之前一个值的字面相同。这里隐式地说w iota因此w 3。其实上面y和z可同样不用 iota
)
const v iota // 每遇到一个const关键字iota就会重置此时v 0goto
Go有goto语句——请明智地使用它。用goto跳转到必须在当前函数内定义的标签。例如假设这样一个循环
func myFunc() {i : 0
Here: //这行的第一个词以冒号结束作为标签println(i)igoto Here //跳转到Here去
}标签名是大小写敏感的。
2.函数
特性
函数是Go里面的核心设计它通过关键字func来声明它的格式如下
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {//这里是处理逻辑代码//返回多个值return value1, value2
}上面的代码我们看出
关键字func用来声明一个函数funcName函数可以有一个或者多个参数每个参数后面带有类型通过,分隔函数可以返回多个值上面返回值声明了两个变量output1和output2如果你不想声明也可以直接就两个类型如果只有一个返回值且不声明返回值变量那么你可以省略 包括返回值 的括号如果没有返回值那么就直接省略最后的返回信息如果有返回值 那么必须在函数的外层添加return语句
下面我们来看一个实际应用函数的例子用来计算Max值
package main
import fmt
// 返回a、b中最大值.
func max(a, b int) int {if a b {return a}return b
}
func main() {x : 3y : 4z : 5max_xy : max(x, y) //调用函数max(x, y)max_xz : max(x, z) //调用函数max(x, z)fmt.Printf(max(%d, %d) %d\n, x, y, max_xy)fmt.Printf(max(%d, %d) %d\n, x, z, max_xz)fmt.Printf(max(%d, %d) %d\n, y, z, max(y,z)) // 也可在这直接调用它
}上面这个里面我们可以看到max函数有两个参数它们的类型都是int那么第一个变量的类型可以省略即 a,b int,而非 a int, b int)默认为离它最近的类型同理多于2个同类型的变量或者返回值。同时我们注意到它的返 回值就是一个类型这个就是省略写法。
多个返回值
Go语言比C更先进的特性其中一点就是函数能够返回多个值。
变参
Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点首先需要定义函数使其接受变参 func myfunc(arg ...int) {}
arg ...int告诉Go这个函数接受不定数量的参数。注意这些参数的类型全部是int。在函数体中变量arg是一 个int的slice
for _, n : range arg {fmt.Printf(And the number is: %d\n, n)
}传值与传指针
当我们传一个参数值到被调用函数里面时实际上是传了这个值的一份copy当在被调用函数中修改参数值的时候 调用函数中相应实参不会发生任何变化因为数值变化只作用在copy上。
package main
import fmt
//简单的一个函数实现了参数1的操作
func add1(a int) int {a a1 // 我们改变了a的值return a //返回一个新值
}
func main() {x : 3fmt.Println(x , x) // 应该输出 x 3x1 : add1(x) //调用add1(x)fmt.Println(x1 , x1) // 应该输出x1 4fmt.Println(x , x) // 应该输出x 3
}看到了吗虽然我们调用了add1函数并且在add1中执行a a1操作但是上面例子中x变量的值没有发生变化
理由很简单因为当我们调用add1的时候add1接收的参数其实是x的copy而不是x本身。 那你也许会问了如果真的需要传这个x本身,该怎么办呢
这就牵扯到了所谓的指针。我们知道变量在内存中是存放于一定地址上的修改变量实际是修改变量地址处的内 存。只有add1函数知道x变量所在的地址才能修改x变量的值。所以我们需要将x所在地址x传入函数并将函数 的参数的类型由int改为*int即改为指针类型才能在函数中修改x变量的值。此时参数仍然是按copy传递的只 是copy的是一个指针。请看下面的例子
package main
import fmt
//简单的一个函数实现了参数1的操作
func add1(a *int) int { // 请注意*a *a1 // 修改了a的值return *a // 返回新值
}
func main() {x : 3fmt.Println(x , x) // 应该输出 x 3x1 : add1(x) // 调用 add1(x) 传x的地址fmt.Println(x1 , x1) // 应该输出 x1 4fmt.Println(x , x) // 应该输出 x 4
}传指针有什么好处呢 传指针使得多个函数能操作同一个对象。 传指针比较轻量级 (8bytes),只是传内存地址我们可以用指针传递体积大的结构体。如果用参数值传递的 话, 在每次copy上面就会花费相对较多的系统开销内存和时间。所以当你要传递大的结构体的时候用 指针是一个明智的选择。 Go语言中stringslicemap这三种类型的实现机制类似指针所以可以直接传递而不用取地址后传递 指针。注若函数需改变slice的长度则仍需要取地址传递指针
defer
Go语言中有种不错的设计即延迟defer语句你可以在函数中添加多个defer语句。当函数执行到最后时这些 defer语句会按照逆序执行最后该函数返回。特别是当你在进行一些打开资源的操作时遇到错误需要提前返回 在返回前你需要关闭相应的资源不然很容易造成资源泄露等问题。如下代码所示我们一般写打开一个资源是这样 操作的
func ReadWrite() bool {file.Open(file)// 做一些工作if failureX {file.Close()return false}if failureY {file.Close()return false}file.Close()return true
}我们看到上面有很多重复的代码Go的defer有效解决了这个问题。使用它后不但代码量减少了很多而且程序变 得更优雅。在defer后指定的函数会在函数退出前调用。
func ReadWrite() bool {file.Open(file)defer file.Close()if failureX {return false}if failureY {return false}return true
}如果有很多调用defer那么defer是采用后进先出模式所以如下代码会输出4 3 2 1 0
for i : 0; i 5; i {defer fmt.Printf(%d , i)
}函数作为值、类型
在Go中函数也是一种变量我们可以通过type来定义它它的类型就是所有拥有相同的参数相同的返回值的一种类型
函数作为类型到底有什么好处呢那就是可以把这个类型的函数当做值来传递
package mainimport fmttype testInt func(int) bool // 声明了一个函数类型
func isOdd(integer int) bool {if integer%2 0 {return false}return true
}
func isEven(integer int) bool {if integer%2 0 {return true}return false
}// 声明的函数类型在这个地方当做了一个参数
func filter(slice []int, f testInt) []int {var result []intfor _, value : range slice {if f(value) {result append(result, value)}}return result
}
func main() {slice : []int{1, 2, 3, 4, 5, 7}fmt.Println(slice , slice)odd : filter(slice, isOdd) // 函数当做值来传递了fmt.Println(Odd elements of slice are: , odd)even : filter(slice, isEven) // 函数当做值来传递了fmt.Println(Even elements of slice are: , even)
}函数当做值和类型在我们写一些通用接口的时候非常有用通过上面例子我们看到testInt这个类型是一个函数类 型然后两个filter函数的参数和返回值与testInt类型是一样的但是我们可以实现很多种的逻辑这样使得我们的程序变得非常的灵活
Panic和Recover
Go没有像Java那样的异常机制它不能抛出异常而是使用了panic和recover机制。一定要记住你应当把它作为 最后的手段来使用也就是说你的代码中应当没有或者很少有panic的东西。这是个强大的工具请明智地使用它。那么我们应该如何使用它呢
Panic
是一个内建函数可以中断原有的控制流程进入一个令人恐慌的流程中。当函数F调用panic函数F的执行被中 断但是F中的延迟函数会正常执行然后F返回到调用它的地方。在调用的地方F的行为就像调用了panic。这一 过程继续向上直到发生panic的goroutine中所有调用的函数返回此时程序退出。恐慌可以直接调用panic产 生。也可以由运行时错误产生例如访问越界的数组。
Recover
是一个内建的函数可以让进入令人恐慌的流程中的goroutine恢复过来。recover仅在延迟函数中有效。在正常 的执行过程中调用recover会返回nil并且没有其它任何效果。如果当前的goroutine陷入恐慌调用 recover可以捕获到panic的输入值并且恢复正常的执行。
下面这个函数演示了如何在过程中使用panic
package mainimport (fmt
)func readSliceElement(slice []int, index int) {// 最后一个()表示立即调用该匿名函数defer func() {if r : recover(); r ! nil {fmt.Println(Recovered from panic:, r)}}()if index 0 || index len(slice) {panic(Index out of range)}fmt.Println(Element at index, index, :, slice[index])
}func main() {slice : []int{1, 2, 3, 4, 5}// 正常读取readSliceElement(slice, 2)// 读取越界的索引readSliceElement(slice, 10)
}main函数和init函数
Go里面有两个保留的函数init函数能够应用于所有的package和main函数只能应用于package main。 这两个函数在定义时不能有任何的参数和返回值。虽然一个package里面可以写任意多个init函数但这无论是对 于可读性还是以后的可维护性来说我们都强烈建议用户在一个package中每个文件只写一个init函数。
Go程序会自动调用init()和main()所以你不需要在任何地方调用这两个函数。每个package中的init函数都是 可选的但package main就必须包含一个main函数。
程序的初始化和执行都起始于main包。如果main包还导入了其它的包那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入那么它只会被导入一次例如很多包可能都会用到fmt包但它只会被导入一次因为没有必要导入多次。当一个包被导入时如果该包还导入了其它的包那么会先将其它包导入进来然后再对这些包 中的包级常量和变量进行初始化接着执行init函数如果有的话依次类推。等所有被导入的包都加载完毕 了就会开始对main包中的包级常量和变量进行初始化然后执行main包中的init函数如果存在的话最后执 行main函数。 3.struct类型
strut
Go语言中也和C或者其他语言一样我们可以声明新的类型作为其它类型的属性或字段的容器。例如我们可以 创建一个自定义类型person代表一个人的实体。这个实体拥有属性姓名和年龄。这样的类型我们称struct。如下代码所示
type person struct {name stringage int
}看到了吗声明一个struct如此简单上面的类型包含有两个字段
一个string类型的字段name用来保存用户名 称这个属性一个int类型的字段age用来保存用户年龄这个属性
如何使用struct呢请看下面的代码
type person struct {name stringage int
}
var P person // P现在就是person类型的变量了
P.name Astaxie // 赋值Astaxie给P的name属性.
P.age 25 // 赋值25给变量P的age属性
fmt.Printf(The persons name is %s, P.name) // 访问P的name属性.除了上面这种P的声明使用之外还有两种声明使用方式
1.按照顺序提供初始化值 P : person{Tom, 25} 2.通过field:value的方式初始化这样可以任意顺序 P : person{age:24, name:Tom}
下面我们看一个完整的使用struct的例子
package mainimport fmt// 声明一个新的类型
type person struct {name stringage int
}// 比较两个人的年龄返回年龄大的那个人并且返回年龄差
// struct也是传值的
func Older(p1, p2 person) (person, int) {if p1.age p2.age { // 比较p1和p2这两个人的年龄return p1, p1.age - p2.age}return p2, p2.age - p1.age
}
func main() {var tom person// 赋值初始化tom.name, tom.age Tom, 18// 两个字段都写清楚的初始化bob : person{age: 25, name: Bob}// 按照struct定义顺序初始化值paul : person{Paul, 43}tb_Older, tb_diff : Older(tom, bob)tp_Older, tp_diff : Older(tom, paul)bp_Older, bp_diff : Older(bob, paul)fmt.Printf(Of %s and %s, %s is older by %d years\n,tom.name, bob.name, tb_Older.name, tb_diff)fmt.Printf(Of %s and %s, %s is older by %d years\n,tom.name, paul.name, tp_Older.name, tp_diff)fmt.Printf(Of %s and %s, %s is older by %d years\n,bob.name, paul.name, bp_Older.name, bp_diff)
}struct的匿名字段
我们上面介绍了如何定义一个struct定义的时候是字段名与其类型一一对应实际上Go支持只提供类型而不写字 段名的方式也就是匿名字段也称为嵌入字段。
当匿名字段是一个struct的时候那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。
让我们来看一个例子让上面说的这些更具体化
package mainimport fmttype Human struct {name stringage intweight int
}
type Student struct {Human // 匿名字段那么默认Student就包含了Human的所有字段speciality string
}func main() {// 我们初始化一个学生mark : Student{Human{Mark, 25, 120}, Computer Science}// 我们访问相应的字段fmt.Println(His name is , mark.name)fmt.Println(His age is , mark.age)fmt.Println(His weight is , mark.weight)fmt.Println(His speciality is , mark.speciality)// 修改对应的备注信息mark.speciality AIfmt.Println(Mark changed his speciality)fmt.Println(His speciality is , mark.speciality)// 修改他的年龄信息fmt.Println(Mark become old)mark.age 46fmt.Println(His age is, mark.age)// 修改他的体重信息fmt.Println(Mark is not an athlet anymore)mark.weight 60fmt.Println(His weight is, mark.weight)
}我们看到Student访问属性age和name的时候就像访问自己所有用的字段一样对匿名字段就是这样能够实现字 段的继承。是不是很酷啊还有比这个更酷的呢那就是student还能访问Human这个字段作为字段名。请看下面的代 码是不是更酷了。
mark.Human Human{Marcus, 55, 220}
mark.Human.age - 1
通过匿名访问和修改字段相当的有用但是不仅仅是struct字段哦所有的内置类型和自定义类型都是可以作为匿名 字段的。请看下面的例子
package main
import fmt
type Skills []string
type Human struct {name stringage intweight int
}
type Student struct {Human // 匿名字段structSkills // 匿名字段自定义的类型string sliceint // 内置类型作为匿名字段speciality string
}
func main() {// 初始化学生Janejane : Student{Human:Human{Jane, 35, 100}, speciality:Biology}// 现在我们来访问相应的字段fmt.Println(Her name is , jane.name)fmt.Println(Her age is , jane.age)fmt.Println(Her weight is , jane.weight)fmt.Println(Her speciality is , jane.speciality)// 我们来修改他的skill技能字段jane.Skills []string{anatomy}fmt.Println(Her skills are , jane.Skills)fmt.Println(She acquired two new ones )jane.Skills append(jane.Skills, physics, golang)fmt.Println(Her skills now are , jane.Skills)// 修改匿名内置类型字段jane.int 3fmt.Println(Her preferred number is, jane.int)
}从上面例子我们看出来struct不仅仅能够将struct作为匿名字段、自定义类型、内置类型都可以作为匿名字段而且 可以在相应的字段上面进行函数操作如例子中的append。
这里有一个问题如果human里面有一个字段叫做phone而student也有一个字段叫做phone那么该怎么办呢
Go里面很简单的解决了这个问题最外层的优先访问也就是当你通过student.phone访问的时候是访问 student里面的字段而不是human里面的字段。
这样就允许我们去重载通过匿名字段继承的一些字段当然如果我们想访问重载后对应匿名类型里面的字段可以通 过匿名字段名来访问。请看下面的例子
package main
import fmt
type Human struct {name stringage intphone string // Human类型拥有的字段
}
type Employee struct {Human // 匿名字段Humanspeciality stringphone string // 雇员的phone字段
}
func main() {Bob : Employee{Human{Bob, 34, 777-444-XXXX}, Designer, 333-222}fmt.Println(Bobs work phone is:, Bob.phone)// 如果我们要访问Human的phone字段fmt.Println(Bobs personal phone is:, Bob.Human.phone)
}4.面向对象
method
在使用method的时候重要注意几点 虽然method的名字一模一样但是如果接收者不一样那么method就不一样 method里面可以访问接收者的字段 调用method通过.访问就像struct里面访问字段一样
package mainimport (fmtmath
)type Rectangle struct {width, height float64
}
type Circle struct {radius float64
}func (r Rectangle) area() float64 {return r.width * r.height
}func (c Circle) area() float64 {return c.radius * c.radius * math.Pi
}func main() {r1 : Rectangle{12, 2}r2 : Rectangle{9, 4}c1 : Circle{10}c2 : Circle{25}fmt.Println(Area of r1 is: , r1.area())fmt.Println(Area of r2 is: , r2.area())fmt.Println(Area of c1 is: , c1.area())fmt.Println(Area of c2 is: , c2.area())
}
method可以定义在任何你自定义的类型、内置类型、struct等各种类型上面。这里你是不是有点迷糊了什么叫自定义类型自定义类型不就是struct嘛不是这样的哦struct只是自定义类型里面一种比较特殊的类型而已还有其他自定义类型申明可以通过如下这样的申明来实现。
type typeName typeLiteral
type ages int
type money float32
type months map[string]int
m : months {January:31,February:28,...December:31,
}看到了吗简单的很吧这样你就可以在自己的代码里面定义有意义的类型了实际上只是一个定义了一个别名,有s点类似于c中的typedef例如上面ages替代了int
你可以在任何的自定义类型中定义任意多的method
package mainimport fmtconst (WHITE iotaBLACKBLUEREDYELLOW
)type Color byte // Color作为byte的别名
type Box struct { // 定义了一个struct:Box含有三个长宽高字段和一个颜色属性width, height, depth float64color Color
}
type BoxList []Box //定义了一个slice:BoxList含有Box
// Volume (b Box)定义了接收者为Box返回Box的容量
func (b Box) Volume() float64 {return b.width * b.height * b.depth
}// SetColor (c Color)把Box的颜色改为c
func (b *Box) SetColor(c Color) {b.color c
}// BiggestsColor ()定在在BoxList上面返回list里面容量最大的颜色
func (bl BoxList) BiggestsColor() Color {v : 0.00k : Color(WHITE)for _, b : range bl {if b.Volume() v {v b.Volume()k b.color}}return k
}// PaintItBlack ()把BoxList里面所有Box的颜色全部变成黑色
func (bl BoxList) PaintItBlack() {for i, _ : range bl {bl[i].SetColor(BLACK)}
}// String()定义在Color上面返回Color的具体颜色(字符串格式)
func (c Color) String() string {strings : []string{WHITE, BLACK, BLUE, RED, YELLOW}return strings[c]
}
func main() {boxes : BoxList{Box{4, 4, 4, RED},Box{10, 10, 1, YELLOW},Box{1, 1, 20, BLACK},Box{10, 10, 1, BLUE},Box{10, 30, 1, WHITE},Box{20, 20, 20, YELLOW},}fmt.Printf(We have %d boxes in our set\n, len(boxes))fmt.Println(The volume of the first one is, boxes[0].Volume(), cm³)fmt.Println(The color of the last one is, boxes[len(boxes)-1].color.String())fmt.Println(The biggest one is, boxes.BiggestsColor().String())fmt.Println(Lets paint them all black)boxes.PaintItBlack()fmt.Println(The color of the second one is, boxes[1].color.String())fmt.Println(Obviously, now, the biggest one is, boxes.BiggestsColor().String())
}method继承
package mainimport fmttype Human struct {name stringage intphone string
}type Student struct {Human // 匿名字段school string
}type Employee struct {Human //匿名字段company string
}func (h *Human) SayHi() {fmt.Printf(My name is %sand my phoneNumber is %s\n, h.name, h.phone)
}func main() {stu : Student{Human{张三, 18, 123456}, 中北大学}//emp : Employee{Human{李四, 19, 987645}, 字节跳动}// 这样写不用写出Human对象var emp Employeeemp.name 李四emp.age 19emp.phone 987654emp.company ByteDancestu.SayHi()emp.SayHi()
}method重写
package mainimport fmttype Human struct {name stringage intphone string
}type Student struct {Human // 匿名字段school string
}type Employee struct {Human //匿名字段company string
}func (h *Human) SayHi() {fmt.Printf(My name is %sand my phoneNumber is %s\n, h.name, h.phone)
}func (s *Student) SayHi() {fmt.Printf(My name is %sand I study at %s\n, s.name, s.school)
}func (e Employee) SayHi() {fmt.Printf(My name is %sand I work at %s\n, e.name, e.company)
}func main() {stu : Student{Human{张三, 18, 123456}, 中北大学}emp : Employee{Human{李四, 19, 987645}, 字节跳动}stu.SayHi()emp.SayHi()
}5.interface
interface
interface本质是个指针
interface是一组method的组合我们通过interface来定义对象的一组行为。
我们前面一章最后一个例子中Student和Employee都能Sayhi虽然他们的内部实现不一样但是那不重要重要的是 他们都能say hi
让我们来继续做更多的扩展Student和Employee实现另一个方法Sing然后Student实现方法BorrowMoney而 Employee实现SpendSalary。
这样Student实现了三个方法Sayhi、Sing、BorrowMoney而Employee实现了Sayhi、Sing、SpendSalary。
上面这些方法的组合称为interface(被对象Student和Employee实现)。例如Student和Employee都实现了interface Sayhi和Sing也就是这两个对象是该interface类型。而Employee没有实现这个interfaceSayhi、Sing和 BorrowMoney因为Employee没有实现BorrowMoney这个方法。
interface类型
interface类型定义了一组方法如果某个对象实现了某个接口的所有方法则此对象就实现了此接口
package mainimport fmttype Human struct {name stringage intphone string
}
type Student struct {Human //匿名字段Humanschool stringloan float32
}
type Employee struct {Human //匿名字段Humancompany stringmoney float32
}// Human对象实现Sayhi方法
func (h *Human) SayHi() {fmt.Printf(Hi, I am %s you can call me on %s\n, h.name, h.phone)
}// Human对象实现Sing方法
func (h *Human) Sing(lyrics string) {fmt.Println(La la, la la la, la la la la la..., lyrics)
}// Human对象实现Guzzle方法
func (h *Human) Guzzle(beerStein string) {fmt.Println(Guzzle Guzzle Guzzle..., beerStein)
}// Employee重载Human的Sayhi方法
func (e *Employee) SayHi() {fmt.Printf(Hi, I am %s, I work at %s. Call me on %s\n, e.name,e.company, e.phone) //Yes you can split into 2 lines here.
}// Student实现BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {s.loan amount // (again and again and...)
}// Employee实现SpendSalary方法
func (e *Employee) SpendSalary(amount float32) {e.money - amount // More vodka please!!! Get me through the day!
}// 定义interface
type Men interface {SayHi()Sing(lyrics string)Guzzle(beerStein string)
}
type YoungChap interface {SayHi()Sing(song string)BorrowMoney(amount float32)
}
type ElderlyGent interface {SayHi()Sing(song string)SpendSalary(amount float32)
}interface值
即多态
如果我们定义了一个interface的变量那么这个变量里面可以存实现这个 interface的任意类型的对象。例如上面例子中我们定义了一个Men interface类型的变量m那么m里面可以存 Human、Student或者Employee值。
因为m能够持有这三种类型的对象所以我们可以定义一个包含Men类型元素的slice这个slice可以被赋予实现了 Men接口的任意结构的对象这个和我们传统意义上面的slice有所不同。
package mainimport fmttype Human struct {name stringage intphone string
}
type Student struct {Human //匿名字段school stringloan float32
}
type Employee struct {Human //匿名字段company stringmoney float32
}// Human实现Sayhi方法
func (h Human) SayHi() {fmt.Printf(Hi, I am %s you can call me on %s\n, h.name, h.phone)
}// Human实现Sing方法
func (h Human) Sing(lyrics string) {fmt.Println(La la la la..., lyrics)
}// Employee重载Human的SayHi方法
func (e Employee) SayHi() {fmt.Printf(Hi, I am %s, I work at %s. Call me on %s\n, e.name,e.company, e.phone) //Yes you can split into 2 lines here.
}// Interface Men被Human,Student和Employee实现
// 因为这三个类型都实现了这两个方法
type Men interface {SayHi()Sing(lyrics string)
}func main() {mike : Student{Human{Mike, 25, 222-222-XXX}, MIT, 0.00}paul : Student{Human{Paul, 26, 111-222-XXX}, Harvard, 100}sam : Employee{Human{Sam, 36, 444-222-XXX}, Golang Inc., 1000}Tom : Employee{Human{Sam, 36, 444-222-XXX}, Things Ltd., 5000}//定义Men类型的变量ivar i Men//i能存储Studenti mikefmt.Println(This is Mike, a Student:)i.SayHi()i.Sing(November rain)//i也能存储Employeei Tomfmt.Println(This is Tom, an Employee:)i.SayHi()i.Sing(Born to be wild)//定义了slice Menfmt.Println(Lets use a slice of Men and see what happens)x : make([]Men, 3)//T这三个都是不同类型的元素但是他们实现了interface同一个接口x[0], x[1], x[2] paul, sam, mikefor _, value : range x {value.SayHi()}
}通过上面的代码你会发现interface就是一组抽象方法的集合它必须由其他非interface类型实现而不能自我实 现 go 通过interface实现了duck-typing:即当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子那 么这只鸟就可以被称为鸭子。
空interface
空interface(interface{})不包含任何的method正因为如此所有的类型都实现了空interface。空interface对于 描述起不到任何的作用(因为它不包含任何的method但是空interface在我们需要存储任意类型的数值的时候相当有用因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。
// 定义a为空接口
var a interface{}
var i int 5
s : Hello world
// a可以存储任意类型的数值
a i
a s一个函数把interface{}作为参数那么他可以接受任意类型的值作为参数如果一个函数返回interface{},那么也 就可以返回任意类型的值。是不是很有用啊
package mainimport fmtfunc show(arg interface{}) {//fmt.Println(arg)// interface{} 如何区分此时引用的底层数据类型到底是什么// 类型“断言”机制value, ok : arg.(string)if !ok {fmt.Println(arg is not string type)} else {fmt.Println(arg is string type, value , value)}
}type Book struct {name string
}func main() {book : Book{Golang}show(book)show(100)show(abc)show(3.14)
}interface函数参数
6.反射
基本用法
package mainimport (fmtreflect
)func main() {var num float64 3.14159reflectNum(num)
}func reflectNum(arg interface{}) {fmt.Println(type:, reflect.TypeOf(arg))fmt.Println(value:, reflect.ValueOf(arg))
}
package mainimport (fmtreflect
)type User struct {Id intName stringAge int
}func (u User) Call() {fmt.Println(user is called..)fmt.Printf(%v\n, u)
}func main() {user : User{1, Tom, 20}DoFiledAndMethod(user)
}func DoFiledAndMethod(input interface{}) {// 获取input的typeinputType : reflect.TypeOf(input)fmt.Println(inputType is :, inputType)// 获取input的valueinputValue : reflect.ValueOf(input)fmt.Println(inputValue is :, inputValue)//通过type 获取里面的字段//1.获取interface的reflect.Type通过Type得到NumFiled进行遍历//2.得到每一个field数据类型//3.通过field有一个Interface{}方法得到对应的valuefor i : 0; i inputType.NumField(); i {field : inputType.Field(i)value : inputValue.Field(i).Interface()fmt.Printf(%s: %v %v\n, field.Name, field.Type, value)}//通过type 获取里面的方法调用for i : 0; i inputType.NumMethod(); i {method : inputType.Method(i)fmt.Printf(%s: %v\n, method.Name, method.Type)}
}反射解析结构体标签Tag
package mainimport (fmtreflect
)type resume struct {Name string info:name doc:我的名字Sex string info:sex
}func findTag(str interface{}) {t : reflect.TypeOf(str).Elem()for i : 0; i t.NumField(); i {tagString : t.Field(i).Tag.Get(info)tagDoc : t.Field(i).Tag.Get(doc)fmt.Println(info:, tagString, doc:, tagDoc)}
}func main() {var re resumefindTag(re)
}结构体标签在json中的应用
package mainimport (encoding/jsonfmt
)type Movie struct {Title string json:titleYear int json:yearPrice int json:priceActors []string json:actors
}func main() {movie : Movie{喜剧之王, 2000, 10, []string{xingye, zhangbozhi}}// 编码的过程 结构体 --- jsonjsonStr, err : json.Marshal(movie)if err ! nil {fmt.Println(json marshal error)return}fmt.Printf(jsonStr %s\n, jsonStr)// 解码的过程 jsonStr --- 结构体// jsonStr {Title:喜剧之王,year:2000,price:10,actors:[xingye,zhangbozhi]}my_movie : Movie{}err json.Unmarshal(jsonStr, my_movie)if err ! nil {fmt.Println(json unmarshal error)return}fmt.Printf(%v, my_movie)
}7.并发
基础
进程和线程
进程占用内存 虚拟内存4GB32Bit OS
线程占用内存 约4MB
缺点高消耗调度CPU、高内存占用
协程 Golang对协程的处理Goroutine
内存几KB可以大量灵活调度可常切换
GMP
Ggoroutine协程Pprocessor处理器Mthread线程
调度器的设计策略 复用线程 空闲的P可以偷有别的P中等待的Gwork stealing 一个M阻塞之后M和P会取消绑定然后P绑定一个新的M 利用并行 限定P的个数一般是CPU核数/2 抢占 绑定之后需要co-routine主动释放CPU旧版co-routine 最多轮询10ms否则新的G一定会抢占CPUgoroutine 全局G队列 优先去其他队列偷也没有的话去全局队列获取涉及加锁解锁
创建goroutine
函数
package mainimport (fmttime
)func newTask() {i : 0for {ifmt.Printf(new Goroutine : i %d\n, i)time.Sleep(1 * time.Second)}
}
func main() {go newTask()// main线程在才会创建协程fmt.Println(main goroutine exit)/*i : 0for {ifmt.Printf(main goroutine : i %d\n, i)time.Sleep(1 * time.Second)}*/
}
匿名函数
package mainimport (fmttime
)func main() {// 用go创建承载一个形参为空返回值为空的一个函数/*go func() {defer fmt.Println(A.defer)func() {defer fmt.Println(B.defer)// 退出当前goroutine用return只能退出当前的无法退出外层的runtime.Goexit()fmt.Println(B)}()fmt.Println(A)}()*/go func(a int, b int) bool {fmt.Println(a , a, b , b)return true}(10, 20)//死循环for {time.Sleep(1 * time.Second)}
}channel
用于goroutine之间通信
无缓冲的channel 在第1步两个 goroutine 都到达通道但哪个都没有开始执行发送或者接收。在第2步左侧的 goroutine 将它的手伸进了通道这模拟了向通道发送数据的行为。这时这个 goroutine 会在通道中被锁住直到交换完成。在第3步右侧的 goroutine 将它的手放入通道这模拟了从通道里接收数据。这个goroutine 一样也会在通道中被锁住直到交换完成。在第4 步和第5步进行交换并最终在第6 步两个 goroutine 都将它们的手从通道里拿出来这模拟了被锁住的 goroutine 得到释放。两个 goroutine 现在都可以去做其他事情了。
package mainimport fmtfunc main() {// 定义一个无缓存的channel最多有一个元素类型为intc : make(chan int)go func() {defer fmt.Println(goroutine结束)fmt.Println(goroutine正在运行...)c - 666 // 将666发送给c}()num : -c // main goroutine在此处阻塞等sub goroutine执行完毕后继续fmt.Println(num , num)fmt.Println(main goroutine 结束)
}有缓冲的channel 在第1步右侧的 goroutine 正在从通道接收一个值。在第2步右侧的这个 goroutine独立完成了接收值的动作而左侧的goroutine正在发送一个新值到通道里在第3步左侧的goroutine 还在向通道发送新值而右侧的 goroutine 正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的也不会互相阻塞。最后在第4 步所有的发送和接收都完成而通道里还有几个值也有一些空间可以存更多的值。特点 当channel已经满再向里面写数据就会阻塞当channel为空从里面取数据也会阻塞
package mainimport (fmttime
)func main() {// 带有缓冲的channel类型为intc : make(chan int, 3)fmt.Println(len(c) , len(c), cap(c) , cap(c))go func() {defer fmt.Println(子go程结束)for i : 0; i 4; i {c - ifmt.Println(子go程正在运行: 发送的元素, i, len(c) , len(c), ,cap(c) , cap(c))}}()time.Sleep(2 * time.Second)for i : 0; i 4; i {num : -c //从c中接收数据并赋值给numfmt.Println(num , num)}fmt.Println(main 结束)
}关闭channel
channel不像文件一样需要经常去关闭只有当你确实没有任何发送数据了或者你想显式的结束range循环之类的才去关闭channel;关闭channel后无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);关闭channel后可以继续从channel接收数据;对于nil channel无论收发都会被阻塞
package mainimport fmtfunc main() {// 带有缓冲的channel类型为intc : make(chan int)go func() {for i : 0; i 5; i {c - i}//close可以关闭一个channelclose(c)}()for {// ok如果为true表示channel没有关闭为false表示channel已经关闭if data, ok : -c; ok {fmt.Println(data)} else {break}}fmt.Println(Main Finished)
}channel与range
package mainimport fmtfunc main() {// 带有缓冲的channel类型为intc : make(chan int)go func() {for i : 0; i 5; i {c - i}//close可以关闭一个channelclose(c)}()/*for {// ok如果为true表示channel没有关闭为false表示channel已经关闭if data, ok : -c; ok {fmt.Println(data)} else {break}}*/// 可以使用range来迭代不断操作channelfor data : range c {fmt.Println(data)}fmt.Println(Main Finished)
}channel与select
单流程下一个go只能监控一个channel的状态select可以完成监控多个channel的状态
package mainimport fmtfunc fibonacci(c, quit chan int) {x, y : 1, 1for {select {case c - x:// 如果c可写则该case就会进来x yy x ycase -quit:fmt.Println(quit)return}}
}func main() {c : make(chan int)quit : make(chan int)go func() {for i : 0; i 6; i {fmt.Println(-c)}quit - 0}()fibonacci(c, quit)
}8.Go Modules模式
go mod命令
go modinit 生成 go.mod 文件
go mod download 下载 go.mod 文件中指明的所有依赖
go mod tidy 整理现有的依赖
go mod graph 查看现有的依赖结构
go mod edit 编辑 go.mod 文件
go mod vendor 导出项目所有的依赖到vendor目录
go mod verify 校验一个模块是否被篡改过
go mod why查看为什么需要依赖某模块go mod 环境变量 GO111MODULE 是否开启go modules模式建议go V1.11之后都设置为on GOPROVY 项目的第三方依赖库的下载源地址建议设置国内的地址 阿里云https://mirrors.aliyun.com/goprovy七牛云https://goproxy.cn,direct direct用于指示Go回源到模块版本的源地址去抓取比如github等 GOSUMDB 用来校验拉取的第三方库是否是完整的默认也是国外的网站如果设置了GOPROXY这个就不用设置了 GONOPROXY通过设置GOPRIVATE即可 GONOSUMDB通过设置GOPRIVATE即可 GOPRIVATE通过设置GOPRIVATE即可 go env -w GOPRIVATEgit.example.com,github.com/accld/zinx表示git.example.com和github.com/accld/zinx是私有仓库不会进行GOPROXY下载和校验go env -w GOPRIVATE*.example.com表示所有模块路径为example.com的子域名比如git.example.com或者hello.example.com都不进行GOPROXY下载和校验 通过go env来查看环境变量 go env -w GO111MODULEon或者通过Linux export环境方式也可以
go mod
//indirect表示间接依赖go sum
go.sum文件的作用罗列当前项目直接或者间接的依赖所有模块版本保证今后项目依赖的版本不会被篡改
h1:hash表示整体项目的zip文件打开之后的全部文件的校验和来生成的hash如果不存在表示依赖的库可能用不上
9.生态
WEB 国内 beegohttps://github.com/astaxie/beego适宜初学者 国外 ginhttps://github.com/gin-gonic/gin高性能轻量级框架echohttps://github.com/labstack/echoIrishttps://github.com/kataras/iris
微服务框架 go kithttp://gokit.io一套集成组成方案有很多工具灵活适合小项目 Istiohttps://istio.io一体化集成式比较全面适合繁琐的大项目
容器编排
Kuberneteshttps://github.com/kubernetes/kubernetesgoogle出来更加通用swarmhttps://github.com/docker/classicswarmdocker团队开发docker中集成
服务发现服务注册
consulhttps://github.com/hashicorp/consul
存储引擎
etcdk/v存储https://github.com/coreos/etcd类似redis比redis好在支持分布式kv存储一致性做的也不错tidb分布式SQL存储https://github.com/pingcap/tidb
静态建站
hugohttps://github.com/gohugoio/hugo
中间件 消息队列 nsqhttps://github.com/nsqio/nsq Tcp长连接框架轻量级服务器 zinxhttps://github.com/aceld/zinx 游戏服务器 Leafhttps://github.com/name5566/leaf RPC框架 gRPC比较标准的rpc https://grpc.iohttps://github.com/grpc/grpc-go Redis集群 codishttps://github.com/CodisLabs/codis
爬虫框架
go queryhttps://github.com/PuerkitoBio/goquery
10.WEB
gingorm简单使用
package mainimport (fmtgithub.com/gin-gonic/gingorm.io/driver/mysqlgorm.io/gorm
)type Film struct {ID uintName stringCategoryIDs stringKeywords stringDirectorItems stringAddress stringIntroduction stringShowTime stringCreateTime stringUpdateTime stringClickCount intImgURL stringResourceURL stringIsOn intLastOperatorID int
}func main() {dsn : root:roottcp(127.0.0.1:3306)/film_recsys// 连接 MySQL 数据库db, err : gorm.Open(mysql.Open(dsn), gorm.Config{})if err ! nil {panic(failed to connect database)}// 创建默认的gin引擎r : gin.Default()// 添加路由处理程序r.GET(/films, func(c *gin.Context) {var films []Film// 查询所有电影db.Table(film).Find(films)// 返回JSONc.JSON(200, films)})// 启动服务器if err : r.Run(:8080); err ! nil {fmt.Println(Server failed to start.)}
}