Go语言的指针,结构体,数组,slice,range

写在前面

  • 从这部分开始,学习的难度开始增加
  • 不像很多编程语言,Go作为新语言并没有抛弃指针
  • slice作为一种较新的数据类型,让人感觉很迷
  • 这一部分,有c++基础的会比较好理解,涉及到“引用”,“地址”,“深复制和浅复制”,但官方教程对此没作深入理解
  • 这段时间接触这份教程,觉得它教给初学者的是感性认识,作为入门的确合适

指针(pointer)

  • 教程原话:A pointer holds the memory address of a value.
  • 也就是说指针存储了值的内存地址
  • 指针的使用参见以下代码:
//一般定义用到了符号*,默认初始值为nil
var p *int      //p才是指针

//获取某个变量的地址用到了符号&
v := 22
p = &v

//获取指针所指向内存地址的值也用到符号*
//官方教程也提到了一般称这个行为作提领(dereferencing)或者indirecting
fmt.Println(*p)     // *p代表的是指针所存储内存地址里的内容
  • 注意,Go中指针不像C或C++,它没有指针算术,这意味着它不能加减

结构体(struct)

  • 结构体,可以理解为多种数据类型的集合体,它的用法如下:
//如下定义一个名为Cube的结构体
type Cube struct {
    X int
    Y int
    Z int
}

//初始化一个立方体,使用了初始化符号:=和大括号{}
square := Cube{2, 2, 2}

//访问立方体的长宽高(用英文的句号访问)
square.X = 6
square.Y = 3
square.Z = 5
fmt.Println(square.X, square.Y, square.Z)

//同样的,可以定义某个结构体的指针类型
p := &square
//特别地,以下两种写法等价
(*p).X = 8
p.X = 8
  • 结构体中的特殊语法 name:
type Cube struct {
    X, Y, Z int
}
s1 := Cube{X:1}   //X的值为1,其他默认为0
s2 := Cube{}     //X Y Z都为0
s3 := Cube{X:1, Z:2}   //X为1, Y为0,Z为2

数组(Array)

  • 类型 [n]T 表示,有n个T类型的数组
  • 数组是静态的,n必须为常量,一经定义长度将无法再改变
var my_array [10]int
my_array2 := [3]int{1, 2, 3}
fmt.Println(my_array)    // [0 0 0 0 0 0 0 0 0 0]
fmt.Println(my_array2)    // [1 2 3]
fmt.Println(my_array2[2])   // 3

slice

  • 关于slice常被翻译为切片之类的,我这里觉得有点不妥,干脆不翻译
  • 如果读者去过菜市场,那么买冬瓜的时候,通常商家都会准备好一大条冬瓜,问你要多少,
  • 然后你可能会用手比画,大概一根手指的长度吧,于是商家一刀切下去,你就得到了冬瓜中一根手指长度的部分(有点流水帐。。。)
  • 而slice,就相当于这一刀切下去,得到的一根手指长的部分,冬瓜就是数组(这个比喻不算严谨,但能说明slice并非一片)
//slice的定义和数组相似,它的类型写法是 []T
//slice是一种动态类型,它可以“截取”数组的一部分
//从数组中截取第low位到第high-1位的数据(这个操作其实叫做slicing):array[low:high]
a := [6]int{2,3,5,7,11,13}
var s []int = a[2:5]    //此时s为{5,7,11}    可以理解为数学中区间,左闭右开
  • int a[] 和 int* a在C/C++中可以说等价的,而Go中的slice是否也可以理解为特殊的指针呢?
a := [6]int{2,3,5,7,11,13}
var s []int = a[2:5] 
fmt.Println(a)    // {2, 3, 5, 7, 11, 13}
fmt.Println(s)    // {5, 7, 11}

s[2] = 8
fmt.Println(a)  // {2, 3, 5, 7, 8, 13}
fmt.Println(s)  // {5, 7, 8}

