非零基础速成Go语言_数组、切片、字典、流程与异常控制

这里不带着安装 Go 环境了,推荐大家准备 Goland + Go 环境

以下是所有语言都有的特性,看看 Go 有什么不一样的地方

整形再细分

Go 语言中,整数类型可以再细分成10个类型

image1

int 代表有符号,可表示负数 uint 代表无符号,只能表示正数。

结构包

数组

数组定义

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go语言中很少直接使用数组。一般用切片代替,即 Java 中的 List 结构

// [3] 里的3 表示该数组的元素个数及容量
var arr [3]int
arr[0] = 1
arr[1] = 2
arr[2] = 3

定义并使用

// 第一种方法
var arr [3]int = [3]int{1,2,3}

// 第二种方法
arr := [3]int{1,2,3}
切片

切片(Slice)与数组一样,也是可以容纳若干类型相同的元素的容器。与数组不同的是,无法通过切片类型来确定其值的长度。每个切片值都会将数组作为其底层数据结构。我们也把这样的数组称为切片的底层数组

当你在初始化时不制定容量,它就是容量动态变化的数组

切片追加元素的函数是 append 方法:

func main() {
		//初始化一个切片
    myarr := []int{1}
    // 追加一个元素
    myarr = append(myarr, 2)
    // 追加多个元素
    myarr = append(myarr, 3, 4)
    // 追加一个切片, ... 表示解包,不能省略
    myarr = append(myarr, []int{7, 8}...)
    // 在第一个位置插入元素
    myarr = append([]int{0}, myarr...)
    // 在中间插入一个切片(两个元素)
    myarr = append(myarr[:5], append([]int{5,6}, myarr[5:]...)...)

字典

类似 Java 的 HashMap,用来存储键值对元素。

与 HashMap 一样,要求 key 是唯一的,以便支持 Hash 特性

在声明字典时,必须指定好你的key和value是什么类型的,然后使用 map 关键字来告诉Go这是一个字典

map[KEY_TYPE]VALUE_TYPE

三种声明并初始化字典的方法

// 第一种方法
var scores map[string]int = map[string]int{"english": 80, "chinese": 85}

// 第二种方法
scores := map[string]int{"english": 80, "chinese": 85}

// 第三种方法
//make 代表为 map 开辟内存空间 score 指向这块空间
scores := make(map[string]int)
scores["english"] = 80
scores["chinese"] = 85

要注意的是,第一种方法如果拆分成多步(声明、初始化、再赋值),和其他两种有很大的不一样了,相对会比较麻烦。

    // 声明一个名为 score 的字典
    var scores map[string]int

    // 未初始化的 score 的零值为nil,否则无法直接进行赋值
    if scores == nil {
        // 需要使用 make 函数先对其初始化
        scores = make(map[string]int)
    }

    // 经过初始化后,就可以直接赋值
    scores["chinese"] = 90
    fmt.Println(scores)
}

循环获取 map 的减值

    scores := map[string]int{"english": 80, "chinese": 85}

		//键和值都要
    for subject, score := range scores {
        fmt.Printf("key: %s, value: %d\n", subject, score)
    }
		//只要值
    for _, score := range scores {
        fmt.Printf("key: %s, value: %d\n", subject, score)
    }
		//只要键
    for subject, _ := range scores {
        fmt.Printf("key: %s, value: %d\n", subject, score)
    }

指针

在 Java中 基本数据类型传递为值传递 引用类型传递就地址传递,在 Go 中也是类似的,但是操作起来还是有些许区别:Go 中指针/引用是在 C++上进行的改良

当我们定义一个变量 name

var name string = "Go"

此时,name 是变量名,它只是编程语言中方便程序员编写和理解代码的一个标签。

当我们访问这个标签时,会返回给我们它指向的内存地址里存储的值:“Go”。

出于某些需要,我们会将这个内存地址赋值给另一个变量名,通常叫做 ptr(pointer的简写),而这个变量,我们称之为指针变量。

换句话说,指针变量(一个标签)的值是指针,也就是内存地址,与 Java 中的引用变量一样

根据变量指向的值,是内存地址还是内存里的值,我把变量分为两种:

  • 普通变量:存数据值本身
  • 指针变量:存值的内存地址 属于引用类型

创建指针

指针创建有三种方法

1.先定义对应的变量,再通过变量取得内存地址,创建指针

//
// 定义普通变量
aint := 1
// 定义指针变量
ptr := &aint

2.先创建指针,分配好内存后,再给指针指向的内存地址写入对应的值。

// 创建指针
astr := new(string)
// 给指针赋值
*astr = "Go"

3.先声明一个指针变量,再从其他变量取得内存地址赋值给它

aint := 1
var bint *int  // 声明一个指针
bint = &aint   // 初始化

上面的三段代码中,指针的操作都离不开这两个符号:

  • & :从一个普通变量中取得内存地址
  • *:当*在赋值操作符(=)的右边,是从一个指针变量中取得变量的值,当*在赋值操作符(=)的左边,是指该指针指向的变量

流程控制

Go里的流程控制方法还是挺丰富,整理了下有如下这么多种:

  • if - else 条件语句
  • switch - case 选择语句
  • for - range 循环语句
  • goto 无条件跳转语句
  • defer 延迟执行

