Go语言从入门到实战 — 基础篇

First Go Program

编译 & 运行

go语言吧 go语言从入门到实战_go语言吧

基本程序结构

go语言吧 go语言从入门到实战_字符串_02

应用程序入口

go语言吧 go语言从入门到实战_golang_03

package main

import "fmt"

func main() {
    fmt.Println("Hello World")
}

退出返回值

go语言吧 go语言从入门到实战_后端_04

package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Println("Hello World")
    os.Exit(-1)
}

go语言吧 go语言从入门到实战_数组_05

获取命令行参数

go语言吧 go语言从入门到实战_go语言吧_06

package main

import (
    "fmt"
    "os"
)

func main() {
	fmt.Println(os.Args[0])
	fmt.Println(os.Args[1])
	fmt.Println("Hello World")
	os.Exit(-1)
}

go语言吧 go语言从入门到实战_数组_07

Test

编写测试程序

go语言吧 go语言从入门到实战_字符串_08

package test

import "testing"

func TestFirstTry(t *testing.T) {
	t.Log("My first try!")
}

go语言吧 go语言从入门到实战_go语言吧_09

变量 & 常量

变量如果定义后未使用,则编译不通过,这是Go语言的特性,极大的减少无效内存。

实现Fibonacci数列

package fib

import (
    "fmt"
    "testing"
)

func TestFibList(t *testing.T) {
   	var a int = 1
    var b int = 1
    //var (
    //	a int = 1
    //	b     = 1
    //)
    //a := 1
    //b := 1
    fmt.Print(a)
    for i := 0; i < 5; i++ {
      	fmt.Print(" ", b)
      	tmp := a
      	a = b
        b = tmp + a
    }
    fmt.Println()
}

go语言吧 go语言从入门到实战_go语言吧_10

变量赋值

go语言吧 go语言从入门到实战_go语言吧_11

// 变量交换
func TestExchange(t *testing.T) {
    a := 1
    b := 2
    // 不需要tmp中间值
    //tmp := a
    //a = b
    //b = tmp
    a, b = b, a
    t.Log(a, b)
}

go语言吧 go语言从入门到实战_后端_12

常量定义

go语言吧 go语言从入门到实战_go语言吧_13

package constant_test

import "testing"

const (
    Monday = 1 + iota
    Tuesday
    Wednesday
)

func TestConstantTry(t *testing.T) {
    t.Log(Monday, Tuesday, Wednesday)
}

go语言吧 go语言从入门到实战_数组_14

package constant_test

import "testing"

const (
	Readable   = 1 << iota // 可读 最后一位为1
	Writable               // 可写 倒数第二位为1
	Executable             // 可执行 倒数第三位位3
)

func TestConstantTry1(t *testing.T) {
	a := 7 // 0111
	t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}

go语言吧 go语言从入门到实战_数组_15

数据类型

基本数据类型

go语言吧 go语言从入门到实战_golang_16

  • byteuint8 别名,无符号8位整型
  • rune:代表 Unicode 编码值,跟字符串 string 相关,后面讲
  • comlpex:复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位
  • TODO 不知道怎么用

类型转换

go语言吧 go语言从入门到实战_字符串_17

package type_test

import (
    "math"
    "testing"
)

type MyInt int64

func TestImplicit(t *testing.T) {
    var a int = 1
    var b int64
  	// 显示类型转换(不支持隐式转换)
    b = int64(a)
  	//b=a // Cannot use 'a' (type int) as the type int64
    var c MyInt
    c = MyInt(b) // 别名类型也不可以隐式转换
    t.Log(a, b, c)

    t.Log(math.MaxInt) // 类型的预定义值
}

指针

不支持🙅🏻♀️指针运算。

// 指针
func TestPoint(t *testing.T) {
    a := 1
    aPtr := &a
    //aPtr += 1 // go语言不支持指针运算
    t.Log(a, aPtr)
    t.Logf("%T %T", a, aPtr)
}

go语言吧 go语言从入门到实战_数组_18

字符串

string是值类型,其默认的初始化值为空字符串,而不是 nil

// 字符串
func TestString(t *testing.T) {
    var s string
    t.Log("*" + s + "*") // string默认是空字符串
    t.Log(len(s))
}

go语言吧 go语言从入门到实战_go语言吧_19

修改字符串

func TestChangeString(t *testing.T) {
	s1 := "big"
	// 强制类型转换
	byteS1 := []byte(s1)
	byteS1[0] = 'p'
	t.Log(string(byteS1))

	s2 := "白萝卜"
	runeS2 := []rune(s2)
	runeS2[0] = '红'
	t.Log(string(runeS2))
}

