Go语言基础整理

发布于 2021-11-18 17:09 ,所属分类:软件编程学习资料

学无止境

Book of the Year 2021

爱读书的人聚在一起,便是广厦千万间






读书的用,不在眼前,不在当下,而是像甘霖雨露滋润万物之后,万物所呈现的那种清新、新鲜、水灵时的生机勃勃,是看不见和摸不着的。”








0x01 语言特性










0x02 基础介绍--编译






2.1 项目目录结构

2.1.1 Go源码的组织方式

  1. Go通过package的方式来组织源码,并且package要放在非注释的第一行
  2. 每个项目入口为main文件,且只能有一个main文件
  3. 可执行程序的包名必须为main,并且包含一个main函数
  4. 除了可执行程序外,用户可以写自定义包,自定义包编译成静态库
  5. 任何一个源码都属于一个包
  6. 优势:代码复用和可读性

2.1.2 程序的基本结构

packagemain

import"fmt"

funcmain(){
fmt.Println("helloword")
}
  1. import 关键字,引入其他包
  2. 包中函数调用
    1. 同一个包中,直接用函数名调用
    2. 不同包中函数,通过包名.函数名进行调用
  3. 包访问控制规则
    1. 大写意味着这个函数/变量可以导出
    2. 小写以为着这个函数/变量是私有的,包外部不能访问

2.1.3 编译命令


go run(编译并运行)

  1. go run不会在运行目录下生成任何文件,可执行文件被放在临时文件中被执行,工作目录被设置为当前目录。在go run的后部可以添加参数,这部分参数会作为代码可以接受的命令行输入提供给程序。
  2. go run不能使用“go run+包”的方式进行编译,如需快速编译运行包,需要使用go build
  3. go run 在执行时,如果有多个文件,需要把所有文件都写在go run后面

go build(编译)

  1. go build 编译go代码,如果是可执行程序,默认会在当前目录下生成可执行程序,可以使用-o指定可执行程序生成的目录
  2. go build 无参数编译:如果源码中没有依赖 GOPATH 的包引用,那么这些源码可以使用无参数 go build
  3. go build + 文件列表:编译同目录的多个源码文件时,可以在 go build 的后面提供多个文件名,go build 会编译这些源码,输出可执行文件,默认选择文件列表中第一个源码文件作为可执行文件名输出。
  4. go build + 包:在设置 GOPATH 后,可以直接根据包名进行编译,即便包内文件被增(加)删(除)也不影响编译指令。

go build 还有一些附加参数,可以显示更多的编译信息和更多的操作,详见下表所示。

附加参数备 注
-v编译时显示包名
-p n开启并发编译,默认情况下该值为 CPU 逻辑核数
-a强制重新构建
-n打印编译时会用到的所有命令,但不真正执行
-x打印编译时会用到的所有命令
-race开启竞态检测

go install(编译)

go install 的编译过程有如下规律:

  1. go install 是建立在 GOPATH 上的,无法在独立的目录里使用 go install。
  2. GOPATH 下的 bin 目录放置的是使用 go install 生成的可执行文件,可执行文件的名称来自于编译时的包名。
  3. go install 输出目录始终为 GOPATH 下的 bin 目录,无法使用-o附加参数进行自定义。
  4. GOPATH 下的 pkg 目录放置的是编译期间的中间文件。
  5. go install编译go代码,并且把可执行程序拷贝到GOPATH的bin目录,自定义或第三方包会拷贝到GOPATH的pkg目录

go clean(清除编译文件)

go clean命令可以移除当前源码包和关联源码包里面编译生成的文件,这些文件包括以下几种:

  1. 执行go build命令时在当前目录下生成的与包名或者 Go 源码文件同名的可执行文件。在 Windows 下,则是与包名或者 Go 源码文件同名且带有“.exe”后缀的文件。
  2. 执行go test命令并加入-c标记时在当前目录下生成的以包名加“.test”后缀为名的文件。在 Windows 下,则是以包名加“.test.exe”后缀的文件。
  3. 执行go install命令安装当前代码包时产生的结果文件。如果当前代码包中只包含库源码文件,则结果文件指的就是在工作区 pkg 目录下相应的归档文件。如果当前代码包中只包含一个命令源码文件,则结果文件指的就是在工作区 bin 目录下的可执行文件。
  4. 在编译 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 _标识符

  1. 导入某个包,但是没有引用该包的任何内容,只是想要该包的init函数执行,在导入包名前加_.
  2. 使用变量接收返回多值的函数时,,
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语言关键字

breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthoughifrangetype
continueforimportreturnvar


3.3 变量常量


3.3.1 变量

变量的作用域

  1. 全局变量,在程序整个生命周期有效
  2. 局部变量,在当前函数内部有效
  3. 局部变量和全局变量重名,采用就近原则


3.3.2 常量

3.4 简单数据类型

3.4.1 字符串原理解析

  1. 字符串底层就是一个byte数组,所以可以和[]byte类型互相转换
  2. 字符串之中的字符是不能修改的,需要先将字符串转成[]byte类型,然后对byte类型修改,然后转回去
  3. 字符串是由byte字节组成,所以字符串的长度是byte字节的长度
  4. rune类型用来表示utf8字符,一个rune字符由1个或多个byte组成

3.4.2 时间类型---time包

  1. time.Time类型,用来表示时间
  2. time.NOW(),获取当前时间
  3. time.NOW().Day(),获取天
  4. time.NOW().Minute(),获取当前分钟
  5. time.NOW().Mouth(),获取当前月
  6. time.NOW().Year(),获取当前年
  7. fmt.printf("%02d-%02d-%02d %02d:%02d:%02d",year,mouth,day,hour,minute,send),格式化输出
  8. time.NOW().Format("2006/01/02 15:04:05") //go语言诞生时间
  9. time.NOW().Unix(),获取当前时间戳
  10. 时间戳转Time类型 ,time.Unix()
  11. time.Duration用来表示纳秒
  12. 定时器的简单使用
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三者之间的区别

  1. fmt.println:可以输出字符串、变量(默认换行)
  2. fmt.print:可以输出字符串、变量(无换行)
  3. 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 切片的基本操作

  1. arr[start:end],创建一个从start到end-1的切片
  2. arr[start:],创建一个从start到结尾的切片
  3. arr[:end],创建一个从头到end-1的切片
  4. 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 指针类型和值类型

  1. 普通变量都是值类型,普通变量存储的是对应类型的值
  2. 切片是指针类型
  3. 指针类型存储的是一个地址,又叫引用类型,指针本身也有变量地址
  4. 指针初始值为nil
  5. 指针类型定义:var a *int = &b
  6. 指针传参:与正常变量传参一样

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的区别

  1. make为内建类型slice、map和channel分配内存
  2. new用于分配除引用类型的所有其他类型的内存,如int、数组等。
  3. 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 内置函数

  1. close:主要用来关闭channel
  2. len:用来求长度,比如string、array、slice、map、channel
  3. new:用来分配内存,主要用来分配类型,比如int、struct,返回的是指针
  4. make:用来分配内存,主要用来分配引用类型,比如chan、map、slice
  5. append:用来追加元素到数组、slice中
  6. 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()
}
  1. 可以为当前包内的任何类型增加方法
  2. 函数不属于任何类型,方法属于特定类型

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()
}

指针传参,修改的是实例
值传参,修改的是实例的拷贝

什么时候使用值类型/指针类型作为接收者

  1. 需要修改接收者中的值
  2. 接收者是大对象的时候,拷贝副本的代价比较大
  3. 一般来说,通常使用指针类型作为接收者





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


END








龟速更新·催稿无效







相关资源