条件模型

if 条件 1 {
  分支 1
} else if 条件 2 {
  分支 2
} else if 条件 ... {
  分支 ...
} else {
  分支 else
}

这部分不用多说,会任意一门语言的都行

这里需要注意,go 的 if 后允许接收表达式 对返回的值进行判断 这是一个比较常用的写法

    if age := 20;age > 18 {
        fmt.Println("已经成年了")
    }

Swith-case

与其他语言一样

switch 表达式 {
    case 表达式1:
        代码块
    case 表达式2:
        代码块
    case 表达式3:
        代码块
    case 表达式4:
        代码块
    case 表达式5:
        代码块
    default:
        代码块
}

for

go 的循环只有 for 一种

for [condition |  ( init; condition; increment ) | Range]
{
   statement(s);
}

基于其模型,有 while 的等效 写法

a := 1
for a <= 5 {
    fmt.Println(a)
    a ++
}

常规 for 写法

    for i := 1; i <= 5; i++ {
        fmt.Println(i)
    }

无限循环写法

for {
    代码块
}

// 等价于
for ;; {
    代码块
}

【常用】集合遍历写法 for-range

遍历一个可迭代对象,是一个很常用的操作。在 Go 可以使用 for-range 的方式来实现

range 后可接数组、切片,字符串等

由于 range 会返回两个值:索引和数据,若你后面的代码用不到索引,需要使用 _ 表示

    myarr := [...]string{"world", "python", "go"}
    for _, item := range myarr {
        fmt.Printf("hello, %s\n", item)
    }

注意:如果你用一个变量来接收的话,接收到的是索引

go to 无条件跳转

goto 可以打破原有代码执行顺序,直接跳转到某一行执行代码

    goto flag
    fmt.Println("B")
flag:
    fmt.Println("A")

执行结果,并不会输出 B ,而只会输出 A

goto 语句通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能,类似 Java 的 break 和 continue 关键字

    i := 1
    for {
        if i > 5 {
            goto flag
        }
        fmt.Println(i)
        i++
    }
flag:

用 goto 实现 类型 continue的效果

    i := 1
flag:
    for i <= 10 {
        if i%2 == 1 {
            i++
            goto flag
        }
        fmt.Println(i)
        i++
    }

注意:goto语句与标签之间不能有变量声明,否则编译错误。

defer 延迟语句

这是 Go 独有的特性,它允许函数执行完毕后 执行 defer 再跳转,如我们的程序在函数中使用一些资源,最后关闭的时候可以使用 defer,比如归还数据库连接资源等

func myfunc() {
    fmt.Println("B")
}

func main() {
    defer myfunc()
    fmt.Println("A")
}

使用 defer 只是延时调用函数,此时传递给函数里的变量,不应该受到后续程序的影响。

比如这边的例子

import "fmt"

func main() {
    name := "go"
    defer fmt.Println(name) // 输出: go

    name = "python"
    fmt.Println(name)      // 输出: python
}

输出如下,可见给 name 重新赋值为 python,后续调用 defer 的时候,仍然使用未重新赋值的变量值,就好在 defer 这里,给所有的这是做了一个快照一样。

python
go

如果 defer 后面跟的是匿名函数,情况会有所不同, defer 会取到最后的变量值

package main

import "fmt"


func main() {
    name := "go"
    defer func(){
    fmt.Println(name) // 输出: python
}()
    name = "python"
    fmt.Println(name)      // 输出: python
}

注意: defer 是return 后才调用的

异常机制

编程语言一般都会有异常捕获机制,在 Java 中 是使用throw / catch 和 try 语句来实现的异常抛出和异常捕获的

在 Golang 中,有不少常规错误,在编译阶段就能提前告警,比如语法错误或类型错误等,但是有些错误仅能在程序运行后才能发生,比如数组访问越界、空指针引用等,这些运行时错误会引起程序退出。

当然能触发程序宕机退出的,也可以是我们自己,比如经过检查判断,当前环境无法达到我们程序进行的预期条件时(比如一个服务指定监听端口被其他程序占用),可以手动触发 panic,让程序退出停止运行。

手动触发宕机,是非常简单的一件事,只需要调用 panic 这个内置函数即可,就像这样子

func main() {
    panic("crash")
}

发生了异常,有时候就得捕获

这就不得不引出另外一个内建函数 – recover,它可以让程序在发生宕机后起生回生。

但是 recover 的使用,有一个条件,就是它必须在 defer 函数中才能生效,其他作用域下,它是不工作的。

这是一个简单的例子:

import "fmt"

func set_data(x int) {
    defer func() {
        // recover() 可以将捕获到的panic信息打印
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()

    // 故意制造数组越界,触发 panic
    var arr [10]int
    arr[x] = 88
}

func main() {
    set_data(20)

    // 如果能执行到这句,说明panic被捕获了
    // 后续的程序能继续运行
    fmt.Println("everything is ok")
}

注意:无法跨协程:即使 panic 会导致整个程序退出,但在退出前,若有 defer 延迟函数,还是得执行完 defer 。

但是这个 defer 在多个协程之间是没有效果,在子协程里触发 panic,只能触发自己协程内的 defer,而不能调用 main 协程里的 defer 函数的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值