前言
考虑到印象笔记以后不续费了,这里转存到博客园一份
因内容是自己写的笔记, 未作任何润色, 所以看着很精简, 请见谅
查看官方文档
在新的go安装包中,为了减小体积默认去除了go doc
安装go语言后在DOS中输入
godoc -http=:9000
然后在浏览器中打开 127.0.0.1:9000 即可(不能关闭DOS)
该系列参照了大佬的学习路线,加上本人的代码实践,大佬链接
https://www.liwenzhou.com/posts/Go/go_menu
目录结构
GOPATH > src 代码(pkg包/bin编译后文件) > 域名 > 用户名 > 项目 > 模块
双引号单引号
单引号代表字符 > 'a'
双引号代表字符串 > "abcd"
编译代码
进入目录(项目)
go build -o 文件名
main.go
main.go是项目主入口
调试代码
go run 文件
设置生成文件的格式(跨平台)
命令行
// 生成linux可执行文件
SET CGO_ENABLED=0 //禁用CGO
SET GOOS=linux //设置目标平台为linux
SET GOARCH=amd64 //目标架构为amd64
// 生成win
SET GOOS=windows
// 生成mac
SET GOOS=darwin
git控制
只需控制 src 下即可
变量
引用类型和值类型
引用类型空值为 niu
值类型空就为空
声明单个变量
var 变量名 变量类型
声明多个变量
声明多个变量
var (
变量名 变量类型
变量名 变量类型
...
)
变量默认值
命令变量后有默认值
变量声明时指定值
创建变量时指定值
var 变量名 变量类型 = 值
声明变量时直接指定变量类型
自动识别值的类型并指定变量类型
var 变量名 = 值
fmt模块
打印输出
导入
import "fmt"
带换行输出
可换行
fmt.Println()
格式化(带换行)输出
可加 %s 等占位
fmt.Printf()
短变量声明
在[函数内部]声明只能在该函数内使用的变量
声明短变量
变量名 := 值
等同于
var 变量名 变量类型 = 值
函数返回值
return返回
func foo()(返回值1类型, 返回值2类型, ...){
return 返回值1, 返回值2, ...
}
匿名变量
接收不需要的值
func foo()(string, int){
return "aa", 500
}
name, _ := foo()
此时我们使用匿名变量占位
常量
const 常量名 = 值
const (
常量名1 = 值1
常量名2 = 值2
常量名3 // 如不赋值默认拿上一行的值
常量名4
)
const(
a, b = 1, 2
c, d // 1, 2
)
枚举
const (
a = iota // 0
b = iota // 1
c = iota // 2
)
经典题目
const (
a = iota // 初始化0
_ = iota // 声明匿名常量,所以+1
b = iota // 2
)
const (
a = iota // 初始化0
b = 100 // 赋值100
c = iota // 定义两个常量所以为2
d // 不定义默认跟上一行也是iota所以为3
)
const e = iota // 初始化0
const (
_ = iota // 0
KB = 1 << (10 * iota) // 1<<10(位运算,2进制向左移10位) == 2的10次方 == 1024
MB = 1 << (10 * iota) // 2的20次方
GB = 1 << (10 * iota) // 2的30次方
TB = 1 << (10 * iota)
PB = 1 << (10 * iota)
)
const (
a, b = iota + 1, iota + 2 // iota=0, a=1, b=2
c, d // iota=1, c=2, d=3 iota声明常量一行变一次,不判断一行定义几个
e, f // iota=2, e=3, f=4
)
数据类型1
整型
len
进制
package main
import "fmt"
func main() {
var a int = 10 // 二进制
var b int = 077 // 八进制
var c = 0xff // 十六进制int可省略,可自己判断
fmt.Println(a, b) // 转换成十进制
fmt.Printf("%b \n", a) // b代表二进制 \n换行
fmt.Printf("%o \n", b) // n代表八进制
fmt.Printf("%x \n", c) // x代表十六进制
fmt.Printf("%p", &a) // 打印数据a的内存地址(十六进制)
}
浮点
方法1
将数字转换成字符串进行逻辑编写
方法2
将小数变成整数计算后再变成小数
数据类型1(补充)
布尔
字符串
字符串转义
\r 回车(光标移动至下一行行首)
\n 换行(移动至下一行同样位置)
\t 制表符
\' 单引号
\'' 双引号
\ 反斜杠
多行字符串
`aa
bb
cc
`
字符串常用方法
package main
import (
"fmt"
"strings"
)
// 字符串操作
func main() {
// 字符串位数
s1 := "我是字符串1"
fmt.Println(len(s1)) // 字符串位数(s1包含中文返回16)
// 字符串拼接
s2 := "我是字符串2"
fmt.Println(s1+"affag"+s2) // 推荐使用+号
s3 := fmt.Sprintf("%safaf%s", s1, s2) // Sprintf返回拼接后的字符串
fmt.Println(s3)
// 字符串分割
ret := strings.Split(s1, "是") //中文不能单引号
fmt.Println(ret)
// 判断是否包含
ret2 := strings.Contains(s1, "是")
fmt.Println(ret2)
// 判断前缀(第一个字符)
ret3 := strings.HasPrefix(s1, "我")
fmt.Println(ret3)
// 判断后缀(最后一个字符)
ret4 := strings.HasSuffix(s1, "1")
fmt.Println(ret4)
// 求子串位置
s4 := "123321"
fmt.Println(strings.Index(s4, "1")) // 1在s4中的第一个位置
fmt.Println(strings.LastIndex(s4, "1")) // 1的最后一个位置
// join(合并切片为str)
a1 := []string{"a", "b"}
fmt.Println(strings.Join(a1, "-"))
}
byte和rune类型
byte类型, (unit8类型), 代表 ASCII码, 英文这种占一个字节的字符
rune类型, (int32), 代表UTF8, 中文/日文等复合字符
package main
// 字符
import "fmt"
func main() {
s1 := "ChnMig"
c1 := 'A'
fmt.Println(s1, c1)
s2 := "中文"
c2 := '中'
fmt.Println(s2, c2)
fmt.Println(c2)
s3 := "Hello,中国"
// 遍历字符串
for i:=0;i<len(s3);i++{
fmt.Printf("%c\n", s3[i]) // 中文乱码
}
// for range 不乱码
for k, v := range s3{
fmt.Printf("%d%c\n", k, v)
}
}
类型强制转换
package main
import "fmt"
// 强制类型转换
func main() {
s1 := "big" // 建立s1
ByteArray := []byte(s1) // 建立一个存放类型为byte的列表将s1赋值进去(赋值时会强制转换为byte)
ByteArray[0] = 'p' // 按索引修改
s1 = string(ByteArray) // 转换为str重新赋值给s1
fmt.Println(s1)
}
流程控制
if
package main
import "fmt"
// if/ else if
func main() {
age := 19
if age > 18{
fmt.Println("18+")
}else if age < 18{ // else, else if 不能再换行写
fmt.Println("FBI WARING")
}else {
fmt.Println(18)
}
}
for
package main
import "fmt"
// 初始语句结束语句省略
func main() {
age := 18
for age > 0{
fmt.Println(age)
age--
}
}
package main
import "fmt"
// 完整版
func main() {
for age:=18;age > 0;age--{
fmt.Println(age)
}
}
package main
// 死循环
func main() {
for {
//死循环(慎用)
}
}
switch
package main
import "fmt"
// switch
func main() {
age := 18
switch age { // switch 要判断的值
case 1: // 判断
fmt.Println("1")
case 18: // 判断
fmt.Println("18")
default: // 以上判断都不符合
fmt.Println("以上判断全都不符合")
}
}
package main
import "fmt"
// switch
func main() {
age := 18
switch age {
case 1, 2, 3, 4, 5: // 判断
fmt.Println("1")
case 18, 19, 20: // 判断
fmt.Println("18")
default: // 以上判断都不符合
fmt.Println("以上判断全都不符合")
}
}
package main
import "fmt"
// switch
func main() {
age := 18
switch {
case age < 25:
fmt.Println(">25")
case age > 25:
fmt.Println("<25")
}
}
break 和 continue
fmt模块
println
printf
数组
package main
import (
"fmt"
)
func main() {
a := [5]int{1, 2, 3, 4, 5}
b := [10]int{1, 2} //定义10位但是只填了两位那之后的默认0
fmt.Println(a)
fmt.Println(b)
var c [3]int //用var定义
var d [3]int = [3]int{1, 2, 3} //用var定义并赋值
fmt.Println(c)
fmt.Println(d)
}
自动判断几位
package main
import (
"fmt"
)
func main() {
a := [...]int{1, 2, 3, 4, 5} // [...]会自己查询赋值几位然后填充位数
fmt.Println(a)
}
根据索引赋值
数组的遍历
package main
import (
"fmt"
)
func main() {
a := [100]int{50: 3}
//通过索引遍历
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
//通过range遍历
for index, value := range a {
fmt.Println(index, value)
}
}
多维数组
package main
import (
"fmt"
)
func main() {
//声明后赋值
var a [3][2]int //[[0, 0] [0, 0] [0, 0]]
a = [3][2]int{
[2]int{1, 2}, //这里给a的第一位[0, 0]赋值
[2]int{4, 5}, //第二位
}
fmt.Println(a) //[[1 2] [4 5] [0 0]]
//声明时赋值
var b = [3][2]int{
[2]int{1, 2},
[2]int{4, 5},
}
fmt.Println(b)
//多维数组索引
fmt.Println(a[0][0]) //1
//多维数组遍历
for i := 0; i < (len(a)); i++ {
for k := 0; k < (len(a[i])); k++ {
fmt.Println(a[i][k])
}
}
//range
for _, v1 := range a {
for _, v2 := range v1 {
fmt.Println(v2)
}
}
}
切片
切片大小: 当前切片长度
切片地址: 切片中第一个元素地址 len()
切片容量: 底层数组最大能存放的元素个数 cap()
切片扩容
package main
import "fmt"
func main() {
//主动声明
var a = []int{1, 2, 3} //不规定长度即为切片(内部其实是先生成一个数组然后切片)
fmt.Printf("a:%T", a)
//从数组得到切片
var b = [3]int{1, 2, 3}
var c []int
c = b[0:2] //从索引0切到1,c=b[:]从开始切到结束,c=b[:2]从开始切到索引1,c=b[1:]从索引1切到最后
fmt.Println(c)
//切片大小(当前)
fmt.Println(len(c))
//容量(底层数组最大放多少)
//切片初始容量为他生成时的长度
fmt.Println(cap(c)) //cap查看当前切片的容量
//切片追加
var d = []int{} //没有填充数据时没有内存地址
fmt.Println(d, len(d), cap(d)) //[] 0 0
d = append(d, 1) //append(d, 1, 2)代表添加两个
fmt.Println(d, len(d), cap(d)) //[1] 1 1
}
切片COPY
package main
import "fmt"
func main() {
a := []int{1, 2, 3}
b := a
b[0] = 100
fmt.Println(a) //[100 2 3]
fmt.Println(b) //[100 2 3]
var c []int //为空没有申请内存
c = make([]int, 3, 3) //使用make函数申请长度为3容量为3的内存
copy(c, a) //copy a的值到c,如果c的容量小于a则会数据丢失
a[0] = 10
fmt.Println(a) //[10 2 3]
fmt.Println(c) //[100 2 3]
}
切片内容的删除
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4}
a = append(a[:2], a[3:]...) //先切索引1之前的元素再切1之后的元素然后重新赋值给a, ...是将切片的多个内容切分, ...写在追加的参数后,多个参数多个...
fmt.Println(a) //[1 3 4]
}
难题
package main
import "fmt"
func main() {
//切片地址永远指切片起始的地址内存,不包含结尾
a := [4]int{1, 2, 3, 4} //数组
b := a[:] //切片
b[0] = 100
fmt.Println(a[0]) //100,因切片是指向性数据,b与a共用内存
c := a[2:3]
fmt.Println(c)
fmt.Println(cap(c)) //切片如果是由数组产生,那么他的容量为 切片开始处 到 数组结束处 长度
d := b[:]
fmt.Println(d) //[100 2 3 4],因指向性数据,dcb都是指向数组a
e := d[2:]
fmt.Println(e) //切片不是从头开始所以地址发生了变化
}
数组排序
package main
import (
"fmt"
"sort"
)
func main() {
a := [5]int{5, 6, 1, 3, 2}
sort.Ints(a[:]) //sort.Ints方法将切片排序
fmt.Println(a) //因切片数据指向数组所以实际上排序了数组
}
map
package main
import (
"fmt"
)
func main() {
var a map[string]int //声明map,k为string类型,v为int,不赋值为nil
fmt.Println(a) //map[]
//初始化(初始化后部位nil但是还是空)
a = make(map[string]int, 8) //map可不指定容量但是推荐指定
fmt.Println(a) //map[]
//添加键值对
a["one"] = 1
a["two"] = 2
fmt.Println(a) //map[one:1 two:2]
//声明+初始化
b := map[string]int{
"one": 1,
"two": 2,
}
fmt.Println(b) //map[one:1 two:2]
}
map常用方法
package main
import (
"fmt"
"sort"
)
func main() {
// 判断某个键存不存在
a := map[int]int{
1: 1,
3: 3,
2: 2,
}
v, ok := a[1] //返回两个值, 值1为这个键的值, 2为这个键存不存在
fmt.Println(v, ok) //1 true
v1, ok1 := a[5]
fmt.Println(v1, ok1) //0 false 不存在值1为值类型的默认值,2为false
//遍历map
for k, v := range a {
fmt.Println(k, v)
}
//只遍历map的key
for k := range a {
fmt.Println(k)
}
//遍历map的v
for _, v := range a {
fmt.Println(v)
}
//按照某个顺序遍历
// 取key存放至切片
// 切片排序
// 按key对map排序
keys := make([]int, 3)
for k := range a {
keys = append(keys, k)
}
sort.Ints(keys)
for _, key := range keys {
fmt.Println(key, a[key])
}
// 删除键值对
delete(a, 1) //(map, 键)
}
切片与map混合
package main
import (
"fmt"
)
func main() {
//元素类型为map的切片
var a = make([]map[string]int, 8) //只完成了切片的初始化
a[0] = make(map[string]int, 2) //初始化切片[0]的map
a[0]["one"] = 1
fmt.Println(a) //[map[one:1] map[] map[] map[] map[] map[] map[] map[]]
//值为切片的map
var b = make(map[int][]int, 8) // map初始化
b[0] = make([]int, 5) //切片初始化
b[0][0] = 1
b[0][1] = 2
fmt.Println(b) //map[0:[1 2 0 0 0]]
}
函数
函数组成
func 函数名(参数)(返回值){
函数体
}
例如
package main
import "fmt"
func sum(x int, y int) int {
// 求和
return x + y
}
func main() {
s := sum(1, 2)
fmt.Println(s)
}
函数接收n个参数
package main
import "fmt"
func sum(a ...int) int { //参数名后加...代表接收多个,但是参数类型是定死的,这样接受的参数为一个切片,此时a可不传
ret := 0
for _, v := range a {
ret = ret + v
}
return ret
}
func main() {
s := sum(1, 2, 3)
fmt.Println(s)
}
package main
import "fmt"
func sum(a int, b ...int) int { //传a为int,b为多个,a必传
ret := a
for _, v := range b {
ret = ret + v
}
return ret
}
func main() {
s := sum(1, 2, 3)
fmt.Println(s)
}
package main
import "fmt"
func sum(a, b int) int { //如果a,b都为int那么可以简写 a, b int
return a + b
}
func main() {
s := sum(1, 2)
fmt.Println(s)
}
函数返回多个值
package main
import "fmt"
func calc(a, b int) (sum, sub int) { //多返回值
sum = a + b //sum与sub已经定义
sub = a - b
return //直接return会自己寻找返回值
}
func main() {
a, b := calc(1, 2)
fmt.Println(a, b)
}
defer
package main
import "fmt"
func main() {
// defer延迟执行,代码将要结束时再执行,执行顺序为先读到的后执行
fmt.Println("go")
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println("on")
// go on 2 1
}
变量作用域
package main
import "fmt"
//全局变量num
var num = 10
//定义函数
func a() {
num := 100 //如定义了num则先用函数内的num
fmt.Println(num)
}
func main() {
a()
}
将函数作为参数
package main
import "fmt"
var num = 10
func a(x, y int) int {
return x + y
}
func b(x, y int) int {
return x - y
}
func c(x, y int, op func(int, int) int) int { //第三个参数为函数func类型,接收两个参数int
return op(x, y)
}
func main() {
a1 := c(50, 60, a)
b1 := c(50, 60, b)
fmt.Println(a1)
fmt.Println(b1)
}
匿名函数
package main
import "fmt"
func main() {
//定义然后执行
a := func() {
fmt.Println("匿名函数")
}
a()
//定义直接执行(函数后加())
func() {
fmt.Println("定义并执行")
}()
}
闭包
package main
import "fmt"
func a(name string) func(string) string { //返回值为函数
return func(end string) string {
return name + end
}
}
func main() {
r := a("name") //r为函数(闭包)函数内部引用外部变量
ret := r("end") //执行匿名函数
fmt.Println(ret)
}
内置函数
- close 关闭channel
- len 求长度
- new 分配内存(值类型)
- make 分配内存(引用类型)
- append 追加
- panic和recover 错误处理
panic/recover
package main
import "fmt"
func a() {
fmt.Println("a")
}
func b() {
defer func() { //defer放到b函数最后执行
err := recover() //recover捕捉错误信息
if err != nil { //如果err不为空
fmt.Println("error")
}
}()
panic("b") //panic报异常,准备结束,执行defer
}
func c() {
fmt.Println("c")
}
func main() {
a()
b()
c()
}
指针
package main
import "fmt"
func main() {
a := 1
fmt.Println(a)
b := &a
fmt.Println(b) //0xc000010090
fmt.Printf("%T\n", b) //*int int类型的内存地址(不同类型不能相互转换)
fmt.Println(*b) //1 知道内存地址来取存储的值
}
指针与地址
通常的使用方法
package main
import "fmt"
func funA(a *[3]int) { //*代表接收数组的指针
a[0] = 100 //按指针修改数据 (*a)[0] = 100
}
func main() {
l1 := [3]int{1, 2, 3}
funA(&l1)
fmt.Println(l1) // [100 2 3]成功在内部修改了外部变量
}
建立指针
package main
import "fmt"
func main() {
var a = new(int) //new一个int类型指针
fmt.Println(a) //0xc000064058
*a = 10
fmt.Println(a) //0xc000064058
fmt.Println(*a) // 10
var c = new([3]int) //new一个切片
fmt.Println(c) //&[0 0 0]
(*c)[0] = 1 //等同于 c[0] = 1
fmt.Println(*c) //[1 0 0]
}
结构体(struct)和方法
创建自定义类型和给类型起别名
package main
import "fmt"
//NewInt 创建一个新的类型 NewInt 实际上是int类型
type NewInt int // 定义变量时首字母大写代表该变量是公共的,别人导入这个包时也可使用变量,注释要按照格式 变量名 注释
type int1 = int //创建类型别名
func main() {
var a NewInt
fmt.Println(a) //0 基于int所以默认值0
fmt.Printf("%T\n", a) //main.NewInt 自定义类型
var b int1
fmt.Println(b) //0
fmt.Printf("%T\n", b) //int 起别名所以还是int
var c int
fmt.Printf("%T\n", c) //起别名的情况下原名字也是可用的
}
结构体
基本实例化
package main
import "fmt"
// 定义结构体
// 使用type建立一个学生类型
type student struct {
name string
age int
sex string
hobby []string
}
func main() {
//实例化方法1
var s1 = student{} //如果只实例化不赋值则默认为该数据类型初始值
fmt.Printf("s1_name:%v\n", s1.name) //string默认为空
//实例化方法2
var s2 = new(student)
fmt.Println(s2) // &{ 0 []} ,new出来的是指针
s2.name = "s2" // 等同于 (*s2).name 取地址
fmt.Println(s2.name) //s3
//实例化方法3
var s3 = &student{} // 等同于2
fmt.Println(s3)
}
基本初始化
package main
import "fmt"
// 定义结构体
// 使用type建立一个学生类型
type student struct {
name string
age int
sex string
hobby []string
}
func main() {
// 初始化方法1
var s1 = student{ //少传则默认值替代
name: "s1",
age: 18,
sex: "男",
hobby: []string{"A", "b", "c"},
}
fmt.Println(s1)
fmt.Println(s1.name) //结构体可以通过 . 的形式拿出来属性
//初始化方法2
var s2 = student{ //顺序与定义结构体时相同,必须全部传齐
"s2",
18,
"男",
[]string{"A"},
}
fmt.Println(s2.name)
//初始化方法3
var s3 = &student{ //顺序与定义结构体时相同,必须全部传齐
"s3",
18,
"男",
[]string{"A"},
}
fmt.Println(s3.name)
}
结构体内存
package main
import "fmt"
// 结构体内存布局
func main() {
type test struct {
a int8
b int8
c int8
}
var t = test{
a: 1,
b: 2,
c: 3,
}
fmt.Println(&(t.a)) //0xc000064058
fmt.Println(&(t.b)) //0xc000064059
fmt.Println(&(t.c)) //0xc00006405a
}
结构体嵌套
package main
import "fmt"
// 结构体的嵌套
type address struct {
province string
city string
}
type student struct {
name string
age int
addr address // 嵌套结构体 address
}
func main() {
s1 := student{
name: "s1",
age: 22,
addr: address{
province: "p",
city: "c",
},
}
fmt.Println(s1) // {s1 22 {p c}}
fmt.Println(s1.addr.city) // c
}
赋值给指针
package main
import "fmt"
// 结构体内存布局
func main() {
type test struct {
a int8
b int8
c int8
}
var t = test{
a: 1,
b: 2,
c: 3,
}
fmt.Println(&(t.a)) //0xc000064058
fmt.Println(&(t.b)) //0xc000064059
fmt.Println(&(t.c)) //0xc00006405a
t2 := &t //将t的地址给t2
fmt.Printf("%T\n", t2) //*main.test
t2.a = 5 //(*stu3).a,找到内存地址
fmt.Println(t.a, t2.a) //5 5 两个都修改了
}
构造函数
package main
import "fmt"
// 构造函数
type student struct {
name string
age int
gender string
hobby []string
}
// 构造函数(名字带new方便理解)
// 1版本,初始化后返回接收,如初始化内容较多返回再接受较占内存
func newStudent(name string, age int, gender string, hobby []string) student {
return student{
name: name,
age: age,
gender: gender,
hobby: hobby,
}
}
// 2版本,初始化后返回初始化的指针,外面调用直接操作指针,避免1的问题
func newStudent2(name string, age int, gender string, hobby []string) *student {
return &student{
name: name,
age: age,
gender: gender,
hobby: hobby,
}
}
func main() {
s1Hobby := []string{"a", "b"}
s1 := newStudent("s1", 18, "男", s1Hobby)
fmt.Println(s1)
}
方法和接收者
func (接受者变量 接受者类型) 方法名(参数列表) (返回参数) {
函数体
}
package main
import "fmt"
// 方法
type people struct {
name string
gender int
}
// 函数
// func deram() {
// fmt.Println("梦想")
// }
// 指定接受类型是people
// 通常这个型参命名为类型的首字母小写, 比如 people叫 p
func (p people) deram() {
p.gender = 11 // 这里改并不会变,因为p是值类型,在函数内赋值无用
fmt.Println("梦想")
}
func (p *people) deram2() {
p.gender = 11 // 传入指针时就有用
fmt.Println("梦想")
}
func main() {
var p1 = people{
name: "p1",
gender: 20,
}
p1.deram() // .deram会自己找方法执行
p1.deram2() // 原版为(&p1.deram2), Go语言可以简写
}
什么时候使用指针接受者
方法的追加
package main
import "fmt"
// MyInt 无法给外包加方法但是我们可以先自定义一个类型再给自定义类型加方法
type MyInt int
func (m *MyInt) sayHi() {
fmt.Println("Hello")
}
func main() {
var a MyInt
fmt.Println(a)
a.sayHi()
}
结构体镶嵌达到继承的效果
package main
import "fmt"
type animal struct { // 建立一个结构体
name string
}
type dog struct {
feet int
*animal //镶嵌结构体的指针
}
func (a *animal) move() { //建立一个方法传入 animal 的指针
fmt.Println(a.name)
}
func (d *dog) wang() { // 建立一个方法传入 dog 指针
fmt.Println(d.name)
}
func main() {
var a = dog{
feet: 4,
animal: &animal{ // 指针
name: "dog1",
},
}
a.wang()
a.move()
}
获取用户输入信息
package main
import "fmt"
// DOS交互
func main() {
// 从DOS获取值
// var变量
var (
name string
age int
)
fmt.Scan(&name, &age) //传入变量指针,用户输入直接赋值, 空格或换行分割
fmt.Scanf("name:%S age:%d \n", &name, &age) //不常用,规定必须按照Scanf内容格式输入
fmt.Scanln(&name, &age) // 与1差不多但是换行就结束(只能空格分隔)
fmt.Println(name, age)
}
程序的退出
os.Exit(0)
匿名字段
package main
import "fmt"
// 匿名字段
type student struct { // 结构体
name string
string // 匿名字段
int
}
func main() {
s1 := student{
name: "s1",
}
fmt.Println(s1.string) // 为空(string的默认值)
}
JSON序列化
package main
import (
"encoding/json"
"fmt"
)
// 序列化
// Student 学生
type Student struct {
ID int `json:"id"` // 如想改变json的k,语法同本行,不带空格
Gender string `json:"gender"`
Name string `json:"name"`
}
func main() {
s1 := Student{
ID: 1,
Gender: "男",
Name: "s1",
}
// 序列化
v, err := json.Marshal(s1) // json.Marshal方法,json序列化,返回值和报错信息
if err != nil { // 不为nil代表报错
fmt.Println(err)
}
fmt.Println(v) // [123 34 73 68 34 58 49 44 34 71 101 110 ...] 每个字节
fmt.Println(string(v)) // []byte转string, json
// 反序列化
j1 := string(v)
s2 := &Student{} //指针赋给s2
json.Unmarshal([]byte(j1), s2) // 接收两个参数,一是切片,二是要转换的类型指针
fmt.Println(s2) // &{1 男 s1}, 指针
fmt.Println(*s2) // {1 男 s1}, 内容
}
defore深入理解
package main
import "fmt"
// defore 难题
// return在汇编上本质分为 获取返回值 > RET指令, 加上defore是 获取返回值 > defore > RET指令
func f1() int {
x := 5
defer func() {
x++
}()
return x // 返回值为int, 走到return时先看到返回值不是x而是int所以先拿到返回值5, 执行defore x++, 对返回值无影响, 返回5
}
func f2() (x int) {
defer func() {
x++
}()
return 5 // 返回值为 x, func时x=0, return时5赋值给x,然后执行defore, x++=6, 返回6
}
func f3() (y int) {
x := 5
defer func() {
x++
}()
return x // 返回值为y, func时y=0, y为值类型所以return时将x的值5copy给y, defore x++对y无影响, 返回5
}
func f4() (x int) {
defer func(x int) {
x++
}(x)
return 5 // 返回值为x, func时x=0, return时5赋值给x, 执行defore 将x传入, 但是Go函数是形参,所以defore函数内部新建一个x, x++对外部x无影响, 返回5
}
func main() {
fmt.Println(f1())
fmt.Println(f2())
fmt.Println(f3())
fmt.Println(f4())
}
Go语言的包
定义包
package 包名
可见性
导入包
import 别名 包
匿名导入包
import _ 包
初始化函数
time包
时间戳
package main
import (
"fmt"
"time"
)
func myTimeStamp(timestamp int64) {
timeObj := time.Unix(timestamp, 0) // 时间戳转时间格式, 0是纳秒的偏移量,通常为0(不偏移)
fmt.Println(timeObj)
}
func main() {
now := time.Now() // 当前时间,实例化一个time.Now结构体
fmt.Printf("%#v", now) // time.Time{wall:0xbf3f74ec230757d8, ext:5984001, loc:(*time.Location)(0x57bfc0)}
fmt.Println(now.Year()) // 年 2019
fmt.Println(now.Month()) // 月 July
fmt.Println(now.Day()) // 日 4
fmt.Println(now.Hour()) // 时 9
fmt.Println(now.Minute()) // 分 56
fmt.Println(now.Second()) // 秒 39
fmt.Println(now.Nanosecond()) // 纳秒 531441700
// 时间戳
fmt.Println(now.Unix()) // 时间戳 1562205575
fmt.Println(now.UnixNano()) // 纳秒级别时间戳 1562205575360303400
}
定时器
package main
import (
"fmt"
"time"
)
// 定时器
func tickDemo() {
ticker := time.Tick(time.Second) // 定义一个间隔1s的定时器
for i := range ticker {
fmt.Println(i)
}
}
func main() {
tickDemo()
}
时间格式化
package main
import (
"fmt"
"time"
)
func format() {
now := time.Now()
// 格式化时间模板为Go的出生日期 2006年1月2日15点04分
fmt.Println(now.Format("2006-01-02 15:04:05")) // 2019-07-04 15:54:22
fmt.Println(now.Format("2006/01/02 15:04:05")) // 2019/07/04 15:54:22
fmt.Println(now.Format("2006-01-02")) // 2019-07-04
fmt.Println(now.Format("15:04")) // 15:54
}
func main() {
format()
}
接口
type 接口类型名 interface{
方法名1(参数列表1) 返回值列表1
方法名2(参数列表2) 返回值列表2
}
为什么使用接口
接口实现
package main
import "fmt"
// 接口实现
// 就这里来说, 只要一个类型实现了wash 和 dry 方法,我们就叫这个类型实现了 xiyiji 这个接口
type xiyiji interface {
wash() // 洗衣
dry() // 甩干
}
type haier struct {
name string
price float64
}
func (h haier) wash() {
fmt.Println("haier wash")
}
func (h haier) dry() {
fmt.Println("haier price")
}
type people struct {
name string
}
func (p people) wash() {
fmt.Println("people wash")
}
func (p people) dry() {
fmt.Println("people price")
}
func (p people) dry1() {
fmt.Println("people price1")
}
// haier 和 people 都有dry和wash方法, 接口赋值后可以像调用方法一样使用, 所以接口是抽象的,不关心赋值的结构体类型, 只要你有我规定的方法即可(可以比我规定的方法多)
func main() {
var a xiyiji
h1 := haier{
name: "小神童",
price: 188.8,
}
a = h1 // 接口是抽象类型,所以可以被赋值结构体, 前提是结构体必须有接口规定的方法
fmt.Println(a)
a.dry() // haier price 调用haier的dry
p1 := people{
name: "p1",
}
a = p1
p1.dry() // people price 调用people的dry
}
package main
import "fmt"
// 为什么使用接口,狗和猫都能叫, 我们为什么不能将其合在一起
// Cat 猫结构体
type Cat struct{}
// Dog 狗结构体
type Dog struct{}
// Sayer 结构体
type Sayer interface {
Say() string
}
// Say 猫叫
func (c Cat) Say() string {
return "喵喵喵"
}
// Say 狗叫
func (d Dog) Say() string {
return "汪汪汪"
}
func main() {
// c := Cat{}
// d := Dog{}
// fmt.Println(c.Say())
// fmt.Println(d.Say())
// 接口形式
var animalList []Sayer
c := Cat{}
d := Dog{}
animalList = append(animalList, c, d)
for _, i := range animalList {
fmt.Println(i.Say())
}
}
类型与接口的关系
接口的嵌套
package main
import "fmt"
// 接口1
type speaker interface {
speak()
}
// 接口2
type mover interface {
move()
}
// 接口嵌套
type animal interface {
speaker // 嵌套接口1
mover // 接口2
}
// 结构体
type cat struct {
name string
}
func (c cat) speak() {
fmt.Println("speak")
}
func (c cat) move() {
fmt.Println("move")
}
func main() {
var x animal
x = cat{name: "cat1"}
x.move() // 直接.方法 即可
x.speak()
}
空接口
package main
import "fmt"
// 空接口
func showType(a interface{}) {
fmt.Printf("%T\n", a)
}
func main() {
showType(1)
showType("wwwaw")
showType(1.256)
// 值为空接口的map
var stuInfo = make(map[string]interface{}, 100)
stuInfo["A"] = 100
stuInfo["B"] = true
stuInfo["C"] = "ahdha"
fmt.Println(stuInfo)
}
类型断言
值, 判断是否类型对应 := 变量.(类型)
package main
import "fmt"
// 类型断言
func main() {
var x interface{}
x = 100
v, ok := x.(int)
fmt.Println(v, ok) // 100 true
}
package main
import "fmt"
// 类型断言
func main() {
var x interface{}
x = 100
switch v := x.(type) {
case string:
fmt.Println("string", v)
case int:
fmt.Println("int", v)
}
}
接口值
func main() {
var x interface{}
var a int64 = 100
var b int32 = 10
var c int8 = 1
x = a // 此时x为 <int64, 100>
x = b // <int32, 10>
x = c // <int8, 10>
x = false // <bool, false>
fmt.Println(x)
}
Go对文件进行操作
打开文件
关闭文件
读取文件
初始版
package main
import (
"fmt"
"io"
"os"
)
// 文件操作
// 打开关闭文件
func open() {
file, err := os.Open("./a.txt") // 打开文件, 参数1为文件, 2为报错信息
if err != nil { // 不为nil代表出现错误
fmt.Printf("文件打开失败,错误:%v", err)
return
}
// 文件可以打开
defer file.Close() // defer延迟关闭文件
// 读文件
var tmp [128]byte // 定义一个128长度的字节数组
for { // 每次读取128字节
n, err := file.Read(tmp[:]) // 将文件内容赋值给tmp, 返回两个返回值, n为本次读取长度, err代表读取错误
if err == io.EOF {
// 文件读取完毕会报出一个 EOF 错误, 并且n为0
fmt.Println("文件读取完毕")
return
}
if err != nil {
// 文件读取失败
fmt.Println("err:", err)
return
}
fmt.Println(n) // 本次长度
fmt.Print(string(tmp[:])) // 文本内容就在切片里
}
}
func main() {
open()
}
bufio读取文件
package main
import (
"bufio"
"fmt"
"io"
"os"
)
// bufio
func main() {
file, err := os.Open("./a.txt")
if err != nil {
return
}
defer file.Close()
// 读取文件
reader := bufio.NewReader(file)
for { // 会自己循环
str, err := reader.ReadString('\n') // 读取到指定字符结束
if err == io.EOF {
fmt.Print(str)
return
}
if err != nil {
return
}
fmt.Print(str)
}
}
ioutil读取文件
package main
import (
"fmt"
"io/ioutil"
)
func readFile(f string) { // 传入filename
content, err := ioutil.ReadFile(f)
if err != nil {
return
}
fmt.Println(string(content))
}
// ioutil
func main() {
readFile("./a.txt")
}
OpenFile操作文件
OpenFile源代码如下
func OpenFile(name string, flag int, perm FileMode) (*File, error){
...
}
其中, name是打开的文件, flag为打开文件的模式
prm为文件权限, 八进制数, r(读)04, w(写)02, x(执行)01
package main
import "os"
// 文件写
func main() {
file, err := os.OpenFile("x.txt", os.O_CREATE|os.O_WRONLY, 0755) // 打开文件,没有则新建
if err != nil {
return
}
defer file.Close()
str := "架飞机啊" // 写入
file.WriteString(str)
}
bufio.NewWriter写入文件
func main() {
file, err := os.OpenFile("xx.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("open file failed, err:", err)
return
}
defer file.Close()
writer := bufio.NewWriter(file)
for i := 0; i < 10; i++ {
writer.WriteString("hello沙河\n") //将数据先写入缓存
}
writer.Flush() //将缓存中的内容写入文件}
ioutil.WriteFile写入文件
package main
import (
"fmt"
"io/ioutil"
)
// 文件写
func main() {
str := "hello 沙河"
err := ioutil.WriteFile("./xx.txt", []byte(str), 0666) // 只支持切片
if err != nil {
fmt.Println("write file failed, err:", err)
return
}
}
反射
reflect包
TypeOf
package main
import (
"fmt"
"reflect"
)
// reflect
type cat struct {
name string
}
func myTypeOf(i interface{}) {
v := reflect.TypeOf(i) // 获取变量i的类型
fmt.Println(v, v.Name(), v.Kind()) // v.Name()输出这个i的类型名, v.Kind()输出这个i的大的种类,比如所有结构体都是结构体,引用类型的Name为空
}
func main() {
myTypeOf("jagjgag") // string string string
a := false
myTypeOf(a) // bool bool bool
var c1 = cat{
name: "花花",
}
myTypeOf(c1) // main.cat cat struct (可以查找出出自定义的结构体等)
}
ValueOf
package main
import (
"fmt"
"reflect"
)
// 值进入该函数变成空接口,ValueOf获取这个参数原有的值
func reflectValue(x interface{}) {
v := reflect.ValueOf(x) // 获取接口值信息
k := v.Kind() // kind获取值对应类型
switch k { // 对比判断
case reflect.Int64:
fmt.Printf("Int64 %d\n", int64(v.Int()))
case reflect.Float32:
fmt.Printf("float32 %f\n", float32(v.Float()))
}
}
func main() {
var a float32 = 3.14
var b int64 = 200
reflectValue(a)
reflectValue(b)
}
通过反射修改外部变量值
package main
import (
"fmt"
"reflect"
)
// 通过反射修改值
func editValue(x interface{}) {
// 想要在函数内修改函数外的值必须通过修改指针对应值的形式
v := reflect.ValueOf(x) // 获取传入的值
kind := v.Kind() // 获取值的类型
switch kind {
case reflect.Ptr: // 判断为指针,修改
v.Elem().SetInt(500) // v.Elem()才是传入值的指针, SetInt()为修改值为Int
}
}
func main() {
var a int64 = 100
editValue(&a)
fmt.Println(a)
}
Tag
type student struct {
Name string `json:"name" a:"b"`
}
结构体反射查看字段
package main
import (
"fmt"
"reflect"
)
// 结构体反射
type student struct {
Name string `json:"name"`
}
func main() {
s1 := student{
Name: "s1",
}
t := reflect.TypeOf(s1)
fmt.Println(t.Name(), t.Kind()) // student struct
// 循环遍历结构体s1, NumField() 为结构体长度
for i := 0; i < t.NumField(); i++ {
field := t.Field(i) // 拿出某个字段i
fmt.Println(field.Name, field.Index, field.Type, field.Tag.Get("json")) // 字段名, 索引, 类型, jsonTag
}
// 通过字段名
scoreField, ok := t.FieldByName("Name") // 查找名为Name的字段, ok为True代表有字段, 此时scoreField为该字段
if ok {
fmt.Println(scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json")) // Name [0] string name
}
}
结构体反射查看方法
package main
import (
"fmt"
"reflect"
)
// 查看结构体的方法
func printMethod(x interface{}) {
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
for i := 0; i < v.NumMethod(); i++ { // 遍历方法
methodType := v.Method(i).Type() // 拿到方法
fmt.Println(t.Method(i).Name) // 方法名
fmt.Println(methodType) // 方法
// 通过反射调用方法, 需要传值必须是 []reflect.Value{} 类型
var args = []reflect.Value{}
v.Method(i).Call(args)
}
}
并发
并发与并行
并发: 同一时段做多个事情
并行: 同一时刻做多个事情
进程,线程,协程
进程: 一个程序启动后创建一个进程
线程: 操作系统调度的最小单位
协程: 用户态的线程
goroutine
package main
import (
"fmt"
"sync"
)
// 启动 goroutine
// goroutine 通常和 sync 的 WaitGroup 结合使用
// WaitGroup是一个计时器
var wg sync.WaitGroup
func hello() {
fmt.Println("hello")
defer wg.Done() // 执行结束后告诉wg执行完毕,推荐用defer
}
func main() {
// 函数前加 go 就是调用 goroutine 执行
// 1. 创建goroutine
// 2. 在goroutine执行hello
wg.Add(1) // 计时器加1, 启动一个 goroutine
go hello()
fmt.Println("func hello on")
wg.Wait() // 等待wg全部执行结束(Done)才结束
}
goroutine和线程的区别
https://www.liwenzhou.com/posts/Go/14_concurrence/
- 一个操作系统可开启多个 goroutine
- Go程序可以同时使用多个系统线程
- goroutine和os线程是多对多的关系, m:n(goroutine : CPU核心)
channel
package main
import "fmt"
// channel
func main() {
// c1为chan类型的管道, 传输1个int
c1 := make(chan int, 1) // chan是引用类型所以需要make
// channel 发送和接收 <-
c1 <- 10 // 把10发送到c1
ret := <-c1 // 接收c1(不写ret则是丢弃值)
fmt.Println(ret)
close(c1) // 关闭管道, 关闭的通道再接受值不报错,如果管道有值,则依次取出,无值 取到对应值的零值, 不可发送值, 不可重复关闭一个通道
}
无缓冲通道和缓冲通道
package main
import "fmt"
// channel
func recv(ch chan bool) {
<-ch
}
func main() {
c1 := make(chan bool) // 创建无缓冲通道(不规定长度)
go recv(c1)
c1 <- false // 不会报错因为recv函数中已经在等待接收c1的值
fmt.Println(len(c1), cap(c1)) // len()获取当前管道的值长度, cap()获取管道容量
}
判断通道是否关闭
package main
import "fmt"
// 判断通道是否关闭
func send(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
defer close(ch)
}
func main() {
var ch1 = make(chan int, 100)
go send(ch1)
// 方法1
for {
ret, ok := <-ch1 // 如通道关闭ok为false
if !ok {
break
}
fmt.Println(ret)
}
// 方法2
for ret := range ch1 { // range自己判断
fmt.Println(ret)
}
}
select多路复用
package main
import (
"fmt"
"time"
)
// select多路复用
var ch1 = make(chan string, 100)
var ch2 = make(chan string, 100)
func f1(ch chan string) {
for i := 0; i < 10; i++ {
ch1 <- fmt.Sprintf("f1:%d", i)
}
}
func f2(ch chan string) {
for i := 0; i < 10; i++ {
ch2 <- fmt.Sprintf("f2:%d", i)
}
}
func main() {
go f1(ch1)
go f2(ch2)
for {
select { // select多个ret通道
case ret := <-ch1:
fmt.Println(ret)
case ret := <-ch2:
fmt.Println(ret)
default:
fmt.Println("取不到值")
time.Sleep(time.Millisecond * 500)
}
}
}
单向通道
package main
// 传入的chan只能写
func p(ch chan<- int) {
}
// 传入的chan只能读
func p1(ch <-chan int) {
}
// 传入的chan可读写
func p2(ch chan int) {
}
并发控制与锁
https://www.liwenzhou.com/posts/Go/14_concurrence/
互斥锁
package main
import (
"fmt"
"sync"
)
var num int
var wg sync.WaitGroup
// 互斥锁
var lock sync.Mutex
func add() {
defer wg.Done()
for i := 0; i < 5000; i++ {
lock.Lock() // 加互斥锁
num = num + 1
lock.Unlock() // 解锁
}
}
func main() {
wg.Add(5)
go add()
go add()
go add()
go add()
go add()
wg.Wait()
fmt.Println(num)
}
读写互斥锁
package main
import (
"fmt"
"sync"
)
var num int
var wg sync.WaitGroup
// 读写锁
var rwLock sync.RWMutex
func add() {
defer wg.Done()
for i := 0; i < 5000; i++ {
if i%2 == 0 {
rwLock.RLock() // 加读锁
fmt.Println(num)
rwLock.RUnlock() // 释放读锁
} else {
rwLock.Lock() // 加写锁
num = num + 1
rwLock.Unlock() // 释放写锁
}
}
}
func main() {
wg.Add(5)
go add()
go add()
go add()
go add()
go add()
wg.Wait()
fmt.Println(num)
}
map的并发
https://www.liwenzhou.com/posts/Go/14_concurrence/#autoid-4-3-0
package main
import (
"fmt"
"strconv"
"sync"
)
// sync.map
var m = sync.Map{} // 自己加了互斥锁
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
key := strconv.Itoa(n)
m.Store(key, n)
v, _ := m.Load(key)
fmt.Println(key, v)
wg.Done()
}(i)
}
wg.Wait()
}
单元测试
https://www.liwenzhou.com/posts/Go/16_test/
Go语言单元测试
基准测试
https://www.liwenzhou.com/posts/Go/16_test/#autoid-2-5-0
Setup和TearDown
https://www.liwenzhou.com/posts/Go/16_test/#autoid-3-5-0
网络编程
https://www.liwenzhou.com/posts/Go/15_socket/
package main
import (
"fmt"
"net/http"
)
// HTTP server
func res(w http.ResponseWriter, r *http.Request) { // 接受参数
fmt.Println(r.Method) // 请求方式
r.ParseForm() // 解析数据
r.Form.Get("name") // 获取form表单中key为name的数据
}
func main() {
http.HandleFunc("/", res) // 访问根路径走res函数
err := http.ListenAndServe(":9090", nil) // 监听本地端口
if err != nil { // 错误捕捉
return
}
}
MySql
https://www.liwenzhou.com/posts/Go/go_mysql/
驱动下载
go get -u github.com/go-sql-driver/mysql
普通连接
package main
import (
"database/sql"
"fmt"
// 导入下载的驱动包, 前面加_ 代表只用了init()
_ "github.com/go-sql-driver/mysql"
)
// 连接mysql
func main() {
// dsn := "user:password@tcp(ip:port)/databasename"
dsn := "root:pwd@tcp(cdb-ecfs2q68.bj.tencentcdb.com:10075)/go_test" // 连接信息
// 注册mysql信息
db, err := sql.Open("mysql", dsn)
if err != nil {
// 这里的错误只是参数格式错误
fmt.Println("db err")
return
}
// 尝试连接数据库
err = db.Ping()
if err != nil {
fmt.Println("db filed")
return
}
}
连接池连接
package main
import (
"database/sql"
"fmt"
// 导入下载的驱动包, 前面加_ 代表只用了init()
_ "github.com/go-sql-driver/mysql"
)
// 连接mysql(连接池版)
// DB 数据库连接句柄
var DB *sql.DB
func initDB(dsn string) (err error) {
DB, err = sql.Open("mysql", dsn) // 注意此处是将全局变量DB赋值mysql连接,所以是 = 而不是 :=
if err != nil {
return err
}
err = DB.Ping()
if err != nil {
return err
}
// 设置最大连接数(有默认)
DB.SetMaxOpenConns(100)
// 最大空闲连接数(有默认)
DB.SetMaxIdleConns(20)
return nil
}
func main() {
// dsn := "user:password@tcp(ip:port)/databasename"
dsn := "root:pwd@tcp(cdb-ecfs2q68.bj.tencentcdb.com:10075)/go_test" // 连接信息
err := initDB(dsn)
if err != nil {
fmt.Println(err)
}
}
占位符
查询数据
package main
import (
"database/sql"
"fmt"
// 导入下载的驱动包, 前面加_ 代表只用了init()
_ "github.com/go-sql-driver/mysql"
)
// 连接mysql(连接池版) 增删改查
// DB 数据库连接句柄
var DB *sql.DB
// User 结构体
type User struct {
id int
name string
age int
}
func initDB(dsn string) (err error) {
DB, err = sql.Open("mysql", dsn) // 注意此处是将全局变量DB赋值mysql连接,所以是 = 而不是 :=
if err != nil {
return err
}
err = DB.Ping()
if err != nil {
return err
}
// 设置最大连接数(有默认)
DB.SetMaxOpenConns(100)
// 最大空闲连接数(有默认)
DB.SetMaxIdleConns(20)
return nil
}
func main() {
// dsn := "user:password@tcp(ip:port)/databasename"
dsn := "root:pwd@tcp(cdb-ecfs2q68.bj.tencentcdb.com:10075)/go_test" // 连接信息
err := initDB(dsn)
if err != nil {
fmt.Println(err)
}
var user User
// 查询单条数据
sqlStr := "SELECT id,name,age FROM `user` WHERE id=1"
err = DB.QueryRow(sqlStr).Scan(&user.id, &user.name, &user.age)
if err != nil {
fmt.Println(err)
}
fmt.Println(user.id, user.name, user.age)
var user2 User
// 查询多条
sqlStr2 := "SELECT id,name,age FROM `user` where id > ?" // ?指占位符
rows, err := DB.Query(sqlStr2, 0) // 将0替换到?
if err != nil {
fmt.Println(err)
}
// rows最后要close,不close会夯住
defer rows.Close()
// 循环读取数据
for rows.Next() {
err = rows.Scan(&user2.id, &user2.name, &user2.age)
if err != nil {
return
}
fmt.Println(user2)
}
}
插入数据
package main
import (
"database/sql"
"fmt"
// 导入下载的驱动包, 前面加_ 代表只用了init()
_ "github.com/go-sql-driver/mysql"
)
// 连接mysql(连接池版) 增删改查
// DB 数据库连接句柄
var DB *sql.DB
// User 结构体
type User struct {
id int
name string
age int
}
func initDB(dsn string) (err error) {
DB, err = sql.Open("mysql", dsn) // 注意此处是将全局变量DB赋值mysql连接,所以是 = 而不是 :=
if err != nil {
return err
}
err = DB.Ping()
if err != nil {
return err
}
// 设置最大连接数(有默认)
DB.SetMaxOpenConns(100)
// 最大空闲连接数(有默认)
DB.SetMaxIdleConns(20)
return nil
}
func main() {
// dsn := "user:password@tcp(ip:port)/databasename"
dsn := "root:pwd@tcp(cdb-ecfs2q68.bj.tencentcdb.com:10075)/go_test" // 连接信息
err := initDB(dsn)
if err != nil {
fmt.Println(err)
}
// 插入数据
sqlStr := "INSERT INTO `user`(`name`, age) VALUES(?, ?)"
name := "u3"
age := "33"
// 执行
ret, err := DB.Exec(sqlStr, name, age)
if err != nil {
return
}
// 获取到插入数据的id(不同数据库有不同实现)
ID, err := ret.LastInsertId()
if err != nil {
return
}
fmt.Println(ID)
}
更新数据
package main
import (
"database/sql"
"fmt"
// 导入下载的驱动包, 前面加_ 代表只用了init()
_ "github.com/go-sql-driver/mysql"
)
// 连接mysql(连接池版) 增删改查
// DB 数据库连接句柄
var DB *sql.DB
// User 结构体
type User struct {
id int
name string
age int
}
func initDB(dsn string) (err error) {
DB, err = sql.Open("mysql", dsn) // 注意此处是将全局变量DB赋值mysql连接,所以是 = 而不是 :=
if err != nil {
return err
}
err = DB.Ping()
if err != nil {
return err
}
// 设置最大连接数(有默认)
DB.SetMaxOpenConns(100)
// 最大空闲连接数(有默认)
DB.SetMaxIdleConns(20)
return nil
}
func main() {
// dsn := "user:password@tcp(ip:port)/databasename"
dsn := "root:pwd@tcp(cdb-ecfs2q68.bj.tencentcdb.com:10075)/go_test" // 连接信息
err := initDB(dsn)
if err != nil {
fmt.Println(err)
}
// 更新数据
sqlStr := "UPDATE `user` SET age=? WHERE id=?"
age := 222
ID := 2
ret, err := DB.Exec(sqlStr, age, ID)
if err != nil {
return
}
// 返回受影响的行数
rowLen, err := ret.RowsAffected()
if err != nil {
return
}
fmt.Println(rowLen)
}
删除数据
package main
import (
"database/sql"
"fmt"
// 导入下载的驱动包, 前面加_ 代表只用了init()
_ "github.com/go-sql-driver/mysql"
)
// 连接mysql(连接池版) 增删改查
// DB 数据库连接句柄
var DB *sql.DB
// User 结构体
type User struct {
id int
name string
age int
}
func initDB(dsn string) (err error) {
DB, err = sql.Open("mysql", dsn) // 注意此处是将全局变量DB赋值mysql连接,所以是 = 而不是 :=
if err != nil {
return err
}
err = DB.Ping()
if err != nil {
return err
}
// 设置最大连接数(有默认)
DB.SetMaxOpenConns(100)
// 最大空闲连接数(有默认)
DB.SetMaxIdleConns(20)
return nil
}
func main() {
// dsn := "user:password@tcp(ip:port)/databasename"
dsn := "root:pwd@tcp(cdb-ecfs2q68.bj.tencentcdb.com:10075)/go_test" // 连接信息
err := initDB(dsn)
if err != nil {
fmt.Println(err)
}
// 删除数据
sqlStr := "DELETE FROM `user`WHERE id=?"
ID := 3
ret, err := DB.Exec(sqlStr, ID)
if err != nil {
return
}
// 返回受影响的行数
rowLen, err := ret.RowsAffected()
if err != nil {
return
}
fmt.Println(rowLen)
}
预处理
预处理增删改
package main
import (
"database/sql"
"fmt"
// 导入下载的驱动包, 前面加_ 代表只用了init()
_ "github.com/go-sql-driver/mysql"
)
// 连接mysql(连接池版) 增删改查
// DB 数据库连接句柄
var DB *sql.DB
// User 结构体
type User struct {
id int
name string
age int
}
func initDB(dsn string) (err error) {
DB, err = sql.Open("mysql", dsn) // 注意此处是将全局变量DB赋值mysql连接,所以是 = 而不是 :=
if err != nil {
return err
}
err = DB.Ping()
if err != nil {
return err
}
// 设置最大连接数(有默认)
DB.SetMaxOpenConns(100)
// 最大空闲连接数(有默认)
DB.SetMaxIdleConns(20)
return nil
}
func main() {
// dsn := "user:password@tcp(ip:port)/databasename"
dsn := "root:pwd@tcp(cdb-ecfs2q68.bj.tencentcdb.com:10075)/go_test" // 连接信息
err := initDB(dsn)
if err != nil {
fmt.Println(err)
}
// 预处理
sqlStr := "INSERT INTO `user`(`name`, age) VALUES(?, ?)"
// 不拼接
stmt, err := DB.Prepare(sqlStr)
if err != nil {
return
}
// 注册关闭
defer stmt.Close()
// 执行重复的插入命令
for i := 4; i < 20; i++ {
name := fmt.Sprintf("u%d", i)
stmt.Exec(name, i)
}
}
预处理查询
package main
import (
"database/sql"
"fmt"
// 导入下载的驱动包, 前面加_ 代表只用了init()
_ "github.com/go-sql-driver/mysql"
)
// 连接mysql(连接池版) 增删改查
// DB 数据库连接句柄
var DB *sql.DB
// User 结构体
type User struct {
id int
name string
age int
}
func initDB(dsn string) (err error) {
DB, err = sql.Open("mysql", dsn) // 注意此处是将全局变量DB赋值mysql连接,所以是 = 而不是 :=
if err != nil {
return err
}
err = DB.Ping()
if err != nil {
return err
}
// 设置最大连接数(有默认)
DB.SetMaxOpenConns(100)
// 最大空闲连接数(有默认)
DB.SetMaxIdleConns(20)
return nil
}
func main() {
// dsn := "user:password@tcp(ip:port)/databasename"
dsn := "root:pwd@tcp(cdb-ecfs2q68.bj.tencentcdb.com:10075)/go_test" // 连接信息
err := initDB(dsn)
if err != nil {
fmt.Println(err)
}
// 预处理
sqlStr := "SELECT id, name, age FROM user WHERE id=?"
// 不拼接
stmt, err := DB.Prepare(sqlStr)
if err != nil {
return
}
// 注册关闭
defer stmt.Close()
// 执行重复的插入命令
for i := 1; i < 20; i++ {
rows, err := stmt.Query(i)
if err != nil {
continue
}
defer rows.Close()
var name string
var id int
var age int
for rows.Next() {
rows.Scan(&id, &name, &age)
fmt.Println(id, name, age)
}
}
}
事务
package main
import (
"database/sql"
"fmt"
// 导入下载的驱动包, 前面加_ 代表只用了init()
_ "github.com/go-sql-driver/mysql"
)
// 连接mysql(连接池版) 事务
// DB 数据库连接句柄
var DB *sql.DB
// User 结构体
type User struct {
id int
name string
age int
}
func initDB(dsn string) (err error) {
DB, err = sql.Open("mysql", dsn) // 注意此处是将全局变量DB赋值mysql连接,所以是 = 而不是 :=
if err != nil {
return err
}
err = DB.Ping()
if err != nil {
return err
}
// 设置最大连接数(有默认)
DB.SetMaxOpenConns(100)
// 最大空闲连接数(有默认)
DB.SetMaxIdleConns(20)
return nil
}
func main() {
// dsn := "user:password@tcp(ip:port)/databasename"
dsn := "root:pwd@tcp(cdb-ecfs2q68.bj.tencentcdb.com:10075)/go_test" // 连接信息
err := initDB(dsn)
if err != nil {
fmt.Println(err)
}
// 开始事务
tx, err := DB.Begin()
if err != nil {
return
}
// 执行事务内操作
sqlStr := "UPDATE user SET age=? WHERE id=?"
age := "30"
ID := "3"
// 执行
_, err = tx.Exec(sqlStr, age, ID)
if err != nil {
// 出现错误回滚
tx.Rollback()
return
}
age = "40"
ID = "4"
// 执行
_, err = tx.Exec(sqlStr, age, ID)
if err != nil {
// 出现错误回滚
tx.Rollback()
return
}
// 走到这里证明两条语句都执行成功
// commit提交事务
err = tx.Commit()
if err != nil {
// commit也可能失败
tx.Rollback()
return
}
}
第三方库 SQLX
安装
go get -u github.com/jmoiron/sqlx
基本使用
查
package main
import (
"fmt"
"github.com/jmoiron/sqlx"
// 导入下载的驱动包, 前面加_ 代表只用了init()
_ "github.com/go-sql-driver/mysql"
)
// 连接mysql(连接池版)
// DB 数据库连接句柄
var DB *sqlx.DB
// User 结构体
type User struct {
ID int `db:"id"` // db的tag为sqlx所用,标记对应的字段名
Name string `db:"name"`
Age int `db:"age"`
}
func initDB(dsn string) (err error) {
DB, err = sqlx.Connect("mysql", dsn) // 注意此处是将全局变量DB赋值mysql连接,所以是 = 而不是 :=
if err != nil {
return err
}
// 设置最大连接数(有默认)
DB.SetMaxOpenConns(100)
// 最大空闲连接数(有默认)
DB.SetMaxIdleConns(20)
return nil
}
// 查询单行
func queryDemo() {
sqlStr := "SELECT id, name, age FROM user WHERE id=?"
var user User
err := DB.Get(&user, sqlStr, 1)
if err != nil {
return
}
fmt.Println(user)
}
// 查询多行
func querysDemo() {
sqlStr := "SELECT id, name, age FROM user WHERE id>?"
var users []User // 切片每个都是User
err := DB.Select(&users, sqlStr, 1)
if err != nil {
return
}
for _, user := range users {
fmt.Println(user)
}
}
func main() {
// dsn := "user:password@tcp(ip:port)/databasename"
dsn := "root:pwd@tcp(cdb-ecfs2q68.bj.tencentcdb.com:10075)/go_test" // 连接信息
err := initDB(dsn)
if err != nil {
fmt.Println(err)
}
// queryDemo()
querysDemo()
}
增/删/改
事务
package main
import (
"fmt"
"github.com/jmoiron/sqlx"
// 导入下载的驱动包, 前面加_ 代表只用了init()
_ "github.com/go-sql-driver/mysql"
)
// 连接mysql(连接池版)
// DB 数据库连接句柄
var DB *sqlx.DB
// User 结构体
type User struct {
ID int `db:"id"` // db的tag为sqlx所用,标记对应的字段名
Name string `db:"name"`
Age int `db:"age"`
}
func initDB(dsn string) (err error) {
DB, err = sqlx.Connect("mysql", dsn) // 注意此处是将全局变量DB赋值mysql连接,所以是 = 而不是 :=
if err != nil {
return err
}
// 设置最大连接数(有默认)
DB.SetMaxOpenConns(100)
// 最大空闲连接数(有默认)
DB.SetMaxIdleConns(20)
return nil
}
// 事务
func transDemo() {
// 如果MustExec引发panic, Beginx会自己Rollback
tx, err := DB.Beginx()
if err != nil {
return
}
sqlStr := "UPDATE user SET age=? WHERE id=?"
age := "30"
ID := "3"
tx.MustExec(sqlStr, age, ID) // 带Must方法一般指一出错直接panic
age = "40"
ID = "4"
tx.MustExec(sqlStr, age, ID) // 带Must方法一般指一出错直接panic
err = tx.Commit() // 提交
if err != nil {
tx.Rollback()
return
}
}
func main() {
// dsn := "user:password@tcp(ip:port)/databasename"
dsn := "root:pwd@tcp(cdb-ecfs2q68.bj.tencentcdb.com:10075)/go_test" // 连接信息
err := initDB(dsn)
if err != nil {
fmt.Println(err)
}
}
Go使用Redis
https://www.liwenzhou.com/posts/Go/go_redis/
package main
import (
"fmt"
"github.com/go-redis/redis"
)
// 连接池
var redisDB *redis.Client
func initClient() (err error) {
redisDB = redis.NewClient(&redis.Options{ // 替换全局变量 redisDB
Addr: "127.0.0.1:6379", // HOST
Password: "", // 密码
DB: 0, // 数据库
})
_, err = redisDB.Ping().Result() // 连接
if err != nil {
return err
}
return nil
}
func main() {
err := initClient()
if err != nil {
return
}
// 获取a1的值
ret := redisDB.Get("a1").Val()
fmt.Println(ret)
}
NSQ消息队列
https://www.liwenzhou.com/posts/Go/go_nsq/
依赖管理(old)
https://www.liwenzhou.com/posts/Go/go_dependency/
安装
go get -u github.com/tools/godep
保存依赖信息
godep save
会在当前目录下生成两个文件夹 Godeps
vendor
Godeps
存放项目依赖的包信息vendor
存放项目依赖包的副本
使用
依赖管理(new)
https://www.liwenzhou.com/posts/Go/go_dependency/#autoid-2-5-0
go mod命令
打开mod支持
set GO111MODULE=on
生成mod
go mod init 项目名 // 初始化
在当前目录下生成 go.mod 文件
下载依赖
go mod download
根据 go.mod 文件下载依赖
Gin框架
https://gin-gonic.com/zh-cn/
https://www.liwenzhou.com/posts/Go/Gin_framework/
安装/更新
go get -u github.com/gin-gonic/gin
基础版
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func indexHandler(c *gin.Context) {
// 返回 json, 状态码使用http包中的StatusOK(200)
c.JSON(http.StatusOK, gin.H{
"msg": "hello",
})
}
func main() {
// 路由模式为 Default
router := gin.Default()
// /hello GET 返回JSON 状态码200
router.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"msg": "hello",
})
})
// /index GET
router.GET("/index", indexHandler)
// 启动
router.Run("127.0.0.1:9000")
}
Gin请求方式
Gin支持接受所有请求方法, 包括GET/POST/PUT等等
当我们不想在路由时候限制某个请求的请求方式时, 选择 Any 即可
在函数内获取该请求的方式为 c.Request.Method
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func indexHandler(c *gin.Context) {
// 字符串形式 POST/GET/PUT ...
fmt.Println(c.Request.Method)
}
// 路由组
func main() {
// Default默认使用Use 加了两个全局中间件, Logger(), Recovery(), Logger是打印日志, Recovery是panic返回500
r := gin.Default()
// Any代表匹配所有请求方式
r.Any("/index", indexHandler)
r.Run("127.0.0.1:9000")
}
Gin的渲染
package main
import (
"github.com/gin-gonic/gin"
)
func indexHandler(c *gin.Context) {
// 返回 json, 状态码使用http包中的StatusOK(200)
// c.JSON(http.StatusOK, gin.H{
// "msg": "hello",
// })
// 返回 HTMl(配合模板语法) 状态码, 模板文件, 模板文件传值
// c.HTML(http.StatusOK, "web/login.html", gin.H{
// "msg": "login",
// })
// 返回结构体对象(结构体字段首字母一定大写)
// type info struct {
// Info string `json:"info"`
// }
// i1 := info{
// Info: "aaa",
// }
// c.JSON(http.StatusOK, i1)
// 返回 XML
// c.XML(http.StatusOK, gin.H{
// "msg": "hello",
// })
// 结构体的XML
// type info struct {
// Info string
// }
// i1 := info{
// Info: "aaa",
// }
// c.XML(http.StatusOK, i1)
// yaml
// c.YAML(http.StatusOK, gin.H{
// "msg": "hello",
// })
}
func main() {
// 路由模式为 Default
router := gin.Default()
// 加载模板文件夹(当前目录下的tpmplates文件夹下所有文件夹下所有模板文件)
router.LoadHTMLGlob("templates/**/*")
// 设置静态文件目录(代码使用路径(url访问), 实际路径)
router.Static("static", "./statics")
// /index GET
router.GET("/index", indexHandler)
// 启动
router.Run("127.0.0.1:9000")
}
Gin获取参数
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func indexHandler(c *gin.Context) {
// 获取url参数
// a := c.Query("a") // 没有参数为nil
a := c.DefaultQuery("a", "av") // 没有参数默认av
// 获取form参数
// f := c.PostForm("f") // 没有参数为nil
f := c.DefaultPostForm("f", "fdef") // 没有参数默认 fdef
c.JSON(http.StatusOK, gin.H{
"a": a,
"f": f,
})
}
func urlHandler(c *gin.Context){
// 提取url参数
u := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"u": u,
})
}
func main() {
// 路由模式为 Default
router := gin.Default()
// /index GET
router.GET("/index", indexHandler)
// 获取URL上的可变参数 id为可变参数
router.GET("/urlget/:id", urlHandler)
// 启动
router.Run("127.0.0.1:9000")
}
Gin跳转
HTTP跳转
c.Redirect(状态码, "地址")
路由跳转
c.Request.URL.Path = "跳转路由"
r.HandleContext(c)
Gin路由组
// 路由组
func main() {
r := gin.Default()
// 普通路由(多了不易维护)
r.GET("/book", bookListHandler)
r.POST("/book", bookInsertHandler)
r.DELETE("/book/:ID", bookDeleteHandler)
// 分组路由(易维护,可多层嵌套)
apiV1Group := r.Group("/api/v1")
{
apiV1Group.GET("/index", v1IndexHandler) // URL: /api/v1/index
apiV1Group.GET("/home", v1HomeHandler) // URL: /api/v1/home
}
r.Run("127.0.0.1:9000")
}
Gin上传文件
单文件
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func fileHandler(c *gin.Context) {
fileObj, err := c.FormFile("file") // file为字段名
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"err": err,
})
return
}
// filePath 要保存在本地的路径(相对)
filePath := fmt.Sprintf("./%s", fileObj.Filename) // fileObj.Filename: 上传文件的文件名
// 保存文件到本地
c.SaveUploadedFile(fileObj, filePath)
c.JSON(http.StatusOK, gin.H{
"data": filePath,
})
}
// 路由组
func main() {
r := gin.Default()
// 提交文件默认是32MB大小
r.MaxMultipartMemory = 8<<20 // 8MB
// 接受文件
r.POST("/file", fileHandler)
r.Run("127.0.0.1:9000")
}
多文件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func fileHandler(c *gin.Context) {
form, err := c.MultipartForm()
if err != nil {
return
}
files := form.File["file"]
for index, file := range files {
// index 顺序
fmt.Println(index)
dst := fmt.Sprintf("./%s", file.Filename)
c.SaveUploadedFile(file, dst)
}
}
// 路由组
func main() {
r := gin.Default()
// 提交文件默认是32MB大小
r.MaxMultipartMemory = 8 << 20 // 8MB
// 接受文件
r.POST("/file", fileHandler)
r.Run("127.0.0.1:9000")
}
Gin中间件
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
)
func indexHandler(c *gin.Context) {
}
// 统计耗时的中间件
func castTime(c *gin.Context) {
// 获取当前时间
startTime := time.Now()
// 运行下一个注册的Handler函数
c.Next()
// 统计耗时
cast := time.Since(startTime)
fmt.Println(cast)
}
// 路由组
func main() {
// Default默认使用Use 加了两个全局中间件, Logger(), Recovery(), Logger是打印日志, Recovery是panic返回500
r := gin.Default()
// (全局中间件) r.Use代表全局中间件
r.Use(castTime)
// (单url中间件) 路由注册时可以写多个函数, 请求进入先进第一个再往后走, 可以做中间件
r.GET("/index", castTime, indexHandler)
// (组中间件)
apiV1Group := r.Group("/api/v1", castTime)
{
apiV1Group.GET("/index", v1IndexHandler) // URL: /api/v1/index
apiV1Group.GET("/home", v1HomeHandler) // URL: /api/v1/home
}
r.Run("127.0.0.1:9000")
}
参数绑定
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
// 数据绑定
// User struct
type User struct {
// form tag 代表该字段对应的请求字段名, binding tag 代表没接收到传错(不写默认不报错)
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
func indexHandler(c *gin.Context) {
if c.Request.Method == "POST" {
// 实例化
var u User
// 将结构体u与用户传来的字段进行绑定(根据Content-Type来按格式解析)
err := c.ShouldBind(&u)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"err": err.Error(),
})
return
}
// 绑定成功,拿到数据
fmt.Println(u)
}
}
// 路由组
func main() {
// Default默认使用Use 加了两个全局中间件, Logger(), Recovery(), Logger是打印日志, Recovery是panic返回500
r := gin.Default()
// Any代表匹配所有请求方式
r.Any("/index", indexHandler)
r.Run("127.0.0.1:9000")
}
Gin连表查询
跨表查询我们可以使用多种途径应对
sqlx结构体嵌套
因为sqlx传入的是一个结构体, 而与数据库的同步需要对照 db 的 tag, 所以我们可以使用结构体嵌套
要注意的是结构体嵌套过程中务必避免结构体的字段相同否则只会返回最外面的结构体(实际上只是 c.JSON 时出现的错误)
sqlx结构体不镶嵌
我们也可以直接将所有返回值写一个结构体里
sql
我们不使用sqlx模块, 而是直接赋值给变量, 再组合拿到想要的结果也是可以的
logrus模块(日志)
https://www.liwenzhou.com/posts/Go/go_logrus/
使用
package main
// logrous
import (
log "github.com/sirupsen/logrus"
)
func main() {
// 设置此项输出的日志为JSON格式
log.SetFormatter(&log.JSONFormatter{})
// 设置此项有字段表示打印日志的文件位置(对性能有影响,不推荐线上使用)
log.SetReportCaller(true)
// 终端打印日志
Userinf := log.WithFields(log.Fields{
"name": "t1",
"age": 3000,
})
// .Info指级别
Userinf.Info("user info")
// time="2019-08-22T16:36:00+08:00" level=info msg="user info" age=3000 name=t1
log.Info("info") // 可不加自定义key/v
// time="2019-08-22T17:08:16+08:00" level=info msg=info
}
搭配Gin使用
https://www.liwenzhou.com/posts/Go/go_logrus/#autoid-0-12-0
安装
go get -u github.com/sirupsen/logrus
Cookie/Session
https://www.liwenzhou.com/posts/Go/Cookie_Session/
Cookie
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
// Cookie示例
// User struct
type User struct {
// form tag 代表该字段对应的请求字段名, binding tag 代表没接收到传错(不写默认不报错)
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
func getCookie(c *gin.Context) {
username, err := c.Cookie("username")
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 1005,
})
return
}
// 设置username并执行下一个函数
c.Set("username", username)
c.Next()
}
func cookieHandler(c *gin.Context) {
// GET验证Cookie
if c.Request.Method == "GET" {
username, ok := c.Get("username")
if ok {
// 取到
fmt.Println(username.(string))
} else {
// 没取到
c.JSON(http.StatusOK, gin.H{
"code": 1008,
})
return
}
}
// POST生成Cookie
if c.Request.Method == "post" {
// 参数绑定
var u User
err := c.ShouldBind(&u)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
})
return
}
// 身份验证
if u.Username == "t1" && u.Password == "t1p" {
// 生成Cookie
c.SetCookie("username", u.Username, 20, "/", "127.0.0.1", false, true)
} else {
c.JSON(http.StatusOK, gin.H{
"code": 1003,
})
return
}
}
}
func main() {
r := gin.Default()
r.Any("/cookie", getCookie, cookieHandler)
r.Run("127.0.0.1:9000")
}