Go 语言入门与实战课程笔记 | 青训营笔记

99 阅读8分钟

Go 语言入门与实战课程笔记 | 青训营笔记

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

基础入门

基础环境配置

安装

go语言的sdk 可以在官网下载,Get Started - The Go Programming Language (google.cn)。下载最新版即可,当前为1.19.5

go语言 开发环境可选vscode,Golang.后者为jetrain的付费IDE,功能更加完善。

环境配置

常见变量:(明后补全)

  • GOROOT:
  • GOPATH:
  • GOPROXY

基础语法学习

基础数据类型

go的数据类型有如下几种:

  • 整型: 包括int8,int16,int32,int64,uint8,uint16,unint32,unint64
  • 浮点型: 包含float32float64
    • float64:64位浮点数 相当于Java中的double
    • float32: 32位浮点数 相当于普通的float
  • 字符串:string
  • 布尔值: 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true
  • 若干派生类型

变量

Go语言的变量声明有三种常用的方法:

// 常见的创建变量格式
var identifier type
// var 可直接定义变量
var a = "initial"
// 后置类型可省略,var初始化时可以自动推导类型
var num int =  4
// 相同类型的变量可以在同一行声明并初始化
var b, c int = 1, 2
var d = true
// 如果未为一个变量初始化,则必须显式指定变量类型,此时,变量会被以初始值自动初始化:
var e float64
// 海象运算符:不需要var,可自动推导类型并赋值
// 等价于 var f = 3.2
f := float32(e)

常量

常量用const修饰,并且不需要声明类型,可由上下文自动推导

const variable = value

流程控制

条件语句

与其他语言的主要区别:

  • golang中的IF语句不需要括号,
  • IF语句后必须接花括号,即使是对于单行语句块,也必须添加括号,而不能像其他语言那样直接省略。
循环
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
}

由上述代码,go中的循环相比其他语言简化了许多。while,do全部统一为for,且for语句内不需要括号,

switch
t := time.Now()
switch {
case t.Hour() < 12:
   fmt.Println("It's before noon")
default:
   fmt.Println("It's after noon")
}

相比 C 或者 C++ , go语言里面的 switch 功能更强大。可以使用任意的变量类型,甚至可以用来取代任意的 if else 语句。你可以在 switch 后面不加任何的变量,然后在 case 里面写条件分支。这样代码相比你用多个 if else 代码逻辑会更为清晰。

需要注意的是,与其他语言恰好相反,switch 语句中每个 casebreak 是隐式存在的,也就是说,每个 case 的逻辑会在执行完毕后立刻退出,而不是跳转到下一个 case

要想跳转到下一个 case,则应该使用 fallthrough 关键字

数组

数组就是一个具有编号且长度固定的元素序列。 比如这里的话是一个可以存放 5 个int元素的数组 A 。

var a [5]int

数组初始化,此处创建了有5个元素,内部元素分别为12345的数组。

b := [5]int{1, 2, 3, 4, 5}

对于一个数组,可以很方便地取特定索引的值或者往特定索引取存储值,然后也能够直接去打印一个数组。不过,在真实业务代码里面,我们很少直接使用数组,因为它长度是固定的,我们用的更多的是切片。

a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))


fmt.Println(b)

var twoD [2][3]int

切片

切片可以粗浅理解为更灵活的动态数组。比如说我们可以用 make 来创建一个切片,可以像数组一样去取值,使用 append 来追加元素。

s := make([]string, 3)

上述就创建了一个长度为3,容量为3的字符串slice。这其中slice的长度和容量与Java中ArrayListsizecapacity概念比较类似。

注意 append 的用法的话,你必须把 append 的结果赋值为原数组。因为 slice 的原理实际上是存储了长度和容量,加指向数组的指针,在append时,如果容量不够的话,会扩容并且返回新的 slice。

slice 初始化的时候也可以指定长度。

slice 拥有像 python 一样的切片操作,索引区间是左闭右开,如果左或者右不写则表示到最远端。不过不同于python,不支持负数索引

s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2])   // c
fmt.Println("len:", len(s)) // 3

// append 向尾部添加元素,返回值必须赋值给原切片
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]

c := make([]string, len(s))

//可以使用 copy 方法将一个切片内的元素复制到另一个切片中
copy(c, s)
fmt.Println(c) // [a b c d e f]

fmt.Println(s[2:5]) // [c d e]
fmt.Println(s[:5])  // [a b c d e]
fmt.Println(s[2:])  // [c d e f]

