Go语言入门教程 – wiki基地

Go语言入门教程

I. Go语言简介

A. 什么是Go?
Go(或Golang)是由Google开发的一种开源编程语言,于2009年首次发布。它旨在提高开发人员的生产力,尤其是在处理大规模、高并发的系统时。Go语言融合了C语言的效率、Python的开发速度以及内置的并发支持。

Go语言的主要特点:
* 并发性(Concurrency):通过Goroutines和Channels提供轻量级并发机制。
* 垃圾回收(Garbage Collection):自动内存管理,减少开发人员的心智负担。
* 静态类型(Static Typing):在编译时进行类型检查,有助于捕获错误并提高代码可靠性。
* 快速编译:Go的编译器速度极快,使得开发迭代周期缩短。
* 简洁性:语法简洁明了,易于学习和阅读。
* 强大的标准库:提供了丰富的内置包,涵盖网络、文件I/O、加密等诸多领域。

B. 为什么要学习Go?
* 高性能:Go程序编译为机器码,执行效率高。
* 简单易学:语法规则少,上手快。
* 强大的生态系统:拥有活跃的社区和丰富的第三方库。
* 广泛的应用场景:被广泛应用于网络服务、微服务、云计算、命令行工具等领域。许多知名公司(如Google、Uber、Dropbox)都在使用Go。

C. 环境搭建
1. 安装Go
访问Go官方网站下载适用于您操作系统的安装包。按照安装向导的指示完成安装。
2. 验证安装
打开命令行工具(如CMD、PowerShell或Terminal),输入 go version。如果显示Go的版本信息,则表示安装成功。
3. GOPATH 和 Go Modules
* GOPATH (旧方式):Go 1.11之前,所有Go项目都必须放在GOPATH目录下。现在,Go Modules已成为主流。
* Go Modules (推荐):Go 1.11及以后版本引入,是官方推荐的依赖管理方式。它允许您在文件系统的任何位置创建项目,并管理项目的依赖。

  1. 您的第一个 “Hello, World!” 程序
    在您的项目目录下创建一个名为 main.go 的文件,并写入以下代码:

    “`go
    package main // 声明主包,可执行程序必须包含main包

    import “fmt” // 导入fmt包,用于格式化输入输出

    func main() { // main函数是程序的入口点
    fmt.Println(“Hello, World!”) // 打印字符串到控制台
    }
    “`

    保存文件,然后在命令行中导航到该文件所在目录,运行:go run main.go
    您将看到输出 Hello, World!

II. Go语言基础

A. 变量与数据类型
1. 变量声明
* 使用 var 关键字:
go
var name string = "Go语言"
var age int
age = 15

* 使用 := 短变量声明(只能在函数内部使用):
go
message := "学习Go很有趣!"
score := 100

* 批量声明:
go
var (
a string = "Apple"
b int = 10
)

2. 基本类型
* 整型int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr (无符号整型)。
* 浮点型float32, float64
* 布尔型bool (值为 truefalse)。
* 字符串string (UTF-8编码)。

  1. 类型转换:Go是强类型语言,不同类型之间需要显式转换。
    go
    var i int = 42
    var f float64 = float64(i)
    var u uint = uint(f)

  2. 常量:使用 const 关键字。
    go
    const Pi = 3.14159
    const Greeting = "Hello"

B. 运算符
Go语言支持常见的算术、关系、逻辑和位运算符。

  1. 算术运算符+, -, *, /, %
  2. 关系运算符==, !=, >, <, >=, <=
  3. 逻辑运算符&& (AND), || (OR), ! (NOT)
  4. 位运算符&, |, ^, <<, >>

