函数function
-
go函数不支持嵌套、重载和默认参数
-
但支持以下特性:
无需声明原型、不定长度变参、多返回值、命名返回值参数、匿名函数、闭包 -
定义函数使用关键字func,且左大括号不能另起一行
-
函数也可以作为一种类型使用
package main
import "fmt"
func main() {
A(1,2,3,4,5,6,7)
}
// 不定长变参必须放在最后一个参数,传入的为slice
func A(a ...int) {
fmt.Println(a)
}
这里有个必须清楚的事情,这里传入的不定长参数,虽然传入之后为slice,但是跟实际传入一个silce还是存在不同,传入的不定长参数为值的copy,在函数中并不能真正修改原来的值,但是传入slice是地址的copy,是可以真正修改传入的slice
那么如何真正修改传入的值类型:传递指针, 就是相当于传递地址,相当于地址的copy
package main
import "fmt"
// 重点理解!!
func main() {
a := 1
fmt.Println(&a)
// 要想真正修改该值,就直接传递指针
A(&a)
fmt.Println(a)
}
// 这里接收的参数是一个指针所以是指针类型,类型前加*
// 这个时候的a是个地址,想修改值就需要通过加*取到值,再进行修改
func A(a *int) {
//
*a = 2
fmt.Println(*a)
}
package main
import (
"fmt"
)
func main() {
a := A
// A的复制品,A的类型
a()
}
func A() {
fmt.Println("func A")
}
转换一下变为匿名函数
package main
import (
"fmt"
)
func main() {
// 这里为这个函数命名,所以函数体去掉命名就可以了
// 这里称为匿名函数
a := func(){
fmt.Println("func A")
}
a()
}
闭包函数
package main
import "fmt"
// 闭包
func main() {
f:=closure(10)
fmt.Println(f(1)) // 11
fmt.Println(f(2)) // 12
}
// 返回值是一个函数,这个函数还接收函数,
func closure(x int)(func(int) int) {
return func(i int) int {
// 这个内层函数中的x可以直接取到外层函数的x
return x + i
}
}
如何证明内层函数的x是外层函数的x
package main
import "fmt"
// 闭包
func main() {
f:=closure(10)
fmt.Println(f(1)) // 11
fmt.Println(f(2)) // 12
}
// 返回值是一个函数,这个函数还接收函数,
func closure(x int)(func(int) int) {
fmt.Printf("%p\n", &x) // 0xc420014050
return func(i int) int {
fmt.Printf("%p\n", &x) // 0xc420014050
// 这个内层函数中的x可以直接取到外层函数的x
return x + i
}
}
是同一个x
defer
-
执行方式类似其它语言中的析构函数,在函数体执行结束后,按照调用顺序的相反顺序逐个执行
-
即使函数发生严重错误也会执行
-
支持匿名函数的调用
-
通常用于资源清理、文件关闭、解锁以及记录时间扽操作
-
通过与匿名函数配合可在return之后修改函数计算结果
-
如果函数体内某个变量作为defer时匿名函数的参数,则在定义defer时即已经获得了拷贝,否则则是引用某个变量的地址
-
go没有异常机制,但有panic/recover模式来处理错误
-
panic可以在任何地方引发,但recover只有在defer调用的函数中有效
package main
import "fmt"
func main() {
// 倒序输出
for i := 0; i < 3; i++ {
defer fmt.Println(i) // 输出2 1 0
}
}
但是上面是值copy,一旦存在地址引用的时候就会出现不同的情况
package main
import "fmt"
func main() {
// 将输出i放在匿名函数中,不再直接打印
for i := 0; i < 3; i++ {
// 原因是闭包,之前是值的copy,如今是地址的引用,
// 程序结束后i为3,所以三次输出都为3
defer func() {
fmt.Println(i) // 3 3 3
}()
}
}
存在地址引用的时候就要看defer的时候程序输出是最终地址中的值还是copy的值
go中处理异常机制
package main
import "fmt"
// 引发一个panic,进行recover
func main() {
// 依次执行三个函数
A()
B()
C()
}
func A(){
fmt.Println("func A")
}
func B() {
// 先从执行顺序,先panic触发错误,之后利用defer recover恢复
// 所以错误不会输出
// 如果panic放在前面,defer不会执行,
// defer放在前面,提前注册,就提前知道defer存在,panic之后会执行defer
defer func() {
if err:= recover(); err!=nil{
fmt.Println("recover in B")
}
}()
panic("panic in B")
}
func C() {
fmt.Println("func C")
}
/*
func A
recover in B
func C
*/
分析:
package main
import (
"fmt"
)
// 分析
func main() {
// array存4个func
var fs = [4]func(){}
for i := 0; i < 4; i++ {
defer fmt.Println("defer i = ", i)
defer func() { fmt.Println("defer_closure i = ", i) }()
fs[i] = func() { fmt.Println("closure i = ", i) }
}
for _, f := range fs {
f()
}
}
/*
首先循环4次,定义数组中四个函数,
之后下一个循环,迭代array,函数调用,闭包调用地址
closure i = 4
closure i = 4
closure i = 4
closure i = 4
defer_closure i = 4
defer i = 3
defer_closure i = 4
defer i = 2
defer_closure i = 4
defer i = 1
defer_closure i = 4
defer i = 0
*/
// 注意defer一定是函数执行完之后才开始的
结构struct
- go中的struct与C中的struct非常相似,并且go没有class
- 使用type <Name> struct{}定义结构,名称遵循可见性规则
- 支持指向自身的指针类型成员
- 支持匿名结构,可用作成员或定义成员变量
- 匿名结构也可以用于map的值
- 可以使用字面值对结构进行初始化
- 允许直接通过指针来读写结构成员
- 相同类型的成员可进行直接拷贝赋值
- 支持==与!=比较运算符,但不支持>或<
- 支持匿名字段,本质上是定义了以某个类型名为名称的字段
- 嵌入结构作为匿名字段看起来像继承,但不是继承
- 可以使用匿名字段指针
声明结构
package main
import "fmt"
type person struct {
Name string
Age int
}
func main() {
// 两种方法赋值
a := person{
Name: "james",
}
a.Age = 19
fmt.Println(a) // {james 19}
}
结构的传递属于值copy
package main
import "fmt"
type person struct {
Name string
Age int
}
func main() {
// 两种方法赋值
a := person{
Name: "james",
Age:19,
}
A(a)
fmt.Println(a) // {james 19} 还是19,并没有修改
}
func A(per person) {
per.Age = 13
fmt.Println("A", per) // A {james 13}
}
传递指针来解决
package main
import "fmt"
type person struct {
Name string
Age int
}
func main() {
a := person{
Name: "james",
Age:19,
}
A(&a)
fmt.Println(a) // {james 13} 实现真正改变
}
func A(per *person) {
// 虽然是个指针,但是对他操作的时候不许要加*了
per.Age = 13
fmt.Println("A", *per) // A {james 13}
}
更简便的方法在初始化赋值的时候直接取地址
// a成为了指向这个结构的地址,不需要每次传值的时候取地址
a := &person{
Name: "james",
Age:19,
}
匿名结构
package main
import "fmt"
type person struct {
Name string
Age int
Contact struct {
Phone, City string
}
}
func main() {
a := &person{
Name:"james",
Age:19,
}
// 内层匿名结构赋值
a.Contact.Phone = "12324324324"
a.Contact.City = "beijing"
fmt.Println(a)
}
匿名字段
package main
import "fmt"
type person struct {
string
int
}
func main() {
a := &person{
"james",
19,
}
fmt.Println(a)
var b *person
b = a
fmt.Println(b)
}
嵌入结构
package main
import "fmt"
type person struct {
Sex int
}
type teacher struct {
person
Name string
Age int
}
type student struct {
person
Name string
Age int
}
func main() {
// 嵌入结构的初始化
a := teacher{Name: "james", Age: 19, person: person{Sex: 1}}
b := student{Name: "mrsliu", Age: 30, person: person{Sex: 0}}
fmt.Println(a, b)
}
package main
import "fmt"
type person struct {
Sex int
}
type teacher struct {
person
Name string
Age int
}
type student struct {
person
Name string
Age int
}
func main() {
// 嵌入结构的初始化
a := teacher{Name: "james", Age: 19, person: person{Sex: 1}}
b := student{Name: "mrsliu", Age: 30, person: person{Sex: 0}}
fmt.Println(a, b)
a.Name = "jj"
a.Age = 10
// 两种修改内层结构的方式
a.person.Sex = 100
fmt.Println(a)
a.Sex = 200
fmt.Println(a)
}
当嵌套的结构与外层出现重名字段的时候会有一个查找顺序,
如果在外层查找到这个字段名就不会再找内层的,如果外层没有才会找内层嵌套
当然也可以使用 outName.inName.sameName指定输出嵌套的内层的同名字段
type human struct {
Name string
}
type man struct {
human
Name string
}
func main() {
a := man{
Name: "james",
human: human{Name: "jhon"},
}
fmt.Println(a)
fmt.Println(a.human.Name, a.Name)
// {{jhon} james}
// jhon james
}
但是这个查找关系不适用于嵌套的内层有两个同名字段,这个时候就会抛出错误
type A struct {
B
C
}
type B struct {
Name string
}
type C struct {
Name string
}
func main() {
a := A{
B: B{Name: "B"},
C: C{Name: "C"},
}
fmt.Println(a)
fmt.Println(a.Name, a.B.Name, a.C.Name)
// ambiguous selector a.Name
}
网友评论