借助豆包 MarsCode AI 刷题助力 Go 语言学习
在 Go 语言学习的旅程中,豆包 MarsCode AI 刷新了以往我学习编程语言观念
字节内部课程知识讲解深入浅出,面对 Go 复杂特性,像并发编程里的协程与通道,起初概念似一团迷雾。向 AI 求助,它先以通俗比喻阐述原理,将协程类比工厂多条流水线并行作业,通道则是流水线间传递 “货物” 的传送带,助我把握基础。接着结合代码实例详述细节,展示如何精准创建、调度协程及高效利用通道通信,让抽象知识具象化。
代码纠错高效实用。刷题时语法或逻辑出错,AI 迅速定位,曾因疏忽在函数闭包中误改外部变量致结果偏差,AI 点明 Go 变量作用域规则,给出修正思路,还附优化建议提升性能。
智能出题更是一绝,依我薄弱点生成 Go 接口使用、切片操作难题,在 “练中学” 里稳步攻克知识短板,实现能力进阶。
编程像冒险,Go 语言这片天地,充满挑战。从谨慎处理变量声明,到费力搭建函数,再到钻研并发编程,一路摸索,历经刷题实战、借助 AI 助力,收获不少。现在,我把积攒的 Go 语言基础知识第二期亮出来,就像分享一箱实用工具,助大家开拓编程之路。
流程控制
for
go 中唯一的循环结构。同C 的区别就是没有()。初始化语句和后置语句也是可选的。
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
func main() {
var sum int
for sum := 1; sum < 1000; {
sum += sum
}
fmt.Println(sum) // 0
}
while
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
如果省略循环条件,该循环就不会结束,因此无限循环可以写得很紧凑。
for{
}
if
同C,但是不需要(),必须有{ }
同 for 一样, if 语句可以在条件表达式前执行一个简单的语句。v 的作用域只在if 块内,if else是同一块。
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}else {
fmt.Printf("%g >= %g\n", v, lim)
}
fmt.Println(v) // v undefined
return lim
}
switch
case后有默认的break,除非以fallthrough结束。
case 值不必为常量,取值也不必是整数,可以是字符串。
func main() {
fmt.Println("When's Saturday?")
today := time.Now().Weekday()
switch time.Saturday {
case today + 0:
fmt.Println("Today.")
case today + 1:
fmt.Println("Tomorrow.")
case today + 2:
fmt.Println("In two days.")
default:
fmt.Println("Too far away.")
}
}
没有条件的 switch 同 switch true 一样。
这种形式能将一长串 if-then-else 写得更加清晰。
defer
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
func main() {
defer fmt.Println("world")
fmt.Print("hello ")
}
// hello world
defer 栈
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
更多关于 defer 语句的信息,请阅读此博文。
高级
指针
go 有指针, *T 是指向T类型的指针,空值为nil
&取地址。
i := 42
p = &i
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21 // 通过指针 p 设置 i
与C不同的是,Go没有指针运算。
结构体
type Vertex struct {
X int
Y int
}
func main() {
fileds := Vertex{1, 2}
fmt.Println(fileds)
fmt.Println(fileds.X)
}
用.访问元素
结构体指针的隐式访问
如果我们有一个指向结构体的指针 p,那么可以通过 (*p).X 来访问其字段 X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X 就可以。
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9 // (*p).X
fmt.Println(v)
}
指定赋值
使用name: val 指定某一字段的赋值。可以不按struct定义的顺序指定赋值。
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
v2 = Vertex{X: 1} // Y:0 被隐式地赋予
v3 = Vertex{} // X:0 Y:0
p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)
func main() {
fmt.Println(v1, p, v2, v3)
}
数组
[n]T :拥有n个T类型的数组,一旦定义 [n]T相当于类型,n作为长度不可更改。
var a [10]int
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
Hello World [Hello World] [2 3 5 7 11 13]
切片
[]T 表示一个元素类型为 T 的切片。
a[low : high] 表示[low, high)
low <= high,而且不能越界。不像python 有-1代表最后一个索引,但支持缺省值。
切片并不存储任何数据,它只是描述了底层数组中的一段。改变切片也会改变底层。
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}
[]bool{true, true, false} 等同于 [3]bool{true, true, false}
在进行切片时,默认是上下界。
// 对于数组 var a [10]int来说,以下切片是等价的:
a[0:10]
a[:10]
a[0:]
a[:]
make创建
make可以创建切片,也可以创建动态数组。
切片的长度和容量
切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。
所有的切片扩展都是在底层原数组的基础上操作的。
切片的零值是 nil。
nil 切片的长度和容量为 0 且没有底层数组。
并发
goroutine
由go运行时管理的轻量级线程
信道
使用前必须创建
ch := make(chan int)