Golang基础复习

558 阅读20分钟

Golang基础复习

1. 主要特征

1.1 特征

  1. 垃圾回收
  2. 丰富的内置类型
  3. 多返回值
  4. 匿名函数
  5. 闭包
  6. OOP
  7. 并发编程
  8. 反射
  9. 错误处理

1.1.2 命名规则

  • 首字母可以是不能是数组
  • 可以由Unicode字符、下划线、数字构成
  • 长度不限
  • 严格固定首字母大小写,有不同含义

1.1.3 Go项目

一个Golang工程中主要包含三个目录:

在GOPATH目录下

  • bin:编译后的二进制文件
  • src:源代码文件
  • pkg:包文件

1.2. 丰富的内置类型

1.2.1 普通类型

  • bool
  • int(32 or 64), int8, int16, int32, int64
  • uint(32 or 64), uint8(byte), uint16, uint32, uint64
  • float32, float64
  • string
  • complex64, complex128
  • array

官方文档:int is a signed integer type that is at least 32 bits in size. It is a distinct type, however, and not an alias for, say, int32.

int整形最少占32位,int和int32是两码事


complex:复数类型

可以存储两个数值:real、image

实数部分和虚数部分都是[float32]类型

可以通过**real(complex)拿到real值,同理可以通过imag(complex)**拿到image值

例如:

func main() {
   c := complex(11.11, 22.22)
   fmt.Printf("%f\n%f", real(c), imag(c))
}

输出为:

11.110000 22.220000


array为固定长度


1.2.2 引用类型

  • slice
  • map
  • chan

1.3 内置接口error

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
// 表示错误条件,nil值表示没有错误。
type error interface {
    Error() string
}

只要实现了这个方法,就是实现了error

可以自定义error

例如:

// 定义一个类
type Demo struct {
}

// 这个类实现Error方法,也就是实现error接口
func (d *Demo) Error() string {
   return "自定义错误"
}

func main() {

   err := testErr()
   fmt.Println(err)
}

// 模拟一个错误
func testErr() error {
   return &Demo{}
}

此时打印:自定义错误


1.4. 函数

1.4.1 init函数

  • init函数用于包(package)的初始化,该函数是go语言的一个重要特性
  • 每个包都可以有多个init()
  • 包里每个源文件也可以有多个init()
  • 多个init()执行顺序没有明确的规定
  • 不同包的init()按照包导入的依赖关系决定执行顺序
  • init()不能被其他函数调用
  • 在main()执行前自动被调用

1.4.2 main函数

Go语言程序的默认入口函数(主函数)


  • 两个函数在定义时不能有任何的参数和返回值
  • Go程序自动调用
  • init函数可以应用于任意包中,且可以重复定义多个
  • main函数只能用于main包中,且只能定义一个

1.5 Golang命令

  • go env用于打印Go语言的环境信息。
  • go run命令可以编译并运行命令源码文件。
  • go get可以根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装。
  • go build命令用于编译我们指定的源码文件或代码包以及它们的依赖包。
  • go install用于编译并安装指定的代码包及它们的依赖包。
  • go clean命令会删除掉执行其它命令时产生的一些文件和目录。
  • go doc命令可以打印附于Go语言程序实体上的文档。我们可以通过把程序实体的标识符作为该命令的参数来达到查看其文档的目的。
  • go test命令用于对Go语言编写的程序进行测试。
  • go list命令的作用是列出指定的代码包的信息。
  • go fix会把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码。
  • go vet是一个用于检查Go语言源码中静态错误的简单工具。
  • go tool pprof命令来交互式的访问概要文件的内容。

1.6下划线

“_”是特殊标识符,用来忽略结果。

1.6.1 import中

​ 有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。这个时候就可以使用 import 引用该包

​ 即使用 import _ 包路径 引用该包,仅仅是为了调用init()函数,所以无法通过包名来调用包中的其他函数。

1.6.2 接收返回值中

  • 占位符,意思是那个位置本应赋给某个值,但是咱们不需要这个值。该值赋给下划线,意思是丢掉不要。
  • 任何类型的单个值都可以丢给下划线。
  • 方法返回两个结果,而你只想要一个结果。那另一个就用 "_" 占位,而如果用变量的话,不使用,编译器是会报错的

