Chiriri's blog Chiriri's blog
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)

Iekr

苦逼后端开发
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)
  • JavaSE

  • JavaEE

  • Linux

  • MySQL

  • NoSQL

  • Python

  • Python模块

  • 机器学习

  • 设计模式

  • 传智健康

  • 畅购商城

  • 博客项目

  • JVM

  • JUC

  • Golang

    • Golang

      • Go 基础
        • Go的安装
        • 配置Go代理
        • 第一个Go程序
        • 变量
        • 常量
        • 数据类型
          • 类型转换
          • 类型别名
        • fmt 输入输出
        • 流程控制
          • 条件语句if
          • 条件语句switch
          • 循环语句for
          • goto
        • 数组
        • 切片Slice
        • Map
        • Range
        • 函数
          • 不定参数列表
          • 函数类型
          • 匿名函数与闭包
        • 指针
          • new 函数
          • 指针做参数
        • 结构体
          • 结构体方法
        • 异常处理
          • panic
          • recover
        • 字符串
          • 字符串操作
          • 字符串格式化
        • JSON
        • 文件操作
        • Time
        • Strconv
        • 进程信息
        • 延迟调用(defer)
        • 面向对象
          • 匿名字段
          • 成员的操作
          • 同名字段
          • 方法
          • 基础类型作为接收者
          • 结构体作为接收者
          • 结构体指针作为接受者
          • 方法集
          • 匿名字段中方法的重写
          • 表达式
          • 接口
          • 接口实现
          • 接口组合
          • 嵌入
          • 转换
          • 空接口
          • 类型查询
      • Go 项目案例
      • Go 进阶
      • 性能调优
    • gRPC

  • Kubernetes

  • 硅谷课堂

  • C

  • 源码

  • 神领物流

  • RocketMQ

  • 短链平台

  • 后端
  • Golang
  • Golang
Iekr
2023-01-21
目录

Go 基础

# Go 基础

# Go 的安装

Go 官网 (opens new window)

# 配置 Go 代理

Goproxy.cn (opens new window)

使用 PowerShell 执行添加

$env:GO111MODULE = "on"
$env:GOPROXY = "https://goproxy.cn"
1
2

# 第一个 Go 程序

package main

import (
	"fmt"
)

func main() {
	fmt.Println("hello world")
}
1
2
3
4
5
6
7
8
9

package 必须为 main 否则无法执行主程序

go run 执行

go run main.go
1

go build 构建后执行

go build main.go
./main
1
2

# 变量

package main

import (
	"fmt"
	"math"
)

