GO语言实战之函数与方法

摘要
首页显示摘要内容(替换成自己的)

写在前面


  • 嗯,学习GO,所以有了这篇文章
  • 博文部分内容为《GO语言实战》读书笔记之一
  • 主要涉及知识
    • 函数
    • 结构体
    • 方法

傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。——–王小波


对于这个概念小伙伴一定不陌生,常用的脚本语言中一般都会用,通过函数来整合一些逻辑上耦合性较强的代码块,从而提高代码的可读性,可复用性。

GO语言中的函数语法格式和其他语言略有不同,函数可以先使用后定义,同时go支持闭包,拆包,异常信息返回等

语法格式

1
2
3
func funcName(param paramType)(returnVal1 returnType, err error){
// do something
}

demo

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
package main

import (
"fmt"
"time"
)

func main() {
nowTime := time.Now()
fmt.Println(nowTime.Format("2006-01-02 15:04:05"))
// 拆包
i, j := func_demo()
//调用有参函数
func_demos("你好,世界!")
fmt.Println(i, j)
// 匿名函数
get_func_demo := func(x int) int {
return x
}
fmt.Println(get_func_demo(3))
// 闭包
f := get_func_()
fmt.Println(f())
}
// 拆包
func func_demo() (int, int) {
fmt.Println("你好,世界!")
return 1, 2
}
// 接收参数
func func_demos(mes string) {
fmt.Println(mes)
}
// 闭包
func get_func_() (func() int){
i := 1
return func() (int) {
i+=1
return i
}
}
============
2022-04-14 10:01:03
你好,世界!
你好,世界!
1 2
3
2

其他的脚本语言对比

对于Shell脚本来说,shell脚本中的函数只能先定义后使用,函数可以看做是一个微小版的shell脚本,功能语法相对简单

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
#!/bin/bash

# 带关键字定义
function f(){
echo $(date)
}
f

# 直接函数名的方式定义
func_name(){
echo "Hello,Word!"
}
func_name

# 接受入参
func_names(){
echo "${0}"
echo "${1}"
}
func_names 5

# 返回数组
func_array(){
my_array=("liruilong" 1 2 3)
echo ${my_array[*]}
}
retuens=($(func_array))
echo "${retuens[*]}"

# 返回信号
func_return(){
return 1
}
func_return

===========================
2022年 04月 14日 星期四 10:36:20 CST
Hello,Word!
fun.sh
5
liruilong 1 2 3

对于Python来讲,函数也必须先定义后使用。python的函数语法要多一点,支持可变参数,关键字参数,缺省参数,同时支持拆包,闭包等

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
#!/usr/bin/python3
# -*- coding: UTF-8 -*-


def func_demo():
print("Hello,Word")
func_demo()

def func_param(a,b,c=","):
print(a,c,b)
func_param("Hello","Word")
func_param(b="Word",a="Hello")

def func_return():
return 2
print(func_return())


def func_arges(a,*arges):
for i in arges:
print(i)
func_arges(1,2,3)
func_arges(1)


def func_aeges_(**arges):
print(arges)
func_aeges_(name='liruilong', age=18, id=110)

def return_num():
return 100, 200
num1, num2 = return_num()
==========================
Hello,Word
Hello , Word
Hello , Word
2
2
3
{'name': 'liruilong', 'age': 18, 'id': 110}

关于函数介绍到这里,来看看方法,GO语言中方法实际上也是函数,只是在声明时,在关键字func方法名之间增加了一个参数

在面向对象里,方法是类行为和对象行为的体现(类方法和实例方法),GO不是面向对象类型的语言,但是Go方法和java中的方法类似,同样不能单一存在,必须要绑定。

Java中的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @Classname Function
* @Description TODO
* @Date 2022/4/14 23:09
* @Created LiRuilong
*/
public class Function {
String str;
// 对象方法
public String getStr(){
return str;
}
// 类方法
public static Function build(){
return new Function();
}

public static void main(String[] args) {
System.out.println("Hello,Word!");
}
}

