基础语法

命名规范

变量名、函数名、常量名采用驼峰法

如果变量名、函数名、常量名首字母大写,则可以被其他的包访问,如果首字母小写,则只能在本包中使用,可以理解为首字母大写是公开,小写是私有的,方法名也遵守这个规则

包名和文件名一般为小写字母

如果一个类型实现了String()方法,fmt.Println默认会调用该方法进行输出

原码、反码、补码

  • 最高位表示符号位,0正数,1负数
  • 正数的原码、反码、补码都一样
  • 负数的反码=它的原码符号位不变,其他位取反
  • 负数的补码=它的反码+1
  • 计算机运算时,都是以补码的方式来运算

数据类型

整形:

  • int8:有符号整型,占1个字节存储空间,范围:-128到127

  • int16:占2个字节

  • int32: 占4个字节

  • int64:占8个字节

  • uint8:无符号整型,范围:0-255

  • uint16:

  • uint32:

  • uint64:

  • int:有符号,32位系统占4个字节,64位系统占8个字节,默认推导类型

  • uint:无符号,32位系统占4个字节,64位系统占8个字节

  • rune:有符号,和int32一样,表示一个Unicode码

  • byte:无符号,和uint8一样,当要存储字符时选用byte

  • uintprt: 可以保存任意指针的位模式的整数类型

浮点数都是有符号的

  • float32:IEEE-754 32位浮点型数, 4个字节
  • float64:IEEE-754 64位浮点型数 ,8个字节,默认推导类型
  • complex64:32 位实数和虚数
  • complex128:64 位实数和虚数

布尔类型:bool 占用1个字节

字符类型:在Go中,字符的本质是一个整数,直接输出是该字符对应的UTF-8码值

string类型:Go的string底层是用byte数组实现的,一但赋值就不能再修改了

  • 查看一个变量的数据类型使用reflect.TypeOf(x)

  • Go在不同类型之间赋值时需要显式转换,语法 T(v),将值v转换为类型T,T比如int32、int64、float32

  • 基本类型和string的转换,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 方式1
    var num int = 99
    var str string
    str = fmt.Sprintf("%d", num)

    // 方式2
    strconv包函数
    strconv.FormatInt...
    strconv.ParseInt...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 默认值
整型:0
浮点:0
字符串:“”
布尔:false

var x float64 = 3.4
var y string = "abc"
var c int = 100
var b = false

var age byte = 90
var c1 byte = 'a' // 字符a,输出时则是其ASCII值
var c2 int = 22269 // 该值对应utf-8的‘国’
var n1 = 10 + 'a' // 10+97=107

var num = 5.12e2 // 5.12 * 10 的2次方
var num = 5.12E2 // 5.12 * 10 的2次方
var num = 5.12E-2 // 5.12 * 10 的-2次方

// 类型转换
var i int32 = 100
var n1 float32 = float32(i) //将int转换成float

rune用法

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

import (
"fmt"
"unicode/utf8"
)

func main() {
var str = "hello 中国"

// golang的string底层是用byte数组实现的
// 直接求len实际就是求字节数,utf8下一个汉子占3个字节,所以是12
fmt.Printf("len: %v \n", len(str)) // 12

// rune等同于int32,但是它用来区分字符值,如下把两个汉子算成2个长度
fmt.Printf("len: %v \n", len([]rune(str))) // 8

// 此处是用utf8包下的函数求字符串长度
fmt.Printf("len: %v \n", utf8.RuneCountInString(str)) // 8
}

值类型和引用类型

1
2
值类型:int系列、float系列、bool、string、数组、结构体
引用类型:指针、slice切片、map、管道chan、interface

new/make

new: 用来分配值类型内存

make:用来分配引用类型内存

1
2
var num *int = new(int) //返回指针*int,是一个内存地址
*num = 100 // 赋值100

变量

1
2
3
4
5
6
7
8
// 1. 指定变量类型,声明后若不赋值,使用默认值0
var a int

// 2. 根据值自行判定变量类型
var b = 10

// 3. 省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误
c : = 10

多变量声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 无初始值
var x, y int

// 有初始值
var c, d int = 1, 2
var e, f = 123, "liudanbing"

// 这种分解的写法,一般用于声明全局变量
var (
a int
b bool
)