1.7 特殊基本类型

  • rune:当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。 Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾

1.8 数组

Golang的数组和以往的很多数组有点不大相同

1.8.1 数组的特征

  1. 数组:是同一种数据类型的固定长度的序列。
  2. 数组定义:var a [len]int,比如:var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变
  3. 长度是数组类型的一部分,因此,var a [5]int和var a [10]int不同的类型。
  4. 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
  5. 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
  6. 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
  7. 支持 "=="、"!=" 操作符,因为内存总是被初始化过的。
  8. **指针数组 [n]T,数组指针 [n]T。
  9. 内置函数 len 和 cap 在数组中都返回数组长度 (元素数量)。

1.8.2 数组的创建方式

  • 一维数组:

    全局:
        var arr0 [5]int = [5]int{1, 2, 3}
        var arr1 = [5]int{1, 2, 3, 4, 5}
        var arr2 = [...]int{1, 2, 3, 4, 5, 6}
        var str = [5]string{3: "hello world", 4: "tom"}
    
    局部:
        a := [3]int{1, 2}           // 未初始化元素值为 0。
        b := [...]int{1, 2, 3, 4}   // 通过初始化值确定数组长度。
        c := [5]int{2: 100, 4: 200} // 使用索引号初始化元素。
    
  • 多维数组:

    全局
        var arr0 [5][3]int
        var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
    局部:
        a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
        b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 除了第一维,其他不能用 "..."。
    

1.9 slice

slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案

1.9.1 特征

  1. 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体值拷贝传递
  2. 切片的长度可以改变,因此,切片是一个可变的数组。
  3. 切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制。
  4. cap可以求出slice最大扩张容量,不能超出数组限制。
  5. 切片的定义:var 变量名 []类型,比如 var str []string var arr []int。
  6. 如果 slice == nil,那么 len、cap 结果都等于 0

1.9.2 创建方式

   //1.声明切片
   var s1 []int
   // 2.:=
   s2 := []int{}
   // 3.make()
   var s3 []int = make([]int, 0)
   // 4.初始化赋值
   var s4 []int = make([]int, 0, 0)
   s5 := []int{1, 2, 3}
   // 5.从数组切片
   arr := [5]int{1, 2, 3, 4, 5}
   var s6 []int
   // 前包后不包
   s6 = arr[1:4]
   fmt.Println(s6)

1.9.3 初始化

全局:
// 声明一个数组
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// 切片操作
// 从start到end所有元素:len=end-start
var slice0 []int = arr[start:end]
// 从0到end所有元素:len=end
var slice1 []int = arr[:end]
// 从start到len-1所有元素
var slice2 []int = arr[start:]
// 所有元素
var slice3 []int = arr[:]

局部:
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[start:end]
slice6 := arr[:end]        
slice7 := arr[start:]     
slice8 := arr[:]

1.9.4 超出原 slice.cap 限制

通常以 2 倍容量重新分配底层数组。

1.9.5 字符串和切片

string底层就是一个byte的数组,因此,也可以进行切片操作。

string本身是不可变的:

str := "hello"
// 这样错误
str[1] = 'a'

// 需要这样
//中文字符需要用[]rune(str)
s := []byte(str)
// 此时可以修改
s[1] = 'a'
// 也可以追加
s = append(s, 'a')
// 也可以切片截取
s = s[1:]
// 转回string给str
str = string(s)
fmt.Println(str)

1.10 指针

Go语言中的指针不能进行偏移和运算,是安全指针。

1.10.1 &(取地址)和 *(根据地址取值)

  • 每个变量在运行时都拥有一个地址,使用&字符放在变量前面对变量进行“取地址”操作
  • 获得这个变量的指针后,可以对指针使用*操作
  • 空指针:当一个指针被定义后没有分配到任何变量时,它的值为 nil

1.10.2 初始化

  • 对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间
  • 对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。

1.10.3 new和make两个函数主要用来分配内存

  • // new()接受一个类,返回一个对象指针
    func new(Type) *Typ
    
    • 该返回的指针对应的值为该类型的零值
    • 声明了一个指针变量但是没有初始化,使用new()进行初始化之后就可以正常赋值了
  • // 接受一个类型,一个整形参数(定义大小)
    func make(t Type, size ...IntegerType) Type
    
    • 只用于slice、map以及chan的内存创建
    • 返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型本身就是引用类型
  • new和make的异同:

    1. 二者都是用来做内存分配的。

    2. make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身

    3. 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针


