Eli's Blog

1. 数据类型

1.1 Go数据类型

类型 长度 默认值 说明
bool 1 false
byte 1 0 uint8的别名,相互不需要转换
int, uint 4, 8 0 默认整型,长度依平台而定,32或64
int8, uint8 1 0 -128 ~ 127, 0 ~ 255
int16, uint16 2 0
int32, uint32 4 0
int64, uint64 8 0
float32 4 0.0
float64 8 0.0 默认
complex64 8
complex128 16
rune 4 0 Unicode Code Point, int32的别名
uintptr 4, 8 0 存储指针的uint
string “” 默认值空字符串,而非nil
array 数组
struct 结构体
function nil
interface nil
map nil 字典,引用类型
slice nil 切片,引用类型
channel nil 通道,引用类型 |

1.2 值类型和引用类型

  • 值类型:基本数据类型(int, float, bool, string)、数组(array)和结构体(struct)

    • 变量直接存储,内存通常在栈中分配 (栈区:存放生命周期较短的数据)
  • 引用类型:ptr、slice、map、channel、interface

    • 变量存储的是一个地址,该地址对应的空间才真正存储数据。内存通常在堆上分配。当没有任何变量引用这个地址时,该地址对应的数据空间就成了垃圾,由GC来回收。(堆区:存放生命周期较长的数据。一个值类型,一般存储在栈区,但它如果在别的函数也用到,此时有可能放堆区,它要做逃逸分析)

2. 基本数据类型

2.1 const常量

2.1.1 常量特性:

  • readonly
  • cannot get address
1
2
3
4
5
6
7
8
const x = 0x100
y := &x // error

const x = 100
const y byte = x // ok, 相当于const y byte = 100

const x int = 100
const y byte = x // error,需强制转换

2.1.2 常量初始化和枚举:

iota: 常量计数器

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
const (
a = "A"
b // "A"
c = iota // 2
d // 3
)

const (
e = iota // 0
f
)

const (
SUN = iota
MON
TUE
WED
THU
FRI
SAT
)

const (
B float64 = 1 << (iota * 10)
KB
MB
GB
)

// 需要显示恢复
const (
a = iota // 0
b // 1
c = 100 // 100
d // 100
e = iota // 4
f // 5
)

2.2 数值类型

2.2.1 类型转换

必须显示转换,不支持像Java一样向上自动转换

1
2
3
4
byte   // uint8, 处理ASCII字符
rune // int32, 处理Unicode字符,比如中文

float64 // 系统默认类型,明确声明时,也推荐使用

2.2.2 运算符

1
2
3
4
5
6
7
8
9
10
11
12
// 除法
fmt.Println(10 / 4) // 2

var n1 float32 = 10 / 4
fmt.Println(n1) // 2

var n2 float32 = 10.0 / 4
fmt.Println(n2) // 2.5

// 取余 a % b = a - a / b * b
10 % 3 // 1
-10 % 3 // -1

2.2.3 两个变量,进行值交换,不允许使用中间变量

1
2
3
4
5
6
var a int = 3
var b int = 8

a = a + b
b = a - b // b = (a + b) - b = a
a = a - b // a = (a + b) - a = b

2.2.4 数值进制转换

1
2
3
4
5
6
7
8
9
func main() {
a, _ := strconv.ParseInt("1100100", 2, 32)
b, _ := strconv.ParseInt("0144", 8, 32)
c, _ := strconv.ParseInt("64", 16, 64)

println("0b" + strconv.FormatInt(a, 2))
println("0" + strconv.FormatInt(b, 8))
println("0x" + strconv.FormatInt(c, 16))
}

2.2.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
func main() {
a, _ := strconv.ParseInt("10100101", 2, 32)
b, _ := strconv.ParseFloat("3.1415926", 64)

fmt.Printf("%T, %v\n", a, a) // int64 165
fmt.Printf("%T, %v\n", b, b) // float64 3.1415926

fmt.Println("0x" + strconv.FormatInt(a, 16)) // 0xa5

c := string(65)
fmt.Printf("%T, %v\n", c, c) // string, A

d := int(c[0])
fmt.Printf("%T, %v\n", d, d) // int, 65

e := strconv.Itoa(65)
fmt.Printf("%T, %v\n", e, e) // string, 65

f, _ := strconv.Atoi(e)
fmt.Printf("%T, %v\n", f, f) // int, 65

g, _ := strconv.ParseBool("true")
fmt.Printf("%T, %v\n", g, g) // bool, true
}