//如下这种不带声明格式的只能在函数体内声明
//g, h := 123, "需要在func函数体内实现"

常量

常量的数据类型只可以是布尔、数字型(整数、浮点和复数)、字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 显式类型定义
const b string = "abc"

// 隐式类型定义
const b = "abc"

const (
Unknown = 0
Female = 1
Male = 2
)

// 函数必须是内置函数,否则编译不过
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
)

自增长iota

简化了常量用于增长数字的定义

1
2
3
4
5
const ( 
CategoryBooks = iota // 0
CategoryHealth // 1
CategoryClothing // 2
)

自增长常量经常包含一个自定义类型

1
2
3
4
5
6
7
type Stereotype int //这是一个自定义类型 Stereotype 实际上就是 int类型
const (
TypicalNoob Stereotype = iota // 0
TypicalHipster // 1
TypicalUnixWizard // 2
TypicalStartupFounder // 3
)

另外,我们可以使用下划线跳过不想要的值

1
2
3
4
5
6
7
8
9
type AudioOutput int 
const (
OutMute AudioOutput = iota // 0
OutMono // 1
OutStereo // 2
_
_
OutSurround // 5
)

用于表达式,在 Go 语言的spec中, 这就是所谓的隐性重复最后一个非空的表达式列表.

1
2
3
4
5
6
7
8
type Allergen int 
const (
IgEggs Allergen = 1 << iota // 1 << 0 which is 00000001
IgChocolate // 1 << 1 which is 00000010
IgNuts // 1 << 2 which is 00000100
IgStrawberries // 1 << 3 which is 00001000
IgShellfish // 1 << 4 which is 00010000
)

省略第一个值

1
2
3
4
5
6
7
8
9
10
11
12
type ByteSize float64 
const (
_ = iota // ignore first value
KB ByteSize = 1 << (10 * iota) // 1 << (10*1)
MB // 1 << (10*2)
GB // 1 << (10*3)
TB // 1 << (10*4)
PB // 1 << (10*5)
EB // 1 << (10*6)
ZB // 1 << (10*7)
YB // 1 << (10*8)
)

当你在把两个常量定义在一行的时候会发生什么?

Banana 的值是什么? 2 还是 3? Durian 的值又是?

1
2
3
4
5
6
7
8
9
10
11
12
const ( 
Apple, Banana = iota + 1, iota + 2
Cherimoya, Durian
Elderberry, Fig
)

// Apple: 1
// Banana: 2
// Cherimoya: 2
// Durian: 3
// Elderberry: 3
// Fig: 4

运算符

1
2
3
4
5
6
7
8
9
A = 0011 1100 
B = 0000 1101
-----------------
A&B = 0000 1100 // 按位与:对应位上都是1时则是1
A|B = 0011 1101 // 或:对应位上有一个是1时则是1
A^B = 0011 0001 // 异或:对应位上相同时为0,对应的二进位相异时为1
~A = 1100 0011 // 非:每个位取相反
A<<2 = 1111 0000 // 左移:十进制为240,左移n位就是乘以2的n次方,高位丢弃,低位补0
A>>2 = 0000 1111 // 右移:十进制为15,右移n位就是除以2的n次方

if语句

支持在if中,直接定义一个变量,然后再写一个判断语句

1
2
3
if age := 20; age > 18 {
....
}

Swtich语句

  • swtich后可以直接定义一个变量,分号结束

  • 不需要加break,程序执行完case语句后,会直接退出switch,如果需要依次执行下一个case,则可以在case语句块后增加fallthrough,称为switch穿透

  • Type switch:来判断某个interface变量中实际指向的变量类型

1
2
3
4
5
6
7
8
9
10
11
var x interface{}
var y = 10.0
x = y
switch i := x.(type) {
case nil:
...
case int:
...
case float64:
...
}

for语句

Go语言的For循环有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
25
26
27
28
29
30
31
32
package main

import (
"fmt"
)