1.11 map

map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型必须初始化才能使用。

1.11.1 定义

// KeyType:表示键的类型
// ValueType:表示键对应的值的类型。
map[KeyType]ValueType

// 变量默认初始值为nil,需要使用make()函数来分配内存
// cap表示map的容量,该参数不是必须的
make(map[KeyType]ValueType, [cap])

1.11.2 判断key是否存在

value, ok := map[key]
// 如果key存在:		ok为true,v为对应的值
// 如果key不存在:	ok为false,v为值类型的零值

1.11.3 map的遍历

使用for range遍历map

func main() {
	usermap := make(map[int]string)
	usermap[1] = "user01"
	usermap[2] = "user02"
	usermap[3] = "user03"

	for key, value := range usermap {
		fmt.Println(key, "|", value)
	}
}

1.11.4 delete()删除键值对

从map中删除一组键值对

delete()函数的格式:delete(map,key)


1.12 结构体

Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。

Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。

1.12.1 type

自定义类型和起别名

// 自定义类型
type myType Type
// 起别名
type typeAlias = type

// rune和byte就是类型别名
type byte = uint8
type rune = int32

区别:

一个是新的类型:自定义类型

一个是老的类型,新的名字:起别名


1.12.2 struct

(1) 使用type和struct关键字组合来定义结构体

/*
  	1.类型名:标识自定义结构体的名称,在同一个包内不能重复。
    2.字段名:表示结构体字段名。结构体中的字段名必须唯一。
    3.字段类型:表示结构体字段的具体类型。
*/

type 类名 struct {
    字段名 字段类型
    字段名 字段类型
    …
}

(2) 结构体实例化

结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。

var 结构体实例 结构体类型

(3) 匿名结构体

var 结构体实例 struct{
    字段名 字段类型
    字段名 字段类型
    …
}

(4) 使用new对结构体进行实例化

// 得到的是结构体的地址
var 指针实例 = (结构体)

(5) 取结构体地址实例化

使用**&对结构体进行取地址操作相当于对该结构体类型进行了一次new**实例化操作。

指针实例 := &结构体{}

(6) 结构体初始化

如果不赋值,只是声明会自动初始化零值

type User struct {
   id       int
   username string
}

func main() {
   var u User

   // 输出:{0 }
   fmt.Println(u)
}

(7) 结构体的匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。

(8) 结构体的内存分布规律如下:

type test struct {
   a int8
   b int8
   c int8
   d int8
}

func main() {
   n := test{
      1, 2, 3, 4,
   }
   
   fmt.Printf("n.a %p\n", &n.a)
   fmt.Printf("n.b %p\n", &n.b)
   fmt.Printf("n.c %p\n", &n.c)
   fmt.Printf("n.d %p\n", &n.d)
   
    // 打印如下
    // 是连续的
    n.a 0xc00000a0a8
    n.b 0xc00000a0a9
    n.c 0xc00000a0aa
    n.d 0xc00000a0ab
    
}

1.12.3 struct中的成员方法

// 接收者类似其他语言的this或者self
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
        函数体
}


// 使用指针接收者
// 由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。
func (接收者变量 接收者类型指针) 方法名(参数列表)(返回参数) {
    函数体
}

// 同上,但是使用值类型接收者时
// Go语言会在代码运行时将接收者的值复制一份。
// 在值类型接收者的方法中可以获取接收者的成员值
// 但修改操作只是针对副本,无法修改接收者变量本身。

// 此时得到结论:
// 在以下情况,使用指针类型会好一些	
1.需要修改接收者中的值
2.接收者是拷贝代价比较大的大对象
3.保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

1.12.4 嵌套结构体

一个结构体中可以嵌套包含另一个结构体或结构体指针作为成员变量。

type A struct {
   a int8
   b int8
}

type B struct {
   c int8
   a A
}

// 此时也可以匿名嵌套
type C struct {
    // 匿名结构体
	B
}

// 当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。

1.12.5 字段冲突

当嵌套结构体字段名冲突,这个时候为了避免歧义需要指定具体的内嵌结构体的字段。

type A struct {
   a int8
}

type B struct {
   a int8
}

