Go 语言入门指南:基础语法和常用特性解析 | 青训营

94 阅读8分钟

一、简单介绍下Go语言

1.1 产生

使用C++构建大型应用程序需要花费较长时间,二进制文件大,编译链接时间长,依赖管理有问题...当时的C++已经有三十多年历史了,在如今多核、分布式的环境下,需要一门新的编程语言来提供支持。在这种背景下,Golang诞生了。

1.2 语言特性

(1)简洁的语法、学习曲线平缓

Go语言的语法和C、Java等语言类似,但又不像它们那样啰嗦。例如声明变量可直接使用 ” := “。学过C语言或其他高级编程语言的开发者可以很快上手Go语言。(当然也有人一开始觉得很不习惯,比如变量类型在变量名后,某些语句不加括号等) Go语言官网也提供了许多友好的教程,A Tour of Go针对每一章都给出示例代码并能直接在服务器上运行,对初学者十分友好。 学完了这些就可以开始看框架了,初学者可以在学习的过程中慢慢深入,领会Go语言的高级特性(interface{},Goroutine,channel等)

(2)编译速度快

Go语言有两个编译器(单独实现),速度快是二者的共同特点。然而依赖管理特性才是Go编译速度快的真正原因。

(3)更严格的依赖模型

通过package管理依赖,基本思路和java的类文件、函数库等相同,不存在循环依赖,import 解决重复编译的问题(在C++中会出现多个项目文件include相同库,从而多次编译)。同时拥有最小版本选择原则。

(4)垃圾回收机制(GC)

堆区的内存由编译器和垃圾收集器管理回收,不需要手动释放内存。在Go1.8中使用的是三色标记+混合写屏障的方法实现垃圾回收。

(5)高性能、天然支持并发

goroutine是Go中的携程,需要并发执行时开启一个Go程即可,Go拥有自己的调度器,不涉及内核的频繁切换。在内存分配和调度中处于用户态,不直接调用malloc,成本调度低于OS层级的线程。

1.3 开发环境配置

Golang官网下载:go.dev/dl/ (截至目前最新版本为1.20.6)

VSCode配置Golang开发环境:learn.microsoft.com/zh-cn/azure…

Goland下载:www.jetbrains.com/go/

这里不过多介绍开发环境配置,网上的教程很详细,建议直接上手go mod模式,拉取依赖慢的话可以配置goproxy。

go moudules机制下,设置goproxy
go env -w GOPROXY=https://goproxy.cn,direct

二、基础语法

2.1 简单示例

先来看看golang写的HelloWorld:

package main  
  
import "fmt"  
  
func main() {  
	fmt.Println("Hello World!")  
}

其中fmt是Go语言的一个标准库,用于标准输入输出 有两种方式编译执行

  • go run main.go 或者
  • go build main.go
  • ./main

go run 命令将两步合为一步

2.2 变量与数据类型

Go语言是静态类型语言,变量声明时需要声明变量的类型。Go的变量类型在变量名称后(这点和其他多数语言不太一样,可能有人觉得不习惯)

未赋值的变量默认未0

以下是声明变量的几种方式:

# 变量
var a string        // 声明变量a 
var b,c int = 1,2   // 声明时赋值 指明类型
var d = true        // 自动推断类型
f := 3.3            // 省略var和类型 使用:=

空值:nil 整数类型:int,int8,int16... 浮点类型:float32,float64 字符串类型:string 布尔类型:boolean 常量:const 当然还有数组(值类型),切片(引用类型),map,指针,结构体,interface等。 同时,标准库中也有很多操作字符串类型变量的方法

数组

var a [5]int  b // 声明一维数组        
a[4] = 100c
var b = [5]int{1, 2, 3, 4, 5}    // 声明时初始化
c := [5]int{1, 2, 3, 4, 5}   

var a2 [5][5]int // 声明二维数组

数组的长度不能改变,很多时候使用受限,在go语言中一般使用切片

切片