func main() {
// 第一种
for a := 0; a < 10; a++ {
fmt.Printf("a 的值为: %d\n", a)
}

// 第二种
// for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环
b := [6]int{1, 2, 3, 5}
for i, x := range b {
fmt.Printf("第%d 位x的值 = %d\n", i, x)
}

// 第三种
var c int
var d int = 15
for c < d {
c++
fmt.Printf("c 的值为: %d\n", c)
}

// 第四种,无限循环,需配合break使用
for {

}
}
  • break 语句 经常用于中断当前 for 循环或跳出 switch 语句 ,如果在多层嵌套语句中,可以通过标签指明要终止哪一层语句块,默认是跳出最近的for循环
1
2
3
4
5
6
7
8
lable2:
for i := 0; i<4; i++ {
for j := 0; j<10; j++ {
if j==2 {
break lable2
}
}
}
  • continue 语句 跳过当前循环的剩余语句,然后继续进行下一轮循环。 也可以使用标签指明要跳过的是哪一层循环,规则和break一样
  • goto 语句无条件将控制转移到被标记的语句
1
2
3
4
5
6
7
8
9
10
11
	var a int = 10

LOOP:
for a < 20 {
if a == 15 {
a = a + 1
goto LOOP
}
fmt.Printf("a的值为:%d\n", a)
a++
}

输出如下,跳过了a=15时的迭代:

1
2
3
4
5
6
7
8
9
a的值为:10
a的值为:11
a的值为:12
a的值为:13
a的值为:14
a的值为:16
a的值为:17
a的值为:18
a的值为:19

golang没有while语句

错误机制

defer、panic、recover

Go中可以抛出一个panic的异常,然后在defer中通过recover捕获,然后正常处理

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

import (
"fmt"
)

/**
使用defer+recover 处理异常
*/
func test() {
defer func() {
fmt.Println("enter defer")
err := recover() // 内置函数,可以捕获到异常
if err != nil {
fmt.Println("err= ", err)
fmt.Println("发送邮件给admin@sohu.com")
}
}()

n1 := 10
n2 := 0
res := n1 / n2
fmt.Println("res= ", res)
}

func main() {
test()
}

自定义错误

  • errors.New(“错误说明”),会返回一个error类型,表示一个错误
  • panic内置函数,接受一个interface{}类型的值,即任何类型,可以接受error类型,输出错误信息,并退出程序
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 (
"errors"
"fmt"
)

func readConf(name string) error {
if name == "init.conf"{
// 读取
return nil
} else {
// 返回一个自定义错误
return errors.New("读取文件错误")
}
}

func test1() {
err := readConf("ini.conf")
if err != nil {
// 抛出错误,终止程序
panic(err)
}

fmt.Println("test1 end ...")
}

func main() {
test1()
fmt.Println("main() ...")

}

函数

  • 基本数据类型和数组默认都是值传递的,在函数中修改,不会影响原来的值

  • 函数不支持重载

  • 支持可变参数,如果一个函数形参中有可变参数,要放到最后

1
2
3
4
// args是slice切片,通过args[index]可以访问到各个值
func sum(args... int) int {
....
}
  • Go 函数可以返回多个值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
// "reflect"
)

func main() {
a, b := swap("MA", "Ku")
fmt.Println(a, b)

}

func swap(x, y string) (string, string) {
return y, x
}

引用传递

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

import "fmt"

func main() {
var a int = 100
var b int = 200

fmt.Printf("交换前,a 的值 : %d\n", a)
fmt.Printf("交换前,b 的值 : %d\n", b)

swap(&a, &b)

fmt.Printf("交换后,a 的值 : %d\n", a)
fmt.Printf("交换后,b 的值 : %d\n", b)
}

func swap(x *int, y *int) {
var temp int
temp = *x // 保存x地址上的值
*x = *y // 将y值赋给x
*y = temp // 将temp值给y
}

输出:
交换前,a 的值 : 100
交换前,b 的值 : 200
交换后,a 的值 : 200
交换后,b 的值 : 100

函数作为值

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

import (
"fmt"
"math"
)

func main() {
getSquareRoot := func(x float64) float64 {
return math.Sqrt(x)
}

fmt.Println(getSquareRoot(9))
}

输出:3

闭包

Go 语言支持匿名函数,可作为闭包。

闭包就是一个匿名函数和其相关的引用环境组合的一个整体。

以下实例中,我们创建了函数 getSequence() ,返回另外一个匿名函数,该匿名函数引用到函数外的i变量,因此这个匿名函数就和i变量形成了一个整体,构成闭包。当我们反复调用nextNumber函数时,因为n是初始化一次,所以每调用一次就进行累计

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

