Golang 函数 Golang函数简介 函数的go语言中的一级公民 ,我们把所有的功能单元都定义在函数中,可以重复使用。函数包含函数的名称、参数列表和返回值类型,这些构成了函数的签名(signature)。
Go语言中函数特性
go语言中有3种函数:普通函数、匿名函数(没有名称的函数)、方法(定义在struct上的函数)。receiver
go语言中不允许函数重载(overload),也就是说不允许函数同名。
go语言中的函数不能嵌套函数,但可以嵌套匿名函数。
函数是一个值,可以将函数赋值给变量,使得这个变量也成为函数。
函数可以作为参数传递给另一个函数。
函数的返回值可以是一个函数。
函数调用的时候,如果有参数传递给函数,则先拷贝参数的副本,再将副本传递给函数。
函数参数可以没有名称。
Go语言中函数的定义和调用 函数在使用之前必须先定义,可以调用函数来完成某个任务。函数可以重复调用,从而达到代码重用。
Go语言函数定义语法 1 2 3 4 func function_name ( [parameter list] ) [return_types ]{ 函数体 }
语法解析:
func
:函数由func
开始声明
function_name
:函数名称,函数名和参数列表一起构成了函数签名。
[parameter list]
:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
return_types
:返回类型,函数返回一列值。return_types
是该列值的数据类型。有些功能不需要返回值,这种情况下return_types
不是必须的。
函数体:函数定义的代码集合。
Go语言函数定义实例 定义一个求和函数
1 2 3 4 func sum (a int , b int ) (ret int ) { ret = a + b return ret }
定义一个比较两个数大小的函数
1 2 3 4 5 6 7 8 func compare (a int , b int ) (max int ) { if a > b { max = a } else { max = b } return max }
Go语言函数调用 当我们要完成某个任务时,可以调用函数来完成。调用函数要传递参数,如何有返回值可以获得返回值。
1 2 3 4 5 6 7 func main () { s := sum(1 , 2 ) fmt.Printf("s: %v\n" , s) max := compare(1 , 2 ) fmt.Printf("max: %v\n" , max) }
运行结果
Golang函数的返回值 函数可以有0或多个返回值,返回值需要指定数据类型,返回值通过 return
关键字来指定。
return
可以有参数,也可以没有参数,这些返回值可以有名称,也可以没有名称。go中的函数可以有多个返回值。
return
关键字中指定了参数时,返回值可以不用名称。如果return
省略参数,则返回值部分必须带名称
当返回值有名称时,必须使用括号包围,逗号分隔,即使只有一个返回值
但即使返回值命名了,return
中也可以强制指定其它返回值的名称,也就是说return
的优先级更高
命名的返回值是预先声明好的,在函数内部可以直接使用,无需再次声明。命名返回值的名称不能和函数参数名称相同,否则报错提示变量重复定义
return
中可以有表达式,但不能出现赋值表达式,这和其它语言可能有所不同。例如return a+b
是正确的,但return c=a+b
是错误的。
Go语言函数返回值实例 没有返回值
1 2 3 func f1 () { fmt.Printf("我没有返回值,只是进行一些计算" ) }
有一个返回值
1 2 3 4 func sum (a int , b int ) (ret int ) { ret = a + b return ret }
多个返回值,且在return中指定返回的内容
1 2 3 4 5 func f2 () (name string , age int ) { name = "老郭" age = 30 return name, age }
多个返回值,返回值名称没有被使用
1 2 3 4 5 func f3() (name string, age int) { name = "老郭" age = 30 return // 等价于return name, age }
return覆盖命名返回值,返回值名称没有被使用
1 2 3 4 5 func f4 () (name string , age int ) { n := "老郭" a := 30 return n, a }
Go中经常会使用其中一个返回值作为函数是否执行成功、是否有错误信息的判断条件。例如 return value,exists
、return value,ok
、return value,err
等。
当函数的返回值过多 时,例如有4个以上的返回值,应该将这些返回值收集到容器中,然后以返回容器的方式去返回。例如,同类型的返回值可以放进slice中,不同类型的返回值可以放进map中。
但函数有多个返回值时,如果其中某个或某几个返回值不想使用,可以通过下划线 _
来丢弃这些返回值。例如下面的 f1
函数两个返回值,调用该函数时,丢弃了第二个返回值b,只保留了第一个返回值a赋值给了变量 a
。
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func f1 () (int , int ) { return 1 , 2 } func main () { _, x := f1() fmt.Printf("x: %v\n" , x) }
运行结果
Golang函数的参数 Go语言函数可以有0或多个参数,参数需要指定数据类型 。
声明函数时的参数列表叫做形参,调用时传递的参数叫做实参。
Go语言是通过传值的方式传参 的,意味着传递给函数的是拷贝后的副本,所以函数内部访问、修改的也是这个副本。
Go语言可以使用变长参数 ,有时候并不能确定参数的个数,可以使用变长参数,可以在函数定义语句的参数部分使用 ARGS...TYPE
的方式。这时会将 ...
代表的参数全部保存到一个名为ARGS的slice中,注意这些参数的数据类型都是TYPE。
Golang函数的参数实例 Go语言传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func f1 (a int , b int ) int { if a > b { return a } else { return b } } func main () { r := f1(1 , 2 ) fmt.Printf("r: %v\n" , r) }
演示参数传递,按值传递
1 2 3 4 5 6 7 8 9 10 func f1 (a int ) { a = 200 fmt.Printf("a1: %v\n" , a) } func main () { a := 100 f1(a) fmt.Printf("a: %v\n" , a) }
运行结果
从运行结果可以看到,调用函数f1后,a的值并没有被改变,说明参数传递是拷贝了一个副本,也就是拷贝了一份新的内容进行运算。
map
、slice
、interface
、channel
这些数据类型本身就是指针 类型的,所以就算是拷贝传值也是拷贝的指针,拷贝后的参数仍然指向底层数据结构,所以修改它们可能 会影响外部数据结构的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" func f1 (a []int ) { a[0 ] = 100 } func main () { a := []int {1 , 2 } f1(a) fmt.Printf("a: %v\n" , a) }
运行结果
从运行结果发现,调用函数后,slice内容被改变了。
变长参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" func f1 (args ...int ) { for _, v := range args { fmt.Printf("v: %v\n" , v) } } func f2 (name string , age int , args ...int ) { fmt.Printf("name: %v\n" , name) fmt.Printf("age: %v\n" , age) for _, v := range args { fmt.Printf("v: %v\n" , v) } } func main () { f1(1 , 2 , 3 ) fmt.Println("------------" ) f1(1 , 2 , 3 , 4 , 5 , 6 ) fmt.Println("------------" ) f2("tom" , 20 , 1 , 2 , 3 ) }
运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 v: 1 v: 2 v: 3 ------------ v: 1 v: 2 v: 3 v: 4 v: 5 v: 6 ------------ name: tom age: 20 v: 1 v: 2 v: 3
Golang函数类型与函数变量 可以使用 type
关键字来定义一个函数类型,语法格式如下:
1 type fun func (int , int ) int
上面语句定义了一个 fun
函数类型,它是一种函数类型,这种函数接收两个 int
类型的参数,并且返回一个 int
类型的返回值。
下面我们定义两个这样结构的两个函数,一个求和,一个比较大小:
1 2 3 4 5 6 7 8 9 10 11 func sum (a int , b int ) int { return a + b } func max (a int , b int ) int { if a > b { return a } else { return b } }
下面定义一个 fun
函数类型,把 sum
和 max
赋值给它
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 package mainimport "fmt" type fun func (int , int ) int func sum (a int , b int ) int { return a + b } func max (a int , b int ) int { if a > b { return a } else { return b } } func main () { var f fun f = sum s := f(1 , 2 ) fmt.Printf("s: %v\n" , s) f = max m := f(3 , 4 ) fmt.Printf("m: %v\n" , m) }
运行结果
Golang高阶函数 go语言的函数,可以作为函数的参数,传递给另外一个函数,可以可以作为,另外一个函数的返回值返回。
Go语言函数作为参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func sayHello (name string ) { fmt.Printf("Hello,%s" , name) } func f1 (name string , f func (string ) ) { f(name) } func main () { f1("tom" , sayHello) }
运行结果
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 package main import "fmt" func add(x, y int) int { return x + y } func sub(x, y int) int { return x - y } func cal(s string) func(int, int) int { switch s { case "+": return add case "-": return sub default: return nil } } func main() { add := cal("+") r := add(1, 2) fmt.Printf("r: %v\n", r) fmt.Println("-----------") sub := cal("-") r = sub(100, 50) fmt.Printf("r: %v\n", r) }
运行结果
Golang匿名函数 Go语言函数不能嵌套,但是在函数内部可以定义匿名函数,实现一下简单功能调用。
所谓匿名函数就是,没有名称的函数。
语法格式如下:
当然可以既没有参数,可以没有返回值
匿名函数实例 1 2 3 4 5 6 7 8 9 10 11 12 func main () { max := func (a int , b int ) int { if a > b { return a } else { return b } } i := max(1 , 2 ) fmt.Printf("i: %v\n" , i) }
运行结果
自己执行
1 2 3 4 5 6 7 8 9 10 11 12 func main () { func (a int , b int ) { max := 0 if a > b { max = a } else { max = b } fmt.Printf("max: %v\n" , max) }(1 , 2 ) }
运行结果
Golang闭包 闭包可以理解成定义在一个函数内部的函数 。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。或者说是函数和其引用环境的组合体。
闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境
。 首先我们来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" func add () func (int ) int { var x int return func (y int ) int { x += y return x } } func main () { var f = add() fmt.Println(f(10 )) fmt.Println(f(20 )) fmt.Println(f(30 )) fmt.Println("-----------" ) f1 := add() fmt.Println(f1(40 )) fmt.Println(f1(50 )) }
运行结果
1 2 3 4 5 6 10 30 60 ----------- 40 90
变量 f
是一个函数并且它引用了其外部作用域中的 x
变量,此时 f
就是一个闭包。 在 f
的生命周期内,变量 x
也一直有效。 闭包进阶示例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" func add (x int ) func (int ) int { return func (y int ) int { x += y return x } } func main () { var f = add(10 ) fmt.Println(f(10 )) fmt.Println(f(20 )) fmt.Println(f(30 )) fmt.Println("----------" ) f1 := add(20 ) fmt.Println(f1(40 )) fmt.Println(f1(50 )) }
运行结果
1 2 3 4 5 6 20 40 70 ---------- 60 110
闭包进阶示例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func makeSuffixFunc (suffix string ) func (string ) string { return func (name string ) string { if !strings.HasSuffix(name, suffix) { return name + suffix } return name } } func main () { jpgFunc := makeSuffixFunc(".jpg" ) txtFunc := makeSuffixFunc(".txt" ) fmt.Println(jpgFunc("test" )) fmt.Println(txtFunc("test" )) }
运行结果
闭包进阶示例3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func calc (base int ) (func (int ) int , func (int ) int ) { add := func (i int ) int { base += i return base } sub := func (i int ) int { base -= i return base } return add, sub } func main () { f1, f2 := calc(10 ) fmt.Println(f1(1 ), f2(2 )) fmt.Println(f1(3 ), f2(4 )) fmt.Println(f1(5 ), f2(6 )) }
运行结果
闭包其实并不复杂,只要牢记 闭包=函数+引用环境
。
Golang递归 函数内部调用函数自身的函数称为递归函数。
使用递归函数最重要的三点:
递归就是自己调用自己。
必须先定义函数的退出条件,没有退出条件,递归将成为死循环。
go语言递归函数很可能会产生一大堆的goroutine,也很可能会出现栈空间内存溢出问题。
Go语言递归实例 阶乘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func a (n int ) int { if n == 1 { return 1 } else { return n * a(n-1 ) } } func main () { n := 5 r := a(n) fmt.Printf("r: %v\n" , r) }
运行结果
斐波那契数列
它的计算公式为 f(n)=f(n-1)+f(n-2)
且 f(2)=f(1)=1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainfunc f (n int ) int { if n == 1 || n == 2 { return 1 } return f(n-1 ) + f(n-2 ) } func main () { r := f(5 ) fmt.Printf("r: %v\n" , r) }
运行结果
Golang defer语句 go语言中的 defer
语句会将其后面跟随的语句进行延迟 处理。在 defer
归属的函数即将返回时,将延迟处理的语句按 defer
定义的逆序 进行执行,也就是说,先被 defer
的语句最后被执行,最后被 defer
的语句,最先被执行。stack
defer特性
关键字defer
用于注册延迟调用。
这些调用直到return
前才被执。因此,可以用来做资源清理。
多个defer
语句,按先进后出的方式执行。
defer
语句中的变量,在defer
声明时就决定了。
defer用途
关闭文件句柄
锁资源释放
数据库连接释放
Go语言defer语句实例 查看执行顺序
1 2 3 4 5 6 7 func main () { fmt.Println("start" ) defer fmt.Println("step1" ) defer fmt.Println("step2" ) defer fmt.Println("step3" ) fmt.Println("end" ) }
运行结果
1 2 3 4 5 start end step3 step2 step1
Golang init函数 Golang有一个特殊的函数 init
函数,先于 main
函数执行,实现包级别的一些初始化 操作。
init函数的主要特点
init函数先于main函数自动执行 ,不能被其他函数调用;
init函数没有输入参数、返回值;
每个包可以有多个init函数;
包的每个源文件也可以有多个init函数 ,这点比较特殊;
同一个包的init执行顺序,golang没有明确定义,编程时要注意程序不要依赖这个执行顺序。
不同包的init函数按照包导入的依赖关系决定执行顺序。
Golang 初始化顺序 初始化顺序:变量初始化->init()->main()
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" var a int = initVar()func init () { fmt.Println("init2" ) } func init () { fmt.Println("init" ) } func initVar () int { fmt.Println("init var..." ) return 100 } func main () { fmt.Println("main..." ) }
运行结果
1 2 3 4 init var... init2 init main...
Golang指针 Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针,而无须拷贝数据。
类型指针不能进行偏移和运算。
Go语言中的指针操作非常简单,只需要记住两个符号:&
(取地址)和 *
(根据地址取值)。
指针地址和指针类型 每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用 &
字符放在变量前面对变量进行取地址 操作。 Go语言中的值类型 (int、float、bool、string、array、struct)
都有对应的指针类型,如:*int、*int64、*string
等。
指针语法 一个指针变量指向了一个值的内存地址。(也就是我们声明了一个指针之后,可以像变量赋值一样,把一个值的内存地址放入到指针当中。)
类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:
var-type
:为指针类型
var_name
:为指针变量名
*
:用于指定变量是作为一个指针。
指针声明实例 1 2 var ip *int var fp *float32
指针使用实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func main () { var a int = 20 var ip *int ip = &a fmt.Printf("a 变量的地址是: %x\n" , &a ) fmt.Printf("ip 变量储存的指针地址: %x\n" , ip ) fmt.Printf("*ip 变量的值: %d\n" , *ip ) }
运行结果
1 2 3 a 变量的地址是: c00000a0a8 ip 变量储存的指针地址: c00000a0a8 *ip 变量的值: 20
Golang指向数组的指针 定义语法 1 var ptr [MAX]*int ; 表示数组里面的元素的类型是指针类型
实例演示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" const MAX int = 3 func main () { a := []int { 1 , 3 , 5 } var i int var ptr [MAX]*int ; fmt.Println(ptr) for i = 0 ; i < MAX; i++ { ptr[i] = &a[i] } for i = 0 ; i < MAX; i++ { fmt.Printf("a[%d] = %d\n" , i,*ptr[i] ) } }
运行结果
1 2 3 4 [<nil> <nil> <nil>] a[0] = 1 a[1] = 3 a[2] = 5
Golang类型定义和类型别名 在介绍结构体 之前,我们先来看看什么是类型定义和类型别名。
Go语言类型定义 类型定义的语法
实例
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "fmt" func main () { type MyInt int var i MyInt i = 100 fmt.Printf("i: %v i: %T\n" , i, i) }
运行结果
Go语言类型别名 类型别名的语法
实例
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "fmt" func main () { type MyInt2 = int var i MyInt2 i = 100 fmt.Printf("i: %v i: %T\n" , i, i) }
Go语言类型定义和类型别名的区别
类型定义相当于定义了一个全新的类型 ,与之前的类型不同;但是类型别名并没有定义一个新的类型,而是使用一个别名来替换之前的类型
类型别名只会在代码 中存在,在编译完成 之后并不会存在该别名
因为类型别名和原来的类型是一致的,所以原来类型所拥有的方法 ,类型别名中也可以 调用,但是如果是重新定义的一个类型,那么不可以 调用之前的任何方法。
Golang 结构体 go语言没有面向对象的概念了,但是可以使用结构体来实现,面向对象编程的一些特性,例如:继承、组合等特性。
Go语言结构体的定义 上一节我们介绍了类型定义,结构体的定义和类型定义类似,只不过多了一个 struct
关键字,语法结构如下:
1 2 3 4 5 6 type struct_variable_type struct { member definition; member definition; ... member definition; }
type
:结构体定义关键字
struct_variable_type
:结构体类型名称
struct
:结构体定义关键字
member definition;
:成员定义
实例
下面我们定义一个人的结构体Person
1 2 3 4 5 6 type Person struct { id int name string age int email string }
以上我们定义一个Person结构体,有四个成员,来描述一个Person的信息。
形同类型的可以合并到一行 ,例如:
1 2 3 4 type Person struct { id, age int name, email string }
声明一个结构体变量 声明一个结构体变量和声明一个普通变量相同,例如:
1 2 3 4 var tom Personfmt.Printf("tom: %v\n" , tom) kite := Person{} fmt.Printf("kite: %v\n" , kite)
也行结果
1 2 tom: {0 0 } kite: {0 0 }
结构体成员,在没有赋值之前都是零值。
访问结构体成员 可以使用点运算符(.
),来访问结构体成员,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" func main () { type Person struct { id, age int name, email string } var tom Person tom.id = 1 tom.name = "tom" tom.age = 20 tom.email = "[email protected] " fmt.Printf("tom: %v\n" , tom) }
运行结果如下
匿名结构体 如果结构体是临时使用,可以不用起名字,直接使用,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" func main () { var dog struct { id int name string } dog.id = 1 dog.name = "花花" fmt.Printf("dog: %v\n" , dog) }
Golang结构体的初始化 未初始化的结构体,成员都是零值 int 0 float 0.0 bool false string nil nil
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" func main () { type Person struct { id, age int name, email string } var tom Person fmt.Printf("tom: %v\n" , tom) }
运行结果
使用键值对对结构体进行初始化 实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { type Person struct { id, age int name, email string } kite := Person{ id: 1 , name: "kite" , age: 20 , email: "[email protected] " , } fmt.Printf("kite: %v\n" , kite) }
运行结果
使用值的列表初始化 实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { type Person struct { id, age int name, email string } kite := Person{ 1 , 20 , "kite" , "[email protected] " , } fmt.Printf("kite: %v\n" , kite) }
运行结果
注意:
必须初始化结构体的所有字段。
初始值的填充顺序必须与字段在结构体中的声明顺序一致。
该方式不能和键值初始化方式混用。
部分成员初始化 用不到的成员,可以不进行初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func main () { type Person struct { id, age int name, email string } kite := Person{ id: 1 , name: "kite" , } fmt.Printf("kite: %v\n" , kite) }
运行结果
Golang结构体指针 结构体指针和普通的变量指针相同,我先来回顾一下普通变量的指针,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" func main () { var name string name = "tom" var p_name *string p_name = &name fmt.Printf("name: %v\n" , name) fmt.Printf("p_name: %v\n" , p_name) fmt.Printf("*p_name: %v\n" , *p_name) }
运行结果
1 2 3 name: tom p_name: 0xc00010e120 *p_name: tom
Go结构体指针 实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { type Person struct { id int name string } var tom = Person{1 , "tom" } var p_person *Person p_person = &tom fmt.Printf("tom: %v\n" , tom) fmt.Printf("p_person: %p\n" , p_person) fmt.Printf("*p_person: %v\n" , *p_person) }
运行结果
1 2 3 tom: {1 tom} p_person: 0xc000004078 *p_person: {1 tom}
使用 new
关键字创建结构体指针 我们还可以通过使用 new
关键字对结构体进行实例化,得到的是结构体的地址,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" func main () { type Person struct { id int name string } var p_person = new (Person) fmt.Printf("p_person: %T\n" , p_person) }
运行结果
从运行结果,我们发现p_person为指针类型
访问结构体指针成员 访问结构体指针成员,也使用点运算符(.
),例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" func main () { type Person struct { id int name string } var p_person = new (Person) fmt.Printf("p_person: %T\n" , p_person) p_person.id = 1 p_person.name = "tom" fmt.Printf("*p_person: %v\n" , *p_person) }
运行结果
1 2 p_person: *main.Person *p_person: {1 tom}
Golang结构体作为函数参数 go结构体可以像普通变量一样,作为函数的参数,传递给函数,这里分为两种情况:
直接传递结构体,这是是一个副本(拷贝),在函数内部不会改变外面结构体内容。
传递结构体指针,这时在函数内部,能够改变外部结构体内容。
直接传递结构体 实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" type Person struct { id int name string } func showPerson (person Person) { person.id = 1 person.name = "kite" fmt.Printf("person: %v\n" , person) } func main () { person := Person{1 , "tom" } fmt.Printf("person: %v\n" , person) fmt.Println("----------------" ) showPerson(person) fmt.Println("----------------" ) fmt.Printf("person: %v\n" , person) }
运行结果
1 2 3 4 5 person: {1 tom} ---------------- person: {1 kite} ---------------- person: {1 tom}
从运行结果可以看出,函数内部改变了结构体内容,函数外面并没有被改变。
传递结构体指针 实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" type Person struct { id int name string } func showPerson (person *Person) { person.id = 1 person.name = "kite" fmt.Printf("person: %v\n" , person) } func main () { person := Person{1 , "tom" } fmt.Printf("person: %v\n" , person) fmt.Println("----------------" ) showPerson(&person) fmt.Println("----------------" ) fmt.Printf("person: %v\n" , person) }
运行结果
1 2 3 4 5 person: {1 tom} ---------------- person: &{1 kite} ---------------- person: {1 kite}
从运行结果,我们可以看到,调用函数后,参数被改变了。
Golang嵌套结构体 go语言没有面向对象编程思想,也没有继承关系,但是可以通过结构体嵌套来实现这种效果。
下面通过实例演示如何实现结构体嵌套,加入有一个人 Person
结构体,这个人还养了一个宠物 Dog
结构体。
下面我们来看一下:
Dog结构体
1 2 3 4 5 type Dog struct { name string color string age int }
Person结构体
1 2 3 4 5 type person struct { dog Dog name string age int }
访问它们
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 package mainimport "fmt" type Dog struct { name string color string age int } type person struct { dog Dog name string age int } func main () { var tom person tom.dog.name = "花花" tom.dog.color = "黑白花" tom.dog.age = 2 tom.name = "tom" tom.age = 20 fmt.Printf("tom: %v\n" , tom) }
运行结果
1 tom: {{花花 黑白花 2} tom 20}
Golang方法 go语言没有面向对象的特性,也没有类对象的概念。但是,可以使用结构体来模拟这些特性,我们都知道面向对象里面有类方法等概念。我们也可以声明一些方法,属于某个结构体。
Go语言方法的语法 Go中的方法,是一种特殊的函数 ,定义于struct之上(与struct关联、绑定),被称为struct的接受者(receiver)。
通俗的讲,方法就是有接收者的函数。
语法格式如下:
1 2 3 4 type mytype struct {}func (recv mytype) my_method (para) return_type {}func (recv *mytype) my_method (para) return_type {}
mytype
:定义一个结构体
recv
:接受该方法的结构体(receiver)
my_method
:方法名称
para
:参数列表
return_type
:返回值类型
从语法格式可以看出,一个方法和一个函数非常相似,多了一个接受类型。
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" type Person struct { name string } func (per Person) eat () { fmt.Println(per.name + " eating...." ) } func (per Person) sleep () { fmt.Println(per.name + " sleep...." ) } func main () { var per Person per.name = "tom" per.eat() per.sleep() }
运行结果
1 2 tom eating.... tom sleep....
Go语言方法的注意事项
方法的receiver type并非一定要是struct类型,type定义的类型别名、slice、map、channel、func类型等都可以。
struct结合它的方法就等价于面向对象中的类。只不过struct可以和它的方法分开,并非一定要属于同一个文件,但必须属于同一个包。
方法有两种接收类型:(T Type)
和(T *Type)
,它们之间有区别。
方法就是函数,所以Go中没有方法重载(overload)的说法,也就是说同一个类型中的所有方法名必须都唯一。
如果receiver是一个指针类型,则会自动解除引用。
方法和type是分开的,意味着实例的行为(behavior)和数据存储(field)是分开的,但是它们通过receiver建立起关联关系。
Golang方法接收者类型 结构体实例,有值类型和指针类型,那么方法的接收者是结构体,那么也有值类型和指针类型。区别就是接收者是否复制结构体副本。值类型复制,指针类型不复制。
值类型结构体和指针类型结构体 实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" type Person struct { name string } func main () { p1 := Person{name: "tom" } fmt.Printf("p1: %T\n" , p1) p2 := &Person{name: "tom" } fmt.Printf("p2: %T\n" , p2) }
运行结果
1 2 p1: main.Person p2: *main.Person
从运行结果,我们可以看出p1是值类型,p2是指针类型。
下面看一个传参结构体的例子
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 package mainimport "fmt" type Person struct { name string } func showPerson (per Person) { fmt.Printf("per: %p\n" , &per) per.name = "kite" fmt.Printf("per: %v\n" , per) } func showPerson2 (per *Person) { fmt.Printf("per: %p\n" , per) per.name = "kite" fmt.Printf("per: %v\n" , per) } func main () { p1 := Person{name: "tom" } fmt.Printf("p1: %p\n" , &p1) showPerson(p1) fmt.Printf("p1: %v\n" , p1) fmt.Println("---------------" ) p2 := &Person{name: "tom" } fmt.Printf("p2: %p\n" , p2) showPerson2(p2) fmt.Printf("p2: %v\n" , p2) }
运行结果
1 2 3 4 5 6 7 8 9 p1: 0xc000046240 per: 0xc000046250 per: {kite} p1: {tom} --------------- p2: 0xc000046280 per: 0xc000046280 per: &{kite} p2: &{kite}
从运行结果,我们看到p1是值传递,拷贝了副本,地址发生了改变,而p2是指针类型,地址没有改变。
方法的值类型和指针类型接收者 值类型和指针类型接收者,本质上和函数传参道理相同。
实例
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 package mainimport "fmt" type Person struct { name string } func (per Person) showPerson () { fmt.Printf("per: %p\n" , &per) per.name = "kite" fmt.Printf("per: %v\n" , per) } func (per *Person) showPerson2 () { fmt.Printf("per: %p\n" , per) per.name = "kite" fmt.Printf("per: %v\n" , per) } func main () { p1 := Person{name: "tom" } fmt.Printf("p1: %p\n" , &p1) p1.showPerson() fmt.Printf("p1: %v\n" , p1) fmt.Println("---------------" ) p2 := &Person{name: "tom" } fmt.Printf("p2: %p\n" , p2) p2.showPerson2() fmt.Printf("p2: %v\n" , p2) }
运行结果
1 2 3 4 5 6 7 8 9 p1: 0xc000046240 per: 0xc000046250 per: {kite} p1: {tom} --------------- p2: 0xc000046280 per: 0xc000046280 per: &{kite} p2: &{kite}
从运行结果,我们看到p1是值传递,拷贝了副本,地址发生了改变,而p2是指针类型,地址没有改变。