GO语言入门 | 青训营笔记

91 阅读9分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记

GO语言的特性

  1. 高性能、高并发
  2. 丰富的标准库
  3. 完善的工具链
  4. 静态链接
  5. 快速编译
  6. 跨平台
  7. 垃圾回收

开发环境

  1. 安装golang可以去官网,打不开可以去golang中国的镜像下载
  2. 开发者工具推荐使用Goland或者VScode

基础语法

目录结构

bin

存放编译后可执行的文件。

pkg

存放编译后的应用包。

src

存放应用源代码。

例如:

├─ code  -- 代码根目录

│  ├─ bin

│  ├─ pkg

│  ├─ src

│     ├── hello

│         ├── hello.go

变量

go语言为强类型语言,字符串为内置类型可以直接拼接且使用等于号判断是否相等

声明一个变量

  1. 指定变量类型,声明后不赋值,使用默认值
var name string = "小明"
  1. 根据值自行判定变量类型(类型推断Type inference)

如果一个变量有一个初始值,Go将自动能够使用初始值来推断该变量的类型。因此,如果变量具有初始值,则可以省略变量声明中的类型。

  1. 省略var, 注意 :=左侧的变量不应该是已经声明过的(多个变量同时声明时,至少保证一个是新变量),否则会导致编译错误(简短声明)
c := 10

fmt.Println(c)

这种方式它只能被用在函数体内,而不可以用于全局变量的声明与赋值

常量的使用

常量是一个简单值的标识符,在程序运行时,不会被修改的量

显式类型定义:const b string = "abc"

隐式类型定义:const b = "abc"

if-else机构

if语句不需要括号,必须直接接大括号

package main



import "fmt"



func main() {

   bool1 := true

   if bool1 {

      fmt.Printf("The value is true\n")

   } else {

      fmt.Printf("The value is false\n")

   }

}

循环

go中没有while.do-while循环,只有for循环

package main



import "fmt"



func main() {

    for i := 0; i < 5; i++ {

        fmt.Printf("This is the %d iteration\n", i)

    }

}

无限循环

如果 for 循环的头部没有条件语句,那么就会认为条件永远为 true,因此循环体内必须有相关的条件判断以确保会在某个时刻退出循环。for { }可以用在服务器,用于不断等待和接受新的请求

for-range 结构

这是 Go 特有的一种的迭代结构,您会发现它在许多情况下都非常有用。它可以迭代任何一个集合,一般格式:for ix, val := range coll { }

Switch

go语言的switch十分强大,使用时可以不加break语句,且可以取代任意的if-else语句

switch var1 {

    case val1:

        ...

    case val2:

        ...

    default:

        ...

}

数组与切片

Go 语言中的数组是一种 值类型(不像 C/C++ 中是指向首元素的指针),所以可以通过 new()来创建: var arr1 = new([5]int)

package main



import "fmt"



func main() {

   var arr1 [5]int

   for i := 0; i < len(arr1); i++ {

      arr1[i] = i * 2

   }



   for i := 0; i < len(arr1); i++ {

      fmt.Printf("Array at index %d is %d\n", i, arr1[i])

   }

}

相比于数组的长度固定使用切片会更加灵活

通过make([] type,len)来创建切片,并可以通过下标将元素存取

s := make([]string, 3)

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]

可以使用copy函数来进行切片的复制

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

copy(c, s)

fmt.Println(c) // [a b c d e f]

go语言的切片操作与python操作类似,但不支持复数索引

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]

Map

同样使用make来创建,m := make(``map``[string]int),这里的string为键的类型,int为值的类型

m := make(map[string]int)

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"]) // 0

可以使用delete函数来对map中的数据进行删除delete(m, "one"),第一个参数为map,第二个参数为你要删除的键值对的键。

range

for-range,当遍历数组时,第一个参数是下标,第二个参数是对于下标的值,如果不需要下标可以将第一个参数设置为_。

nums := []int{2, 3, 4}

sum := 0

for i, num := range nums {

   sum += num

   if num == 2 {

      fmt.Println("index:", i, "num:", num) // index: 0 num: 2

 }

}