go语言吧 go语言从入门到实战_go语言吧_20

运算符

算术运算符

A := 10
B := 20

go语言吧 go语言从入门到实战_字符串_21

比较运算符

A := 10
B := 20

go语言吧 go语言从入门到实战_golang_22

== 比较数组

go语言吧 go语言从入门到实战_字符串_23

// 数组的比较
func TestCompareArray(t *testing.T) {
	a := [...]int{1, 2, 3, 4}
	b := [...]int{1, 3, 4, 5}
	//c := [...]int{1, 4, 5, 6, 7}
	d := [...]int{1, 2, 3, 4}
	t.Log(a == b)
	//t.Log(a == c) // 无法比较长度不同的数组
	t.Log(a == d)
}

go语言吧 go语言从入门到实战_数组_24

逻辑运算符

go语言吧 go语言从入门到实战_后端_25

位运算符

A := 60 // 0011 1100
B := 13 // 0000 1101

go语言吧 go语言从入门到实战_字符串_26

按位清零

Go的特性:&^

go语言吧 go语言从入门到实战_字符串_27

package operator_test

import "testing"

const (
	Readable   = 1 << iota // 可读 最后一位为1
	Writable               // 可写 倒数第二位为1
	Executable             // 可执行 倒数第三位位3
)

// 按位清零
func TestBitClear(t *testing.T) {
	a := 7
	a &^= Readable // 清零
	t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}

go语言吧 go语言从入门到实战_后端_28

循环

go语言吧 go语言从入门到实战_golang_29

代码示例

go语言吧 go语言从入门到实战_go语言吧_30

条件

if

go语言吧 go语言从入门到实战_数组_31

go语言吧 go语言从入门到实战_后端_32

func TestIfMultiSec(t *testing.T) {
	if a := 1 == 1; a {
		t.Log("1 == 1")
	}
	// 比较常用的if使用
	/*if v, err := someFun(); err == nil {
		t.Log(v)
	} else {
		t.Log("error!")
	}*/
}

switch

go语言吧 go语言从入门到实战_go语言吧_33

switch 中的 case 自带 break。

// go的switch支持多个变量
func TestSwitchMultiCase(t *testing.T) {
	for i := 0; i < 5; i++ {
		switch i {
		case 0, 2:
			t.Log("偶数")
		case 1, 3:
			t.Log("奇数")
		default:
			t.Log("not 0-3")
		}
	}
}

// go的switch还支持条件表达式
func TestSwitchCaseCondition(t *testing.T) {
	for i := 0; i < 5; i++ {
		switch {
		case i%2 == 0:
			t.Log("偶数")
		case i%1 == 0:
			t.Log("奇数")
		default:
			t.Log("not 0-3")
		}
	}
}

数组

数组的声明

go语言吧 go语言从入门到实战_go语言吧_34

func TestArrayInit(t *testing.T) {
	var arr [3]int
	arr1 := [4]int{1, 2, 3, 4}
	arr2 := [...]int{1, 3, 5, 7} // 如果不知道需要创建多大的空间 可用'...'
	t.Log(arr[1], arr1[1], arr2[1])

	arr3 := [...][3]int{{3, 4}, {1, 2, 3}} // 二维数组只有第一维可以用'...' 二维必须是确定的值
	t.Log(arr3[0][0])
}

数组元素遍历

go语言吧 go语言从入门到实战_字符串_35

func TestArrayTravel(t *testing.T) {
	arr := [...]int{1, 3, 4, 5}
	for i := 0; i < len(arr); i++ {
		t.Log(arr[i])
	}

    // 类似java增强for循环
	for idx, e := range arr {
		t.Log(idx, e)
	}
    
    // 如果不想要idx这个值,则可以使用下划线'_'(空标识符)
    // 空标识符可用于任何语法需要变量名但程序逻辑不需要的时候,
    // 例如, 在循环里,丢弃不需要的循环索引, 保留元素值。
    for _, e := range arr {
		t.Log(e)
	}
}

数组截取

go语言吧 go语言从入门到实战_go语言吧_36

func TestArraySection(t *testing.T) {
	arr := [...]int{1, 2, 3, 4, 5}
	arrSec := arr[:3]
	t.Log(arrSec)
	arrSec2 := arr[3:]
	t.Log(arrSec2)
    arrSec3 := arr[0:3] // [0, 3)
	t.Log(arrSec3)
}

