前言:
今天的篇幅有一些长,内容相对而言也不是很容易理解,今天分享的内容主要就是关于包的引用,感谢大佬“栗”提出的宝贵意见,关于包的引用在第一天分享的时候说的话不是特别的准确,在引用第三方的包的时候,包名需要与目录名一致才可以,下面将函数的封装里面会详细的说明;
函数格式
在go语言中,声明函数的格式如下:
func function_name ( [parameter list]) [return_types] {
//函数体
}
1.func:函数声明关键字
2.function_name:函数名称,函数名和参数列表一起构成了函数签名
3.parameter list:参数列表,是可选项。餐胡就像是一个占位符,当函数被调用的时候,可以将值传递给参数,这个值被称为“实际参数”。参数列表指定的是参数类型、顺序及参数个数。参数是可选的,奇函数可以不包含参数
4.return_types:包含返回类型的返回值,是可选项。如果函数需要返回一列值,则该项值的数据类型是返回值。如果有些功能不需要返回值,则return_types可以为空。
5.函数体:函数定义的代码集合
// var.go
package main
import (
"fmt"
)
func main() {
array := []int{1, 3, 5} //定义局部变量
var ret int
ret = min(array) //调用min函数
fmt.Printf("最小值是:%d\n", ret) //返回最小值
}
func min(arr []int) (min int) { //min函数
min = arr[0]
for _, v := range arr {
if v < min {
min = v
}
}
return
}
示例 2:
package main
import (
"fmt"
)
func compute(x, y int) (int, int) {
return x + y, x * y
}
func main() {
a, b := compute(2, 3)
fmt.Println(a, b)
return
}
如果按照函数头声明的顺序返回值,则return语句后表达式为空。若是return后面表达式不为空,则会返回return表达式的值
// var.go
package main
import (
"fmt"
)
func change(a, b int) (x, y int) {
x = a + 100
y = b + 100
//return //返回 102 103
return y, x
}
func main() {
a := 2
b := 3
c, d := change(a, b)
fmt.Println(c, d)
}
函数参数
1.参数的作用
参数可以有一个或多个参数。如果函数使用参数,则该参数被称为函数的形参,形参就像是定义在函数体内的局部变量
形参:在定义函数时,用于接收外部传入的数据被称为形式参数,简称形参
实参:在调用函数时,传给形参的实际的数据被称为实际参数,简称实参
函数参数调用需遵守如下形式:
函数名称必须匹配
实参与形参必须一一对应:顺序、个数、类型
2.可变参数
Go函数支持可变参数,接收变参的函数有着不定数量的参数。定义可接受变参的函数形式如下
func myFunc (arg … string) {
//…
}
arg … string 告诉go这个函数可接受不定数量的参数。需要注意的是,这些参数的类型全部都是string。在相应的函数体里,变量arg是一个string的slice,可以通过for-range语句进行遍历
for _, v := range arg {
fmt.Printf(“string is : %s\n”, v)
}
3.参数传递
调用函数,可以通过如下两种方式来传递参数
1.值传递
值传递,是指在调用函数时将实际参数复制一份传递到函数中。在这样的函数中,对参数进行修改,不会影响到实际参数的值
// var.go
package main
import (
"fmt"
)
func change(a, b int) int {
var tmp int
tmp = a
a = b
b = tmp
return tmp
}
func main() {
num1 := 2
num2 := 3
fmt.Printf("交换前num1的值:%d\n", num1)
fmt.Printf("交换前num2的值:%d\n", num2)
change(num1, num2)
fmt.Printf("交换后num1的值:%d\n", num1)
fmt.Printf("交换后num2的值:%d\n", num2)
}
大家可以看到,此时并没有更改成功,相信大家学习了指针的调换之后,一定对这个代码很眼熟,他们虽然传递了,但是并没有实际的建立起关系。
值传递并没有实现真正的交换,若是想达到真正的交换,影响实际参数的值,需要使用引用传递。
2.引用传递
引用传递,指的是在调用函数时,将参数的地址传递到函数中。那么,在函数对参数进行修改的时候,将会修改实际参数的值
// var.go
package main
import (
"fmt"
)
func change(a, b *int) int {
var tmp int
tmp = *a //将a值赋予给tmp
*a = *b //将b值赋予给a
*b = tmp //将tmp赋予给b
return tmp
}
func main() {
num1 := 2 //定义局部变量
num2 := 3
fmt.Printf("交换前num1的值:%d\n", num1)
fmt.Printf("交换前num2的值:%d\n", num2)
change(&num1, &num2) //调用函数
fmt.Printf("交换后num1的值:%d\n", num1)
fmt.Printf("交换后num2的值:%d\n", num2)
}
匿名函数
匿名函数也被称为“匿包”,是指一类无需定义标识符(函数名)的函数或子程序。匿名函数没有函数名,只有函数体。函数可以作为一种被赋值给函数类型的变量;匿名函数往往以变量方式传递
1.匿名函数的定义
匿名函数可以被理解为没有名字的普通函数
func (参数列表) (返回值列表) {
//函数体
}
匿名函数是一个“内联”语句或表达式。匿名函数的优越性在于:可以直接使用函数内的变量,不用声明
// var.go
package main
import (
"fmt"
)
func main() {
x, y := 6, 8
defer func(a int) { //创建匿名函数 ,defer为延迟语句,会先执行y = y + 100,若是没有defer,则返回6,8
fmt.Println("defer x, y =", a, y) //在结束fmt.Println(x, y)的输出后打印
}(x) //将x := 6传入匿名函数并调用,而非x = x + 10
x = x + 10
y = y + 100
fmt.Println(x, y)
}
2.匿名函数的调用
1)在定义是调用匿名函数
匿名函数可以在声明后直接调用,也可以直接声明并调用
// var.go
package main
import (
"fmt"
)
func main() {
a := func(job string) { //定义匿名函数并赋值给变量 a
fmt.Println("This is a job like", job)
}
a("teacher") //此时a变量的类型是func(),可以直接调用
func(job string) {
fmt.Println("This is a job like", job)
}("engineer") //直接声明并调用
}
2)用匿名函数做为回调函数
回调函数简称“回调”,是指通过函数参数传递到其他代码的某一块可执行代码的引用
匿名函数作为回调函数来使用,在go语言的系统包是很常见的。在strings包中就有这种实现:
func TrimFunc (s string, f func (rune) bool) string {
return TrimRightFunc (TrimLeftFunc(s, f), f)
}
可以使用匿名函数体作为参数,来实现对切片中的元素的遍历操作
// var.go
package main
import (
"fmt"
)
func visitPrint(list []int, f func(int)) {
for _, value := range list {
f(value)
//fmt.Println(value)
}
}
func main() {
sli := []int{1, 3, 5}
visitPrint(sli, func(value int) {
fmt.Println(value)
})
}
defer延迟语句
1.什么是refer延迟语句
在函数中,经常需要创建资源(不如数据库链接、文件句柄等)。为了在函数执行完毕后及时的释放资源,此时可以应用defer语句
defer语句主要用在函数当中,用来在函数结束(return或者panic异常导致结束)之前执行某个动作,是一个函数结束前最后执行的动作
defer语句执行逻辑如下
1)当程序执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入一个专门存储defer语句的栈中,然后执行函数下一个语句
2)当函数执行完毕后,再从defer栈中依次从栈顶取出语句执行(注:先进去的最后执行,最后进去的最先执行
3)在defer语句放入栈时,也会将相关的值复制进入栈中
// var.go
package main
import (
"fmt"
)
func main() {
deferAll()
}
func deferAll() {
defer func1()
defer func2()
defer func3()
}
func func1() {
fmt.Println(1)
}
func func2() {
fmt.Println(2)
}
func func3() {
fmt.Println(3)
}
2.defer与return的执行顺序
// var.go
package main
import (
"fmt"
)
var name string = "go"
func myfunc() string {
defer func() {
name = "python"
}()
fmt.Printf("myfunc函数里的name:%s\n", name) //此时结果为全局变量 name = go
return name //return执行顺序高于defer,此时name依然是go
}
func main() {
myname := myfunc() //先一步调用defer,此时name已经被更改为python
//fmt.Println(myname)
fmt.Printf("main函数里的name:%s\n", name) //受defer影响,此时name已经变成python
//myname := myfunc() //若是输出打印后调用,则结果发生改变
fmt.Printf("myfunc函数里的name:%s\n", myname) //return先一步输出name值为go
}
3.defer常用应用场景
1)关闭资源
在创建资源后,需要释放资源内存,避免占用内存,系统资源等。可以再打开资源的语句的下一句,直接用defer语句提前把关闭资源的操作注册了,这样做会减少程序员忘写关闭资源的情况
2)和recover()函数一起使用
当程序出现宕机或者遇到panic错误时,recover()函数可以恢复执行,而且不会报告宕机错误。defer不但可以在return返回前调用,也可以在程序宕机显示panic错误时,在程序出现宕机之前被执行,以此来恢复程序。
Go语言面向对象编程
go语言没有class类的概念,但是并不代表go不支持面向对象编程,面向对象有如下特征
封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式
继承:使得子类具有父类的属性和方法或者重新定义、追加属性和方法等
多态:不同对象中同种行为的不同实现方式
封装
1.属性
go语言中可以使用结构体对属性进行封装。结构体就像是类的一种简化形式。例如,我们要定义一个三角形,每个三角形都有底和高,可以进行这样封装
type Triangle struct {
Bottom float32
Height float32
}
2.方法
既然有了类,那类的方法在哪呢?Go语言中也有方法(Methods)。方法是作用在接受者(receiver)上的一个函数,接受者是某种类型的变量。因此,方法是一种特殊类型的函数
定义方法格式如下:
func (recv recv_type) methodName (parameter_list) (return_value_list) {…}
上面以及定义了一个Triangle 类,下面为三角形类定义一个方法Area()来计算其面积
// var.go
package main
import (
"fmt"
)
type Triangle struct {
Bottom float32
Height float32
}
func (t *Triangle) Area() float32 {
return (t.Bottom * t.Height) / 2
}
func main() {
r := Triangle{6, 8}
fmt.Println(r.Area())
}
3.访问权限
在面向对象编程中,常会说一个类的属性是公有地还是私有的,这就是访问权限的范畴。在其他编程语言中,常用public和private关键字来表达这样一种访问权限
在go语言中,没有public、private、protected这样的访问控制修饰符,而是通过字符大小写来控制可见性的。
如果定义的常量、变量、类型、接口、结构体、函数等的名称是大写字母开头,则表示他们能被其他包含访问或调用(相当于public);非大写开头的就只能在包内使用(相当于private)
例如,定义一个学生结构体来描述名字和分数:
type student struct {
name string
score float32
Age int
}
在以上结构体中,Age属性是大写字母开头,其他包可以直接访问。而name是小写字母,不能直接访问
和其他面向对象语言一样,Go语言也有实现获取和设置属性的方式:
对于设置方法使用Set前缀
对于获取方法使用成员名
包的引用
这里讲一个东西哈,上次有个兄弟说我讲解的包引用有个问题,包名如果和目录的名字不一致或报错,这一次特意讲解一下这里,他说的没有错,是我描述的有问题,在引用第三方的包的时候,如果文件里面的package 后面的名字和目录名字对不上,那么他是找不到go文件的哈
包引用
标准包的源码位于 $GOROOT/src 目录下,标准包可以直接引用。自定义的包和第三方包的源码必须放到 $GOPATH/src 目录下才能被引用。
使用 go env可获取到工作路径以及执行路径
可以看到,我这里使用的是默认安装的路径
GOPATH=C:\Users\Administrator\go
GOROOT=C:\Program Files\Go
在引用的时候,其实他们的后面还有一个src的,这里需要注意,GoPATH的目录默认是没有src目录的,需要大家自己去创建
包引用路径
包的引用路径有两种写法,一种是全路径,另一种是相对路径。
全路径引用
包的绝对路径就是 “$GOROOT/src 或 $GOPATH/src” 路径后包的源码路径的全路径,比如下面的包引用:
import “person” //引用第三方,就是自己写的
import “fmt” //引用本地的
相对路径引用
相对路径引用只用于引用 $GOPATH/src 下的包,标准包的引用只能使用全路径引用。比如下面两个包:包 a 的源码路径是 $GOPATH/src/lab/a,包 b 的源码路径为 $GOPATH/src/lab/b,假设包 b 引用了包 a,则可以使用相对路径引用方式。示例如下:
// 在包b中使用相对路径方式引用包a
import "../a"
// 在包b中使用全路径方式引用包a
import "lab/a"
示例代码如下:
// student.go
package person
type Student struct { //创建学生结构体
name string
score float32
}
func (s *Student) GetName() string { //获取name
return s.name
}
func (s *Student) SetName(newName string) { //设置name
s.name = newName
}
student.go处于C:\Users\Administrator\go\src\person\student.go
此时引用的包名和目录名是一样的
我们看看主函数代码
// arr.go
package main
import (
"fmt"
"person"
)
func main() {
s := new(person.Student)
s.SetName("Shirdon")
fmt.Println(s.GetName())
}
这个arr.go文件我存放的就比较随意了
继承
Go语言中没有extends关键字,而是使用在结构体中内嵌名类型的方法来实现继承。例如,定义一个Engine的接口类型和一个Bus结构体,让Bus结构体包含一个Engine接口的匿名字段
type Engine interface {
Run()
Stop()
}
type Bus struct {
Engine //包含Engine 类型的匿名字段
}
此时,匿名字段Engine 上的方法“晋升”为外层Bus方法。可以构建如下代码
func (c *Bus) Working() {
c.Run() //开动汽车
c.Stop() //停车
}
多态
在面向对象中,多态的特征是不同的对象中同种行为的不同实现方式。在Go语言中可以使用接口来实现这个特征。
先定义一个正方形Square和一个三角形的结构体Triangle
type Square struct {
sideLen float32
}
type Triangle struct {
Bottom float32
Heigth float32
}
然后,希望可以计算出这两个几个图形的面积。由于他们的面积计算方式不同,所以需要定一两个不同的Area()方法
于是定义一个Area()方法的接口Shapr,让Square和Triangle 都实现这个接口里的
Area()
func (t *Triangle) Area() float32 {
return (t.Bottom * t.Heigth) / 2
}
type Shape interface {
Area() float32
}
func (sq *Square) Area() float32 {
return sq.sideLen * sq.sideLen
}
func main() {
t := &Triangle{6, 8}
s := &Square{8}
shapes := []Shape{t, s}
for n, _ := range shapes {
fmt.Println("图形数据:", shapes[n])
fmt.Println("他的面积是:", shapes[n].Area())
}
}
完整代码如下
// arr.go
package main
import (
"fmt"
)
type Square struct {
sideLen float32
}
type Triangle struct {
Bottom float32
Heigth float32
}
func (t *Triangle) Area() float32 {
return (t.Bottom * t.Heigth) / 2
}
type Shape interface {
Area() float32
}
func (sq *Square) Area() float32 {
return sq.sideLen * sq.sideLen
}
func main() {
t := &Triangle{6, 8}
s := &Square{8}
shapes := []Shape{t, s}
for n, _ := range shapes {
fmt.Println("图形数据:", shapes[n])
fmt.Println("他的面积是:", shapes[n].Area())
}
}