【Go】Go语言学习笔记--Go语言基础【下】

简介: 运算符Go语言内置的运算符:算术运算符关系运算符逻辑运算符位运算符赋值运算符

值为切片的map

var sliceM1 = make(map[string][]string, 4)
sliceM1["names"] = make([]string, 4)
sliceM1["names"] = append(sliceM1["names"], "小王","小黄","小李")
fmt.Println(sliceM1)
复制代码

练习题

  1. 写一个程序,统计一个字符串中每个单词出现的次数。比如:”how do you do”中how=1 do=2 you=1。
// 我的解法
var words string = "how do you do"
var wordsSlice []string = strings.Split(words, " ")
var wordCountMap map[string]int = make(map[string]int)
for _, word := range wordsSlice{
   var _, isIn = wordCountMap[word]
   if isIn {
      wordCountMap[word]++
   } else {
      wordCountMap[word] = 1
   }
}
var printMsg = "在"how do you do"中"
for key,val := range wordCountMap {
   str:= key + "=" + strconv.Itoa(val) + ";"
   printMsg += str
}
fmt.Print(printMsg)
复制代码
  1. 观察下面代码,写出最终的打印结果。
type Map map[string][]int
m := make(Map) //[]
s := []int{1,2} // [1 2] len 2 cap 2 x001
s = append(s,3) [1 2 3] len 3 cap 4 x002
fmt.Printf("%+v \n", s) //[1 2 3] len3 cap4 x002
m["q1mi"] = s // [q1mi: [1 2 3] len3 cap4 x002]
s = append(s[:1], s[2:]...) //[1 3] len 2 cap4 x002
fmt.Printf("%+v\n",s) //len2 cap4 x002 [1 3]
fmt.Printf("%+v\n",m["q1mi"])// len3 cap4 //[1 3 3] x002
[1 2 3] [1 3] [1 3 3]
复制代码

函数

Go语言中支持函数、匿名函数和闭包。

函数定义

格式如下:

func 函数名(参数)(返回值){
    函数体
}
复制代码

举个🌰

func main(){
    fmt.Println(intSum(1,2)) //3
}
func intSum(x int, y int)(int) {
    return x + y
}
复制代码

参数

类型简写

函数参数中相邻变量的类型相同,则可以省略类型,举个🌰

func intSum(x,y int) int {
    return x + y
}
复制代码

可变参数

指函数的参数数量不固定,通过在参数名后面加...来表示。

注意:可变参数通常要作为函数的最后一个参数,举个🌰

func intSum(x ...int) int {
    sum := 0
    for _,v := range x {
        x += sum
    }
    return sum
}
复制代码

也可以与固定参数搭配使用

func intSum(x int, y ...int) int {
    sum := x
    for _,v := range x {
        x += sum
    }
    return sum
}
复制代码

本质上,函数的可变参数是通过切片来实现的。

返回值

Go语言通过return关键字向外输出返回值,这一点与JavaScript相同,但也有不同点。

多个返回值

func calc(x, y int) (int, int) {
    sum := x + y
    sub := x - y
    return sub, sum
}
复制代码

返回值命名

函数在定义时可以给返回值命名,并在函数体重使用这些变量,最后用return返回

func calc(x, y int) (sub, sum int) {
    sum = x + y
    sub = x - y
    return
}
复制代码

返回值补充

当函数返回值类型为slice是,nil可以看做是一个有效的slice,没必要返回一个长度为0的切片。

func someFunc(x string) []int {
    if x == "" {
        return nil
    } else {
        res := make([]int,4)
        return res
    }
}
复制代码

变量作用域

全局变量

全局变量指的是定义在函数外部的变量,程序运行期间一直都可以有效访问,与JavaScript的全局变量类似。

var commonNum int = 100 // 全局变量 commonNum
func main() {
    fmt.Println("num:", num) //100
}
func testCommonVar() {
    fmt.Println("num": num) //100
}
复制代码

局部变量

如果全局变量与局部变量重名,那么优先访问局部变量。

var num = 1
func main() {
 num := 10
 fmt.Println(num) // 10
}
复制代码

语句块定义的变量(类似JavaScript中的块级作用域),比如forif等,举个🌰

func testLocalVar(x, y int) {
    fmt.Println(x,y)// 函数的参数也只在函数内生效
    if x>0 {
      z := 10
      fmt.Println(z)// z只能在此处生效
    }
    fmt.Println(z) //不生效
}
复制代码

函数类型与变量

