Go语言基础 | 青训营笔记

82 阅读7分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天

1月15日上了后端基础班的第一课,主要就是Go快速入门。

在这篇笔记中我记录了Go语法中我不熟悉或者需要特别注意的地方,因为之前有编程基础,所以过于基础的部分就不写啦。

基础语法

Printf

通用占位符

占位符说明
%v值的默认格式表示
%+v类似%v,但输出结构体时会添加字段名
%#v值的Go语法表示
%T打印值的类型
%%百分号

如果只是需要打印出变量值、相关信息,不需要像C一样记忆各种占位符,只需使用通用占位符。

num := 1
name := "jack"
// %v俗称占位符
fmt.Printf("a=%v\n", a)
// %T 打印类型
fmt.Printf("name的类型是%T.\n")

当然,还有专门用于Integer、String、Boolean等类型的占位符,这里不再赘述。

switch

  1. Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。 除非以 fallthrough 语句结束,否则分支会自动终止。
  2. Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。
    i := 2
    fmt.Print("Write ", i, " as ")
    switch i {
    case 1:
        fmt.Println("one")
    case 2:
        fmt.Println("two")
    case 3:
        fmt.Println("three")
    }

没有条件的 switch 同 switch true 一样

   switch {
      case 1 + 1 == 2 :
         fmt.Printf("我会输出\n" )    
      case 1 + 1 == 6 :
         fmt.Printf("我不会输出\n" ) 
      default:
         fmt.Printf("我是默认\n" );
   }

程序输出

我会输出

defer

  1. defer 语句会将函数推迟到外层函数返回之后执行。推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
  2. 推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。

指针

与 C 不同,Go 没有指针运算。

数组

var array [n]T            // 拥有n个T类型的值的数组array

数组效率高但是不灵活。我们提前处理数据时,一般都不知道元素的数量。因此,我们使用切片。

切片

切片是一个轻量级的结构体封装,这个结构体被封装后,代表一个数组的一部分。这里指出了一些创建切片的方式,并指出我们以后会在何时使用这些方式。

第一种方式和我们创建一个数组时有点细微的变化:

scores := []int{1,4,293,4,9}

和声明数组不同的是,声明切片不需要在方括号中指定其大小。

另一种方式,使用make:

scores := make([]int, 10)

需要特别指出的是,我们需要为底层数组分配内存,并且也要初始化这个切片。在上面的例子中,我们初始化了一个长度和容量都是10的切片。长度表示切片的长度,容量表示底层数组的大小。在使用make创建切片时,我们可以分别的指定长度和容量大小:

scores := make([]int, 0, 10)

上面创建了一个长度为0但是容量为10的切片。

初始化一个切片

4种常用的方式去初始化一个切片:

names := []string{"leto", "jessica", "paul"}
checks := make([]bool, 10)
var names []string
scores := make([]int, 0, 20)
  • 第一种你不需要太多的说明。但是使用这种方式你得提前知道你想往数组存放的值。
  • 第二种方式在你想往切片的特定位置写入一个值时很有用
  • 第三种方式会返回一个空切片,一般和append一起使用,此时切片的元素数量是未知的。
  • 最后一种方式可以让我们指定切片的初始容量

切片并不存储任何数据,它只是描述了底层数组中的一段。

更改切片的元素会修改其底层数组中对应的元素

与它共享底层数组的切片都会观测到这些修改。

scores := []int{1,2,3,4,5}
slice := scores[2:4]
slice[0] = 999
fmt.Println(scores)

底层数组被修改了,所以输出是[1, 2, 999, 4, 5]

方法

Go 没有类。不过你可以为结构体类型定义方法。

方法就是一类带特殊的 接收者 参数的函数。

方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。