good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]

map

map 中文译作映射,是无序的键值对集合。

m := make(map[string]int) 声明了一个键(key)为 string 类型,值(value)为 int 类型的 Map。

map元素的访问与赋值如下:

m["one"] = 1
m["two"] = 2
fmt.Println(m)           // map[one:1 two:2]
fmt.Println(len(m))      // 2
fmt.Println(m["one"])    // 1
fmt.Println(m["unknow"])

map[key]可以返回value和bool类型的变量两个值,这也是golang的特殊之处,函数天生支持多个返回值。 如果map[key]中的key在map中不存在,那么访问会返回默认值,如下代码,

v, err := m["hello"]
fmt.Println(v, err)
// 执行结果为0 false

map可定义时提前初始化,基本逻辑与其他语言类似,类型后加花括号,

m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)

删除map中的元素,基本格式为delete(map,key)

delete(m, "one")

range

range的概念是为了更方便地遍历数组arraymap

range函数用在数组中将返回两个参数,分别是索引与对应位置的值。如果索引不需要,则可以用下划线替代。(golang中如返回参数的err不需要,可以统一用下划线接收,相当于垃圾桶)

range 处理map则会循环输出key和value

m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
   fmt.Println(k, v) // b 8; a A
}
for k := range m {
   fmt.Println("key", k) // key a; key b
}

函数,结构体,指针

函数

golang中的函数与其他语言主要区别在于 类型后置,且可以返回多个参数。返回值写在参数括号后面,func关键字则相当于void

func exists(m map[string]string, k string) (v string, ok bool) {
   v, ok = m[k]
   return v, ok
}
指针

go的指针特性有限,主要作用是对传入参数进行修改。一般情况下传参默认传值,

基本操作:使用 *type 声明一个指针变量,使用 * 对一个变量进行解引用,使用 & 获取一个变量的指针(引用)。

默认情况下,Go 的方法传参均为传值,而不是传引用,如果不传入指针而直接传入一个值的话,则方法实参会被复制一份再传入。

结构体

go中结构体与c类似,可以用 type className struct{}来定义结构体。 构造时需要传入每个字段的初始值,也可以采用键值对的形式初始化一部分字段。

在传入函数作为参数时可使用结构体的指针,这样可以实现修改结构体参数。

a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
c := user{name: "wang"}
c.password = "1024"
var d user
d.name = "wang"
d.password = "1024"

fmt.Println(a, b, c, d)                 // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(a, "haha"))   // false
fmt.Println(checkPassword2(&a, "haha")) // false
结构体方法

结构体方法类似其他语言中的类成员函数,对应的类及参数名写在func后面,同时参数可以选是否带指针,带指针的话则可以修改结构体参数内部的信息,否则只是拷贝参数。

func (u user) checkPassword(password string) bool {
   return u.password == password
}

func (u *user) resetPassword(password string) {
   u.password = password
}

错误处理

不同于 Java 自家家使用的异常。go语言的处理方式,能够很清晰地知道哪个函数返回了错误,并且能用简单的 if else 来处理错误。

在函数里面,我们可以在那个函数的返回值类型里面,后面加一个 error, 就代表这个函数可能会返回错误。

那么在函数实现的时候, return 需要同时返回两个值,要么就是如果出现错误的话,那么可以 return nil 和一个 error。如果没有的话,那么返回原本的结果和 nil

字符串操作

string字符串相关操作,需要导入string包。

a := "hello"
fmt.Println(strings.Contains(a, "ll"))                // true
fmt.Println(strings.Count(a, "l"))                    // 2
fmt.Println(strings.HasPrefix(a, "he"))               // true
fmt.Println(strings.HasSuffix(a, "llo"))              // true
fmt.Println(strings.Index(a, "ll"))                   // 2
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2))                     // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1))         // hEllo
fmt.Println(strings.Split("a-b-c", "-"))              // [a b c]
fmt.Println(strings.ToLower(a))                       // hello
fmt.Println(strings.ToUpper(a))                       // HELLO
fmt.Println(len(a))                                   // 5
b := "你好"
fmt.Println(len(b)) // 6

字符串格式化

fmt.Printf("s=%v\n", s)

格式化输出类似C语言中的printf函数,但是不需要格式控制符 比如%s来控制输出字符串或者数字。只需要%v即可,%+v可以打印详细结果。