func main() {

	var a = "initial" //var为自动推导类型

	var b, c int = 1, 2 //也可以手动指定类型

	var d = true

	var e float64

	f := float32(e) //自动推导类型

	g := a + "foo" //拼接变量
	fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
	fmt.Println(g)                // initialfoo

	const s string = "constant" //常量
	const h = 500000000 //如果不指定类型,则通过上下文进行确定
	const i = 3e20 / h
	fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 常量

常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量

const (
    x = iota // x == 0
    y = iota // y == 1
    z = iota // z == 2
    w // 这里隐式地说 w = iota,因此 w == 3。其实上面 y 和 z 可同样不用"= iota"
)
1
2
3
4
5
6

# 数据类型

类型 名称 长度 说明
bool 布尔类型 1
byte 字节型 1 uint8 别名
rune 字符类型 4 专用于存储 unicode 编码,等价于 uint32
int, uint 整型 4 或 8
int8, uint8 整型 1
int16, uint16 整型 2
int32, uint32 整型 4
int64, uint64 整型 8
float32 浮点型 4 小数位精确到 7 位
float64 浮点型 8 小数位精确到 15 位
complex64 复数类型 8
complex128 复数类型 16
uintptr 整型 4 或 8 足以存储指针的 uint32 或 uint64 整数
string 字符串

# 类型转换

Go 语言中不允许隐式转换,所有类型转换必须显式声明,而且转换只能发生在两种相互兼容的类型之间。

var ch byte = 97
//var a int = ch //err, cannot use ch (type byte) as type int inassignment
var a int = int(ch)
1
2
3

# 类型别名

type bigint int64 //int64 类型改名为 bigint
var x bigint = 100

type (
    myint int //int 改名为 myint
    mystr string //string 改名为 mystr
)
1
2
3
4
5
6
7

# fmt 输入输出

格式 含义
%% 一个 % 字面量
%b 一个二进制整数值 (基数为 2),或者是一个 (高级的) 用科学计数法表示的指数为 2 的浮点数
%c 字符型。可以把输入的数字按照 ASCII 码相应转换为对应的字符
%d 一个十进制数值 (基数为 10)
%e 以科学记数法 e 表示的浮点数或者复数值
%E 以科学记数法 E 表示的浮点数或者复数值
%f 以标准记数法表示的浮点数或者复数值
%g 以 % e 或者 % f 表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%G 以 % E 或者 % f 表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%o 一个以八进制表示的数字 (基数为 8)
%p 以十六进制 (基数为 16) 表示的一个值的地址,前缀为 0x, 字母使用小写的 a-f 表示
%q 使用 Go 语法以及必须时使用转义,以双引号括起来的字符串或者字节切片 [] byte,或者是以单引号括起来的数字
%s 字符串。输出字符串中的字符直至字符串中的空字符(字符串以 '\0‘结尾,这个 '\0' 即空字符)
%t 以 true 或者 false 输出的布尔值
%T 使用 Go 语法输出的值的类型
%U 一个用 Unicode 表示法表示的整型码点,默认值为 4 个数字字符
%v 使用默认格式输出的内置或者自定义类型的值,或者是使用其类型的 String () 方式输出的自定义值,如果该方法存在的话
%x 以十六进制表示的整型值 (基数为十六),数字 a-f 使用小写表示
%X 以十六进制表示的整型值 (基数为十六),数字 A-F 使用小写表示
// 输出
f := 3.14
fmt.Printf("f = %f, %g\n", f, f) //f = 3.140000, 3.14
fmt.Printf("f type = %T\n", f) //f type = float64

//输入
var v int
fmt.Println("请输入一个整型:")
fmt.Scanf("%d", &v)
//fmt.Scan(&v)
fmt.Println("v = ", v)
1
2
3
4
5
6
7
8
9
10
11

# 流程控制

# 条件语句 if

可省略条件表达式括号

package main

import "fmt"

func main() {

	if 7%2 == 0 {
		fmt.Println("7 is even")
	} else {
		fmt.Println("7 is odd")
	}

	if 8%4 == 0 {
		fmt.Println("8 is divisible by 4")
	}

    //支持一个初始化表达式, 初始化字句和条件表达式直接需要用分号分隔
	if num := 9; num < 0 {
		fmt.Println(num, "is negative")
	} else if num < 10 {
		fmt.Println(num, "has 1 digit")
	} else {
		fmt.Println(num, "has multiple digits")
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 条件语句 switch

switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上直下逐一测试,直到匹配为止。 Golang switch 分支表达式可以是任意类型,不限于常量。可省略 break,默认自动终止。

package main

import (
	"fmt"
	"time"
)

func main() {

	a := 2
	switch a {
	case 1:
		fmt.Println("one")
	case 2:
		fmt.Println("two")
	case 3:
		fmt.Println("three")
	case 4, 5: //val1 和 val2 则可以是同类型的任意值,使用逗号进行分割
		fmt.Println("four or five")
	default:
		fmt.Println("other")
	}

	t := time.Now()
	switch { //省略条件表达式
	case t.Hour() < 12: //可以使用条件语句,优化多条if else
		fmt.Println("It's before noon")
	default:
		fmt.Println("It's after noon")
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

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

package main

import "fmt"

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("未知型")
    }
    //写法二
    var j = 0
    switch j {
    case 0:
    case 1:
        fmt.Println("1")
    case 2:
        fmt.Println("2")
    default:
        fmt.Println("def")
    }
    //写法三
    var k = 0
    switch k {
    case 0:
        println("fallthrough")
        fallthrough
        /*
            Go的switch非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;
            而如果switch没有表达式,它会匹配true。
            Go里面switch默认相当于每个case最后带有break,
            匹配成功后不会自动向下执行其他case,而是跳出整个switch,
            但是可以使用fallthrough强制执行后面的case代码。
        */
    case 1:
        fmt.Println("1")
    case 2:
        fmt.Println("2")
    default:
        fmt.Println("def")
    }
    //写法三
    var m = 0
    switch m {
    case 0, 1:
        fmt.Println("1")
    case 2:
        fmt.Println("2")
    default:
        fmt.Println("def")
    }
    //写法四
    var n = 0
    switch { //省略条件表达式,可当 if...else if...else
    case n > 0 && n < 10:
        fmt.Println("i > 0 and i < 10")
    case n > 10 && n < 20:
        fmt.Println("i > 10 and i < 20")
    default:
        fmt.Println("def")
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

# 循环语句 for

package main

import "fmt"

func main() {

	i := 1
	for { //相当于for (;;),替代 while (true)
		fmt.Println("loop")
		break
	}
	for j := 7; j < 9; j++ {
		fmt.Println(j)
	}

	for n := 0; n < 5; n++ {
		if n%2 == 0 {
			continue //同样支持break和continue
		}
		fmt.Println(n)
	}
	for i <= 3 { //相当于for (; i <= 3;),替代 while (i <= 3)
		fmt.Println(i)
		i = i + 1
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# goto

用 goto 跳转到必须在当前函数内定义的标签

func main() {
    for i := 0; i < 5; i++ {
    	for {
            fmt.Println(i)
            goto LABEL //跳转到标签 LABEL,从标签处,执行代码
        }
    }
	fmt.Println("this is test")
LABEL:
	fmt.Println("it is over")
}
1
2
3
4
5
6
7
8
9
10
11

# 数组

  • 访问越界,如果下标在数组合法范围之外,则触发访问越界,会 panic
  • 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
  • 指针数组 [n]*T,数组指针 *[n] T。
package main

import "fmt"

func main() {

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

	b := [5]int{1, 2, 3, 4, 5}
    c := [...]int{1, 2, 3, 4}  // 通过初始化值确定数组长度。
    d := [5]int{2: 100, 4: 200} // 使用引号初始化指定下标的元素。
    e := [...]struct {
        name string
        age  uint8
    }{
        {"user1", 10}, // 可省略元素类型。
        {"user2", 20}, // 别忘了最后一行的逗号。
    } //结构体数组
	fmt.Println(b)
    fmt.Println(c)

	var twoD [2][3]int
	for i := 0; i < 2; i++ {
		for j := 0; j < 3; j++ {
			twoD[i][j] = i + j
		}
	}
	fmt.Println("2d: ", twoD)
    
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 切片 Slice

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

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

import "fmt"

func main() {

	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

	s = append(s, "d") //追加后需要赋值回原切片才能修改原切片,因为如果容量不够时会进行自动扩容变成一个新的切片
	s = append(s, "e", "f")
	fmt.Println(s) // [a b c d e f]

	c := make([]string, len(s)) //指定长度
    d := make([]string, len(s), 10) //指定长度和10的容量
	copy(c, s) //可以使用copy进行两个切片元素复制
	fmt.Println(c) // [a b c d e f]

	fmt.Println(s[2:5]) // [c d e] 前闭后开,不支持Python中的负下标
	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]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# Map

map 类型的变量默认初始值为 nil,需要使用 make () 函数来分配内存。

package main

import "fmt"

func main() {
	m := make(map[string]int) //其中[string]为key的类型 int为value的类型
    m2 := make(map[string]int, 3) //指定map的cap容量 3
	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

	r, ok := m["unknow"] //获取map中的元素 ok则为map是否存在指定的key
	fmt.Println(r, ok) // 0 false

	delete(m, "one") //删除指定key

	m2 := map[string]int{"one": 1, "two": 2}
	var m3 = map[string]int{"one": 1, "two": 2}
	fmt.Println(m2, m3)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# Range

使用 Range 快速遍历数组或切片

package main

import "fmt"

func main() {
	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

	m := map[string]string{"a": "A", "b": "B"}
	for k, v := range m { //返回 key和value
		fmt.Println(k, v) // b 8; a A
	}
    
	for k := range m { //只遍历key
		fmt.Println("key", k) // key a; key b
	}
    
    for i := range s { //支持 string/array/slice/map。
   		fmt.Printf("%c\n", s[i])
    }
    
    for _, c := range s { // 忽略 index
    	fmt.Printf("%c\n", c)
    }
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 函数

语法

func FuncName(/*参数列表*/) (o1 type1, o2 type2/*返回类型*/) {
	//函数体
    
	return v1, v2 //返回多个值
}
1
2
3
4
5
  • func:函数由关键字 func 开始声明
  • FuncName:函数名称,根据约定,函数名首字母小写即为 private,大写即为 public
  • 参数列表:函数可以有 0 个或多个参数,参数格式为:变量名 类型,如果有多个参数通过逗号分隔,不支持默认参数
  • 返回类型:
    • 上面返回值声明了两个变量名 o1 和 o2 (命名返回参数),这个不是必须,可以只有类型没有变量名
    • 如果只有一个返回值且不声明返回值变量,那么你可以省略,包括返回值的括号
    • 如果没有返回值,那么就直接省略最后的返回信息
    • 如果有返回值, 那么必须在函数的内部添加 return 语句
package main

import "fmt"

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
}

func Test01() (int, string) { //方式 2
	return 250, "sb"
}

func Test02() (a int, str string) { //方式 3, 给返回值命名
    a = 250
    str = "sb"
    return
}

func main() {
	res := add(1, 2)
	fmt.Println(res) // 3

	v, ok := exists(map[string]string{"a": "A"}, "a")
	fmt.Println(v, ok) // A True
    
    v1, v2 := Test01() //函数调用
    _, v3 := Test02() //函数调用, 第一个返回值丢弃
    v4, _ := Test02() //函数调用, 第二个返回值丢弃
    fmt.Printf("v1 = %d, v2 = %s, v3 = %s, v4 = %d\n", v1, v2, v3, v4)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 不定参数列表

不定参数是指函数传入的参数个数为不定数量。为了做到这点,首先需要将函数定义为接受

//形如...type 格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数
func Test(args ...int) {
    for _, n := range args { //遍历参数列表
    	fmt.Println(n)
    }
}

func main() {
    //函数调用,可传 0 到多个参数
    Test()
    Test(1)
    Test(1, 2, 3, 4)
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 函数类型

函数也是一种数据类型,我们可以通过 type 来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种

类型。

type FuncType func(int, int) int //声明一个函数类型, func 后面没有函数名

//函数中有一个参数类型为函数类型:f FuncType
func Calc(a, b int, f FuncType) (result int) {
    result = f(a, b) //通过调用 f()实现任务
    return
}

func Add(a, b int) int {
	return a + b
}
func Minus(a, b int) int {
	return a - b
}
func main() {
    //函数调用,第三个参数为函数名字,此函数的参数,返回值必须和 FuncType 类型一致
    result := Calc(1, 1, Add)
    fmt.Println(result) //2
    var f FuncType = Minus
    fmt.Println("result = ", f(10, 2)) //result = 8
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 匿名函数与闭包

所谓闭包就是一个函数 “捕获” 了和它在同一作用域的其它常量和变量。这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量。它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用它,这些变量就还会存在。

在 Go 语言里,所有的匿名函数 (Go 语言规范中称之为函数字面量) 都是闭包。

func main() {
	i := 0
	str := "mike"
    
	//方式 1
	f1 := func() { //匿名函数,无参无返回值
		//引用到函数外的变量
		fmt.Printf("方式 1:i = %d, str = %s\n", i, str)
	}
	f1() //函数调用
    
	//方式 1 的另一种方式
	type FuncType func() //声明函数类型, 无参无返回值
	var f2 FuncType = f1
	f2() //函数调用
    
	//方式 2
	var f3 FuncType = func() {
		fmt.Printf("方式 2:i = %d, str = %s\n", i, str)
	}
	f3() //函数调用
    
	//方式 3
	func() { //匿名函数,无参无返回值
		fmt.Printf("方式 3:i = %d, str = %s\n", i, str)
	}() //别忘了后面的(), ()的作用是,此处直接调用此匿名函数
    
	//方式 4, 匿名函数,有参有返回值
	v := func(a, b int) (result int) {
		result = a + b
		return
	}(1, 1) //别忘了后面的(1, 1), (1, 1)的作用是,此处直接调用此匿名函数, 并传参
	fmt.Println("v = ", v)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

闭包

func main() {
	i := 10
	str := "mike"
	func() {
		i = 100
		str = "go"
		//内部:i = 100, str = go
		fmt.Printf("内部:i = %d, str = %s\n", i, str)
	}() //别忘了后面的(), ()的作用是,此处直接调用此匿名函数
	//外部:i = 100, str = go
	fmt.Printf("外部:i = %d, str = %s\n", i, str)
}
1
2
3
4
5
6
7
8
9
10
11
12

# 指针

Go 语言虽然保留了指针,但与其它编程语言不同的是:

  • 默认值 nil,没有 NULL 常量
  • 不支持指针运算,不支持 "->" 运算符,直接⽤ "." 访问目标成员
package main

import "fmt"

func add2(n int) {
	n += 2
}

//传递指针地址
func add2ptr(n *int) {
    //解析指针地址并修改
	*n += 2
}

func main() {
	n := 5
	add2(n)
	fmt.Println(n) // 5
	add2ptr(&n) //取地址
	fmt.Println(n) // 7
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# new 函数

表达式 new (T) 将创建一个 T 类型的匿名变量,所做的是为 T 类型的新值分配并清零一块内存空间,然后将这块内存空间的地址作为结果返回,而这个结果就是指向这个新的 T 类型值的指针值,返回的指针类型为 * T。

func main() {
	var p1 *int
	p1 = new(int)              //p1 为*int 类型, 指向匿名的 int 变量
	fmt.Println("*p1 = ", *p1) //*p1 = 0
	p2 := new(int)             //p2 为*int 类型, 指向匿名的 int 变量
	*p2 = 111
	fmt.Println("*p2 = ", *p2) //*p1 = 111
}
1
2
3
4
5
6
7
8

# 指针做参数

func swap01(a, b int) {
	a, b = b, a
	fmt.Printf("swap01 a = %d, b = %d\n", a, b)
}

func swap02(x, y *int) {
	*x, *y = *y, *x
}

func main() {
	a := 10
	b := 20
	//swap01(a, b) //值传递
	swap02(&a, &b) //变量地址传递
	fmt.Printf("a = %d, b = %d\n", a, b)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 结构体

package main

import "fmt"

type user struct {
	name     string
	password string
}

func main() {
	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 checkPassword(u user, password string) bool {
	return u.password == password
}

//结构体指针作为参数
func checkPassword2(u *user, password string) bool {
	return u.password == password
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 结构体方法

package main

import "fmt"

type user struct {
	name     string
	password string
}

//声明一个属于user结构体的checkPassword方法
func (u user) checkPassword(password string) bool {
	return u.password == password
}

//带指针可以方便对结构体进行修改
func (u *user) resetPassword(password string) {
	u.password = password
}

func main() {
	a := user{name: "wang", password: "1024"}
	a.resetPassword("2048")
	fmt.Println(a.checkPassword("2048")) // true
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 异常处理

Go 语言引入了一个关于错误处理的标准模式,即 error 接口,它是 Go 语言内建的接口类型,该接口的定义如下:

type error interface {
	Error() string
}
1
2
3

案例

package main

import (
	"errors"
	"fmt"
)

type user struct {
	name     string
	password string
}

//返回值带 error 类型,表明函数可能会有错误
func findUser(users []user, name string) (v *user, err error) {
	for _, u := range users {
		if u.name == name {
            //没有错误返回结果和nil
			return &u, nil
		}
	}
    //出现错误返回错误结果
	return nil, errors.New("not found")
}

func main() {
    //如果函数声明了会携带error,则必须接受error结果
	u, err := findUser([]user{{"wang", "1024"}}, "wang")
    //先判断错误是否存在,再进行操作
	if err != nil {
		fmt.Println(err)
		return
	}
    //如不判断错误是否存在,则u可能为空指针错误
	fmt.Println(u.name) // wang

	if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
		fmt.Println(err) // not found
		return
	} else {
		fmt.Println(u.name)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

还有另一个可以生成 error 类型值的方法是调用 fmt 包中的 Errorf 函数:

import (
	"errors"
	"fmt"
)

func main() {
	var err1 error = errors.New("a normal err1") //新建一个异常,可用于返回或者直接输出
	fmt.Println(err1) //a normal err1
	var err2 error = fmt.Errorf("%s", "a normal err2")
	fmt.Println(err2) //a normal err2
}
1
2
3
4
5
6
7
8
9
10
11

返回异常案例

func Divide(a, b float64) (result float64, err error) {
	if b == 0 {
		result = 0.0
		err = errors.New("runtime error: divide by zero")
		return
	}
	result = a / b
	err = nil
	return
}
func main() {
	r, err := Divide(10.0, 0)
	if err != nil {
		fmt.Println(err) //错误处理 runtime error: divide by zero
	} else {
		fmt.Println(r) // 使用返回值
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# panic

当遇到不可恢复的错误状态的时候,如数组访问越界、空指针引用等,这些运行时错误会引起 painc 异常。使用 error 类型值处理显然就不适合了。我们不应通过调用 panic 函数来报告普通的错误,而应该只把它作为报告致错误的一种方式。

一般而言,当 panic 异常发生时,程序会中断运行,并立即执行在该 goroutine。随后,程序崩溃并输出日志信息。日志信息包括 panic value 和函数调用的堆栈跟踪信息。

func TestA() {
	fmt.Println("func TestA()")
}

func TestB() {
	panic("func TestB(): panic")
}

func TestC() {
	fmt.Println("func TestC()")
}

func main() {
	TestA()
	TestB() //TestB()发生异常,中断程序
	TestC()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

image-20231114172545919

# recover

运行时 panic 异常一旦被引发就会导致程序崩溃。这当然不是我们愿意看到的,因为谁也不能保证程序不会发生任何运行时错误。 不过,Go 语言为我们提供了专用于 “拦截” 运行时 panic 的内建函数 ——recover。它可以是当前的程序从运行时 panic 的状态中恢复并重新获得流程控制权。

注意:recover 只有在 defer 调用的函数中有效。

func TestA() {
   fmt.Println("func TestA()")
}

func TestB() (err error) {
   defer func() { //在发生异常时,设置恢复
      if x := recover(); x != nil {
         //panic value 被附加到错误信息中;
         //并用 err 变量接收错误信息,返回给调用者。
         err = fmt.Errorf("internal error: %v", x)
      }
   }()
   panic("func TestB(): panic")
}

func TestC() {
   fmt.Println("func TestC()")
}

func main() {
   TestA()
   err := TestB()
   fmt.Println(err)
   TestC()
   /*
      运行结果:
      func TestA()
      internal error: func TestB(): panic
      func TestC()
   */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 字符串

# 字符串操作

package main

import (
	"fmt"
	"strings"
)

func main() {
	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
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 字符串格式化

package main

import "fmt"

type point struct {
	x, y int
}

func main() {
	s := "hello"
	n := 123
	p := point{1, 2}
	fmt.Println(s, n) // hello 123
	fmt.Println(p)    // {1 2}

	fmt.Printf("s=%v\n", s)  // s=hello %v格式化输出支持任意类型
	fmt.Printf("n=%v\n", n)  // n=123
	fmt.Printf("p=%v\n", p)  // p={1 2}
	fmt.Printf("p=%+v\n", p) // p={x:1 y:2} %+v带结构体属性名格式化
	fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2} %#v文件名+结构体属性

	f := 3.141592653
	fmt.Println(f)          // 3.141592653
	fmt.Printf("%.2f\n", f) // 3.14 浮点型格式化
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# JSON

package main

import (
	"encoding/json"
	"fmt"
)

//需要保证字段是以大写字母开头
type userInfo struct {
	Name  string
	Age   int `json:"age"` //默认以字段作为key,如需要小写则需要在类型后面使用json别名
	Hobby []string
}

func main() {
	a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
	buf, err := json.Marshal(a) //序列化
	if err != nil {
		panic(err)
	}
	fmt.Println(buf)         // [123 34 78 97...]
	fmt.Println(string(buf)) //需要转为字符串输出 {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}

	buf, err = json.MarshalIndent(a, "", "\t")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(buf))

	var b userInfo
	err = json.Unmarshal(buf, &b) //反序列化
	if err != nil {
		panic(err)
	}
	fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 文件操作

写文件

import (
	"fmt"
	"os"
)

func main() {
	fout, err := os.Create("./xxx.txt") //新建文件
	//fout, err := os.OpenFile("./xxx.txt", os.O_CREATE, 0666)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer fout.Close() //main 函数结束前, 关闭文件
	for i := 0; i < 5; i++ {
		outstr := fmt.Sprintf("%s:%d\n", "Hello go", i)
		fout.WriteString(outstr) //写入 string 信息到文件
		fout.Write([]byte("abcd\n")) //写入 byte 类型的信息到文件
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

读文件

func main() {
	fin, err := os.Open("./xxx.txt") //打开文件
	if err != nil {
		fmt.Println(err)
	}
	defer fin.Close()
	buf := make([]byte, 1024) //开辟 1024 个字节的 slice 作为缓冲
	for {
		n, _ := fin.Read(buf) //读文件
		if n == 0 { //0 表示已经到文件结束
			break
		}
		fmt.Println(string(buf)) //输出读取的内容
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

拷贝文件

import (
   "fmt"
   "io"
   "os"
)

func main() {
   args := os.Args //获取用户输入的所有参数
   //如果用户没有输入,或参数个数不够,则调用该函数提示用户
   if args == nil || len(args) != 3 {
      fmt.Println("useage : xxx srcFile dstFile")
      return
   }
   srcPath := args[1] //获取输入的第一个参数
   dstPath := args[2] //获取输入的第二个参数
   fmt.Printf("srcPath = %s, dstPath = %s\n", srcPath, dstPath)
   if srcPath == dstPath {
      fmt.Println("源文件和目的文件名字不能相同")
      return
   }
   srcFile, err1 := os.Open(srcPath) //打开源文件
   if err1 != nil {
      fmt.Println(err1)
      return
   }
   dstFile, err2 := os.Create(dstPath) //创建目的文件
   if err2 != nil {
      fmt.Println(err2)
      return
   }
   buf := make([]byte, 1024) //切片缓冲区
   for {
      //从源文件读取内容,n 为读取文件内容的长度
      n, err := srcFile.Read(buf)
      if err != nil && err != io.EOF {
         fmt.Println(err)
         break
      }
      if n == 0 {
         fmt.Println("文件处理完毕")
         break
      }
      //切片截取
      tmp := buf[:n]
      //把读取的内容写入到目的文件
      dstFile.Write(tmp)
   }
   //关闭文件
   srcFile.Close()
   dstFile.Close()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# Time

时间类型有一个自带的方法 Format 进行格式化,需要注意的是 Go 语言中格式化时间模板不是常见的 Y-m-d H:M:S 而是使用 Go 的诞生时间 2006 年 1 月 2 号 15 点 04 分(记忆口诀为 2006 1 2 3 4 5)

package main

import (
	"fmt"
	"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) //构造Date
	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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# Strconv

strconv 包实现了基本数据类型与其字符串表示的转换,主要有以下常用函数: Atoi ()、Itia ()、parse 系列、format 系列、append 系列。

package main

import (
	"fmt"
	"strconv"
)

func main() {
	f, _ := strconv.ParseFloat("1.234", 64)
	fmt.Println(f) // 1.234

	n, _ := strconv.ParseInt("111", 10, 64) //参数1为需要解析的字符串 参数2为进制,如果传递0则自动推导 参数3为64位int类型
	fmt.Println(n) // 111

	n, _ = strconv.ParseInt("0x1000", 0, 64)
	fmt.Println(n) // 4096

	n2, _ := strconv.Atoi("123")
	fmt.Println(n2) // 123

	n2, err := strconv.Atoi("AAA")
	fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 进程信息

Command 函数 os/exec 将允许我们执行此操作。它接受至少一个字符串 - 您要运行的命令 / 二进制文件的名称 - 后接任意数量的字符串作为其参数。

package main

import (
	"fmt"
	"os"
	"os/exec"
)

func main() {
	// go run example/20-env/main.go a b c d
	fmt.Println(os.Args)           // 获取执行参数 第一个为二进制程序执行的临时路径[/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
	fmt.Println(os.Getenv("PATH")) //获取环境变量 /usr/local/go/bin...
	fmt.Println(os.Setenv("AA", "BB")) //写入环境变量

    //执行命令/程序
	buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
	if err != nil {
		panic(err)
	}
	fmt.Println(string(buf)) // 127.0.0.1       localhost
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 延迟调用(defer)

defer 特性:

  • 关键字 defer 用于注册延迟调用。
  • 这些调用直到 return 前才被执。因此,可以用来做资源清理。
  • 多个 defer 语句,按先进后出的方式执行。
  • defer 语句中的变量,在 defer 声明时就决定了。

defer 用途:

  • 关闭文件句柄
  • 锁资源释放
  • 数据库连接释放

defer 是先进后出,后面的语句会依赖前面的资源,因此如果先前面的资源先释放了,后面的语句就没法执行了。

package main

import "fmt"

func main() {
    var whatever [5]struct{}

    for i := range whatever {
        defer fmt.Println(i)
    }
}
1
2
3
4
5
6
7
8
9
10
11

输出结果

4
3
2
1
0
1
2
3
4
5

# 面向对象

Go 语言中没有封装、继承、多态这些概念,比如继承 (不支持继承,尽管匿名字段的内存布局和行为类似 继承,但它并不是继承)、虚函数、构造函数和析构函数、隐藏的 this 指针等。但同样通过别的方式实现这些特性:

  • 封装:通过方法实现
  • 继承:通过匿名字段实现
  • 多态:通过接口实现

# 匿名字段

Go 支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段

//人
type Person struct {
	name string
	sex byte
	age int
}

//学生
type Student struct {
	Person // 匿名字段,那么默认 Student 就包含了 Person 的所有字段
	id int
	addr string
}

func main() {
	//顺序初始化
	s1 := Student{Person{"mike", 'm', 18}, 1, "sz"}
	//s1 = {Person:{name:mike sex:109 age:18} id:1 addr:sz}
	fmt.Printf("s1 = %+v\n", s1)
	//s2 := Student{"mike", 'm', 18, 1, "sz"} //err
	//部分成员初始化 1
	s3 := Student{Person: Person{"lily", 'f', 19}, id: 2}
	//s3 = {Person:{name:lily sex:102 age:19} id:2 addr:}
	fmt.Printf("s3 = %+v\n", s3)
	//部分成员初始化 2
	s4 := Student{Person: Person{name: "tom"}, id: 3}
	//s4 = {Person:{name:tom sex:0 age:0} id:3 addr:}
	fmt.Printf("s4 = %+v\n", s4)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

所有的内置类型和自定义类型都是可以作为匿名字段的

type mystr string //自定义类型
type Person struct {
	name string
	sex  byte
	age  int
}
type Student struct {
	Person // 匿名字段,结构体类型
	int    // 匿名字段,内置类型
	mystr  // 匿名字段,自定义类型
}

func main() {
	//初始化
	s1 := Student{Person{"mike", 'm', 18}, 1, "bj"}
	//{Person:{name:mike sex:109 age:18} int:1 mystr:bj}
	fmt.Printf("%+v\n", s1)
	//成员的操作,打印结果:mike, m, 18, 1, bj
	fmt.Printf("%s, %c, %d, %d, %s\n", s1.name, s1.sex, s1.age, s1.int,
		s1.mystr)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

结构体指针类型

type Person struct { //人
	name string
	sex  byte
	age  int
}
type Student struct { //学生
	*Person // 匿名字段,结构体指针类型
	id      int
	addr    string
}

func main() {
	//初始化
	s1 := Student{&Person{"mike", 'm', 18}, 1, "bj"}
	//{Person:0xc0420023e0 id:1 addr:bj}
	fmt.Printf("%+v\n", s1)
	//mike, m, 18
	fmt.Printf("%s, %c, %d\n", s1.name, s1.sex, s1.age)
	//声明变量
	var s2 Student
	s2.Person = new(Person) //分配空间
	s2.name = "yoyo"
	s2.sex = 'f'
	s2.age = 20
	s2.id = 2
	s2.addr = "sz"
	//yoyo 102 20 2 20
	fmt.Println(s2.name, s2.sex, s2.age, s2.id, s2.age)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 成员的操作

var s1 Student //变量声明
//给成员赋值
s1.name = "mike" //等价于 s1.Person.name = "mike"
s1.sex = 'm'
s1.age = 18
s1.id = 1
s1.addr = "sz"
fmt.Println(s1) //{{mike 109 18} 1 sz}

var s2 Student //变量声明
s2.Person = Person{"lily", 'f', 19}
s2.id = 2
s2.addr = "bj"
fmt.Println(s2) //{{lily 102 19} 2 bj}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 同名字段

//人
type Person struct {
	name string
	sex  byte
	age  int
}

//学生
type Student struct {
	Person // 匿名字段,那么默认 Student 就包含了 Person 的所有字段
	id     int
	addr   string
	name   string //和 Person 中的 name 同名
}

func main() {
	var s Student //变量声明
	//给 Student 的 name,还是给 Person 赋值?
	s.name = "mike"
	//{Person:{name: sex:0 age:0} id:0 addr: name:mike}
	fmt.Printf("%+v\n", s)
	//默认只会给最外层的成员赋值
	//给匿名同名成员赋值,需要显示调用
	s.Person.name = "yoyo"
	//Person:{name:yoyo sex:0 age:0} id:0 addr: name:mike}
	fmt.Printf("%+v\n", s)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 方法

⽅法总是绑定对象实例,并隐式将实例作为第⼀实参 (receiver),方法的语法如下:

func (receiver ReceiverType) funcName(parameters) (results)
1
  • 参数 receiver 可任意命名。如⽅法中未曾使⽤,可省略参数名。
  • 参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接口或指针。
  • 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。

# 基础类型作为接收者

type MyInt int //自定义类型,给 int 改名为 MyInt
//在函数定义时,在其名字之前放上一个变量,即是一个方法
func (a MyInt) Add(b MyInt) MyInt { //面向对象
   return a + b
}

//传统方式的定义
func Add(a, b MyInt) MyInt { //面向过程
   return a + b
}
func main() {
   var a MyInt = 1
   var b MyInt = 1
   //调用 func (a MyInt) Add(b MyInt)
   fmt.Println("a.Add(b) = ", a.Add(b)) //a.Add(b) = 2
   //调用 func Add(a, b MyInt)
   fmt.Println("Add(a, b) = ", Add(a, b)) //Add(a, b) = 2
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 结构体作为接收者

方法里面可以访问接收者的字段,调用方法通过点 (.) 访问,就像 struct 里面访问字段一样:

type Person struct {
	name string
	sex  byte
	age  int
}

func (p Person) PrintInfo() { //给 Person 添加方法
	fmt.Println(p.name, p.sex, p.age)
}

func main() {
	p := Person{"mike", 'm', 18} //初始化
	p.PrintInfo()                //调用 func (p Person) PrintInfo()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 结构体指针作为接受者

type Person struct {
	name string
	sex  byte
	age  int
}

//指针作为接收者,引用语义
func (p *Person) SetInfoPointer() {
	//给成员赋值
	(*p).name = "yoyo"
	p.sex = 'f'
	p.age = 22
}

//值作为接收者,值语义
func (p Person) SetInfoValue() {
	//给成员赋值
	p.name = "yoyo"
	p.sex = 'f'
	p.age = 22
}
func main() {
	//指针作为接收者,引用语义
	p1 := Person{"mike", 'm', 18} //初始化
	fmt.Println("函数调用前 = ", p1)   //函数调用前 = {mike 109 18}
	(&p1).SetInfoPointer()
	fmt.Println("函数调用后 = ", p1) //函数调用后 = {yoyo 102 22}
	fmt.Println("==========================")
	p2 := Person{"mike", 'm', 18} //初始化
	//值作为接收者,值语义
	fmt.Println("函数调用前 = ", p2) //函数调用前 = {mike 109 18}
	p2.SetInfoValue()
	fmt.Println("函数调用后 = ", p2) //函数调用后 = {mike 109 18}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 方法集

方法集是指可以被该类型的值调用的所有方法的集合。

用实例实例 value 和 pointer 调用方法(含匿名字段)不受方法集约束,编译器编总是查找全部方法,并自动转换 receiver 实参。

一个指向自定义类型的值的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。

type Person struct {
	name string
	sex  byte
	age  int
}

//指针作为接收者,引用语义
func (p *Person) SetInfoPointer() {
	(*p).name = "yoyo"
	p.sex = 'f'
	p.age = 22
}

//值作为接收者,值语义
func (p Person) SetInfoValue() {
	p.name = "xxx"
	p.sex = 'm'
	p.age = 33
}
func main() {
	//p 为指针类型
	var p *Person = &Person{"mike", 'm', 18}
	p.SetInfoPointer()  //func (p) SetInfoPointer()
	p.SetInfoValue()    //func (*p) SetInfoValue()
	(*p).SetInfoValue() //func (*p) SetInfoValue()
    
    //p 为普通值类型
    var p Person = Person{"mike", 'm', 18}
    (&p).SetInfoPointer() //func (&p) SetInfoPointer()
    p.SetInfoPointer() //func (&p) SetInfoPointer()
    p.SetInfoValue() //func (p) SetInfoValue()
    (&p).SetInfoValue() //func (*&p) SetInfoValue()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 匿名字段中方法的重写

如果匿名字段实现了一个方法,那么包含这个匿名字段的 struct 也能调用该方法,并可以重写该匿名字段的方法。

type Person struct {
	name string
	sex  byte
	age  int
}

//Person 定义了方法
func (p *Person) PrintInfo() {
	fmt.Printf("Person: %s,%c,%d\n", p.name, p.sex, p.age)
}

type Student struct {
	Person // 匿名字段,那么 Student 包含了 Person 的所有字段
	id     int
	addr   string
}

//Student 定义了方法
func (s *Student) PrintInfo() {
	fmt.Printf("Student:%s,%c,%d\n", s.name, s.sex, s.age)
}

func main() {
	p := Person{"mike", 'm', 18}
	p.PrintInfo() //Person: mike,m,18
	s := Student{Person{"yoyo", 'f', 20}, 2, "sz"}
	s.PrintInfo()        //Student:yoyo,f,20
	s.Person.PrintInfo() //Person: yoyo,f,20
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 表达式

类似于我们可以对函数进行赋值和传递一样,方法也可以进行赋值和传递。

type Person struct {
	name string
	sex  byte
	age  int
}

func (p *Person) PrintInfoPointer() {
	fmt.Printf("%p, %v\n", p, p)
}

func (p Person) PrintInfoValue() {
	fmt.Printf("%p, %v\n", &p, p)
}

func main() {
	p := Person{"mike", 'm', 18}
	p.PrintInfoPointer()         //0xc0420023e0, &{mike 109 18}
	pFunc1 := p.PrintInfoPointer //方法值,隐式传递 receiver
	pFunc1()                     //0xc0420023e0, &{mike 109 18}
	pFunc2 := p.PrintInfoValue
	pFunc2() //0xc042048420, {mike 109 18}

	p2 := Person{"mike", 'm', 18}
	p2.PrintInfoPointer() //0xc0420023e0, &{mike 109 18}
	//方法表达式, 须显式传参
	//func pFunc1(p *Person))
	pFunc3 := (*Person).PrintInfoPointer
	pFunc3(&p) //0xc0420023e0, &{mike 109 18}
	pFunc4 := Person.PrintInfoValue
	pFunc4(p) //0xc042002460, {mike 109 18}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 接口

接口类型是一种抽象的类型,它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合,它们只会展示出它们自己的方法。因此接口类型不能将其实例化。

type Humaner interface {
	SayHi()
}
1
2
3
  • 接口命名习惯以 er 结尾
  • 接口只有方法声明,没有实现,没有数据字段
  • 接口可以匿名嵌入其它接口,或嵌入到结构中

# 接口实现

接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现,一个实现了这些方法的具体类型是这个接口类型的实例。

type Humaner interface {
	SayHi()
}
type Student struct { //学生
	name  string
	score float64
}

//Student 实现 SayHi()方法
func (s *Student) SayHi() {
	fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}

type Teacher struct { //老师
	name  string
	group string
}

//Teacher 实现 SayHi()方法
func (t *Teacher) SayHi() {
	fmt.Printf("Teacher[%s, %s] say hi!!\n", t.name, t.group)
}

type MyStr string

//MyStr 实现 SayHi()方法
func (str MyStr) SayHi() {
	fmt.Printf("MyStr[%s] say hi!!\n", str)
}

//普通函数,参数为 Humaner 类型的变量 i
func WhoSayHi(i Humaner) {
	i.SayHi()
}

func main() {
	s := &Student{"mike", 88.88}
	t := &Teacher{"yoyo", "Go 语言"}
	var tmp MyStr = "测试"
	s.SayHi()   //Student[mike, 88.880000] say hi!!
	t.SayHi()   //Teacher[yoyo, Go 语言] say hi!!
	tmp.SayHi() //MyStr[测试] say hi!!
    
	//多态,调用同一接口,不同表现
	WhoSayHi(s)   //Student[mike, 88.880000] say hi!!
	WhoSayHi(t)   //Teacher[yoyo, Go 语言] say hi!!
	WhoSayHi(tmp) //MyStr[测试] say hi!!
	x := make([]Humaner, 3)
	//这三个都是不同类型的元素,但是他们实现了 interface 同一个接口
	x[0], x[1], x[2] = s, t, tmp
	for _, value := range x {
		value.SayHi()
	}
	/*
	   Student[mike, 88.880000] say hi!!
	   Teacher[yoyo, Go 语言] say hi!!
	   MyStr[测试] say hi!!
	*/
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

# 接口组合

# 嵌入

如果一个 interface1 作为 interface2 的一个嵌入字段,那么 interface2 隐式的包含了 interface1 里面的方法。

type Humaner interface {
   SayHi()
}

type Personer interface {
   Humaner //这里像写了 SayHi()一样
   Sing(lyrics string)
}

type Student struct { //学生
   name  string
   score float64
}

//Student 实现 SayHi()方法
func (s *Student) SayHi() {
   fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}

//Student 实现 Sing()方法
func (s *Student) Sing(lyrics string) {
   fmt.Printf("Student sing[%s]!!\n", lyrics)
}

func main() {
   s := &Student{"mike", 88.88}
   var i2 Personer
   i2 = s
   i2.SayHi()     //Student[mike, 88.880000] say hi!!
   i2.Sing("学生哥") //Student sing[学生哥]!!
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 转换

超集接口对象可转换为⼦集接口,反之出错:

type Humaner interface {
	SayHi()
}

type Personer interface {
	Humaner //这里像写了 SayHi()一样
	Sing(lyrics string)
}

type Student struct { //学生
	name  string
	score float64
}

//Student 实现 SayHi()方法
func (s *Student) SayHi() {
	fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}

//Student 实现 Sing()方法
func (s *Student) Sing(lyrics string) {
	fmt.Printf("Student sing[%s]!!\n", lyrics)
}

func main() {
	//var i1 Humaner = &Student{"mike", 88.88}
	//var i2 Personer = i1 //err
	//Personer 为超集,Humaner 为子集
	var i1 Personer = &Student{"mike", 88.88}
	var i2 Humaner = i1
	i2.SayHi() //Student[mike, 88.880000] say hi!!
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 空接口

空接口 (interface {}) 不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型

的数值。它有点类似于 C 语言的 void * 类型。

var v1 interface{} = 1 // 将 int 类型赋值给 interface{}
var v2 interface{} = "abc" // 将 string 类型赋值给 interface{}
var v3 interface{} = &v2 // 将*interface{}类型赋值给 interface{}
var v4 interface{} = struct{ X int }{1}
var v5 interface{} = &struct{ X int }{1}
1
2
3
4
5

# 类型查询

interface 的变量里面可以存储任意类型的数值 (该类型实现了 interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?

  • comma-ok 断言
  • switch 测试

Go 语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里 value 就是变量的值,ok 是一个 bool 类型,element 是 interface 变量,T 是断言的类型。

如果 element 里面确实存储了 T 类型的数值,那么 ok 返回 true,否则返回 false。

type Element interface{}

type Person struct {
	name string
	age int
}

func main() {
	list := make([]Element, 3)
	list[0] = 1 // an int
	list[1] = "Hello" // a string
	list[2] = Person{"mike", 18}
	for index, element := range list {
		if value, ok := element.(int); ok {
			fmt.Printf("list[%d] is an int and its value is %d\n", index,
				value)
		} else if value, ok := element.(string); ok {
			fmt.Printf("list[%d] is a string and its value is %s\n",
				index, value)
		} else if value, ok := element.(Person); ok {
			fmt.Printf("list[%d] is a Person and its value is [%s, %d]\n",
				index, value.name, value.age)
		} else {
			fmt.Printf("list[%d] is of a different type\n", index)
		}
	}
	/* 打印结果:
	   list[0] is an int and its value is 1
	   list[1] is a string and its value is Hello
	   list[2] is a Person and its value is [mike, 18]
	*/
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

Switch 测试

type Element interface{}

type Person struct {
	name string
	age int
}

func main() {
	list := make([]Element, 3)
	list[0] = 1 //an int
	list[1] = "Hello" //a string
	list[2] = Person{"mike", 18}
	for index, element := range list {
		switch value := element.(type) {
		case int:
			fmt.Printf("list[%d] is an int and its value is %d\n", index,
				value)
		case string:
			fmt.Printf("list[%d] is a string and its value is %s\n",
				index, value)
		case Person:
			fmt.Printf("list[%d] is a Person and its value is [%s, %d]\n",
				index, value.name, value.age)
		default:
			fmt.Println("list[%d] is of a different type", index)
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
编辑 (opens new window)
上次更新: 2023/12/06, 01:31:48
并发集合
Go 项目案例

← 并发集合 Go 项目案例→

最近更新
01
k8s
06-06
02
进程与线程
03-04
03
计算机操作系统概述
02-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 Iekr | Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式