go语言吧 go语言从入门到实战_golang_37

切片

切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。

切片是一个引用类型(结构体),它的内部结构包含地址长度容量。切片一般用于快速地操作一块数据集合。

切片内部结构

go语言吧 go语言从入门到实战_golang_38

切片声明

go语言吧 go语言从入门到实战_go语言吧_39

func TestSliceInit(t *testing.T) {
	var s0 []int // 声明方式很像数组,但它不需要声明大小,因为它是可变长的
	t.Log(len(s0), cap(s0))
	s0 = append(s0, 1)
	t.Log(len(s0), cap(s0))

	s1 := []int{1, 2, 3, 4}
	t.Log(len(s1), cap(s1))

	s2 := make([]int, 3, 5) // 使用make()函数,声明切片的长度为3,容量为5
	t.Log(len(s2), cap(s2))

	t.Log(s2[0], s2[1], s2[2]) // 只初始化了前3个元素
	//t.Log(s2[3], s2[4]) // runtime error: index out of range [3] with length 3
}

go语言吧 go语言从入门到实战_后端_40

切片如何实现可变长

func TestSliceGrowing(t *testing.T) {
	var s []int
	for i := 0; i < 10; i++ {
		s = append(s, i)
		t.Log(len(s), cap(s))
	}
}

空间不够自动扩容,每次扩容为原来的2倍。

所以为什么append()函数要写成这样:s = append(s, i),重新赋值给s,因为结构体指向的连续存储空间每次扩容都会发生变化,创建一个新的连续存储空间,把原来的值拷贝过来,所以需要重新赋值。

所以我们不需要关注切片创建的大小,使用起来很方便,但也不能无限制的利用切片的这种特性,如果数据过多那么拷贝的代价是非常大的。

go语言吧 go语言从入门到实战_数组_41

切片共享存储结构

go语言吧 go语言从入门到实战_后端_42

func TestSliceShareMemory(t *testing.T) {
	year := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
	Q2 := year[3:6]
	t.Log(Q2, len(Q2), cap(Q2))

	summer := year[5:8]
	t.Log(summer, len(summer), cap(summer))

	summer[0] = "UnKnow"
	t.Log(Q2)
}

go语言吧 go语言从入门到实战_字符串_43

数组 vs 切片

go语言吧 go语言从入门到实战_字符串_44

  • 数组不可以伸缩,但可以比较。
  • 切片可以伸缩,但不可以比较。

Map集合

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

Map声明

go语言吧 go语言从入门到实战_数组_45

func TestMapInit(t *testing.T) {
	m1 := map[int]int{1: 1, 2: 4, 3: 9}
	t.Log(m1[2])
	t.Logf("len m1 = %d", len(m1))
	m2 := map[int]int{}
	m2[4] = 16
	t.Logf("len m2 = %d", len(m2))
	m3 := make(map[int]int, 10)
	t.Logf("len m3 = %d", len(m3))
}

go语言吧 go语言从入门到实战_后端_46

Map中value的默认值

/*
  我们获取map的key有两种情况
  1.key不存在
  2.key存在,但value为空
*/
func TestAccessNotExistingKey(t *testing.T) {
	m := map[int]int{}
	t.Log(m[1]) // 不存在的value值,为0
	m[2] = 0    // 我们将key为2的value设置为0
	t.Log(m[2]) // value为0
	// 也就是说go中的map,无论value是否存在,他们都会被默认赋值为0
	// 这就跟java不一样了,java会返回null,则会造成空指针异常,而go就不会出现这个问题
	// 但我们无法判断他是默认值还是我们赋的值,所以go需要我们自己判断这个值是否存在
	if v, ok := m[3]; ok {
		t.Logf("key 3's value is %d", v)
	} else {
		t.Log("key 3 is not existing.")
	}
}

go语言吧 go语言从入门到实战_golang_47

Map的遍历

func TestTravelMap(t *testing.T) {
	m1 := map[int]int{1: 1, 2: 4, 3: 9}
	for k, v := range m1 {
		t.Log(k, v)
	}
}

go语言吧 go语言从入门到实战_字符串_48

Map与工厂模式

go语言吧 go语言从入门到实战_go语言吧_49

func TestMapWithFunValue(t *testing.T) {
	m := map[int]func(op int) int{}
	m[1] = func(op int) int { return op }
	m[2] = func(op int) int { return op * op }
	m[3] = func(op int) int { return op * op * op }
	t.Log(m[1](2), m[2](2), m[3](2))
}

