基础语法

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
}

解释:

  1. 关于p和*p的区别:可以把 p 看作是一个写着地址的纸条,而 *p 是根据这个纸条找到的房子里的实际内容。
  2. &i得到变量 i 的内存地址

注:

  1. Go 中的指针比 C/C++ 更安全,不会允许非法访问或指针运算。
  2. 使用指针可以减少内存拷贝,提高性能,尤其是在处理大结构体或函数参数传递时。
  3. 注意空指针(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():主函数在这里等,直到所有进去的人都说“我做完了”

posted @ 2025-07-15 08:33  Charlie-Pang  阅读(6)  评论(0)    收藏  举报