2.2.6 处理浮点数

1
2
3
4
5
6
7
8
9
10
func main() {
var a float32 = 5.1234567890
var b float32 = 5.12345678
var c float32 = 5.123456789

println(a, b, c) // +5.123457e+000 +5.123457e+000 +5.123457e+000
println(a==b, a==c) // true true, 新版本go1.14.2,false, false

fmt.Printf("%v, %v, %v\n", a, b, c) // 5.123457, 5.123457, 5.123457
}

2.2.7 浮点数精度问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
a := 0.6
a += 0.7
fmt.Println(a) // 1.2999999999999998

b := truncate(a)
fmt.Println(b) // 1.3

c := round(a, 8)
fmt.Println(c)
}

func truncate(f float64) float64 {
str := fmt.Sprintf("%.8f", f)
fmt.Println(str) // 1.30000000
f, _ = strconv.ParseFloat(str, 64)
return f
}

func round(f float64, n int) float64 {
n10 := math.Pow10(n)
return math.Trunc((f+0.5/n10)*n10) / n10
}

2.3 字符串

本质:是一个不可变byte序列,本身是一个复合结构

1
2
3
4
type stringStruct struct {
str unsafe.Pointer
len int
}

头部指针指向字节数组,但没有NULL结尾

原始字符串raw string : 使用反引号”`”包裹,支持跨行

2.3.1 元素

允许索引方式访问元素,但不能获取元素地址

1
2
3
4
5
6
func main() {
s := "abc"

println(s[1])
println(&s[1]) // cannot take the address
}

2.3.2 切片

切片语法返回的子字符串,其内部依旧指向原始数组

  • reflect.StringHeader和string头结构相同
  • unsafe.Pointer用于指针类型转换
1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
s := "abcdefg"

s1 := s[:3]
s2 := s[1:4]
s3 := s[2:]

println(s1, s2, s3)

fmt.Printf("%#v\n", (*reflect.StringHeader)(unsafe.Pointer(&s)))
fmt.Printf("%#v\n", (*reflect.StringHeader)(unsafe.Pointer(&s1)))
fmt.Printf("%#v\n", (*reflect.StringHeader)(unsafe.Pointer(&s2)))
}

2.3.3 字符串遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
s := "中文"

// byte
for i := 0; i < len(s); i++ {
fmt.Printf("%d: [%c]\n", i, s[i])
}

// rune
for i, c := range s {
fmt.Printf("%d: [%c]\n", i, c)
}
}

2.3.4 修改字符串

字符串对象不可变,要修改,先将其转换为可变类型 []rune[]byte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
s := "hello world"

bs := []byte(s)
s1 = string(bs)

rs := []rune(s)
s2 := string(rs)

func toString(bs []byte) string {
return *(*string)(unsafe.Pointer(&bs))
}

// 该方法利用了[]byte和string头结构“部分相同”,以非安全的指针类型转换来实现类型“变更”,从而避免了底层数组复制。在很多Web Framework中都能看到此类做法,在高并发压力下,此种做法能有效改善执行性能。只是使用unsafe存在一定的风险,须小心谨慎!
s3 := toString(bs)

// 修改字符串
bs = append(bs, "abc"...)
s4 := string(bs)

2.3.5 处理Unicode

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
r := '中' // rune
s := string(r) // string
b := byte(r) // byte, 丢弃超出范围的bit

fmt.Printf("%T, %T, %T\n", r, s, b) // int32, string, uint8

s1 := string(b)
r2 := rune(b)

fmt.Println(s, b, s1, r2) // 中 45 - 45
}

2.3.6 截取字符串

1
2
3
4
5
6
7
func main() {
s := "中C文"
fmt.Println(len(s), utf8.RuneCountInString(s)) // 7 3

s = string(s[0:1] + s[3:4])
fmt.Println(s, utf8.ValidString(s))
}

2.3.7 字符串拼接性能

测试命令: go test -bench=.

1) 较差

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func test() string {
var s string

for i := 0; i < 10000; i++ {
s += "a"
}

return s
}

func BenchmarkTest(b *testing.B) {
for i := 0; i < b.N; i++ {
test()
}
}

2) 改进1 strings.Join(sa, "")

1
2
3
4
5
6
7
8
9
func test() string {
sa := make([]string, 10000)

for i := 0; i < len(sa); i++ {
sa[i] = "a"
}

return strings.Join(sa, "")
}

3) 改进2 byte.Buffer

1
2
3
4
5
6
7
8
9
10
func test() string {
var b bytes.Buffer
b.Grow(10000)

for i := 0; i < 10000; i++ {
b.WriteString("a")
}

return b.String()
}

2.3.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
len("abc")
r := []rune("中文") // 字符串遍历,同时支持处理中文

n, err := strconv.Atoi("123")
str := strconv.Itoa(123)

bytes := []byte("abc") // [97, 98, 99],二进制写入时有用
str := string([]byte{97, 98, 99}) // abc
str := strconv.FormatInt(123, 2) // 进制转换 base=2, 8, 16

b := strings.Contains("seafood", "foo")
count := strings.Count("seafood", "o")
b := strings.EqualFold("abc", "ABC") // 不区分大小写

n := strings.Index("go golang", "go") // 0
n := strings.LastIndex("go golang", "go") // 3

str := strings.Replace("go golang", "c", n) 替换个数n,n=-1表示全部

strArr := strings.Split("hello,world,ok", ",")

str := strings.toLower("Go")
str := strungs.toUpper("Go")

str := strings.TrimSpace(" I am a gopher, haha. ")
str := strings.Trim("!Hello World!", "!")
str := strings.TrimRight("!Hello World!", "!")
str := strings.TrimLeft("!Hello World!", "!")

b := strings.HasPrefix("http://google.com", "http")
b := strings.HasSuffix("index.html", "html")

2.3.9 获取中文字符串长度:

1
2
3
4
5
6
7
8
var str = "hello 你好"

len(str) // 12

import "unicode/utf8"
utf8.RuneCountInString(str) // 8

len([]rune(str)) // 8

2.4 数组

2.4.1 声明和初始化

1
2
3
4
5
6
7
8
9
10
11
12
// 声明
var balance [10]float32

// 初始化
var balance = [5]float32 {4.0, 1.3, 2.2, 3.9, 3.0}

var balance = [...]float32 {4.0, 1.3, 2.2}

balance[2] = 3.2

// 访问数组
var tag float32 = balance[1]

2.4.2 数组遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
var arr [10]int

for i := 0; i < 10; i++ {
arr[i] = i + 100
}

for j := 0; j < 10; j++ {
fmt.Printf("%d\t", arr[j])
}

fmt.Println()
}

2.4.3 二分查找

二分查找逻辑:

  • 数组必须有序arr
  • 中间的下标:midIndex = (firstIndex + lastIndex) / 2
  • 让arr[midIndex]与targetValue比较
    • arr[midIndex] > targetValue,返回firstIndex … (midIndex-1)
    • arr[midIndex] < targetValue,返回(midIndex+1) … lastIndex
    • arr[midIndex] == targetValue,找到
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
func main() {
a := []int{0, 3, 5, 8, 9, 12, 14, 16}

index := binarySearch(&a, 0, len(a)-1, 12)
if index == -1 {
fmt.Println("未找到")
} else {
fmt.Printf("找到,位置在 %d\n", index)
}
}

func binarySearch(a *[]int, left, right, target int) int {
if left >= right {
return -1
}

mid := (left + right) / 2

if (*a)[mid] > target {
return binarySearch(a, left, mid-1, target)
} else if (*a)[mid] < target {
return binarySearch(a, mid+1, right, target)
} else {
return mid
}
}

2.4.4 多维数组

只允许第一维缺省

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func main() {
a := [2][2]int {
{1, 2},
{3, 4},
}

b := [...][3]int {
{1, 2, 3},
{4, 5, 6},
}

c := [...][2][2]int {
{
{1, 2},
{3, 4},
},
{
{5, 6},
{7, 8},
},
}

fmt.Println(a, b, c)
}

2.4.5 数组长度

lencap都只返回一维数组长度

1
2
3
4
5
6
7
8
9
func main() {
a := [...][2]int {
{1, 2},
{3, 4},
{5, },
}

fmt.Println(len(a), cap(a), len(a[0]), cap(a[0]), len(a[2]), cap(a[2]))
}

2.4.6 比较操作

如元素类型支持“==、!=”操作符,那么数组也支持此操作。

前提:类型必须一致!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
var a, b [2]int
fmt.Println(a == b)

c := [2]int{1, 2}
d := [2]int{1, 3}
fmt.Println(c == d)

e := [3]int{1, 2, 3}
fmt.Println(c == e) // 类型不一致,无法比较

var x, y [2]map[string]int
fmt.Println(x == y) // map不支持==和!=操作,数组无法支持
}

2.4.7 指针数组和数组指针

  • 指针数组: 元素为指针类型的数组
  • 数组指针: 数组变量的地址
1
2
3
4
5
6
7
8
9
func main() {
x, y := 10, 20

a := [...]*int {&x, &y} // 指针数组
p := &a // 数组指针

fmt.Printf("%T %v\n", a, a)
fmt.Printf("%T %v\n", p, p)
}

使用指针操作数组:

1
2
3
4
5
6
7
8
9
func main() {
a := [...]int{1, 2}

fmt.Println(&a, &a[0], &a[1])

p := &a
p[1] += 3
fmt.Println(p, a)
}

2.4.8 数组复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func test1(x [2]int) {
fmt.Printf("x: %p, %v\n", &x, x)
}

func test2(y *[2]int) {
fmt.Printf("y: %p, %v\n", y, *y)
}

func main() {
a := [2]int{1, 2}

var b [2]int
b = a // 值复制,地址并未复制

fmt.Printf("a: %p, %v\n", &a, a)
fmt.Printf("b: %p, %v\n", &b, b)

test1(a) // 按值传递
test2(&a) // 按地址传递
}

2.4.9 排序算法

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
func main() {
arr := [...]int{5, 7, 8, 1, 2, 4, 9, 0, 3, 6}

//bubbleSort(arr[:])
//selectSort(arr[:])
//insertSort(arr[:])
quickSort(arr[:], 0, len(arr)-1)

fmt.Println(arr)
}

func bubbleSort(a []int) {
for i := 0; i < len(a); i++ {
for j := 1; j < len(a)-i; j++ {
// 相邻比较,交换位置
if a[j] < a[j-1] {
a[j], a[j-1] = a[j-1], a[j]
}
}
}
}

func selectSort(a []int) {
for i := 0; i < len(a); i++ {
for j := i + 1; j < len(a); j++ {
// 选择a[i]作为标兵,将它与i+1...的值比较,找到最小或最大,赋值给a[i]
if a[i] > a[j] {
a[i], a[j] = a[j], a[i]
}
}
}
}

func insertSort(a []int) {
// 假定第一个元素是有序的,后的元素与之比较,满足条件逐个插入
for i := 1; i < len(a); i++ {
for j := i; j > 0; j-- {
// 前一个元素大于后一个元素,跳过比较
if a[j] > a[j-1] {
break
}
a[j], a[j-1] = a[j-1], a[j]
}
}
}

func quickSort(a []int, left, right int) {
if left >= right {
return
}

// 选取一个元素,作为比较项
k := left
val := a[k]

for i := left + 1; i <= right; i++ {
// 比基准值小的摆放在基准前面,比基准值大的摆在基准的后面
if a[i] < val {
a[k] = a[i]
a[i] = a[k+1]
k++
}
}

a[k] = val

quickSort(a, left, k-1)
quickSort(a, k+1, right)
}