golang:基础语法学习

本文详细介绍了Go语言的基础语法,包括包名、导包、常量、变量、函数规范、指针、数组、切片、遍历、defer、map、面向对象编程(struct、封装、继承、多态)、interface{}、反射以及结构体标签等内容,帮助初学者全面理解Go语言的核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

包名

每个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)
}

使用typestruct能够位一组数据类型定义一个结构体

// 定义一个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}
}

学习自刘丹冰Aceld视频:https://www.bilibili.com/video/BV1gf4y1r79E

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值