tour of go day1

起因是本来花了好几天折腾好了libbpf的交叉编译环境,结果用libunwind用的是bionic-libc,libbpf用的是glibc,尝试用ndk编译libbpf始终无法成功,只能尝试切换到go/cilium生态,然后把libunwind用cgo的形式添加到项目中
tour of go链接

go的软件都以包的形式存在,程序的入口点是main包,包名定义在程序开头

1
package main

go的依赖导入使用import,有两种写法

1
2
import "math"
import "fmt"
1
2
3
4
import(
"math"
"fmt"
)

go中符号的导入导出依赖于命名约定,大写字母开头 的符号是导出的,否则不导出

声明变量

go的变量类型是定义在变量名之后的(类似ts但是不带:),通过空格隔开,如果指定了初始值可以自动推导类型

1
var a int
1
2
3
func test(x int,y int) int {
return x + y
}

go的函数可以返回多个返回值(可能是用了类似打包解包的写法)

1
2
3
4
5
6
7
8
func swap(x int,y int) (int,int){
return y,x
}

func main(){
var x,y = 1,2
x,y = swap(x,y)
}

go的多个变量在一行中初始化只能把变量名全写在左边,初始值全写在右边

1
2
var x=1,y=2 // wrong!
var x,y = 1,2 // right

go可以使用短变量赋值写法简化变量定义,但是短变量赋值只能在函数内部使用

1
a:=10 // var a=10

基本类型

go有以下几种基本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool

string

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 的别名

rune // int32 的别名
// 表示一个 Unicode 码位

float32 float64

complex64 complex128

其中uint,int,uintptr位数和操作系统位数相同,其他类型都是定长类型

go中的零值有0,false,"",nil,未初始化的变量会被自动赋予对应类型的零值

类型转换

go中没有隐式类型转换,通过type()的方式进行类型转换

1
2
var i int = 42
var j float32 = float32(i)

常量使用const声明,和变量唯一的区别就是不能使用:=简写声明

循环

go语言中只有for一种循环,除了没有小括号和必须加大括号外其他和c完全一样

1
2
3
4
5
6
7
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}

如果只有循环条件的话两个;也可以不写,此时就和while没什么区别了

1
2
3
4
5
6
7
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}

if

go的if也是不用写括号,可以在条件之前执行一句初始化语句

1
2
3
4
5
6
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}

初始化语句的生命周期限制在if分支内和同级的else分支内

switch

switch在书写上也和if类似,go中的switch默认执行完第一个匹配的分支就会终止,除非在分支结尾加上fallthrough

1
2
3
4
5
6
7
8
9
10
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}

go中switchcase不限定为数字和常量,可以是任何值或变量甚至表达式,在运行时从上往下逐个匹配

switch正常是以表达式的结果作为分支的判断标准,这里有一种switch true的语法糖

1
2
3
4
5
6
7
8
9
10
11
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("早上好!")
case t.Hour() < 17:
fmt.Println("下午好!")
default:
fmt.Println("晚上好!")
}
}

这样可以让一大串if else更加清楚

defer

defer是go的一种特性,带defer修饰的语句其参数会正常求值,但调用会在函数返回才时执行
这个延迟采用栈结构,最先遇到的defer语句最后执行

1
2
3
4
5
6
7
8
9
func main() {
defer fmt.Println("and you")
defer fmt.Println("world")

fmt.Println("hello")
}
//hello
//world
//and you

指针

go中的指针也是类型名前加*,也就是*Type

引用解引用都和c一样,但是没有指针运算

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
i, j := 42, 2701

p := &i // 指向 i
fmt.Println(*p) // 通过指针读取 i 的值
*p = 21 // 通过指针设置 i 的值
fmt.Println(i) // 查看 i 的值

p = &j // 指向 j
*p = *p / 37 // 通过指针对 j 进行除法运算
fmt.Println(j) // 查看 j 的值
}

结构体

结构体采用c风格定义,通过.号访问成员,并支持{}初始化

1
2
3
4
5
6
7
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}

与c中访问结构体指针的成员时需要通过->不同,go中仍然使用.,这里是自动做了解引用操作
支持具名变量形式初始化,未显式初始化的成员就初始化为零值

1
v2 = Vertex{X: 1}

数组

数组通过[]T定义

1
2
3
4
5
6
7
8
9
10
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)
}

结构体传参是深拷贝,直接构造新对象,数组传参也是深拷贝,切片传参因为本身有引用性质所以是浅拷贝

切片

go中的数组支持切片

1
a[low : high]

这个区间是左闭右开的,也就是[low,high),这个上下界也是可以省略的,和python一样分别默认值是0和数组长度
切片本身是一块 引用 多块切片可以共享一个数组所拥有的一片内存

切片同样可以作为右值,[]bool{true, true, false}等价于创建了一个[3]bool{true, true, false},然后再构造一个指向它的切片
切片对应的零值是nil即空指针
切片的长度可以动态修改,分别可以根据len()cap()两个函数获取切片的长度和容量
长度就是切片实际上包含的元素数,容量指的是从切片的第一个元素开始,一直到其所指向数组的末尾的元素数
切片的右端点可以左右移动,但是 左端点只能往右移动
也就是说s=[2:]会让s永久失去对其前两个元素的访问权

可以使用make([]T,len,cap)创建切片,三个参数分别是数组类型,长度,容量,这也是go指定的创建动态数组的方式(但是切片本身可以使用右值创建,而且切片的容量是可以自动拓展的,不理解make的意义是什么)

Author

SGSG

Posted on

2025-07-24

Updated on

2025-07-29

Licensed under