Go基础
约 3373 字大约 11 分钟
2025-03-27
1.Go语言简介
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。它提供了海量并行的支持。
特点:
- 简洁、快速、安全
- 并行、有趣、开源
- 内存管理、数组安全、编译迅速
2.安装环境Go
2.1 Windows
安装包下载地址:https://go.dev/dl/
Windows 环境下载 .msi 后缀的安装包来安装。默认情况下 .msi 文件会安装在 c:\Go 目录下。默认会将 c:\Go\bin 目录添加到 Path 环境变量中。
安装测试:
创建工作目录,创建 test.go 测试文件
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
使用 go run 命令执行以上代码输出结果如下:
2.2 Mac
待补充
3.数据类型
3.1 基本数据类型
基本数据类型分为以下六种:整型、特殊整型、浮点型、复数、布尔型、字符串、
关键字 | 描述 | 使用举例 | |
---|---|---|---|
整形 | uint8(byte) uint16(short) uint32 uint64(long) | 无符号整型 8位、0-255 16位、0-65535 32位 64位 | |
int8、int16、int32、int64 | 有符号整型 8位、-128-127 16位、-32768-32767 32位 64位 | ||
特殊整型 | uint | 32位操作系统上是 uint32、64位操作系统上是 uint64 | |
int | 32位操作系统上是 int32、64位操作系统上是 int64 | ||
uintptr | 无符号整型,用于存放一个指针 | ||
浮点型 | float32 | 最大值为:math.MaxFloat32(3.4e38) | 显示声明如下,f2 := float32(1.23456) |
float64 | 最大值为:math.MaxFloat64(1.8e308),默认Go中的小数都是float64类型 | ||
复数 | complex64 | 32位实部,32位虚部 | var c1 complex64 = complex(5, 6) // 5 + 6i |
complex128 | 128位实部,128位虚部 | ||
布尔型 | bool | 默认值为false | var b1 bool = true |
字符串 | "" | 内部使用 UTF-8 编码 | s:="Hello 中国" |
3.2 引用数据类型
引用数据类型分为以下五种:切片、字典、接口、函数、以及通道
关键字 | 描述 | 使用举例 | |
---|---|---|---|
切片 | slice | 动态数组 | numbers := []int{1, 2, 3} numbers = append(numbers, 4, 5) |
字典 | map | 哈希表,键值对存储 | person := make(map[string]string) person["name"] = "John" person["age"] = "30" |
接口 | interface | 抽象类型,可以表示任何类型。任何类型只要实现了接口中的方法,就可以被视为实现了该接口 | |
函数 | func | 函数类型,可以作为变量传递 | ![]() |
通道 | channel | 用于 goroutine 之间通信,支持同步 | ![]() |
3.3 结构体类型
描述:结构体类型,一种聚合数据类型,它允许将不同类型的数据组合在一起,形成一个单一的复合数据类型
定义:关键字 struct
type StructName struct { Field1 FieldType Field2 FieldType // ... }
举例:
// 定义一个结构体类型 "Person" type Person struct { Name string Age int } func main() { // 1. 创建结构体实例 person1 := Person{Name: "Alice", Age: 30} person2 := Person{"Bob", 25} // 也可以省略字段名 // 2. 访问结构体字段 fmt.Println(person1.Name) // 输出: Alice fmt.Println(person2.Age) // 输出: 25 // 3. 修改结构体字段 person1.Age = 31 fmt.Println(person1.Age) // 输出: 31 // 4. 使用指针修改结构体字段 personPointer := &person1 personPointer.Age = 32 fmt.Println(person1.Age) // 输出: 32 // 5. 结构体作为函数参数 printPersonInfo(person1) } // 结构体作为函数参数 func printPersonInfo(p Person) { fmt.Println("Name:", p.Name) fmt.Println("Age:", p.Age) }
4.基本结构
4.1 变量
使用 var 关键字声明:
// 声明一个变量
var 变量名 变量类型
// 一次声明多个变量
var 变量名1, 变量名2 type
初始化变量
// 1:没有初始化则为零值,即默认值
var b int
fmt.Print(b) # 为0
// 2:根据值自行判断变量类型
var b = true
值类型和引用类型的区别?
- 值类型:值类型的变量直接指向存在内存中的值,类型有:int、float、bool、string
- 引用类型:引用类型的变量指向内存中值的内存地址。通过 &i 来获取变量 i 的内存地址,内存地址通常称为指针
4.2 常量
常量是在程序运行时,不会被修改的量,使用 const 关键字修饰
const c_name [type] = value
const c_name1, c_name2 = value1, value2
4.3 数组
4.4 切片
切片特点:
- 动态长度:切片是一个动态数组,长度可以在运行时变化。它是一个对数组的引用,可以增长或缩小。
- 引用传递:切片是引用类型,当将切片传递给函数时,不会复制底层数组,而是传递底层数组的引用。
- 内存分配:切片的底层是基于数组的,但是切片本身只是数组的一个视图。它包含三个部分:指向底层数组的指针、切片的长度、切片的容量(即底层数组的大小)。
- 类型:切片的类型是由元素类型决定的,不包含长度信息。例如,
[]int
和[]string
是两种不同的切片类型。
举例:
切片长度(len)和容量(cap)的区别?
- 长度 (
len
):切片中当前元素的个数 - 容量 (
cap
):切片底层数组的总大小,表示切片能够容纳的最大元素个数
数组 和 切片 的区别?
特点 | 数组 | 切片 |
---|---|---|
长度 | 固定长度,在声明时确定,不能更改 | 动态长度,可以增加或缩小 |
类型 | 值类型(传递数组时是值拷贝) | 引用类型(传递切片时是引用传递) |
内存分配 | 在定义时分配,长度固定 | 底层数组动态分配,可以扩展 |
传递方式 | 按值传递,传递整个数组的副本 | 按引用传递,传递底层数组的引用 |
创建方式 | 通过 [n]T 声明固定大小的数组 | 通过 []T 创建动态大小的切片,或者通过 make 创建切片 |
容量 | 数组的容量就是它的长度,固定 | 切片有容量(cap)和长度(len)之分,容量大于等于长度 |
4.6 指针
指针:指向一个值的内存地址的变量成为指针
// var_name:指针名 var-type:指针类型
// * 号用于指定变量是作为一个指针
var var_name *var-type
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */
使用指针的步骤:
- 声明实际变量
- 声明指针变量
- 将指针变量赋值为实际变量的内存地址值
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */
fmt.Printf("a 变量的地址是: %x\n", &a )
/* 指针变量的存储地址 */
fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
Go 空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil,也称为空指针。
5.控制结构
5.1 if-else 结构
// 两个分支
if condition {
// do something
} else {
// do something
}
// 多个分支
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
5.2 for 结构
- 基本形式:
for 初始化语句; 条件语句; 修饰语句 {}
- 举例:
func main() {
for i := 0; i < 5; i++ {
fmt.Printf("This is the %d iteration\n", i)
}
}
5.3 switch 结构
switch var1 {
case val1:
...
case val2:
...
default:
...
}
6.函数
6.1 函数分类
- 普通的带有名字的函数
- 匿名函数
- 方法
6.2 函数格式
- 格式:
// func:函数关键字
// function_name:函数名
// parameter list:方法惨谁
// [return_types]:返回类型
func function_name( [parameter list] ) [return_types] {
函数体
}
- 举例:
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
/* 声明局部变量 */
var result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
6.3 函数参数
调用函数,可以通过两种方式来传递参数:值传递和引用传递
值传递:
- 传递的是变量的副本,函数内部修改副本的值,不会影响原始变量。
- 用于基本类型(如
int
、float
、bool
等)或结构体(struct
)较小、无需修改原始数据的场景
引用传递:
传递的是变量的地址,函数通过指针修改原始数据的值。
用于较大的结构体、数组
6.4 defer关键字
- 延迟执行:
defer
声明的语句不会在defer
语句本身所在的地方立即执行,而是会等到包含它的函数执行完毕、即将返回时才执行。 - 后进先出(LIFO)顺序:最后声明的
defer
语句会最先执行。 defer
语句的参数是立即计算的:defer
语句中的参数(例如函数调用中的值或变量)会在defer
声明时计算,而不是在defer
执行时计算。这意味着,即使在函数中修改了这些参数的值,defer
语句中已经计算过的值不会受到影响。
例子:
package main
import "fmt"
func testDefer() {
fmt.Println("Start of testDefer function")
defer fmt.Println("This is deferred 1")
defer fmt.Println("This is deferred 2")
defer fmt.Println("This is deferred 3")
fmt.Println("End of testDefer function")
}
func main() {
testDefer()
}
输出结果:
Start of testDefer function
End of testDefer function
This is deferred 3
This is deferred 2
This is deferred 1
7.数组&切片
7.1 数组
声明数组格式:
// arrayName:数组名称
// size:数组大小
// dataType:数组中元素类型
var arrayName [size]dataType
例子:
var arr [10]float32
初始化数组格式:
// 默认初始化
var numbers [5]int
// 初始化值
var numbers = [5]int{1, 2, 3, 4, 5}
7.2 切片
声明并初始化切片的两种方式:字面量和 make 关键字
// 字面量方式
var sliceName = []dataTYpe{data1,data2,data3}
// make 关键字
var sliceName =
// 1、通过字面量创建切片
slice1 := []int{1, 2, 3}
// 2、通过 make 函数创建切片
slice2 := make([]int, 3) // 创建一个长度为3的切片
slice3 := make([]int, 3, 5) // 创建一个长度为 3,容量为 5 的切片
// 3、通过切片操作从数组中创建切片
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // 从数组中创建一个切片,包括索引 1 到 3 的元素
fmt.Println(slice) // 输出: [2 3 4]
8.Map
8.1 声明 Map
定义Map的两种方式:使用内置函数 make+map 或 map 关键字
- 内置函数 make 创建 map
// 创建一个空的 Map
map1 := make(map[string]int)
// 创建一个初始容量为 10 的 Map
m := make(map[string]int, 10)
- 关键字 map 创建 map
// 使用字面量创建 Map
m := map[string]int{
"apple": 1,
"banana": 2,
"orange": 3,
}
- 获取元素
// 获取键值对
v1 := m["apple"]
v2, ok := m["pear"] // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值
- 修改元素
// 修改键值对
m["apple"] = 5
- 获取 Map 长度
// 获取 Map 的长度
len := len(m)
- 遍历 Map
// 遍历 Map
for k, v := range m {
fmt.Printf("key=%s, value=%d\n", k, v)
}
- 删除元素
// 删除键值对
delete(m, "banana")
9.类型转换
9.1 string 和 int 、float 互换
使用 strconv 包实现 string 类型和 int 、float 互换
1、strconv.Atoi 和 strconv.Itoa
- strconv.Atoi :将字符串转为 int 类型
- strconv.Itoa:将 int 类型转为 字符串
2、strconv.Parse 系列函数:将字符串解析为指定类型
ParseInt 、ParseBool 、ParseFloat
3、strconv.Format 系列函数:将指定类型转为字符串
FormatInt 、FormatFloat
9.2 map 和 json 互换
使用 json 的 Marshal 方法和 Unmarshal 方法进行
- map 转 json 串(本质是string),把 map 转为 byte 数组,再把 byte 数组转为 Json 串
- json 串(本质是string)转 map,先把 Json 串转为 byte 数组,再把 byte 数组转为 map
// map 转 json
DataMap := map[string]int{"a": 1, "b": 2, "c": 3}
marshal, err := json.Marshal(DataMap)
string(marshal)
// json 转 map
dataStr := `{"a":1,"b":2,"c":3}`
var dataMap map[string]int
err = json.Unmarshal([]byte(dataStr), &dataMap)
if err != nil {
fmt.Printf("Json串转化为Map失败,异常:%s\n", err)
return
}
9.3 结构体 和 json 互换
// 结构体 转 json
s := S{
Name: "小红",
Age: 18,
Sex: "女",
}
marshal, err := json.Marshal(s)
string(marshal)
// json 转 结构体
9.4 time.Time 和 timestamp.Timestamp 互换
proto 文件定义时间
import public "google/protobuf/timestamp.proto"; message person{ google.protobuf.Timestamp birthday = 1; }
生成的 .pb.go 文件:
Birthday *timestamp.Timestamp
转换:使用
github.com/golang/protobuf/ptypes
的 TimestampProto 方法和 Timestamp 进行 time.Time 和 timestamp.Timestamp 互换// Time 转 Timestamp,问题:少了8个小时 nowTime := time.Now() pbTimestamp, _ := ptypes.TimestampProto(nowTime) fmt.Println(pbTimestamp) // Timestamp 转 Time pbTimestamp2 := ptypes.TimestampNow() goTime, _ := ptypes.Timestamp(pbTimestamp) // 此方法默认 UTC 时区 fmt.Println(goTime.Local()) // 设定为系统时区
new 和 make 的区别
new | make | |
---|---|---|
用途 | 用于初始值为零值的类型的数据的初始化 | 用于引用类型的数据都初始化 |
返回内容 | 该类型的指针 | 已经初始化的引用类型,不是指针 |
是否初始化 | 分配内存,但不初始化该内存中的内容 | 分配内存,并初始化底层数据结构 |
- new 举例:
var p *int
p = new(int) // 分配内存并返回一个指向 int 类型的指针
fmt.Println(*p) // 输出 0,因为 int 的零值是 0
- make 举例:
// 切片
s := make([]int, 5) // 创建一个长度为 5 的切片,元素的默认值为 0
fmt.Println(s) // 输出 [0 0 0 0 0]
// 映射
m := make(map[string]int) // 创建一个空映射
m["key"] = 10
fmt.Println(m) // 输出 map[key:10]
// 通道
ch := make(chan int, 2) // 创建一个缓冲通道,容量为 2
ch <- 1
ch <- 2
fmt.Println(<-ch) // 输出 1