GolangNote

1. Go基础简介

Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。

1.1 Go 特点

  • 强类型静态语言
  • 语法简单
  • 垃圾回收
  • 快速编译
  • 简单的依赖管理
  • 优秀的并发能力

1.2 Go 资源地址

1.3. Windows 安装

  1. 下载 go 安装包 msi 文件,按照提示进行安装
  2. 安装完成后重启所有终端或命令行

1.4. Linux 或 MacOS 安装和配置环境变量

  1. 下载go包 wget -c https://go.dev/dl/go1.19.5.linux-amd64.tar.gz
  2. 删除之前的go安装包 rm -rf /usr/local/go
  3. 将安装包解压到指定目录 tar -C /usr/local -xzf go1.19.5.linux-amd64.tar.gz
  4. 配置环境变量 export PATH=$PATH:/usr/local/go/bin

1.5 卸载

  1. 删除 go 的安装目录,通常为 /usr/local/go
  2. 删除 go 的环境变量,通常为 /etc/profile 或者 ~/.bash_profile 中的 /usr/local/go/bin 目录

1.6 配置

使用go命令配置:

  • go env -w GOPROXY=https://goproxy.cn,direct - 配置proxy
  • go env -w GO111MODULE="on" - 配置使用go module,1.13版本后默认开启

使用环境变量配置

  • export GO111MODULE="on" - 配置使用go module,1.13版本后默认开启
  • export GOPROXY=https://goproxy.io - 配置proxy

1.7 相关命令

  • go version - 查看go的版本
  • go build - 项目目录下编译,在该项目目录下生成可执行文件
  • go build 路径名 - 在其他路径下执行go build,路径名从工程目录的scr开始往下写,在当前目录下生成可执行文件
  • go build -o 自定义文件名 - 自定义可执行文件文件名
  • go run 源码文件名 - 以脚本文件执行,不生成可执行文件
  • go install - 类似分两步执行,第一步为执行go build,第二步为将可执行文件移动到GOPATH/bin文件夹下
  • go env - 查看所有go的环境配置
    • -w - 可以以 NAME=VALUE 的方式修改环境变量,例如:go env -w GO111MODULE=on
    • -u - 将用 -w 设置的变量恢复为默认值,例如:go env -u GO111MODULE
    • -json - 以json格式输出
  • go get - 下载安装第三方包
    • -t - 让命令程序同时下载并安装指定的代码包中的测试源码文件中依赖的代码包。
    • -u - 让命令利用网络来更新已有代码包及其依赖包。默认情况下,该命令只会从网络上下载本地不存在的代码包,而不会更新已有的代码包。
  • go mod - 使用go module管理三方包
  • go clean - 命令会删除掉执行其它命令时产生的一些文件和目录

1.8 跨平台编译

命令格式

SET CGO_ENABLED=0  // 禁用CGO,使用cgo不支持跨平台,默认值为1
SET GOOS=linux  // 目标平台是linux,windows写windows,mac写drawin
SET GOARCH=amd64  // 目标处理器架构是amd64

runtime.GOOS - 获取当前操作系统平台

1.9 注意事项

注释:

  • // - 行注释
  • /* */ - 块注释

大括号:Go语言规定,左括号必须与关键字在一行,不能放在其他位置。

for
{// 错误,无法通过编译
xxx
}

注意:{必须和关键字在同一行,如果有类似else的语句,else必须和上一个语句块的}在一行

1.10 go mod管理包

  1. 使用 go mod init 项目名称 初始化项目,如 go mod init github.com/stolenzc/hello_world
  2. vscode配置以下内容使用vendor依赖

     "go.toolsEnvVars": {
     "GOFLAGS": "-mod=vendor"
     }
    
  3. 使用 go mod tidy 维护go.mod文件。

  4. 使用 go build -mod=vendor 使用vendor目录的依赖进行编译

1. 常量变量和标识符

标识符规则

数字、字母、下划线组成,数字不能开头

变量和函数命名规范遵循驼峰原则

可见性

  • 声明在函数内部,是函数的本地值,仅在函数内部可用
  • 声明在函数外部,对当前包可见,同一个包的所有.go文件均可见
  • 声明在函数外部且首字母是大写,对所有包可见,是全局值

关键字

25个关键字

break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var

37个保留字

Constants:
        true    false   iota    nil

Types:
        int     int8    int16       int32       int64
        uint    uint8   uint16      uint32      uint64      uintptr
        float32 float64 complex128  complex64
        bool    byte    rune        string      error

Functions:
        make    len     cap     new     append
        copy    close   delete  complex real
        imag    panic   recover

声明

GO声明的四种方式

  • var(声明变量)
  • const(声明常量)
  • type(声明类型)
  • func(声明函数)

变量

变量声明

var name string // 默认声明

var (
  name string
  age int
) // 批量声明

var name string = "大锤" // 声明并初始化

var age = 18 // 自动识别补足类型
name := "大锤" // 短变量声明,不能用于全局变量

var name, age = "大锤", 18

_ - 匿名变量不需要声明,不分配内存,可以重复使用

注意

  • 默认初始化值:int(0), float(0), string(""), boolean(false), 切片、函数、指针(nil)
  • GO语言中局部变量声明后必须使用
  • 匿名变量不会分配内存空间,可以重复声明

常量

常量声明用const,常量在声明时必须赋值:

声明

const pi = 3.14259265 //不需要类型声明,自动推导,必须赋值
const (
  pi = 3.14
  e = 2.7182
  n
) // 多个常量声明,不写值默认等于上一行的值

