Go 语言快速入门——高级特性

高级特性

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
27
28
29
30
31
32
package main

import "fmt"

// 闭包

func f1(f func()) {
fmt.Println("this is f1")
f()
}

func f2(x, y int) {
fmt.Println("this is f2")
fmt.Println(x + y)
}

// 要求:
// f1(f2)

func f3(f func(int, int), x, y int) func() {
tmp := func() {
f(x, y)
}
return tmp
}

func main() {
ret := f3(f2, 100, 200) // 把原来需要传递两个int类型的参数包装成一个不需要传参的函数
fmt.Printf("%T\n", ret)
// ret()
f1(ret)
}

【使用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
27
28
29
30
31
32
33
package main

import "fmt"

// 闭包是什么?
// 闭包是一个函数,这个函数包含了他外部作用域的一个变量

// 底层的原理:
// 1. 函数可以作为返回值
// 2. 函数内部查找变量的顺序,先在自己内部找,找不到往外层找

func adder1() func(int) int {
var x int = 100
return func(y int) int {
x += y
return x
}
}

func adder2(x int) func(int) int {
return func(y int) int {
x += y
return x
}
}

func main() {
ret := adder2(100)

ret2 := ret(200)

fmt.Println(ret2)
}

【使用2】

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
package main

import (
"fmt"
"strings"
)

// 闭包

func makeSuffixFunc(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}

func main() {
jpgFunc := makeSuffixFunc(".jpg")
txtFunc := makeSuffixFunc(".txt")

fmt.Println(jpgFunc("test")) //test.jpg
fmt.Println(jpgFunc("呵呵.jpg"))
fmt.Println(txtFunc("test")) //test.txt
fmt.Println(txtFunc("呵呵.txt"))
}
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"

// 关键字 函数名(参数)(返回值){}
func calc(base int) (func(int) int, func(int) int) {
add := func(i int) int {
base += i
return base
}

sub := func(i int) int {
base -= i
return base
}
return add, sub
}

func main() {
f1, f2 := calc(10)

fmt.Println(f1(1), f2(2)) // 11 9
fmt.Println(f1(3), f2(4)) // 12 8;13 9
fmt.Println(f1(5), f2(6)) // 13 7
}

2、递归

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
package main

import "fmt"

// 永远不要高估自己!

// 递归:函数自己调用自己!
// 递归适合处理那种问题相同\问题的规模越来越小的场景
// 递归一定要有一个明确的退出条件

// 3! = 3*2*1 = 3*2!
// 4! = 4*3*2*1 = 4*3!
// 5! = 5*4*3*2*1 = 5*4!

// 计算n的阶乘
func f(n uint64) uint64 {
if n <= 1 {
return 1
}
return n * f(n-1)
}

// 上台阶的面试题
// n个台阶,一次可以走1步,也可以走2步,有多少种走法。
func taijie(n uint64) uint64 {
if n == 1 {
// 如果只有一个台阶就一种走法
return 1
}
if n == 2 {
return 2
}
return taijie(n-1) + taijie(n-2)
}

func main() {
// ret := f(7)
// fmt.Println(ret)
ret := taijie(4)
fmt.Println(ret)
}

3、type 语句

自定义类型和类型别名

1
2
3
// type后面跟的是类型
type myInt int // 自定义类型
type yourInt = int // 类型别名

4、地址与指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

//

func main() {
var a int
a = 100
b := &a
fmt.Printf("type a:%T type b:%T\n", a, b)
// 将a的十六进制内存地址打印出来
fmt.Printf("%p\n", &a)
fmt.Printf("%p\n", b) // b的值
fmt.Printf("%v\n", b)
fmt.Printf("%p\n", &b) // b的内存地址
}

5、结构体

  • 普通定义与匿名结构体
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
package main

import "fmt"

// 结构体

type person struct {
name string
age int
gender string
hobby []string
}