go语言吧 go语言从入门到实战_字符串_50

实现Set

go语言吧 go语言从入门到实战_后端_51

// 使用map构建set
func TestMapForSet(t *testing.T) {
	mySet := map[int]bool{} // key的类型自己设定,value的类型只能是bool类型
	mySet[1] = true
	// 所以我们判断元素是否存在则直接判断value是否为true
	for i := 1; i < 3; i++ {
		if mySet[i] {
			t.Logf("%d is existing", i)
		} else {
			t.Logf("%d is not existing", i)
		}
	}
	delete(mySet, 1) // 删除元素
	t.Log(mySet[1] == true)
    t.Log(len(mySet)) // 元素个数
}

go语言吧 go语言从入门到实战_golang_52

字符串

string

go语言吧 go语言从入门到实战_后端_53

func TestString(t *testing.T) {
	var s string
	t.Log(s) // 初始化默认零值""
	s = "hello"
	t.Log(len(s))
	//s[1] = '3' // string是不可变的byte slice [cannot assign to s[1]]
	s = "\xE4\xB8\xA5" // 可以存储任何二进制数据
	t.Log(s)
	t.Log(len(s))
	s = "中"
	t.Log(len(s)) // 是byte数
}

go语言吧 go语言从入门到实战_go语言吧_54

Unicode & UTF8

go语言吧 go语言从入门到实战_后端_55

func TestString(t *testing.T) {
	var s string
	s = "中"
	t.Log(len(s)) // 是byte数

	c := []rune(s) // rune 可以取出字符串里的unicode
	t.Log(len(c))
	// t.Log("rune size:", unsafe.Sizeof(c[0]))
	t.Logf("中 unicode %x", c[0])
	t.Logf("中 UTF8 %x", s)
}

go语言吧 go语言从入门到实战_数组_56

编码与存储

go语言吧 go语言从入门到实战_字符串_57

格式化输出

func TestStringToRune(t *testing.T) {
	s := "中华人民共和国"
	for _, c := range s {
		t.Logf("%[1]c %[1]d", c) // [1]就是只和第1个参数匹配,以%c和%d格式化
	}
}

go语言吧 go语言从入门到实战_后端_58

常用函数

func TestStringFun(t *testing.T) {
	s := "A,B,C"
	parts := strings.Split(s, ",") // 分割
	for _, part := range parts {
		t.Log(part)
	}
	t.Log(strings.Join(parts, "-")) // 拼接

	s = strconv.Itoa(10)     // 整数转字符串
	t.Log(reflect.TypeOf(s)) // 利用反射查看元素类型
	a, _ := strconv.Atoi(s)  // 字符串转整形 会额外返回一个错误值
	t.Log(reflect.TypeOf(a))
}

函数

函数式编程

go语言吧 go语言从入门到实战_golang_59

// 多个返回值的函数
func returnMultiValues() (int, int) {
	return rand.Intn(10), rand.Intn(20)
}

// 计算函数操作的时长
func timeSpend(inner func(op int) int) func(op int) int {
	// 类似装饰者模式,对原来的函数进行了一层包装
	return func(n int) int {
		start := time.Now()
		ret := inner(n)
		fmt.Println("time spend:", time.Since(start).Seconds())
		return ret
	}
}

// 休眠1s的函数
func slowFun(op int) int {
	time.Sleep(time.Second * 1)
	return op
}

func TestFunc(t *testing.T) {
	a, _ := returnMultiValues()
	t.Log(a)

	tsSF := timeSpend(slowFun) // 调用计算时长方法 传入一个函数
	t.Log(tsSF(10)) // 输出我们传入的值
}

可变参数

go语言吧 go语言从入门到实战_go语言吧_60

func Sum(ops ...int) int {
	ret := 0
	for _, op := range ops {
		ret += op
	}
	return ret
}

func TestVarParam(t *testing.T) {
	t.Log(Sum(1, 2, 3))
	t.Log(Sum(1, 2, 3, 4, 5))
}

go语言吧 go语言从入门到实战_后端_61

延迟执行函数defer

类似于 java 的 try/finally

go语言吧 go语言从入门到实战_字符串_62

func Clear() {
	fmt.Println("Clear resources.")
}

func TestDefer(t *testing.T) {
    defer Clear() // 使用defer关键字调用Clear()函数 即使出现异常也可以保证释放资源
	fmt.Println("Start")
	panic("err") // Exception 抛异常
}

go语言吧 go语言从入门到实战_数组_63