基础语法
for循环
func main() {
sum := 0
for i := 0; i < 100; i++ {
sum = sum + 1
}
fmt.Println("sum =", sum)
}
defer
先defer的后执行,后defer的先执行。
案例1
所有 defer 注册的语句会按照 后进先出 的顺序,在 main() 函数结束前执行。
func main() {
fmt.Println("counting")
for i := 0; i < 5; i++ {
fmt.Println("i =", i)
defer fmt.Println(i)
}
fmt.Println("done")
}
结果输出
counting
done
4
3
2
1
0
案例2
func main() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
}
输出结果
3
2
1
指针
func main() {
var p *int
i := 42
p = &i
fmt.Println(*p) // 42
*p = 21 //修改 p 所指向的内容,此时 a 的值也会被修改
fmt.Println(i) //21
fmt.Println(*p) //21
fmt.Println(p) // 16进制的地址值 0xc00000a0c8
}
解释:
- 关于p和*p的区别:可以把 p 看作是一个写着地址的纸条,而 *p 是根据这个纸条找到的房子里的实际内容。
- &i得到变量 i 的内存地址
注:
- Go 中的指针比 C/C++ 更安全,不会允许非法访问或指针运算。
- 使用指针可以减少内存拷贝,提高性能,尤其是在处理大结构体或函数参数传递时。
- 注意空指针(nil)检查,避免运行时 panic。
结构体
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
}
数组
//数组的长度不能改变大小
func main() {
var a [2]string
a[0] = "hello"
a[1] = "world"
fmt.Println(a[0], a[1]) //hello world
fmt.Println(a) //[hello world]
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes) //[2 3 5 7 11 13]
}
切片
//切片可以提供动态大小,基于数组构建,类似数组的引用,切片比数组更常用。
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13} //声明一个长度为6的数组
fmt.Println(primes)
//切片通过两个下标来界定,一个下界和一个上界,二者以冒号分割:
//a[low:high]
//它会选出一个半闭半开区间,前包后不包,即[low,high)]
//下面的例子中1:4表示从索引1到索引4的元素,即:3,5,7
var s []int = primes[1:4]
fmt.Println(s) //3,5,7
}
切片扩容
s := []int{1, 2, 3}
s = append(s, 4, 5, 6) // 自动扩容
如果超出容量,Go 会 自动创建一个新数组并拷贝数据。
新容量一般为原容量的 2 倍或 1.5 倍,依据当前长度增长策略。
len和cap的区别
len:当前切片中元素的个数
cap:从切片起始位置到底层数组末尾可容纳的元素个数
func main() {
//创建一个长度是5的切片
a := make([]int, 5)
printSlice("a", a)
//创建一个长度是0,容量是5的切片
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c) //0,0
d := c[2:5] //0,0,0
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n", s, len(x), cap(x), x)
}
关于切片与数组的区别
| 特性 | 数组(Array) | 切片(Slice) |
|---|---|---|
| 长度是否固定 | 固定(类型的一部分) | 可变(动态扩容) |
| 定义后能改长度 | ❌ 不可以 | ✅ 可以通过 append 增长 |
| 内存结构 | 值本身(存放元素) | 引用类型(指向底层数组) |
| 传参行为 | 值传递(拷贝整个数组) | 引用传递(指向同一个底层数组) |
| 内存占用 | 数组大小 × 元素大小 | 包含指针、长度、容量(3 个字段) |
| 使用场景 | 数量固定、性能敏感 | 数量动态变化的常规业务 |
| 作为函数参数 | 会复制一份数据 | 传递引用,操作原数据 |
举例传参行为
数组传参
func modifyArray(arr [3]int) {
arr[0] = 100
fmt.Println("In modifyArray: arr =", arr)
}
func main() {
a := [3]int{1, 2, 3}
modifyArray(a)
fmt.Println(a) //原数组内容不变,结果是:1,2,3
}
切片传参
func modifySlice(s []int) {
s[0] = 100
}
func main() {
s := []int{1, 2, 3}
modifySlice(s)
fmt.Println(s) // 输出:[100 2 3],原切片被修改了
}
range
range 是一个用于遍历数组、切片、字符串、映射(map)、通道(channel) 的关键字。它简化了对这些数据结构的迭代操作。
func main() {
var pow = []int{1, 2, 3, 4, 5}
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
解释代码:
pow 是一个整型切片,包含元素 [1, 2, 3, 4, 5]
range pow 遍历该切片:
- i 是索引(index)
- v 是索引对应位置的值(value)
Map(映射)
//结构体
type Vertex struct {
Lat, Long float64
}
//声明一个全局变量m,key是string,value实时Vertex
var m map[string]Vertex
func main() {
//使用make初始化map
m = make(map[string]Vertex)
//给map中添加一个键值对
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
函数
package main
import (
"fmt"
"math"
)
/*
*
* compute 是一个高阶函数,它接收一个函数作为参数:
参数 fn 是一个函数类型:func(float64, float64) float64
接收两个 float64 类型的参数
返回一个 float64 类型的结果
该函数调用传入的 fn,并传入 3 和 4 作为参数。
*/
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12)) //math.Sqrt(5*5+12*12)=13
fmt.Println(compute(hypot)) //等于hypot(3, 4)
fmt.Println(compute(math.Pow))
}
闭包
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
f := adder()
fmt.Println(f(1)) // 输出 1
fmt.Println(f(2)) // 输出 3
fmt.Println(f(3)) // 输出 6
}
- sum 是 adder 函数内部的局部变量;
- 但 f := adder() 返回了一个 匿名函数,这个函数引用了 sum;
- 即使 adder 已经结束执行,sum 变量并没有销毁,而是被返回的函数引用并保持着状态;
- 所以每次调用 f(...) 时,sum 会累计并返回。
| 特性 | 说明 |
|---|---|
| 变量保留 | 内部函数可以访问外部函数作用域中的变量,即使外部函数已执行完 |
| 持久状态 | 每个闭包有自己的变量副本,状态是隔离的 |
| 常见用途 | 封装状态、构造回调函数、延迟求值、函数工厂等 |
并发
go协程
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
信道
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
//发送sum到c
c <- sum
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
//创建一个channel 信道
c := make(chan int)
go sum(s[:len(s)/2], c) //7,2,8 =17
go sum(s[len(s)/2:], c) //-9,4,0 =-5
//从c接收两个sum
x, y := <-c, <-c
fmt.Println(x, y, x+y)
}
带缓冲的信道
//信道类似与一个队列(先进先出),数据从一端发送(ch <- data),另一端接收(<- ch)
// 当信道中没有数据时,接收方会阻塞。
//当信道满时,发送方会阻塞。
//如果信道没有缓冲区,发送方会阻塞知道接收方从信道中取出数据。
func main() {
//创建一个长度为2的信道
ch := make(chan int, 2)
//发送两个数据1和2
ch <- 1
ch <- 2
//从信道中获取两个数据
fmt.Println(<-ch)
fmt.Println(<-ch)
}
信道中使用range和close关键字
func chanDemo(c chan int) {
for i := 0; i < 100; i++ {
c <- i
fmt.Println("chanDemo:", i)
//放开下面的注释会报错:panic: send on closed channel,不能向已关闭的信道发送数据
// if i == 10 {
// close(c)
// }
}
//循环完后,关闭信道
close(c)
}
func main() {
c := make(chan int, 3)
go chanDemo(c)
for i := range c {
fmt.Println("i=", i)
}
}
在go中创建匿名函数:go func() {}()
go func() {
for {
time.Sleep(time.Second * 1)
ch1 <- "来自ch1的消息"
}
}()
select
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
//启动一个goroutine 向ch1发送数据
go func() {
for {
time.Sleep(time.Second * 1)
ch1 <- "来自ch1的消息"
}
}()
//启动一个goroutine 向ch2发送数据
go func() {
for {
time.Sleep(time.Second * 2)
ch2 <- "来自ch2的消息"
}
}()
//使用select等待任意一个channel 有数据可接收
for {
select {
case msg1 := <-ch1:
fmt.Println("接收到 ch1:", msg1)
case msg2 := <-ch2:
fmt.Println("接收到 ch2:", msg2)
default:
fmt.Println("当前没有可用的 channel 数据")
time.Sleep(time.Second * 1)
}
}
}
Mutex互斥锁
在 Go 中,sync.Mutex 是一种用于控制并发访问共享资源的机制。它可以帮助你在多个 goroutine 同时运行时,避免数据竞争(data race)和不一致的状态。
package main
import (
"fmt"
"sync"
)
// 定义全局变量,在当前package中都可以被访问和修改
var (
counter = 0 //共享变量
mutex sync.Mutex //互斥锁
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 1000; i++ {
mutex.Lock() //加锁
counter++ //安全的修改共享变量
mutex.Unlock() //解锁
}
}
func main() {
var wg sync.WaitGroup
//创建5个goroutine
for i := 0; i < 5; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait() //等待所有goroutine完成
fmt.Println("最终计数器的值:", counter)
}
可以把 sync.WaitGroup 想象成一个“计数器门卫”:
Add(1):你告诉门卫:“我要进去做事”
Done():你做完事后告诉门卫:“我做完了”
Wait():主函数在这里等,直到所有进去的人都说“我做完了”

浙公网安备 33010602011771号