type C struct {
   a A
   b B
}

func main() {
   var c C
   c.a.a = 2
   fmt.Println(c.a.a)
}

1.12.6 继承

结构体嵌套也可以实现其他语言的继承

type A struct {
}

type B struct {
   *A
}

func (a *A) methodA() {
   fmt.Println("A方法")
}

func main() {
   var b B

   // 可以成功调用
   b.methodA()
}

1.12.7 结构体的可见性

字段和类:首字母大写为public,小写为private

1.12.8 Json序列化和反序列化

type user struct {
   Id string
}

func main() {
   a := user{"001"}

   // 序列化 
   data, _ := json.Marshal(a)
   fmt.Printf("json => %s", data)
    
   	s := string(data)
	var u user
	// 反序列化
    // 参数为:json字符串的[]byte,一个接受的对象
    json.Unmarshal([]byte(s), &u)
	fmt.Println()
	fmt.Printf("%v", u)
}

1.12.9 tag

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。

Tag在结构体字段的后方定义,由一对反引号包裹起来

例如:

type user struct {
    // 结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。
    // 键值对之间使用一个空格分隔。 
    // 注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。
    // 结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。
    // 例如不要在key和value之间添加空格。
   Id string `json:"id"`
}

2.分支结构

这里很多语言都很类似,我们只看Golang特殊的部分

2.1 Type Switch

在Golang中默认Switch每个case最后带有break

可以使用fallthrough强制执行后面的case代码

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

switch x.(type){
    case type:
       statement(s)      
    case type:
       statement(s)
    /* 你可以定义任意个数的case */
    default: /* 可选 */
       statement(s)
}

例如:

func main() {
    var x interface{}
    
    switch i := x.(type) {
    case nil:
        fmt.Printf(" x 的类型 :%T\r\n", i)
    case int:
        fmt.Printf("x 是 int 型")
    case float64:
        fmt.Printf("x 是 float64 型")
    case func(int) float64:
        fmt.Printf("x 是 func(int) 型")
    case bool, string:
        fmt.Printf("x 是 bool 或 string 型")
    default:
        fmt.Printf("未知型")
    }
}

2.2 select 语句

  • 语法类似与switch,但是每个case都必须是一个通信
  • 所有channel表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通信可以进行,它就执行;其他被忽略。
  • 如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。
  • 否则:
  • 如果有default子句,则执行该语句。
  • 如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
  • 自我感觉就像一把锁,用来管理channel的并发

2.2.1 基本使用

  • 如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行。

  • 如果没有可运行的case语句,且有default语句,那么就会执行default的动作。

    func main() {
       var a chan int
       select {
       case <-a:
          fmt.Println("a收到")
       default:
          fmt.Printf("阻塞\n")
       }
    }
    
  • 如果没有可运行的case语句,且没有default语句,select将阻塞,直到某个case通信可以运行

2.3 for

Golang只有for,去掉了while和do while

// 语法
for 初始语句; 条件表达式; 结束语句 {
		
}

// 例如
for i := 0; i < 5; i++ {
   
}


// 也可以像while一样
i := 0
for i < 5 {
    i++
}

// 也可以死循环
for {
    
}

2.4 for range

可以使用for range遍历array、slice、string、map、channel

for range遍历的规律:

  1. array、slice、string返回索引和值
  2. map返回key和value
  3. channel只能返回通道内的值

实例

// map的实例,其他的可以推敲出来
m := make(map[int]string)
m[1] = "user01"
m[2] = "user02"

for k, v := range m {
    fmt.Println(k, "|", v)
}

2.5 break

  • 在for中和其他语言类似,用来结束当前的代码块
  • 上面提到:在select中和switch中自带break
  • switch中可以设置fallthough进行穿透
  • 在有fallthough的情况下,使用break可以终止掉fallthough后面的case
  • 带标签的break可以跳出多层select、switch的作用域,不用一层一层跳

2.6 goto

代码间无条件跳转不建议使用


3. 函数

3.1 特点

  • 无需声明原型。
  • 支持不定变参。
  • 支持多返回值。
  • 支持命名返回参数。
  • 支持匿名函数和闭包。
  • 函数也是一种类型,一个函数可以赋值给变量。
  • 不支持 嵌套 (nested) 一个包不能有两个名字一样的函数。
  • 不支持 重载 (overload)。
  • 不支持 默认参数 (default parameter)。

