Go 语言基础之指针、复合类型【数组、切片、指针、map、struct】(1)

简介: Go 语言基础之指针、复合类型【数组、切片、指针、map、struct】

1、数组

特别需要注意的是在 Go 语言中,数组长度也是数组类型的一部分!所以尽管元素类型相同但是长度不同的两个数组,它们的类型并不相同。

1.1、数组的初始化

1.1.1、通过初始化列表{}来设置值

    var arr [3]int // int类型的数组默认会初始化全为 0
    var addr = [3]string{"beijing","wuhan","shanghai"} // 指定初始化值

1.1.2、自动推断数组长度

var addr = [...]string{"beijing","wuhan","shanghai"}

1.1.3、通过索引初始化部分元素

    // 数组类型 [6]string
    var addr = [...]string{0:"beijing",3:"wuhan",5:"shanghai"}
    // 数组类型 [3]int
    nums := [...]int{0:1,2:3}

1.2、数组的遍历

1.2.1、一维数组的遍历

    var arr = [3]int{1,2,3}
    arr[1] = 3 // 通过索引修改数组的值
 
    for i:=0;i<len(arr);i++{
        fmt.Printf("%d ",arr[i])    // 1 3 3 
    }

1.2.2、二维数组的遍历

二维数组的定义

注意在二维数组中,列数必须指定,无法自动推导! 行数可以用 [...] 来自动推导。

    // 二维数组的初始化
    var table = [2][3]int{
        {1,2,3},
        {4,5,6}
    }
    
  fmt.Println(table) // [[1 2 3] [4 5 6]]
普通遍历

这种遍历方式就是利用索引来遍历:

for i := 0; i < len(table); i++ {
    for j := 0; j < len(table[i]); j++ {
      fmt.Print(table[i][j]," ")
    }
    fmt.Println()
  }
    // 1 2 3 
    // 4 5 6
使用 range 遍历

range 关键字是专门用来遍历数组或切片的,它会返回两个值:索引元素值

    for _,i := range table{
    for _,j := range i{
      fmt.Print(j," ")
    }
    fmt.Println()
  }

       对于索引我们不需要,所以直接赋值给 _ ,而外层的元素值 i 代表的是二维数组 table 的每一行,相当于是一个一维数组。

所以,对于上面的一维数组遍历,我们同样可以采用这种方式:

    for _,i := range arr{
        fmt.Print(i," ")
    }

1.3、数组是值类型的

      数组是值类型的!这是非常重要的一点。这意味着如果把数组作为参数传递给函数进行处理,那么实际的数组并不会发生改变。而且数组的容量是固定的,在定义时必须就确定!

       但是 Go 语言提供了一种可以引用类型的特殊数组——切片(slice)。对于切片 ,它不仅是引用类型(作为函数参数时,如果形参被修改,那么实参也将被修改),同时也是可动态扩容的,也就是说我们不需要向数组那样在声明时就初始化大小。

func main(){
    s1 := [2]int{0,0} // [0,0]
    fmt.Println(s1) // [0,0]
    addOne(s1)
    fmt.Println(s1) // [0,0]
}
func addOne(s [2]int){
    for i:=0;i<len(s);i++{
        s[i] += 1
    }
}

       可以看到,正因为数组是值类型的,所以当把数组传递给函数的时候,函数中操作的形参相当于是拷贝的这么一个数组,所以操作完毕之后实参并不受影响。(这里的形参在 Java 中就像存在 addOne 方法自己的栈区,而实参存在 mian 方法的栈区,所以操作的就不是同一个内存地址)

1.3.1、数组的比较

       也正因为数组是值类型的,所以它支持比较

    var arr1 = [3]int{1,2,3}
    var arr2 = [3]int{1,2,3}
    fmt.Println(arr1 == arr2) // true

可以看到,两个相同类型元素相同的数组是相同的。

2、切片

       正因为数组的长度固定,并且数组长度属于数组类型的一部分,所以使用数组非常局限。比如我们定义一个遍历数组的方法,那么我们必须指定数组的类型!

       比如下面这个方法只能遍历存放三个元素的数组:

func printArr(arr [3]int){
    for _,i := range arr{
        fmt.Print(i," ")
    }   
}

2.1、切片的定义

切片定义时不需要指定容量,所以也就没有什么初始值:

var arr [] string

       需要注意的一点是:因为切片是引用类型而不是值类型的,所以它不能直接比较(和其它切片用 == 进行比较,只能和 nil 进行比较

2.1.1、切片的长度和容量

       切片拥有自己的长度和容量,我们可以通过使用内建的 len() 函数求长度,使用内建的 cap() 函数求切片的容量。

  • 长度(Length):长度表示切片中当前可以访问的元素个数,即从第一个元素到最后一个元素的数目。
  • 容量(Capacity):容量是指切片的底层数组的大小,即从切片的第一个元素到底层数组的最后一个元素之间的元素总数。容量代表了在不重新分配内存的情况下,切片可以增长到的最大大小。

2.1.2、简单切片表达式

通过数组来给切片初始化:

s := a[low : high]

注意:切片表达式中的low和high表示一个索引范围()左包含,右不包含)

    var a = [5]int{1,2,3,4,5}
    s := a[1:3]
    fmt.Println(a) // [1,2,3,4,5]
    fmt.Println(s) // [2,3]
    fmt.Printf("cap(s)=%v,len(s)=%v",cap(s),len(s)) // cap(s)=4,len(s)=2

