Golang快速入门
Posted On 2025年9月11日
// 单行注释
/* 多行注释 */
/* 一个构建标签是一个以 //go:build 开头的行注释,可以通过 go build -tags="foo bar" 命令执行。
构建标签应放置在包声明之前,靠近或在文件顶部,后跟空行或其他行注释。 */
//go:build prod || dev || test
// 包声明语句从每个源文件开始。
// main 是一个特殊名称,表示可执行程序,而不是库。
package main
// 导入声明声明了此文件中引用的库包。
import (
"fmt" // Go 标准库中的一个包。
"io" // 实现了各种 I/O 工具函数。
m "math" // 带有本地别名 m 的数学库。
"net/http" // 是的,一个 Web 服务器!
_ "net/http/pprof" // 导入仅用于副作用的分析库
"os" // 与文件系统一起使用的操作系统函数
"strconv" // 字符串转换。
)
// 函数定义。main 是特殊的。它是可执行程序的入口点。Go 使用花括号。
func main() {
// Println 将行输出到标准输出。
// 它来自 fmt 包。
fmt.Println("Hello world!")
// 调用此包中的另一个函数。
beyondHello()
}
// 函数参数用括号定义。
// 如果没有参数,则需要使用空括号。
func beyondHello() {
var x int // 变量声明。变量必须在使用前声明。
x = 3 // 变量赋值。
// “短”声明使用 := 推断类型,声明并赋值。
y := 4
sum, prod := learnMultiple(x, y) // 函数返回两个值。
fmt.Println("sum:", sum, "prod:", prod) // 简单的输出。
learnTypes() // < y 分钟,了解更多!
}
/* <- 多行注释
函数可以有参数和(多个!)返回值。
在这里,x、y 是参数,sum、prod 是签名(返回值)。
请注意,x 和 sum 都接收 int 类型。
*/
func learnMultiple(x, y int) (sum, prod int) {
return x + y, x * y // 返回两个值。
}
// 一些内置类型和字面量。
func learnTypes() {
// 短声明通常会给你你想要的东西。
str := "Learn Go!" // 字符串类型。
s2 := `A "raw" string literal
can include line breaks.` // 相同的字符串类型。
// 非 ASCII 字面量。Go 源是 UTF-8。
g := 'Σ' // rune 类型,是 int32 的别名,存储 Unicode 代码点。
f := 3.14159 // float64,IEEE-754 64 位浮点数。
c := 3 + 4i // complex128,内部表示为两个 float64。
// 使用初始值进行 var 语法。
var u uint = 7 // 无符号,但实现依赖于 int 的大小。
var pi float32 = 22. / 7
// 使用转换语法进行短声明。
n := byte('\n') // byte 是 uint8 的别名。
// 数组的大小在编译时固定。
var a4 [4]int // 一个包含 4 个 int 的数组,初始化为 0。
a5 := [...]int{3, 1, 5, 10, 100} // 初始化为固定大小为五个元素的数组,值为 3、1、5、10 和 100。
// 数组具有值语义。
a4_cpy := a4 // a4_cpy 是 a4 的副本,是两个独立的实例。
a4_cpy[0] = 25 // 只有 a4_cpy 被更改,a4 保持不变。
fmt.Println(a4_cpy[0] == a4[0]) // false
// 切片具有动态大小。数组和切片各有优缺点,但切片的使用场景更加常见。
s3 := []int{4, 5, 9} // 与 a5 进行比较。这里没有下划线。
s4 := make([]int, 4) // 分配一个包含 4 个 int 的切片,初始化为 0。
var d2 [][]float64 // 仅声明,此处未分配任何内存。
bs := []byte("a slice") // 类型转换语法。
// 切片(以及映射和通道)具有引用语义。
s3_cpy := s3 // 两个变量指向相同的实例。
s3_cpy[0] = 0 // 这意味着两个都会更新。
fmt.Println(s3_cpy[0] == s3[0]) // true
// 由于它们是动态的,因此可以按需将元素追加到切片。
// 要将元素追加到切片,可以使用内置的 append() 函数。
// 第一个参数是我们要追加的切片,通常,切片变量会就地更新,如下所示。
s := []int{1, 2, 3} // 结果是一个长度为 3 的切片。
s = append(s, 4, 5, 6) // 添加了 3 个元素。切片现在长度为 6。
fmt.Println(s) // 更新后的切片现在为 [1 2 3 4 5 6]
// 要追加另一个切片,而不是原子元素列表,我们可以
// 通过传递切片字面量(带有尾随下划线)来使用,它会展开其元素,并将它们追加到切片 s。
s = append(s, []int{7, 8, 9}...) // 第二个参数是一个切片字面量。
fmt.Println(s) // 更新后的切片现在为 [1 2 3 4 5 6 7 8 9]
p, q := learnMemory() // 声明 p、q 为 int 指针类型。
fmt.Println(*p, *q) // * 遵循一个指针。这打印两个 int。
// 映射是一种动态可增长的关联数组类型,类似于其他语言的哈希或字典类型。
m := map[string]int{"three": 3, "four": 4}
m["one"] = 1
// 查找不存在的键返回零值,在本例中为 0,因为它是 map[string]int
m["key not present"] // 0
// 检查键是否存在于映射中,如下所示:
if val, ok := m["one"]; ok {
// 做某事
}
// 在 Go 中,未使用的变量是一种错误。
// 下划线允许你“使用”一个变量,但丢弃其值。
_, _, _, _, _, _, _, a5, s4, bs := str, s2, g, f, u, pi, n, a5, s4, bs
// 通常,你使用它来忽略函数返回的值。
file, _ := os.Create("output.txt")
fmt.Fprint(file, "This is how you write to a file, by the way")
file.Close()
// 输出当然被视为使用变量。
fmt.Println(s, c, a4, s3, d2, m)
learnFlowControl() // 回到流程。
}
// 函数可以具有命名返回值。
// 将类型名称分配给函数声明中的返回值允许我们从函数中返回多个值。
func learnNamedReturns(x, y int) (z int) {
z = x + y // 返回两个值。
return // z 是隐式的,因为我们稍后命名了它。
}
// Go 是完全的垃圾回收。它有指针,但没有指针算术。
// 你可以犯一个使用空指针的错误,但不能通过递增指针来犯错误。
// 与 C/C++ 不同,从本地变量获取地址也是安全的。
func learnMemory() (p, q *int) {
// 命名返回值 p 和 q 的类型为 int 指针。
p = new(int) // 内置函数 new 分配内存。
// 分配一个包含 20 个 int 的切片,初始化为 0。
s := make([]int, 20)
s[3] = 7 // 赋值一个值。
r := -2 // 声明另一个局部变量。
return &s[3], &r // & 获取一个对象的地址。
}
// 使用别名的数学库(参见导入,上面)。
func expensiveComputation() float64 {
return m.Exp(10)
}
func learnFlowControl() {
// if 语句需要花括号,并且不需要括号。
if true {
fmt.Println("told ya")
}
// 格式化由命令行命令 "go fmt" 标准化。
if false {
// Pout。
} else {
// Gloat。
}
// 在 if 语句中,switch 语句比连续的 if 语句更常见。
x := 42.0
switch x {
case 0:
case 1, 2: // 可以在一个 case 中有多个匹配项
case 42:
// 案例不“穿透”。
default:
// 默认案例是可选的。
}
// 类型开关允许你根据值而不是类型来切换。
var i interface{}
i = ""
switch c := data.(type) {
case string:
fmt.Println(c, "is a string")
case int64:
fmt.Printf("%d is an int64\n", c)
default:
// 所有其他情况
}
// 与 if 一样,for 没有括号。
for x := 0; x < 3; x++ { // ++ 是一个语句。
fmt.Println("iteration", x)
}
// x == 42 在这里。
// for 是 Go 中的唯一循环语句,但它有其他形式。
for { // 无限循环。
break // 只是开玩笑。
continue // 未达到。
}
// range 可以用于迭代数组、切片、字符串和映射。
// range 返回一个(通道)或两个值(数组、切片、字符串和映射)。
for key, value := range map[string]int{"one": 1, "two": 2, "three": 3} {
// 对于映射中的每个键值对,打印键和值
fmt.Printf("key=%s, value=%d\n", key, value)
}
// 如果你只需要值,可以使用下划线作为键
for _, name := range []string{"Bob", "Bill", "Joe"} {
fmt.Println("Hello, ", name)
}
// 简短声明通常会给你你想要的东西。
if y := expensiveComputation(); y > x {
x = y
}
// 函数字面量是闭包。
func() {
fmt.Println("I'm a function literal")
}
learnConcurrency() // Go 正在做它。你想要做它。
}
// 单个函数从 http 包开始启动一个 Web 服务器。
func learnWebProgramming() {
// 第一个参数 ListenAndServe 是要监听的 TCP 地址。
// 第二个参数是一个接口,具体是 http.Handler。
go func() {
err := http.ListenAndServe(":8080", pair{})
fmt.Println(err) // 不要忽略错误
}()
requestServer()
}
// 使 pair 成为 http.Handler,通过实现其唯一的 ServeHTTP 方法。
func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 使用 http.ResponseWriter 写入数据。
w.Write([]byte("You learned Go in Y minutes!"))
}
func requestServer() {
resp, err := http.Get("http://localhost:8080")
fmt.Println(err)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
fmt.Printf("\nWebserver said: \"%s\"", string(body))
}