使用make创建切片,参数为类型,长度,容量

切片在到达容量上限时会自动扩容(不详细展开了)

// 声明切片
slice1 := make([]float32, 0)  // 长度为0的切片
slice2 := make([]int, 5, 10)  // [0 0 0 0 0 ] 长度为5,容量为10的切片

// 使用append向切片中添加元素
slice1 = append(slice1, 1, 2, 3)

// 像python一样处理切片

slice3 := slice1[1:] // [2, 3]
slice4 := slice1[:3] // [1, 2, 3]

map

map类似于python的字典,和其它语言的hashmap,用于存储键值对。

方括号中为键的类型,后面为值的类型

map1 := make(map[string]string)    // 使用make创建map
map2 := map[stirng]int {           // 声明时初始化
	"Tom": 10"Jone": 12,
}

map1["Tom"] = "man"  //赋值

指针

指针类型变量存放地址,在变量前加上&表示取地址。Go中的指针类似于C语言的指针,但功能没有C语言中的指针强大,想要深入了解指针建议查阅C语言指针相关资料。

指针类型一个非常重要的作用就是在函数中修改传入参数的实际值,而不是修改值的副本。

以下给出一个简单的指针示例:

number := 1
var pointer *int = &number    // pointer作为指针,指向nunmber(存放number的地址)
// 即:pointer中的值为number的地址
// 若想通过pointer中存放的变量地址(number的地址)去得到这个地址对应的值(number的值)
// 需要加上*
*p = 2    // 即number = 2

结构体

和c语言中的结构体类似,在go语言中没有类的概念,结构体从某种意义上来说等同于“类”,但是又有所区别,涉及到面向对象的思想,不做过多展开。

2.5 结构体与方法中详细介绍结构体

关于interfacechannel等,初学者可以先跳过,这里也暂时不做介绍。

2.3 流程控制

Golang中的流程控制语句有if-else,switch,for,这些大部分也出现在其他语言中,非常类似。

break和continue的用法也和其他语言一样。

对于数组,切片,map,可以使用for range 进行遍历。

sum := 0  
for i := 0; i < 10; i++ {  
	if sum > 50 {  
		break  
	} else {        // 此处的else可以省略
		sum += i  
	} 
}

nums := []int{10, 20, 30, 40}  
# i 代表索引, num代表nums[i]
for i, num := range nums {  
	fmt.Println(i, num)  
}

m2 := map[string]int{  
	"Tom": 10,  
	"Jone": 12,  
}  

// 第一个变量为map中的key, 第二个为对应的value
for key, value := range m2 {  
	fmt.Println(key, value)  
}

2.4 函数

函数从语法上来看由fun + 函数名(参数)+ 返回类型组成。

Golang中的函数可以具有多个返回值。

# 简单的 a + b
func add (a int, b int) int {
	return a + b
}

# 返回商和余数
func div(a int, b int) (int, int) {
	return a / b, a % b
}

2.5 结构体与方法

结构体类似于其他语言中的类,可以在结构体中定义对应多个字段。当然,只声明结构体还是不够的,若要使用结构体,我们也需要实例化。

方法针对于特定的结构体。方法于函数类似,在函数名前加上括号(),中间填写变量名称和类型,表示属于该类型的方法。

参考课上代码:

package main

import "fmt"

type user struct {
	name string
	pwd  string
}

func (u user) checkPwd(pwd string) bool {
	return u.pwd == pwd
}

// 若调用者非*类型,会自动转换为*类型
func (u *user) resetPwd(pwd string) {
	u.pwd = pwd
}

func main() {
	a := user{name: "test", pwd: "123"}
	a.resetPwd("456")
	fmt.Println(a.checkPwd("456")) // true
}

三、Go语言实战

3.1 猜谜游戏

编写数字猜谜小游戏非常适合编程语言初学者,可以帮助初学者更好的理解分支、循环结构和其他语句。接下来是完整的代码实现:

# 数字猜谜游戏
package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {
	maxNum := 100
	rand.Seed(time.Now().UnixNano())
	secretNumber := rand.Intn(maxNum)
	// fmt.Println("The secret number is ", secretNumber)

	fmt.Println("Please input your guess")
	reader := bufio.NewReader(os.Stdin)
	for {
		input, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("An error occured while reading input. Please try again", err)
			continue
		}
		input = strings.Trim(input, "\r\n")

		guess, err := strconv.Atoi(input)
		if err != nil {
			fmt.Println("Invalid input. Please enter an integer value")
			continue
		}
		fmt.Println("You guess is", guess)
		if guess > secretNumber {
			fmt.Println("Your guess is bigger than the secret number. Please try again")
		} else if guess < secretNumber {
			fmt.Println("Your guess is smaller than the secret number. Please try again")
		} else {
			fmt.Println("Correct, you Legend!")
			break
		}
	}
}

注意点:

  • 使用Seed初始化随机数种子,一般使用时间戳
  • 使用bufio.NewReader将输入转化为流,再使用流对象的ReadString方法截取至换行符'\n'
  • 使用strconv.Atoi将字符类型转换为数字

3.2 简单的字典

这段代码中结构体和http请求头的设置可能看起来有些复杂,这些都是第三方工具可以帮我们生成的。重点是理解语法和逻辑。

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

type DictRequest struct {
	TransType string `json:"trans_type"`
	Source    string `json:"source"`
	UserID    string `json:"user_id"`
}

type DictResponse struct {
	Rc   int `json:"rc"`
	Wiki struct {
		KnownInLaguages int `json:"known_in_laguages"`
		Description     struct {
			Source string      `json:"source"`
			Target interface{} `json:"target"`
		} `json:"description"`
		ID   string `json:"id"`
		Item struct {
			Source string `json:"source"`
			Target string `json:"target"`
		} `json:"item"`
		ImageURL  string `json:"image_url"`
		IsSubject string `json:"is_subject"`
		Sitelink  string `json:"sitelink"`
	} `json:"wiki"`
	Dictionary struct {
		Prons struct {
			EnUs string `json:"en-us"`
			En   string `json:"en"`
		} `json:"prons"`
		Explanations []string      `json:"explanations"`
		Synonym      []string      `json:"synonym"`
		Antonym      []string      `json:"antonym"`
		WqxExample   [][]string    `json:"wqx_example"`
		Entry        string        `json:"entry"`
		Type         string        `json:"type"`
		Related      []interface{} `json:"related"`
		Source       string        `json:"source"`
	} `json:"dictionary"`
}

func query(word string) {
	client := &http.Client{}
	request := DictRequest{TransType: "en2zh", Source: word}
	buf, err := json.Marshal(request)
	if err != nil {
		log.Fatal(err)
	}
	var data = bytes.NewReader(buf)
	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("DNT", "1")
	req.Header.Set("os-version", "")
	req.Header.Set("sec-ch-ua-mobile", "?0")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")
	req.Header.Set("app-name", "xy")
	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
	req.Header.Set("Accept", "application/json, text/plain, */*")
	req.Header.Set("device-id", "")
	req.Header.Set("os-type", "web")
	req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
	req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
	req.Header.Set("Sec-Fetch-Site", "cross-site")
	req.Header.Set("Sec-Fetch-Mode", "cors")
	req.Header.Set("Sec-Fetch-Dest", "empty")
	req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
	req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	bodyText, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	if resp.StatusCode != 200 {
		log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
	}
	var dictResponse DictResponse
	err = json.Unmarshal(bodyText, &dictResponse)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
	for _, item := range dictResponse.Dictionary.Explanations {
		fmt.Println(item)
	}
}

func main() {
	if len(os.Args) != 2 {
		fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
		`)
		os.Exit(1)
	}
	word := os.Args[1]
	query(word)
}

3.3 Proxy

这个示例对初学者来说就没有那么友好了,本篇也暂时不做介绍。