java中方法必定是和或者对象绑定的,和类绑定的方法被称为静态方法,和对象绑定的为实例方法。GO中的方法必须要和接收者绑定,那GO中的所谓的接收者又是什么?,即用户定义的类型,听着有些别扭,类比C中我们常讲的结构体

用户定义的类型(结构体)

Go 语言允许用户定义类型(结构体)。当用户声明一个新类型时,这个声明就给编译器提供了一个框架,告知必要的内存大小表示信息

Go 语言里声明用户定义的类型有两种方法。最常用的方法是使用关键字struct,它可以让用户创建一个结构类型

结构里每个字段都会用一个已知类型声明。这个已知类型可以是内置类型,也可以是其他用户定义的类型

  • 声明一个结构类型
1
2
3
4
5
type user struct {
name string
email string
}

  • 使用结构类型声明变量,并初始化为其零值
1
var bill user

当声明变量时,这个变量对应的值总是会被初始化。这个值要么用指定的值初始化,要么用零值(即变量类型的默认值)做初始化

  • 对数值类型来说,零值是 0
  • 对字符串来说,零值是空字符串
  • 对布尔类型,零值是 false

:=:一个短变量声明操作符在一次操作中完成两件事情:声明一个变量,并初始化

  • 使用结构字面量来声明一个结构类型的变量
1
2
3
4
lisa := user{
name: "liruilong",
email: "liruilong@qq.com"
}
  • 使用结构字面量创建结构类型的值
1
2
3
4
user{
name: "liruilong",
email: "liruilong@qq.com"
}
  • 不使用字段名,创建结构类型的值,这种形式下,值的顺序很重要,必须要和结构声明中字段的顺序一致。
1
user{"Bill", "bill@email.com"}

当声明结构类型时,字段的类型并不限制在内置类型,也可以使用其他用户定义的类型

  • 使用其他结构类型声明字段
1
2
3
4
5
6
7
8
type admin struct{
liruilong user,
leve string
}
fred := admin{
liruilong: user{"Bill", "bill@email.com"},
level: "super",
}

另一种声明用户定义的类型的方法是,基于一个已有的类型,将其作为新类型的类型说明

  • 基于 int64 声明一个新类型
1
type Duration int64

Duration 是一种描述时间间隔的类型,单位是纳秒(ns)。这个类型使用内置的 int64 类型作为其表示

我们把 int64 类型叫作 Duration 的基础类型,Go 并不认为 Duration 和 int64 是同一种类型。这两个类型是完全不同的有区别的,这里和有些面向对象的语言有很大的区别。

  • 给不同类型的变量赋值会产生编译错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
)
type Duration int64

func main() {
var dur Duration
//正确写法 dur = Duration(1000)
dur = int64(1000)
fmt.Println(dur)
}

============
[Running] go run "d:\GolandProjects\code-master\demo\hello.go"
# command-line-arguments
demo\hello.go:10:6: cannot use int64(1000) (type int64) as type Duration in assignment

[Done] exited with code=2 in 0.705 seconds

OK,了解了GO中的结构体,我们来看看方法

方法

GO中,通过方法能给用户定义的类型也就是俗称的结构体添加新的行为。声明时,func方法名之间增加了一个结构体参数
我们把关键字func和函数名之间的参数被称作接收者,将函数接收者的类型绑在一起。如果一个函数有接收者,这个函数就被称为方法。

Go 语言里有两种类型的接收者:值接收者,指针接收者

使用值接收者声明一个方法

1
2
3
4
5
6
// notify 使用值接收者实现了一个方法
func (u user) notify() {
fmt.Printf("Sending User Email To %s<%s>\n",
u.name,
u.email)
}

如果使用 值接收者声明方法调用时会使用这个值的一个副本来执行。使用 bill 的值作为接收者进行调用,方法 notify 会接收到 bill 的值的一个副本。

1
2
3
// user 类型的值可以用来调用使用值接收者声明的方法
bill := user{"Bill", "bill@email.com"}
bill.notify()

