数据类型
整型取值范围
var n int8 n=100 fmt.Println(n) //100 没有问题 //如果赋值为200 则不行 因为int8取值范围最大是127
字符串
v1 := 'A' v2 := "A"
//单引号存储的是 ASCII编码 //A的ASCII值=65 //B的ASCII值B=66 //a的ASCII值a=97
fmt.Printf("v1的类型是%T,%d,值为%s\n", v1, v1) // int32 A 65
fmt.Printf("v2的类型是%T,%s\n", v2, v2) //string A
//字符串的长度 //中文占3个字节
fmt.Println(len(v2)) //1
//获取某个字节
s2 := `hello go`
fmt.Println(s2[0]) //104
占位符号
字符转义
双引号单引号如果作为字符串输出,需要使用\"
进行转义后才能使用 \n \r \t 为特殊作用字符。
//字符转义空格
fmt.Println("hello \t golang")//hello golang
//输出换行 \n
//回车符号 \r
//输出路径
fmt.Println("E:\\golang\\go.jpg")//E:\golang\go.jpg
//双引号
fmt.Println("hello \"golang\"") //hello "golang"
布尔
//布尔类型作为条件比较返回结果只有true和false两种
a:=10
fmt.Println(a==2) //false
fmt.Println(a!=2) //true
浮点型
Go语言有两种精度的浮点数 float32 和 float64。浮点类型的数据取值范围可以从很小或者很巨大.
1.79E-308
是 1.79 乘以 10的负308次方。 1.79E+308
是 1.79 乘以 10的308次方。
单精度双精度两者区别
在内存中占有的字节数不同
- 单精度浮点数在机内占4个字节。
- 双精度浮点数在机内占8个字节。
有效数字位数不同
- 单精度浮点数 有效数字7位。
- 双精度浮点数 有效数字16位。
使用情况区别
- 一般用来表示美元和分的时候用单精度类型。
- 超出人类经验的数字函数,例如 sin() cos() tan() sqrt() 都使用双精度值。
数据类型的转换
var a int8 = 10 var b int16 //b=a 因为类型不同不能直接转换 b= int16(a) fmt.Println(a,b)//10,10f1:=3.12 var c int c = int(f1) fmt.Println(f1,c)// 3.12 , 3 浮点类型转为整形的时候 只是取了整数部分 //不是所有类型都能互相转换 //数值类型的可以转换 int 和bool 不能转换
程序控制结构
数组for 循环语句
arr := [5]int{1,2,3,4,5} //for循环 for i:=0; i<len(arr);i++{ fmt.Println(arr[i]) }
for循环中的break 和continue
break 用来终止执行for循环语句,终止整个循环。continue 终止当前循环的迭代,重新进入下一条件,进入循环。+
goto语句
//语法
lable:func1
...
goto label
冒泡排序
package main import "fmt" func main() { values := []int{4, 3, 14, 85, 34, 27, 91, 95, 26,12,32} fmt.Println(values) BubblingASC(values)//正序冒泡 BubblingDESC(values)//倒序冒泡 } //冒泡排序 正序,大的靠后 小的靠前。 func BubblingASC(values []int) { for i := 0; i < len(values)-1; i++ { for j := i+1; j < len(values); j++ { if values[i]>values[j]{ //左右两边数据对比 values[i],values[j] = values[j],values[i] //数据交换 } } } fmt.Println(values) } //冒泡排序 倒序, 大的靠前 小的靠后。 func BubblingDESC(values []int) { for i := 0; i < len(values)-1; i++ { for j := i+1; j < len(values); j++ { if values[i]<values[j]{ //左右两边数据对比 values[i],values[j] = values[j],values[i] //数据交换 } } } fmt.Println(values) }
return 返回
package main import "fmt" func main() { a, b, c := myfunc() fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c) } func myfunc() (a, b, c int) { a, b, c = 111, 222, 333 return }
切片Slice
切片也是一种存储相同类型的数据结构,但是不同于数组的是它的大小可以改变,如果长度不够可以自动扩充。
//声明一个切片slice var slice []int //使用make函数创建切片 s1:=make([]int,0,5) fmt.Println(s1)// [] 打印空的切片 s1=append(s1,1,2) fmt.Println(s1)// [1,2] //因为切片可以扩容 所以定义容量为5 但是可以加无数个数值 s1=append(s1,3,4,5,6,7) fmt.Println(s1)// [1,2,3,4,5,6,7] //添加一组切片到另一切片中 s2:=make([]int,0,3) s2=append(s2,s1...) //...表示将另一个切片数组完整加入到当前切片中
make()与new() 的区别
make()是Go语言中的内置函数,主要用于创建并初始化slice切片类型,或者map字典类型,或者channel通道类型数据。他与new方法的区别是。new用于各种数据类型的内存分配,在Go语言中认为他返回的是一个指针。指向的是一个某种类型的零值。make 返回的是一个有着初始值的非零值。
//测试使用new方法新建切片 slice1 := new([]int) fmt.Println(slice1) //输出的是一个地址 &[] //使用make创建切片 slice2 := make([]int, 5) fmt.Println(slice2)//输出初始值都为0的数组, [0 0 0 0 0] fmt.Println(slice1[0])//结果出错 slice1是一个空指针 invalid operation: slice1[0] (type *[]int does not support indexing) fmt.Println(slice2[0])//结果为 0 因为已经初始化了
切片是如何扩容的
package main import ( "fmt" ) func main() { s1 := make([]int, 0, 3) fmt.Printf("地址%p,长度%d,容量%d\n", s1, len(s1), cap(s1)) s1 = append(s1, 1, 2) fmt.Printf("地址%p,长度%d,容量%d\n", s1, len(s1), cap(s1)) s1 = append(s1, 3, 4, 5) fmt.Printf("地址%p,长度%d,容量%d\n", s1, len(s1), cap(s1)) //地址0xc000010540,长度0,容量3 //地址0xc000010540,长度2,容量3 //地址0xc00000e4b0,长度5,容量6 //如果添加的数据容量够用, 地址则不变。 //如果实现了扩容, 地址就会发生改变成新的地址,旧的则自动销毁。 }
- 每一个切片都引用了一个底层数组。
- 切片本身不能存储任何数据,都是这底层数组存储数据,所以修改切片的时候修改的是底层数组中的数据。
- 当切片添加数据时候,如果没有超过容量,直接进行添加,如果超出容量自动扩容成倍增长。
- 切片一旦扩容,指向一个新的底层数组内存地址也就随之改变。
值传递与引用传递
- 基本类型:
int、float、string、bool
- 复合类型:
array、slice、map、struct、pointer、function、chan
- 值类型:
int、float、string、bool、array、struct
- 引用类型:
slice、pointer、map、chan
等都是引用类型。
引用传递因为存储的是内存地址,所以传递的时候则传递是内存地址,所以会出现多个变量引用同一个内存。
//数组为值传递类型 //定义一个数组 arr1 arr1 := [4]int{1, 2, 3, 4} arr2 := arr1 //将arr1的值赋给arr2 fmt.Println(arr1, arr2) //[1 2 3 4] [1 2 3 4] 输出结果 arr1与arr2相同, arr1[2] = 200 //修改arr1中下标为2的值 fmt.Println(arr1, arr2) //[1 2 200 4] [1 2 3 4] 结果arr1中结果改变,arr2中不影响 //说明只是将arr1中的值给了arr2 修改arr1中的值后并不影响arr2的值 //切片是引用类型 //定义一个切片 slice1 slice1 := []int{1, 2, 3, 4} slice2 := slice1 //将slice1的地址引用到slice2 fmt.Println(slice2, slice2) //[1 2 3 4] [1 2 3 4] slice1输出结果 slice2输出指向slice1的结果, slice1[2] = 200 //修改slice1中下标为2的值 fmt.Println(slice1, slice2) //[1 2 200 4] [1 2 200 4] 结果slice1中结果改变,因为修改的是同一份数据 //说明只是将slice1中的值给了slice2 修改slice1中的值后引用地址用的是同一份 slice1 和slice2 同时修改 fmt.Printf("%p,%p\n", slice1, slice2)//0xc000012520,0xc000012520 //切片引用的底层数组是同一个 所以值为一个地址 是引用的底层数组的地址 fmt.Printf("%p,%p\n", &slice1, &slice2)//0xc0000044a0,0xc0000044c0 //切片本身的地址
深拷贝和浅拷贝
深拷贝是指将值类型的数据进行拷贝的时候,拷贝的是数值本身,所以值类型的数据默认都是深拷贝。浅拷贝指的是拷贝的引用地址,修改拷贝过后的数据,原有的数据也被修改。 那么如何做到引用类型的深拷贝?也就是需要将引用类型的值进行拷贝。修改拷贝的值不会对原有的值造成影响。
1,使用range循环获取元素中的值 进行拷贝
//使用range循环将切片slice中的元素一个一个拷贝到切片s2中 slice := []int{1, 2, 3, 4} s2 := make([]int, 0) for _, v := range slice { s2 = append(s2, v) } fmt.Println(slice) //结果 [1 2 3 4] fmt.Println(s2) //结果 [1 2 3 4]
2,使用深拷贝数据函数: copy(目标切片,数据源)
//copy(目标切片,数据源) 深拷贝数据函数 s2 := []int{1, 2, 3, 4} s3 := []int{7, 8, 9} copy(s2, s3) //将s3拷贝到s2中 fmt.Println(s2) //结果 [7 8 9 4] fmt.Println(s3) //结果 [7 8 9] copy(s3, s2[2:]) //将s2中下标为2的位置 到结束的值 拷贝到s3中 fmt.Println(s2) //结果 [1 2 3 4] fmt.Println(s3) //结果 [3 4 9] copy(s3, s2) //将s2拷贝到s3中 fmt.Println(s2) //结果 [1 2 3 4] fmt.Println(s3) //结果 [1 2 3]
切片的删除
删除切片中元素的方法
//方法一 获取切片指定位置的值 重新赋值给当前切片 slice:=[]int{1,2,3,4} slice=slice[1:]//删除切片中开头1个元素 结果 [2,3,4] //方法二 使用append不会改变当前切片的内存地址 slice = append(slice[:0], slice[1:]...) // 删除开头1个元素 fmt.Println(slice)
删除指定的下标元素
slice:=[]int{1,2,3,4} i := 2 // 要删除的下标为2 slice = append(slice[:i], slice[i+1:]...) // 删除中间1个元素 fmt.Println(slice) //结果[1 2 4]
删除切片结尾的方法
slice := []int{1, 2, 3, 4} slice = slice[:len(slice)-2] // 删除最后2个元素 fmt.Println(slice) //结果 [1,2]
复合数据 map 键值对 key:value 类型的数据
var m1 map[int]string //只是声明 nil var m2 = make(map[int]string) //创建 m3 := map[string]int{"语文": 89, "数学": 23, "英语": 90} fmt.Println(m1 == nil) //true fmt.Println(m2 == nil) //false fmt.Println(m3 == nil) //false //map 为nil的时候不能使用 所以使用之前先判断是否为nil if m1 == nil { m1 = make(map[int]string) } //1存储键值对到map中 语法:map[key]=value m1[1]="小猪" m1[2]="小猫" //2获取map中的键值对 语法:map[key] val := m1[2] fmt.Println(val) //3判断key是否存在 语法:value,ok:=map[key] val, ok := m1[1] fmt.Println(val, ok) //结果返回两个值,一个是当前获取的key对应的val值。二是当前值否存在,会返回一个true或false。 //4修改map 如果不存在则添加, 如果存在直接修改原有数据。 m1[1] = "小狗" //5删除map中key对应的键值对数据 语法: delete(map, key) delete(m1, 1) //6 获取map中的总长度 len(map) fmt.Println(len(m1)) //map的遍历 因为map是无序的 如果需要获取map中所有的键值对 可以使用 for range map1 := make(map[int]string) map1[1] = "张无忌" map1[2] = "张三丰" map1[3] = "常遇春" map1[4] = "胡青牛" //遍历map for key, val := range map1 { fmt.Println(key, val) }
map结合Slice
//创建一个map存储第一个人的信息 map1 := make(map[string]string) map1["name"] = "张无忌" map1["sex"] = "男" map1["age"] = "21" map1["address"] = "明教" //如果需要存储第二个人的信息则需要重新创建map map2 := make(map[string]string) map2["name"] = "周芷若" map2["sex"] = "女" map2["age"] = "22" map2["address"] = "峨眉山" //将map存入切片 slice中 s1 := make([]map[string]string, 0, 2) s1 = append(s1, map1) s1 = append(s1, map2) //遍历map for key, val := range s1 { fmt.Println(key, val) }
sync.map的使用
package main import ( "fmt" "sync" ) //声明sync.Map var syncmap sync.Map func main() { //Store方法将键值对保存到sync.Map syncmap.Store("zhangsan", 97) syncmap.Store("lisi", 100) syncmap.Store("wangmazi", 200) // Load方法获取sync.Map 键所对应的值 fmt.Println(syncmap.Load("lisi")) // Delete方法键删除对应的键值对 syncmap.Delete("lisi") // Range遍历所有sync.Map中的键值对 syncmap.Range(func(k, v interface{}) bool { fmt.Println(k, v) return true }) }
包管理和常用包介绍
包的概念就是我们程序中的目录,我们所写的所有代码都放在包中在定义的时候用package定义包, 然后使用 import 引入包。Go语言提供了很多内置包,例如:fmt、strings、strconv、os、io 等等。
strings包
//是否包含指定的字符串中任意一个字符 有一个出现过 就返回true fmt.Println(strings.ContainsAny(s1,"glass")) //返回指定字符出现的次数 fmt.Println(strings.Count(s1,"g")) //文本的开头 fmt.Println(strings.HasPrefix(s1,"ok")) //文本的结尾 fmt.Println(strings.HasSuffix(s1,".txt")) //查找指定字符在字符串中存在的位置 如果不存在返回-1 fmt.Println(strings.Index(s1,"g")) //查找字符中任意一个字符出现在字符串中的位置 fmt.Println(strings.IndexAny(s1,"s")) //查找指定字符出现在字符串中最后一个的位置 fmt.Println(strings.LastIndex(s1,"s")) //字符串的拼接 s2:=[]string{"123n","abc","ss"} s3:=strings.Join(s2,"_") fmt.Println(s3)// 123n_abc_ss //字符串的切割 s4:=strings.Split(s3,"_") fmt.Println(s4)// 返回切片[]string{"123n","abc","ss"} //字符串的替换 s5 := "okoletsgo" s6 := strings.Replace(s5, "o", "*", 1) fmt.Println(s6)//*koletsgo //TODO 1 只替换1次, -1 全部替换 //字符串的截取 //str[start:end]包含start 不包含end
strconv包
主要用于字符串和基本类型的数据类型的转换
/str:="aa"+100 //字符串和整形数据不能放在一起 所以需要将100 整形转为字符串类型 //+号在字符串中表示字符串的连接 在整形中表示数据的计算 //string 转 bool类型 s1 := "true" //字符串 b, err := strconv.ParseBool(s1) if err != nil { fmt.Println(err) //打印错误信息 } fmt.Printf("%T,%t", b, b) //bool,true //string 转int s1 := "100" //字符串 b, err := strconv.ParseInt(s1, 10, 64) //10 表示s1要转的数据是10进制 64位 if err != nil { fmt.Println(err) //打印错误信息 } fmt.Printf("%T,%d", b, b) //int64,100 //整形转为字符串 s := strconv.Itoa(23) //字符串转为整形 i, e := strconv.Atoi(s)
time包
time包操作的都是时间,时间的单位都包括年,月,日,时,分,秒,毫秒,微妙,纳秒,皮秒。
package main import ( "fmt" "time" ) func main() { //获取当前时间 t := time.Now() fmt.Println(t) //2020-03-31 21:26:01.7307507 +0800 CST m=+0.001999001 //获取的时间后面的信息是时区 //上面的时间看起来不是很方便 于是需要格式化时间 s := t.Format("2006年1月2日 15:04:05") fmt.Println(s) }
需要注意的是Go语言中时间的格式化,需要指定格式化时间的模板, 不管年月日的类型格式怎么写,但是具体的数值必须写成2006-01-02 15:04:05
, 如果不是这个日期就不能够格式化,这个时间也是为了纪念Go语言诞生的时间。
s := t.Format("2006-1-2 15:04:05") fmt.Println(s) //打印出的格式就是当前的时间 2020-3-31 23:08:35 s := t.Format("2006/1/2") fmt.Println(s) //打印出的格式就是当前的年月日 2020/3/31 //字符串类型的时间 str := "2020年3月31日" //第一个参数是模板,第二个是要转换的时间字符串 s, _ := time.Parse("2006年1月2日", str) fmt.Println(s) //打印出的格式就是2020-03-31 00:00:00 +0000 UTC //获取年月日信息 year, month, day := time.Now().Date() fmt.Println(year, month, day) //2020 March 31 //获取时分秒信息 hour, minute, second := time.Now().Clock() fmt.Println(hour, minute, second) //23 23 54 //获取今年过了多少天了 tday := time.Now().YearDay() fmt.Println(tday) //91 (今年已经过了91天了) //获取今天是星期几 weekday := time.Now().Weekday() fmt.Println(weekday) //Tuesday
时间戳
时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数。它主要是为用户提供一份电子证据.
package main import ( "fmt" "time" ) func main() { //获取指定日期的时间戳 t := time.Date(2020, 3, 31, 23, 30, 0, 0, time.UTC) timestamp := t.Unix() fmt.Println(timestamp) //1585697400 //获取当前时间的时间戳 timestamp2 := time.Now().Unix() fmt.Println(timestamp2) //1585669151 //当前时间的以纳秒为单位的时间戳 timestamp3 := time.Now().UnixNano() fmt.Println(timestamp3) //1585669151296330900 } //时间间隔 相加 now := time.Now() //当前时间加上一分钟 t := now.Add(time.Minute) fmt.Println(now) //2020-03-31 23:43:35.0004791 +0800 CST m=+0.002999201 fmt.Println(t) //2020-03-31 23:44:35.0004791 +0800 CST m=+60.002999201 //计算两个时间的间隔 d := t.Sub(now) fmt.Println(d) //1m0s 相差一分钟
时间戳与时间格式互转
//将指定时间转为时间戳格式 beforetime := "2020-04-08 00:00:00" //待转化为时间戳的字符串 timeLayout := "2006-01-02 15:04:05" //转化所需模板 loc := time.Now().Location() //获取时区 theTime, _ := time.ParseInLocation(timeLayout, beforetime, loc) //使用模板在对应时区转化为time.time类型 aftertime := theTime.Unix() //转化为时间戳 类型是int64 fmt.Println(theTime) //打印输出theTime 2020-04-08 00:00:00 +0800 CST fmt.Println(aftertime) //打印输出时间戳 1586275200 //再将时间戳转换为日期 dataTimeStr := time.Unix(aftertime, 0).Format(timeLayout) //设置时间戳 使用模板格式化为日期字符串 fmt.Println(dataTimeStr)
dep管理方案
但是早期的Go语言被很多开发者所诟病的一个问题是依赖包的管理,在Golang1.5版本之前需要设置GOPATH来解决所有包依赖的问题,但是这样会有很多问题,如果我们两个项目引用的包版本不一致,而GOPATH中只有一个版本,就需要使用多个GOPATH来解决这样的问题,这样来回切换GOPATH是很不方便的。于是Go在1.9之后加入包管理方案解除了GOPATH的依赖。于是出现了dep
和glide
在项目中加入了vender
目录来存储所有项目中需要引入的包。
安装dep
MacOS brew install dep
Linux curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
Windows go get -u github.com/golang/dep/cmd/dep
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/64adf95bb54a40d89466c37751be51a2~tplv-k3u1fbpfcp-zoom-1.image)
使用 dep status
查看项目依赖的详细信息和状态。
dep ensure
查看所有的依赖库都已经安装,如果没有就去下载。 dep ensure add github.com/go-sql-driver/mysql
下载添加新的依赖库,并增量更新清单文件和校验描述文件。 dep不是每次都去下载,他会先在本地环境中找如果没有找到则会到网上下载并添加到本地仓库。
mod初始化
使用mod需要注意的是:
- 如果Go的版本太低不能使用,建议将Go的版本升级到最新。
- 环境变量中可以增加
GOPROXY=https://goproxy.io
这样没有梯子的情况下可以正确的加载相应的包文件。 - 环境变量
GO111MODULE
不要设置,如果已经增加了这个变量请务必设置为GO111MODULE=auto
。 - 在项目的根目录下使用命令
go mod init projectName
。
执行上面的命令之后,就已经可以开发编译运行此项目了。他已经自动引用了项目中所有的包文件。
函数
1,大写首字母函数共用,小写私用
匿名函数
定义一个匿名函数直接加上()就相当于直接调用了,通常只能调用一次,可以将匿名函数赋值给一个变量,这个变量就代表了这个函数。则可以调用多次。
- 匿名函数可以作为另一个函数的参数
- 匿名函数可以作为另一个函数的返回值
package main import ( "fmt" ) func main() { res2 := oper(20, 12, add) fmt.Println(res2)
//匿名函数作为回调函数直接写入参数中 res3 := oper(2, 4, func(a, b int) int { return a + b }) fmt.Println(res3)
- } func add(a, b int) int { return a + b } func reduce(a, b int) int { return a - b } //oper就叫做高阶函数 //fun 函数作为参数传递则fun在这里叫做回调函数 func oper(a, b int, fun func(int, int) int) int { fmt.Println(a, b, fun) //20 12 0x49a810A 第三个打印的是传入的函数体内存地址 res := fun(a, b)//fun 在这里作为回调函数 程序执行到此之后才完成调用 return res }