赞
踩
Go中的指针和C中的指针不同,Go中的指针是安全指针,不能偏移和计算,作用无非也就是 取址 【&】 和 取值【*】
内存中数据总是有内存地址的,内存地址就是指针变量的值。
比如:
我把 “我是中国人” 这个串赋值给 变量 A;
把内存地址赋值给变量 B;
B 就是一个指针变量。
Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int、*int64、*string等。
基本语法如 ptr:= &v
,其中 v的类型为T
*T
,*
代表指针func fn15(){
var a = 10
var b = &a
// b is a pointer
fmt.Printf("type:%T , b: %v", b, b) //type:*int , b:0xc00001a078
}
指针图示:
使用 *
来取值:
func fn15(){
var a = 10
var b = &a
fmt.Printf("%v", *b) // b is a pointer variable
}
只要记住: *a
能把指针对应地址的值取出来即可。
func fn16(){
var a = 1
modify(a)
fmt.Printf("a=%v \n", a) // a = 1
modify2(&a) // 注意:这里传入的不是 a ,而是 a 的地址(指针)
fmt.Printf("a=%v \n", a) // a=100
}
func modify(x int){
x =10
}
func modify2(x *int){
*x = 100
}
有一个要注意的地方:
func fn17(){
var a *int //a is a pointer var
*a = 100
fmt.Println(*a) // panic: runtime error: invalid memory address or nil pointer dereference
}
再看:
func fn18(){
var b map[string]int // 这里没有初始化
b["test"]=1 // panic: assignment to entry in nil map
fmt.Println(b)
}
这个case会报错,原因在于:
make
和new
了func new(Type) *Type
*Type
,并且该指针对应的值为该类型的 零值比如说:
func fn19() { var a = new(int) fmt.Printf("%T \n", a) // *int fmt.Printf("%v \n", *a) // 0 var b = new(bool) fmt.Printf("%T \n", b) // *bool fmt.Printf("%v \n", *b) // false } func fn21(){ var a *int fmt.Println(a) // <nil> fmt.Printf("%T \n", a) // *int a = new(int) fmt.Println(a) // 0xc0000120a8 *a = 10 fmt.Println(*a) //10 b := &a // a 是指针 ,b 是指针的指针 fmt.Printf("%T \n", b) // **int fmt.Println(b) // 0xc000006028 }
make也是用于内存分配的,区别于new,
它只用于slice、map以及channel的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了
func make(t Type, size ...IntegerType) Type
func fn20(){
var b = make(map[string]int) // 声明并分配内存空间
b["test"] = 1
fmt.Println(b) // map[test:1]
}
Go中支持函数、匿名函数、闭包,函数在Go中是一等公民
啥也不说,先举个栗子:
func intSum(x ...int) int {
var sum = 0
for _, v := range x {
sum = sum + v
}
return sum
}
go语言的函数可以返回多个返回值,这个跟Java倒是不大一样,也十分有趣。
func handle(x int,y int) (int ,int){
return x -y ,x +y
}
func fn34() {
x, y := handle(10, 5)
}
()
将返回值类型包起来函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。【Java表示做不到】
func handle2(x int,y int)(sum int,sub int){
sum = x +y
sub = x- y
return
}
函数返回值类型为slice时,nil也是有效的slice,没必要显示返回一个长度为0的切片。
func fn36() [] int {
// 没必要返回 []int{}
return nil
}
go支持函数类型的变量,这个在Java中是木有的,没法在java中将一个方法赋值给一个变量。函数类型变量的存在,也说明了函数在Go中是一等公民
下面格式定义一个函数类型:这个函数接收两个int类型的函数,返回一个int类型的返回值。只要是满足这个条件的函数(比如下面的add sum
)都属于 calculation
类型。
type calculation func(int, int) int
func add(x int, y int) int {
return x + y
}
func sub(x int, y int) int {
return x - y
}
func fn35() {
var cal calculation
cal = add
sum := cal(10,20)
fmt.Printf("%v \n", sum)
}
有了函数类型变量,我们就可以很方便地传递一个函数。
func fn37(){
result := calculate(1,10, add)
fmt.Println(result)
result = calculate(1,10, sub)
fmt.Println(result)
}
func calculate(x int,y int,handler func(a,b int) int) int {
return handler(x,y)
}
func fn38() { a, _ := do("+") result := a(1, 20) fmt.Println(result) } func do(s string) (calculation, error) { switch s { case "+": return add, nil case "-": return sub, nil default: return nil, errors.New("not support this operand") } }
函数可以作为返回值,但是在函数内部不能直接再定义普通函数了,可以定义匿名函数。
匿名函数在回调函数和闭包中用途较多
func fn39() {
// 1 匿名函数赋值给变量
add := func(x, y int) int {
return x + y
}
result := add(1, 11)
fmt.Println(result)
// 2 匿名函数立刻执行
result = func(x, y int) int {
return x - y
}(2, 1)
fmt.Println(result)
}
只要记住:闭包,就是 函数+ 函数的运行环境(函数外部变量的引用)。 要说闭包的原理,其实就两句:
case0: 闭包的一个典型应用
func main() { fn46() } func fn46(){ ret := foo3(foo2, 2,20) foo1(ret) } // 一次接口是如此定义的:以无参的函数作为入参 func foo1(f func()) { fmt.Println("this is f1") f() } // 另一个接口是如此定义的,接收 x,y 两个参数。 //假如 foo1 是别人的接口,要让 foo2 去适配 foo1,我们没法直接把 foo2 // 传给 foo1 .这里就是 闭包的 典型应用场景 func foo2(x, y int) { fmt.Println("this is f2") fmt.Println(x + y) } //适配 f2 ,使其返回一个 没有入参、没有出参的函数类型 func foo3(f func(int,int) , a int,b int) func(){ fmt.Println("this is f3") ret := func () { f(a,b) } return ret }
case1:
func fn40() {
f := adder() //f 是个 闭包= f函数 + f运行环境(持有外部作用域的变量 var x)
fmt.Println(f(1)) //1
fmt.Println(f(3)) // 4
fmt.Println(f(5)) //9
}
func adder() func(int) int {
var x = 0
return func(i int) int {
x = x + i
return x
}
}
case2:
func fn41() { f := suffix(".aa") fmt.Println(f("test")) //test.aa f1 := suffix(".bb") fmt.Println(f1("test")) //test.bb } func suffix(s string) func(string) string { return func(name string) string { if !strings.HasSuffix(name, s) { return name + s } return name } }
case3:
func fn42() { add, sub := calc(10) // 返回的函数,都没有去修改 base 参数的值 fmt.Println(add(1), sub(2)) //11 8 fmt.Println(add(3), sub(4)) //13 6 } func calc(base int) (func(int) int, func(int) int) { add := func(delta int) int { return base + delta } sub := func(delta int) int { return base - delta } return add, sub }
defer会将后面跟随的语句延时处理:在defer所在的函数即将return时,将defer的语句按defer的顺序逆序执行。这个关键字在处理 资源释放、解锁、记录时间时特别有用。Java没有在语言层面提供类似的特性。
不如看个例子:
func fn42() { add, sub := calc(10) // 返回的函数,都没有去修改 base 参数的值 fmt.Println(add(1), sub(2)) //11 8 fmt.Println(add(3), sub(4)) //13 6 } func calc(base int) (func(int) int, func(int) int) { add := func(delta int) int { return base + delta } sub := func(delta int) int { return base - delta } return add, sub }
defer的本质:
在Go中return
不是一个原子操作,return x
实际分为了两个指令:
defer执行的时机就在返回值赋值操作后,RET指令执行前。
下面来看一个很有意思的面试热题:
Case1:
func fn1() { fmt.Println(f1()) //5 fmt.Println(f2()) //6 fmt.Println(f3()) //5 fmt.Println(f4()) //5 fmt.Println(f5()) //5 fmt.Println(f6()) //5 fmt.Println(f7()) //6 } func f1() int { // 没有给返回值 命名 x := 5 defer func() { x++ // 修改的 x 并不是返回值,而是 f1 函数的局部变量 }() return x // return 的三个操作:1.返回值赋值 2.defer 3.真正的RET 命令 } func f2() (x int) { // 返回值 x defer func() { x++ }() return 5 // 给返回值赋值 x =5 ; 执行defer 中的 x++,则x=6 ;return x,则return 6 } func f3() (y int) { //返回值 y x := 5 defer func() { x++ }() return x //给返回值赋值 y =x=5 ; 执行 defer 中的x++ ,修改了x=6, y 不变;return y=5 } // f4 和 f5 其实是 完全等价的函数 func f4() (x int) { defer func(x int) { // 只是将 函数中的 x 变量的副本加了1 而已,其实这里主要是变量命名 x 容易迷惑人,假如改成别的命名,比如 y 就 // 容易区分了 x++ }(x) return 5 //给返回值赋值 x=5 } func f5() (x int) { defer func(y int) { y++ }(x) return 5 } func f6() (x int) { defer func(x int) int { x++ // 修改的仍然只是 这个立即执行的匿名函数 的变量副本 return x }(x) return 5 //给返回值赋值 x=5 } // 传一个 变量 指针到匿名函数中去 func f7() (x int) { defer func(y *int) { (*y)++ }(&x) return 5 }
再来看defer的另一个特性:defer注册要延迟执行的函数时该函数所有的参数都需要确定其值
func fn() { x := 1 y := 2 defer calc("AA", x, calc("A", x, y)) // calc("A", x, y) 这个语句并不是在return前执行的,而是在 y:=2 之后就执行了 // 只不过最后在 return前,执行了整个 defer 语句 x = 10 defer calc("BB", x, calc("B", x, y)) // calc("B", x, y) 这个语句并不是在return前执行的,而是在 x = 10 之后就执行了 // 只不过最后在 return前,执行了整个 defer 语句 y = 20 } func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } // A 1 2 3 // B 10 2 12 // BB 10 12 22 // AA 1 3 4
目前go (1.15)没有异常机制,但是可以用 painc recover
来处理错误。go的设计思想是所有的异常都是具体的值,这可能导致开发时可以会出现很多的对错误的判断,这也是go为人诟病的地方。 panic recover
并不是 try catch
在go中的实现。
go中的panic可以在任何地方引发,但recover只有在defer调用的函数中有效:
先看个简单的例子:
func fn2() { f8() f10() f9() } func f8(){ fmt.Println("begins....") } func f9(){ fmt.Println("ends....") } // 主动 触发了 panic func f10(){ panic("panic happens") }
再看另一个例子:
func fn2() { f8() f10() f9() // 这句还是会执行的 fmt.Println("here it still runs") } func f8(){ fmt.Println("begins....") } func f9(){ fmt.Println("ends....") } // 主动 触发了 panic func f10(){ defer func () { err:= recover() if err!=nil { fmt.Println("recovers....") } }() panic("panic happens") }
这里要注意:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。