3.2 函数声明

// 单返回值
func 函数名(参数列表) 返回值 {
   
}

func 函数名(参数列表) (返回值列表) {
   
}

3.3 函数参数

和其他语言类似

分为:值传递和引用传递

值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

注意点:

  1. 无论是值传递,还是引用传递,传递给函数的都是变量的副本
  2. 值传递是值的拷贝。
  3. 引用传递是地址的拷贝,
  4. 一般来说,地址拷贝更为高效。
  5. 而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。
  6. map、slice、chan、指针、interface默认以引用的方式传递。
  7. 不定参数传值 就是函数的参数不是固定的,后面的类型是固定的。
  8. Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。
  9. 在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可。
  10. interface{}传递任意类型数据
  11. 而且interface{}是类型安全的
//0个或多个参数
func myfunc01(args ...interface{}) {
}

//1个或多个参数
func myfunc02(a int, args ...interface{}) {
}

//2个或多个参数
func myfunc03(a int, b int, args ...interface{}) {
}

// 使用时
func main() {
	// 普通的类型
	var x int
	// slice
	nums := []int{1, 2, 3}

	myfunc01(x, x, x, x, x, x, x, x, x)
	myfunc02(1, nums...)
}

// 其中args是一个slice
// 我们可以通过arg[index]依次访问所有参数
// 通过len(arg)来判断传递参数的个数.

3.4 返回值

"_"标识符,用来忽略函数的某个返回值

Go 的返回值可以被命名,并且就像在函数体开头声明的变量那样使用。

返回值的名称应当具有一定的意义,可以作为文档使用。

没有参数的 return 语句返回各个返回变量的当前值。这种用法被称作“裸”返回。

func main() {
    // 使用_可以忽略b的返回
	a, _ := method()
	fmt.Println(a)
}

// 双返回值
// 可以被命名
func method() (a int,b string) {
	// 可以直接返回
	//return 1, "test"

	// 也可以赋值后return空
	a = 1
	b = "test"
	return
    // 直接返回语句在长的函数中它们会影响代码的可读性。
}

3.5 匿名函数

匿名函数是指不需要定义函数名的一种函数实现方式

在Go里面,函数可以像普通变量一样被传递或使用,

Go语言支持随时在代码里定义匿名函数。

匿名函数由一个不带函数名的函数声明和函数体组成。

匿名函数的优越性在于可以直接使用函数内的变量,不必声明。

例如

// 可以赋值
a := func(num int) int {
    fmt.Println(num)
    return num
}

// 可以调用
// 可以传参
// 可以有返回值
num := a(1)
fmt.Println(num)

3.6 闭包

闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。

func main() {
    // 变量c实际上是指向了函数b()
    c := a()
    // 再执行函数c()后就会显示i的值
    // 因为 i++, 第一次为1,第二次为2,第三次为3,以此类推
    c()
    c()
    c()

    a()
}

func a() func() int {
    i := 0
    // 函数b嵌套在函数a内部
    b := func() int {
        i++
        fmt.Println(i)
        return i
    }
    // 函数a返回函数b 
    return b
}

// 因为变量c引用了函数a()内的函数b()

// 当内部函数b()被函数a()外的一个变量引用的时候,就创建了一个闭包。

// 由于闭包的存在使得函数a()返回后,a中的i始终存在,这样每次执行c(),i都是自加1后的值

3.7 defer

  1. 关键字 defer 用于注册延迟调用。
  2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
  3. 滥用 defer 可能会导致性能问题,尤其是在一个 "大循环" 里。
  4. go 语言的defer功能强大,对于资源管理非常方便,但是如果没用好,也会有陷阱。
  5. defer 是先进后出
  6. 这个很自然,后面的语句会依赖前面的资源,因此如果先前面的资源先释放了,后面的语句就没法执行了。
  7. defer语句中的变量,在defer声明时就决定了。
defer fmt.Println("3")
defer func(x int) {
    fmt.Println(1/x)
}(0)
defer fmt.Println("1")

// 此时输出是
// 1
// 3
// panic: runtime error: integer divide by zero

// 先进后出
// 某个延迟调用发生错误,后面的调用依旧会被执行。

4. 异常