iota - 常量计数器,const出现被置为零,每新增一行,iota值加1

// 遇到const就置零
const n5 = iota //0
const (
  a, b = iota + 1, iota + 2 //1,2
  c, d                      //2,3
  e, f                      //3,4
)

类型

类型声明有两种方式


type byte = uint8 // 给uint8起别名 (此时两个类型可以互相赋值和直接使用,只是一个别名,不存在新的类型)
type Duration int64 // 定义一个新的类型 (此时两个类型互不相同,相应的变量无法直接替换使用)

数据类型

值类型和引用类型

值类型:

  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64
  • float32, float64
  • complex64, complex128
  • string, bool, array

引用类型:

  • slice, map, chan

整型

类型 描述
uint8 无符号 8位整型 (0 到 2^8-1)
uint16 无符号 16位整型 (0 到 2^16-1)
uint32 无符号 32位整型 (0 到 2^32-1)
uint64 无符号 64位整型 (0 到 2^64-1)
int8 有符号 8位整型 (-2^7 到 2^7-1)
int16 有符号 16位整型 (-2^15 到 2^15-1)
int32 有符号 32位整型 (-2^31 到 2^31-1)
int64 有符号 64位整型 (-2^63 到 2^63-1)
uint 32位操作系统上就是uint32,64位操作系统上就是uint64
int 32位操作系统上就是int32,64位操作系统上就是int64
uintptr 无符号整型,用于存放一个指针
  • len(v Type) int - 返回数据类型占用的字节数
  • 0b开头表示二进制数,0o开头表示八进制数,0x开头表示十六进制数
  • 123_456 允许用_分割数字,等同于123456

浮点型

类型 描述
float32 最大范围3.4e38,可以使用math.MaxFloat32查看
float64 最大范围1.8e308,可以说使用math.MaxFloat64查看

复数

类型 描述
complex64 complex64的实部和虚部为32位
complex128 complex128的实部和虚部为64位
  • real(c ComplexType) FloatType - 返回complex的实部
  • imag(c ComplexType) FloatType - 返回complex的虚部

布尔

类型 描述
bool 有true和false两个值,不可参与其他类型运算,无法转换为其他类型

字符

类型 描述
byte uint8类型,代表了ASCII码的一个字符。
rune int32类型,代表一个 UTF-8字符

字符串

注意:字符串不可变,修改字符串通常需要转换为字符的切片类型,[]byte[]rune,然后进行修改。会重新进行内存分配。