C. 控制流
1. if-else 语句
“`go
if score >= 60 {
fmt.Println(“及格”)
} else {
fmt.Println(“不及格”)
}

// 可以在if语句中包含一个简短的声明
if val := someFunction(); val > 0 {
    fmt.Println("值大于0")
} else {
    fmt.Println("值小于等于0")
}
```
  1. for 循环:Go只有 for 循环,但它可以模拟其他语言中的 whiledo-while

    • 基本 for 循环:
      go
      for i := 0; i < 5; i++ {
      fmt.Println(i)
      }
    • while 风格:
      go
      sum := 1
      for sum < 100 {
      sum += sum
      }
      fmt.Println(sum)
    • 无限循环:
      go
      for {
      // ...
      }
    • range 循环(用于遍历切片、数组、字符串、map、通道):
      go
      numbers := []int{1, 2, 3}
      for index, value := range numbers {
      fmt.Printf("Index: %d, Value: %d\n", index, value)
      }
  2. switch 语句
    “`go
    day := “Monday”
    switch day {
    case “Monday”, “Tuesday”:
    fmt.Println(“工作日”)
    case “Saturday”, “Sunday”:
    fmt.Println(“周末”)
    default:
    fmt.Println(“未知”)
    }

    // switch 语句可以没有条件表达式
    t := time.Now()
    switch {
    case t.Hour() < 12:
    fmt.Println(“上午”)
    default:
    fmt.Println(“下午”)
    }

    // fallthrough 关键字
    i := 0
    switch i {
    case 0:
    fmt.Println(“0”)
    fallthrough // 会执行下一个case
    case 1:
    fmt.Println(“1”)
    } // 输出:0 1
    “`

D. 函数
1. 定义函数
“`go
func add(x int, y int) int { // 参数类型在变量名之后,返回值类型在参数列表之后
return x + y
}

func greet(name string) {
    fmt.Println("Hello, " + name)
}
```
  1. 多返回值:Go函数可以返回多个值。
    “`go
    func swap(x, y string) (string, string) {
    return y, x
    }

    a, b := swap(“hello”, “world”) // a=”world”, b=”hello”
    “`

  2. 命名返回值
    go
    func split(sum int) (x, y int) { // x, y被命名,并作为返回值
    x = sum * 4 / 9
    y = sum - x
    return // 直接返回x和y的值
    }

  3. 可变参数(Variadic Functions)
    “`go
    func sumAll(nums …int) int { // …int表示可变参数列表
    total := 0
    for _, num := range nums {
    total += num
    }
    return total
    }

    fmt.Println(sumAll(1, 2, 3, 4)) // 输出 10
    “`

  4. 匿名函数和闭包
    “`go
    // 匿名函数
    func() {
    fmt.Println(“这是一个匿名函数”)
    }() // 立即执行

    // 闭包
    func makeMultiplier(factor int) func(int) int {
    return func(n int) int {
    return n * factor
    }
    }
    double := makeMultiplier(2)
    fmt.Println(double(5)) // 输出 10
    “`

E. 包(Packages)
Go程序由包组成,main包是可执行程序的入口。
1. 导入包
go
import (
"fmt"
"math"
)

  1. 创建和使用自定义包
    在项目目录下创建子目录 mymath,并在其中创建 mymath.go
    “`go
    // mymath/mymath.go
    package mymath

    // Add 函数首字母大写表示它是导出的(公开的)
    func Add(a, b int) int {
    return a + b
    }
    在 `main.go` 中使用:go
    package main

    import (
    “fmt”
    “your_module_name/mymath” // 替换your_module_name为您的项目模块名
    )

    func main() {
    fmt.Println(mymath.Add(10, 20))
    }
    ``
    (注意:需要先初始化Go Module:
    go mod init your_module_name`)

  2. 可见性规则

    • 标识符(变量、函数、类型等)首字母大写表示它是导出的(Exported),可以在包外部访问。
    • 标识符首字母小写表示它是未导出的(Unexported),只能在当前包内部访问。

III. 数据结构

A. 数组(Arrays)
数组是具有相同数据类型且固定长度的序列。
“`go
var a [5]int // 声明一个包含5个整数的数组,默认值为0
b := [3]int{1, 2, 3} // 声明并初始化
c := […]int{4, 5, 6, 7} // 编译器自动推断数组长度

fmt.Println(b[0]) // 访问元素
b[1] = 20 // 修改元素
“`

