Chapter 01
-
编译器是全文翻译,解释器是实时翻译。
1.环境搭建
1.1 MAC&Linux
-
下载编译器:
-
MAC:在golang.google.cn中点击Download Go,选择Apple macOS即可,默认安装路径是:/usr/local/go/。
-
Linux:
-
在命令窗口中输入”wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz“.
-
解压到/usr/local目录”tar-xvf go1.14.2.linux-amd64.tar.gz -c /usr/local“
-
-
-
个人配置
-
创建一个任意目录用于存放你写的代码,在目录下创建三个文件夹“bin”、“pkg”、“src”。
-
bin存放编译后的可执行文件、pkg存放包文件、src存放你的项目。
-
-
配置环境变量
vim-/.bash_profile//进入后将环境变量写入即可 //环境变量 1. go编译器路径:export PATH=/usr/local/go/bin;$PATH 2. go安装路径:export GOROOT=/usr/local/go 3. 自己创建的路径:export GOPATH=/users/golangProjects 4. 自己创建路径下的bin目录:export GOBIN=/users/golangProjects/bin
-
查看版本
-
go,进入go
-
go version,显示go的版本
-
-
-
测试
cd $GOPATH //快速进入主文件夹 cd src/ ls //查看当前文件夹 mkdir crm //创建project touch app.go //创建.go文件 pwd//查看当前路线 vim app.go//打开app.go文件 """ package main import "fmt" func main(){//创建函数 fmt.Println("holle world!")//调用fmt的println函数 } """ go run app.go //运行程序 编译分为三种: 1.{ go build//编译 ./crm //执行程序文件 go build -o xx//可编译时修改文件名 //本质是先执行go build 后保存在一个零时文件夹中,然后执行。 } 2.go install、go run //2&3一样都是先执行go build后,再把文件保存在相应文件中。
1.2 Windows
-
下载go编译器去官网下,安装编译器后选好目录并创建(注意那三个文件夹也需要创建)。
-
设置环境变量:
PATH=c:/GO/bin; GOROOT=c:/GO GOPATH=e:/project/go GOBIN=e:/project/go/bin
-
开发工具:Goland,IDE(集成开发环境)
2.基础学习
2.1 初识包管理
-
一个文件夹可以称为一个包,在文件夹中可以创建多个文件。在同一个包下的每个为文件中必须指定相同包名称。
-
包分为程序包和模块包:
-
程序包的
package
是main
,程序是需要主程序运行的,所以main就需要一个主函数,这个函数只能有一个。package main//主包名 func main(){//程序主函数入口 }
-
程序包中可以创建子包,子包的name与子包的package name相同。同目录下创建的文件可认为是子文件夹的仓库。
-
主程序调用同级目录下的文件函数时,可直接写函数名加括号,无需写文件名或路径。
-
主程序调用子级目录下的文件函数时,需要在
import
中写入程序名/子包名
才可调用函数,package名加函数名加括号。package main import( "luggy/api" )
-
函数名首字母大写称为公共函数,其他文件可以调用。函数首字母小写称为私有函数,只能在自文件中使用。
-
-
模块包中的
package
是除main之外,都可以使用并且无需写main函数指定程序入口。
-
2.2 输出和输入
-
输入分为自带和模块,自带有两钟print和println。
-
模块也是go自带的模块“fmt”,其中有三种输出:print、println、printf。
package main import"fmt" func main(){ Print("baba")//print不带换行 Println("baba")//println带换行 fmt.Print("baba")//print不带换行 fmt.Println("baba")//println带换行 //printf是格式化输出,其中占位符有%s字符、%d数字、%f是小数、%.2f是保留两位小数并四舍五入。 fmt.Printf("I'm your %s","dad") }
-
输入是计算机与人交互,输入由三种:Scan、Scanln、Scanf,这三种都返回两个值,count,err。
-
Scan是要求输入两个或多个时,必须输入相对应的数据,不然会一直等待,每个值用空格分割。
-
Scanln是不管输入几个,只要回车就结束输入状态。
-
Scanf是格式化输入,按照指定的字符串输入并提取数据,在要提取的数据后面加上空格才可以。
package main import"fmt" func main(){ var name,age string fmt.Println("aaa") //count,err := fmt.Scan(&name,&age) //count,err := fmt.Scanln(&name,&age) count,err := fmt.Scanf("我是你%s,今年%s"&name,&age) fmt.Println(name,count,age) }
-
这三种输入都有一个问题,不能还有空格。因此有一种方式解决。
package main import ( "bufio" "os" "fmt" ) func main(){ /* os.Stdin是正则输入,可以将输入的存储起来。 bufio.NewReader()是将输入的转为reader对象。 reader.ReadLine()是读取并返回三个参数。 isprefix=false是一次性读完 isprefix=True是未读完 */ reader:= bufio.NewReader(os.Stdin) Line,isprefix,err := reader.ReadLine() data:=string(Line) fmt.Println(isprefix,data,err) }
-
-
注释:单行注释用
//
,而多行注释用/**/
。 -
字符串格式化
package main import "fmt" func main(){ var a,b string fmt.Scan(&a,&b) fmt.Printf("我是你%s,永远都是你的%s",a,b) }
2.3 初识数据类型
-
整型(int)
-
整型是由10个基数组成的,可以进行运算。
-
-
字符串(string)
-
双引号引起来的字符、数字、符号都称为字符串,字符串之间可以相加。
-
-
布尔型(bool)
-
布尔型有两个状态
true
和false
,计算机中只有真和假。
-
2.4 变量和常量
-
变量是由字母、数字、下划线组成,不能以数字开头,不能使用关键字。
//变量分为定义和赋值,定义时不赋初值默认是int型默认值是0、string默认值是“”、bool默认值是false var age int//定义未赋初值,默认是0 var name string = "123"//定义并赋值 age = 12//赋值
-
起名时建议使用:见名知意、驼峰式。
-
变量简写
var name="alex" name := "alex" var name,age,data string var(//这种称为因式分解 name="alex" age="18" num int )
-
常量是保持不变的量,在局部和全局都可以使用,通过const定义并赋值。
package main main(){ const data = 999 const( pi=123 gender="boy" ) }
2.5 作用域和赋值及内存
-
作用域程序层次化,以大括号区分、上级不能使用子级变量,上级可以使用同级变量,下级可以使用上级变量。
-
全局变量是在入口外,不可以使用:=定义变量。
-
a="1234",b=a时b会复制a,而并不会指向同一个内存地址,在变量前加上&可以查看内存地址,当赋值时内存位置不变,但只会变。
-
注意:使用int、string、bool这三种数据类型时,如果遇到变量的赋值则会拷贝一份(赋值型)。
2.6 iota
-
iota可以在因式分解中使用,用来给多个常量或者变量赋连续的值,iota的作用是在需要定义连续值的时候使用。
package main main(){ //会自动给后面的变量赋值,值是连续的 const( v1 = iota //0 v2//1 v3//2 ) }
2.7 编码
-
ascii以8位表示,最高位用于表示正负。
-
Unicode分为ucs2(16位表示)和ucs4(32位表示)。
-
utf-8是Unicode的压缩,以码位范围选择模板。
3.语句和运算符
3.1条件结构
-
if语句可以用于单分支、双分支和多分枝。
package main import "fmt" func main(){ var number int fmt.Println("请输入数字") fmt.Scanln(&number) if number >0{ fmt.Println("你输入的数字是个正数") }else if number <0{ fmt.Println("你输入的数字是个负数") }else{ fmt.Println("你输入的是0") } }
-
switch也是多分支,使用表达式的结果,再于条件比较。
package main import "fmt" func main(){ var number int fmt.Println("请输入数字") fmt.Scanln(&number) switch true{ case number==1: fmt.Print("a") case number==2: fmt.Print("b") default://所有条件不满足时触发。 fmt.Print("c") } }
3.2 循环
-
for循环,循环是将一段代码重复执行,for循环默认是true。
for(){//不加条件会一直循环 循环体 } for(表达式1,表达式2,表达式3){//表达式1只有在第一次循环时执行,表达式2时条件,表达式3是每次循环都会执行。 循环体 }
-
continue是结束本次循环执行下次循环,break是结束循环。
3.3 跳跃
-
goto语句,跳跃是指从一个地方跳到一个地方。
package main import "fmt" func main(){ fmt.Println("a") goto A //A的跳跃点 fmt.Println("b") A: //A的降落点 fmt.Println("c") }
3.4 运算符
-
算数运算符:*、/、%、+、-、++、--
-
关系运算符:==、!=、<、>、<==、>==
-
逻辑运算:&&、||、!
-
位运算:&(与)、|(或)、^(异或)、<<(左移)、>>(右移)、&^(以前面的位基准,让前后的值比较,如果都是1,则将前面的值是1的改为0.)
-
赋值运算符:=、+=、-=、/=、%=、<<=、>>=、&=、^=、|=
-
运算符优先级:位运算高于算数运算高于关系运算高于逻辑运算。
4.数据类型
4.1 整型
-
go 中的整型分为有符号和无符号,有符号包含负号。
-
有符号整型分为int8、int16、int32、int64,计算方式是2的几次方。32位操作系统下是2的32次方,正负各占一半。
-
无符号整型相同64位操作系统下2的64次方个,正号全占。
-
整型的转换
package main import ( "fmt" "reflect" "strconv" ) func main(){ //1.整型之间的转换 var v1 int8 =12 var v2 int16 =23 v3 :=int16(v1)+v2 //这样可以将其直接转换 fmt.Println(v3) //2.整型与字符串之间的转换 result:=strconv.Itoa(16) //将整型转为字符串 fmt.Println(result,reflect.TypeOf(result)) //typeof是查看数据类型 result1,err:=strconv.Atoi("234") //将字符串转为整型,如果是无法转换的字符err会告诉你的。 fmt.Println(result1,reflect.TypeOf(result1),err) //typeof是查看数据类型 }
-
进制之间的转换
package main import( "strconv" "fmt" ) func main(){ //只有十进制是整型形式存在的,其他都是字符形式存在。 v1:=strconv.FormatInt(int64(64),16) //十进制转其他进制 fmt.Println(v1) //第一个参数是数据,第二个参数是数据的进制,第三个参数是要转成的进制 result,err:=strconv.ParseInt("123D",16,0) fmt.Println(result,err) }
-
常见的数学运算
package main import"math" func main(){ math.Abs(v1) //取绝对值 math.Floor(v1)//取最小整数 math.Ceil(v1) //取最大整数 math.Round(v1)//四舍五入 math.Mod(v1,v2) //取余数 math.Pow(v1,v2) //计算次方 math.Pow10(v2) //计算10的几次方 math.Max(v1,v2) //取最大值 math.Min(v1,v2) //取最小值 }
-
指针是用来存储内存地址的,主要用于节省内存空间;有两钟定义方式
-
var 变量 * int; //这种创建后默认值是“nil”,它们所存储的内存地址相同
-
变量:=new(int) //这种创建后默认值是“0”,它们会创建后并指向那块内存地址。
-
nil代表的是空,new用于创建内存并进行内部数据的初始化,而且返回一个指针类型。
-
2 超大整型
-
使用超大整型需要导入“math/big”模块,用big.int来定义。
import "math/big" func main(){ var v1 := new(big.Int) //也可以创建指针形式的超大整型 var v2 big.Int //定义超大整型 v2.SetInt64(12) //通过int64来赋值,最大不能超过2的64次方。 v2.SetSrting("2222",10) //通过字符串的形式来赋值的,并不受大小限制。第一个参数是数据,第二个参数是进制。 v3:=big.NewInt(21) //newint可以完成定义并赋值。 }
-
超大整型的基本加减乘除
v1 := big.NewInt(11) v2 := big.NewInt(21) result := new(big.Int) //加 result.Add(v1,v2) //减 result.Sub(v1,v2) //乘 result.Mul(v1,v2) //除 result.Div(v1,v2) //除,得商和余 miner := new(big.Int) //存余 result.DivMod(v1,v2,mider) //存商 var r1 big.Int r1.Add(&v1,&v2) //如果需要将其转换为指针类型,也可以使用变量前面加&的方式来实现。
4.2 浮点数
1.go自带
-
浮点数,在go中分为Float32(4个字节)和Float64(8个字节)。
package main import"fmt" func main(){ var v1 float32 //定义浮点型 var v2 float32 v1=0.1 v2=0.2 fmt.Println(v1+v2) }
-
float底层存储原理
-
先将浮点型数据转换位二进制,整数/2,小数*2。
-
科学计数法表示,就是首位为1,剩下的均为小数,用2的n次方来实现。
-
存储:以float32为例存储浮点型数据,32位会分为sign、exponent、fraction
-
sign,占最高位的1位字节来表示正负、0表示正数、1表示负数。
-
exponent,除最高一位占8位,共有256种但是正负(-127~128)。存储2的几次方数
-
fraction,存储所有小数数据。
-
float64:sign=1\exponent=11\fraction=52
-
-
2.第三方库
-
decimal需要在本地的Go环境中先安装再使用。第三方包源码地址:https://github.com/shopspring/decimal。
-
安装第三方包
go get github.com/shopspring/decimal
命令执行完成,在$GOPATH/src的目录下就会出现github/shopspring/decimal的目录,这就是第三方模块安排的位置。
-
使用decimal包
package main import( "fmt" "github.com/shopspring/decimal" ) func main(){ var v1 = decimal.NewFromFloat(0.000002) var v1 = decimal.NewFromFloat(0.00422) //加 var v3 = v1.Add(v2) //减 var v3 = v1.Sub(v2) //乘 var v3 = v1.Mul(v2) //除 var v3 = v1.Div(v2) fmt.Println(v3,v4,v5,v6) var price = decimal.NewFromFloat(2.345) var data1 = price.Round(1) //四舍五入,保留一位小数 var data2 = price.Truncate(1)//保留一位小数 fmt.Println(data1,data2) }
4.3 布尔类型
-
表示真假,一般是和条件等配合使用,用于满足某个条件时,执行每个操作。
package main import( "fmt" "strconv" ) func main(){ //字符串转布尔值 //true:"1","t","T","true","TRUE","True" //false:"0","f","F","false","FALSE","False" v1,err := strconv.ParseBool("t") fmt.Println(v1,err) //布尔值转字符串 v2 := strconv.FormatBool(false) fmt.Println(v2) }
4.4 字符串
-
在编写程序时,使用字符串来进行文本处理的。
var name string = "李永强" fmt.Println(name[0]) //这种索引得到的是一个字节 fmt.Println(len(name)) //这样可以查看里面有几个字节 //字符串转为一个字节集合 byteSet := []byte(name) //转为字节集合 fmt.Println(byteSet) //有九个字节 //字节集合转为一个字符串 byteList := []byte{230,173,166,230,178,155,233,189,144} targetString := string(byteList) fmt.Println(targetString) //rune 将字符串转为Unicode字符集码点的集合 tempSet := []rune(name) fmt.Println(tempSet) //rune转换为字符串 runeList := []rune{27494,27803,40784} targetName := string(runeList) fmt.Println(targetName) //长度处理 runeLength := utf8.RuneCountInString(name) fmt.Println(runeLength) //处理后长度就变成了3
1.常用功能
-
获取长度
package main import ( "fmt" "unicode/utf8" ) func main(){ var name string = "李永强" fmt.Println(len(name)) //获取字节长度 fmt.Println(utf8.RuneCountInString(name)) //获取字符长度 }
-
是否以什么开头和结尾
package main import( "fmt" "strings" ) func main(){ name:="李永强" result := strings.HasPrefix(name,"李") //是否以什么字符串开头 fmt.Println(result) //输出逻辑值 result2 := strings.HasSuffix(name,"强") //是否以什么字符串结尾 fmt.Println(result1) }
-
是否包含
package main import( "fmt" "strings" ) func main(){ name := "我是你爸爸你相信吗" result:=strings.Contains(name,"爸爸") //判断爸爸是否在里面 fmt.Println(result) }
-
变大写和小写
package main import ( "fmt" "strings" ) func main(){ name := "liyongqiang" result := strings.ToUpper(name) result1 := strings.ToLower(name) fmt.Println(result,result1) }
-
去两边
package main import( "fmt" "strings" ) func main(){ name:="liyongqiang" result1 := strings.TrimRight(name,"qiang") result2 := strings.TrimLeft(name,"li") result3 := strings.Trim(name,"l") }
-
替换
package main import( "fmt" "strings" ) func main(){ name:="leyongqiang" //第一个参数是数据,第二个参数是要替换的,第三个是替换成的,第四个是替换几个(-1代表全部) result1 := strings.Replace(name,"e","i",1) result2 := strings.Replace(name,"e","i",-1) }
-
分割
package main import( "fmt" "strings" ) func main(){ name:="aaaaaaavaaaaaa" result:=strings.Split(name,"v") fmt.Println(result) }
-
拼接
package main import( "bytes" "fmt" "strings" ) func main(){ //不建议 message := "aa"+"bb" fmt.Print(message) //建议 dataList:= []string{"aa","bb"} result := strings.Join(dataList,"_") fmt.Println(result) //建议(1.10之前) var buffer bytes.Buffer buffer.WriteString("aa") buffer.WriteString("bb") data := buffer.String() fmt.Println(data) //建议(1.10之后) var builder strings.Builder builder.WriteSering("aa") builder.WriteSering("bb") value := builder.String() fmt.Println(value) }
-
string和int互转
package main import ( "fmt" "strconv" ) func main(){ num := "666" var result = strconv.Itoa(888) //转为string fmt.Println(result) //内部调用的就是ParseInt var data,_ = strconv.Atoi(num) //转为int fmt.Println(data) }
-
字符串与字节集合和rune集合
package main import ( "fmt" "strconv" "unicode/utf8" ) func main(){ var name string ="李永强" //字符串转字节集合 byteSet := []byte(name) fmt.Println(byteSet) //字节集合转字符串 byteList := []byte{230,173,166,230,178,155,233,189,144}) targetString := string(byteList) fmt.Println(targetString) //rune 将字符串转为Unicode字符集码点的集合 tempSet := []rune(name) fmt.Println(tempSet) //rune转换为字符串 runeList := []rune{27494,27803,40784} targetName := string(runeList) fmt.Println(targetName) }
-
string和字符
package main import( "fmt" "unicode/utf8" ) func main(){ //数字转字符串 v1 := string(65) fmt.Println(v1) //字符串转数字 v2,size := utf8.DecodeRuneInString("A") fmt.Println(v2,size) }
2.索引切片
-
只能操作字符串,其他类型不可以的
package main import "fmt" func main(){ var name string = "李永强" //1.索引获取字节 v1 := name[0] fmt.Println(v1) //2.切片获取字节区间 v2 := name[0:3] fmt.Println(v2) //3.手动循环获取所有字节 for i:=0;i<len(anme); i++{ fmt.Println(i,name[i]) } //4.for range循环获取所有字节 for index,item := range name{ fmt.Println(index,item) } //5.转成rune集合 dataList := []rune(name) fmt.Println(dataList[0],string(dataList[0])) }
4.5 数组
-
数组是由定长且元素类型一致的数据集合。
//方式一:先声明再赋值(声明时内存中已开辟空间,内存初始化的值是0) var number [3]int number[1] =34 //方式二:声明并赋值 var names = [2] string{"李永强","吴佩琦"} //方式三:声明并赋值加指定位置 var ages = [3]int{0:23,1:34,2:12} //方式四:省略个数 var name = []string{"吴佩琦","李永强"} //声明指针型的数组,不会开辟内存初始值数组的值是nil var number*[3]int //声明数组并初始化,返回的是指针类型数组 number := new([2]int)
-
数组不仅有一维数组还有,二维和多维
package main func main(){ name :=[][2]string{"aa","bb","cc"} name :=[2][2]string{"aa","bb","cc"} }
-
数组的内存地址是连续的,数组的内存地址实际上就是第一个元素的内存地址,每个字符串的内部存储:len+str。
type stringStruct struct { str unsafe.Pointer len int }
-
可变和拷贝
-
可变,是指数组的大小不变而值可变。
-
将一个变量赋予另一个变量时会复制一份的
-
-
索引切片循环
package main import "fmt" func main(){ //1.长度 name := [2]string{"aa","bb"} fmt.Println(len(name)) //2.切片 name := [2]string{"aa","bb","cc"} v2 := name[0:1] fmt.Println(v2) //3.循环 name := [2]string{"aa","bb","cc"} for i:=0;i<len(name); i++{ fmt.Println(i,name[i]) } //4.for range循环 name := [2]string{"aa","bb","cc"} for index,item := range name{ fmt.Println(index,item) } }
4.6 切片
-
切片也被称为动态数组,切片是Go中重要的数据类型,每个切片对象内部都维护着:数组指针、切片长度、切片容量三个数据。
-
在向切片中追加的数据个数大于容量时,内部会自动扩容且每次扩容都是当前容量的2倍(当容量超过1024时每次扩容则指增加4分之一的容量)。
-
创建切片
//创建切片 var nums []int var data = []int{11,22,33} //make只用于 切片、字典、channel //第二个参数是初始化个数,第三个参数是声明个数 var users = make([]int,1,3) //创建切片(指针型) var v1 =new([]int) //只声明并不初始化 var v2*[]int //声明后,初始化指向nil
-
自动扩容
package main import "fmt" func main(){ v1 := make([]int,1,2) fmt.Println(len(v1),cap(v1)) v2 := append(v1,66) //v1和v2地址相同但是,因取值范围的限制v1只能取到【0】的值,v2可以取到【0:1】的值。 v3 := append(v2,77) //当扩容时,地址会发生变化,v2和v3的地址就不同,给v3修改时,v2中的值并不变化。 }
1.常用功能
-
长度和容量
v1 :=[]int{11,22,33} fmt.Println(len(v1),cap(v1))
-
索引和切片
v1 :=[]int{11,22,33} v1[0]//注意虽然定义了,但是没有初始化或赋值的,也不可以索引 v1[1] v1[2] v2 :=v1[0:1] v3 :=v1[:2] v4 :=v1[1:]
-
追加和删除
package main import "fmt" func main(){ v1 :=[]int{11,22,33,44,55,66} v2 :=append(v1,77) v3 :=append(v1,88,99) //使用另一个切片时,需要在后面加上三个点才可以使用 v4 :=append(v1,[]int{100,200,300}...) //go本身并没有删除,我们可以使用修改来实现 //它们本身还是同一个地址,使用v1输出时就会发现,最后一个值出现了两边。 deleteNum :=2 v5 :=append(v4[:deleteNum],v4[deleteNum+1:]...) }
-
循环
package main import "fmt" func main(){ v1 :=[]int{11,22,33,44,55,66} for i := 0;i<len(v1);i++{ fmt.Println(i,v1[i]) } for index,value := range v1{ fmt.Println(index,value) } }
4.7 字典类型(Map)
-
map是以“键:值”形式存储的数据类型,这种存储方式最大的有点就是查询速度快,由应为他底层是基于哈希表存储的。
-
以
取模+拉链法
大概了解一下。-
写好键值对后将键转为哈希值,再将哈希值 mod 4 后将结果按顺序存储(重复时并形存储)。
-
-
Map的特点是键不可以重复、键必须是可哈希的(int/bool/float/string/array)、Map是无序的。
1.声明和初始化
-
Map的键值对增加、修改使用的都是“变量[键]=值”,map不管扩不扩容,它们的内存地址永远相同。
//声明,声明时记得把大括号带上 UserInfo := map[string]string{} //声明并初始化 UserInfo := map[string]string{"name":"李永强","age":"18"} //查看 fmt.Println(UserInfo["name"]) for key,value := range data{ fmt.Println(key,value) } //当map中没有此键值对时是增加,有时是修改 UserInfo["age"]=19 UserInfo["gender"]="男" //删除,参数一是map,参数二是键 delete(UserInfo,"gender") //使用make,make定义时可以不加大括号 data :=make(map[string]int) data["a1"]=1 data["a2"]=2 //new,不能直接使用只能用于map转存,定义不使用new也不加大括号的用法相同。 value := new(map[string]int) value = &UserInfo //数组做键值对 v1 := make(map[2]int)int) v1[[2]int{1,2}]=2 v1[[2]int{1,4}]=2 ... //代表任意类型 //map嵌套就那么个,简单的很
2.原理解析
-
map创建后,每个键值对会有count计数并创建桶(2**B),每个桶中可存放8个(tophash哈希值、keys、values、overflow指针)。
-
初始化
-
创建一个hmap结构体对象,生成一个哈希因子hash0并赋值到hmap对象中(用于后续为key创建哈希值)。
-
根据hint=10,并根据算法规则来创建B,根据B去创建桶(bmap对象)并存放在buckets数组中。
-
当B小于4时,2^B(标准桶)
-
当B大于4时,2^B+2^(B-4)(标准桶+溢出桶)
-
注意:每个bmap中可以存储8个键值对,当不够存储时需要使用溢出桶,并将当前bmap中的overflow字节指向溢出桶的位置。
-
-
4.8 指针
-
指针相当于创建了一个地址的引用,以后根据这个引用再去获取他里面的值。
-
取指针的指针时,需要设置好相应的*才可以。
name := "wupeiqi" var a1 *string = &name var a2 **string = &a1 var a3 ***string = &a2 //一定要把*号写够,*号代表指针的引用层数,如果只写了一个*就代表直接储存name的内存地址,而写够后a3储存a2的内存地址。
-
计算指针
package main import ( "fmt" "unsafe" ) func main(){ dataList := [3]int8{11,22,33} //1.获取数组第一个元素的地址(指针) var firstDataPtr *int8 = &dataList[0] //2.转换成Pointer类型 ptr := unsafe.Pointer(firstDataPtr) //3.转换成uintptr类型,然后进行内存地址的计算 targetAddress :=uintptr(ptr)+1 //4.根据新地址,重新转成Pointer类型 newPtr := unsafe.Pointer(targetAddress) //5.Pointer对象转换为int8指针类型 value :=(*int8)(newPtr) //6.根据指针获取值 fmt.Println("最终结果为",*value) }
4.9 结构体
-
结构体时一个复合类型,用于表示一组数据,结构体有一系列属性组成,每个属性都有自己的里类型和值。
-
定义
type 结构体名称1 struct { 字段 类型 标签 //标签可加可不加,主要用于提示。 } type 结构体名称2 struct { 字段 类型 标签 字段 结构体名称1 //这种是结构体嵌套 结构体名称1 //这种也是结构体嵌套,但是结构体名称1也是结构体名称2中的字段。也称匿名字段 }
-
初始化
//可以按位置用逗号分割,给赋予不同类型的数据。 var 结构体对象 = 结构体名称2{"数据",17} 结构体对象.字段 //这样可以获取对应的值。 //定义一个结构体 type ageN struct { age int } type genderS struct { gender string } type Person struct { name string hobby []string agen ageN genderS } //方式一:先后顺序 var p1 = Person{"liyongqiang",[]string{"computer"},ageN{12},genderS{"男"}} fmt.Println(p1.name,p1.hobby,p1.agen.age,p1.gender) //方式二:关键字 var p2 = Person{name:"liyongqiang",hobby:[]string{"computer"},agen:ageN{age:12},genderS:genderS{gender:"男"}} fmt.Println(p2.name,p2.hobby,p2.agen.age,p2.gender,p2.genderS.gender) //方式三:先声明再赋值 var p3 Person p3.name = "liyongqiang" p3.hobby = []string{"computer"} p3.agen = ageN{age:12} p3.genderS = genderS{gender:"男"} p3.gender = "男"
-
结构体指针
type Person struct { name string } //初始化结构体指针 p1 := &Person{"liyongqiang"} p2 := *Person = new(Person) p2.name = "liyongqiang"
-
标签的查询方式
package main import ( "fmt" "reflect" ) func main(){ type Person struct{ name string "姓名" age int32 "年龄" } p1 := Person{name:"wupeiqi",age:18} p1Tyep := reflect.TypeOf(p1) //top1 filed1 := p1Type.Field(0) fmt.Println(filed1.Tag) //top2 filed2,_:= p1Type.FieldByName("name") fmt.Println(filed2.Tag) //循环获取 fieldNum :=p1Type.NumField() for index := 0;index<fieldNum;index++{ field:=p1Type.Field(index) fmt.Pringln(field.Name,field.Tag) } }
4.10 函数
-
函数的基本写法
func 函数名(参数名 参数类型)返回值类型{ 代码块 return 返回值 } 变量 :=函数名(参数值)
-
函数做参数
package main import "fmt" func add100(arg int)(int,bool){ return arg + 100,true } //这种也可以称为起别名。 type f1 func(arg int)(int,bool) func proxy(data int,exec func(int) (int,bool)或f1) int{ data,flag := exec(data) if flag { return data }else{ return 9999 } } func main(){ fesult := proxy(123,add100) fmt.Println(result) }
-
接收多个参数
package main import "fmt" //可接受多个int类型的参数, func do(num...int) int{ fmt.Println(num) return sum } func main(){ r1:=do(1,4,5,6) fmt.Println(result) }
-
函数做返回值
package main import "fmt" func add100(arg int)(int,bool){ return arg + 100,true } type f1 func(arg int)(int,bool) func proxy(data int) func(int) (int,bool)或f1{ pass return add100 } func main(){ fesult := proxy(123,add100) result := fesult(145) fmt.Println(result) }
-
匿名函数
package main import "fmt" func F1 () func(int) string{ return func()string{ return "baba" } } func main(){ v1 := func() int { return 123 } //自执行 value := func() int { return 123 }() }
-
闭包
package main import "fmt" func main(){ var functionList []func() for i := 0;i<5;i++{ function := func(arg int)func(){ return func(){ fmt.Println(arg) } }(i) functionList = append(functionList,function) } functionList[0]() functionList[1]() functionList[2]() }
-
defer
package main import "fmt" func do() int{ fmt.Print(1) //defer的效果是延迟执行,函数执行完之后执行。 defer fmt.Print(2) defer fmt.Print(3) fmt.Print(4) return 666 } func main(){ ret:=do() fmt.Println(ret) }
Type自定义类型,就那么个用法,自己用就行