Go语言基本语法入门(二)| 豆包MarsCode AI 刷题

80 阅读5分钟

借助豆包 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)

出处:

Go学习笔记

reference

《Go语言圣经》

tour.go-zh.org/

gopl-zh.github.io**/**