B. 切片(Slices)
切片是Go中最常用的动态数组,它是一个引用类型。
1. 创建切片
* 基于数组创建:
go
primes := [6]int{2, 3, 5, 7, 11, 13}
s := primes[1:4] // 从索引1到索引4(不包含4)
fmt.Println(s) // 输出 [3 5 7]

* 使用 make 函数:
go
s := make([]int, 5) // 创建一个长度为5,容量为5的切片
s2 := make([]int, 0, 10) // 创建一个长度为0,容量为10的切片

* 切片字面量:
go
s3 := []string{"a", "b", "c"}

2. append()len()cap()
* len(s):返回切片的长度。
* cap(s):返回切片的容量(底层数组的长度)。
* append(s, elements...):向切片追加元素,如果容量不足会重新分配底层数组。
go
var s []int
s = append(s, 1)
s = append(s, 2, 3, 4)

C. 映射(Maps)
Map是键值对的无序集合。
1. 声明与初始化
* 使用 make
go
m := make(map[string]int) // 键为string,值为int

* 字面量:
go
m2 := map[string]string{
"name": "Alice",
"city": "New York",
}

2. 添加、访问、删除
go
m["age"] = 30 // 添加或修改
fmt.Println(m["name"]) // 访问
delete(m, "city") // 删除

3. 检查键是否存在
go
val, ok := m["name"]
if ok {
fmt.Println("name存在,值为:", val)
} else {
fmt.Println("name不存在")
}

D. 结构体(Structs)
结构体是用户自定义的类型,用于组合不同类型的字段。
1. 定义结构体
go
type Person struct {
Name string
Age int
City string
}

2. 创建和访问
“`go
p1 := Person{“Bob”, 25, “London”} // 按字段顺序初始化
p2 := Person{Name: “Alice”, Age: 30} // 字段名:值 初始化,未赋值的字段为零值

fmt.Println(p1.Name) // 访问字段
p1.Age = 26          // 修改字段
```
  1. 匿名结构体
    go
    anon := struct {
    X int
    Y int
    }{10, 20}
    fmt.Println(anon.X, anon.Y)

IV. 指针(Pointers)

A. 什么是指针?
指针存储变量的内存地址。Go语言中的指针不如C/C++中那么强大,但仍然很有用。
B. 声明与使用:
* & 运算符:获取变量的内存地址。
* * 运算符:访问指针指向的值(解引用)。

go
i := 42
p := &i // p现在指向i的内存地址
fmt.Println(*p) // 读取p指向的值,输出 42
*p = 21 // 通过p修改i的值
fmt.Println(i) // 输出 21

C. 何时使用指针?
* 当您需要修改函数外部的变量时(Go是值传递)。
* 传递大型数据结构时,可以避免复制整个结构体,提高效率。

V. 方法与接口

A. 方法(Methods)
Go语言中,方法是绑定到特定类型(通常是结构体)的函数。
1. 定义方法
“`go
type Rectangle struct {
Width, Height float64
}

// 值接收者方法
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 指针接收者方法
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

rect := Rectangle{10, 5}
fmt.Println(rect.Area()) // 输出 50
rect.Scale(2)
fmt.Println(rect.Area()) // 输出 200
```
  1. 接收者类型(值接收者 vs. 指针接收者)
    • 值接收者:方法操作的是接收者的一个副本。如果想修改接收者的原始值,值接收者是无效的。
    • 指针接收者:方法操作的是接收者的内存地址。可以通过指针修改接收者的原始值,也更适用于大型结构体,避免复制开销。

B. 接口(Interfaces)
接口定义了一组方法的集合。一个类型只要实现了接口中所有的方法,就称该类型实现了这个接口。
1. 定义接口
go
type Geometry interface {
Area() float64
Perimeter() float64
}