import (
"fmt"
)

// func() int是指返回类型
func getSequence() func() int {
i := 0
return func() int {
i += 1
return i
}
}

func main() {
// nextNumber 为一个函数,函数 i 为 0
nextNumber := getSequence()

// 调用 nextNumber 函数,i 变量自增 1 并返回
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber())

//创建新的函数 nextNumber1,并查看结果
nextNumber1 := getSequence()
fmt.Println(nextNumber1())
fmt.Println(nextNumber1())
}

输出:
1
2
3
1
2

main函数和init函数

Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数 (只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。

虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的 可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。

Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个 package中的init函数都是可选的,但package main就必须包含一个main函数。

程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在 编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次 (例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多 次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进 来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的 话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常 量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。

如果一个文件同时包含全局变量定义,init() main()则执行顺序是:

全局变量定义 –> init函数 –>main函数

defer函数

当执行到defer时,暂时不执行,会将defer后面语句压入到独立的栈,defer栈

当函数执行完毕后,再从defer栈,按照先入后出的方式出栈执行

在defer将语句放入到栈时,也会将相关的值拷贝同时入栈

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

import "fmt"

func sum(n1 int, n2 int) int {
defer fmt.Println("ok1 n1 = ", n1)
defer fmt.Println("ok2 n2 = ", n2)

n1++
n2++
res := n1 + n2
fmt.Println("ok3 res=", res)
return res
}

func main() {
res := sum(10, 20)
fmt.Println("res ", res)
}

输出:
ok3 res= 32
ok2 n2 = 20
ok1 n1 = 10
res 32

指针

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

import (
"fmt"
)

func main() {

var a int = 20
var ip *int // 声明指针变量

ip = &a // 指针存储变量a值的地址

fmt.Printf("a 变量的地址是: %x\n", &a)
fmt.Printf("ip 变量储存的指针地址: %x\n", ip)
fmt.Printf("*ip 变量的值: %d\n", *ip)
}


输出:
a 变量的地址是: c00000e1c8
ip 变量储存的指针地址: c00000e1c8
*ip 变量的值: 20

空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil。nil 指针也称为空指针。

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。

一个指针变量通常缩写为 ptr。

1
2
3
4
5
var ptr *int 
fmt.Printf("ptr 的值为 : %x\n", ptr )

if(ptr != nil) /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */

指针数组

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"
)

const MAX int = 3

func main() {

a := []int{10, 20, 30}

var ptr [MAX]*int

for i := 0; i < MAX; i++ {
ptr[i] = &a[i]
}

for i := 0; i < MAX; i++ {
fmt.Printf("a[%d] = %d\n", i, *ptr[i])
}
}

输出:
a[0] = 10
a[1] = 20
a[2] = 30

数组和切片

数组

  • 一个数组一但定义,长度固定,不能动态改变
  • 数组创建后,如果没有赋值,有默认值0
  • 注意数组是值传递,如果想在其他函数中修改原数组数据,需要传地址
  • 数组作为函数形参时要写上长度,实参和形参的长度也要一样
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"

func main() {
// 声明即开辟了内存空间
var arr [3]int
fmt.Println("arr: ", arr)

// 初始化
var arr1 [3]int = [3]int{1,2,3}
fmt.Println("arr: ", arr1)

var arr2 = [3]int{5,6,7}
fmt.Println("arr: ", arr2)

var arr3 = [...]int{8,9,10}
fmt.Println("arr: ", arr3)

var arr4 = [...]int{1: 800, 0: 900, 2:999}
fmt.Println("arr: ", arr4)

arr5 := [...]string{1: "tom", 0: "jack"}
fmt.Println("arr: ", arr5)

// 遍历
for i:=0; i<len(arr1); i++ {
fmt.Printf("arr1: %v=%v\n", i, arr1[i])
}

for index, val := range arr2 {
fmt.Printf("arr2: %v=%v\n", index, val)
}
}

切片Slice

切片是一个动态数组,属于引用传递

1
2
3
4
5
6
7
8
9
10
11
// 空切片,定义完不能使用,要让其引用一个数组,或者make一个内存空间来使用
var numbers []int

var slice1 []type = make([]type, len)
也可以简写为
slice1 := make([]type, len)

也可以指定容量,其中capacity为可选参数
make([]type, length, capacity)

这里 len 是数组的长度并且也是切片的初始长度

切片初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3
// s := []int{1, 2, 3}

// 初始化切片s,是数组arr的引用
// s := arr[:]

// 将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片
// s := arr[startIndex:endIndex]

// 缺省endIndex时将表示一直到arr的最后一个元素
// s := arr[startIndex:]

// 缺省startIndex时将表示从arr的第一个元素开始
// s := arr[:endIndex]

// 通过切片s初始化切片s1
// s1 := s[startIndex:endIndex]

切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

切片截取

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"
)