也可以使用指针来调用使用值接收者声明的方法,使用指向 user 类型值的指针来调用 notify 方法

1
2
3
   // 指向 user 类型值的指针也可以用来调用使用值接收者声明的方法
lisa := &user{"Lisa", "lisa@email.com"}
lisa.notify() //(*lisa).notify()

指针被解引用为值,不管是变量调用还是指针调用,notify 操作的都是一个副本。

使用指针接收者声明一个方法

1
2
3
4
// changeEmail 使用指针接收者实现了一个方法
func (u *user) changeEmail(email string) {
u.email = email
}

使用指针接收者声明。这个接收者的类型是指向 user 类型值的指针,而不是 user 类型的值。当调用使用指针接收者声明的方法时,这个方法会共享调用方法时接收者所指向的值

这一点和使用值接收有很大的区别,某种意义上讲,使用值接收一般用于消费,使用指针接收,一般用于生产加工修饰。

1
2
3
4
lisa := &user{"Lisa", "lisa@email.com"}
// 指向 user 类型值的指针可以用来调用使用指针接收者声明的方法
lisa.changeEmail("lisa@newdomain.com")
lisa.notify()

值接收者使用值的副本来调用方法,而指针接受者使用实际值来调用方法。

1
2
3
4
bill := user{"Bill", "bill@email.com"}
// user 类型的值可以用来调用使用指针接收者声明的方法
bill.changeEmail("bill@newdomain.com")
bill.notify() //(&bill).changeEmail ("bill@newdomain.com")

Go语言既允许使用值,也允许使用指针来调用方法,不必严格符合接收者的类型

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
// 声明 并使用方法
package main

import (
"fmt"
)

// user 在程序里定义一个用户类型
type user struct {
name string
email string
}

// notify 使用值接收者实现了一个方法,一般用于消费对象
func (u user) notify() {
fmt.Printf("Sending User Email To %s<%s>\n",
u.name,
u.email)
}

// changeEmail 使用指针接收者实现了一个方法,一般用于生产加工对象
func (u *user) changeEmail(email string) {
u.email = email
}

// main 是应用程序的入口
func main() {
// user 类型的值可以用来调用使用值接收者声明的方法
bill := user{"Bill", "bill@email.com"}
bill.notify()

// 指向 user 类型值的指针也可以用来调用使用值接收者声明的方法
lisa := &user{"Lisa", "lisa@email.com"}
lisa.notify()

// user 类型的值可以用来调用使用指针接收者声明的方法
bill.changeEmail("bill@newdomain.com")
bill.notify()

// 指向 user 类型值的指针可以用来调用使用指针接收者声明的方法
lisa.changeEmail("lisa@newdomain.com")
lisa.notify()
}
================
[Running] go run "d:\GolandProjects\code-master\chapter5\listing11\tempCodeRunnerFile.go"
Sending User Email To Bill<bill@email.com>
Sending User Email To Lisa<lisa@email.com>
Sending User Email To Bill<bill@newdomain.com>
Sending User Email To Lisa<lisa@newdomain.com>

[Done] exited with code=0 in 2.288 seconds

bill := user{"Bill", "bill@email.com"}lisa := &user{"Lisa", "lisa@email.com"} 的区别

  • bill 是值接收者,它创建了一个user类型的值。任何对bill的操作都不会影响实际的user对象。
  • lisa 是指针接收者,它创建了一个指向user对象的指针*user。任何对lisa的操作实际上都是对底层user对象的操作。

具体来说:

  • bill是一个用户值,而lisa是一个用户地址。
  • 对bill的修改,只会修改bill本身,不会影响实际的user。
  • 而对lisa的修改,通过它修改的是实际的user对象。
  • 值传递只影响局部变量,不影响原对象
  • 指针传递通过指针可以修改原对象

博文部分内容参考

© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知,这是一个开源项目,如果你认可它,不要吝啬星星哦 :)


《GO语言实战》


© 2018-2023 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

发布于

2022-03-11

更新于

2023-09-05

许可协议

评论
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×