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 = "tom@gmail.com" fmt.Printf("tom: %v\n" , tom) }
运行结果如下
1 tom: {1 20 tom tom@gmail.com}
匿名结构体 如果结构体是临时使用,可以不用起名字,直接使用,例如:
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: "kite@gmail.com" , } fmt.Printf("kite: %v\n" , kite) }
运行结果
1 kite: {1 20 kite kite@gmail.com}
使用值的列表初始化 实例
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" , "kite@gmail.com" , } fmt.Printf("kite: %v\n" , kite) }
运行结果
1 kite: {1 20 kite kite@gmail.com}
注意:
必须初始化结构体的所有字段。
初始值的填充顺序必须与字段在结构体中的声明顺序一致。
该方式不能和键值初始化方式混用。
部分成员初始化 用不到的成员,可以不进行初始化
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是指针类型,地址没有改变。