func main() {

/* 创建切片 */
numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
printSlice(numbers)

/* 打印原始切片 */
fmt.Println("numbers ==", numbers)

/* 打印子切片从索引1(包含) 到索引4(不包含)*/
fmt.Println("numbers[1:4] ==", numbers[1:4])

/* 默认下限为 0*/
fmt.Println("numbers[:3] ==", numbers[:3])

/* 默认上限为 len(s)*/
fmt.Println("numbers[4:] ==", numbers[4:])

numbers1 := make([]int, 0, 5)
printSlice(numbers1)

/* 打印子切片从索引 0(包含) 到索引 2(不包含) */
number2 := numbers[:2]
printSlice(number2)

/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
number3 := numbers[2:5]
printSlice(number3)
}

func printSlice(x []int) {
fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}

输出:
len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
numbers == [0 1 2 3 4 5 6 7 8]
numbers[1:4] == [1 2 3]
numbers[:3] == [0 1 2]
numbers[4:] == [4 5 6 7 8]
len=0 cap=5 slice=[]
len=2 cap=9 slice=[0 1]
len=3 cap=7 slice=[2 3 4]

append() 函数

append操作的本质就是对数组扩容,底层会新建一个数组,将原来元素拷贝过去,再继续加新元素,最后把新数组地址重新引用到

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"
"reflect"
)

func main() {
var arr [5]int = [...]int{1,2,3,4,5}
slice1 := arr[:]

slice2 := slice1[1: 2]
fmt.Printf("slice1 = %v \nslice2 = %v\n", slice1[1], slice2[0])

slice2[0] = 30
fmt.Printf("slice1 = %v \nslice2 = %v\n", slice1[1], slice2[0])

slice2 = append(slice2, 7, 8, 9,8,7,6)
fmt.Println(slice2) //[30 7 8 9 8 7 6]

slice1 = append(slice1, 7, 8, 9,8,7,6)
fmt.Println(reflect.TypeOf(slice1))
fmt.Println(slice1)// [1 30 3 4 5 7 8 9 8 7 6]

}

string和slice

  • string底层是一个byte数组,因此也可以进行切片处理-
  • string是不可变的,也就是说不能用str[0]=’z’来修改

map

  • 声明不会分配内存,初始化需要make分配内存,之后才能赋值和使用
  • map是引用传递
  • map容量到达后,会自动扩容
  • map的value可用struct类型
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
package main

import "fmt"

func main() {
// 创建
var map1 map[string]string
map1 = make(map[string]string, 10)
map1["one"] = "111"
fmt.Println(map1)

map2 := map[int]string{
1: "one",
2: "tow",
}
fmt.Println(map2)

// 删除
delete(map2, 1)
fmt.Println(map2)

// 获取
val, ok := map2[2]
if ok {
fmt.Printf("ok: %v\n", val)
} else {
fmt.Println("ok no")
}


/**
一个map存放学生信息,每个学生有name和sex
*/
stuMap := make(map[string]map[string]string)
stuMap["stu01"] = make(map[string]string, 2)
stuMap["stu01"]["name"] = "tom"
stuMap["stu01"]["sex"] = "man"

fmt.Println(stuMap)
fmt.Println(stuMap["stu01"])
fmt.Println(stuMap["stu01"]["name"])
}

map切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// map切片
ms := make([]map[string]string, 1)

ms[0] = make(map[string]string)
ms[0]["name"] = "jack"
ms[0]["age"] = "88"

fmt.Println(ms)