2. 隐式实现:Go语言的接口实现是隐式的,不需要显式声明。
“`go
type Circle struct {
Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// Circle 类型隐式实现了 Geometry 接口
func Measure(g Geometry) {
    fmt.Println(g)
    fmt.Println(g.Area())
    fmt.Println(g.Perimeter())
}

c := Circle{Radius: 10}
Measure(c)
```
  1. 空接口 (interface{}):可以持有任何类型的值。
    go
    var i interface{}
    i = 42
    fmt.Println(i)
    i = "hello"
    fmt.Println(i)
  2. 类型断言与类型开关
    • 类型断言:用于从空接口中提取底层值。
      “`go
      var i interface{} = “hello”
      s := i.(string) // 断言i是string类型
      fmt.Println(s)

      s, ok := i.(string) // 安全断言,检查是否成功
      if ok {
      fmt.Println(s)
      }
      * **类型开关**:判断接口变量的动态类型。go
      func do(i interface{}) {
      switch v := i.(type) {
      case int:
      fmt.Printf(“这是一个整数 %d\n”, v)
      case string:
      fmt.Printf(“这是一个字符串 %s\n”, v)
      default:
      fmt.Printf(“未知类型 %T\n”, v)
      }
      }
      do(21)
      do(“world”)
      “`

VI. 并发(Goroutines 和 Channels)

Go语言的并发模型是其最强大的特性之一。

A. Goroutines
Goroutine是Go运行时管理的轻量级线程。它们比操作系统线程开销小得多。
1. go 关键字:在函数调用前加上 go 关键字即可启动一个Goroutine。
“`go
func say(s string) {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}

func main() {
    go say("world") // 启动一个Goroutine
    say("hello")    // 在主Goroutine中执行
}
```
(注意:主Goroutine结束时,所有其他Goroutine也会终止。)
  1. sync.WaitGroup:用于等待一组Goroutine完成。
    “`go
    func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 在函数退出时调用wg.Done()
    fmt.Printf(“Worker %d starting\n”, id)
    time.Sleep(time.Second)
    fmt.Printf(“Worker %d done\n”, id)
    }

    func main() {
    var wg sync.WaitGroup
    for i := 1; i <= 5; i++ {
    wg.Add(1) // 增加WaitGroup计数器
    go worker(i, &wg)
    }
    wg.Wait() // 等待所有worker Goroutine完成
    fmt.Println(“All workers finished”)
    }
    “`

B. Channels
Channel是Goroutine之间进行通信的管道。
1. 创建Channel
go
ch := make(chan int) // 创建一个无缓冲的int类型Channel
bufCh := make(chan string, 3) // 创建一个容量为3的缓冲string类型Channel

2. 发送和接收数据
* 发送:ch <- value
* 接收:value := <-ch
“`go
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 将和发送到Channel
}

func main() {
    s := []int{7, 2, 8, -9, 4, 0}
    c := make(chan int)
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // 从Channel接收数据
    fmt.Println(x, y, x+y) // 输出两个Goroutine计算的部分和,以及总和
}
```
  1. 缓冲Channel
    go
    ch := make(chan int, 2)
    ch <- 1 // 放入元素1
    ch <- 2 // 放入元素2
    // ch <- 3 // 如果继续放会阻塞,直到有元素被取出
    fmt.Println(<-ch) // 取出元素1
    fmt.Println(<-ch) // 取出元素2
  2. select 语句:用于同时等待多个Channel操作。
    “`go
    func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
    select {
    case c <- x: // 尝试向c发送x
    x, y = y, x+y
    case <-quit: // 尝试从quit接收
    fmt.Println(“quit”)
    return
    }
    }
    }

    func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
    for i := 0; i < 10; i++ {
    fmt.Println(<-c)
    }
    quit <- 0 // 发送退出信号
    }()
    fibonacci(c, quit)
    }
    ``
    5. **关闭Channel和
    range**:发送者可以通过close(ch)关闭Channel。接收者可以通过for range循环或v, ok := <-ch` 检查Channel是否关闭。

VII. 错误处理(Error Handling)

