一、简单介绍下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 结构体与方法中详细介绍结构体
关于interface,channel等,初学者可以先跳过,这里也暂时不做介绍。
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
这个示例对初学者来说就没有那么友好了,本篇也暂时不做介绍。