定义函数类型

可以使用type关键字定义一个函数的类型,格式如下:

type calculation func(int, int) int
复制代码

上面语句定义了一个calculation类型,是一种函数类型,这种函数接受两个int类型的参数并返回一个int类型的返回值。

也就是说,凡是满足这个条件的函数,都是calculation类型的函数,举个🌰

func add(x, y int) int {
    return x + y
}
func sub(x, y int) int {
    return x - y
}
复制代码

add和sub都能赋值给calculation类型的变量。

var c calculation
c = add
c(3,4) //7
复制代码

函数类型变量

可以声明函数类型的变量并且为该变量赋值

var c calculation
c = add
fmt.Printf("type of c: %T\n", c) // main.calculation
fmt.Println(c(1,2))
f := add
fmt.Printf("type of f: %T\n", f) // func(int, int) int
fmt.Println(c(2,3))
复制代码

高阶函数

高阶函数分为函数作为参数函数作为返回值两部分

函数作为参数

func add(x, y int) int {
    return x+y
}
func calc(x, y int, callback func(int, int) int) int {
    return callback(10, 20)
}
func main() {
    res := calc(10, 20, add)
    fmt.Println(res) //30
}
复制代码

函数作为返回值

func do(s string) (func(int, int) int, error) {
   switch s {
   case "+":
      return add, nil
   case "-":
      return sub, nil
   default:
      err := errors.New("无法识别的操作符")
      return nil, err
   }
}
复制代码

匿名函数和闭包

匿名函数

格式如下:

func(参数)(返回值){
    函数体
}
复制代码

匿名函数因为没有函数名,所以不能直接调用,需要保存到某个变量里,或者作为立即执行函数。

add := func(x, y) int {
    return x + y
}
add(10, 20)
func(x, y) {
    fmt.Println(x + y)
}(1, 2)
复制代码

匿名函数多用于实现回调函数或者闭包

闭包

闭包值得是一个函数与其相关的引用环境组合而成的实体。简单来说就是:闭包=函数+引用环境,举个🌰

func add() func(int) int {
   var x int
   return func(y int) int {
       x += y
       return x
   }
}
f := add()
fmt.Println(f(10))//10
fmt.Println(f(20))//30
f1 := add()
fmt.Println(f1(20))//20
fmt.Println(f1(40))//60
复制代码

与JavaScript相似,函数体内的变量如果被函数体以外的环境引用,就会形成闭包,该变量不会销毁。

闭包示例2:

func makeSuffixFunc(suffix string) func(string) string {
    return func(fileName string) string {
        if !strings.HasSuffix(fileName, suffix) {
            return fileName + suffix
        }
        return fileName
    }
}
复制代码

闭包示例3:

func calc(base int) (func(int) int, func(int) int) {
    add := func(num int) int {
        base += num
        return base
    }
    sub := func(num int) int {
        base -= num
        return base
    }
    return add, sub
}
add, sub := calc(10)
fmt.Println(add(1)) // 11
fmt.Println(add(2)) // 9
复制代码

defer语句

defer语句会将其后面跟孙的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先defer的后执行,后defer的先执行,举个🌰

fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println(4)
fmt.Println("end")
// start 4 end 3 2 1
复制代码

多用于处理资源释放问题,例如:资源清理文件关闭解锁记录时间等。

defer的执行时机

返回值=x --> 运行defer --> ret指令

牢记defer的三条规则

  1. 延迟函数的参数在defer语句出现的时候就已经确定下来了
  2. 后进先出的规则
  3. 延迟函数可能会影响主函数的具名返回值

defer案例

// 主函数拥有匿名返回值,返回变量
//一个主函数拥有一个匿名的返回值,返回使用本地或全局变量,这种情况下defer语句可以引用到返回值,但不会改变返回值。
// 返回5
func f1() int {
  x := 5
  defer func() {
    x++
  }()
  return x 
}
// 主函数拥有具名返回值
// 主函声明语句中带名字的返回值,会被初始化成一个局部变量,函数内部可以像使用局部变量一样使用该返回值。如果defer语句操作该返回值,可能会改变返回结果。
// x =5
// x++
// return
func f2() (x int) {
  defer func() {
    x++
  }()
  return 5
}
// 主函数拥有具名返回值
// x = 5
// y = x
// x++
// return
func f3() (y int) {
  x := 5
  defer func() {
    x++
  }()
  return x
}
// 主函数拥有具名返回值,但是延迟函数的参数从defer开始就确定了,相当于拷贝一份,所以后面的x++是自执行函数x的++
func f4() (x int) {
  defer func(x int) {
    x++
  }(x)
  return 5
}
func main() {
  fmt.Println(f1()) // 5
  fmt.Println(f2()) // 6
  fmt.Println(f3()) // 5
  fmt.Println(f4()) // 5
}
复制代码