//这部分代码的输出结果,基本可以断定,slice的实质是指针,且它有自己配套的运算方法
  • 给出一个比较复杂的完整例子
package main

import "fmt"

func main() {
    q := []int{2, 3, 5, 7, 11, 13}
    fmt.Println(q)

    r := []bool{true, false, true, true, false, true}
    fmt.Println(r)

    s := []struct {
        i int
        b bool
    }{
        {2, true},
        {3, false},
        {5, true},
        {7, true},
        {11, false},
        {13, true},
    }
    fmt.Println(s)
}
  • 在进行slicing时(即array[low:high]),可以利用low和high的默认值
my_array := [6]int{2,3,5,7,11,13}
s0 := my_array[2:4]   //  {5,7}
s1 := my_array[:2]   // {2,3}
s2 := my_array[4:]   //{11,13}
  • slice有两个重要属性:len和cap
  • len:The length of a slice is the number of elements it contains.
  • len就是slice中可访问(为什么是翻译为可访问?)的元素个数
  • cap: The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.
  • cap是指slice背后指向的数组中的容量吗?错。slice每次都要从array中获取low到high - 1位置的访问方式,而cap只是获取其中low到该数组array最后部分的容量
  • 对于slice s而言,len和cap可以这样获取:len(s) cap(s)
  • 下面是例子
s := []int{2, 3, 5, 7, 11, 13}     //s为指向数组{2, 3, 5, 7, 11, 13}的slice
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)       //len=6,cap=6,{2, 3, 5, 7, 11, 13}

s = s[:0]    // {}
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)   //len=0,cap=6,{}

s = s[:4]      //{2,3,5,7}
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)   //len=4,cap=6,{2,3,5,7}

s = s[2:]     //{5,7},这时候的low开始改变了!!
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)   //len=2,cap=4,{5,7}
  • 和指针一样,当slice指向为空,那么slice将为nil值
var s []int
fmt.Println(len(s), cap(s), s)
if s == nil {
    fmt.println("nil!")
}
  • 还可以使用make函数来创建slice
  • 第一个参数是类型
  • 第二个参数是len,可以是变量
  • 第三个参数时cap,可省略,省略后cap和len相同,可以是变量
s := make([]int, 5)       //{0,0,0,0,0}
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)    //len=5, cap=5, {0,0,0,0,0}

s2 := make([]int, 5, 10)    //{0,0,0,0,0}
fmt.Printf("len=%d cap=%d %v\n", len(s2), cap(s2), s2)     //len=5, cap=10, {0,0,0,0,0}
  • slice还能包含slice,也就是二维slice
  • 以下直接给出官方完整例子
package main

import (
    "fmt"
    "strings"
)

func main() {
    // Create a tic-tac-toe board.
    board := [][]string{
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
    }

    // The players take turns.
    board[0][0] = "X"
    board[2][2] = "O"
    board[1][2] = "X"
    board[1][0] = "O"
    board[0][2] = "X"

    for i := 0; i < len(board); i++ {
        fmt.Printf("%s\n", strings.Join(board[i], " "))
    }
}
  • 还可以用append方法来为slice添加元素
var s []int
s = append(s, 0)
s = append(s, 1, 2)
s = append(s, 3, 4, 5, 6)
  • 关于slice的更多内容,还是建议看看https://blog.golang.org/go-slices-usage-and-internals

range

  • range一般中文翻译作“范围”
  • 会在for语句中使用,要结合for循环来理解
  • 每次for循环迭代时,range 数组或者slice,会往后跑一位,返回位置和数值
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

for i, v := range pow {
    fmt.Printf("2**%d = %d\n", i, v)
}
  • 位置或数值是可以省略的,省略的方式有点不同
pow := make([]int, 10)
for i := range pow {
    pow[i] = 1 << uint(i) // == 2**i
}
for _, value := range pow {
    fmt.Printf("%d\n", value)
}

猜你喜欢

转载自www.cnblogs.com/laiyuanjing/p/10864992.html