包名
每个go文件第一句是包名 package 包名
若想要当前main作为项目的入口,则包名必须为main,即package main
导包
使用import "包名"
// 导一个包
import "fmt"
// 导多个包 方式一
import(
"fmt"
"time"
)
// 导多个包 方式二
import "fmt"
import "time"
注意:导入的包必须要在程序中使用,如果没有用到则会报错。
三种特殊的导包方式
_:上面提到,如果导入的包在程序中没有用到,则会报错。但有时候,我们想要执行包下的init方法,而不需要调用该包,那么我们可以在前面添加_
符号,如下:
import (
// 在包名前面使用_
_ "helloworld/test1"
)
func main() {
// 方法中即使没有用到导入包,也不会报错
}
自定义别名:在包名前面可以自定义包的别名,调用的时候就使用别名调用即可。
import lena "helloworld/test1"
func main() {
//test1.Method4() // 原先的这种调用方法会报错
lena.Method3() // 利用别名调用包的方法
}
.:使用.
加在包名前面,可以将其他包的所有方法加入到当前包中,因此可以不通过包名,直接调用方法,如下:
// 原先是这样导入包
import "helloworld/test1"
func main() {
// 通过包名.方法名调用其他包的方法
test1.Method4()
}
// 在导包前面加入.
import . "helloworld/test1"
func main() {
//test1.Method4() // 原先的这种调用方法会报错
// 省略包名,可以直接调用其他包下的方法。
Method4()
}
注意:如果引入的多个包前面都添加了.
,可能不同包下有同名方法,那么会发生歧义。
执行顺序
从main包为入口。如果当前go文件有import
则一直向下执行,直到没有import的包下,然后往回开始执行这些文件的全局常量、全局变量、init()方法。如下图:
常量
使用关键词const
。定义的时候必须指明值,或者使用const进行多个常量声明时,前面的常量使用了iota
。
单个常量
func Const1() {
// 定义常量 只读
const a int = 1
fmt.Println("a =",a) // a = 1
//a=2 // 若试图更改常量值,会报错
}
func Const2() {
// 不指明类型
const b = "hello"
fmt.Println("b =",b) // b = hello
}
// 全局常量
const c = 3
func Const3() {
fmt.Println("c =",c) // c = 3
}
多个常量 枚举类型
func Const4() {
// 单行定义
const a,b="hello","world"
fmt.Println(a,b) // hello world
// 多行定义
const(
d=1
e="lena"
)
fmt.Println(d,e) // 1 lena
}
// 全局常量
const a,b="hello","lena"
func Const5() {
fmt.Println(a,b) // hello lena
}
// 枚举类型
const (
SPRING=1
SUMMER=2
AUTUMN=3
WINTER=4
)
func Const6() {
fmt.Println(SPRING) //1
fmt.Println(SUMMER) //2
fmt.Println(AUTUMN) //3
fmt.Println(WINTER) //4
}
iota
和const一起使用,用于给const常量自动赋值。每次出现的时候初始值都是0
func Iota1() {
// iota初始值是0
const a=iota
fmt.Println(a) // 0
//var a=iota //报错:iota只能配合const
}
func Iota2() {
// iota每次默认自增1
const (
a=iota
b
c
d
)
fmt.Println(a,b,c,d) // 0 1 2 3
}
func Iota3() {
// 不同const的iota互不干扰
const(
a=iota
b
)
fmt.Println(a,b) // 0 1
const (
c=iota
d
)
fmt.Println(c,d) // 0 1
}
func Iota4() {
// 同行iota的值相等 iota=0
const a, b = iota + 1, iota + 10
fmt.Println(a, b) // 1 10
const (
// iota=0,c=1,d=10
c, d = iota + 1, iota + 10
// iota=1,e=3,f=11
e, f = iota + 2, iota + 10
)
fmt.Println(c, d) // 1 10
fmt.Println(e, f) // 3 11
}
func Iota5(){
// iota规则延续第一个定义的规则
// 如果定义时一行定义了两个常量,接下来都要遵循这个规则,不能只定义一个
const(
// iota=0,g=0,c=0
g,h=iota*2,iota*4
// iota=1,i=1*2=2,j=1*4=4
i,j
// iota=2,l=2*2=4,m=2*4=8
l,m
//n // 只定义一个会报错
)
fmt.Println(g,h) // 0 0
fmt.Println(i,j) // 2 4
fmt.Println(l,m) // 4 8
}
func Iota6() {
const(
a=iota+5 // 5:0+5
k // 6:1+5
b=10 // 定义了常量会打断iota规则 后续的值如果没使用iota都是此常量
c // 10:跟着b的值
d=iota // 4:iota的规则已经被打破 当前iota与第一个相差4行 iota=4
g // 5:iota+1
)
fmt.Println(a,k,b,c,d,g) // 5 6 10 10 4 5
const(
e=1
f
)
fmt.Println(e,f) // 1 1
}
变量
单个变量声明
以下是四种方式定义变量,使用var
定义。
import "fmt"
// 定义变量
// 方式一:初始化 但不赋值
func Var1() {
// 先写名字 再写类型
var a int
// int型初始化默认是0
fmt.Println("a = ",a) // a = 0
}
// 方式二:初始化并赋值
func Var2() {
var b int = 10
fmt.Println("b = ",b) // b = 10
}
// 方式三:初始化时省略数据类型(自动匹配类型)
func Var3() {
var c = 10
fmt.Println("c = ",c) // c = 10
}
// 方式四:利用“:=”符号
func Var4() {
// 声明+赋值 必须是未被声明的变量
d:=10
fmt.Println("d = ",d) // d = 10
}
上面定义的都是局部变量,如果要定义全局变量,除了方法四都可以。
// 方法一
var ga int
// 方法二
var gb int = 10
// 方法三
var gb = 10
多个变量声明
多行定义多变量
// 局部变量
func Var5() {
var (
a int = 100
b int = 10
)
fmt.Println("a =",a,"b =",b) // a = 100 b = 10
}
// 全局变量
var (
ga int = 20
gb int = 10
)
func Var6() {
fmt.Println("ga =",ga,"gb =",gb) // ga = 20 gb = 10
}
单行定义多变量
// 局部变量
func Var7() {
var a,b = 10,20
fmt.Println("a =",a,"b =",b) // a = 10 b = 20
var c,d = true,"hello"
fmt.Println("c =",c,"d =",d) // c = true d = hello
}
// 全局变量
var gc,gd="a","b"
func Var8() {
fmt.Println("gc =",gc,"gd =",gd) // gc = a gd = b
}
语句结束可以加分号也可以不加,一般情况下是不加的。
函数规范
使用func 方法名(参数) (返回值) {}
的形式,其中左括号必须和func定义语句在同一行,否则会编译错误。
方法名首字母如果大写,表示对外开放;如果方法名首字母小写,则该方法只能包内访问。
方法传参:
// 一个参数
func Method1(a int) { // 传入2
fmt.Println("a =",a); // a = 2
a = 10;
fmt.Println("a =",a); // a = 10
}
// 多个参数
func Method2(a int,b string,c bool) { // 传入 10,"b",true
fmt.Println("a =",a); // a = 10
fmt.Println("b =",b); // b = b
fmt.Println("c =",c); // c = true
}
方法带返回值:
// 有一个返回值
func Method3() (int) {
return 3;
}
// 有多个返回值,匿名返回值
func Method4() (int,int) {
return 4,5;
}
// 多个参数多个返回值
func Method5(e int,f int) (int,int) {
return e,f;
}
// 多个返回值赋变量名称,返回值作为该方法的形参
func Method6(a int,b int) (c int,d int) {
// 不用定义 因为作为返回值变量已经初始化
c=a+10
d=b+10
// 无需指明,会自动返回cd
return
}
结果如下:
指针
使用&变量
获取到的是变量所在的地址;*变量
获取到的是地址对应的值。
// 使用 &变量 ,获取的是变量的地址
func Pointer1() {
a:=1
fmt.Println(&a) // 打印a的地址:0xc00000a0a8
}
func Method(p *int) {
fmt.Println(p,*p,&p) // 0xc000126058 2 0xc00014a018
// *p获取的是传入地址对应的值,修改对应的值为10
*p=10
}
func Pointer2() {
a:=2
//Method(a) // 报错:不能从int->*int
Method(&a) // 传入a的地址
fmt.Println("a =",a) // a = 10
}
数组
固定数组
每一种长度的固定数组是一种数据类型,不同长度的固定数组,不算同种类型。
func Array1() {
// 方式一:定义固定的数组长度
var array1 [5]int
fmt.Println(array1) // [0 0 0 0 0]
// 方式二:定义并赋值
var array2 = [5]int{1,2,3,4,5}
fmt.Println(array2) // [1 2 3 4 5]
// 方式三:定义并赋值
array3:=[3]int{1,2,3}
fmt.Println(array3) // [1 2 3]
// 查看数组类型:不同长度的固定数组,不是同种类型
fmt.Printf("%T",array1) // [5]int
Array2(array1) // 成功调用
//Array2(array3) // 报错:不能从[3]int->[5]int
}
func Array2(array [5]int) {
fmt.Println(array)
}
固定数组的传递是值传递,在方法内更改值,起作用只在方法内,不影响实际的值。
func Array3() {
array:=[3]int{1,2,3}
fmt.Println("before:",array) // before: [1 2 3]
method(array)
fmt.Println("result:",array) // result: [1 2 3]
}
func method(array [3]int) {
array[0]=10
fmt.Println("in:",array) // in: [10 2 3]
}
动态数组 slice切片
声明动态数组(slice切片)的方式:
可以使用make(数据类型,分配数量)
为数组分配空间
func Array5() {
// 方式一:有初始化值
array1 := []int{1,2,3}
fmt.Printf("%d,%v",len(array1),array1) // 3,[1 2 3]
// 方式二:定义一个切片,利用make分配空间
var array2 []int
array2=make([]int,3)
fmt.Printf("%d,%v",len(array2),array2) // 3,[0 0 0]
// 方式三:定义一个切片同时分配空间
var array3 []int=make([]int,5)
fmt.Printf("%d,%v",len(array3),array3) // 5,[0 0 0 0 0]
// 方式四:利用符号:=自动匹配出这是一个切片
array4:=make([]int,5)
fmt.Printf("%d,%v",len(array4),array4) // 5,[0 0 0 0 0]
}
动态数组传递方式的是引用传递,因此在方法内修改的值是实际的值。
func Array4() {
array1 := []int{1,2,3,4,5} // 不指定数组长度
//fmt.Printf("%T",array1) // 类型:[]int
fmt.Println(array1) // [1 2 3 4 5]
method2(array1)
fmt.Println(array1) // [1 2 10 4 5]
}
func method2(array []int) {
// 引用传递:实际值会被改变
array[2]=10
}
追加
如果在定义的时候,初始化了元素,那么当前动态数组的长度和容量等于当前元素个数,当在数组后面使用append(动态数组,要追加的元素...)
向数组追加元素,当追加后数组的长度大于容量,那么容量会扩大为原来的两倍,如下的array1数组:
func Array6() {
array1 := []int{1,2,3}
fmt.Printf("%d,%d,%v\n",len(array1),cap(array1),array1) // 3,3,[1 2 3]
array1=append(array1,2) // 追加元素2
fmt.Printf("%d,%d,%v\n",len(array1),cap(array1),array1) // 4,6,[1 2 3 2]
array2 :=make([]int,5,7)
fmt.Printf("%d,%d,%v\n",len(array2),cap(array2),array2) // 5,7,[0 0 0 0 0]
array2=append(array2, 1,2,3,4) // 追加4个元素
fmt.Printf("%d,%d,%v\n",len(array2),cap(array2),array2) // 9,14,[0 0 0 0 0 1 2 3 4]
}
在初始化的时候也可以使用make(数据类型,数组长度,容量大小)
来定义一个数组,这样实际存储的元素和容量大小会依据定义的值,如上代码array2,同样当追加后数组的长度大于容量,那么容量会扩大为原来的两倍。
截取
截取某一数组中的值,其中包括开始索引,不包括结束索引。
func Array7() {
array1 := []int{0,1,2,3,4,5,6,7}
// 截取索引[start,end)
array2 := array1[0:3]
fmt.Println(array2) // [0 1 2]
}
实际上截取的数组,只是在原来数组上的操作,而不是重新开辟了一个空间,复制了截取的部分,因此只要修改了数组的值,对应的数组都会被影响,例子如下:
func Array8() {
array1 := []int{0,1,2,3,4,5,6,7}
array2 := array1[0:3] // [0 1 2]
// 修改截取数组的值
array2[0]=100
// 原来数组的值也改变了
fmt.Println(array1) // [100 1 2 3 4 5 6 7]
fmt.Println(array2) // [100 1 2]
}
遍历
func Array9() {
array1 := []int{0,1,2,3,4,5,6,7}
// 遍历输出:index是索引,value是值
for index,value := range array1 {
fmt.Println(index,value)
}
// 遍历输出:忽略index值
for _,value := range array1 {
fmt.Println(value)
}
}
defer
作用类似于java中finally块,用于最后执行资源释放等操作。
无论defer写在哪里,都是最后执行。
func Defer1() {
defer fmt.Println("defer")
fmt.Println("last")
// 先输出last,最后输出defer
}
如果同时有defer和return,那么return先执行,defer在return后执行
func method3() int {
defer fmt.Println("defer") // 第二个执行:defer
return returndata()
}
func returndata() int {
fmt.Println("return") // 第一个执行:return
return 10
}
func Defer2() {
a:=method3()
fmt.Println(a) // 第三个执行:10
}
map
声明map方式如下:
func Map1() {
// 方式一 : 已知所需多少空间 在使用前分配
var map1 map[string]string
if(map1 == nil) {
fmt.Println("map1 is null") // map1 is null
}
// 使用前必须先分配空间 否则会报错:panic: assignment to entry in nil map
map1=make(map[string]string,3)
map1["a"]="111"
map1["b"]="222"
map1["c"]="333"
fmt.Printf("%d,%v\n",len(map1),map1) // 3,map[a:111 b:222 c:333]
// 方式二 : 不声明空间的大小 但在初始化的时候输入元素
map2:=map[string]string{
"a":"111",
"b":"222",
"c":"333", // 即使是最后一个也必须用","
}
// 之后也可以再添加
map2["d"]="444"
fmt.Println(map2) // map[a:111 b:222 c:333 d:444]
// 方式三 : 不声明大小 由系统自动分配
map3:=make(map[string]string)
map3["a"]="111"
map3["b"]="222"
map3["c"]="333"
fmt.Println(map3) // map[a:111 b:222 c:333]
}
注意:使用方式二的时候,初始化并传入数据的时候,最后一个数据的","也必须加。
基本操作如下:
func Map2() {
// 初始化
map1:=make(map[string]string)
map1["a"]="111"
map1["b"]="222"
map1["c"]="333"
// 遍历
for index,value := range map1 {
fmt.Println(index,value)
}
// 增加
map1["d"]="444"
fmt.Println(map1) // map[a:111 b:222 c:333 d:444]
// 修改
map1["a"]="aaa"
fmt.Println(map1) // map[a:111 b:222 c:333 d:444]
// 删除
delete(map1,"b")
fmt.Println(map1) // map[a:aaa c:333 d:444]
}
map传递方式是引用传递
func Map3() {
// 初始化
map1:=make(map[string]string)
map1["a"]="111"
map1["b"]="222"
map1["c"]="333"
fmt.Println(map1) // map[a:111 b:222 c:333]
method4(map1)
fmt.Println(map1) // map[a:aaa b:222 c:333]
}
func method4(map1 map[string]string) {
map1["a"]="aaa"
}
OOP
struct
使用type
能为某一数据类型定义别名
// type的作用就相当于为某一类型起别名
// 为int起别名intint
type intint int
func Struct1() {
// 使用intint定义的数据类型 实际上是int型
var a intint
fmt.Println(a)
}
使用type
和struct
能够位一组数据类型定义一个结构体
// 定义一个student结构体
type student struct {
sid int
name string
}
func Struct2() {
var student1 student
// 为结构体赋值
student1.sid=1001
student1.name="lena"
fmt.Println(student1) // {1001 lena}
}
向方法中传参,实际是值传递,传递的是副本。如果想要进行引用传递,要使用指针
func method5(student1 student) {
// 实际使用的是副本
student1.sid=7777
}
func Struct3() {
var student1 student
// 为结构体赋值
student1.sid=1001
student1.name="lena"
fmt.Println(student1) // {1001 lena}
method5(student1)
// 值没有改变,说明是值传递
fmt.Println(student1) // {1001 lena}
}
func method6(student1 *student) {
// 引用传递:传递的是实际地址
student1.sid=7777
}
func Struct4() {
var student1 student
// 为结构体赋值
student1.sid=1001
student1.name="lena"
fmt.Println(student1) // {1001 lena}
method6(&student1)
// 值改变了
fmt.Println(student1) // {7777 lena}
}
封装
封装的对象、对象属性、对象方法,首字母开头大写的话其他包也能够访问;首字母小写表明是当前包私有的。在定义对象方法的时候,使用func (this *对象名) 方法名...
将方法绑定到对象上,其中必须使用指针,否则this只是当前对象的副本,修改副本对原对象没有改变。
import "fmt"
// 封装teacher对象:首字母大写其他包也能访问到
type Teacher struct {
// 属性开头如果是小写,就是私有属性
Tid int
Name string
}
// 在func后面用this绑定当前对象 必须使用指针 不然的话this只是当前对象的一个副本
func (this *Teacher) SetTid(tid int) {
this.Tid=tid
}
// 首字母大写的方法能够被外界访问
func (this *Teacher) GetTid() int {
return this.Tid
}
func (this *Teacher) SetName(name string) {
this.Name=name
}
func (this *Teacher) GetName() string {
return this.Name
}
func (this *Teacher) Print() {
fmt.Println(this.Tid,this.Name)
}
func Test() {
// 创建对象
teacher1 := Teacher{Tid:1001,Name:"lena"}
teacher1.Print() // 1001 lena
// 获取值
s:=teacher1.GetName()
fmt.Println(s) // lena
// 修改值
teacher1.SetName("golang")
teacher1.Print() // 1001 golang
}
继承
若要继承某一个类,在定义属性的时候写入父类。子类会继承父类的公有方法、属性,也可以重写父类的方法,或新增方法。
import "fmt"
type Human struct {
Name string
Age int
}
func (this *Human) Action() {
fmt.Println("human action ...")
}
func (this *Human) Show() {
fmt.Println("human show ...")
}
func (this *Human) Print() {
fmt.Println(this.Name,this.Age)
}
type Woman struct{
Human // 将父类的类名写到子类的属性中:继承Human
talent string
}
// 重写父类方法
func (this *Woman) Action() {
fmt.Println("woman action ...")
}
// 子类添加新方法
func (this *Woman) Dance() {
fmt.Println("woman dance ...")
}
func Extend() {
human := Human{Name:"human",Age:18}
human.Action() // human action ...
human.Show() // human show ...
human.Print() // human 18
fmt.Println("---------------")
woman := Woman{talent: "dance"}
woman.Name="woman" // 继承了父类的属性方法
woman.Age=20
woman.Print() // woman 20
woman.Action() // woman action ...
woman.Dance() // woman dance ...
woman.Show() // human show ...
}
多态
利用interface
定义一个接口,接口内定义方法,只要实现该接口的所有公有方法的结构体,都是该接口的实现类。
import "fmt"
// 多态
type Animal interface {
Sleep()
GetName() string
GetType() string
}
// 猫:实现接口Animal所有公有方法
type Cat struct {
Name string
}
func (this *Cat) Sleep() {
fmt.Println("cat:",this.Name,"is sleep...")
}
func (this *Cat) GetName() string {
return this.Name
}
func (this *Cat) GetType() string {
return "cat"
}
// 狗:实现接口Animal所有公有方法
type Dog struct {
Name string
}
func (this *Dog) Sleep() {
fmt.Println("dog:",this.Name,"is sleep...")
}
func (this *Dog) GetName() string {
return this.Name
}
func (this *Dog) GetType() string {
return "dog"
}
func Run(animal Animal) string {
// 多态:传入接口animal,会根据实际值调用方法
return animal.GetType()
}
func Polymorphic() {
var animal1 Animal=&Cat{"little cat"}
animal1.Sleep() // cat: little cat is sleep...
fmt.Println(animal1.GetName()) // little cat
fmt.Println(animal1.GetType()) // cat
fmt.Println("--------------------------")
var animal2 Animal=&Dog{"little dog"}
animal2.Sleep() // dog: little dog is sleep...
fmt.Println(animal2.GetName()) // little dog
fmt.Println(animal2.GetType()) // dog
fmt.Println("--------------------------")
fmt.Println(Run(animal1)) // cat
fmt.Println(Run(animal2)) // dog
}
interface{} 万能类型
使用interface{}
作为变量的类型,类似于Object,是一个万能类型。作为方法的参数,可以传入任意类型的变量。
int、string、float32、float64、struct…都实现了interface{}
// 万能类型:interface{}
func Interface1() {
var a interface{}
fmt.Printf("%T,%v\n",a,a) // <nil>,<nil>
a="hello"
fmt.Printf("%T,%v\n",a,a) // string,hello
a=100
fmt.Printf("%T,%v\n",a,a) // int,100
}
// 可传入任意类型
func method7(a interface{}) {
fmt.Printf("%T,%v\n",a,a)
}
func Interface2() {
a:="hello"
method7(a) // string,hello
b:=true
method7(b) // bool,true
}
类型断言
使用interface {} 定义变量的时候,可以使用类型断言,即确定一个数据类型,当变量为当前数据类型的时候返回value值,形式如:变量值,是否是当前指定类型:=变量名.(指定类型)
// 通过方法判断传入的类型再做处理
func method8(a interface{}) {
// value为a的值,flag为a是否是string类型。若不需要的值,可以用"_"表示
// 注意:value只有在flag为true的时候,才会是a的值,否则是空
value,flag:=a.(string)
if(flag) {
fmt.Println(value,"is string")
} else { // else一定要写在if右括号同一行
fmt.Println(value,"is not string")
}
}
func Interface3() {
a:=100
method8(a) // is not string
b:="hello"
method8(b) // hello is string
}
变量的内置pair
一个变量由type(类型)+值(value)组成,这两者就是一个pair,即pair=type+value
。其中,type又可分为static type(静态类型,如int、string…),和concrete type(具体类型,即interface{}变量实际的类型)
在定义了未初始化的时候,变量的pair是不确定的。在接受了其他参数传递的时候pair跟着传递了进来。
func Pair1() {
a:="hello"
var b interface{}
fmt.Printf("%T\n",b) // <nil>
b=a
fmt.Printf("%T\n",b) // string
}
func Pair2() {
// tty:pair<type:*os.File,value:"/lena"文件描述符>;error为异常
tty,error:=os.OpenFile("/dev/tty",os.O_RDWR,0)
if error != nil {
fmt.Println(tty,error)
}
}
// 在参数传递的时候pair是永远不变的,也跟着传递
func Pair3() {
// tty:pair<type:*os.File,value:"/lena"文件描述符>;error为异常
tty,error:=os.OpenFile("/dev/tty",os.O_RDWR,0)
if error != nil {
fmt.Println(tty,error)
}
// 定义一个type=io.Reader(是interface) 此时type和value都未知
var reader io.Reader
// reader:pair<type:*os.File,value:"/lena"文件描述符>
reader=tty
fmt.Println(reader)
}
reflect 反射
当我们只有一个变量的名的时候,可以通过反射,获取到改变了的信息,类似Java中的反射。使用reflect.TypeOf(变量名)
能够获取到变量类型;使用reflect.ValueOf(变量名)
能够获取到值。
import (
"fmt"
"reflect"
)
func method9(a interface{}) {
fmt.Println(reflect.TypeOf(a)) // 反射获取类型
fmt.Println(reflect.ValueOf(a)) // 反射获取值
}
func Reflect1() {
a:="hello"
method9(a) // string hello
b:=student{sid:1001,name:"hello"}
method9(b) // test1.student {1001 hello}
}
此外,如果我们的变量是一个结构体对象,那么我们也可以通过反射获取该结构体中的属性名、属性类型、变量值、方法信息等。
type people struct {
Id int
Name string
Age int
}
func (this people) Show() {
fmt.Println(this.Id,this.Name,this.Age)
}
func ReflectMethod(p interface{}) {
// 获取字段/方法信息
ptype:=reflect.TypeOf(p)
// 获取值信息
pvalue:=reflect.ValueOf(p)
// 遍历获取p的所有字段,NumField()获取字段总数
for i:=0; i<ptype.NumField(); i++ {
// 获取字段信息,field.Name=字段名称,field.Type=字段类型
field:=ptype.Field(i)
// 获取字段值
value:=pvalue.Field(i).Interface()
fmt.Println(field.Name,field.Type,value)
}
// 遍历获取p的所有方法,NumMethod()获取方法数量
for i:=0; i<ptype.NumMethod();i++ {
// 获取方法信息,method.Name=方式名称,method.Type=方法类型
method:=ptype.Method(i)
fmt.Println(method.Name,method.Type) // Show func(test1.people)
}
}
func Reflect2() {
p:=people{1001,"hello",18}
ReflectMethod(p)
}
更多的reflect方法可以参考中文文档:https://studygolang.com/pkgdoc
结构体标签
在定义结构体的时候,可以在变量行中前后使用反引号+key:"value"形式定义结构体标签,多个标签使用空格分开。具体如下:
// 结构体标签:利用反引号以key:"value"形式
type structTag struct {
Name string `info:"name" doc:"名字"`
Sex string `info:"sex" doc:"性别"`
}
这样定义的话,我们在有需要的时候就可以通过反射获取结构体标签:
func findTag(tag interface{}) {
// 利用反射获取标签内容
t:=reflect.TypeOf(tag).Elem()
for i:=0; i<t.NumField(); i++ {
info:=t.Field(i).Tag.Get("info") // 标签info的值
doc:=t.Field(i).Tag.Get("doc") // 标签doc的值
fmt.Println(info,doc) // name 名字
}
}
func Struct5() {
tag:=structTag{"hello","women"}
findTag(&tag)
}
实际使用:有时候我们对结构体对象进行转json格式,或者json格式转结构体对象的时候,可能对于变量名与定义的不符,那么我们可以利用结构体标签定义其json转换时候使用的实际值:
import (
"encoding/json"
"fmt"
)
type Grade struct {
Sid int `json:"student_id"`
Cid int `json:"class_id"`
Grade int `json:"grade"`
}
func Json1() {
grade:=Grade{1001,183,99}
// json编码:将结构体转换成json格式 其中变量名采用结构体标签定义的json值
jsonStr,err:=json.Marshal(grade)
if err != nil { // 无异常的时候err为<nil>
fmt.Println(err)
}
fmt.Printf("%s\n",jsonStr) // {"student_id":1001,"class_id":183,"grade":99}
// json解码:将json格式的数据转换为结构体对象
grade1:=Grade{}
// func Unmarshal(data []byte, v interface{}) error
err=json.Unmarshal(jsonStr,&grade1)
if err != nil { // 无异常的时候err为<nil>
fmt.Println(err)
}
fmt.Println(grade1) // {1001 183 99}
}