Go语言入门
安装
Mac M1 Pro:https://golang.google.cn/dl/
选择Arm,PKG格式的安装,默认会安装到/usr/local/go
文件夹下面
配置环境变量~/.bash_profile
:
# 下面是配置go环境
GOPATH=/usr/local/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN
查看版本以及是否配置环境变量成功:
go version
基础语法
HelloWorld
可以使用Jerbrain的GoLand集成开发环境进行开发
package main
import (
"fmt"
)
func main() {
s := "cxk"
fmt.Println("Hello and welcome, %s!", s)
for i := 1; i <= 5; i++ {
fmt.Println("i =", 100/i)
}
}
变量
在 Go 语言中,变量定义遵循以下规则:
- 声明变量:
- 使用
var
关键字可以声明变量。 - 可以在同一行中声明多个变量,类型可以相同或不同。
- 使用
- 初始化变量:
- 变量可以在声明时直接赋值,也可以稍后赋值。
- 如果没有显式初始化,变量会被赋予其类型的零值(例如,整数为 0,布尔值为 false,字符串为空等)。
- 简短声明:
- 使用
:=
语法可以在函数内声明并初始化变量,无需使用var
关键字。
- 使用
- 常量:
- 使用
const
关键字定义常量,常量在编译时就确定其值,不能被修改。
- 使用
package main
import (
"fmt"
"math"
)
func main() {
var a = "initial"
var b, c int = 1, 2
var d = true
var e float64
f := float32(e)
g := a + "foo"
fmt.Println(a, b, c, d, e, f)
fmt.Println(g)
const s string = "constant"
const h = 500000000
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
分支
if-else分支:if
后面的条件表达式必须是布尔类型,返回 true
或 false
。
可以在 if
语句中进行短变量声明。在这种情况下,变量的作用域仅限于 if
语句块内
package main
import (
"fmt"
)
func main() {
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
switch分支:
switch
语句用于根据不同的条件执行不同的代码块。
可以通过指定表达式来进行匹配,如果不指定,默认匹配 true
可以在同一个 case
中列出多个条件,用逗号分隔
可以不使用表达式,直接写多个 case
,通常用于范围判断或布尔条件。
switch 默认不会穿透到下一个 case,如果需要,可以使用 fallthrough 关键字显式地继续执行下一个 case
package main
import (
"fmt"
"time"
)
func main() {
a := 2
switch a {
case 1:
fmt.Println("a is 1")
case 2:
fmt.Println("a is 2")
case 3:
fmt.Println("a is 3")
case 4, 5:
fmt.Println("a is 4 or 5")
default:
fmt.Println("a is not 1, 2, 3, 4 or 5")
}
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
default:
fmt.Println("Good afternoon!")
}
}
循环
无限循环:可以在 for
后面不写条件,形成一个无限循环,通常与 break
配合使用。
计数循环:通过初始化语句、条件和迭代语句来实现传统的计数循环,适合已知次数的操作。
条件循环:可以直接 在 for
后写条件,循环将持续执行直到条件不再满足,类似于 while
循环。
continue
语句:使用 continue
可以跳过当前迭代的剩余部分,直接进入下一次迭代。
package main
import "fmt"
func main() {
i := 1
for {
fmt.Println("loop")
break
}
for j := 7; j < 9; j++ {
fmt.Println(j)
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
for i <= 3 {
fmt.Println(i)
i = i + 1
}
}
数组
用法:
数组声明:使用 var
关键字声明数组,可以指定长度和元素类型。例如,可以声明一个固定长度的整型数组。
元素赋值:可以通过索引直接对数组的元素进行赋值,索引从 0 开始。
数组长度:使用 len()
函数可以获取数组的长度,数组长度是固定的。
数组初始化:可以在声明时通过字面量初始化数组,直接为每个元素赋值。
多维数组:Go 支持多维数组,可以通过嵌套数组的方式定义,例如二维数组,并且可以通过嵌套循环对其进行赋值。
输出:可以使用 fmt.Println()
输出数组的元素或整个数组,方便查看数组的内容和结构。
更多情况下会使用切片而不是数组
package main
import "fmt"
func main() {
var a [5]int
a[4] = 100
fmt.Println(a[4], len(a))
b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
切片
可以理解为可变数组,类似于Java的ArrayList
切片声明:可以使用 make()
函数创建切片,指定其类型和初始长度。切片是动态大小的数据结构,适合存储可变长度的数据。
元素赋值:可以通过索引直接对切片的元素进行赋值,索引从 0 开始,切片的长度可以在运行时变化。
获取长度:使用 len()
函数可以获取切片的长度,便于在处理数据时进行控制。
追加元素:可以使用 append()
函数向切片追加元素。append()
函数可以一次追加一个或多个元素,并返回新的切片。
复制切片:可以使用 copy()
函数将一个切片的元素复制到另一个切片,确保数据的独立性。
切片操作:通过切片的索引可以获取子切片,使用 s[start:end]
语法来截取切片的一部分。
切片字面量:可以直接声明并初始化切片,通过字面量的方式指定元素,简洁方便。
package main
import "fmt"
func main() {
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get[2]:", s[2])
fmt.Println("len:", len(s))
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println("apd:", s)
c := make([]string, len(s))
copy(c, s)
fmt.Println("cpy:", c)
fmt.Println(s[2:5])
fmt.Println(s[:5])
fmt.Println(s[2:])
good := []string{"g", "o", "o", "d"}
fmt.Println("dcl:", good)
}
map
类似于Java中的HashMap
映射声明:可以使用 make()
函数创建映射,指定键值对的类型。映射是一种无序的键值对集合,适合快速查找和存储数据。
元素赋值:通过键直接对映射的元素进行赋值,如果键不存在,会自动添加。
获取长度:使用 len()
函数可以获取映射中键值对的数量。
获取元素:通过键可以获取对应的值,使用 r, ok := mp[key]
语法可以检查键是否存在。
删除元素:可以使用 delete()
函数从映射中删除指定的键及其对应的值。
映射字面量:可以通过字面量的方式直接声明并初始化映射,简洁方便。
package main
import "fmt"
func main() {
mp := make(map[string]int)
mp["one"] = 1
mp["two"] = 2
fmt.Println(mp)
fmt.Println(len(mp))
fmt.Println(mp["one"])
fmt.Println(mp["two"])
r, ok := mp["unknown"]
fmt.Println(r, ok)
delete(mp, "one")
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
}
range
range
关键字的用法主要用于遍历切片、映射和其他数据结构,具体包括以下几个方面:
- 遍历切片:使用
for i, num := range nums
可以遍历切片,i
是当前元素的索引,num
是对应的值。这样可以在遍历的同时获取每个元素的索引和值。 - 计算和条件判断:在遍历切片的过程中,可以对元素进行计算(如求和)和条件判断(如查找特定元素的索引)。
- 遍历映射:使用
for k, v := range m
可以遍历映射,k
是键,v
是对应的值,这使得访问映射中的每个键值对变得简单。 - 仅遍历键:如果只需要遍历映射的键,可以使用
for k := range m
,这样只会获取键而忽略值。 - 简洁性:
range
提供了一种简洁的方式来遍历各种数据结构,避免了手动管理索引或键的复杂性。
package main
import "fmt"
func main() {
nums := []int{2, 7, 11, 15}
sum := 0
for i, num := range nums {
sum += num
if num == 7 {
println("index of 7 is ", i)
}
}
fmt.Println("sum:", sum)
m := map[string]string{"name": "zhangsan", "age": "20"}
for k, v := range m {
fmt.Println(k, v)
}
for k := range m {
fmt.Println(k) //只打印key
}
}
函数
函数定义:使用 func
关键字定义函数,后接函数名、参数列表和返回值类型。函数可以接收一个或多个参数,并返回一个或多个值。
参数类型:在参数列表中,可以为每个参数单独指定类型,也可以在同一行中为多个参数指定相同类型,例如 add2(a, b int)
。
返回值:函数可以直接返回值,也可以使用命名返回值。在命名返回值的情况下,可以在函数体内直接赋值,最后使用 return
返回即可。
调用函数:在 main
函数中可以调用定义的函数,传入参数并接收返回值。
映 射作为参数:函数可以接收映射作为参数,并在函数内部访问映射的元素,这使得函数更加灵活。
package main
import "fmt"
func add(a int, b int) int {
return a + b
}
func add2(a, b int) int {
return a + b
}
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k] //v is the value, ok is a boolean
return v, ok
}
func main() {
res := add(1, 2)
fmt.Println(res)
v, ok := exists(map[string]string{"a": "b"}, "a")
fmt.Println(v, ok)
}
指针
在 Go 语言中,函数参数的传递方式主要有值传递和指针传递:
- 值传递:默认情况下,函数参数是通过值传递的。在函数内部对参数的修改不会影响到外部变量。例如,在
add
函数中对n
的修改不会改变main
函数中的n
的值。 - 指针传递:通过传递变量的指针,可以在函数内部修改外部变量的值。在
add2ptr
函数中,通过传递&n
(n
的地址),可以直接修改外部变量的值。 - 指针解引用:在指针参数中,使用
*n
来解引用,从而访问和修改指针指向的值。 - 函数调用:在
main
函数中,通过调用函数并传入相应的参数,可以观察到值传递和指针传递的效果。
package main
import "fmt"
func add(n int) {
n += 2
}
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
add(n)
fmt.Println(n) // 5
add2ptr(&n)
fmt.Println(n) // 7
}
结构体
在 Go 语言中,结构体的用法包括以下几个方面:
- 结构体定义:使用
type
关键字定义结构体,结构体由一组字段组成,可以包含不同类型的数据。字段可以指定名称和类型。 - 结构体实例化:可以通过字段名或按顺序初始化结构体。例如,可以使用
user{name: "admin", password: "admin"}
或user{"admin", "admin"}
进行实例化。 - 字段访问和修改:可以通过点(
.
)操作符访问和修改结构体的字段。例如,c.password = "123456"
修改了c
的password
字段。 - 方法定义:可以为结构体定义方法,使用接收者(receiver)来关联方法与结构体。通过方法可以对结构体进行操作或提供功能。
- 值传递与指针传递:在函数中可以通过值传递(传递结构体副本)或指针传递(传递结构体的 地址)来访问和修改结构体。
- 方法调用:可以通过点操作符调用结构体的方法,如
a.checkPassword("admin")
。
package main
import "fmt"
type user struct {
name string
password string
}
func main() {
a := user{name: "admin", password: "admin"}
b := user{"admin", "admin"}
c := user{name: "cxk"}
c.password = "123456"
var d user
d.name = "cxk"
d.password = "1024"
fmt.Println(a, b, c, d)
fmt.Println(checkPassword(a, "admin"))
fmt.Println(checkPassword2(&a, "admin"))
fmt.Println(a.checkPassword("admin"))
}
func checkPassword(u user, password string) bool {
return u.password == password
}
func checkPassword2(u *user, password string) bool {
return u.password == password
}
// 结构体方法
func (u user) checkPassword(password string) bool {
return u.password == password
}
错误处理
在 Go 语言中,错误处理和指针的用法包括以下几个方面:
- 错误处理:Go 语言通过返回值来处理错误,通常在函数返回多个值时,最后一个值是一个
error
类型,用于指示函数是否成功执行。例如,findUser
函数返回一个指向user
的指针和一个错误值。 - 条件检查:在调用可能返回错误的函数后,通常使用
if err != nil
来检查是否发生了错误。如果发生错误,可以通过打印错误信息或采取其他处理措施。 - 返回指针:在
findUser
函数中,返回的是指向用户结构体的指针。在遍历用户切片时,通过return &u
返回找到用户的地址。注意,直接返回结构体的地址可能会导致问题,因为循环中的u
是局部变量。 - 局部变量作用域:在
main
函数中,使用if u, err := findUser(...);
的形式可以在条件语句内定义局部变量u
和err
,该变量在if
语句块内有效,有助于避免变量冲突。 - 简洁性:通过错误处理机制,Go 提供了一种清晰且直观的方式来管理和响应运行时错误,增强了代码的可读性和可维护性。
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil
}
}
return nil, errors.New("user not found")
}
func main() {
u, err := findUser([]user{{"a", "b"}}, "a")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name)
if u, err := findUser([]user{{"a", "b"}}, "b"); err != nil {
fmt.Println(err)
} else {
fmt.Println(u.name)
}
}
实战案例
猜数字游戏
输入处理的方式如下:
- 使用 bufio 包:通过
bufio.NewReader(os.Stdin)
创建一个新的读取器,专门用于从标准输入(键盘)读取数据。 - 读取输入:使用
reader.ReadString('\n')
方法读取用户输入,直到遇到换行符为止。这将返回用户输入的字符串,包括换行符。 - 处理换行符:使用
strings.TrimSuffix(input, "\n")
去除字符串末尾的换行符,以便后续处理。 - 字符串转换为整数:通过
strconv.Atoi(input)
将输入的字符串转换为整数。如果转换失败,会返回一个错误。 - 错误处理: 在读取和转换过程中,程序通过检查返回的错误,判断用户输入是否合法,并给予相应提示。
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNum := rand.Intn(maxNum)
fmt.Println("the secret number is", secretNum)
fmt.Println("Please input a number between 0 and", maxNum)
reader := bufio.NewReader(os.Stdin)
for {
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("An error occurred while reading input. Please try again", err)
continue
}
input = strings.TrimSuffix(input, "\n")
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invalid input. Please enter an integer")
continue
}
fmt.Println("You guessed:", guess)
if guess > secretNum {
fmt.Println("Your guess is too high")
} else if guess < secretNum {
fmt.Println("Your guess is too low")
} else {
fmt.Println("Congratulations! You guessed the secret number ")
break
}
}
}
命令行词典
curl转go:https://curlconverter.com/
json转go:https://oktools.iokde.com/json2go
结构体后面的部分是结构体标签,用于指定在 JSON 编码和解码时字段的名称。例如,TransType
将被序列化为 "trans_type"
,Source
将被序列化为 "source"
。
- 序列化(将结构体转换为 JSON):
- 使用
json.Marshal
函数将DictRequest
结构体实例request
转换为 JSON 格式的字节切片。 buf, err := json.Marshal(request)
将request
序列化为 JSON 字符串,存储在buf
中。如果发生错误,会记录并终止程序。
- 使用
- 反序列化(将 JSON 转换为结构体):
- 使用
json.Unmarshal
函数将从 HTTP 响应中读取的 JSON 数据转换为DictResponse
结构体。 err = json.Unmarshal(bodyText, &dictResponse)
将响应的 JSON 数据解析为dictResponse
,如果发生错误,会记录并终止程序。
- 使用
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
)
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
}
type DictResponse struct {
Rc int `json:"rc"`
Wiki struct {
} `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: "boy",
}
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("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh")
req.Header.Set("app-name", "xiaoyi")
req.Header.Set("authorization", "bearer")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "40fc51033a5ba5e96b5fc956ca6175a5")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("priority", "u=1, i")
req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("sec-ch-ua", `"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"macOS"`)
req.Header.Set("sec-fetch-dest", "empty")
req.Header.Set("sec-fetch-mode", "cors")
req.Header.Set("sec-fetch-site", "cross-site")
req.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
//fmt.Printf("%s\n", bodyText)
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
log.Printf("%+v\n", dictResponse)
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: %s word\n", os.Args[0])
os.Exit(1)
}
word := os.Args[1]
query(word)
}
运行结果:
Sockets5代理
echo server
具体步骤:
-
创建监听器:
- 使用
net.Listen("tcp", "127.0.0.1:1080")
创建一个 TCP 监听器,绑定到本地的1080
端口。如果创建失败,程序会调用panic
终止运行。
- 使用
-
接受连接:
- 在无限循环中,使用
server.Accept()
接受客户端的连接请求。如果发生错误,则记录错误并继续下一次循环。
- 在无限循环中,使用
-
并发处理:
- 每当接受到一个客户端连接,使用
go process(client)
启动一个新的 Goroutine 来处理该连接。这样可以同时处理多个客户端请求。
- 每当接受到一个客户端连接,使用
-
处理客户端连接:
-
在
process
函数中,使用defer conn.Close()
确保在函数结束时关闭连接。 -
创建一个
bufio.NewReader
以从连接中读取数据。 -
使用
reader.ReadByte()
循环读取字节。如果读取成功,将读取到的字节通过conn.Write
写回给客户端,实现回显功能。如果读取或写入过程中发生错误,循环将终止。
-
func main() {
server, err := net.Listen("tcp", "127.0.0.1:1080")
if err != nil {
panic(err)
}
for {
client, err := server.Accept()
if err != nil {
log.Printf("accept error: %v\n", err)
continue
}
go process(client)
}
}
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
b, err := reader.ReadByte()
if err != nil {
break
}
_, err = conn.Write([]byte{b})
if err != nil {
break
}
}
}