一、函数
函数是基本的代码块,用于执行一个任务。
go语言至少有个main()函数
1)函数定义
func function_name( [parameter list] ) [return_types] {
函数体
}
func:声明这是一个函数
function_name:函数名称,函数名和参数列表一起构成了函数签名
parameter list:参数列表,注意类型在变量名之后
return_types:返回类型,不是必须的,当没有返回值时,可以不指定返回类型,也可以返回多个值,如(string,string)
函数体:函数定义的代码集合
常用函数用法:
// 函数多参无返回值 func func_name(a,b int, c string){} // 函数无参无返回值 func func_name(){} // 单个返回值 func func_name(s string) string{} // 多个返回值 func func_name (s string) (string,int){} // 命名返回参数 func func_name(s string) (result string){ ... result=1 return } // 可变参数,可变参数只能做为函数参数存在,并且是最后一个参数,本质上是slice func func_name(s string,args ...int){} // 匿名函数,调用:f(1,2) f := func(x,y int) int { return x + y }
注意:
①:Go函数不支持重载
②:一个包中不能有两个名字一样的函数
③:当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他的类型可以省略
④:函数可以返回任意数量的返回值
⑤:使用关键字 func 定义函数,左大括号依旧不能另起一行
2)函数参数
函数如果使用参数,该变量可称为函数的形参。
形参就像定义在函数体内的局部变量。
调用函数,传递过来的变量就是函数的实参,可以通过两种方式来传递参数:
传递类型 | 描述 |
---|---|
值传递 | 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 |
引用传递 | 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。 |
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数
注意:
①:无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低
②:map、slice、chan、指针、interface默认以引用的方式传递
可变参数:
Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。
在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可
任意类型的不定参数:
就是函数的参数和每个参数的类型都不是固定的
用空接口:interface{}传递任意类型数据是Go语言的惯例用法,而且interface{}是类型安全的
3)递归函数
递归,就是在运行的过程中调用自己
Go语言支持递归,注意在使用递归时,要设置退出条件,避免陷入死循环
func recursion() { recursion() /* 函数调用自身 */ } func main() { recursion() }
构成递归需具备的条件
①:子问题须与原始问题为同样的事,且更为简单
②: 不能无限制地调用本身,须有个出口,化简为非递归状况处理
4)defer
Go语言的 defer 语句会将其后面跟随的语句进行延迟处理
在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。
关键字 defer 的用法类似于面向对象编程语言 Java 和 C# 的 finally 语句块,它一般用于释放某些已分配的资源,典型的例子就是对一个互斥解锁,或者关闭一个文件
defer关键字特性:
①:关键字 defer 用于注册延迟调用
②:这些defer调用直到所在函数 return 前才被执行。因此,可以用来做资源清理
③:多个defer语句,defer 所在的函数返回后,将按照后进先出的顺序执行 defer 保存的延迟调用函数,也就是说,后定义的defer函数先执行
④:defer 延迟调用函数可以读取并分配给返回函数的命名返回值
⑤:defer语句中的变量,在defer声明时就决定了
defer用途:
①:关闭文件句柄
②:锁资源释放
③:数据库连接释放
④:panic捕获
5)init函数和mian函数
①:init函数
go语言中init函数用于包(package)的初始化,该函数是go语言的一个重要特性
init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等
每个包可以拥有多个init函数,包的每个源文件也可以拥有多个init函数
对同一个go文件的init()调用顺序是从上到下的。
对同一个包中不同文件是按文件名字符串比较“从小到大”顺序调用各文件中的init()函数
不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序
init函数不能被其他函数调用,而是在main函数执行之前,自动被调用
②:main函数
Go语言程序的默认入口函数(主函数)
func main(){
//函数体
}
6)函数表达式
使用函数表达式实现三目运算
// 函数表达式,实现三目运算 // 格式:func() returnType {...}() i := 1 j := 2 k := func() int { if i > j { return i } return j }() fmt.Println(k)
带参数的函数表达式:
h := func(a, b int) int { if a > b { return a } return b }(i, j)
7)匿名函数
匿名函数是指不需要定义函数名的一种函数实现方式,匿名函数由一个不带函数名的函数声明和函数体组成。
匿名函数的优越性在于可以直接使用函数内的变量,不必声明
在Go里面,函数可以像普通变量一样被传递或使用。Golang匿名函数可赋值给变量,做为结构字段,或者在 channel 里传送。
如:
getSqrt := func(a float64) float64 { return math.Sqrt(a) } fmt.Println(getSqrt(4))
上面先定义了一个名为getSqrt 的变量,初始化该变量时和之前的变量初始化有些不同,使用了func,func是定义函数的,可是这个函数和上面说的函数最大不同就是没有函数名,也就是匿名函数。这里将一个函数当做一个变量一样的操作
8)闭包
什么是闭包?闭包就是能够读取其他函数内部变量的函数。
通常只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁
Go语言支持闭包,通过匿名函数的方式,如:
// 创建函数a,返回另外一个函数。该函数的目的是在闭包中递增i的变量 func a() func() int { i := 0 b := func() int { i++ fmt.Println(i) return i } return b } func main() { c := a() c() // 1 c() // 2 c() // 3 a() //不会输出i }
9) new和make函数
在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。要分配内存,就引出来今天的new和make。 Go语言中new和make是内建的两个函数,主要用来分配内存
①:new
new函数的签名: func new(Type) *Type
Type表示参数类型,*type表示类型指针,new函数返回一个指向该类型内存地址的指针
new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。
如var a *int只是声明了一个指针变量a但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。
应该按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了:
func main() { var a *int a = new(int) *a = 10 fmt.Println(*a) }
new引发panic异常:
//定义一个结构体,age字段为指针 type Student struct { age *int } //获取结构体对象指针 func getStudent() *Student { s := new(Student) // panic,因为new只会为结构体Student申请一片内存空间,不会为结构体中的指针age申请内存空间 return s }
②:make
make函数的签名:func make(t Type, size ...IntegerType) Type
make也是用于分配内存的,区别于new,它只用于slice、map以及channel的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没必要返回他们的指针了。
make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作
new函数和make函数的区别
相同点:
底层都是通过mallocgc申请内存
不同点:
①:make 返回值是”引用类型“,new 返回值是指针类型
②:make仅用于初始化 slice,map 和 chan;new 可用于初始化任意类型(new并不常用)
二、方法
Go 语言中同时有函数和方法。一个方法就是一个包含了接收者的函数。
方法可以将类型和方法封装在一起,实现强耦合。
接收者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集
Go语言中的方法时一种作用于特定类型变量的函数。这种特定类型变量叫做接收者,接收者的概念类似于Java语言中的this,和Python语言中的self。只不过Go语言中需要将this显式的声明出来。
方法的定义格式如下:
func (接收者变量 接收者类型) 方法名(参数列表) (返回类型) {
方法体
}
方法示例:
// 定义结构体 type User struct { name string gender string address string age int } // 接收User类型的方法 // 值类型的接收者 func (user User) getName() string { return user.name } // 指针类型的接收者 func (user *User) setName(name string) { user.name = name } func main() { user := &User{ name: "yangyongjie", age: 27, gender: "male", address: "nanjing", } name := user.getName() // 该方法只能User结构体类型的变量或指针才能调用 fmt.Println(name) // yangyongjie user1 := &User{} user1.setName("yyj") fmt.Println(user1.name) // yyj }
值类型的接收者和值类型的接收者方法的区别:
指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式十分接近于Java语言中的this,和Python语言中的self
当方法作用于值类型的接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但是修改操作只是针对副本,无法修改接收者变量本身。
如:
import "fmt" // 定义结构体 type User struct { name string gender string address string age int } // 值类型的接收者 func (user User) setAddress(address string) { user.address = address } // 指针类型的接收者 func (user *User) setName(name string) { user.name = name } func main() { user := &User{ name: "yangyongjie", age: 27, gender: "male", address: "nanjing", } // 接收值类型的方法,user变量本身值没有被修改 user.setAddress("beijing") fmt.Println(user.address) // nanjing // 接收指针类型的方法,user变量本身值没有被修改 user.setName("yyj") fmt.Println(user.name) // yyj }
什么时候应该使用指针类型接收者?
①:需要修改接收者中的值
②:接收者是拷贝代价比较大的大对象
③:保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者
结构体变量和结构体指针的理解:
结构体指针指向的是结构体变量的内存地址
结构体变量是结构体类型的变量本身的值
END.