数据类型

整型取值范围

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,值为%sn", v1, v1) // int32 A 65

fmt.Printf("v2的类型是%T,%sn", v2, v2) //string A

//字符串的长度 //中文占3个字节

fmt.Println(len(v2)) //1

//获取某个字节

s2 := `hello go`

fmt.Println(s2[0]) //104

占位符号

字符转义

"

//字符转义空格

fmt.Println("hello t golang")//hello golang

//输出换行 n

//回车符号 r

//输出路径

fmt.Println("E:\golang\go.jpg")//E:golanggo.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-3081.79E+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 = %dn", 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,容量%dn", s1, len(s1), cap(s1))
    s1 = append(s1, 1, 2)
    fmt.Printf("地址%p,长度%d,容量%dn", s1, len(s1), cap(s1))
    s1 = append(s1, 3, 4, 5)
    fmt.Printf("地址%p,长度%d,容量%dn", s1, len(s1), cap(s1))
    
    
    //地址0xc000010540,长度0,容量3
    //地址0xc000010540,长度2,容量3
    //地址0xc00000e4b0,长度5,容量6
    //如果添加的数据容量够用, 地址则不变。
    //如果实现了扩容, 地址就会发生改变成新的地址,旧的则自动销毁。
}
复制代码
  • 每一个切片都引用了一个底层数组。
  • 切片本身不能存储任何数据,都是这底层数组存储数据,所以修改切片的时候修改的是底层数组中的数据。
  • 当切片添加数据时候,如果没有超过容量,直接进行添加,如果超出容量自动扩容成倍增长。
  • 切片一旦扩容,指向一个新的底层数组内存地址也就随之改变。

值传递与引用传递

int、float、string、boolarray、slice、map、struct、pointer、function、chanint、float、string、bool、array、structslice、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,%pn", slice1, slice2)//0xc000012520,0xc000012520
//切片引用的底层数组是同一个 所以值为一个地址 是引用的底层数组的地址
fmt.Printf("%p,%pn", &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)
}
复制代码
2006-01-02 15:04:05
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管理方案

depglidevender

安装dep

brew install dep
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
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 ensuredep ensure add github.com/go-sql-driver/mysql

mod初始化

使用mod需要注意的是:

GOPROXY=https://goproxy.ioGO111MODULEGO111MODULE=autogo mod init projectName

执行上面的命令之后,就已经可以开发编译运行此项目了。他已经自动引用了项目中所有的包文件。

函数

1,大写首字母函数共用,小写私用

匿名函数

定义一个匿名函数直接加上()就相当于直接调用了,通常只能调用一次,可以将匿名函数赋值给一个变量,这个变量就代表了这个函数。则可以调用多次。

//匿名函数作为回调函数直接写入参数中 res3 := oper(2, 4, func(a, b int) int {     return a + b }) fmt.Println(res3)  复制代码