1.函数的声明定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
//func关键字 //getStudent函数名 //(id int, classId int) 参数列表 //(name string,age int) 返回值列表 func getStudent(id int , classId int )(name string ,age int ) { //函数体 if id== 1 &&classId== 1 { name = "BigOrange" age = 26 } //返回值 return name, age // 支持多重返回值 } |
有意思的是Go语言的返回值可以有多个,并且放在了参数列表后面,而C#等都是在函数名之前,也没有关键字。
2.函数的调用
1
2
3
4
5
|
import "fmt" //调用fmt包中的Println方法。 fmt. Println ( "Name:" , std.Name, "Age:" ,std.Age) |
3.函数编写的原则
很好奇为什么没有public private等关键字,那函数怎么才能定义为公用和私有呢?
Go语言有这样的规则:小写字母开头的函数只在本包内可见,大写字母开头的函数才
能被其他包使用。这个规则也适用于类型和变量的可见性。
4.不定参数问题
不定参数是指函数传入的参数个数为不定数量。
1
2
3
4
5
|
func myfunc(args ... int ) { for _, arg := range args { fmt. Println (arg) } } |
函数myfunc()接受不定数量的参数,这些参数的类型全部是int
※形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。
它是一个语法糖(syntactic sugar),即这种语法对语言的功能并没有影响,但是更方便程序员使用。
从内部实现机理上来说,类型...type本质上是一个数组切片,也就是[]type,这也是为
什么上面的参数args可以用for循环来获得每个传入的参数。
不定参数的传递
1
|
myfunc3(args ... int ) |
对应上面的这个函数,传递参数可以为下面两种
1
2
3
4
|
// 按原样传递 myfunc3(args...) // 传递片段,实际上任意的int slice都可以传进去 myfunc3(args[ 1 :]...) |
任意类型的不定参数
可以看到 fmt.Println()方法接受了不定参数,但它是 ...interface{}
用interface{}传递任意类型数据是Go语言的惯例用法。
5.多返回值
Go语言函数可以返回多个返回值
如果调用方调用了一个具有多返回值的方法,但是却不想关心其中的某个返回值,可以简单
地用一个下划线“_”来跳过这个返回值
6.匿名函数
Go语言支持随时在代码里定义匿名函数。
匿名函数由一个不带函数名的函数声明和函数体组成
1
2
3
|
func (a, b int , z float64 ) bool { return a*b < int (z) } |
匿名函数可以直接赋值给一个变量或者直接执行:(有点像js哈)
1
2
3
|
f := func (x, y int ) int { return x + y } |
1
2
3
|
func (ch chan int ) { ch <- ACK } (reply_chan) // 花括号后直接跟参数列表表示函数调用 |
7.闭包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package main import ( "fmt" ) func main() { var j int = 5 a := func ()( func ()) { var i int = 10 return func () { fmt.Printf( "i, j: %d, %d\n" , i, j) } }() a() j *= 2 a() } |
结果:
i, j: 10, 5
i, j: 10, 10
分析:
1---"...func()(func()) {....."
表明此匿名函数返回值的类型是func(), 即此匿名函数返回一个函数指针(此处引用一下c 的概念);
2---"...returnfunc() {
fmt.Printf("i, j: %d, %d\n", i, j)
}..."
表明返回的函数指针指向一个打印i, j: %d, %d\n的函数;
3---"...a := func()(func()) {
...
}()..."
末尾的括号表明匿名函数被调用,并将返回的函数指针赋给变量a ;
综合来看:
1
2
3
4
5
6
7
8
9
10
11
|
"...a := func ()( func ()) { var i int = 10 return func () { fmt.Printf( "i, j: %d, %d\n" , i, j) } }()..." |
此代码片段的意思"等价于"
1
2
3
4
5
|
a := func () { fmt.Printf( "i, j: %d, %d\n" , i, j) } |
至于为何要用匿名函数如此的转一圈,是因为要引用闭包的概念,此概念省略不表,多写点代码试试就能体会了。
补充:传值和传引用
1.type 定义一个类型,有两种方式
- ①配合struct,创建一个新的结构,类似C#里面的Class
- ②配合既存的类型(int64...),创建一个新的类型,类似C++里面的typedef
2.Struct的如果不进行赋值,会使用0值进行初始化。
3.type使用既存类型定义新结构,和既存类型不是同一个类型,不能进行转换,例如
1
2
3
4
5
6
7
8
|
package main type Integer int64 func main() { var srcInt Integer srcInt = int64 ( 1000 ) } |
结果:
4.方法和函数的区别
方法能给用户定义的类型添加新的行为。方法实际上也是函数,只是在声明时,在关键字func 和方法名之间增加了一个参数
例如:
这是函数,它的调用就是直接在使用的时候 传入参数获取返回值就行
1
2
3
4
|
func getStudentName(student Student)(name string ) { //返回值 return student.Name } |
这是方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
package main import ( "fmt" ) type Student struct { Name string Age int } //Student类的方法 使用值接收者实现了一个方法 func (student Student) getStudentName()( string ){ return student.Name } //Student类的方法 使用指针接收者实现了一个方法 func (student *Student) changeStudentName(name string ){ student.Name = name fmt. Println ( "方法执行之后name" ,student.Name) } //Student类的方法 使用指针接收者实现了一个方法 func (student Student) changeStudentNameByValue(name string ){ student.Name = name fmt. Println ( "方法执行之后name" ,student.Name) } func main() { bigOrange:=Student{ Name: "BigOrange" ,Age: 18 , } bigApple:=Student{ Name: "BigApple" ,Age: 20 , } //使用函数获取学生名称 name1 := getStudentName(bigOrange) name2 := getStudentName(bigApple) fmt. Println ( "========通过传地址ChangeName之后==========" ) fmt. Println ( "方法执行之前name" ,name1) bigOrange.changeStudentName( "BigBanana" ) name1 = bigOrange.getStudentName() fmt. Println ( "方法返回之后Name" ,name1) fmt. Println ( "========通过传值ChangeName之后===========" ) fmt. Println ( "方法执行之前name" ,name2) bigApple.changeStudentNameByValue( "BigPear" ) name2 = bigApple.getStudentName() fmt. Println ( "方法返回之后Name" ,name2) } |
结果:
========通过传地址ChangeName之后==========
方法执行之前name BigOrange
方法执行之后name BigBanana
方法返回之后Name BigBanana
========通过传值ChangeName之后===========
方法执行之前name BigApple
方法执行之后name BigPear
方法返回之后Name BigApple
上面的例子中
分别使用了函数和方法进行获取学生姓名
分别使用了传值和传地址的方式对学生名称进行修正
综上
- ① Go语言中的方法,相比于函数,多了一个接收者参数
- ② Go 语言里有两种类型的接收者:值接收者和指针接收者
方法如果使用 值接收者,那么调用者可以是值接收者类型、也可以是它的指针类型,请看下面的例子(补充上例):
1
2
3
4
5
6
7
8
|
fmt. Println ( "========使用指针来调用值类型声明的接收者方法===========" ) bigGrape := &Student{ Name: "bigGrape" ,Age: 22 } //取地址!!!! name3 := bigGrape.getStudentName(); fmt. Println ( "========通过传值ChangeName之后===========" ) fmt. Println ( "方法执行之前name" ,name3) bigGrape.changeStudentNameByValue( "BigXXXX" ) name3 = bigGrape.getStudentName() fmt. Println ( "方法返回之后Name" ,name3) |
结果:
========使用指针来调用值类型声明的接收者方法===========
name bigGrape
========通过传值ChangeName之后===========
方法执行之前name bigGrape
方法执行之后name BigXXXX
方法返回之后Name bigGrape
如上代码 使用了&获取地址,所以bigGrape是一个student类型的指针。下面的代码和上例一样,直接使用了【变量.方法】的方式调用方法,结果也和值类型调用的一样。
为什么?
【Go 在代码背后的执行动作】
1
|
name4:=(*bigGrape).getStudentName() |
Go 编译器为了支持这种方法调用背后做的事情。【指针被解引用为值】,这样就符合了值接收者的要求。再强调一次,getStudentName 操作的是一个副本,只不过这次操作的是从bigGrape指针指向的值的副本。
同理还记得上面的,这句代码吗?对bigOrange这个变量修改了名称
1
|
bigOrange.changeStudentName( "BigBanana" ) |
bigOrange和bigApple明显是值类型,但是 changeStudentName 接收者是一个指针类型,为什么能调用,也是基于Go代码背后的执行动作,将值类型取了地址。
1
|
(&bigOrange).changeStudentName( "BigOrange" ) |
所以对于Go语言调用方法的类型,使用值或者指针都是可以的,不用拘泥于类型。
到此这篇关于Go语言函数的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://www.cnblogs.com/dcz2015/p/10096374.html