1. Go语言入门
1.1 helloworld示例
一个基本的helloworld示例程序如下.
// 这是该文件所在的包名,在同目录下,只允许有一个package语句,强制规定.
package main
// 引入fmt标准库,用于格式化I/O
import "fmt"
// 函数以func声明,后接函数名,main为程序
func main() {
// 类似于java中的System.out.println();,同样的带有输出换行功能.
// 在语句后,不再需要添加分号.
fmt.Println("Hello, World!")
}关于这个包名,真是个大坑
此外,go.mod必须要放在根目录中
可以直接使用
go run main.go运行程序或使用go build构建程序.main.go,一般放在根目录中,如果是多入口的程序,会放在/cmd/app下
这种东西一般应用于一个项目会编译出多个不同的可执行文件时,如一个web服务程序,一个处理定时任务的worker程序等.
需要注意,在同一级目录下,只能有一个package声明.
同一个目录下的所有.go文件,会被视为同一个包
在同一个目录下同时出现package A和package B,会直接报错.
例外: go 语言允许在同一个目录下存在一个包及其对应的测试包,如
package user和package user_test
强烈建议包名与目录名相同! 原因是
若在子目录domain中,有一个包名为package pkg2的文件,那么
在引入时,需要写 import “GoLearn/domain”,但是在使用时,需要pkg2.xxx,这很容易导致混乱,因为包名与目录名不一致,根本不知道domain里面会有一个pkg2.
另外,go语言官方建议 包名应全小写,单数形式.
在自己写的程序中,引用自己写的模块,需要使用 <模块名>/包名 的方式引入,具体使用方法见 1.9 函数作用域
无论当前文件位于项目的哪个层级,要引用项目内的其它包,都必须以 根模块名(项目名)作为绝对路径的起点.
示例
txtGoLearn/ (根目录, go.mod 所在处) ├── go.mod (内容: module GoLearn) ├── main.go (package main) └── domain/ └── user.go (package domain)pythonpackage main // 必须带上 GoLearn 这个模块名前缀 import "GoLearn/domain" func main() { // 调用时使用包名 domain domain.NewUser() }
2 语法
2.1 数据类型
变量名使用驼峰命名法命名
变量作用域的控制是通过首字母大小写控制的,而非使用public / private 等关键字.
大写字母: 全局变量.
小写字母: 局部变量,限于本包
实际上,该规则同时适用于函数
var PublicVar int // 对外可见
var privateVar int // 仅包内可见
func PublicFunc() {} // 对外可见
func privateFunc() {} // 仅包内可见在go语言中,int8,int16等,可以省略为int,此时,go会根据计算机架构,自动选择整型.
数字类型
package main
import "fmt"
func main() {
// 整数类型,其未指定长度时,默认位数跟随平台.
// 支持uint无符号数
// 支持int8,int16,int32,int64等指定长度的类型,依次类推,uint同样支持.
var intVar int = 1
// 要注意这里,并非是float类型,而是float64类型,
var floatVar float64 = 1.01
// 其它数字类型
// byte: 类似于uint8
// rune: 类似于int32
// 虚数类型
var complex64Var complex64 = 1 + 2i // 虚数类型,占用8字节
var complex128Var complex128 = 1 + 2i // 虚数类型,占用16字节,若未指定类型,则默认为complex128
// 虚数类型的另外一种声明方式,complex(<实部>,<虚部>)创建,支持complex64,complex128.
var complex64Var2 complex64 = complex(1, 2)
fmt.Println(complex64Var2)
fmt.Printf("基本类型: intVar = %d, floatVar = %.2f ", intVar, floatVar)
fmt.Printf("complex64Var = %v, complex128Var = %v\n", complex64Var, complex128Var)
// 4. 类型转换 (Go不支持隐式转换)
var castIntToFloat float64 = float64(intVar) + floatVar
fmt.Printf("类型转换: castIntToFloat=%.2f\n", castIntToFloat)
}虚数类型相关知识点
package main
import (
"fmt"
)
func main() {
// 声明方式
// ===== 方式1:字面量直接赋值(最常用)=====
var c1 complex128 = 1 + 2i // 默认就是 complex128
var c2 complex64 = 3 + 4i // 需显式指定 complex64
// ===== 方式2:complex(<实部,虚部>) 内置函数构造 =====
var c3 complex128 = complex(1, 2) // 实部=1, 虚部=2 → 1+2i
var c4 complex64 = complex(3, 4) // 3+4i
// ===== 方式3:短变量声明(自动推导为 complex128)=====
c5 := 5 + 6i // c5 的类型是 complex128
c6 := complex(7, 8) // c6 的类型也是 complex128
// ===== 方式4:只有虚部的写法 =====
var singlePartComplex1 complex128 = 2i // 等价于 0 + 2i
var singlePartComplex2 complex128 = 3.14i // 实部为 0
// ===== 方式5:零值 =====
var zeroComplex complex128 // 零值为 (0+0i)
fmt.Println(c1, c2, c3, c4, c5, c6, singlePartComplex1, singlePartComplex2, zeroComplex)
// 常用方法
// 提取实部或提取虚部
fmt.Println("实部:", real(c1), "虚部:", imag(c1))
// 数学运算
a := 1 + 2i
b := 3 + 4i
// 加法: (1+3) + (2+4)i = 4+6i
fmt.Println("a + b =", a+b) // (4+6i)
// 减法: (1-3) + (2-4)i = -2-2i
fmt.Println("a - b =", a-b) // (-2-2i)
// 乘法: (1*3 - 2*4) + (1*4 + 2*3)i = -5+10i
fmt.Println("a * b =", a*b) // (-5+10i)
// 除法: (a*b̄) / |b|²
fmt.Println("a / b =", a/b) // (0.44+0.08i)
/* 了解即可,这是虚数的四则运算规则.
设 a = x₁ + y₁i, b = x₂ + y₂i
加法: a + b = (x₁+x₂) + (y₁+y₂)i
减法: a - b = (x₁-x₂) + (y₁-y₂)i
乘法: a * b = (x₁x₂ - y₁y₂) + (x₁y₂ + x₂y₁)i
除法: a / b = [(x₁x₂ + y₁y₂) + (y₁x₂ - x₁y₂)i] / (x₂² + y₂²)
*/
complexCompare1 := 1 + 2i
complexCompare2 := 1 + 2i
complexCompare3 := 1 + 3i
// == 和 != 可以比较(实部和虚部分别相等)
fmt.Println(complexCompare1 == complexCompare2) // true
fmt.Println(complexCompare1 != complexCompare3) // true
// 如果实部或虚部包含NaN,则==会固定返回false,因为NaN != NaN
// 不支持 < > <= >= 比较(复数没有大小概念)
// fmt.Println(a1 < b1) // 编译错误!
// 同样的,complex是不支持隐式转换的,必须显式转换
var c64 complex64 = 1 + 2i
var c128 complex128
c128 = complex128(c64) // complex64 → complex128(安全)
c64 = complex64(c128) // complex128 → complex64(可能丢失精度)
}精度丢失问题及其解决方案
package main
import "fmt"
// 解决方案
// 1. 引入高精度decimal包
import "github.com/shopspring/decimal"
func main() {
var floatVar1 float64 = 0.1
var floatVar2 float64 = 0.2
var floatVar3 float64 = floatVar1 + floatVar2 // 在这里出现了精度丢失问题.
fmt.Println(floatVar3)
// 2. 使用decimal.Decimal类型进行计算
var decimalVar1 decimal.Decimal = decimal.NewFromFloat(0.1)
var decimalVar2 decimal.Decimal = decimal.NewFromFloat(0.2)
// 3. 使用类似这样的方法进行数学运算避免精度丢失问题.
var decimalVar3 decimal.Decimal = decimalVar1.Add(decimalVar2)
fmt.Println(decimalVar3)
}布尔类型(bool)
package main
import "fmt"
func main() {
// 声明布尔变量
var boolVar bool = true
fmt.Println(boolVar)
if boolVar {
fmt.Println("boolVar是真,1")
} else {
fmt.Println("boolVar是假")
}
if boolVar == true {
fmt.Print("boolVar是真,2")
}
// 逻辑运算
var x bool = true
var y bool = false
fmt.Println("x && y: ", x && y)
fmt.Println("x || y: ", x || y)
fmt.Println("!x :", !x)
}其它派生类型
指针类型 *
使用 & 取出地址
使用 *<类型> 声明一个指针类型的变量
package main
import "fmt"
func main() {
var intVar int = 10
// 声明一个指针,指向intVar
var ptrVar *int = &intVar
fmt.Println("intVar的值@地址: ", intVar, "@", ptrVar)
fmt.Println("通过指针访问intVar,*ptrVar: ", *ptrVar)
var nullPtrVar *int
fmt.Print("定义了空指针nullPtrVar: ", nullPtrVar)
// 对空指针解引用会产生NPE.
// 定义一个结构体
type Person struct {
name string
age int
}
// 声明一个标准变量
var person1 Person = Person{
name: "Alice",
age: 18,
}
// 声明一个结构体指针
var ptrPerson1 *Person = &Person{
name: "Alice",
age: 18,
}
fmt.Println("person1姓名: ", person1.name)
fmt.Println("ptrPerson1姓名: ", (*ptrPerson1).name) // 这是标准写法,需要先解引用指针去访问结构体,再访问属性
fmt.Println("ptrPerson1姓名: ", ptrPerson1.name) // 但是呢,在Go语言中,提供了语法糖,可以不必解引用就可以直接访问属性.
}字符串类型
声明方式
// 声明字符串,共四种方式.
var strVar string = "Hello, \tKulve\n" // 使用双引号创建的字符串,是支持转义字符的.
var strVar2 string = `Hello, \tkulve\n` // 使用反引号创建的字符串,是不支持转义字符的.
var formatStrVar string = fmt.Sprintf("Hello, %s", "Kulve")
var stringBuilder strings.Builder
stringBuilder.WriteString("Hello, ")
stringBuilder.WriteString("Kulve")
fmt.Println("strVar: ", strVar)
fmt.Println("strVar2: ", strVar2)
fmt.Println("formatStrVar: ", formatStrVar)
fmt.Println("stringBuilder: ", stringBuilder.String())常用属性
// 常用的属性
strVar = "Hello, Kulve"
strVar2 = "你好, 圐圙" // 其它字符还是一字节
// 获取字节长度,中文UTF8每字符占用3字节
fmt.Printf("len(strVar): %d \n", len(strVar))
fmt.Printf("len(strVar2): %d \n", len(strVar2))
// 获取字符数量
fmt.Printf("utf8.RuneCountInString(strVar): %d \n", utf8.RuneCountInString(strVar))
fmt.Printf("utf8.RuneCountInString(strVar2): %d \n", utf8.RuneCountInString(strVar2))
// 使用索引访问字符串,返回的是byte而不是字符
fmt.Printf("strVar[0]: %d \n", strVar[0])
// 遍历字符串
for index, char := range strVar2 {
fmt.Printf("%d,%c \t", index, char)
}
fmt.Printf("\n")常用方法
// 常用的方法(strings包)
strVar = "Hello, Kulve"
fmt.Println("包含Kulve: ", strings.Contains(strVar, "Kulve"))
fmt.Println("以Hello开头: ", strings.HasPrefix(strVar, "Hello"))
fmt.Println("以Kulve结尾: ", strings.HasSuffix(strVar, "Kulve"))
fmt.Println("返回,首次出现的索引: ", strings.Index(strVar, ","))
fmt.Println("返回e最后一次出现的索引: ", strings.LastIndex(strVar, "e"))
fmt.Println("字符e出现次数: ", strings.Count(strVar, "e"))
fmt.Println("转换大写: ", strings.ToUpper(strVar))
fmt.Println("转换小写: ", strings.ToLower(strVar))
fmt.Println("移除两边空白字符: ", strings.TrimSpace(" Hello, Kulve "))
fmt.Println("移除两端的指定字符: ", strings.Trim("||Hello, Kulve||", "|"))
fmt.Println("只移除左边的指定字符: ", strings.TrimLeft("||Hello, Kulve||", "|"))
fmt.Println("只移除右边的指定字符: ", strings.TrimRight("||Hello, Kulve||", "|"))
fmt.Println("替换第1个找到的kulve为圐圙: ", strings.Replace(strVar, "Kulve", "圐圙", 1))
fmt.Println("替换所有的kulve为圐圙: ", strings.ReplaceAll(strVar, "Kulve", "圐圙"))
fmt.Println("分割字符串: ", strings.Split("Hello, Kulve, 圐圙", ",")) // 返回值是一个列表/数组
fmt.Println("分割字符串为指定长度的列表: ", strings.SplitN("Hello, Kulve, 圐圙", ",", 2))
fmt.Println("将指定的字符串列表以指定分隔符连接为字符串: ", strings.Join([]string{"Hello", "Kulve", "圐圙"}, "#"))
fmt.Println("重复字符串: ", strings.Repeat("Hello, Kulve. \n", 3))类型转换
package main
import "fmt"
func main() {
var strVar string = "Hello"
// 字符串和切片类型转换
// string -> []byte
var byteSlice []byte = []byte(strVar)
fmt.Println(byteSlice)
// string -> []rune,注意,这样才是正确的处理UTF8的正确方式.
var runeSlice []rune = []rune(strVar)
fmt.Println(runeSlice)
// []byte -> string
var strResult1 string = string(byteSlice)
fmt.Println(strResult1)
// []rune -> string
var strResult2 string = string(runeSlice)
fmt.Println(strResult2)
}类型转换2
package main
import (
"fmt"
"strconv"
)
func main() {
var strNum string = "12345"
// string -> int 方式一: strconv.AtoI(<字符串>)
var intNum1, err1 = strconv.Atoi(strNum)
if err1 != nil {
fmt.Println("转换失败: ", err1)
} else {
fmt.Println("strconv.Atoi(strNum): ", intNum1)
}
// string -> int 方式二: strconv.ParseInt(<字符串>,<进制>,<位数>)
// 这种方式更灵活
var intNum2, err2 = strconv.ParseInt(strNum, 10, 64)
if err2 != nil {
fmt.Println("转换失败: ", err2)
} else {
fmt.Println("strconv.ParseInt(strNum,10,64): ", intNum2)
}
// int -> string 方式一: strconv.Itoa(<整数>)
var intVar int = 100
var strResult = strconv.Itoa(intVar)
fmt.Println("strconv.Itoa(intVar): ", strResult)
// int -> string 方式二: strconv.FormatInt(<int64值>,<进制>)
strResult = strconv.FormatInt(int64(intVar), 10)
fmt.Println("strconv.FormatInt(int64(intVar), 10): ", strResult)
// int -> string 方式三: strconv
strResult = fmt.Sprintf("%d", intVar)
fmt.Println("fmt.Sprintf(\"%d\", intVar): ", strResult)
var floatStr string = "123.456"
// string -> float
var floatNum, err3 = strconv.ParseFloat(floatStr, 64)
if err3 != nil {
fmt.Println("转换失败: ", err3)
} else {
fmt.Println("strconv.ParseFloat(floatStr,64): ", floatNum)
}
// float -> string 方式一: strconv.FormatFloat(<float64>,'f',)
var floatVar float64 = 100.01
// 形参解释:
// fmt: 'f' 普通十进制小数,无指数
// fmt: 'e' 科学计数法,小写e
// fmt: 'E' 科学计数法,大写E
// fmt: 'g' 根据情况自动选择f或e
// fmt: 'G' 根据情况自动选择f或E
// prec: <精度>, 保留几位小数,若不足则补0. 若为-1,则为最小必要位数,不再强制补0.
// bitSize: <32/64>, 指定float的长度.
strResult = strconv.FormatFloat(floatVar, 'f', 2, 64)
fmt.Println("strconv.FormatFloat(floatVar, 'f', 2, 64): ", strResult)
strResult = strconv.FormatFloat(floatVar, 'e', -1, 64)
fmt.Println("strconv.FormatFloat(floatVar, 'e', -1, 64): ", strResult)
}数组类型 []
声明
package main
import "fmt"
func main() {
// 1. 声明时指定长度,会自动初始化为默认值.
var arrVar1 [5]string
fmt.Println(arrVar1)
// 2. 在声明时指定长度并手动初始化.
var arrVar2 = [3]string{"Hello, ", "Kulve", "."}
fmt.Println(arrVar2)
// 3. 在声明时由编译器自动确定长度.
var arrVar3 = [...]string{"Hello, ", "Kulve", "."}
fmt.Println(arrVar3)
// 4. 邪门玩法: 在声明时指定下标,其它元素初始化为默认值.
var arrVar4 = [5]string{1: "Hello, ", 3: "Kulve", 4: "."}
fmt.Println(arrVar4)
}遍历与数组的传递方式
package main
import "fmt"
func main() {
// 1. 遍历数组
var arrVar = [...]int{1, 2, 3, 4, 2, 4, 5}
for index, value := range arrVar {
fmt.Printf("arrVar[%d]: %d\n", index, value)
}
// 2. 获取数组的长度和容量,在数组中,长度与容量是相等的.
fmt.Printf("长度: %d, 容量: %d\n", len(arrVar), cap(arrVar))
// 3. 数组是值类型,这与其它大部分语言不同.
arrCopy := arrVar
arrCopy[0] = 999
fmt.Println("原数组: ", arrVar[0]) // 1 (未改变)
fmt.Println("新数组: ", arrCopy[0]) // 999
}func main() {
// 声明一个包含指定元素数量的数组
var arr [5]int = [5]int{1, 2, 3, 4, 5}
// 简写形式
arr2 := [3]string{"Go", "Python", "Java"}
// 让编译器推断长度,将[<长度>]换成[...]就可以
arr3 := [...]int{10, 20, 30}
fmt.Println(arr[0]) // 输出: 1
fmt.Println(len(arr)) // 输出: 5
fmt.Println(arr2) // 输出: [Go Python Java]
fmt.Println(arr3) // 输出: [10 20 30]
}结构体 type struct
// 声明结构体
// 这个与java中的class不同,它不能存储方法.
type Person struct {
Name string
Age int
Email string
}
func main() {
// 为结构体字段赋值
// 方式1: 按字段顺序
p1 := Person{"Alice", 30, "alice@example.com"}
// 方式2: 指定字段名(推荐,也就是命名方式)
p2 := Person{
Name: "Bob",
Age: 25,
}
// 方式3: 先声明再赋值,有点类似于class
var p3 Person
p3.Name = "Charlie"
fmt.Println(p1.Name) // 输出: Alice
fmt.Println(p2) // 输出: {Bob 25 }
}通道 channel
需要注意的是,若缓冲区满了或者创建的是无缓冲区的通道,那么在接收方或发送方无法接收或发送时,程序将会阻塞.
func main() {
// channel: 通道
// 用于在协程之间通信使用
// 创建缓冲为 2 的 int 类型 channel
bufferedChannel := make(chan int, 2)
// 发送数据到 channel
bufferedChannel <- 100
bufferedChannel <- 200
// 从 channel 接收数据
// 先进先出,类似一个队列
x := <-bufferedChannel
y := <-bufferedChannel
fmt.Println(x, y) // 输出: 100 200
// 无缓冲 channel
unbufferedChannel := make(chan string)
// 异步发送数据
go func() {
unbufferedChannel <- "Hello from goroutine"
}()
msg := <-unbufferedChannel
fmt.Println(msg)
}函数 func
// 示例1: 定义一个函数类型的接口
type AddFunc func(int, int) int
func main() {
// 函数作为变量
// 声明函数,并赋值给变量保存起来
var add AddFunc = func(a, b int) int {
return a + b
}
// 调用时,使用变量名作为函数名使用.
result := add(3, 5)
fmt.Println(result) // 输出: 8
// 示例2: 函数作为参数
// 参数1: a
// 参数2: b:int
// 参数3: op:func(int, int) int,是一个函数类型
operate := func(a, b int, op func(int, int) int) int {
return op(a, b)
}
// 使用,这里先声明一个函数,作用是将a,b相乘,并返回结果.
multiply := func(a, b int) int { return a * b }
// 使用operate函数,传入值a,b,以及函数multiply
fmt.Println(operate(4, 5, multiply)) // 输出: 20
// 示例3: 函数作为返回值
// 函数名: makeMultiplier
// 接收参数: factor int
// 函数返回值类型: func(int) int
makeMultiplier := func(factor int) func(int) int {
// 在这里,返回了一个函数,用于计算x*factor.
return func(x int) int {
return x * factor
}
}
double := makeMultiplier(2)
fmt.Println(double(7)) // 输出: 14
}切片类型 slice
package main
import "fmt"
func main() {
// 1. 通过数组创建切片
var arrVar = [...]int{1, 2, 3, 4, 2, 4, 5}
var sliceVar = arrVar[1:4]
fmt.Println(sliceVar)
// 2. 直接创建切片
var sliceVar2 = []int{10, 20, 30}
fmt.Println(sliceVar2)
// 3. 使用make创建切片: make([]类型,<长度>,<容量>)
var sliceVar3 = make([]int, 10, 20)
sliceVar3[1] = 10
fmt.Println(sliceVar3)
// 相关方法
// 追加元素
sliceVar3 = append(sliceVar3, 1, 2)
fmt.Println(sliceVar3) // 输出: [a b c d e]
// 切片复制
s4 := make([]int, len(sliceVar3))
copy(s4, sliceVar3)
}一个示例: 在切片中搜索指定内容
package main
import (
"fmt"
"slices"
)
func main() {
// 示例: 在切片中搜索指定内容.方式一: 遍历切片
var sliceVar = []string{"Hello", ", ", "Kulve", "."}
var targetString string = "Kulve"
var found bool = false
var index int = 0
for i, v := range sliceVar {
if v == targetString {
found = true
index = i
break
}
}
if found {
fmt.Printf("搜索%s,下标%d\n", targetString, index)
} else {
fmt.Printf("搜索%s,不存在\n", targetString)
}
// 在切片中搜索指定内容.方式二: 使用Go1.21+提供的slices包.
found = slices.Contains(sliceVar, targetString)
if found {
index = slices.Index(sliceVar, targetString)
fmt.Printf("搜索%s,下标%d\n", targetString, index)
} else {
fmt.Printf("搜索%s,不存在\n", targetString)
}
}常用方法
package main
import "fmt"
func main() {
// 在切片中添加一个元素
var sliceVar = []string{"Hello", ", ", "Kulve", "."}
sliceVar = append(sliceVar, "圐圙")
fmt.Println(sliceVar)
// 在切片中添加多个元素
sliceVar = append(sliceVar, "村", "委", "会")
fmt.Println(sliceVar)
// 在切片中添加另外一个切片,需要使用...对切片解包
var sliceVar2 = []string{"大", "队", "."}
sliceVar = append(sliceVar, sliceVar2...)
fmt.Println(sliceVar)
// 切片没有内置的删除方法,删除只能通过对切片进行切片后拼接删除.
}切片的传递
package main
import "fmt"
func main() {
// 在切片中添加一个元素
var sliceVar1 = []string{"Hello", ", ", "Kulve", "."}
var sliceVar2 = sliceVar1
sliceVar1 = append(sliceVar1, "圐圙") // 当注释掉这行后,下一行的修改将会被传递到sliceVar2上.
// 其原因是因为切片扩容,导致地址改变,但sliceVar2仍然持有原地址
sliceVar1[1] = "西西米托"
fmt.Println("sliceVar1: ", sliceVar1)
fmt.Println("sliceVar2: ", sliceVar2)
}接口类型
// 定义接口
type Animal interface {
Speak() string
Move() string
}
// Dog 实现 Animal 接口
// Go语言的接口,是隐式实现的,不需要显式声明实现的接口
// 只需要同时实现接口内的所有函数,就可以将实现赋值到接口类型的对象上,见下面的ShowAnimal函数,传入值是一个animal接口类型对象.
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return "汪汪!"
}
func (d Dog) Move() string {
return "四条腿跑"
}
// Cat 实现 Animal 接口
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return "喵喵~"
}
func (c Cat) Move() string {
return "悄无声息地走"
}
// 多态函数
func ShowAnimal(a Animal) {
fmt.Printf("%s, %s\n", a.Speak(), a.Move())
}
func main() {
// 声明两个实体类
dog := Dog{"旺财"}
cat := Cat{"咪咪"}
// 这里调用了多态函数,传入了两个实体类,由多态函数自动处理.
ShowAnimal(dog) // 输出: 汪汪!, 四条腿跑
ShowAnimal(cat) // 输出: 喵喵~, 悄无声息地走
// 空接口: 可以存储任意类型,限一个,如基本类型,函数类型,切片类型,结构体等.
// 在Go中,所有的方法都实现了空接口
var anything interface{} = "hello"
anything = 42
anything = 3.14
fmt.Println(anything)
}map类型
这是一个非常重要的类型
基本使用
func main() {
// 方式1: make 创建
scores := make(map[string]int)
scores["Alice"] = 95
scores["Bob"] = 87
// 方式2: 字面量
ages := map[string]int{
"Tom": 20,
"Jerry": 18,
}
// 访问
fmt.Println(scores["Alice"]) // 输出: 95
// 判断键是否存在
if score, ok := scores["Charlie"]; ok {
fmt.Println("存在:", score)
} else {
fmt.Println("Charlie 不存在")
}
// 删除
delete(ages, "Tom")
// 遍历
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}
}新增Map元素的几种方式
没有单独的新增函数,若要新增函数,需要在循环中实现,利用map中键不存在,会自动新增的特性实现添加.
func main() {
// 创建一个空的map
scores := make(map[string]int)
// 新增元素
// 方式1: 直接赋值新增
// 当键不存在时,会自动新增元素
scores["Alice"] = 95
scores["Bob"] = 87
fmt.Println("初始map:", scores) // map[Alice:95 Bob:87]
// 方式2: 在循环中动态新增
names := []string{"Charlie", "David", "Eve"}
// i: 值
// name: 键
// 循环遍历namesmap,利用键不存在时,自动新增元素的特性,将names中的元素添加到scores中
// 若已存在,那么值将会被覆盖
for i, name := range names {
scores[name] = 90 + i*5 // Charlie:90, David:95, Eve:100
}
fmt.Println("新增后:", scores) // map[Alice:95 Bob:87 Charlie:90 David:95 Eve:100]
// 方式3: 从另一个map复制新增
// 原理与上一个差不多
extraScores := map[string]int{
"Frank": 88,
"Grace": 92,
}
for k, v := range extraScores {
scores[k] = v // 如果key不存在就是新增
}
fmt.Println("合并后:", scores) // map[Alice:95 Bob:87 ... Frank:88 Grace:92]
// 方式4: 条件新增(只有key不存在时才添加)
if _, exists := scores["Henry"]; !exists {
scores["Henry"] = 85
fmt.Println("新增Henry:", scores["Henry"]) // 85
}
}链式运算
Go的链式运算,遵循运算符的运算规则,从左到右.
2.3 变量声明
变量声明方式
// 声明方式一: 若活力类型,编译器会自动推断类型
var <变量名> [类型] = <值>
// 声明方式二: 此时编译器会自动推断类型
<变量名> := <值>
// 声明方式三: 一次性声明多个变量
var <变量名列表> [类型] = <值列表>2.4 值类型和引用类型
int,float,string,bool类型为值引用类型,即在使用=时,会在内存中复制一份
其它复杂类型为引用类型.
2.5 常量声明
常量推荐使用大驼峰或全大写+下划线
go官方推荐使用大驼峰.
基础声明
package main
import "fmt"
func main() {
const LENGTH int = 10
const WIDTH int = 5
var area int
const a, b, c = 1, false, "str" // 多重赋值
area = LENGTH * WIDTH
fmt.Printf("面积为 : %d", area) // 面积为 : 50
fmt.Println()
fmt.Println(a, b, c) // 1 false str
}使用常量组
const (
a = 1
b
c
d
)
func main() {
fmt.Println(a)
// b、c、d没有初始化,使用上一行(即a)的值
fmt.Println(b) // 输出1
fmt.Println(c) // 输出1
fmt.Println(d) // 输出1
}一个特殊的iota 常量计数器
iota是一个特殊常量,用于生成递增的序列
基础使用
// iota 从 0 开始,每行加 1
const (
a = iota // 0
b // 1,将会使用上一行的iota递增序列.
c // 2
)
func main() {
fmt.Printf("a=%d, b=%d, c=%d\n", a, b, c) // a=0, b=1, c=2
}进阶使用
iota在常量声明时,若在中间有显式赋值的变量时,将会停止使用iota,显式赋值的变量之后声明的新变量,将会使用上一行的值,也就是使用显式赋值的变量的值,等到下次使用显式赋值或使用iota时,变量才会改变值.
实际上,这是go语言的机制,参见常量组的声明
package main
import "fmt"
func main() {
const (
a = iota // 0
b // 1
c // 2
d = "ha" // 独立值,iota += 1,实际值为给定的值而不是iota
e // "ha",iota += 1,实际值为上一行的值而不是iota
f = 100 // 独立值,iota += 1,同样的,实际值为上一行的值而不是iota
g // 100,iota += 1,同上.
h = iota // 7,恢复使用 iota 值,iota经过中间几个变量后,值为7
i // 8
)
fmt.Printf("a=%d, b=%d, c=%d, d=%s, e=%s, f=%d, g=%d, h=%d, i=%d\n", a, b, c, d, e, f, g, h, i)
}1.6 运算符
同其它语言
1.7 程序结构
if分支结构
if <condition> {
<if block>
} [else {
<else block>
}]switch分支结构
switch var1 {
case val1:
...
case val2:
...
default:
...
}select 分支结构
这个结构只能用于通道操作,每个case上的表达式,必须是一个通道操作,要么是发送,要么是接收
不带default子句的select,一般用于同时监听多个通道,只要任意一个通道有数据,那么就可以执行下去.
select {
case <- channel1:
// 执行的代码
case value := <- channel2:
// 执行的代码
case channel3 <- value:
// 执行的代码
// 你可以定义任意数量的 case
default:
// 所有通道都没有准备好,执行的代码
}- 每个case都是一个通道
- 所有的channel在执行到select时,都将会被求值,此时,在本次执行select时,值已经被确定,不会再修改.
- 如果有多个case可以运行,那么select将会随机选出一个执行,其它的不会执行
- 在找不到可执行的case时,如果有default,那么将执行default,如果没有default,那么将会阻塞,此时将不再对channel或值进行求值,此时即使将通道替换为nil,也不会改变select正在监听的通道内的对象
for 循环结构
go语言中只有for循环,但有三种形式可选,可以用其中一种代替while循环.
// 方式一: 类似于其它语言的for
for init; condition; post {}
// 方式二: 类似于其它语言的while
for condition {}
// 方式三: 类似于其它语言的for(;;)
for {}示例
func main() {
numbers := [6]int{1, 2, 3, 5}
// 这里的key, value 类似于js的解构赋值,可以同时取出键和值.
for key, value := range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", key, value)
}
}// 控制循环语句
break: 中断本层循环,跳出本层循环
continue: 中断本次循环,重新开始本层循环.// 无条件跳转语句,非常不建议使用该语句
goto <label>
...
label: <标签名>1.8 函数
函数使用大驼峰命名
大驼峰命名的函数会导出,也就是全局可用.
小驼峰命名的函数不会导出,也就是仅限于本包.
结构
func <函数名>( [参数列表] ) [返回值类型] {
函数体
}函数是允许返回多个值的,如
func swap(x, y string) (string, string) {
return y, x
}1.9 函数作用域
- 在函数内部声明的变量,包括形式参数与返回值,都是局部变量
- 在函数外声明的变量,可以在本包内使用
- 若使用导出函数,那么可以在外部包使用
如何确定是否导出?
导出: 首字母大写,如 var ExportedVar int = 100
不导出: 首字母小写,如 var unexportedVar int = 100;
使用外部包的变量,需要注意的就是文章刚开头说的,关于包,项目文件的组织安排的问题.
// GoLearn/main.go
package main
import (
"GoLearn/domain" // 这里的导入,需要从项目的模块名开始写.
"fmt"
)
func main() {
// 可以访问导出的内容
fmt.Println(domain.ExportedVar) // 正常访问
fmt.Println(domain.ExportedFunc()) // 正常访问
obj := domain.ExportedStruct{
Name: "张三", // 大写字段可访问
// age: 25, // 编译错误!age 未导出
}
// 以下全部编译错误
// pack2.unexportedVar // 未导出
// pack2.unexportedFunc() // 未导出
fmt.Println(obj.Name)
fmt.Println(obj.Address)
}// GoLearn/domain/pack2.go
package domain
// 导出(大写开头)—— 外部包可用
var ExportedVar = "我是导出的变量"
func ExportedFunc() string { return "导出函数" }
type ExportedStruct struct {
Name string // 导出字段
age int // 未导出字段(包外不可见)
Address string // 导出字段
}
// 未导出(小写开头)—— 仅限包内
var unexportedVar = "私有变量"
func unexportedFunc() string { return "私有函数" }Go 拓展使用
2.1 指针 pointer
啊,指针,危险又美丽的东西
基本声明与使用
func main() {
var a int
var ptr *int
var pptr **int // Go支持指向指针的指针,使用**就可以了
a = 3000
// 指针 ptr 地址
ptr = &a
// 指向指针 ptr 地址
pptr = &ptr
// 获取 pptr 的值
fmt.Printf("变量 a = %d\n", a)
fmt.Printf("指针变量 *ptr = %d\n", *ptr)
fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
}指针在数组中的应用
const max = 3
func main() {
number := [max]int{5, 6, 7}
var ptrs [max]*int // 声明int类型的指针数组
// 将number数组的值的地址赋给ptrs
// &number: 指向了number数组,在这块内存区域中,使用for遍历
// 注意,这里使用number也是可以的,number与&number的区别在于,number是将数组复制了一份,页&number是指向数组的一个指针,使用数组复制时,是有性能开销的,但适用于单线程环境或者多线程不修改数组的环境.
// i: 键
// x: 值
for i, x := range &number {
// 取出x的内存地址,赋给指针数组的对应元素
ptrs[i] = &x
}
// 将
for i, x := range ptrs {
fmt.Printf("指针数组:索引:%d 值:%d 值的内存地址:%d\n", i, *&*x, x)
}
}2.2 结构体 struct
基本声明与使用
// 定义
type <结构体名> struct {
<成员名1> <类型>
<成员名2> <类型>
...
}
// 声明
var <变量名> <结构体类型名>
// 使用
<变量名>.<成员名>结构体同样支持指针.
2.3 切片 slice
基本声明与使用
切片与
func main() {
// 声明一个未指定大小的数组,这就是一个切片了
// 声明方法一: 直接声明一个空切片
var identfier []int
fmt.Println(identfier)
// 声明方法二: 使用make()函数
// make(<类型>,<长度>[,<最大容量>])
//
identfier2 := make([]int, 0)
fmt.Println(identfier2)
var numbers = make([]int, 3, 5)
fmt.Println(numbers)
// 切片的初始化
var initArray = []int{1, 2, 3} // 实际上,这里省略了[]int类型,initSlice1是由编译器自动推导的,最最完整的定义是这样的 var initSlice1 []int = []int{1, 2, 3}
// 切片的截取/使用
// 可以通过设置下限及上限来设置截取切片[lower:upper]
// 数组名[起始索引:结束索引],包含起始索引,不包含结束索引
// 若省略结束索引,则到数组末尾
// 若省略起始索引,则从0开始
var initSlice1 = initArray[0:2]
fmt.Println(initArray)
fmt.Println(initSlice1)
// 这一行的意思是,将initSlice切片,增加了一个元素,值为4.
// append(<切片>[,<元素列表>])
initSlice1 = append(initSlice1, 0)
fmt.Println(initSlice1)
// 获取切片长度
initSlice1Len := len(initSlice1)
fmt.Println(initSlice1Len)
// 获取切片容量
initSlice1Cap := cap(initSlice1)
fmt.Println(initSlice1Cap)
// 复制一份切片
// copy(<目标切片>,<源切片>)
var initSlice2 = make([]int, 66)
copy(initSlice2, initSlice1)
fmt.Println(initSlice2)
}- 切片的数据是存放在原数组中的,多个切片可以共享同一数组
- 但,若切片超出的容量,那么将会复制一份原数据,再将新数据添加进来.
2.4 范围 range
范围主要用于迭代数组,切片,通道或集合中的元素. 用于for循环中. 其返回值根据迭代对象决定
func main() {
// 声明一个包含 2 的幂次方的切片
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
// 遍历数组,会返回两个值,第一个是索引,第二个是值
for index, value := range pow {
// 打印 2 的 i 次方等于 v
fmt.Printf("2^%d = %d\n", index, value)
}
// 遍历字符串,会返回两个值,一个是索引,一个是字符(类似于Char)
for i, c := range "hello我0" {
fmt.Printf("index: %d, char: %c\n", i, c)
}
// 创建一个空的 map,key 是 int 类型,value 是 float32 类型
floatMap := make(map[int]float32)
// 向 map1 中添加 key-value 对
// float类型的map
floatMap[1] = 1.0
floatMap[2] = 2.0
floatMap[3] = 3.0
floatMap[4] = 4.0
// 遍历map时,会返回两个值,一个是键,一个是值
for key, value := range floatMap {
// 打印 key 和 value
fmt.Printf("key is: %d - value is: %.2f\n", key, value)
}
// range 也可以用在 map 的键值对上。
// 字符串类型的map
stringMap := map[string]string{"a": "apple", "b": "banana"}
for k, v := range stringMap {
fmt.Printf("%s -> %s\n", k, v)
}
// 遍历通道中的元素,直到通道关闭为止
ch := make(chan int, 2)
ch <- 100
ch <- 2
close(ch)
// 遍历通道,以获取通道中的元素
for v := range ch {
fmt.Println(v)
}
}2.5 集合 map
Map是一种无序键值对的集合,可通过key快速检索value的值,当然,也可以使用切片迭代map.
此外,map是引用类型,将map的值,赋值给另外一个变量后,若在另外一个变量中修改了map,则原map也会被修改.
map必须先声明,再初始化后才能使用,否则直接报错.
func main() {
var siteMap map[string]string // 创建集合,但此时siteMap为nil,需要使用make初始化后才能使用.
fmt.Println(siteMap) // map[]
fmt.Println(siteMap == nil) // true
// 懒人写法,一步到位
//siteMap := make(map[string]string)
siteMap = make(map[string]string) // 初始化map,分配空间
// 插入新元素时,只要键不存在,那么就是新增,若键已存在,那么就是修改
siteMap["Google"] = "谷歌"
siteMap["Baidu"] = "百度"
siteMap["KuLve"] = "圐圙"
siteMap["Wiki"] = "维基百科"
// 使用键输出网站集合
for site := range siteMap {
fmt.Println(site, "->", siteMap[site])
}
// 查看元素在集合中是否存在
// ok的值为true或false
// name为值
value, ok := siteMap["Facebook"]
if ok {
fmt.Println("Facebook 的 站点是", value)
} else {
fmt.Println("Facebook 站点不存在")
}
}2.6 强制类型转换
基本语法
<类型>(<表达式>)- 本文链接: https://go.kulve.tech/posts/go%E8%AF%AD%E8%A8%80%E5%85%A5%E9%97%A8/
- 版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。