func (v Vertex) Abs() float64 {
        return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

此例中,Abs方法拥有一个名为 v,类型为 Vertex 的接收者。

接口

接口类型 是由一组方法签名定义的集合。

你也许觉得奇怪,这样做有什么用。接口可以使你的代码从具体的实现中去耦。如果在编程时使用接口,而不是它们具体的实现,我们可以很容易的改变和测试我们代码,但是对我们的代码没有任何影响。

接口与隐式实现

类型通过实现一个接口的所有方法来实现该接口。既然无需专门显式声明,也就没有“implements”关键字。

接口值

在内部,接口值可以看做包含值和具体类型的元组:

(value, type)

空接口

指定了零个方法的接口值被称为 空接口:

interface{}

空接口可保存任何类型的值。

空接口被用来处理未知类型的值。例如,fmt.Print 可接受类型为 interface{} 的任意数量的参数。

package

Go programs are constructed by linking together packages.

A package in turn is constructed from one or more source files that together declare constants, types, variables and functions belonging to the package and which are accessible in all files of the same package. Those elements may be exported and used in another package.

import

Aliased import

import short_name "full_name"

之后可以使用short_name,避免繁琐

Dot import

大多用于测试

Perks
  • using elements of a package without mentioning the name of the package and can be used directly
Drawbacks
  • namespace collisions

Blank import

We can import the package and not use it by placing a blank

Go Modules

Using Go Modules

A module is a collection of Go packages stored in a file tree with a go.mod file at its root. The go.mod file defines the module’s module path, which is also the import path used for the root directory, and its dependency requirements, which are the other modules needed for a successful build.

Make the current directory the root of a module by using go mod init. The go mod init command writes a go.mod file.

The go.mod file only appears at the root of the module. Packages in sub-directories have import paths consisting of the module path plus the path to the sub-directory.

结构体

在go语言中,函数的参数传递都是按值传递,即传递的是一个拷贝。

结构体上的函数

我们可以将一个方法和一个结构体关联:

type Saiyan struct {
        Name stringPower int
}
​
func (s *Saiyan) Super() {
        s.Power += 10000
}

在上面的代码中,我们可以说类型*SaiyanSuper方法的接收者。可以向下面代码一样调用Super:

goku := &Saiyan{"Goku", 9001}
goku.Super()
fmt.Println(goku.Power) // 将打印:19001

构造函数

结构体没有构造函数,你可以创建一个函数返回一个相应类型的实例代替。

new

尽管没有构造函数,go有一个内置的函数new,可以用来分配一个类型需要的内存。new(X)&X{}是等效的:

goku := new(Saiyan)
// 等效
goku := &Saiyan{}

组合

go支持组合,即一种结构体包含另外一个结构体。

type Person struct {
    Name string
}
​
func (p *Person) Introduce() {
    fmt.Printf("Hi, I'm %s\n", p.Name)
}
​
type Saiyan struct {
    *Person
    Power int
}
​
// 使用:
goku := &Saiyan{
    Person: &Person{"Goku"},
    Power: 9001,
}
goku.Introduce()

结构体Saiyan有一个字段是*Persion类型。我们没有明确的给它一个字段名,我们可以间接的使用这个组合类型的字段和方法。然而,go编译器会给该字段一个名字,认为这是完全有效的。

Go JSON

JSON 是一种轻量级的数据交换格式,常用作前后端数据交换,Go 在 encoding/json 包中提供了对 JSON 的支持。

序列化

把 Go struct 序列化成 JSON 对象,Go 提供了 Marshal 方法,正如其含义所示表示编排序列化,函数签名如下:

func Marshal(v interface{}) ([]byte, error)

只有 struct 中支持导出的 field 才能被 JSON package 序列化,即首字母大写的 field

反序列化

反序列化函数是 Unmarshal ,其函数签名如下:

func Unmarshal(data []byte, v interface{}) error

如果要进行反序列化,我们首先需要创建一个可以接受序列化数据的 Go struct:

var m Message
err := json.Unmarshal(b, &m)

math/rand

Package rand implements pseudo-random number generators unsuitable for security-sensitive work.

Random numbers are generated by a Source. Top-level functions, such as Float64 and Int, use a default shared Source that produces a deterministic sequence of values each time a program is run.

Use the Seed function to initialize the default Source if different behavior is required for each run.

rand.Seed(time.Now().UnixNano())

The default Source is safe for concurrent use by multiple goroutines.

This package's outputs might be easily predictable regardless of how it's seeded.

For random numbers suitable for security-sensitive work, see the crypto/rand package.