fmt.Println(sum) // 9

遍历map时,第一个参数为key,第二个参数为value,也可以单独遍历key,需要注意的是go的map是完全无序的无论键或值。

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

}

func

Go 是编译型语言,所以函数编写的顺序是无关紧要的;鉴于可读性的需求,最好把 main()函数写在文件的前面,其他函数按照一定逻辑顺序进行编写(例如函数被调用的顺序),函数的return可以带有0个或者多个参数,且在 Go 里面函数重载是不被允许的(函数重载(function overloading)指的是可以编写多个同名函数,只要它们拥有不同的形参 / 或者不同的返回值)

简单函数的创造:

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]

   return v, ok

}

指针

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址

var i = 5

fmt.Printf("i的地址为%p\n", &i)

声明一个指针

var intP *int

一个指针变量可以指向任何一个值的内存地址,当一个指针被定义后没有分配到任何变量时,它的值为 nil

var intP *int

var i = 5

fmt.Printf("i的地址为%p\n", &i)

intP = &i

fmt.Printf("i的地址为%p\n", intP)

指针的一个高级应用是你可以传递一个变量的引用(如函数的参数),这样不会传递变量的拷贝。指针传递是很廉价的,只占用 4 个或 8 个字节。当程序在工作中需要占用大量的内存,或很多变量,或者两者都有,使用指针会减少内存占用和提高效率。被指向的变量也保存在内存中,直到没有任何指针指向它们,所以从它们被创建开始就具有相互独立的生命周期。

结构体

结构体类型为struct字段类型声明放在变量名字后面

type user struct {

   name     string

   password string

}

初始化一个结构体可以通过结构体名字接大括号来声明,其中大括号中包含结构体属性的值,可以直接通过声明属性名字来赋值也可以使直接赋值,如果没被初始化的字段则会被初始化为空值,也可以使用user.name这种方式来初始化字段。

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"

结构体可以作为参数传入函数中可以是指针或者它本身,使用指针传递可以减少开销并可以对结构体进行修改

结构体方法

//普通方法

func checkPassword(u user, password string) bool {

   return u.password == password

}

//结构体方法

func (u user) checkPassword(password string) bool {

   return u.password == password

}

这样声明后可以直接通过结构体变量.方法去执行这个方法

error

golang中的错误处理较为简单清晰可以清楚的知道是那个函数出了问题

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("not found")

}

字符串

Go 中使用 strings包来完成对字符串的主要操作。

前缀和后缀

  • HasPrefix判断字符串s是否以prefix开头
  • HasSuffix判断字符串s是否以suffix结尾
s := "hello"

fmt.Printf("%t\n", strings.HasPrefix(s, "h"))

fmt.Printf("%t\n", strings.HasSuffix(s, "o"))

字符串包含关系

contains判断字符串s是否包含substr

s := "hello"

fmt.Printf("%t\n", strings.Contains(s, "hl"))

fmt.Printf("%t\n", strings.Contains(s, "hel"))

判断子字符串或字符在父字符串中出现的位置(索引)

  • Index返回字符串在s中的索引(str第一个字符的索引),-1表示s不包含str
  • LastIndex返回字符串在s中最后出现的索引(str第一个字符的索引),-1表示s不包含str
  • 如果 ch是非 ASCII 编码的字符,建议使用以下函数来对字符进行定位:
s := "hello"

fmt.Printf("%d\n", strings.Index(s, "lo"))

fmt.Printf("%d\n", strings.LastIndex(s, "l"))

fmt.Printf("%d\n", strings.Index(s, "loop"))

字符串替换

Replace用于将字符串 str中的前 n个字符串 old替换为字符串 new,并返回一个新的字符串,如果 n = -1则替换所有字符串 old为字符串 new

s := "hellollollo"

fmt.Printf("%s\n", strings.Replace(s, "llo", "lo", 2))

fmt.Printf("%s\n", strings.Replace(s, "llo", "lo", -1))

统计字符串出现的次数

