教程:文档 - Go 编程语言 (studygolang.com)
调用模块代码
在call-module-code需要注意,需要在hello目录下操作
go mod edit -replace example.com/greetings=../greetings
这是一个在Go项目的模块管理中的命令。在Go的模块管理工具(go mod
)中,这个命令用于修改模块依赖关系。
具体来说,go mod edit -replace example.com/greetings=../greetings
这个命令的作用是:
-replace
: 这个选项表示你要替换一个已经存在的依赖。example.com/greetings
: 这是你要替换的依赖的原始路径。../greetings
: 这是你要替换成的新路径。
所以,这个命令的意思是:将example.com/greetings
这个依赖替换为项目根目录下的greetings
目录。
简单来说,这个命令用于将一个远程依赖替换为本地路径的依赖。这在你想要使用本地版本的库而不是远程版本时非常有用,例如在开发过程中。
go mod tidy
go mod tidy
是一个 Go 语言的命令,用于自动添加或删除不必要的模块依赖项,使 go.mod
文件保持最新。
当你使用 Go 语言开发一个项目时,可能会使用 go get
命令来获取外部的依赖库。这些库会被添加到项目的 go.mod
文件中。随着时间的推移,有些依赖可能不再需要,或者新的依赖可能被添加进来。为了保持 go.mod
文件的整洁和一致性,你可以使用 go mod tidy
命令来自动处理这些不必要的依赖。
具体来说,go mod tidy
会做以下几件事:
- 删除不必要的依赖:如果一个依赖在代码中没有被使用,或者其版本与
go.mod
文件中指定的版本不匹配,那么这个依赖会被从go.mod
文件中移除。 - 添加缺失的依赖:如果代码中使用了某个外部库,但这个库还没有被添加到
go.mod
文件中,那么go mod tidy
会自动将其添加进来。 - 更新依赖的版本:如果某个依赖有新版本可用,并且这个新版本与
go.mod
文件中指定的版本不匹配,那么go mod tidy
会自动更新这个依赖的版本。
总之,go mod tidy
是一个非常有用的命令,可以帮助开发者自动管理项目的模块依赖,确保 go.mod
文件始终是最新的。
操作后hello文件夹下go.mod内容为
如果不操作,则报错hello.go:6:2: no required module provides package example.com/greetings; to add it:
go get example.com/greetings
处理错误
package greetings
import (
"errors"
"fmt"
)
// Hello returns a greeting for the named person.
func Hello(name string) (string, error) {
if name == "" {
return "", errors.New("empty name")
}
// Return a greeting that embeds the name in a message.
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message, nil
}
package main
import (
"fmt"
"log"
"example.com/greetings"
)
func main() {
// Get a greeting message and print it.
message, err := greetings.Hello("")
if err != nil {
log.Fatal(err)
}
fmt.Println(message)
}
使用日志
package main
import (
"fmt"
"log"
"example.com/greetings"
)
func main() {
log.SetPrefix("greetings: ")
log.SetFlags(0)
// Get a greeting message and print it.
message, err := greetings.Hello("")
if err != nil {
log.Fatal(err)
}
fmt.Println(message)
}
返回随机问候语
package main
import (
"errors"
"fmt"
"log"
"math/rand"
// "example.com/greetings"
)
func Hello(name string) (string, error) {
if name == "" {
return name, errors.New("Empty name")
}
message := fmt.Sprintf(randomFormat(), name)
return message, nil
}
func randomFormat() string {
stringArray := []string{"Hi, %v. Welcome!",
"Great to see you, %v!",
"Hail, %v! Well met!"}
index := rand.Intn(len(stringArray))
return stringArray[index]
}
func main() {
message, err := Hello("小明")
if err != nil {
log.Fatal(err)
}
fmt.Println(message)
}
向多人问候
package greetings
import (
"errors"
"fmt"
"math/rand"
)
func Hello(name string) (string, error) {
if name == "" {
return name, errors.New("Empty name")
}
message := fmt.Sprintf(randomFormat(), name)
return message, nil
}
func randomFormat() string {
stringArray := []string{"Hi, %v. Welcome!",
"Great to see you, %v!",
"Hail, %v! Well met!"}
index := rand.Intn(len(stringArray))
return stringArray[index]
}
func Hellos(names []string) (map[string]string, error) {
cMap := make(map[string]string)
for _, value := range names {
message, err := Hello(value)
if err != nil {
return nil, err
}
cMap[value] = message
}
return cMap, nil
}
知识点:
1、函数入参为数组怎么写。
2、函数返回值为map怎么写
3、如何创建一个map make(map[string]string)
package main
import (
"fmt"
"log"
"example.com/greetings"
// "example.com/greetings"
)
func main() {
names := []string{
"小明",
"小王",
}
message, err := greetings.Hellos(names)
if err != nil {
log.Fatal(err)
}
fmt.Println(message)
}
添加单元测试
package greetings
import (
"regexp"
"testing"
)
func TestHelloName(t *testing.T) {
name := "Gladys"
want := regexp.MustCompile(`\b` + name + `\b`)
msg, err := Hello("Gladys")
if !want.MatchString(msg) || err != nil {
t.Fatalf(`Hello("Gladys")=%q,%v,want match for %#q,nil`, msg, err, want)
}
}
func TestHelloEmpty(t *testing.T) {
msg, err := Hello("")
if msg != "" || err == nil {
t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
}
}
名为TestHelloName
的测试函数,它接受一个类型为*testing.T
的参数t
。这是Go语言中用于编写测试的标准方式。
TestHelloName方法若msg中不存在name或者err不为nil,则用例失败,
在vscode中,可以在测试函数前点击运行按钮运行,也可以在命令行运行go test或者 go test -v
编译和安装应用程序
代码编译为可执行文件,生成hello.exe
go build
命令行下运行hello.exe
安装软件包
go install
A Tour of Go
这个tour教程不错。
左边为教程,右边为实例
指针
Go 拥有指针。指针保存了值的内存地址。
类型 *T
是指向 T
类型值的指针。其零值为 nil
。
var p *int
&
操作符会生成一个指向其操作数的指针。
i := 42 p = &i
*
操作符表示指针指向的底层值。
fmt.Println(*p) // 通过指针 p 读取 i *p = 21 // 通过指针 p 设置 i
这也就是通常所说的“间接引用”或“重定向”。
与 C 不同,Go 没有指针运算。
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i // 指向 i
fmt.Println(p)
fmt.Println(*p) // 通过指针读取 i 的值
*p = 21 // 通过指针设置 i 的值
fmt.Println(i) // 查看 i 的值
p = &j // 指向 j
*p = *p / 37 // 通过指针对 j 进行除法运算
fmt.Println(j) // 查看 j 的值
}
结构体
一个结构体(struct
)就是一组字段(field)。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
结构体字段
结构体字段使用点号来访问。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
结构体指针
结构体字段可以通过结构体指针来访问。
如果我们有一个指向结构体的指针 p
,那么可以通过 (*p).X
来访问其字段 X
。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X
就可以。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9 //赋值 或者 (*p).X = 1e9 两种写法一样
fmt.Println(v)
}
输出
{1000000000 2}
结构体文法
结构体文法通过直接列出字段的值来新分配一个结构体。
使用 Name:
语法可以仅列出部分字段。(字段名的顺序无关。)
特殊的前缀 &
返回一个指向结构体的指针。
package main
import "fmt"
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
v2 = Vertex{X: 1} // Y:0 被隐式地赋予
v3 = Vertex{} // X:0 Y:0
p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)
func main() {
fmt.Println(v1, p, v2, v3)
}
数组
类型 [n]T
表示拥有 n
个 T
类型的值的数组。
表达式
var a [10]int
会将变量 a
声明为拥有 10 个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小。这看起来是个限制,不过没关系,Go 提供了更加便利的方式来使用数组。
package main
import "fmt"
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
切片
每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
类型 []T
表示一个元素类型为 T
的切片。
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
a[low : high]
它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
以下表达式创建了一个切片,它包含 a
中下标从 1 到 3 的元素:
a[1:4]
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
}
切片就像数组的引用
切片并不存储任何数据,它只是描述了底层数组中的一段。
更改切片的元素会修改其底层数组中对应的元素。
与它共享底层数组的切片都会观测到这些修改。
package main
import "fmt"
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}
修改b的第一个值,切片a和数组中该值都被更改。
切片文法
切片文法类似于没有长度的数组文法。
这是一个数组文法:
[3]bool{true, true, false}
下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片:
[]bool{true, true, false}
package main
import "fmt"
func main() {
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)
r := []bool{true, false, true, true, false, true}
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}
切片的默认行为
在进行切片时,你可以利用它的默认行为来忽略上下界。
切片下界的默认值为 0
,上界则是该切片的长度。
对于数组
var a [10]int
来说,以下切片是等价的:
a[0:10] a[:10] a[0:] a[:]
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
s = s[1:4]
fmt.Println(s)
s = s[:2]
fmt.Println(s)
s = s[1:]
fmt.Println(s)
}
切片的长度与容量
切片拥有 长度 和 容量。
切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片 s
的长度和容量可通过表达式 len(s)
和 cap(s)
来获取。
你可以通过重新切片来扩展一个切片,给它提供足够的容量。试着修改示例程序中的切片操作,向外扩展它的容量,看看会发生什么。
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// 截取切片使其长度为 0
s = s[:0]
printSlice(s)
// 拓展其长度
s = s[:4]
printSlice(s)
// 舍弃前两个值
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=6 cap=6 [2 3 5 7 11 13] len=0 cap=6 [] len=4 cap=6 [2 3 5 7] len=2 cap=4 [5 7]
nil 切片
切片的零值是 nil
。
nil 切片的长度和容量为 0 且没有底层数组。
package main
import "fmt"
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
[] 0 0 nil!
Go 语言之旅 (go-zh.org)