文章目录
- 一、指针
- 1.1 指针地址和指针类型
- 1.2 指针取值
- 1.3 指针传值
- 二、new make
- 2.1 new
- 2.2 make
- 2.3 new VS make
- 三、函数
- 3.1 声明函数
- 3.2 函数返回值
- 3.3 返回值
- 3.4 返回值补充
- 四、函数类型与变量
- 五、高级函数
- 5.1 把函数作为入参
- 5.2 把函数作为返回值
- 5.3 匿名函数
- 5.4 闭包
- 六、defer
- 七、go内置函数
- 八、panic recover
一、指针
Go中的指针和C中的指针不同,Go中的指针是安全指针,不能偏移和计算,作用无非也就是 取址 【&】 和 取值【*】
内存中数据总是有内存地址的,内存地址就是指针变量的值。
比如:
我把 “我是中国人” 这个串赋值给 变量 A;
把内存地址赋值给变量 B;
B 就是一个指针变量。
1.1 指针地址和指针类型
Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int、*int64、*string等。
基本语法如 ptr:= &v
,其中 v的类型为T
- v : 被取址的变量,类型为T
- ptr :指针变量 ,用于接受地址,ptr的类型就是
*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
}
指针图示:
1.2 指针取值
使用 *
来取值:
func fn15(){
var a = 10
var b = &a
fmt.Printf("%v", *b) // b is a pointer variable
}
1.3 指针传值
只要记住: *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会报错,原因在于:
- go对于引用类型 的变量,使用时不仅要声明它,还要为其分配内存空间,否则无法存值。
- 对于值类型的变量,声明时不需要分配内存空间,因为在声明时已经默认分配好了内存。
- 要分配内存就需要使用到
make
和new
了
二、new make
2.1 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
}
2.2 make
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]
}
2.3 new VS make
- 都用于内存分配
- make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
- new用于 基础类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。
三、函数
Go中支持函数、匿名函数、闭包,函数在Go中是一等公民
3.1 声明函数
啥也不说,先举个栗子:
func intSum(x ...int) int {
var sum = 0
for _, v := range x {
sum = sum + v
}
return sum
}
- 可变参数 x 本质是一个 切片
- 可变参数一般要放到参数的最后
3.2 函数返回值
go语言的函数可以返回多个返回值,这个跟Java倒是不大一样,也十分有趣。
func handle(x int,y int) (int ,int){
return x -y ,x +y
}
func fn34() {
x, y := handle(10, 5)
}
- 如果有多个返回值,需要用
()
将返回值类型包起来
3.3 返回值
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。【Java表示做不到】
func handle2(x int,y int)(sum int,sub int){
sum = x +y
sub = x- y
return
}
3.4 返回值补充
函数返回值类型为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)
}
有了函数类型变量,我们就可以很方便地传递一个函数。
五、高级函数
5.1 把函数作为入参
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)
}
5.2 把函数作为返回值
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")
}
}
5.3 匿名函数
函数可以作为返回值,但是在函数内部不能直接再定义普通函数了,可以定义匿名函数。
匿名函数在回调函数和闭包中用途较多
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)
}
5.4 闭包
只要记住:闭包,就是 函数+ 函数的运行环境(函数外部变量的引用)。 要说闭包的原理,其实就两句:
- 函数可以作为返回值
- 函数中查找变量的顺序: 先在函数内部的代码块中找,然后往代码块外,全局这样一层层找
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会将后面跟随的语句延时处理:在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
实际分为了两个指令:
- 为返回值赋值
- RET指令
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内置函数
- close : 用来关闭channel
- len
- new : 分配内存;分配值类型,比如 int struct,返回指针
- make :分配内存,分配引用类型,比如 map slice channel,返回集合
- append
- panic 、 recover
八、panic recover
目前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")
}
这里要注意:
- recover()必须搭配defer使用。
- defer一定要在可能引发panic的语句之前定义。