m1 := make(map[string]string)
m1["name"] = "jane"
m1["age"] = "77"
ms = append(ms, m1)
fmt.Println(ms)

map排序

  • go中没有方法对map进行排序
  • map默认是无序的,也不是按照添加的顺序存放,每次遍历输出可能都不一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	// map排序
mu := make(map[int]string, 3)
mu[9] = "nice"
mu[5] = "five"
mu[3] = "three"

var keys = make([]int, len(mu))
for k,_ := range mu{
keys = append(keys, k)
}

sort.Ints(keys)
fmt.Println(keys)

for _, v := range keys {
fmt.Printf("map[%v] = %v\n", v, mu[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
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import "fmt"

type Student struct {
Name string
Age int
}

func main() {

// 结构体值传递
stu1 := Student{
Name: "Tom",
Age: 5,
}

stu2 := stu1
stu2.Name = "Jack"
stu2.Age = 77
fmt.Printf("stu1: %v\nstu2: %v\n", stu1, stu2)

// 创建方式1
var s1 Student
s1.Name = "xxx"
fmt.Println(s1)

// 创建方式2
s2 := Student{
Name: "xxx",
Age: 2,
}
fmt.Println(s2)

// 创建方式3
var stu3 *Student = new(Student)
stu3.Name = "john" // go底层对stu3做了取值运算(*stu3).Name
(*stu3).Age = 88 // ok
fmt.Printf("stu3: %v\n", *stu3)

// 方式4
var s4 *Student = &Student{}
s4.Name = "xx" // go底层为了方便默认进行取值运算s4
fmt.Println(*s4)

}

  • 结构体可以和其他类型转换,需要有完全相同的字段(名字,个数,类型)
1
2
3
4
5
6
7
8
9
10
11
12
type A struct {
Num int
}
type B struct {
Num int
}
func main() {
var a A
var b B
a = A(b)
fmt.Println(a, b)
}
  • struct的每个字段上,可以写一个tag,可以通过反射机制获取,常见场景就是序列化
  • Go的结构体没有构造函数,通常用工厂模式来解决,如果结构体首字母是大写,则可以引包创建,如果是小写字母,要创建只能用工厂模式来解决
1
2
3
4
5
6
7
8
9
10
11
12
// 工厂模式实现跨包创建结构体实例
type student struct {
Name string
Score float64
}

func NewStudent(n string, s float64) *student {
return &student{
Name: n,
Score: s,
}
}

方法

  • 会将调用方法的变量,当做参数也传递给方法
  • 方法名首字母小写,只能在包内访问,大写可以在其他包访问
  • 不管调用方式如何,真正决定是值传递还是地址传递,是看方法的接收者是和哪种类型绑定,如p Person是值拷贝不会影响原值,p *Person是地址传递会影响
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"math"
)

type Point struct{ X, Y float64 }

// 这是给struct Point类型定义一个方法
// 前个附加的参数p,叫做方法的接收器(receiver)
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}

func main() {
p := Point{1, 2}
q := Point{4, 6}

fmt.Println(p.Distance(q)) //通过p调用方法
}

基于指针接受类型的方法

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 main

import (
"fmt"
"math"
)

type T struct {
name string
}

func (t T) method1() {
t.name = "new name1"
}

func (t *T) method2() {
t.name = "new name2"
}

func main() {

t := T{"old name"}

fmt.Println("method1 调用前 ", t.name)
t.method1()
fmt.Println("method1 调用后 ", t.name)

fmt.Println("method2 调用前 ", t.name)
t.method2()
fmt.Println("method2 调用后 ", t.name)
}

输出:
method1 调用前 old name
method1 调用后 old name
method2 调用前 old name
method2 调用后 new name2

方法值

在一个包的API需要一个函数值、且调用方希望操作的是某一个绑定了对象的方法 的话,方法”值”会非常实用.

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"
"math"
)

type Point struct{ X, Y float64 }

func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}

func main() {

p := Point{1, 2}
q := Point{4, 6}

// 方法值,类似C语言的函数指针
// 实际上distanceFormP 就绑定了 p接收器的方法Distance
distanceFormP := p.Distance

fmt.Println(distanceFormP(q)) // 5
fmt.Println(p.Distance(q)) // 5

// 实际上distanceFormQ 就绑定了 q接收器的方法Distance
distanceFormQ := q.Distance

fmt.Println(distanceFormQ(p)) // 5
fmt.Println(q.Distance(p)) // 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
package main

import (
"fmt"
"math"
)

type Point struct{ X, Y float64 }

func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}