func main() {
// 声明一个person类型的变量p
var p person
// 通过字段赋值
p.name = "周林"
p.age = 9000
p.gender = "男"
p.hobby = []string{"篮球", "足球", "双色球"}
fmt.Println(p)
// 访问变量p的字段
fmt.Printf("%T\n", p)
fmt.Println(p.name)
var p2 person
p2.name = "理想"
p2.age = 18
fmt.Printf("type:%T value:%v\n", p2, p2)

// 匿名结构体:多用于临时场景
var s struct {
x string
y int
}
s.x = "嘿嘿嘿"
s.y = 100
fmt.Printf("type:%T value:%v\n", s, 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import "fmt"

// 结构体是值类型

type person struct {
name, gender string
}

// go语言中函数传参数永远传的是拷贝
func f(x person) {
x.gender = "女" // 修改的是副本的gender
}

func f2(x *person) {
// (*x).gender = "女" // 根据内存地址找到那个原变量,修改的就是原来的变量
x.gender = "女" // 语法糖,自动根据指针找对应的变量
}

func main() {
var p person
p.name = "周林"
p.gender = "男"
f(p)
fmt.Println(p.gender) // 男
f2(&p) // ox1241ac3
fmt.Println(p.gender) // 女
// 结构体指针1
var p2 = new(person) // 指针
(*p2).name = "理想"
p2.gender = "保密"

fmt.Printf("%T\n", p2)
fmt.Printf("%p\n", p2) // p2保存的值就是一个内存地址
fmt.Printf("%p\n", &p2) // 求b2的内存地址
// 2. 结构体指针2
// 2.1 key-value初始化
var p3 = &person{
name: "元帅",
}
fmt.Printf("%#v\n", p3)
// 2.2 使用值列表的形式初始化, 值的顺序要和结构体定义时字段的顺序一致
p4 := &person{
"小王子",
"男",
}
fmt.Printf("%#v\n", p4)
}
  • 结构体占用一块连续的空间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

// 结构体占用一块连续的内存空间

type x struct {
a int8 // 8bit => 1byte
b int8
c int8
}

func main() {
m := x{
a: int8(10),
b: int8(20),
c: int8(30),
}
fmt.Printf("%p\n", &(m.a))
fmt.Printf("%p\n", &(m.b))
fmt.Printf("%p\n", &(m.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
34
35
36
37
38
package main

import "fmt"

// 构造函数

type person struct {
name string
age int
}

type dog struct {
name string
}

// 构造函数:约定成俗用new开头
// 返回的是结构体还是结构体指针
// 当结构体比较大的时候尽量使用结构体指针,减少程序的内存开销
func newPerson(name string, age int) *person {
return &person{
name: name,
age: age,
}
}

func newDog(name string) dog {
return dog{
name: name,
}
}

func main() {
p1 := newPerson("元帅", 18)
p2 := newPerson("周林", 9000)
fmt.Println(p1, p2)
d1 := newDog("周林")
fmt.Println(d1)
}
  • 结构体匿名字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

// 匿名字段
// 字段比较少也比较简单的场景
// 不常用!!!

type person struct {
string
int
}

func main() {
p1 := person{
"周林",
9000,
}
fmt.Println(p1)
fmt.Println(p1.string)
fmt.Println(p1.int)
}
  • 结构体与json

其中反引号包裹的是对应在不同包中解析时准换的名称。

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
package main

import (
"encoding/json"
"fmt"
)

// 结构体与json

// 1.序列化: 把Go语言中的结构体变量 --> json格式的字符串
// 2.反序列化: json格式的字符串 --> Go语言中能够识别的结构体变量

type person struct {
Name string `json:"name" db:"name" ini:"name"`
Age int `json:"age"`
}

func main() {
p1 := person{
Name: "周林",
Age: 9000,
}
// 序列化
b, err := json.Marshal(p1)
if err != nil {
fmt.Printf("marshal failed, err:%v", err)
return
}
fmt.Printf("%v\n", string(b))
// 反序列化
str := `{"name":"理想","age":18}`
var p2 person
json.Unmarshal([]byte(str), &p2) // 传指针是为了能在json.Unmarshal内部修改p2的值
fmt.Printf("%#v\n", p2)
}

6、方法和接收者

不同于函数,方法是作用于特定对象的。

Go语言中如果标识符首字母是大写的,就表示对外部包可见(暴露的,公有的)。

一般使用指针接收者

1
2
3
func (接收者变量 接收者类型) 方法名 (参数列表) (返回参数) {
函数体
}
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
package main

import "fmt"

// 方法

// 标识符:变量名 函数名 类型名 方法名
// Go语言中如果标识符首字母是大写的,就表示对外部包可见(暴露的,公有的).

// dog 这是一个狗的结构体
type dog struct {
name string
}

type person struct {
name string
age int
}

func newPerson(name string, age int) *person {
return &person{
name: name,
age: age,
}
}

// 构造函数
func newDog(name string) dog {
return dog{
name: name,
}
}

// 方法是作用于特定类型的函数
// 接受者表示的是调用该方法的具体类型变量,多用类型名首字母小写表示
func (d dog) wang() {
fmt.Printf("%s:汪汪汪~", d.name)
}

// 使用值接收者:传拷贝进去
// func (p person) guonian() {
// p.age++
// }

// 指针接收者:传内存地址进去
func (p *person) zhenguonian() {
p.age++
}

func (p *person) dream() {
fmt.Println("不上班也能挣钱!")
}

func main() {
// d1 := newDog("zhoulin")
// d1.wang()
p1 := newPerson("元帅", 18)
// p1.wang()
fmt.Println(p1.age) // 18
// p1.guonian()
fmt.Println(p1.age) // 18
p1.zhenguonian()
fmt.Println(p1.age) // ?
p1.dream()
}
  • 运用

不能给别的包里面的类型添加方法,只能给自己包里的类型添加方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

// 给自定义类型加方法
// 不能给别的包里面的类型添加方法,只能给自己包里的类型添加方法

type myInt int

func (m myInt) hello() {
fmt.Println("我是一个int")
}

func main() {
m := myInt(100)
m.hello()
}

7、接口 (interface)

  • 接口是一种类型,是一种特殊的类型,它规定了变量有哪些方法。
  • 基本使用,基本标准:是要实现了接口中的方法,就是该接口类型。
1
2
3
4
5
type 接口名 interface {
方法名1(参数1,参数2...)(返回值1,返回值2...)
方法名2(参数1,参数2...)(返回值1,返回值2...)
...
}
  • 接口实现

一个变量如果实现了接口中规定的所有方法,那么这个变量就实现了这个接口,可以称为这个接口类型的变量。

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
package main

import "fmt"

// 引出接口的实例

// 定义一个能叫的类型
type speaker interface {
speak() // 只要实现了speak方法的变量都是speaker类型, 方法签名
}

type cat struct{}

type dog struct{}

type person struct{}

func (c cat) speak() {
fmt.Println("喵喵喵~")
}

func (d dog) speak() {
fmt.Println("旺旺旺~")
}

func (p person) speak() {
fmt.Println("啊啊啊~")
}

func da(x speaker) {
// 接收一个参数,传进来什么,我就打什么
x.speak() // 挨打了就要叫
}

func main() {
var c1 cat
var d1 dog
var p1 person

da(c1)
da(d1)
da(p1)

var ss speaker // 定义一个接口类型:speaker 的变量:ss
ss = c1
ss = d1
ss = p1
fmt.Println(ss)
}
  • 值接收者与指针接收者实现接口的区别

一般使用指针接收者,也就是对象的指针实现了接口。

  1. 使用值接收者实现接口,结构体类型和结构体指针类型的变量都能存;

  2. 指针接收者实现接口只能存结构体指针类型

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
package main

import "fmt"

// 使用值接收者和指针接收者的区别?
type animal interface {
move()
eat(string)
}

type cat struct {
name string
feet int8
}

// // 使用值接收者实现了接口的所有方法
// func (c cat) move() {
// fmt.Println("走猫步...")
// }

// func (c cat) eat(food string) {
// fmt.Printf("猫吃%s...\n", food)
// }

// 使用指针接收者实现了接口的所有方法
func (c *cat) move() {
fmt.Println("走猫步...")
}

func (c *cat) eat(food string) {
fmt.Printf("猫吃%s...\n", food)
}

func main() {
var a1 animal

c1 := cat{"tom", 4} // cat
c2 := &cat{"假老练", 4} // *cat

a1 = &c1 // 实现animal这个接口的是cat的指针类型,
fmt.Println(a1)
a1 = c2
fmt.Println(a1)
}
  • 一个类型实现多个接口
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
package main

import "fmt"

// 同一个结构体可以实现多个接口
// 接口还可以嵌套

type animal interface {
mover
eater
}

type mover interface {
move()
}

type eater interface {
eat(string)
}

type cat struct {
name string
feet int8
}

// cat实现了mover接口
func (c *cat) move() {
fmt.Println("走猫步...")
}

// cat实现了eater接口
func (c *cat) eat(food string) {
fmt.Printf("猫吃%s...\n", food)
}
  • 空接口

相当于所有的类型都实现了空接口,也就是说所有值都能传进来。

空接口没有必要取名字。

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
package main

import "fmt"

// 空接口

// interface: 关键字
// interface{} :空接口类型

// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}

func main() {
var m1 map[string]interface{}
m1 = make(map[string]interface{}, 16)
m1["name"] = "周林"
m1["age"] = 9000
m1["merried"] = true
m1["hobby"] = [...]string{"唱", "跳", "rap"}
fmt.Println(m1)

show(false)
show(nil)
show(m1)
}

8、类型断言

目的:想知道空接口接收的具体是什么?

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
package main

import "fmt"

type ff func()

// 类型断言1
func assign(a interface{}) {
fmt.Printf("%T\n", a)
str, ok := a.(string)
if !ok {
fmt.Println("猜错了")
} else {
fmt.Println("传进来的是一个字符串:", str)
}
}

// 类型断言2
func assign2(a interface{}) {
fmt.Printf("%T\n", a)
switch t := a.(type) {
case string:
fmt.Println("是一个字符串:", t)
case int:
fmt.Println("是一个int:", t)
case int64:
fmt.Println("是一个int64:", t)
case bool:
fmt.Println("是一个bool:", t)
case []int:
fmt.Println("是一个slice:", t)
case map[string]int:
fmt.Println("是一个map[string]int:", t)
case func():
fmt.Println("是一个函数类型:", t)
}
}
func f() {

}
func main() {
// assign(100)
assign2(true)
assign2("哈哈哈")
assign2(int64(200))
assign2([]int{1, 2, 3})
assign2(map[string]int{"a": 1})
assign2(f)
}

9、包

【注意事项】

  • import 导入语句通常放在文件开头包声明语句下面;
  • 导入的包名需要使用双引号包裹起来;
  • 包名是从 $GOPATH/src/ 后开始计算的,使用/进行路径分隔;
  • Go语言中禁止循环导入包;
  • 想被别的包调用的标识符都要首字母大写;
  • 导入包的时候可以指定别名;
  • 导入包不想使用包内部的标识符,需要使用匿名导入;
  • init方法的调用顺序:
截屏2020-02-03下午7.40.03 初始化函数

【格式】

1
2
3
4
5
6
7
import 别名 "包的路径"

import{
别名 "包的路径"
}

import _ "路径" // 匿名导入
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
package calc

import "fmt"

func init() {
fmt.Println("import 我时自动执行...")
}

// 包中的标识符(变量名\函数名\结构体\接口等)如果首字母是小写的,表示私有(只能在当前这个包中使用)
// 首字母大写的标识符可以被外部的包调用
func Add(x, y int) int {
return x + y
}

---

package main

import (
"fmt"

mzyan "github.com/hustmzyan/day05/10calc"
)

var x = 100

const pi = 3.14

func init() {
fmt.Println("自动执行!")
fmt.Println(x, pi)
}

func main() {
ret := mzyan.Add(10, 20)
fmt.Println(ret)
}

10、文件操作

  • 三种文件读操作:
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
74
75
76
77
78
79
80
81
package main

import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
)

// 打开文件

func readFromFile1() {
fileObj, err := os.Open("./main.go")
if err != nil {
fmt.Printf("open file failed, err:%v", err)
return
}
// 记得关闭文件
defer fileObj.Close()
// 读文件
// var tmp = make([]byte, 128) // 指定读的长度
var tmp [128]byte
for {
n, err := fileObj.Read(tmp[:])
if err == io.EOF {
fmt.Println("读完了")
return
}
if err != nil {
fmt.Printf("read from file failed, err:%v", err)
return
}
fmt.Printf("读了%d个字节\n", n)
fmt.Println(string(tmp[:n]))
if n < 128 {
return
}
}
}

// 利用bufio这个包读取文件
func readFromFilebyBufio() {
fileObj, err := os.Open("./main.go")
if err != nil {
fmt.Printf("open file failed, err:%v", err)
return
}
// 记得关闭文件
defer fileObj.Close()
// 创建一个用来从文件中读内容的对象
reader := bufio.NewReader(fileObj)
for {
line, err := reader.ReadString('\n')
if err == io.EOF {
return
}
if err != nil {
fmt.Printf("read line failed, err:%v", err)
return
}
fmt.Print(line)
}
}

// Ioutil方式,最简单暴力
func readFromFileByIoutil() {

ret, err := ioutil.ReadFile("./main.go")
if err != nil {
fmt.Printf("read file failed, err:%v\n", err)
return
}
fmt.Println(string(ret))
}
func main() {
// readFromFile1()
// readFromFilebyBufio()
readFromFileByIoutil()

}
  • 文件写入操作
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
package main

import (
"bufio"
"fmt"
"io/ioutil"
"os"
)

// 打开文件写内容
// 0100 0000

func writeDemo1() {
fileObj, err := os.OpenFile("./xx.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
fmt.Printf("open file failed, err:%v", err)
return
}
// write
fileObj.Write([]byte("zhoulin mengbi le!\n"))
// writeString
fileObj.WriteString("周林解释不了!")
fileObj.Close()
}

func writeDemo2() {
fileObj, err := os.OpenFile("./xx.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
fmt.Printf("open file failed, err:%v", err)
return
}
defer fileObj.Close()
// 创建一个写的对象
wr := bufio.NewWriter(fileObj)
wr.WriteString("hello沙河\n") // 写到缓存中
wr.Flush() // 将缓存中的内容写入文件
}

func writeDemo3() {
str := "hello 沙河"
err := ioutil.WriteFile("./xx.txt", []byte(str), 0666)
if err != nil {
fmt.Println("write file failed, err:", err)
return
}
}

func main() {
// writeDemo1()
// writeDemo2()
writeDemo3()
}
  • 文件插入操作实例
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
package main

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

// 文件操作

func f2() {
// 打开要操作的文件
fileObj, err := os.OpenFile("./sb.txt", os.O_RDWR, 0644)
if err != nil {
fmt.Printf("open file failed, err:%v\n", err)
return
}
// 因为没有办法直接在文件中间插入内容,所以要借助一个临时文件
tmpFile, err := os.OpenFile("./sb.tmp", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("create tmp file failed, err:%v\n", err)
return
}
defer tmpFile.Close()
// 读取源文件写入临时文件
var ret [1]byte
n, err := fileObj.Read(ret[:])
if err != nil {
fmt.Printf("read from file failed, err:%v", err)
return
}
// 写入临时文件
tmpFile.Write(ret[:n])
// 再写入要插入的内容
var s []byte
s = []byte{'c'}
tmpFile.Write(s)
// 紧接着把源文件后续的内容写入临时文件
var x [1024]byte
for {
n, err := fileObj.Read(x[:])
if err == io.EOF {
tmpFile.Write(x[:n])
break
}
if err != nil {
fmt.Printf("read from file failed, err:%v\n", err)
return
}
tmpFile.Write(x[:n])
}
// 源文件后续的也写入了临时文件中
fileObj.Close()
tmpFile.Close()
os.Rename("./sb.tmp", "./sb.txt")
}

func main() {
f2()
}