你知道本地开发怎么引证本地的go包吗?

首要,我把结论说了,许多人不知道go在本地是怎么引证外部包的。由于许多材料都停留在老版别,不是go mod的版别,让我也迷失了几天,发现网上许多说的都不对。

以下是正确版别:

1、在当前项目目录查找module(也便是go.mod文件项目里对应的package)。找到module后,用import module_name/包的目录途径引证

2、未找到,则逐级查找上级目录的go.mod(是不是有点像咱们npm的node_modules的递归向上查找)

3、一切需求导入的途径中假如在 go.mod 中指定了版别,则从 $GOPATH/pkg/mod/ 下取得相应版别进行导入(咱们下载的第三方包也是放在这个文件夹下的)

4、假如没有被指定则从 GOPATH/src/ 或 GOROOT/src/ 中进行查找导入。

举一个例子

一、引入的包在同一项目下

在实践开发中,这是咱们最见的场景。

一般情况下咱们在项目的目录下面,会建许多的包,他们并不冲突,比方下面结构图中的:kun-package

.
├── main.go
├── go.mod
└── kun-package
    └── Hello.go

kun-package/ Hello.go

package kun-package
import "fmt"
func New(){
    fmt.Println("kun-package.New")
}

go.mod 文件:

module moduledemo
go 1.17

然后在moduledemo/main.go中按如下办法导入kun-package

package main
import (
    "fmt"
    "moduledemo/kun-package"
)
func main() {
    mypackage.New()
    fmt.Println("main")
}

二、不在同一项目下面

其实难免咱们要引证的包,是别人项目下面的,而不是自己项目下面的。

咱们先来看一个项目结构图:

project01
├── go.mod
└── main.go
project02
└── kun-package
    ├── go.mod
    └── hello.go

假设project01要引证project02的包

project02/kun-package/go.mod

module kun-package
go 1.17

project01/go.mod

module project01
go 1.17
require kun-package v0.0.0
replace kun-package => ../project02/kun-package

注意了 replace是关键,他把咱们引证的途径变为了相对途径,然后就能够在01项目的main文件中愉快的运用了:

package main
import (
 kunPackage "kun-package"
)
func main() {
 kunPackage.SayHello()
}

字符串类型的坑(byte和rune)

请看:

// 预备一个字符串类型
var house = "中国人"
fmt.Println(len(house))

结果是9,len是取的字节数,又由于go解析字符串是以utf8为根底,中这个字在utf8中是3个字节展现的,所以一共9个字节。

所以咱们能够看到,go的字符串其实是byte类型。rune是按字符算的,所以咱们需求把它转为字符,算出来的字符数才是咱们想要的

var house = "中国人"
b := []rune(house)
fmt.Println(len(b))

这样就回来3了。

一起还需求注意,for循环中,是按字节取数据的,for range是按字符的,如下:

var s = "中国人"
fmt.Printf("the length of s = %d\n", len(s)) // 9
for i := 0; i < len(s); i++ {
  fmt.Printf("0x%x ", s[i]) // 0xe4 0xb8 0xad 0xe5 0x9b 0xbd 0xe4 0xba 0xba
}
fmt.Printf("\n")
var s = "中国人"
fmt.Println("the character count in s is", utf8.RuneCountInString(s)) // 3
for _, c := range s {
  fmt.Printf("0x%x ", c) // 0x4e2d 0x56fd 0x4eba
}
fmt.Printf("\n")

认识指针地址和指针类型

一个指针变量能够指向任何一个值的内存地址,这个跟32位操作系统和64位操作系统有关, 分别占用 4 或 8 个字节。

当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。

每个变量在运行时都具有一个地址,也便是在内存的方位,如下 & 便是取地址操作符

ptr := &v    // v 的类型为 T

其间 v 代表被取地址的变量,变量 v 的地址运用变量ptr 进行接纳,ptr 的类型为*T,称做 T 的指针类型,*代表指针。

除了取地址操作符,咱们还有一个从地址里取值的操作符 *,请看下面案例

    // 预备一个字符串类型
     var a = "a"
     // 对字符串取地址, ptr类型为*string, 这儿ptr是指针
     ptr := &a 
     // 这儿把指针里存的值取出来,赋值给了value,其实value便是"a" 字符串
     value := *ptr

go里边的过错处理跟js的思路不一样

go希望你对一切或许出错的地方,自己在函数回来的时分,多回来一个error,然后让开发人员自己养成防护过错发生的习气,所以你在写函数的时分,必定要注意或许发生的各种过错。

所以我在这儿简略总结一下,go处理过错的中心思路:

Go言语中的过错处理主要通过在函数回来值中回来一个特殊的过错类型(通常是 error 接口)来完成。

