1.go函数中的三种返回方式
// 函数的返回值有一个,类型是int
func fun1(a string, b int) int {
fmt.Println("--------fun1----------")
fmt.Println("a=", a)
fmt.Println("b=b", b)
c := 100
return c
}
func fun2(a string, b int) (string, int) {
fmt.Println("--------fun2-----------")
fmt.Println("a=", a)
fmt.Println("b=", b)
return "hello go", 2
}
func fun3(a string, b int) (r1 int, r2 int) {
fmt.Println("--------fun3-----------")
fmt.Println("a=", a)
fmt.Println("b=", b)
r1 = 100
r2 = 200
return
}
func main() {
fun1_ret1 := fun1("a", 1)
fun2_ret1, fun2_ret2 := fun2("a", 1)
fun3_ret1, fun3_ret2 := fun3("a", 1)
fmt.Printf("fun1_ret1=%d\n", fun1_ret1)
fmt.Printf("fun2_ret1=%s,fun2_ret2=%d\n", fun2_ret1, fun2_ret2)
fmt.Printf("fun3_ret1=%d,fun3_ret2=%d\n", fun3_ret1, fun3_ret2)
}
2.import导包路径和init方法
-
golang里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。
-
go程序会自动调用init()和main(),但是从程序的可读性和维护性,都建议只有一个init函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数。
-
程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次。
-
等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。下图详细地解释了整个执行过程:
注意:在包外调用的函数首字母需要大写
文件目录结构
lib1.go
package lib1
import "fmt"
func init(){
fmt.Println("init lib1")
}
func Lib1(){
fmt.Println("run Lib1")
}
lib2.go
package lib2
import "fmt"
func init() {
fmt.Println("init lib2")
}
func Lib2() { //包外调用的函数首字母需要大写
fmt.Println("run Lib2")
}
main.go
import (
"fmt"
"in/lib1"
"in/lib2"
)
func init() {
fmt.Println("init main")
}
func main() {
fmt.Println("run main")
lib1.Lib1()
lib2.Lib2()
}
go run main.go
2.1指针声明
与C相同,Go语言让程序员决定何时使用指针。变量其实是一种使用方便的占位符,用于引用计算机内存地址。Go 语言中的的取地址符是&
,放到一个变量前使用就会返回相应变量的内存地址。
指针变量其实就是用于存放某一个对象的内存地址。
go语言存在三种类型的指针:普通指针(*T),uintptr,unsafe.Pointer
*普通指针(T)
普通指针的用法和C语言的用法相同
func Ptr_test() {
var a int = 4
var b float32 = 11.32
var c complex64 = complex(1, 2) //复试的定义方式
//第一种定义方式
var ptra *int = &a
var ptrb *float32 = &b
var ptrc *complex64 = &c
fmt.Printf("type:%T,&p:%p,p:%d\n", ptra, ptra, *ptra)
fmt.Printf("type:%T,&p:%p,p:%f\n", ptrb, ptrb, *ptrb)
fmt.Printf("type:%T,&p:%p,p:%v\n", ptrc, ptrc, *ptrc)
}
uintptr
uinptr是一个无符号的整形,可以保存一个指针地址。可以进行指针运算
uintptr无法持有对象, GC不把uintptr当指针, 所以uintptr类型的目标会被回收。想取值需要转成unsafe.Pointer后, 需再转到相对应的指针类型。
unsafe.Pointer
unsafe.Pointer可以指向任意类型的指针。不能进行指针运算,不能读取内存存储的值(想读取的话需要转成相对应类型的指针)。它是桥梁,让任意类型的指针实现相互转换, 也可以转换成uintptr 进行指针运算。
一般的指针运算会走一下三个步骤。
1.将unsafe.Pointer转换为uintptr => 2.对uintptr执行算术运算 => 3.将uintptr转换回unsafe.Pointer,然后转成访问指向的对象的指针类型。
示例
func Uintptr_test() {
a := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
b := unsafe.Pointer(uintptr(unsafe.Pointer(&a[0])) + 9*unsafe.Sizeof(a[0]))
// b是 unsafe.Pointer 所以可转任意指针,转成(*int)指针后在取值
fmt.Printf("b: %v, unsafe.Sizeof(a[0]): %d\n", *(*int)(b), unsafe.Sizeof(a[0])) //b: 9, unsafe.Sizeof(a[0]): 8
c := unsafe.Pointer(uintptr(unsafe.Pointer(&a)) + uintptr(16)) //int是8位长度 所以16 等于 16/8 挪动了2位,所以下面结果是2
fmt.Printf("c: %v\n", *(*int)(c)) //c: 2
}
实现swap()函数
func swap(a *int, b *int) {
var tmp int = *a
*a = *b
*b = tmp
}
func main() {
var a int = 4
var b int = 5
fmt.Println("before swap,a:%d,b:%d", a, b)
swap(&a, &b)
fmt.Println("after swap,a:%d,b:%d", a, b)
}
2.2空指针
当一个指针被定义后没有分配任何变量时,它的值为nil,也就是空指针,与C语言中的NULL一致。
3.defer
defer语句被用于预定对一个函数的调用。可以把这类被defer语句调用的函数称为延迟函数。
defer作用:
- 释放占用的资源
- 捕捉处理异常
- 输出日志
defer一般用于资源回收或者异常捕获,defer后面的语句会在return执行之再执行。
如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。【defer的底层其实是一个栈,底层存储结构是个链表,每次程序进入函数且运行到defer时,会把其后面的函数放到链表里】
func demo() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
defer fmt.Println("4")
}
func main() {
demo()
}