Go语言采用显式的错误处理机制,通过函数返回一个 error 类型的值。
1. 惯用的错误处理
“`go
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New(“除数不能为0”) // 返回一个错误
}
return a / b, nil // 成功时返回结果和nil
}

func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Println("结果:", result)
    }

    result, err = divide(10, 0)
    if err != nil {
        fmt.Println("错误:", err) // 输出:错误: 除数不能为0
    }
}
```
  1. 创建错误
    • errors.New("错误消息"):创建一个简单的错误。
    • fmt.Errorf("格式化错误: %w", err):创建一个格式化的错误,并可以包装(wrap)底层错误。
  2. 自定义错误类型
    “`go
    type MyError struct {
    Code int
    Msg string
    }

    func (e *MyError) Error() string {
    return fmt.Sprintf(“错误码: %d, 消息: %s”, e.Code, e.Msg)
    }

    func doSomething() error {
    return &MyError{Code: 500, Msg: “内部服务器错误”}
    }
    ``
    4. **
    panicrecover**:
    *
    panic:用于表示程序无法恢复的错误,会终止程序的正常执行。
    *
    recover:用于从panic中恢复,只能在defer函数中使用。
    * **何时使用**:
    panicrecover应谨慎使用,通常只用于发生不可预料的严重错误时。在大多数情况下,应使用error` 进行错误处理。

VIII. 文件I/O和基本网络

A. 读写文件 (os 包)
“`go
import (
“bufio”
“io/ioutil”
“os”
)

func main() {
// 写入文件
err := ioutil.WriteFile(“output.txt”, []byte(“Hello Go!”), 0644)
if err != nil {
panic(err)
}

// 读取文件
data, err := ioutil.ReadFile("output.txt")
if err != nil {
    panic(err)
}
fmt.Println(string(data))

// 按行读取
file, err := os.Open("output.txt")
if err != nil {
    panic(err)
}
defer file.Close() // 确保文件关闭

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    fmt.Println(scanner.Text())
}

}
“`

B. 基本HTTP服务器 (net/http 包)
“`go
import (
“fmt”
“net/http”
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, “Hello, world from Go!”)
}

func main() {
http.HandleFunc(“/”, helloHandler) // 注册路由
fmt.Println(“Server listening on :8080”)
http.ListenAndServe(“:8080”, nil) // 启动HTTP服务器
}
``
运行此程序后,访问
http://localhost:8080` 即可看到 “Hello, world from Go!”。

IX. 测试(Testing)

Go语言内置了强大的测试工具。
1. 编写单元测试
创建一个与源代码文件同目录的 _test.go 文件,例如 mymath_test.go
“`go
// mymath_test.go
package mymath

import "testing"

func TestAdd(t *testing.T) {
    sum := Add(1, 2)
    expected := 3
    if sum != expected {
        t.Errorf("Add(1, 2) 期望 %d, 得到 %d", expected, sum)
    }
}
```
  1. 运行测试
    在命令行中导航到包目录,运行 go test
    您可以使用 go test -v 查看详细的测试输出。
  2. 表驱动测试(Table-driven Tests):一种常见的Go测试模式,用于测试多个输入/输出对。

X. Go Modules(依赖管理)

Go Modules是Go官方的依赖管理系统。
1. 初始化模块
在项目根目录运行 go mod init <module_path>。例如:go mod init github.com/yourusername/myproject
这会创建一个 go.mod 文件。
2. 添加和更新依赖
当您 import 一个新的第三方包并编译代码时,Go会自动下载并添加到 go.mod 文件中。您也可以手动运行:
* go get <package_path>:下载并添加到依赖。
* go mod tidy:清理不再使用的依赖,并添加缺少的依赖。
3. 构建和运行
使用 go buildgo run 命令时,Go会自动根据 go.mod 文件管理依赖。

XI. 后续学习 / 进阶主题

恭喜您完成了Go语言的入门学习!Go语言还有更多高级特性和应用领域值得探索:

  • 反射(Reflection):在运行时检查类型信息和操作变量。
  • Context 包:用于在Goroutine之间传递请求范围的数据、截止日期和取消信号。
  • 泛型(Generics,Go 1.18+):允许编写更通用、可重用的代码。
  • 构建命令行工具:使用 flagcobra 等库。
  • Web开发框架:如 Gin、Echo、Beego 等。
  • 数据库操作database/sql 包及其各种驱动。
  • 微服务架构:Go在构建微服务方面表现出色。

祝您在Go语言的学习旅程中一切顺利!

滚动至顶部