文章目录
Golang 基础(一)
简介
历史与发展
- Go 语言由Google开发,起源于 2007 年,开源于 2009 年。
- 诞生背景,主要是解决其他语言的历史包袱重、复杂、编译慢等问题。
- 其设计理念是less is more,追求简洁、高效、直接。
- 由 Go 开发的开源项目:go、docker、k8s、etcd等。
语言特性
- 编译型,区别于脚本等解释性语言。
- 静态强类型,类型是编译期确定的,需先声明后使用。
- 内存安全,支持内存安全检查和垃圾回收。
- 并发支持,协程作为并发单元,运行层深度优化。
- 不支持继承、泛型等复杂特性。
安装
安装与环境配置
从 go.dev 上下载 Go 安装包,解压至 /usr/local 目录下。增加/usr/local/go/bin路径到PATH环境变量。
Go 的环境变量:
名称 | 描述 |
---|---|
GOROOT | 安装目录,包含编译器、命令行工具、标准库等。 |
GOPATH | 工作目录,包含自动下载的第三方依赖源码和可执行文件。 |
GO111MODULE | 模块管理模式,默认值是 auto 表示根据当前目录来判断。 |
GOPROXY | 模块代理地址,用于下载公开的第三方依赖,多个用逗号隔开,遇到 direct 时表示直接访问源地址。 |
GOSUMDB | 使用 GOPROXY 时,校验和数据库的地址。 |
GONOPROXY | 不通过代理,直接下载的第三方依赖,如公司私有仓库。 |
GONOSUMDB | 不通过代理,直接下载的第三方依赖校验和数据库。 |
GOPRIVATE | 指示哪些仓库下的模块是私有的,等于同时设置 GONOPROXY 和 GONOSUMDB。 |
安装过程:
#用 root 身份
yum install -y wget #安装 wget
#下载 go,版本号可以更新
wget https://go.dev/dl/go1.20.6.linux-amd64.tar.gz
#解压至/usr/local
tar -C /usr/local -zxvf go1.20.6.linux-amd64.tar.gz
#删除安装包
rm -f go1.20.6.linux-amd64.tar.gz
vi /etc/profile #用 vi 编辑 profile 文件
#按 i 在最后添加 export PATH=PATH:/usr/local/go/bin
#按 ESC,输入符号:,再输入 wq 回车保存退出
source /etc/profile #加载配置
echoPATH #确认已添加 PATH
su dev #切换开发者用户,dev 为远程开发用户
#设置 goproxy 为国内镜像
go env -w GOPROXY=https://goproxy.cn,direct
命令行工具
Go 命令行工具位于 /usr/local/go/bin/go。
常用命令:
命令 | 描述 |
---|---|
go version | 查看 Go 版本。 |
go env | 查看 Go 环境变量。 |
go get | 下载依赖源码包,下载后位于 GOPATH/src 下。 |
go install | 编译并安装依赖包,安装后位于 GOPATH/bin 下。 |
go build | 编译源码,生成可执行文件。 |
go run | 运行源码文件,一般用来运行单个文件。 |
go fmt | 格式化源码。 |
go vet | 检查代码错误。 |
go test | 运行单元测试。 |
go generate | 运行代码生成工具。 |
go mod | 模块管理工具,用于下载、更新、删除依赖包。 |
开发工具
常用 vs code,安装 Go 相关插件,可以使用 remote ssh 插件进行远程开发。
基础知识
代码风格
Go 语言的代码风格类 C 语言,但更简洁。
主要区别点:
- 每一行代表一个语句结束,不需要分号;如果一行包含多个语句,需要加分号但不建议。
- 左花括号不另起一行。
- 使用 tab 缩进,go fmt 会自动格式化。
- 无冗余括号,for 和 if 表达式不需要括号。
- 无需显式声明变量类型,编译器自动推断。
- 支持多变量同时赋值,支持函数多返回值。
- switch 语句不需要 break,默认执行完 case 分支后自动 break。
主要相似点:
- 单行注释以 // 开头,多行注释以 /*开头,以*/ 结尾。
- 标识符由字母、数字、下划线组成,其中首个字符不能为数字,区分大小写。
- 运算符与运算符优先级类 c。
数据类型
- 布尔类型,bool,值为 true 或 false
- 数字类型,
- 整型,int8/int16/int32/int64,uint8/uint16/uint32/uint64,int/uint的长度取决于cpu。
- 字符,byte 是 uint8,rune(utf-8 字符) 是 int32。
- 指针,uintptr 是 uint。
- 浮点型,float32/float64
- 复数,complex64、complex128
- 字符串,string,底层为不可变的字节序列
- 复合类型,数组/slice/map/channel/struct/interface/函数
值类型与引用类型: 只有 slice、map、channel 、interface是引用类型,其他都是值类型。值类型如果要避免拷贝需要用指针。
变量
声明与赋值
- Go 是静态强类型语言,变量类型在编译期确定,且会进行类型检查。
- 变量包括局部变量和全局变量。
- 局部变量的声明有多种方式,首选是省略var和类型的极简方式。
- 全局变量声明必须用var关键字,可以省略类型。
//方式一:显示指定类型不赋值,会初始化为默认值
var a int
a = 10
//方式二:显示指定类型并赋值
var b int = 10
//方式三:直接赋值省略类型,编译器会自动推断类型
var b = 10
//方式四:直接赋值省略类型,编译器会自动推断类型,使用符号 := 替代 var 关键字
//注意:已声明过的变量再使用符号 := 会编译出错,只能声明局部变量
c := 10
多变量声明与赋值:
- 每种声明方式都多个变量声明,用逗号分隔,且类型可以不同。
- 可以用 var 加括号的方式进行批量声明,一般用于全局变量。
- 赋值时允许直接交换两变量的值,前提是类型相同。
- 用下划线 _ 忽略某个不需要的值。
//显示指定类型并赋值
var a, b int = 1, 2
//省略类型,可同时声明多种类型
x, s := 123, "str"
//批量声明
var(
data1 int
data2 string
)
//直接交换a和b的值
a, b = b, a
//假设 myfunc 返回两个值,第一个值不需要,用下划线忽略
_, ok := myfunc()
默认值
如果变量只声明未初始化,那么其值为默认值。各类型默认值如下:
- 布尔类型为 false
- 数字类型为 0
- 字符串为 “”,不存在类似其他语言的空引用情况
- 其他为 nil
作用域
按优先级从高到低,作用域分为:
- 块作用域:在代码块内(花括号内或 if/for语句内)声明的变量作用域在代码块内。
- 函数作用域:在函数内声明的变量(包括参数和返回值),作用域在函数内。
- 全局作用域:在函数外声明的变量,作用域在整个包内或外部(需导入包)。
在同一个作用域下变量只能声明一次,但在不同作用域下变量可以同名,按所在作用域的优先级决定使用哪个。
类型转换
- 编译器会做类型检查,如果类型不匹配,编译器会报错。
- 数字类型转换,用 type(x) 的方式,浮点数转整型会丢失小数。
- int 与 int32 或 int64 类型不同,必须强制转换。
- 大整型转小整型超出范围不会报错,但会截断。
- 字节数组或字节转字符串 string(arrbyte)、string(ch)
- 字符串转字节数组 []byte(str),转字符数组[]rune(str)
- 字符串与数字类型的转换借助标准库strconv包,详见标准库字符串章节。
var a float64 = 511.678
var b int32 = int32(a) //浮点数转整型丢失小数
//var c int = b //int 与 int32 属于不同类型,编译报错
var c int = int(b) //强制转换
var d uint8 = uint8(b) //大整型转小整型超出范围不会报错,但会截断
fmt.Println(a, b, c, d) //511.678 511 511 255
arrByte := []byte("abc中")
fmt.Println(arrByte) //[97 98 99 228 184 173]
arrRune := []rune("abc中")
fmt.Println(arrRune) //[97 98 99 20013]
arrByte[0] = 'A'
str := string(arrByte)
fmt.Println(str) //Abc中
s1 := strconv.Itoa(c) //int 转 string
x, err := strconv.Atoi("123") //string 转 int,若成功err为nil
fmt.Println(s1, x, err) //511 123 <nil>
s2 := strconv.FormatFloat(a, 'f', 2, 64) //float 转 string,2位小数四舍五入
f, err := strconv.ParseFloat(s2, 64) //string 转 float
fmt.Println(s2, f, err) //511.68 511.68 <nil>
常量
常量声明与赋值
- 与变量声明类似,常量声明可以省略类型,也支持多常量声明。
- 常量必须在声明时赋值且不能再次赋值,可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。
- 常量可以批量声明与赋值,如果不赋值表示使用前面常量的初始化表达式。
//方式一:显示指定类型
const MAX, MIN int = 100, 1
//方式二:省略类型
const WIDTH, TEXT = 100, "abc"
//方式三:批量声明
const(
DATA = "abc"
DATA2 //使用前面常量的表达式,值也为"abc"
LEN = len(TEXT) //允许对前面的常量进行运算
)
枚举与 iota
- 常量可以用作枚举。
- 用 iota 常量计数器可以简化常量值定义,在 const 内部第一次出现时为 0,每增加 1 行(非空行)加 1。
const(
Known = 0
Male = 1
Female =2
)
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
数字的进制表示
数字常量支持多进制表达:
进制 | 表达方式 | 示例 |
---|---|---|
十进制 | 常规写法 | 123 |
八进制 | 0开头 | 0123 |
十六进制 | 0x开头 | 0x123 |
二进制 | 0b开头 | 0b10101 |
字符串常量
- 字符串常量用双引号括起来,支持转义字符。
- \n 换行
- \t 制表符
- \’ 单引号
- \” 双引号
- \\ 反斜杠
- 字符常量用单引号括起来。
- 反引号`str`内不支持任何转义,完全使用字面量,可以支持多行文本。
运算符
类型 | 运算符 |
---|---|
算术 | +加 -减 *乘 /除 %取余 ++自增 –自减 |
关系 | = =等于 !=不等于 >大于 <小于 >=大于等于 <=小于等于 |
逻辑 | &&与 ||或 !非 |
位 | &位与 |位或 ^位异或 <<左移 >>右移 |
赋值 | =赋值 +=相加赋值 -=相减赋值 *=等 |
字符串 | +拼接 +=拼接赋值 = =等于 !=不等于 |
其他 | &指针取地址 *指针取值 |
注意:
- 区别于其他语言,++和–只能在变量后面作为语句,不能在表达式中使用。
- 字符串也支持>、>=、<、<=关系运算符,作用是逐字符比较。
条件控制
if 条件
- 条件表达式不需要加括号,且左花括号不另起一行。
- 支持在表达式前加执行语句(如声明变量),使代码保持简洁。
- 支持短路求值,即如果第一个条件表达式为 false,则不再计算第二个。
- 支持嵌套。
if a > 2 {
//TODO
} else if a > 3 {
//TODO
} else {
//TODO
}
//在表达式前使用赋值语句,ok 仅在 if 块内有效
if ok := isOk(); ok {
//TODO
}
switch 条件
- 同 if,条件表达式不需要加括号,且左花括号不另起一行。
- 同 if,支持在表达式前加执行语句(如声明变量),使代码保持简洁。
- case 后不需要 break,执行完 case 后自动 break。
- 如果执行完 case 后不要自动 break,需使用fallthrough。
- case 后的表达式可以包含多个,用逗号隔开。
- switch 后可以没有条件,通过 case 后加条件表达式来判断。
- switch 可以用于判断某个 interface 变量的实际类型,结合 x.(type) 表达式使用,该表达式只能用于 switch。
switch num:=myfunc(); num {
case 1: //TODO
case 2: //TODO
case 3, 4, 5 : //TODO
default: //TODO
}
switch {
case score == 100, score == 99: //TODO
case score >= 90: //TODO
case score >= 60: //TODO
default: //TODO
}
switch x.(type) { //假设 x 是 interface{} 类型
case nil:
fmt.Printf("x 是 nil")
case int:
fmt.Printf("x 是 int")
case float64:
fmt.Printf("x 是 float64")
}
select 条件
- select 提供语言层面的多路复用机制。
- 只能作用于通道,case 后必须是通道操作,如果通道为nil会忽略。
- select 会监听所有通道,哪个通道可以执行则执行,其他忽略。
- 如果有多个通道可以执行,则随机公平选取一个执行。
- 如果没有通道可以进行,则执行 default 语句,如没有 default 则阻塞。
select {
case v := <-ch1: //如果ch1有数据 则执行
fmt.Print(v, " ")
case v := <-ch2: //如果ch2有数据 则执行
fmt.Print(v, " ")
default://如果 ch1 和 ch2 都没数据则执行
}
循环控制
Go 语言的循环控制都是用 for 关键字,有以下多种形式:
- for init; condition; post { }
- for condition { },类似其他语言的 while(condition) 循环。
- for { },类似其他语言的 while(true) 循环。
- for range {},类似其他语言的 foreach,用于迭代集合。
- for a,b := range c {} 时每次循环后a和b都是值覆盖,地址不变。
- for i,ch := range “abc” {} 遍历字符串得到的是索引和rune类型。
-
可以用 break/continue/goto+label语句退出循环。
- goto不能跳转到其他函数或内层代码。
for i := 0; i < 10; i++ {
//TODO
}
for i < 100 {
i++
}
for {
//TODO
}
for k, v := range map {
//TODO
}