Count用于计算字符串 str在字符串 s中出现的非重叠次数:

s := "hellollollo"

fmt.Printf("%d\n", strings.Count(s, "llo"))

重复字符串

Repeat用于重复 count次字符串 s并返回一个新的字符串

package main



import (

    "fmt"

    "strings"

)



func main() {

    var origS string = "Hi there! "

    var newS string



    newS = strings.Repeat(origS, 3)

    fmt.Printf("The new repeated string is: %s\n", newS)

}

修改字符串大小写

  • ToLower将字符串中的 Unicode 字符全部转换为相应的小写字符
  • ToUpper将字符串中的 Unicode 字符全部转换为相应的大写字符

修剪,分隔,拼接字符串

  • strings.TrimSpace(s):去除开头和结尾的空白符号
  • strings.Trim(s, "cut"):去除开头和结尾的cut去除
  • 只去除开头:TrimLeft,只去除结尾:TrimRight
  • strings.Fields(s):利用空白作为分隔符将字符串分割为若干块,并返回一个 slice 。如果字符串只包含空白符号,返回一个长度为 0 的 slice
  • strings.Split(s, sep):自定义分割符号对字符串分割,返回 slice
  • Join用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串
var origS = "Hi there! ! !"

s1 := strings.Fields(origS)

s2 := strings.Join(s1, ";")

println(s2)

打印输出

  1. 导入包
import "fmt"
  1. 常用打印函数
  • 打印:Print
  • 格式化打印:Printf
  • 打印后换行

格式化打印中的常用占位符:

%v,原样输出

%T,打印类型

%t,bool类型

%s,字符串

%f,浮点

%d,10进制的整数

%b,2进制的整数

%o,8进制

%x,%X,16进制

%x:0-9,a-f

%X:0-9,A-F

%c,打印字符

%p,打印地址

JSON

在GO结构中解码JSON

将JSON数据解析成go结构,需要把JSON数据映射到GO结构中,package jsonGo中提供了Unmarshal 函数,帮助我们将数据解析为结构体,Unmarshal 要求数据为字节数组,以便将其解析为一个接口

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

从Go结构中编码JSON

Go 的package json 提供了Marshal 函数来帮助将结构编码为 JSON 数据。Marshal 需要一个接口,我们将从中编码JSON数据。让我们把我们的User 对象编码成JSON。

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

time

time包为我们提供了一个数据类型 time.Time(作为值使用)以及显示和测量时间和日期的功能函数。

func main() {

   now := time.Now()

   fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933

 t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)

   t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)

   fmt.Println(t)                                                  // 2022-03-27 01:25:36 +0000 UTC

 fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25

 fmt.Println(t.Format("2006-01-02 15:04:05"))                    // 2022-03-27 01:25:36

 diff := t2.Sub(t)

   fmt.Println(diff)                           // 时间差1h5m0s

 fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900

 t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")

   if err != nil {

      panic(err)

   }

   fmt.Println(t3 == t)    // true

 fmt.Println(now.Unix()) // 1648738080

}

实战

猜谜游戏

在第一步生成随机数中这里需要注意的是生成随机数需要设置随机种子,否则随机数将会与上次生成的一致,随机数种子可以使用当前时间的时间戳来设置。

maxNum := 100

 //设置随机种子

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

secretNumber := rand.Intn(maxNum)

fmt.Println("The secret number is ", secretNumber)

输入数据模块中老师使用的是bufio包读取数据,但需要注意的是Windows中输入数据时去掉后缀需要多消除个\r,否则不能转换为数字。

fmt.Println("Please input your guess")

reader := bufio.NewReader(os.Stdin)

input, err := reader.ReadString('\n')

if err != nil {

   fmt.Println("An error occured while reading input. Please try again", err)

   return

}

 //windows多了个\r去掉换行符

input = strings.TrimSuffix(input, "\r\n")

 //转换成数字

guess, err := strconv.Atoi(input)

作业中使用scanf来读取输入信息会更加简洁

var guess int

_, err := fmt.Scanln(&guess)