切片类型
数组是值传递类型,切片(slice)是对数组的一个连续“片段”的引用,所以切片是一个引用类型。
数组声明:var arr [index]int
切片:arr:=make([ ]int,10)
map类型
-
map定义
Go语言中的一种特殊类型,一种“元素对”的无序集合元素对包含一个key(索引)和一个value(值),也称为“关联数组”。这是一种能够快速寻找值的理想结构:给定一个key就可以迅速找到对应的value。
map是一种引用类型,声明方式:
var name map[key_type]value_type
例:
var literalMap map[string]string
var assignedMap map[string]string
literalMap = map[string]string{"first": "go", "second": "web"}
createMap := make(map[string]float32)
assignedMap = literalMap
createMap["k1"] = 99
createMap["k2"] = 108
assignedMap["second"] = "program"
fmt.Println(literalMap["first"])
fmt.Println(assignedMap["second"])
fmt.Println(createMap["k1"])
fmt.Println(literalMap["third"])
运行结果:
go
program
99
assignedMap是literalMap 的引用,对assignedMap的修改也会影响literalMap 的值。
可以用make()函数构造map,但不能使用new()函数来构造。
//new()函数构造
crete2Map := new(map[string]float32)
crete2Map["k1"] = 10086
# main/pritice/map
.\main.go:14:11: invalid operation: cannot index crete2Map (variable of type *map[string]float32)
.\main.go:20:23: invalid operation: cannot index crete2Map (variable of type *map[string]float32)
- map容量
和数组不同,map可以根据新增的元素对来动态地伸缩,因此它不存在固定长度或最大限制。但也可以选择标明map的容量capacity,格式如下:
make(map[key_type]value_type,cap)
例如:
map:=make(map[string]string,10)
当map增长到容量上限后,如果在增加新的元素对,则map的大小会自动加1
例:
literalMap = map[string]string{
"first": "go", "second": "web",
"third":"haha","woo":"fire",
}
- 用切片作为map的值
既然一个key对应一个value,而value又是一个原始类型,那么一个key要对应多个值怎么办?
通过将value定义为[]int类型或者其他类型的切片,就可以优雅的解决这个问题了,如下:
map1:=make(map[int] []int)
map2:=make(map[int] *[]int)
匿名函数
匿名函数也称为“闭包”,是指一类无需定义标识符(函数名)的函数或子程序,匿名函数往往以变量方式被传递。
- 匿名函数的定义
匿名函数可以理解为没有函数名的普通函数,定义如下:
func (参数列表) (返回值列表) { //函数体 }
匿名函数是一个“内联”语句表达式。匿名函数的优越性在于:可以直接使用函数内的变量,不必声明。
实例:
func main() {
x, y := 10, 20
// 匿名函数
defer func(a int) {
fmt.Println("defer x,y=", a, y)
}(x)
x += 10
y += 100
fmt.Println(x, y)
}
运行结果:
PS E:\Go_work\src\main\pritice\func> .\func.exe
20 120
defer x,y= 10 120
- 匿名函数的调用
(1) 在定义时调用匿名函数
匿名函数可以在声明后调用或者声明时直接调用,例:
// 定义匿名函数并赋值给f变量
f := func(data int) {
fmt.Println("hi this is closure", data)
}
f(6)
// 直接声明并调用
func(data int) {
fmt.Println("hi this is closure,directly", data)
}(8)
运行结果:
PS E:\Go_work\src\main\pritice\func> .\func.exe
hi this is closure 6
hi this is closure,directly 8
匿名函数的用途非常广泛。匿名函数本身是一种值,可以方便地保存在各种容器中实现回调函数和操作封装。
(2)用匿名函数作为回调函数
回调函数简称“回调”,是指通过函数参数传递到其他代码的某一块可执行代码的引用。
匿名函数作为回调函数来使用,在go语言的系统包中是很常见的,在string包中就有这种实现:
func TrimFunc(s string,f func(rune) bool) string {
return TrimRightFunc(TrimLeftFunc(s,f),f)
}
可以使用匿名函数作为参数,来实现对切片中的元素的遍历操作,例:
func visitPrint(list []int, f func(int)) {
for _, value := range list {
f(value)
}
}
func main() {
sli := []int{1, 6, 8}
//仔细观察函数结构,琢磨匿名函数回调的过程
visitPrint(sli, func(value int) {
fmt.Println(value)
})
}
运行结果:
PS E:\Go_work\src\main\pritice\func> .\func.exe
1
6
8
defer 延迟语句
- 什么是defer延迟语句
在函数中,经常需要创建资源(比如数据库连接,文件句柄,锁等)。为了在函数执行完毕后及时地释放资源,Go的设计者提供defer延迟语句。
defer语句主要用在函数中,用在函数结束(return 或 panic 异常导致结束) 之前执行某个动作,是一个函数结束前最后执行的动作。
在Go语言一个函数中,defer 语句的执行逻辑如下。
(1)当程序执行到一个defer时,不会立即执行defer 后的语句,而是将defer后的语句压入一个专门储存defer语句的栈中,然后继续执行函数的下一个语句。
(2)当函数执行完毕后,再从defer栈中依次从栈顶取出语句执行(注:先进去的最后执行,最后进去的最先执行,栈的特点,先进后出)。
(3)在defer将语句放入栈时,也会将相关的值复制进入栈中。
例:
func main() {
deferCall()
}
func deferCall() {
defer func1()
defer func2()
defer func3()
}
func func1() {
fmt.Println("A")
}
func func2() {
fmt.Println("B")
}
func func3() {
fmt.Println("C")
}
运行结果:
PS E:\Go_work\src\main\pritice\defer> .\defer.exe
C
B
A
- defer与return的执行顺序
- 先为返回值赋值,即将返回值放到一个临时变量中,然后执行defer,然后return到函数被调用处。
- 如果所在函数为有名返回值函数,return第一步先把返回值放到有名返回值变量中,如果恰好defer函数中修改了该返回值,那么最终返回值是更新后的。但是如果所在函数为无名返回值函数,那么return第一步先把返回值放到一个临时变量中,defer函数无法获取到这个临时变量地址,所以无论defer函数做任何操作,都不会对最终返回值造成任何变动。
例1:无名返回值(即函数返回值为没有命名的返回值)
(1)
var name string = "go"
func myfunc() string {
defer func() {
name = "python"
}()
fmt.Println("myfunc()函数里的name: ", name)
return name
}
func main() {
myname := myfunc()
fmt.Println("main()函数例的name: ", name)
fmt.Println("main()函数里的mynane: ", myname)
}
运行结果:
PS E:\Go_work\src\main\pritice\defer> .\defer.exe
myfunc()函数里的name: go
main()函数例的name: python
main()函数里的mynane: go
(2)
func reint() int {
var x int
defer func() {
x++
fmt.Println("defer1=", x)
}()
defer func() {
x++
fmt.Println("defer2=", x)
}()
return x
}
func main() {
fmt.Println("return= ", reint())
}
运行结果:
PS E:\Go_work\src\main\pritice\defer> .\defer.exe
defer2= 1
defer1= 2
return= 0
例2:有名返回值(函数返回值为已经命名的返回值)
func reint() (x int) {
defer func() {
x++
fmt.Println("defer1=", x)
}()
defer func() {
x++
fmt.Println("defer2=", x)
}()
return x
}
func main() {
fmt.Println("return= ", reint())
}
运行结果:
PS E:\Go_work\src\main\pritice\defer> .\defer.exe
defer2= 1
defer1= 2
return= 2
- defer常用应用场景
(1)关闭资源
(2)与recover()函数一起使用
当程序出现宕机或者遇到panic错误时,recover()函数可以恢复执行,而且不会报宕机错误。之前说过,defer不但可以在return返回前调用,也可以在程序宕机显示panic错误时,在程序出现宕机之前被执行,依次来恢复程序。
Go面向对象编程
面向对象只是一种编程思想。面向对象有三大基本特征。
- 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。
- 继承:使得子类具有父类的属性和方法或者重新定义,追加属性和方法等。
- 多态:不同对象中同种行为的不同实现方式。
封装
1.属性
Go语言使用结构体对属性进行封装。结构体就像是类的一种简化形式。例如,我们要定义一个三角形,每个三角形都有底和高。可以这样进行封装:
type Triangle struct{
Bottom float32
Height float32
}
2.方法
既然有了“类”,那么“类”的方法在哪里呢?Go语言中也有方法(Methods)。方法是作用在接受者(receiver)上的一个函数,接受者是某种类型的变量。因此,方法是一种特殊类型的函数。
package main
import "fmt"
type Triangle struct {
Bottom float32
Height float32
}
// 定义一个 Area 对三角形面积进行计算
func (t *Triangle) Area() float32 {
return (t.Height * t.Bottom) / 2
}
func main() {
//声明一个 Triangle 类型变量
r := &Triangle{6, 8}
//调用 Area 方法计算面基
fmt.Println(r.Area())
}
运行结果:
24
3.访问权限
Go语言通过字母大小写来控制可见性的
继承
Go语言中没有extends关键字,而是使用在结构体中内嵌匿名类型的方法来实现继承。例如,定义一个Engine接口类型和一个Bus结构体,让Bus结构体包含一个Engine接口的匿名字段:
type Engine interface {
Run()
Stop()
}
type Bus struct {
Engine //包含Engine类型的匿名字段
}
//此时,Engine里面的方法“晋升”为外层Bus里面的方法
func (b *Bus) Working() {
b.Run()
b.Stop()
}
多态
在面向对象中,多态的特征是不同对象中同种行为的不同实现方式。在Go语言中可以使用接口实现这个特征。
//多态
// Square 正方形结构体
type Square struct {
sideLen float32
}
// Triangle 三角形结构体
type Triangle struct {
Bottom float32
Height float32
}
// Shape 接口
type Shape interface {
Area() float32
}
// Area 计算三角形的面积
func (t *Triangle) Area() float32 {
return t.Bottom * t.Height / 2
}
// 计算正方形的面积
func (s *Square) Area() float32 {
return s.sideLen * s.sideLen
}
func main() {
t := &Triangle{6, 8}
s := &Square{8}
shapes := []Shape{t, s}
for n, _ := range shapes {
fmt.Println("图形数据是:", shapes[n])
fmt.Println("面积是:", shapes[n].Area())
}
}
运行结果:
图形数据是: &{6 8}
面积是: 24
图形数据是: &{8}
面积是: 64
接口
接口(interface)类型是对其他类型行为的概括与抽象。接口类型定义一组方法,但是不包含这些方法的具体实现。
接口本质是一种类型,(指针类型),接口可以实现多态功能。
type dog struct{}
func (d *dog) say() {
fmt.Println("汪汪汪~")
}
type cat struct{}
func (c *cat) say() {
fmt.Println("喵喵喵~")
}
type sayer interface {
say()
}
func da(arg sayer) {
arg.say()
}
func main() {
d := dog{}
da(&d)
c := cat{}
da(&c)
}
空接口
如果接口没有任何方法声明,则为空接口(interface{})。它的用途类似面向对象里的根类型,可以被赋值为任何类型的对象。
将空接口作为函数参数,代表该函数可以接收任意参数。map,silce 也可以用接口实现。