中心思路如下:

  1. 函数的回来值中经常包含一个 error 类型的值,这个值能够是 nil,也能够是非 nil 的。假如是 nil,表明函数执行成功;假如是非 nil 的,则表明函数发生了过错。
  2. 在调用函数时,应该判别函数的回来值中的过错是否为 nil,假如是非 nil 的,则表明发生了过错,需求进行相应的处理。
  3. Go言语中也供给了 defer 关键字,能够在函数回来前调用一个函数,用于完成资源开释等整理工作。

通过这种办法,Go言语完成了过错处理的中心思路,能够协助开发人员更方便地处理过错,并且代码风格简练易懂。

go里的切片增修改跟js数组大不一样

在js里,对数组的操作,能够有许多,比方push,pop,shift,unshift,splice等等,可是在go里边,比方咱们添加元素给切片,要运用append

var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包办法
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需求解包

所以咱们就需求封装一个函数,往切片第i个方位添加元素,大概思路如下(你要封装的更好,需求判别下标是否越界,越界会让go程序报错)

var a []int{1,2,3}
a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i个方位刺进x

删去中间i个方位,go简略封装一个办法是这样的

var a []int{1,2,3}
a = append(a[:i], a[i+N:]...)

总感觉有点别扭。。。

类型断语和类型挑选

类型断语

在Go言语中类型断语的语法如下:

value, ok := x.(T)

其间,x 表明一个接口的类型,T 表明一个详细的类型(也可为接口类型)。

也便是说变量或许常量的x是否是T类型。

该断语表达式会回来 x 的值(也便是 value)和一个布尔值(也便是 ok),可根据该布尔值判别 x 是否为 T 类型:

  • 假如 T 是详细某个类型,类型断语会查看 x 的动态类型是否等于详细类型 T。假如查看成功,类型断语回来的结果是 x 的动态值,其类型是 T。
  • 假如 T 是接口类型,类型断语会查看 x 的动态类型是否满足 T。假如查看成功,x 的动态值不会被提取,回来值是一个类型为 T 的接口值。
  • 不管 T 是什么类型,假如 x 是 nil 接口值,类型断语都会失败。

类型挑选

类型挑选是一种按次序从几个类型断语中挑选分支的结构。

类型挑选与一般的 switch 句子相似,不过类型挑选中的 case 为类型(而非值), 它们针对给定接口值所存储的值的类型进行比较。

switch v := i.(type) {
case T:
    // v 的类型为 T
case S:
    // v 的类型为 S
default:
    // 没有匹配,v 与 i 的类型相同
}

举例如下:

package main
import "fmt"
func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}
func main() {
	do(21)
	do("hello")
	do(true)
}

回来

Twice 21 is 42
"hello" is 5 bytes long
I don't know about type bool!

接口和类型的关系是什么

接□实质上是-种类型,确切地说,是指针类型。接□能够完成多态功用。假如—个类型完成 了某个接□‖则一切运用这个接□的地万都支撑这种类型的值。

假如接□没有任何办法声明’则它便是-个空接□(|∩te付ace{}),相似ts的any类型。

接口变量默认值是nil,假如完成接口的类型支撑持平运算, 则可做持平运算否则会报错.

var a interface{}
fmt.Println(a == nil)
fmt.Println(a == 2)

回来

true
false

Go言语的接口不支撑宣接实例化,但支撑赋值操作,然后快速完成接口与完成类的映射。

将完成接口的目标实例赋值给接口

type Num int
func (x Num) Equal(i Num) bool {
	return x == i
}
func (x Num) LessThan(i Num) bool {
	return x < i
}
func (x Num) MoreThan(i Num) bool {
	return x > i
}
func (x *Num) Multiple(i Num) {
	*x = *x + 1
}
func (x *Num) Divide(i Num) {
	*x = *x / i
}

相应的,咱们定义一个接口:

type NumI interface {
	Equal(i Num) bool
	LessThan(i Num) bool
	MoreThan(i Num) bool
	Multiple(i Num)
	Divide(i Num)
}

接着,咱们赋值

var x Num = 8
var y NumI = &x

这儿为什么不能用

var y NumI = x

由于接口类型实质是指针啊,所以不能这么做,可是明明Num里 Num指针,也便是Num,并没有完成比方Equal办法啊,由于Equal办法的接受体是Num不是Num,这个咋解释呢?

由于Go言语会根据下面这样的非指针成员办法:

func (x Num) Equal(i Num) bool {
	return x == i
}

自动生成一个新的与之对应的指针成员办法:

func (x *Num) Equal(i Num) bool {
	return (*x).Equal(i)
}

接口赋值

接口赋值并不要求两个接口彻底等价(办法彻底泪同),假如接口A的办法列表是接口B的办法列表的子集,则接口B能够赋值给接口A。

下篇接着讲并发编程,反射等常见坑点

参考材料:

  • 《go web编程》
  • 《go言语小书》
  • 官网