七天学习Go语言-从0-0.06

前言:

今天的篇幅有一些长,内容相对而言也不是很容易理解,今天分享的内容主要就是关于包的引用,感谢大佬“栗”提出的宝贵意见,关于包的引用在第一天分享的时候说的话不是特别的准确,在引用第三方的包的时候,包名需要与目录名一致才可以,下面将函数的封装里面会详细的说明;

函数格式

在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())
	}
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_48421613/article/details/123245762