Go语言基础整理
发布于 2021-11-18 17:09 ,所属分类:软件编程学习资料
学无止境
Book of the Year 2021
爱读书的人聚在一起,便是广厦千万间
读书的用,不在眼前,不在当下,而是像甘霖雨露滋润万物之后,万物所呈现的那种清新、新鲜、水灵时的生机勃勃,是看不见和摸不着的。”
0x01 语言特性
0x02 基础介绍--编译
2.1 项目目录结构
2.1.1 Go源码的组织方式
Go通过package的方式来组织源码,并且package要放在非注释的第一行 每个项目入口为main文件,且只能有一个main文件 可执行程序的包名必须为main,并且包含一个main函数 除了可执行程序外,用户可以写自定义包,自定义包编译成静态库 任何一个源码都属于一个包 优势:代码复用和可读性
2.1.2 程序的基本结构
packagemain
import"fmt"
funcmain(){
fmt.Println("helloword")
}
import 关键字,引入其他包 包中函数调用 同一个包中,直接用函数名调用 不同包中函数,通过包名.函数名进行调用 包访问控制规则 大写意味着这个函数/变量可以导出 小写以为着这个函数/变量是私有的,包外部不能访问
2.1.3 编译命令
go run(编译并运行)
❝❞
go run不会在运行目录下生成任何文件,可执行文件被放在临时文件中被执行,工作目录被设置为当前目录。在go run的后部可以添加参数,这部分参数会作为代码可以接受的命令行输入提供给程序。 go run不能使用“go run+包”的方式进行编译,如需快速编译运行包,需要使用go build go run 在执行时,如果有多个文件,需要把所有文件都写在go run后面
go build(编译)
❝❞
go build 编译go代码,如果是可执行程序,默认会在当前目录下生成可执行程序,可以使用-o指定可执行程序生成的目录 go build 无参数编译:如果源码中没有依赖 GOPATH 的包引用,那么这些源码可以使用无参数 go build go build + 文件列表:编译同目录的多个源码文件时,可以在 go build 的后面提供多个文件名,go build 会编译这些源码,输出可执行文件,默认选择文件列表中第一个源码文件作为可执行文件名输出。 go build + 包:在设置 GOPATH 后,可以直接根据包名进行编译,即便包内文件被增(加)删(除)也不影响编译指令。
go build 还有一些附加参数,可以显示更多的编译信息和更多的操作,详见下表所示。
附加参数 | 备 注 |
---|---|
-v | 编译时显示包名 |
-p n | 开启并发编译,默认情况下该值为 CPU 逻辑核数 |
-a | 强制重新构建 |
-n | 打印编译时会用到的所有命令,但不真正执行 |
-x | 打印编译时会用到的所有命令 |
-race | 开启竞态检测 |
go install(编译)
❝go install 的编译过程有如下规律:
❞
go install 是建立在 GOPATH 上的,无法在独立的目录里使用 go install。 GOPATH 下的 bin 目录放置的是使用 go install 生成的可执行文件,可执行文件的名称来自于编译时的包名。 go install 输出目录始终为 GOPATH 下的 bin 目录,无法使用-o附加参数进行自定义。 GOPATH 下的 pkg 目录放置的是编译期间的中间文件。 go install编译go代码,并且把可执行程序拷贝到GOPATH的bin目录,自定义或第三方包会拷贝到GOPATH的pkg目录
go clean(清除编译文件)
❝go clean命令可以移除当前源码包和关联源码包里面编译生成的文件,这些文件包括以下几种:
❞
执行go build命令时在当前目录下生成的与包名或者 Go 源码文件同名的可执行文件。在 Windows 下,则是与包名或者 Go 源码文件同名且带有“.exe”后缀的文件。 执行go test命令并加入-c标记时在当前目录下生成的以包名加“.test”后缀为名的文件。在 Windows 下,则是以包名加“.test.exe”后缀的文件。 执行go install命令安装当前代码包时产生的结果文件。如果当前代码包中只包含库源码文件,则结果文件指的就是在工作区 pkg 目录下相应的归档文件。如果当前代码包中只包含一个命令源码文件,则结果文件指的就是在工作区 bin 目录下的可执行文件。 在编译 Go 或 C 源码文件时遗留在相应目录中的文件或目录 。包括:“_obj”和“_test”目录,名称为“_testmain.go”、“test.out”、“build.out”或“a.out”的文件,名称以“.5”、“.6”、“.8”、“.a”、“.o”或“.so”为后缀的文件。这些目录和文件是在执行go build命令时生成在临时目录中的。
go fmt(格式化代码)
❝gofmt 是一个 cli 程序,会优先读取标准输入,如果传入了文件路径的话,会格式化这个文件,如果传入一个目录,会格式化目录中所有 .go 文件,如果不传参数,会格式化当前目录下的所有 .go 文件。
❞
gofmt 命令参数如下表所示:
记名称 | 标记描述 |
---|---|
-l | 仅把那些不符合格式化规范的、需要被命令程序改写的源码文件的绝对路径打印到标准输出。而不是把改写后的全部内容都打印到标准输出。 |
-w | 把改写后的内容直接写入到文件中,而不是作为结果打印到标准输出。 |
-r | 添加形如“a[b:len(a)] -> a[b:]”的重写规则。如果我们需要自定义某些额外的格式化规则,就需要用到它。 |
-s | 简化文件中的代码。 |
-d | 只把改写前后内容的对比信息作为结果打印到标准输出。而不是把改写后的全部内容都打印到标准输出。 命令程序将使用 diff 命令对内容进行比对。在 Windows 操作系统下可能没有 diff 命令,需要另行安装。 |
-e | 打印所有的语法错误到标准输出。如果不使用此标记,则只会打印每行的第 1 个错误且只打印前 10 个错误。 |
-comments | 是否保留源码文件中的注释。在默认情况下,此标记会被隐式的使用,并且值为 true。 |
-tabwidth | 此标记用于设置代码中缩进所使用的空格数量,默认值为 8。要使此标记生效,需要使用“-tabs”标记并把值设置为 false。 |
-tabs | 是否使用 tab('\t')来代替空格表示缩进。在默认情况下,此标记会被隐式的使用,并且值为 true。 |
-cpuprofile | 是否开启 CPU 使用情况记录,并将记录内容保存在此标记值所指的文件中。 |
gofmt和go fmt
gofmt 是一个独立的 cli 程序,而Go语言中还有一个go fmt命令,go fmt命令是 gofmt 的简单封装。
❝go fmt命令本身只有两个可选参数-n和-x:
❞
-n仅打印出内部要执行的go fmt的命令; -x命令既打印出go fmt命令又执行它,如果需要更细化的配置,需要直接执行 gofmt 命令。
go fmt在调用 gofmt 时添加了-l -w参数,相当于执行了gofmt -l -w
go get(获取代码、编译并安装)
go get 命令可以借助代码管理工具通过远程拉取或更新代码包及其依赖包,并自动完成编译和安装。整个过程就像安装一个 App 一样简单。
这个命令可以动态获取远程代码包,目前支持的有 BitBucket、GitHub、Google Code 和 Launchpad。在使用 go get 命令前,需要安装与远程包匹配的代码管理工具,如 Git、SVN、HG 等,参数中需要提供一个包名。
❝参数介绍:
❞
-d 只下载不安装 -f 只有在你包含了 -u 参数的时候才有效,不让 -u 去验证 import 中的每一个都已经获取了,这对于本地 fork 的包特别有用 -fix 在获取源码之后先运行 fix,然后再去做其他的事情 -t 同时也下载需要为运行测试所需要的包 -u 强制使用网络去更新包和它的依赖包 -v 显示执行的命令
默认情况下,goget 可以直接使用。例如,想获取go的源码并编译,使用下面的命令行即可:
goget+远程包
获取前,请确保 GOPATH 已经设置。Go 1.8版本之后,GOPATH默认在用户目录的go文件夹下。
go get 使用时的附加参数
附件参数 | 备注 |
---|---|
-v | 显示操作流程的日志及信息,方便检查错误 |
-u | 下载丢失的包,但不会更新已经存在的包 |
-d | 只下载,不安装 |
-insecure | 允许使用不安全的 HTTP 方式进行下载操作 |
0x03 基础介绍-语法
3.1 注释
3.2 标识符、关键字
3.2.1 _标识符
导入某个包,但是没有引用该包的任何内容,只是想要该包的init函数执行,在导入包名前加_. 使用变量接收返回多值的函数时,,
funccalc(a,bint)(sumint,subint){
sum=a+b
sub=a-b
return
}
funcmain(){
sum,_:=calc(1,2)
fmt.Printf("sum=%d\n",sum)
}
//使用_代替参数
3.2.2 go语言关键字
break | default | func | interface | select |
---|---|---|---|---|
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthough | if | range | type |
continue | for | import | return | var |
3.3 变量常量
3.3.1 变量
变量的作用域
全局变量,在程序整个生命周期有效 局部变量,在当前函数内部有效 局部变量和全局变量重名,采用就近原则
3.3.2 常量
3.4 简单数据类型
3.4.1 字符串原理解析
字符串底层就是一个byte数组,所以可以和[]byte类型互相转换 字符串之中的字符是不能修改的,需要先将字符串转成[]byte类型,然后对byte类型修改,然后转回去 字符串是由byte字节组成,所以字符串的长度是byte字节的长度 rune类型用来表示utf8字符,一个rune字符由1个或多个byte组成
3.4.2 时间类型---time包
time.Time类型,用来表示时间 time.NOW(),获取当前时间 time.NOW().Day(),获取天 time.NOW().Minute(),获取当前分钟 time.NOW().Mouth(),获取当前月 time.NOW().Year(),获取当前年 fmt.printf("%02d-%02d-%02d %02d:%02d:%02d",year,mouth,day,hour,minute,send),格式化输出 time.NOW().Format("2006/01/02 15:04:05") //go语言诞生时间 time.NOW().Unix(),获取当前时间戳 时间戳转Time类型 ,time.Unix() time.Duration用来表示纳秒 定时器的简单使用
functestTicker(){
ticker:=time.Tick(time.Second)
fori:=rangeticker{
fmt.Printf("%v\n",i)
}
}
一些常量
❝const(
//纳秒 Nanosecond Duration =1
//微秒 Microsecond = 1000Nanosecond
//毫秒 Millisecond = 1000Microsecond
//秒 Second = 1000Millisecond
//分钟 Minute = 60Second
//小时Hour = 60*Minute
)
❞
3.5 输入输出
3.5.1 输入
需要接收用户输入的数据,就可使用键盘输入语句来获取。
fmt.Scanln()的使用
varastring//先声明需要的变量
fmt.Scanf(&a)
fmt.Scanf()的使用
fmt.Scanf()可以按指定的格式输入
varaint
fmt.Scanf("%d\n",&a)//%d为占位符,\n为输入截止符
3.5.2 基本输出
fmt包中print、printf、println三者之间的区别
❝❞
fmt.println:可以输出字符串、变量(默认换行) fmt.print:可以输出字符串、变量(无换行) fmt.printf:只可以打印出格式化的字符串,可以输出字符串和变量,不可以输出整形变量和整形
println和fmt.println的区别
❝println是builtin包提供的,语言内置 fmt.println是来自fmt标准库
❞
两者的归属不同
print方法和println方法并不是fmt标准包中的输入输出操作方法,因此在打包压缩的时候并不存在依赖关系。但是同样的也正是由于他们不是fmt标准包中规定的方法,所以在debug调试的时候有时会因为书写方便去使用他们。但并不推荐这种做法,具体看下一个原因。
两者的可输出范围不同
由于print方法和println方法没有fmt标准包支持,所以他们在使用的时候具有很大的局限性。例如这两者均不能打印结构体类型的变量,而fmt包中规定的两个输出方法则不会有这种问题
typeTmpstruct{}
funcmain(){
tmp:=Tmp{}
print(tmp)//报错
println(tmp)//报错
fmt.Println(tmp)
fmt.Print(tmp)
}
0x04基础介绍--流程控制
4.1 if-else语句
4.1.1 if-else语句用法
ifnum:=0;num<10{
dosomething
}else{
dosomething
}
//num变量的作用域只有该if语句内,可以修改为函数的调用
4.1.2 if-else-if语句用法
ifcondition{
dosomething
}elseif{
dosomething
}else{
dosomething
}
4.2 for循环
go语言中只有for循环,类似java中的for循环
forinitialisatrion;condition;post{
dosomething
}
//初始化和post可以省略,;可以省
4.2.1 break 终止循环
forinitialisatrion;condition;post{
if条件{
break
}
dosomething
}
4.2.2 continue 终止本次循环
forinitialisatrion;condition;post{
if条件{
continue
}
dosomething
}
4.2.3 无限循环
for{
dosomething
}
4.3 switch语句
4.3.1 switch语句基础用法
switcha{
case1:
dosomething
case2:
dosomething
default:
dosomething
}//default可以省略
4.3.2 switch变量作用域
switcha:=2;a{
case1:
dosomething
case2:
dosomething
}
//num变量的作用域只有该switch语句内,可以修改为函数的调用
4.3.3 switch多case合并
switcha{
case1,2,3:
dosomething
default:
dosomething
}
//多个case可以合并
4.3.4 switch无参数
switch{
casea>1:
dosomething
casea=1:
dosomething
default:
dosomething
}
//switch无参数,case判断条件
4.3.5 fallthrough用法
switch{
casea>1:
dosomething
fallthrough
casea=1:
dosomething
default:
dosomething
}
//fallthrough穿透到下一语句执行
0x05基础介绍--其他数据类型
5.1 数组
5.1.1 数组的定义
数组是同一类型的元素集合。
var name
[len] type
❝Go中数组下标从0开始,因此长度为n的数组下标范围:0,n-1 整数数组中的元素默认初始化为0,字符串数组中的元素默认初始为""
❞
5.1.2 数组的初始化
vara[3]int
a[0]=10
a[2]=20
a[3]=30
//数组初始化
vara[3]int=[3]int{10,20,30}
//定义数组时初始化
a:=[3]int{10,20,30}
//定义数组时初始化
a:=[...]int{10,20,30}
//定义数组时初始化,数组长度由实际推导
a:=[3]int{10}
//定义数组时初始化部分元素
a:=[3]{2:100}
//根据下标给元素赋值
5.1.3 内置函数len
求数组长度
vara[3]int=[3]int{10,20,30}
len(a)//3
5.1.4 数组遍历
fori:=0;i<len(a);i++{
fmt.Println(a[i])
}
//设置下标遍历数组长度
fori,j:=rangea{
fmt.Println(i,j)
}
i对应数组下标,j对应数组值
//使用_省略下标
5.2 二维数组
❝var a [2][2]int = [2][2] int{{1,1},{2,2}}
❞
5.2.3 二维数组遍历
for_,val:=rangea{
for_,vals:=rangeval{
fmt.Printf("%d\t",vals)
}
5.3 切片
切片是基于数组类型做的一层封装,它非常灵活,可以自动扩容,切片是引用地址,类似指针,扩容的策略是翻倍(当容量不足时,将容量修改为原容量的*2)
❝var a [] int
❞
//定义一个空切片
5.3.1 切片初始化
a[start:end]创建一个从start到end-1的切片
funcqiepiandemo(){
a:=[5]int{1,2,3,4,5}
varb[]int=a[1:4]//基于数组a创建一个切片,包括元素a[start]到a[end]
fmt.Println(b)
}
funcmain(){
c:=[]int{6,7,8}//创建一个数组并返回一个切片
}
5.3.2 使用make创建切片
i:=make([]int,5,5)
第一个5表示切片长度,第二个5表示切片的容量,长度小于容量
切片容量从切片开始位置算,到数组结尾
5.3.3 切片的基本操作
arr[start:end],创建一个从start到end-1的切片 arr[start:],创建一个从start到结尾的切片 arr[:end],创建一个从头到end-1的切片 arr[:],创建一个从头到尾的切片,包含整个数组
5.3.4 切片的修改
通过切片修改数组的值
functsetSlice1(){
a:=[...]int{1,2,3,4,5,6,7,8,9,10}
fmt.Println(a)//[12345678910]
b:=a[2:5]
fmt.Println(b)//[345]
fori,_:=rangeb{
b[i]++
}
fmt.Println(b)//[456]
fmt.Println(a)//[12456678910]
}
5.3.5 空切片和扩容策略
不能直接对空切片进行赋值操作,可以append操作。
扩容策略,每次扩容翻倍。
5.3.6 append一个切片
//定义一个切片
vara:=[]int{1,3,4}
//再定义一个切片
varb:=[]int(2.5.7)
a=append(a,b...)
append追加一个切片时,需要将被追加的切片加。。。,展开切片
5.3.7 切片的传参
funcmodifySlice(a[]int){
a[0]=1000
}
funcmain(){
vara[3]int=[3]int{1,2,3}
fmt.Println(a)//1,2,3
modifySlice(a[:])
fmt.Println(a)//1000,2,3
}
将数组的切片当作参数传给函数时,可以修改切片的值,会导致数组的值修改。
5.3.8 切片的拷贝
functestCopy(){
a:=[]int{1,2,3}
b:=[]int{4,5,6}
copy(a,b)
fmt.Println(a)//4,5,6
a:=[]int{1,2,}
b:=[]int{4,5,6}
copy(a,b)
fmt.Println(a)//4,5
}
copy不会对长度进行扩容,当b和a长度不同时,只拷贝和a长度一致的前几位
5.3.9 切片练习----密码生成工具
packagemain
import(
"flag"
"fmt"
"math/rand"
"time"
)
varlengthint
varcharsetstring
varyuan_resstring
const(
num_str="0123456789"
char_str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
base_str="~!@#$%^&*()_+{}|<>?"
)
funcget_password()string{
varpassword_res[]byte=make([]byte,length,length)
switchcharset{
case"num":
yuan_res=num_str
case"char":
yuan_res=char_str
case"min":
yuan_res=fmt.Sprintf("%v%v",num_str,char_str)
case"advance":
yuan_res=fmt.Sprintf("%v%v%v",num_str,char_str,base_str)
default:
yuan_res=num_str
}
fori:=0;i<length;i++{
index:=rand.Intn(len(yuan_res))
password_res[i]=yuan_res[index]
}
returnstring(password_res)
}
funcmain(){
rand.Seed(time.Now().UnixNano())
flag.IntVar(&length,"l",16,"-l制定密码生成的长度")
flag.StringVar(&charset,"t","num",`
num:只使用数字
char:只使用字母
min:使用字母和数字
advance:使用字母、数字、特殊符号
`)
flag.Parse()
fmt.Println(get_password())
}
5.4 指针
5.4.1 变量和内存地址
每个变量都有内存地址,可以说通过变量来操作对应大小的内存,可以通过&获取变量的地址
varaint=1
a=100
fmt.Println(a)
fmt.Println(&a)
5.4.2 指针类型和值类型
普通变量都是值类型,普通变量存储的是对应类型的值 切片是指针类型 指针类型存储的是一个地址,又叫引用类型,指针本身也有变量地址 指针初始值为nil 指针类型定义:var a *int = &b 指针传参:与正常变量传参一样
5.4.3值拷贝与引用拷贝
值拷贝:
❝a:=100
b:=a
a和b的值都是100,但是内存地址不同
❞
引用拷贝:
❝var a int =100
var b *int =&a
var c *int =b
*c=200
b和c都是指针变量,指向a的地址
❞
5.4.4 make与new的区别
make为内建类型slice、map和channel分配内存 new用于分配除引用类型的所有其他类型的内存,如int、数组等。 new返回是一个指针
5.5 map(字典)
5.5.1 map的初始化
❝var a map[key的类型]value类型
❞
map必须初始化才可以使用,否则为报错
map类型的变量默认初始化为nil,需要使用map分配map内存
❝a=make(map[String]int,16)
❞
5.5.2 map的插入
a:=make(map[string]int)
a["A"]=10
a["B"]=20
//map通过key可以给value赋值
5.5.3 判断map指定的key是否存在
value,ok=map[key]
//如果存在,ok为True,否则为false
直接访问map不存在的key的值,为默认值0。
5.5.4 map的遍历
forkey,value:=ranga{
}
5.5.5 map删除元素
删除指定key
a_map:=map[string]int{
"a":100
"b":10
}
delete(a_map,"a")//删除指定key
删所有
forkey,_:=ranga_map{
delete(a,key)
}//删除所有
5.5.6 map的长度
len(a_map)
5.5.7 map是引用类型
a_map:=map[string]int{
"a":100
"b":10
}
b_map=a_map
b_map["a"]=1000
fmt.Println(a_map)//1000,10
5.5.8 map进行排序
默认情况下,map不是按照key有序进行遍历的。
借助一个切片,保存所有的key,使用sort对切片进行排序,根据切片排序后的结果输出value
varkeys[]string=make([]string,0,len(a_map))
forkey,value:=rang(a_map){
keys=append(keys,key)
}
sort.Strings(keys)
for_,key:=rang(a_map){
fmt.Println(a_map[key])
}
5.5.9 map类型的切片
varmapSlice[]map[string]int
mapSlice=make([]map[String]int,5,16)//map类型的切片,初始长度为5,每个长度都是一个map
mapSlice[0]=make(map[string]int,10)//对第1个map进行初始化,map不初始化无法使用
mapSlice[0]["a"]=10
mapSlice[0]["b"]=10
mapSlice[0]["c"]=10
mapSlice[0]["d"]=10
mapSlice[0]["e"]=10
5.5.10 map的每个值都是一个切片
varsmap[string][]int
s=make(map[string][]int,16)//map的value都是一个切片,切片不初始化无法使用
//需要先判断切片是否存在
key:="a"
value,ok:=s[key]
if!ok:{
s[key]=make([]int.0,6)//如果切片不存在的话,初始化切片
value=s[key]
}
value=append(value,100)
value=append(value,200)
value=append(value,300)
s[key]=value
5.6 自定义类型
5.6.1 Struct 声明与定义
go中面向对象是通过struct来实现的,struct的用户自定义的类型
结构体没有构造函数,需要自己去实现
typeUserstruct{
usernamestring
sexstring
ageint
}
//type是用来定义一种类型的
5.6.2 Struct初始化方法
Struct初始化方法1
varuserUser
user.username="zhangsan"
user.sex="nan"
user.age=10
Strct初始化方法2
varuser2User=User{
username="zhangsan",
sex="nv",
age=20,
}
Struct初始化方法3
user:=User{
username="zhangsan",
sex="nv",
age=20,
}
Struct初始化方法4--指针
varuser*User=&User{
username="zhangsan",
sex="nv",
age=20,
}
Struct初始化方法5--指针
varuser*User=&User{}
user.username="zhangsan"
user.sex="nan"
user.age=10
Struct初始化方法6--new
varuseUser=new(User)
user.username="zhangsan"
user.sex="nan"
user.age=10
「注意:&User{}和new(User)本质上是一样低调,都是返回一个结构体的地址。」
5.6.3 Struct内存布局
结构体的内存布局:占用一段连续的内存空间,int32类型的值,占4个字符。
5.6.4 匿名字段
匿名字段:即没有名字的字段
typeUsersteuct{
usernamestring
sexint
ageint
int
string
}
//注意:匿名字段默认采用类型名作为字段名
5.6.5 结构体嵌套
packagemain
import"fmt"
typeAddressstruct{
provincestring
Citystring
}
typeUserstruct{
Usernamestring
Ageint
Sexstring
addressAddress
}
funcmain(){
user:=&User{
Username:"user01",
Sex:"man",
Age:10,
address:Address{
province:"beijing",
City:"beijing",
},
}
fmt.Printf("use=%#v\n",user)
}
5.6.6 匿名结构体
packagemain
import"fmt"
typeAddressstruct{
provincestring
Citystring
}
typeUserstruct{
Usernamestring
Ageint
Sexstring
Address
}
funcmain(){
user:=&User{
Username:"user01",
Sex:"man",
Age:10,
}
user.province="beijing"
user.City="beijing"
fmt.Printf("use=%#v\n",user)
}
5.6.7 匿名字段冲突解决
当多个结构体中都有同一字段名,使用匿名结构体时,会发送冲突
packagemain
import"fmt"
typeAddressstruct{
Citystring
provincestring
CreateTimestring
}
typeEmailstruct{
Accountstring
CreateTimestring
}
typeUserstruct{
Usernamestring
Sexstring
Ageint
*Address
*Email
CreateTimestring
}
funcmain(){
user:=&User{
Username:"zhangsan",
Sex:"man",
Age:10,
}
user.Address=new(Address)
user.Address.CreateTime="aaaa"
user.CreateTime="beijing"
fmt.Printf("user=%#v\n",user)
}
如果直接使用user去调用,默认调用自己本身的字段。如果多个变量。则需要先初始化匿名结构体,然后使用结构体赋值。
5.6.8 结构体tag
tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来
❝type User struct{
username string
json:"username",db:"user_name"
Sex string
json:"sex"
Age int
json:"age"
CreateTime string
}
字段类型后面,以反引号括起来的key-value结构的字符串,多个tag以逗号隔开。
❞
packagemain
import(
"encoding/json"
"fmt"
)
typeUserstruct{
Usernamestring`json:"username"`
Sexstring`json:"sex"`
Scorefloat32
ageint
}
funcmain(){
user:=User{
Username:"user01",
Sex:"man",
Score:99.1,
age10,
}
data,_:=json.Marshal(user)
fmt.Printf("jsonstr:%s\n",string(data))
}
通过json序列化后,如果自定义了名称,则使用定义的名称,如果未定义,默认使用变量名,如果变量为小写,则无法使用json
0x06 基础介绍--方法与函数
6.1 函数
6.1.1 函数
func 函数名(变量名 变量类型)[返回值类型]{
....
}//变量可以写多个,变量和返回值均可为空
6.1.2 多返回值
func calc(a, b int) (int, int) {
sum := a + b
sub := a - b
return sum, sub
}
6.1.3 对返回值进行命名
func calc(a, b int) (sum int, sub int) {
sum = a + b
sub = a - b
return
}
func main() {
sum, sub := calc(1, 2)
fmt.Printf("sum = %d ,sub = %d \n", sum, sub)
}
6.1.4 可变参数
func calc1(a ...int) int {
sum1 := 0
for i := 0; i < len(a); i++ {
sum1 = sum1 + a[i]
}
return sum1
}
sum1 := calc1(1, 2, 3)
//使用a...可以传入多个参数,为list
6.1.5 defer语句
defer主要用于函数内,用于定义一条语句(主要为函数调用或匿名函数),defer定义的语句并不会立即执行,在函数返回前执行。适合做资源释放。
多个defer语句的执行顺序为先进后出
6.1.6 函数类型变量
函数也是一种类型,因此可以定义一个函数类型的变量
funcadd(a,bint)int{
sum:=a+b
returnsum
}
functestFunc1(){
f1:=add
sum:=f1(1,2)
fmt.Printf("%d\n",sum)//3
fmt.Printf("type=%t\n",f1)//type=%!t(func(int,int)int=0x7fdac0)
}
6.1.7 函数作为参数
funccalc(a,bint,opfunc(int,int)int)int{
returnop(a,b)
}
6.2 内置函数
close:主要用来关闭channel len:用来求长度,比如string、array、slice、map、channel new:用来分配内存,主要用来分配类型,比如int、struct,返回的是指针 make:用来分配内存,主要用来分配引用类型,比如chan、map、slice append:用来追加元素到数组、slice中 panic和recover:用来做错误处理
6.3 匿名函数
匿名函数,即是没有名字的函数
functestFunc2(){
f1:=func(a,bint)int{
returna+b
}
fmt.Printf("f1res=%d\n",f1(1,2))
}
6.3.1 defer使用匿名函数
functestFunc3(){
variint=0
deferfmt.Printf("deferi%d\n",i)
deferfunc(){
fmt.Printf("deferfunci%d\n",i)
}()
i=100
fmt.Println("i%d",i)
return
}
//i%d100
//deferfunci100
//deferi0
6.4 init函数
一个包里可以有多个init函数,在程序启动时会自动调用
先执行全局变量初始化,然后执行init函数,然后执行main函数
6.5 闭包
闭包:一个函数和与其相关的引用环境组合而成的实体
packagemain
import"fmt"
funcAdder()func(int)int{
varxint
returnfunc(dint)int{
x+=d
returnx
}
}
funcmain(){
varf=Adder()//此时f返回为函数,函数中x的值为0
fmt.Println(f(1))//调用函数,x的值变为1
fmt.Println(f(20))//调用函数,x的值变为21
fmt.Println(f(30))//调用函数,x的值变为51
}
由于var f=Adder(),只要在f的生命周期内,函数Adder()内的变量x就不会销毁
6.5.1 闭包的缺陷
functestClosure(){
fori:=0;i<5;i++{
gofunc(){
fmt.Println(i)
}()
}
time.Sleep(time.Second)
}
funcmain(){
testClosure()
}
输出全部为5,多线程在调用输出时,i为闭包成员变量,加入延时才可修改i的值
6.6 方法
6.6.1 方法的定义
和其他语言不一样,go的方法采用另外一种方式实现
go的方法是在函数前面加上一个接收者,这也编译器就知道这个方法属于哪个类了
packagemain
import"fmt"
typePeoplestruct{
Namestring
Countyrstring
}
func(pPeople)Print(){
fmt.Printf("name=%scountry=%s\n",p.Name,p.Countyr)
}
funcmain(){
varpPeople=People{
Name:"zhangsan",
Countyr:"china",
}
p.Print()
}
可以为当前包内的任何类型增加方法 函数不属于任何类型,方法属于特定类型
6.6.2 值类型和指针类型
packagemain
import"fmt"
typePeoplestruct{
Namestring
Countyrstring
}
func(pPeople)Print(){
fmt.Printf("name=%scountry=%s\n",p.Name,p.Countyr)
}
func(p*People)edit(){
p.Name="aaa"
p.Countyr="bbb"
}
funcmain(){
varpPeople=People{
Name:"zhangsan",
Countyr:"china",
}
p.Print()
p.edit()
p.Print()
}
指针传参,修改的是实例
值传参,修改的是实例的拷贝
❝什么时候使用值类型/指针类型作为接收者
❞
需要修改接收者中的值 接收者是大对象的时候,拷贝副本的代价比较大 一般来说,通常使用指针类型作为接收者
0x07基础介绍--面向对象编程
7.1 匿名字段,面向对象继承
packagemain
import"fmt"
typeAnimalstruct{
Namestring
Sexstring
}
func(a*Animal)Talk(){
fmt.Printf("iam%s\n",a.Name)
}
typeDogstruct{
Feetstring
*Animal
}
func(d*Dog)Eat(){
fmt.Println("dogiseat")
}
funcmain(){
vard*Dog=&Dog{
Feet:"4feet",
Animal:&Animal{
Name:"dog",
Sex:"xiong",
},
}
d.Talk()
d.Eat()
}
7.2 结构体和json序列化
packagemain
import(
"encoding/json"
"fmt"
)
typeStudentstruct{
Namestring
Sexstring
Idint
}
typeclassstruct{
Class_namestring
Students[]*Student
}
funcmain(){
c:=&class{
Class_name:"101",
}
fori:=0;i<10;i++{
stu:=&Student{
Name:fmt.Sprintf("stu%d",i),
Sex:"man",
Id:i,
}
c.Students=append(c.Students,stu)
}
data,err:=json.Marshal(c)
iferr!=nil{
fmt.Println("jsonmarshalfailde")
return
}
fmt.Printf("json:%s\n",string(data))
}
相关资源