基础语法

Catalogue
  1. 1. 命名规范
  2. 2. 原码、反码、补码
  3. 3. 数据类型
    1. 3.0.1. rune用法
    2. 3.0.2. 值类型和引用类型
    3. 3.0.3. new/make
  • 4. 变量
  • 5. 常量
    1. 5.0.1. 自增长iota
  • 6. 运算符
  • 7. if语句
  • 8. Swtich语句
  • 9. for语句
  • 10. 错误机制
    1. 10.0.1. 自定义错误
  • 11. 函数
    1. 11.0.1. 引用传递
    2. 11.0.2. 函数作为值
    3. 11.0.3. 闭包
    4. 11.0.4. main函数和init函数
    5. 11.0.5. defer函数
  • 12. 指针
    1. 12.0.1. 空指针
    2. 12.0.2. 指针数组
  • 13. 数组和切片
    1. 13.0.1. 数组
    2. 13.0.2. 切片Slice
    3. 13.0.3. 切片截取
    4. 13.0.4. append() 函数
    5. 13.0.5. string和slice
  • 14. map
    1. 14.0.1. map切片
    2. 14.0.2. map排序
  • 15. 结构体
  • 16. 方法
    1. 16.0.1. 基于指针接受类型的方法
    2. 16.0.2. 方法值
    3. 16.0.3. 方法表达式
  • 17. 封装
  • 18. 继承
  • 19. 接口
  • 20. 多态
  • 21. 类型断言
  • 22. 参考资料
  • 命名规范

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

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

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

    如果一个类型实现了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)

    }

    参考资料