类型 描述
string 需要用""包裹,多行字符串使用反引号`包裹,使用UTF-8编码

字符串转义

转义符 含义
\r 回车符(返回行首)
\n 换行符(直接跳到下一行的同列位置)
\t 制表符
\' 单引号
\" 双引号
\\ 反斜杠

字符串常用操作

方法 介绍
len(str) 求长度,获得的是字节长度
+或fmt.Sprintf 拼接字符串
  • 字符串 str 的第 1 个字节:str[0]
  • 第 i 个字节:str[i-1]
  • 最后 1 个字节:str[len(str)-1]

类型转换

类型(表达式) - 将表达式的结果转换为所指定的类型

格式化占位符号

符号 含义
%d %b %o 表示 十/二/八 进制数
%x %X 表示十六进制数,字母用 小/大 写
%f %.nf 表示浮点数,n为保留小数位数
%U Unicode格式
%s %q 输出字符串表示(string或[]byte),q为%s外加双引号围绕
%p 指针,格式为0x开头的十六进制数
%t 布尔值
%T 相应值的类型
%v 相应值的默认格式
%+v 打印结构体会添加字段名
%#v 相应值的go语法表示,类型+值

运算符

+-*/%、 - 加减乘除余

注意++(自增)和--(自减)在Go语言中是单独的语句,并不是运算符

==!=>>=<<= - 关系运算符

&&||! - 逻辑运算符

&|^>><< - 位运算符(与、或、异或、右移、左移)

=+=-=*=\=%=<<=>>=&=|=^= - 赋值运算符(等、加等、减等、乘等、除等、余等、左移等、右移等、与等、或等、异或等)

指针

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。

  • var intP *int - 定义一个指向整型的指针
  • intP = &i1 - 给指针变量赋值
  • *intP - 获取i1的值

指针的一些特点:

  • 指针的格式化标识符为 %p
  • 空指针值为nil
  • go语言中无法进行指针运算

数组

注意:数组是定长的

声明:

//类型是[5]int,是一个数组类型,函数调用会进行拷贝操作,不会修改实参
var arr1 [5]int
//类型是*[5]int,是一个指针,函数调用会修改实参
var arr1 = new([5]int)
//创建并初始化
var arrAge = [5]int{18, 20, 15, 22, 16}
var arrLazy = [...]int{5, 6, 7, 8, 22} //实际为切片
var arrKeyValue = []string{3: "Chris", 4: "Ron"}//给指定下标赋值

for-range操作数组

//获取的分别是下标和值
for index, value := range arr{}

切片

切片是引用类型,长度可变

声明:

//不需要说明长度,未初始化之前默认为 nil,长度为 0
var identifier []type
//初始化
var slice1 []type = arr1[start:end]
var x = []int{2, 3, 5, 7, 11}
//用make创建一个切片,cap可不填。默认为len的长度
var slice1 []type = make([]type, len, cap)
var slice2 []int = new([100]int)[0:50]

注意:

  • make(T)返回一个类型为T的初始值,适用于切片,map和channel
  • new(T)返回一个类型为*T的内存地址,指向T,使用于数组和结构体

切片的方法:

  • len() - 计算切片的长度
  • cap() - 切片的最长长度。等于切片的长度 + 数组除切片之外的长度
  • copy(dst, src []T) - 将类型为 T 的切片从源地址 src 拷贝到目标地址 dst,覆盖 dst 的相关元素,并且返回拷贝的元素个数
  • slice1 = append(slice1, silce2...) - 将切片2追加到切片1后
  • a = append(a[:i], a[i+1:]...) - 删除位于索引 i 的元素
  • a = append(a[:i], a[j:]...) - 切除切片 a 中从索引 i 至 j 位置的元素
  • a = append(a, make([]T, j)...) - 为切片 a 扩展 j 个元素长度
  • a = append(a[:i], append([]T{x}, a[i:]...)...) - 在索引 i 的位置插入元素 x
  • a = append(a[:i], append(make([]T, j), a[i:]...)...) - 在索引 i 的位置插入长度为 j 的新切片
  • a = append(a[:i], append(b, a[i:]...)...) - 在索引 i 的位置插入切片 b 的所有元素
  • x, a = a[len(a)-1], a[:len(a)-1] - 取出位于切片 a 最末尾的元素 x
  • a = append(a, x) - 将元素 x 追加到切片 a

Map

map是引用类型,用:=需要用make来分配内存, map是无序的

声明:

var map1 map[keytype]valuetype
var map1 map[string]int
//map可以不固定大小,也可以设定cap标明map的最大容量
var map2 := make(map[string]int, cap)

注意:

  • 数组、切片和结构体不能作为 key,string、int、float、指针、接口类型可以
  • map排序需要先把key取出来进行排序,在根据排序后的key来取value

map的方法:

  • len(map1) - 获取map的键值对数量
  • val1, isPresent = map1[key1] - 判断map1是否存在key1这个键,isPresent为true表示存在,为false表示不存在
  • delete(map1, key1) - 删除key1,不存在不会报错
  • for key, value := range map1{} - for-range循环得到的是key和value,只取一个值得到的是key

流程控制

if

if condition1 {
    // do something
} else if condition2 {
    // do something else
} else {
    // catch-all or default
}
  • condition1 处可以包含多条语句

for

for 初始语句;条件表达式;结束语句{
    循环体语句
}
  • 初始语句和结束语句都可以省略,但是分号不能省略
  • 当初始语句和结束语句同时省略时,可以省略分号
  • 当for后没有条件时,为死循环
  • for循环可以通过breakgotoreturnpanic语句强制退出循环

for-range

for1,2 := range3{
    循环体语句
}
  • for range遍历数组、切片、字符串、map 及通道(channel)
  • 数组、切片、字符串返回索引和值
  • map返回键和值
  • 通道(channel)只返回通道内的值

switch-case

func switchDemo1() {
    finger := 3
    switch finger {
    case 1:
        fmt.Println("大拇指")
    case 2:
        fmt.Println("食指")
    case 3:
        fmt.Println("中指")
    case 4:
        fmt.Println("无名指")
    case 5:
        fmt.Println("小拇指")
    fallthrough
    default:
        fmt.Println("无效的输入!")
    }
}
  • case执行后不会继续向后执行
  • 使用fallthrougt来继续向后执行,实现C语言默认的switch-case模式
  • switch后面也可以不写任何值,默认为True进行case判断

标签与goto

标签:大写字母后面连接冒号。HERE:

goto加标签可以跳到标签处继续执行。goto HERE

Break

break语句可以结束forswitchselect的代码块。

break语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的forswitchselect的代码块上。

Continue

continue语句可以结束当前循环,开始下一次的循环迭代过程,仅限在for循环内使用。

continue语句后添加标签时,表示开始标签对应的循环。

十. 常用包介绍

1. bytes

bytes.Buffer - 一种长度可变的bytes,提供Read和Write方法

通过buffer串联字符串:

var buffer bytes.Buffer

buffer.WriteString(s)//s追加到buffer后面
s = buffer.String()//将buffer转换为字符串

2. sort

  • Ints(x []int) - 对int类型对切片排序
  • IntsAreSorted(x []int) bool - 检查数组是否已经排序
  • Float64s(x []float64) - 对float64元素排序
  • Strings(x []string) - 排序字符串元素
  • SearchInts(a []int, x int) int - 搜索n在a中的位置。使用的是二分法,先排序后搜索
  • SearchFloat64s(a []float64, x float64) int - 搜索float64
  • SearchStrings(a []string, x string) int - 搜索字符串

3. strings

  • HasPrefix(s, prefix string) bool - 判断s是否以prefix开头
  • HasSuffix(s, suffix string) bool - 判断s是否以suffix结尾
  • Contains(s, substr string) bool - 判断s是否包含substr
  • Index(s, substr string) int - 获取substr在s中第一次出现的索引,-1表示不存在
  • LastIndex(s, substr string) int - 获取substr在s中第最后出现的索引,-1表示不存在
  • IndexRune(s string, r rune) int - 查询非ASCII字符在s中的位置
  • Replace(s, old, new string, n int) string - 将s中的old替换为new,n为替换次数,-1为全部替换
  • Count(s, substr string) int - 计算substr在s中出现的非重叠次数
  • Repeat(s string, count int) string - 返回s重复count次的字符串
  • ToLower(s string) string - 将s中所有字符转为小写
  • ToUpper(s string) string - 将s中所有字符转为大写
  • Trim(s, cutset string) string - 剔除字符串开头和结尾的cutset字符
  • TrimLeft(s, cutset string) string - 剔除字符串开头的cutset字符
  • TrimRight(s, cutset string) string - 剔除字符串结尾的cutset字符
  • TrimSpace(s string) string - 剔除字符串开头和结尾的空白符号
  • Fields(s string) []string - 将s用连续的空格进行分割,返回字符串切片
  • Split(s, sep string) []string - 将s用sep进行分割,返回字符串切片
  • Join(elems []string, sep string) string - 将切片元素通过sep进行连接
  • NewReader(str) - 生成一个Reader并读取字符串中的内容,返回一个指向该Reader的指针

    Read() - 实现io.Reader接口 ReadByte() - 从字符串中读取下一个byte ReadRune() - 从字符串中读取下一个rune

4. strconv

  • Itoa(i int) string 返回数字 i 所表示的字符串类型的十进制数。
  • FormatFloat(f float64, fmt byte, prec, bitSize int) string - 将浮点型转为字符串,fmt表示格式(b、e、f、g),prec表示精度,bitSize 32表示float32,64表示float64
  • Atoi(s string) (i int, err error) - 将字符串转换为 int 型
  • ParseFloat(s string, bitSize int) (f float64, err error) - 将字符串转换为 float64 型

5. time

  • Now() Time - 获取当前时间
  • Sleep(d Duration) - 暂停当前的goroutine,负值或零值会导致立即返回
  • func (t Time) Year() int - 返回t的年份
  • func (t Time) Month() Month - 返回t的月份
  • func (t Time) Day() int - 返回t的日期
  • func (t Time) Hour() int - 返回t的小时,[0, 23]
  • func (t Time) Minute() int - 返回t的分钟,[0, 59]
  • func (t Time) Second() int - 返回t的秒,[0, 59]
  • func (t Time) UTC() Time - 将时间转换为UTC时间
  • func (t Time) Add(d Duration) Time - 返回t加上d后的时间
  • func (t Time) Sub(d Duration) Time - 返回t减去d后的时间

6. unicode/utf8

  • RuneCount(p []byte) int - 返回p的rune数量
  • RuneCountInString(s string) (n int) - 返回s中rune的数量

四. 函数

类型:普通函数,匿名函数,方法

1. 定义

func g() {
}

调用:pack1.Function(arg1, arg2, …, argn)

注意: go不支持函数重载

切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型都是默认使用引用传递。修改形参会影响实参的值

2. 命名返回值

func funcname(input int) (return1 int, return2 int){
  //计算过程
  return
}

注意:命名返回值return后不需要写返回内容,只有一个返回值时,命名返回值也需要括起来

3. 变长参数

func myfunc(a ...int){}

调用:

slice := []int{1, 2, 3, 4}
myfunc(slice...)

4. defer

将某个语句推迟到函数最后执行(return之后)。多个defer会以栈的形式执行(先进后出)

func f() {
    for i := 0; i < 5; i++ {
        defer fmt.Printf("%d ", i)
    }
}
// 4, 3, 2, 1, 0

5. 内置函数

名称 说明
close 用于管道通信
len、cap len 用于返回某个类型的长度或数量(字符串、数组、切片、map 和管道);cap 是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
new、make new 和 make 均是用于分配内存:new 用于值类型和用户定义的类型,如自定义结构,make 用于内置引用类型(切片、map 和管道)。它们的用法就像是函数,但是将类型作为参数:new(type)、make(type)。new(T) 分配类型 T 的零值并返回其地址,也就是指向类型 T 的指针。它也可以被用于基本类型:v := new(int)。make(T) 返回类型 T 的初始化之后的值,因此它比 new 进行更多的工作new() 是一个函数,不要忘记它的括号
copy、append 用于复制和连接切片
panic、recover 两者均用于错误处理机制
print、println 底层打印函数,在部署环境中建议使用 fmt 包
complex、real imag 用于创建和操作复数

6. 匿名函数

func(x, y int) int { return x + y }

注意:匿名函数必须进行赋值或者被调用。否则会编译失败

7. 闭包

func main() {
    var f = Adder()
    fmt.Print(f(1), " - ")
    fmt.Print(f(20), " - ")
    fmt.Print(f(300))
    //1 - 21 - 321
}

func Adder() func(int) int {
    var x int
    return func(delta int) int {
        x += delta
        return x
    }
}

五. 包

1. 标准库

  • unsafe: 包含了一些打破 Go 语言“类型安全”的命令,一般的程序中不会被使用,可用在 C/C++ 程序的调用中。
  • syscall-os-os/exec
    • os: 提供给我们一个平台无关性的操作系统功能接口,采用类Unix设计,隐藏了不同操作系统间差异,让不同的文件系统和操作系统对象表现一致。
    • os/exec: 提供我们运行外部操作系统命令和程序的方式。
    • syscall: 底层的外部包,提供了操作系统底层调用的基本接口。
  • archive/tar 和 /zip-compress:压缩(解压缩)文件功能。
  • fmt-io-bufio-path/filepath-flag
    • fmt: 提供了格式化输入输出功能。
    • io: 提供了基本输入输出功能,大多数是围绕系统功能的封装。
    • bufio: 缓冲输入输出功能的封装。
    • path/filepath: 用来操作在当前系统中的目标文件名路径。
    • flag: 对命令行参数的操作。
  • strings-strconv-unicode-regexp-bytes:
    • strings: 提供对字符串的操作。
    • strconv: 提供将字符串转换为基础类型的功能。
    • unicode: 为 unicode 型的字符串提供特殊的功能。
    • regexp: 正则表达式功能。
    • bytes: 提供对字符型分片的操作。
  • index/suffixarray: 子字符串快速查询。
  • math-math/cmath-math/big-math/rand-sort:
    • math: 基本的数学函数。
    • math/cmath: 对复数的操作。
    • math/rand: 伪随机数生成。需要设置随机种子才能实现不同的随机
    • sort: 为数组排序和自定义集合。
    • math/big: 大数的实现和计算。   
  • container-/list-ring-heap: 实现对集合的操作。
    • list: 双链表。
    • ring: 环形链表。
  • time-log:
    • time: 日期和时间的基本操作。
    • log: 记录程序运行时产生的日志,我们将在后面的章节使用它。
  • encoding/json-encoding/xml-text/template:
    • encoding/json: 读取并解码和写入并编码 JSON 数据。
    • encoding/xml:简单的 XML1.0 解析器,有关 JSON 和 XML 的实例请查阅第 12.9/10 章节。
    • text/template:生成像 HTML 一样的数据与文本混合的数据驱动模板(参见第 15.7 节)。
  • net-net/http-html:(参见第 15 章)
    • net: 网络数据的基本操作。
    • http: 提供了一个可扩展的 HTTP 服务器和基础客户端,解析 HTTP 请求和回复。
    • html: HTML5 解析器。
  • runtime: Go 程序运行时的交互操作,例如垃圾回收和协程创建。
  • reflect: 实现通过程序运行时反射,让程序操作任意类型的变量。

2. regexp

正则表达式

ok, _ := regexp.Match(pat, []byte(searchIn)) - 简单模式匹配,ok返回true或flase

ok, _ := regexp.MatchString(pat, searchIn) - 简单匹配字符串

re, _ := regexp.Compile(pat) - 将正则表达式编译为一个regexp对象

str := re.ReplaceAllString(searchIn, "##.#") - 将匹配到的部分替换为"##.#"

str2 := re.ReplaceAllStringFunc(searchIn, f) - 参数为函数,匹配内容传入函数,替换为函数的返回内容

3. 锁和sync

import "sync"
type Info struct {
    mu sync.Mutex
    // ... other fields, e.g.: Str string
}
func Update(info *Info) {
    //函数想要改变这个变量可以这样写
    info.mu.Lock()
        // critical section:
        info.Str = // new value
        // end critical section
        info.mu.Unlock()
}
//通过 Mutex 来实现一个可以上锁的共享缓冲器
type SyncedBuffer struct {
    lock     sync.Mutex
    buffer  bytes.Buffer
}

4. 精密计算和big包

import (
  "math"
  "math/big"
)

im := big.NewInt(n)//构造大的整型数字
rm := big.NewRat(n, d)//构造大的有理数(n为分子,d为分母)

5. 自定义包和导入

导包格式

import "包的路径或 URL 地址"

//用点作为别名可以直接使用包内的项目不需要通过包名.项目名
import . "./pack1"

使用go install来安装外部包

6. 使用godoc

godoc -http=:6060 -goroot="."访问自动生成的doc网页

六. 结构和方法

1. 结构体

定义:

type identifier struct {
    field1 type1 tag(可忽略,只有reflect可以使用tag内容)
    field2 type2
    ...
}

声明结构体变量并赋值

type T struct {a, b int}

var s T
s.a = 5
s.b = 8

//new(Type)和&Type{}是等价的
ms := &struct1{10, 15.5, "Chris"}

//&不是必须的,没有&获取的时一个值类型,有&得到的是一个指针类型
intr := T{0, 3}
intr := T{b:5, a:1}
intr := T{b:5} 

var ms struct1
ms = struct1{10, 15.5, "Chris"}

用new创建结构体返回新分配的内存的指针

var t *T = new(T)

var t *T
t = new(T)

t := new(T)

可以使用点给结构体字段赋值(选择器):structname.fieldname = value

结构体或指针都能使用选择器,但用new和&创建但对象本质还是指针。

type myStruct struct { i int }
var v myStruct    // v是结构体类型变量
var p *myStruct   // p是指向一个结构体类型变量的指针
v.i
p.i

结构体嵌套,如双向链表

type Node struct {
    pr      *Node
    data    float64
    su      *Node
}

查看结构体的内存占用:unsafe.Sizeof(T{})

注意:make只能用于slice、map、channels,不能用于结构体

匿名字段和内嵌结构体

结构体如果不设置字段名,那么默认使用结构体名称.变量类型来获取值,在一个结构体中对于每一种数据类型只能有一个匿名字段

结构体中包含结构体,其结构类似继承。

type A struct {
    ax, ay int
}

type B struct {
    A
    bx, by float32
}

func main() {
    b := B{A{1, 2}, 3.0, 4.0}
    fmt.Println(b.ax, b.ay, b.bx, b.by)
    fmt.Println(b.A)
}

变量名相同,类型相同会冲突。类型不同需要使用子结构体名来获取

type A struct {a int}
type B struct {a, b int}

type C struct {A; B}
var c C
//无法获取c.a

type D struct {B; b float32}
var d D
//可以获取d.b,因为类型不同,d.B.b获取整型b

2. 方法

定义:

func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }

recv类似self或this,recv不能是接口类型,不能是指针类型,但是它可以是任何其他允许类型的指针。

方法和类型必须在同一个包里,所以不能在int、float等类型上定义方法。

如果结构体内的字段是私有的,外部包要访问该字段,需要写getter和setter方法,对于 setter 方法使用 Set 前缀,对于 getter 方法只使用成员名。

type Person struct {
    firstName string
    lastName  string
}

func (p *Person) FirstName() string {
    return p.firstName
}

func (p *Person) SetFirstName(newName string) {
    p.firstName = newName
}

内存状态

var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("%d Kb\n", m.Alloc / 1024)
//获取以kb为单位的内存总量

//释放内存
runtime.GC()

//对象在释放前执行的函数
runtime.SetFinalizer(obj, func(obj *typeObj))

七. 接口与反射

1. 接口

定义

接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量。只包含一个方法的接口的名字由方法名加 [e]r 后缀,不适用于er时以 able 结尾,或者以 I 开头

type Namer interface {
    Method1(param_list) return_type
    Method2(param_list) return_type
    ...
}

接口特性:

  • 类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口。
  • 实现某个接口的类型(除了实现接口方法外)可以有其他的方法。
  • 一个类型可以实现多个接口。
  • 接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。
  • 即使接口在类型之后才定义,二者处于不同的包中,被单独编译:只要类型实现了接口中的方法,它就实现了此接口。
  • 实现了接口类型的变量可以赋值给 接收者值
//接上段程序

type Person struct {
    side float32
}

func (p *Person) Method1(param_list){
  return return_value
}

func (p *Person) Method2(param_list){
  return return_value
}

func main(){
  p := new(Person)
  var ai Namer
  //实现了接口类型的变量可以赋值给接口接收者,此处相当于变量引用
  ai = p
}

可以创建接口数组

//Shaper是接口,r、q是实现了该接口类型的变量。r、q类型可以不同
shapes := []Shaper{Shaper(r), Shaper(q)}
shapes := []Shaper{r, q}

接口的嵌套

一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。

接口类型的断言

//varI是一个接口变量,T是类型指针,v是varI转换到类型T的值。ok表示是否转换成功
if v, ok := varI.(T); ok {
    // ...
}

也可以使用type-switch来检测

//type这儿是固定关键字,T1和T2是不同的类型,不允许使用fallthrough
switch t := varI.(type){
  case *T1:
    //do something
  case *T2:
    //do something
}

判断值是否实现了某个接口

//v表示值,Stringer表示接口名。
sv, ok := v.(Stringer);

显式声明实现了某个接口

type Fooer interface {
    Foo()
    ImplementsFooer()
}
type Bar struct{}
func (b Bar) ImplementsFooer() {}
func (b Bar) Foo() {}

空接口实现函数重载

//最后一个参数设置为空接口,允许传任何参数,即可实现函数重载
fmt.Printf(format string, a ...interface{}) (n int, errno error)

接口继承

一个类型包含(内嵌)另一个类型(实现了一个或多个接口)的指针时,这个类型就可以使用(另一个类型)所有的接口方法。

type Task struct {
    Command string
    *log.Logger
}

2. 反射

var x float64 = 3.4
reflect.TypeOf(x)//float64
reflect.ValueOf(x)//<float64 Value>

//进行拷贝赋值
v := reflect.ValueOf(x)
//Kind总是返回最底层类型
v.Kind() == reflect.Float64//true
v.Type()//float64
v.Float()//3.4
v.Interface()//3.4

//是否可以赋值,返回false
v.CanSet()
//设置其为可以赋值
v = v.Elem()
//赋值操作
v.SetFloat(3.1415)

//反射结构类型,遍历结构内的字段
value := reflect.ValueOf(secret)//secret为结构体
value.NumField()//用for循环进行遍历

包的使用

unsafe

unsafe.Pointer(x) - Pointer类型用于表示任意类型的指针,Pointer类型允许程序绕过类型系统读写任意内存

http

func NewServeMux() *ServeMux - 多路复用处理函数

func (mux *ServeMux) Handle(pattern string, handler Handler) - 注册给定匹配字符串的处理程序

gorm.io/gorm

go的数据库处理包

注意:需要_ "github.com/go-sql-driver/mysql""gorm.io/driver/mysql" 驱动支持

定义数据模型:

type Product struct {
  gorm.Model
  Code string
  Price uint
}

protocol-buffers

grpc的格式定义方法

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3;
  // required表示必填,optional可选项,repeated可重复项
  // 使用repeated的效率不高,需要添加packed=true来提高效率
  // repeated int32 samples = 4 [packed=true];
  // 消息需要有唯一编码,reserved 编号或字段名表示保留该字段。
}

message SearchResponse {
  // 定义消息的回复格式
}

五. Array数组

定义

var 数组变量名 [元素数量]元素类型

注意:数组的长度必须是常量,并且长度是数组类型的一部分。一旦定义,长度不能变。不同长度的数组也是不同的类型。

不赋值的变量默认为类型的默认值:int为0,bool为false,string为""

示例:

var intArray [3]int; // [0, 0, 0]
var numArray = [3]int{1, 2} // 用固定的值初始化变量
var numArray = [...]int{1, 2} // 自动推断数组长度
a := [...]int{1: 1, 3: 5} // 使用指定索引值的方式来初始化数组

遍历

用for循环

var a = [...]string{"北京", "上海", "深圳"}

for i := 0; i < len(a); i++ {
    fmt.Println(a[i])
}

用for-range循环

var a = [...]string{"北京", "上海", "深圳"}

for index, value := range a {
    fmt.Println(index, value)
}

注意:如果用for i := range a获取到的i是索引

方法

len(数组) - 获取数组的长度

注意

数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。

数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的。

[n]*T表示指针数组,*[n]T表示数组指针 。

六. 切片

注意:切片是引用类型,内部结构包含地址长度容量,底层指向一个数组,使用前需要先开辟内存

定义

var 变量名 []类型

var a []string              //声明一个字符串切片
var b = []int{}             //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化

a[low : high : max] - 可以使用从数组切片的方式获得切片类型

方法

len(slice) - 获取切片的长度

cap(slice) - 获取切片底层的最大长度(注意:计算的是从切片开始位置到指向数组的最后的长度)

make([]类型, size, cap) - size表示切片中元素的数量,cap表示切片的容量,size和cap都可以不填。当切片的无法存放跟多的值会自动重新分配内存

len(s) == 0 - 判断切片是否为空

copy(slice1, slice2) - 将slice2的元素拷贝到slice1

append(slice1, slice2...) - 将slice2添加到slice1后面

append(slice1, int1, int2, int3) - 将后面的值添加到slice1后面

append(slice[:i], slice[i+1:]) - 将slice删除指定下标的方法

七. map

注意:map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。

定义

map[KeyType]ValueType // 定义

make(map[KeyType]ValueType, [cap]) //分配空间,cap

userInfo := map[string]string{
    "username": "user",
    "password": "password",
} // 定义并赋值

scoreMap := make(map[string]int, 8)
scoreMap["张三"] = 90 // 创建键值或修改键的值

遍历

使用for-range

for key, value := range map1 {
  // 执行语句
}

注意: 使用for key := range map1只能获取到map1的key

方法

value, ok := map1[key] - 判断key是否在map1中,如果在,ok返回true,不在返回false

delete(map1, key) - 删除map1中的key

八. 函数

定义

func 函数名(参数)(返回值){
    函数体
}

参数和返回值

参数简写

func intSum(x, y int) int {
    return x + y
}

可变参数

func funcName(x1 int, x2 int, x ...int) int {
    // 函数体
}

注意:可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加...来标识。

可变参数通常要作为函数的最后一个参数。

多参数返回:

func calc(x, y int) (int, int) {
    sum := x + y
    sub := x - y
    return sum, sub
}

注意:多个返回值时参数后面的返回值必须用()将所有返回值包裹起来。

返回值命名:

func calc(x, y int) (sum, sub int) {
    sum = x + y
    sub = x - y
    return
}

注意:返回值命名相当于函数的开头就定义了这两个变量,不需要再次进行定义

返回值补充:

func someFunc(x string) []int {
    if x == "" {
        return nil // 没必要返回[]int{}
    }
    ...
}

变量作用域:

局部变量和全局变量冲突了。优先使用局部变量

函数类型与变量

定义一个函数类型

type calculation func(int, int) int

凡是满足该参数和返回值的函数都是该类型的函数,该类型的实例可以接受实现了该类型函数的赋值

func add(x, y int) int {
    return x + y
}

var c calculation
c = add

高阶函数

高阶函数分为函数作为参数和函数作为返回值两部分。

函数作为参数

func add(x, y int) int {
    return x + y
}
func calc(x, y int, op func(int, int) int) int {
    return op(x, y)
}
func main() {
    ret2 := calc(10, 20, add)
    fmt.Println(ret2) //30
}

函数作为返回值

func do(s string) (func(int, int) int, error) {
    switch s {
    case "+":
        return add, nil
    case "-":
        return sub, nil
    default:
        err := errors.New("无法识别的操作符")
        return nil, err
    }
}

匿名函数

没有函数名的函数称为匿名函数

func(参数)(返回值){
    函数体
}

匿名函数执行需要复制给变量调用或者定义时执行

func main() {
    // 将匿名函数保存到变量
    add := func(x, y int) {
        fmt.Println(x + y)
    }
    add(10, 20) // 通过变量调用匿名函数

    //自执行函数:匿名函数定义完加()直接执行
    func(x, y int) {
        fmt.Println(x + y)
    }(10, 20)
}

闭包

Gin框架

基础概念

源码地址:gin

优点:

  • 速度快,类似python中的flask,小而快
  • 中间件,支持中间件处理
  • 路由,非常简单实现路由解析功能,支持路由组
  • 内置渲染,支持JSON,XML和HTML的格式渲染

安装gin框架:

go get -u github.com/gin-gonic/gin

简单的gin程序

func main(){
    engine := gin.Default()

    engine.handle("GET", "/hello", func(context *gin.Context){
        context.Writer.Write([]byte("hello, world"))
    })

    engine.run(":8090")
}

异常

os.Exit(1):

  1. 程序立即退出
  2. defer函数不会执行

log.Fatal():

  1. 打印日志
  2. 退出程序
  3. defer不会被执行

panic():

  1. 函数立刻停止
  2. defer函数执行
  3. 调用者收到panic
  4. 调用者执行defer函数
  5. 重复3、4步骤到最上层函数
  6. 执行panic行为

2. Go内置类型和函数

2.2 内置函数

append            // 追加元素到数组、切片中,返回修改后的数组、切片
close            // 主要用来关闭channel
delete            // 从map中删除key对应的value
panic            // 停止常规的goroutine (panic和recover:通常做错误处理)
recover            // 允许程序定义goroutine的panic动作
real            // 返回complex的实部
imag            // 返回complex的虚部
make            // 用来分配内存,返回Type本身(只能用于slice,map,channel)
new                // 用来分配内存,主要用来分配值类型,如int,struct,返回指向type的指针
cap                // 返回某个类型的最大容量(只能用于slice,map)
copy            // 用于复制和连接slice,返回复制的数目
len                // 求长度,如string,array,slice,map,channel,返回长度
print/println    // 底层打印函数,部署环境中建议用fmt包

2.3 内置接口

error接口

type error interface{  // 只要实现了Error()函数,返回值为string的都实现了err接口
    Error() string
}

3. init 和 main函数

3.1 init函数

init函数用于包(package)的初始化,主要有以下特征

  • init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量
  • 每个包可以拥有多个init函数
  • 包的每个源文件可以拥有多个init函数
  • 同一个go文件的init调用顺序是从上到下的
  • 同一个包中不同文件是按照文件名字符串比较从小到大顺序调用各文件中的init函数
  • 不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序
  • init函数不能被其他函数调用,而是在main函数执行之前自动调用

3.2 main函数

main函数是Go语言程序的默认入口函数(主函数)

func main(){
    // 函数体
}

3.3 init和main的异同

相同点:

  • 两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用

不同点:

  • init可以应用于任意包中,且可以重复定义多个
  • main函数只能用于main包中,且只能定义一个

4. 命令

  • go env - 用于打印Go语言的环境信息。
  • go run - 命令可以编译并运行命令源码文件。
  • go get - 可以根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装。
  • go build - 命令用于编译我们指定的源码文件或代码包以及它们的
  • go install - 用于编译并安装指定的代码包及它们的依赖包。
  • go clean - 命令会删除掉执行其它命令时产生的一些文件和目录。
  • go doc - 命令可以打印附于Go语言程序实体上的文档。我们可以通过把程序实体的标识符作为该命令的参数来达到查看其文档的目的。
  • go test - 命令用于对Go语言编写的程序进行测试。
  • go list - 命令的作用是列出指定的代码包的信息。
  • go fix - 会把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码。
  • go vet - 是一个用于检查Go语言源码中静态错误的简单工具。
  • go tool pprof - 命令来交互式的访问概要文件的内容。

5. 运算符

算术运算符:

+-*/% - 加减乘除余

注意++(自增)和--(自减)在Go语言中是单独的语句,并不是运算符

关系运算符:

==!=>>=<<= - 相等、不等、大于、大于等、小于、小于等(返回bool值)

逻辑运算符:

&&||! - 与、或、非(返回bool值)

位运算符:

&|^>><< - 与、或、异或、右移(除以2的n次方)、左移(乘2的n次方)

赋值运算符:

=+=-=*=、\=%=<<=>>=&=|=^= - 等、加等、减等、乘等、除等、余等、左移等、右移等、与等、或等、异或等

6. 下划线

_是特殊标识符,用来忽略结果。不需要提前定义,可以接受任何类型,不占用内存空间

在import中,使用_命名包表示只调用该包的init函数

在代码中,_表示忽略变量或者占位使用

9. 数组

9.1 定义

var 数组变量名 [元素数量]元素类型

示例:

var intArray [3]int; // [0, 0, 0]
var numArray = [3]int{1, 2} // 用固定的值初始化变量
var numArray = [...]int{1, 2} // 自动推断数组长度
a := [...]int{1: 1, 3: 5} // 使用指定索引值的方式来初始化数组

9.2 遍历

用for循环

var a = [...]string{"北京", "上海", "深圳"}

for i := 0; i < len(a); i++ {
    fmt.Println(a[i])
}

用for-range循环

var a = [...]string{"北京", "上海", "深圳"}

for index, value := range a {
    fmt.Println(index, value)
}

9.3 方法

  • len(数组) - 获取数组的长度
  • cap(数组) - 获取数组的长度(元素的数量)

9.4 注意

  • 如果用for i := range a获取到的i是索引
  • 数组的长度必须是常量,并且长度是数组类型的一部分。一旦定义,长度不能变。不同长度的数组也是不同的类型。
  • 不赋值的变量默认为类型的默认值:int为0,bool为false,string为""
  • 数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。
  • 数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的。
  • [n]*T表示指针数组,*[n]T表示数组指针 。

10. 切片

10.1 定义

var 变量名 []类型

var a []string              //声明一个字符串切片
var b = []int{}             //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化

10.2 方法

a[low : high : max] - 可以使用从数组切片的方式获得切片类型

len(slice) - 获取切片的长度

cap(slice) - 获取切片底层的最大长度(注意:计算的是从切片开始位置到指向数组的最后的长度)

make([]类型, len, cap) - len表示切片中元素的数量,cap表示切片的容量,len和cap都可以不填。当切片的无法存放跟多的值会自动重新分配内存

len(s) == 0 - 判断切片是否为空

copy(slice1, slice2) - 将slice2的元素拷贝到slice1

append(slice1, slice2...) - 将slice2添加到slice1后面

append(slice1, int1, int2, int3) - 将后面的值添加到slice1后面

append(slice[:i], slice[i+1:]) - 将slice删除指定下标的方法

10.3 注意

切片是引用类型,内部结构包含地址长度容量,底层指向一个数组,使用前需要先开辟内存

string底层就是一个byte的数组,也可以进行切片操作

s := []byte(str) // 中文字符使用[]rune(str)

跳到最后

Copyright © book.stolenzc.com 2021-2024 all right reserved,powered by GitbookFile Modify: 2024-09-24 02:47:04

results matching ""

    No results matching ""