func main() {

p := Point{1, 2}
q := Point{4, 6}

// 方法表达式, 是一个函数值(相当于C语 言的函数指针)
// var op func(p, q Point) Point 定义一个 op变量,类型是方法表达式
distance1 := Point.Distance

fmt.Println(distance1(p, q)) // 5

// %T表示打出数据类型 ,这个必须放在P rintf使用
fmt.Printf("%T\n", distance1)

distance2 := (*Point).Distance
fmt.Println(distance2(&p, q)) // 5
fmt.Printf("%T\n", distance2)
}

输出:
5
func(main.Point, main.Point) float64
5
func(*main.Point, main.Point) float64

封装

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 data struct {
val int
}

func (p_data *data) set(num int) {
p_data.val = num
}

func (p_data *data) show() {
fmt.Println(p_data.val)
}

func main() {

p_data := &data{4}
p_data.set(5)
p_data.show()
}

继承

  • 子结构体可以使用父结构体的所有的字段和方法,包括首字母大小写的字段和方法。
  • 当父子结构体有相同的字段或方法时,编译器采用就近原则访问,如果希望访问则可以通过结构体名来区分
  • 当嵌入多个匿名结构体时,且该多个匿名结构体中有相同的字段和方法,且被嵌入的结构体本身没有同名的,在访问时,就必须明确指定匿名结构体的名字,否则编译错误
  • 如果嵌套了一个有名结构体,这种模式就是组合,此时在访问组合结构体的字段和方法时,必须带上结构体名字
  • 如果一个结构体有一个int类型的匿名字段,就不能有第二个,否则必须指定名字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
)

type parent struct {
val int
}

type child struct {
parent
num int
}

func main() {

var c child

c = child{parent{1}, 2}
fmt.Println(c.num)
fmt.Println(c.val)
}

接口

  • A结构体继承B结构体,A就可以直接使用B的方法和字段,当A需要扩展功能时,同时不希望去破坏继承关系,就可以实现接口
1
2
3
4
type Usb interface {
Start()
Stop()
}
  • 任何其他类型只要实现了这些方法就是实现了这个接口
  • 接口中不能包含任何变量
  • 空接口interface{} 没有任何方法,所以所有类型都实现了空接口,即可以把任何一个变量赋值给空接口
  • 接口不能创建实例,但可以指向一个实现了该接口的自定义类型的实例
  • 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
1
2
3
4
5
type integer int // 相当于创建了一个新类型,同int

func (i integer) Say() {
...
}
  • 一个接口A可以继承多个别的接口B和C,如果要实现接口A,则要把B和C的方法都实现
1
2
3
4
5
6
7
8
9
10
11
12
type B interface {
f2()
}

type C interface {
f3()
}
type A interface {
B
C
f1()
}
  • interface类型默认是一个指针(引用类型),如果没有对interface初始化,则是nil
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Usb interface {
Say()
}

type Stu struct {

}

func (s *Stu) Say() {
...
}

func main() {
var stu Stu = Stu{}

// var u Usb = stu 报错,会报Stu类型没有实现Usb接口
var u Usb = &stu // ok
u.Say()
}

多态

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 act interface {
write()
}

type xiaoming struct {
}

type xiaofeng struct {
}

func (xm *xiaoming) write() {
fmt.Println("xiaoming write")
}

func (xf *xiaofeng) write() {
fmt.Println("xiaofeng write")
}

func main() {

var a act

xm := xiaoming{}
xf := xiaofeng{}

a = &xm
a.write()

a = &xf
a.write()
}

类型断言

  • 由于接口是一般类型,不知道具体类型,如果要转,就需要使用类型断言
  • 在进行类型断言时,如果不匹配,就会报panic
  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
package main

import (
"fmt"
)

func test1(a interface{}) string {
value, ok := a.(string)
if !ok {
fmt.Println("It's not ok for type string")
return ""
}

fmt.Printf("This is %s\n", value)
return value
}

func main() {
// a := "aaa"

a := 10
test1(a)

}

参考资料