此外

  • s := a[1:] :代表从索引 [0,len(s))
  • s := a[:2]:代表从索引  [0,2)
  • s := a[:]:代表整个数组

注意:对于数组或字符串,如果 0 <= low <= high <= len(a),则索引合法,否则就会索引越界(out of range)。

2.1.3、完整切片表达式

完整的切片表达式是这样的:

s := a[low : high : max]

       除了比普通切片表达式多了一个参数 max (这个 max 的作用是将得到的结果切片的容量设置为 max-low),此外,在完整切片表达式中,只有第一个索引值 low 可以省略,它默认为 0.

    var a = [5]int{1,2,3,4,5}
    s := a[:3:5]
    fmt.Println(a) // [1,2,3,4,5]
    fmt.Println(s) // [1,2,3]
    fmt.Printf("cap(s)=%v,len(s)=%v",cap(s),len(s)) // cap(s)=5,len(s)=3

注意:完整切片表达式需要满足的条件是 0 <= low <= high <= max <= cap(a),其他条件和简单切片表达式相同。

2.1.4、使用 make 函数构造切片

我们上面都是基于已有的数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内建的 make()函数,格式如下:

// T: 切片元素类型
make([]T, size, cap)

比如:

    arr := make([]int,2,10) 
    fmt.Println(arr) // [0,0]
  fmt.Println(cap(arr)) // 10
  fmt.Println(len(arr)) // 2

上面代码中切片 arr 的内部存储空间已经分配了10个,但实际上只用了2个。 容量并不会影响当前元素的个数,所以len(a)返回2,cap(a)则返回该切片的容量 10。

注意:对于未开辟的空间是不能初始化赋值的,比如上面我们指定了 2 个长度,如果此时对 arr[3] := 1 进行赋值,那么会报错,因为切片必须使用特定的方法来进行元素的添加(append)。

2.1.5、切片的本质

切片的本质就是对底层数组的封装,它包含了三个信息:

  1. 底层数组的指针
  2. 切片的长度(len)
  3. 切片的容量(cap)
a := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := a[:5]

s2 := a[3:6]


Go 语言基础之指针、复合类型【数组、切片、指针、map、struct】(2)https://developer.aliyun.com/article/1534258

相关文章
|
17天前
|
Go
go语言常量的类型
【10月更文挑战第20天】
23 2
|
1月前
|
C语言 C++
【C语言】指针篇-一篇搞定不同类型指针变量-必读指南(3/5)
【C语言】指针篇-一篇搞定不同类型指针变量-必读指南(3/5)
|
2月前
|
存储 Go
Go: struct 结构体类型和指针【学习笔记记录】
本文是Go语言中struct结构体类型和指针的学习笔记,包括结构体的定义、成员访问、使用匿名字段,以及指针变量的声明使用、指针数组定义使用和函数传参修改值的方法。
|
2月前
|
Go
Golang语言高级数据类型之指针篇
这篇文章详细讲解了Golang语言中的指针概念、指针地址和类型、定义指针变量、指针的细节操作、指针传值,以及内置函数new和make的用法和它们之间的区别。
24 0
|
3月前
|
安全 Go
|
3月前
|
存储 安全 程序员
|
2月前
|
Go 定位技术 索引
Go 语言Map(集合) | 19
Go 语言Map(集合) | 19
|
2月前
|
存储 前端开发 API
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
该文章详细介绍了ES6中Set和Map数据结构的特性和使用方法,并探讨了它们在前端开发中的具体应用,包括如何利用这些数据结构来解决常见的编程问题。
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
|
1月前
|
存储 分布式计算 Java
Stream很好,Map很酷,但答应我别用toMap():Java开发中的高效集合操作
在Java的世界里,Stream API和Map集合无疑是两大强大的工具,它们极大地简化了数据处理和集合操作的复杂度。然而,在享受这些便利的同时,我们也应当警惕一些潜在的陷阱,尤其是当Stream与Map结合使用时。本文将深入探讨Stream与Map的优雅用法,并特别指出在使用toMap()方法时需要注意的问题,旨在帮助大家在工作中更高效、更安全地使用这些技术。
42 0
|
3月前
|
存储 安全 Java
java集合框架复习----(4)Map、List、set
这篇文章是Java集合框架的复习总结,重点介绍了Map集合的特点和HashMap的使用,以及Collections工具类的使用示例,同时回顾了List、Set和Map集合的概念和特点,以及Collection工具类的作用。
java集合框架复习----(4)Map、List、set