defer面试题

func calc(index string, a, b int) int {
  ret := a + b
  fmt.Println(index, a, b, ret)
  return ret
}
func main() {
  x := 1
  y := 2
  defer calc("AA", x, calc("A", x, y))
  x = 10
  defer calc("BB", x, calc("B", x, y))
  y = 20
}
// A 1 2 3
// B 10 2 12
// BB 10 12 22
// AA 1 3 4
复制代码

内置函数

函数名 介绍
close 用来关闭channel
len 用来求长度,比如string array slice map channel
new 用来分配内存,主要用来分配值类型,比如int, struct。返回的是指针
make 用来分配内存,主要用来分配引用类型,比如chan map slice
append 用来追加元素到数组中、slice中
panic和recover 用来做错误处理

panic/recover

panic用来处理错误,可以在任何地方引发,但recover只有在defer调用的函数中有效,举个🌰

func funcA() {
    fmt.Println("func A")
}
func funcB() {
    panic("func B error")
}
func funcC() {
    fmt.Println("func C")
}
func main() {
    funcA()
    funcB()
    funC()
}
复制代码

这时候程序就会崩溃,因为funcB中引发了panic,但是,我们可以通过recover让程序起死回生!只需要对funcB做一下修改举个🌰

func funcB() {
    defer func(){
        err := recover()
        if err != nil {
            fmt.Println("func B")
        }
    }()
    panic("func B error")
}
复制代码

注意

  1. recover必须搭配defer使用
  2. defer一定要再可能发panic的语句之前定义

练习题

  1. 分金币
/*
你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
分配规则如下:
a. 名字中每包含1个'e'或'E'分1枚金币
b. 名字中每包含1个'i'或'I'分2枚金币
c. 名字中每包含1个'o'或'O'分3枚金币
d: 名字中每包含1个'u'或'U'分4枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
程序结构如下,请实现 ‘dispatchCoin’ 函数
*/
var (
  coins = 50
  users = []string{
    "Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
  }
  distribution = make(map[string]int, len(users))
)
func main() {
  left := dispatchCoin()
  fmt.Println("剩下:", left)
}
// 我的解法
func dispatchCoin() int {
   coinSum := coins
   for _,person := range users{
      distribution[person] = 0
      nameLower := strings.ToLower(person)
      nameSlice := strings.Split(nameLower, "")
      for _,nameWord := range nameSlice {
         switch nameWord {
            case "e":
               distribution[person] += 1
               coinSum-=1
            case "i":
               distribution[person] += 2
               coinSum-=2
            case "o":
               distribution[person] += 3
               coinSum-=3
            case "u":
               distribution[person] += 4
               coinSum-=4
            default:
               break
         }
      }
   }
   for key,value := range distribution{
      fmt.Printf("%v分到了:%v个\n",key, value)
   }
   return coinSum
}
目录
相关文章
|
10天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
30 2
|
8天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
19 2
|
8天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
21 2
|
3天前
|
Go API 数据库
Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
本文介绍了 Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
16 4
|
3天前
|
缓存 监控 前端开发
在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统
本文深入探讨了在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统。
27 1
|
6天前
|
Go
go语言中的continue 语句
go语言中的continue 语句
16 3
|
7天前
|
安全 Go 调度
探索Go语言的并发模型:goroutine与channel
在这个快节奏的技术世界中,Go语言以其简洁的并发模型脱颖而出。本文将带你深入了解Go语言的goroutine和channel,这两个核心特性如何协同工作,以实现高效、简洁的并发编程。
|
8天前
|
Go
go语言中的 跳转语句
【11月更文挑战第4天】
17 4
|
8天前
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
40 1
|
1天前
|
存储 Go PHP
Go语言中的加解密利器:go-crypto库全解析
在软件开发中,数据安全和隐私保护至关重要。`go-crypto` 是一个专为 Golang 设计的加密解密工具库,支持 AES 和 RSA 等加密算法,帮助开发者轻松实现数据的加密和解密,保障数据传输和存储的安全性。本文将详细介绍 `go-crypto` 的安装、特性及应用实例。
11 0
下一篇
无影云桌面