4.1 异常处理

Golang 没有结构化异常,使用 panic 抛出错误,recover 捕获错误。

异常的使用场景简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。

4.2 panic

  1. 内置函数
  2. 假如函数中写了panic语句,会终止其后要执行的代码
  3. 返回函数的调用者中,调用函数语句之后的代码不会执行
  4. 假如函数中存在要执行的defer函数列表,按照defer的逆序执行
  5. 直到goroutine整个退出,并报告错误

4.3 recover()

会捕获异常,返回any

func main() {
   	test()
    // test()捕获后会被执行
    // 没用捕获后不会被执行
    fmt.Println("调用方法后的方法")
}

func test() {
    defer fmt.Println("1")
   	defer func() {
        // 捕获异常
      	if r := recover(); r != nil {
      	   fmt.Println("异常为 => ", r)
     	 }
  	 }()
    defer fmt.Println("2")
    
   	
   	panic("error")
   	// 后面的操作不会被执行
    fmt.Println("panic后面的操作")
}

// 两个额外的defer都会被执行

/* 输出结果为:
2
异常为 =>  error
1
调用方法后的方法
*/

5. 测试

Go语言中的测试依赖go test命令

所有以_test.go为后缀名的源代码文件都是go test测试的一部分

不会被go build编译到最终的可执行文件中。

*_test.go文件中有三种类型的函数:单元测试函数、基准测试函数和示例函数。

5.1 单元测试

单元测试对文件名和方法名,参数都有很严格的要求。

  1. 文件名必须以xx_test.go命名
  2. 方法必须是Test[A-Z]开头
  3. 方法参数必须 t *testing.T
  4. 使用go test执行单元测试

6. 接口

oop的其他的在struct写过些,这里只写接口

在Go语言中接口(interface)是一种类型一种抽象的类型

Golang和其他语言类似:

接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。

6.1 接口的定义

type 接口类型名 interface{
    // 当方法名首字母是大写且这个接口类型名首字母也是大写时
	// 这个方法可以被接口所在的包(package)之外的代码访问。
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    …
}

6.2 接口的实现

一个对象只要全部实现了接口中的方法,那么就实现了这个接口

就可以用这个接口类型进行多态操作了

一个类型可以同时实现多个接口

6.3 空接口

空接口是指没有定义任何方法的接口。因此任何类型 都实现了空接口。

空接口类型的变量可以存储任意类型的变量

个人认为类似java中的object

// 定义一个空接口obj
var obj interface{}

空接口可以存储任意类型的值,那我们如果或者空接口中的值呢

使用类型断言就可以获得存储的值了


7. 并发编程

进程和线程的简单介绍

我们都知道计算机的核心是CPU,它承担了所有的计算任务,而操作系统是计算机的管理者,它负责任务的调度,资源的分配和管理,

统领整个计算机硬件;应用程序是具有某种功能的程序,程序是运行于操作系统之上的。

进程:

是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个 独立 单位,是应用

程序运行的载体。

线程:

每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离

线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。

一个进程可以有一个或多个线程各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。

并行与并发

因为CPU的一个核在某个时只能进行一次处理,如果一个时间交替执行多个处理,那么就是并发

因为CPU的一个核在某个时只能进行一次处理,在同一时,我们CPU有多个核,那么我们就能同一个时刻,执行多条指令,那么这就叫并行

我们Golang中用了一个新的概念:协程

协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。

每个4~5KB的栈内存占用和由于实现机制,而大幅减少的创建和销毁开销,是go高并发的根本原因。

7.1 Goroutine


未完


8. socket网络编程

网络技术类的就不多讲了

Socket是应用层与TCP/IP协议族通信的中间软件抽象层

它把复杂的TCP/IP协议族隐藏在Socket后面

对用户来说只需要调用Socket规定的相关函数,让Socket去组织符合指定的协议数据然后进行通信

7.1 TCP编程

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流传输层通信协议,由IETF的RFC 793 定义。

TCP旨在适应支持多网络应用的分层协议层次结构。

连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。

TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。

原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。

在Golang中,提供了很多的对应的api

我们的操作一般分为服务端和客户端

7.1.1 服务端

服务端一般的工作

  1. 监听端口
  2. 接收客户端请求建立链接
  3. 创建goroutine处理链接。