Willson Chen

Stay Hungry, Stay Foolish.

Golang 基础(一)

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
}