Go语言从入门到实战 — 基础篇
First Go Program
编译 & 运行
基本程序结构
应用程序入口
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
退出返回值
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("Hello World")
os.Exit(-1)
}
获取命令行参数
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println(os.Args[0])
fmt.Println(os.Args[1])
fmt.Println("Hello World")
os.Exit(-1)
}
Test
编写测试程序
package test
import "testing"
func TestFirstTry(t *testing.T) {
t.Log("My first try!")
}
变量 & 常量
变量如果定义后未使用,则编译不通过,这是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()
}
变量赋值
// 变量交换
func TestExchange(t *testing.T) {
a := 1
b := 2
// 不需要tmp中间值
//tmp := a
//a = b
//b = tmp
a, b = b, a
t.Log(a, b)
}
常量定义
package constant_test
import "testing"
const (
Monday = 1 + iota
Tuesday
Wednesday
)
func TestConstantTry(t *testing.T) {
t.Log(Monday, Tuesday, Wednesday)
}
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)
}
数据类型
基本数据类型
-
byte
:uint8
别名,无符号8位整型 -
rune
:代表 Unicode 编码值,跟字符串string
相关,后面讲 comlpex
:复数有实部和虚部,complex64
的实部和虚部为32位,complex128
的实部和虚部为64位
- TODO 不知道怎么用
类型转换
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)
}
字符串
string
是值类型,其默认的初始化值为空字符串,而不是 nil
// 字符串
func TestString(t *testing.T) {
var s string
t.Log("*" + s + "*") // string默认是空字符串
t.Log(len(s))
}
修改字符串
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))
}
运算符
算术运算符
A := 10
B := 20
比较运算符
A := 10
B := 20
用 ==
比较数组
// 数组的比较
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)
}
逻辑运算符
位运算符
A := 60 // 0011 1100
B := 13 // 0000 1101
按位清零
Go的特性:
&^
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)
}
循环
代码示例
条件
if
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
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")
}
}
}
数组
数组的声明
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])
}
数组元素遍历
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)
}
}
数组截取
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)
}
切片
切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。
切片是一个引用类型(结构体),它的内部结构包含地址
、长度
和容量
。切片一般用于快速地操作一块数据集合。
切片内部结构
切片声明
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
}
切片如何实现可变长
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
,因为结构体指向的连续存储空间每次扩容都会发生变化,创建一个新的连续存储空间,把原来的值拷贝过来,所以需要重新赋值。
所以我们不需要关注切片创建的大小,使用起来很方便,但也不能无限制的利用切片的这种特性,如果数据过多那么拷贝的代价是非常大的。
切片共享存储结构
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)
}
数组 vs 切片
- 数组不可以伸缩,但可以比较。
- 切片可以伸缩,但不可以比较。
Map集合
map是一种无序的基于key-value
的数据结构,Go语言中的map是引用类型,必须初始化才能使用。
Map声明
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))
}
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.")
}
}
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)
}
}
Map与工厂模式
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))
}
实现Set
// 使用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)) // 元素个数
}
字符串
string
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数
}
Unicode & UTF8
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)
}
编码与存储
格式化输出
func TestStringToRune(t *testing.T) {
s := "中华人民共和国"
for _, c := range s {
t.Logf("%[1]c %[1]d", c) // [1]就是只和第1个参数匹配,以%c和%d格式化
}
}
常用函数
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))
}
函数
函数式编程
// 多个返回值的函数
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)) // 输出我们传入的值
}
可变参数
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))
}
延迟执行函数defer
类似于 java 的
try/finally
func Clear() {
fmt.Println("Clear resources.")
}
func TestDefer(t *testing.T) {
defer Clear() // 使用defer关键字调用Clear()函数 即使出现异常也可以保证释放资源
fmt.Println("Start")
panic("err") // Exception 抛异常
}