首页 > 其他分享 >Go基础之数组,切片,Map

Go基础之数组,切片,Map

时间:2024-12-29 09:02:17浏览次数:6  
标签:Map func int fmt 切片 numbers 数组 Go

目录

1 数组

1.1 简介

数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。
相对于去声明 number0, number1, ..., number99 的变量,使用数组形式 numbers[0], numbers[1] ..., numbers[99] 更加方便且易于扩展。

数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。

1.1.1 声明数组

Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:
var arrayName [size] dataType 其中,arrayName 是数组的名称,size 是数组的大小,dataType 是数组中元素的数据类型。

1.1.2 初始化数组

声明一个名为 numbers 的整数数组,其大小为 5,在声明时,数组中的每个元素都会根据其数据类型进行默认初始化,对于整数类型,初始值为 0。
var numbers [5]int
还可以使用初始化列表来初始化数组的元素:var numbers = [5]int{1, 2, 3, 4, 5},并将其中的元素分别初始化为 1、2、3、4 和 5。
另外,还可以使用 := 简短声明语法来声明和初始化数组:numbers := [5]int{1, 2, 3, 4, 5}

注意:在 Go 语言中,数组的大小是类型的一部分,因此不同大小的数组是不兼容的,也就是说 [5]int[10]int 是不同的类型。

如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

如果设置了数组的长度,我们还可以通过指定下标来初始化元素:

将索引为 1 和 3 的元素初始化
balance := [5]float32{1:2.0,3:7.0}

初始化数组中 {} 中的元素个数不能大于 [] 中的数字。

1.3 访问数组元素

数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。例如:var salary float32 = balance[9]
以上实例读取了数组 balance 第 10 个元素的值。

以下演示了数组完整操作(声明、赋值、访问)的实例:

package main

import "fmt"

func main() {
   var n [10]int /* n 是一个长度为 10 的数组 */
   var i,j int

   /* 为数组 n 初始化元素 */        
   for i = 0; i < 10; i++ {
      n[i] = i + 100 /* 设置元素为 i + 100 */
   }

   /* 输出每个数组元素的值 */
   for j = 0; j < 10; j++ {
      fmt.Printf("Element[%d] = %d\n", j, n[j] )
   }
}

实例 2

package main

import "fmt"

func main() {
   var i,j,k int
   // 声明数组的同时快速初始化数组
   balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

   /* 输出数组元素 */         ...
   for i = 0; i < 5; i++ {
      fmt.Printf("balance[%d] = %f\n", i, balance[i] )
   }
   
   balance2 := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
   /* 输出每个数组元素的值 */
   for j = 0; j < 5; j++ {
      fmt.Printf("balance2[%d] = %f\n", j, balance2[j] )
   }

   //  将索引为 1 和 3 的元素初始化
   balance3 := [5]float32{1:2.0,3:7.0}  
   for k = 0; k < 5; k++ {
      fmt.Printf("balance3[%d] = %f\n", k, balance3[k] )
   }
}

1.4 多维数组

Go 语言支持多维数组,以下为常用的多维数组声明方式:

var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type

声明了三维的整型数组:var threedim [5][10][4]int

1.4.1 二维数组

二维数组是最简单的多维数组,二维数组本质上是由一维数组组成的。二维数组定义方式如下:

var arrayName [ x ][ y ] variable_type

二维数组中的元素可通过 a[ i ][ j ] 来访问。

package main

import "fmt"

func main() {
    // Step 1: 创建数组
    values := [][]int{}

    // Step 2: 使用 append() 函数向空的二维数组添加两行一维数组
    row1 := []int{1, 2, 3}
    row2 := []int{4, 5, 6}
    values = append(values, row1)
    values = append(values, row2)

    // Step 3: 显示两行数据
    fmt.Println("Row 1")
    fmt.Println(values[0])
    fmt.Println("Row 2")
    fmt.Println(values[1])

    // Step 4: 访问第一个元素
    fmt.Println("第一个元素为:")
    fmt.Println(values[0][0])
}

1.4.2 初始化二维数组

多维数组可通过大括号来初始值。以下实例为一个 3 行 4 列的二维数组:

a := [3][4]int{  
 {0, 1, 2, 3} ,   /*  第一行索引为 0 */
 {4, 5, 6, 7} ,   /*  第二行索引为 1 */
 {8, 9, 10, 11},   /* 第三行索引为 2 */
}

注意:以上代码中倒数第二行的 } 必须要有逗号,因为最后一行的 } 不能单独一行

1.4.3 访问二维数组

二维数组通过指定坐标来访问。如数组中的行索引与列索引,例如:
val := a[2][3]var value int = a[2][3]
以上实例访问了二维数组 val 第三行的第四个元素。
二维数组可以使用循环嵌套来输出元素:

package main

import "fmt"

func main() {
   /* 数组 - 5 行 2 列*/
   var a = [5][2]int{ {0,0}, {1,2}, {2,4}, {3,6},{4,8}}
   var i, j int

   /* 输出数组元素 */
   for  i = 0; i < 5; i++ {
      for j = 0; j < 2; j++ {
         fmt.Printf("a[%d][%d] = %d\n", i,j, a[i][j] )
      }
   }
}

以下实例创建各个维度元素数量不一致的多维数组:

package main

import "fmt"

func main() {
    // 创建空的二维数组
    animals := [][]string{}

    // 创建三一维数组,各数组长度不同
    row1 := []string{"fish", "shark", "eel"}
    row2 := []string{"bird"}
    row3 := []string{"lizard", "salamander"}

    // 使用 append() 函数将一维数组添加到二维数组中
    animals = append(animals, row1)
    animals = append(animals, row2)
    animals = append(animals, row3)

    // 循环输出
    for i := range animals {
        fmt.Printf("Row: %v\n", i)
        fmt.Println(animals[i])
    }
}

1.5 数组与函数

Go 语言中的数组值类型,因此在将数组传递给函数时,实际上是传递数组的副本。如果想向函数传递数组参数,需要在函数定义时,声明形参为数组,可以通过以下两种方式来声明:

形参设定数组大小:
func myFunction(param [10]int) {
    ....
}

形参未设定数组大小:
func myFunction(param []int) {
    ....
}

示例

func main() {
   /* 数组长度为 5 */
   var  balance = [5]int {1000, 2, 3, 17, 50}
   var avg float32

   /* 数组作为参数传递给函数 */
   avg = getAverage( balance, 5 ) ;

   /* 输出返回的平均值 */
   fmt.Printf( "平均值为: %f ", avg );
}
func getAverage(arr [5]int, size int) float32 {
   var i,sum int
   var avg float32  

   for i = 0; i < size;i++ {
      sum += arr[i]
   }

   avg = float32(sum) / float32(size)

   return avg;
}

如果要在函数内修改原始数组,可以通过传递数组的指针来实现。
以下实例演示如何向函数传递数组,函数接受一个数组和数组的指针作为参数:

package main

import "fmt"

// 函数接受一个数组作为参数
func modifyArray(arr [5]int) {
    for i := 0; i < len(arr); i++ {
        arr[i] = arr[i] * 2
    }
}

// 函数接受一个数组的指针作为参数
func modifyArrayWithPointer(arr *[5]int) {
    for i := 0; i < len(*arr); i++ {
        (*arr)[i] = (*arr)[i] * 2
    }
}

func main() {
    // 创建一个包含5个元素的整数数组
    myArray := [5]int{1, 2, 3, 4, 5}

    fmt.Println("Original Array:", myArray)

    // 传递数组给函数,但不会修改原始数组的值
    modifyArray(myArray)
    fmt.Println("Array after modifyArray:", myArray)

    // 传递数组的指针给函数,可以修改原始数组的值
    modifyArrayWithPointer(&myArray)
    fmt.Println("Array after modifyArrayWithPointer:", myArray)
}

2 切片

2.1 简介

切片是对数组的抽象,数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(动态数组),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

2.1.1 定义切片

可以声明一个未指定大小的数组来定义切片:var identifier []type,切片不需要说明长度。
或使用 make() 函数来创建切片:var slice1 []type = make([]type, len),也可以简写为:slice1 := make([]type, len)
也可以指定容量,其中 capacity 为可选参数:make([]T, length, capacity)
这里 len 是数组的长度并且也是切片的初始长度。

2.1.2 切片初始化

切片可以通过数组来初始化,也可以通过内置函数 make() 初始化,初始化时 len=cap,在追加元素时如果容量 cap 不足时将按 len 的 2 倍扩容。

  • s :=[] int {1,2,3 }:直接初始化切片,[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3。
  • s := arr[:] :初始化切片 s,是数组 arr 的引用。
  • s := arr[startIndex:endIndex]:将 arr 中从下标 startIndexendIndex-1 下的元素创建为一个新的切片。
  • s := arr[startIndex:]:默认 endIndex 时将表示一直到arr的最后一个元素。
  • s := arr[:endIndex]:默认 startIndex 时将表示从 arr 的第一个元素开始。
  • s1 := s[startIndex:endIndex]:通过切片 s 初始化切片 s1。
  • s :=make([]int,len,cap):通过内置函数 make() 初始化切片s,[]int 标识为其元素类型为 int 的切片。

2.1.3 len() 和 cap() 函数

切片是可索引的,并且可以由 len() 方法获取长度。切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

package main

import "fmt"

func main() {
   var numbers = make([]int,3,5)
   printSlice(numbers)
}

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

2.1.4 空(nil)切片

一个切片在未初始化之前默认为 nil,长度为 0,实例如下:

package main

import "fmt"
func main() {
   var numbers []int
   printSlice(numbers)
   if(numbers == nil){
      fmt.Printf("切片是空的")
   }
}
func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

结果为:
len=0 cap=0 slice=[]
切片是空的

2.2 切片操作

2.2.1 切片截取

可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound],实例如下:

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

2.2.2 append() 和 copy()

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
appendcopy 是生成了个新的切片,对原切片没影响
下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
需要注意一下:append操作会创建新切片

package main

import "fmt"

func main() {
   var numbers []int
   printSlice(numbers)

   /* 允许追加空切片 */
   numbers = append(numbers, 0)
   printSlice(numbers)

   /* 向切片添加一个元素 */
   numbers = append(numbers, 1)
   printSlice(numbers)

   /* 同时添加多个元素 */
   numbers = append(numbers, 2,3,4)
   printSlice(numbers)

   /* 创建切片 numbers1 是之前切片的两倍容量*/
   numbers1 := make([]int, len(numbers), (cap(numbers))*2)

   /* 拷贝 numbers 的内容到 numbers1 */
   copy(numbers1,numbers)
   printSlice(numbers1)  
}

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

2.2.3 切片展开 ...

可以使用...操作符将一个切片追加到另一个切片。

package main

import (
	"fmt"
)

func main() {   
     
	veggies := []string{"potatoes", "tomatoes", "brinjal"}
	fruits := []string{"oranges", "apples"}
	food := append(veggies, fruits...)
	fmt.Println("food:", food)
}

上面的程序中,将 fruits 追加到 veggies 并赋值给 food。...操作符用来展开切片。程序的输出为:food: [potatoes tomatoes brinjal oranges apples]。

注意:切片本身作为引用类型传递,即使展开它,底层数组还是共享的。所以即使切片被展开成多个单独的字符串参数,如果有函数内的修改仍然会影响原始切片

2.3 切片与函数

在做函数调用时,slice 按引用传递,array 按值传递:

package main

import "fmt"

func main(){
  changeSliceTest()
}

func changeSliceTest() {
    arr1 := []int{1, 2, 3}
    arr2 := [3]int{1, 2, 3}
    arr3 := [3]int{1, 2, 3}

    fmt.Println("before change arr1, ", arr1)
    changeSlice(arr1) // slice 按引用传递
    fmt.Println("after change arr1, ", arr1)

    fmt.Println("before change arr2, ", arr2)
    changeArray(arr2) // array 按值传递
    fmt.Println("after change arr2, ", arr2)

    fmt.Println("before change arr3, ", arr3)
    changeArrayByPointer(&arr3) // 可以显式取array的 指针
    fmt.Println("after change arr3, ", arr3)
}

func changeSlice(arr []int) {
    arr[0] = 9999
}

func changeArray(arr [3]int) {
    arr[0] = 6666
}

func changeArrayByPointer(arr *[3]int) {
    arr[0] = 6666
}

结果:
before change arr1,  [1 2 3]
after change arr1,  [9999 2 3]
before change arr2,  [1 2 3]
after change arr2,  [1 2 3]
before change arr3,  [1 2 3]
after change arr3,  [6666 2 3]

2.4 内存优化

切片持有对底层数组的引用。只要切片在内存中,就不能对数组进行垃圾回收。在内存管理方面,这是必须要关注的。假设我们有一个非常大的数组,我们只处理它的一小部分。因此,我们从该数组创建一个切片并开始处理切片。这里需要注意的重要一点是,由于切片引用了数组,因此数组仍然在内存中。

解决该问题的一个方法是使用 copy 函数 func copy(dst, src []T) int 来创建该切片的一个拷贝。这样我们就可以使用这个新的切片,原来的数组可以被垃圾回收。

package main

import (
	"fmt"
)

func countries() []string {
   
	countries := []string{"USA", "Singapore", "Germany", "India", "Australia"}
	neededCountries := countries[:len(countries)-2]
	countriesCpy := make([]string, len(neededCountries))
	copy(countriesCpy, neededCountries) //copies neededCountries to countriesCpy
	return countriesCpy
}
func main() {
	countriesNeeded := countries()
	fmt.Println(countriesNeeded)
}

在上面程序中,neededCountries := countries[:len(countries)-2] 创建一个底层数组为 countries 并排除最后两个元素的切片。
copy 将 neededCountries 拷贝到 countriesCpy 并在下一行返回 countriesCpy。现在数组 countries 可以被垃圾回收,因为 neededCountries 不再被引用。

3 Map

3.1 简介

Map 是一种无序的键值对的集合,Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,遍历 Map 时返回的键值对的顺序是不确定的。
在获取 Map 的值时,如果键不存在,返回该类型的零值,例如 int 类型的零值是 0,string 类型的零值是 ""。

Map引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量。

3.2 定义 Map

可以使用内建函数 make 或使用 map 关键字来定义 Map

/* 使用 make 函数 */
map_variable := make(map[KeyType]ValueType, initialCapacity)

其中 KeyType 是键的类型,ValueType 是值的类型,initialCapacity 是可选的参数,用于指定 Map 的初始容量。Map 的容量是指 Map 中可以保存的键值对的数量,当 Map 中的键值对数量达到容量时,Map 会自动扩容。如果不指定 initialCapacity,Go 语言会根据实际情况选择一个合适的值。

// 创建一个空的 Map
m := make(map[string]int)

// 创建一个初始容量为 10 的 Map
m := make(map[string]int, 10)

也可以使用字面量创建 Map:
// 使用字面量创建 Map
m := map[string]int{
    "apple": 1,
    "banana": 2,
    "orange": 3,
}

映射的键可以是任何值,这个值的类型并不限制,内置类型或者结构体都可以,需要确定这个值可以使用运算符做比较。需要注意的是,切片、函数以及包含切片的结构类型由于是引用类型,均不能作为映射的键。

dict := map[[]string]int{
   
     }	// 报错:incomparable map key type []string

dict := map[int][]string{
   
     }	// 切片可以作为值,不可以作为键

3.3 操作Map

获取元素:

// 获取键值对
v1 := m["apple"]
v2, ok := m["pear"]  // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值

修改元素:

// 修改键值对
m["apple"] = 5

获取 Map 的长度:

// 获取 Map 的长度
len := len(m)

遍历 Map:

// 遍历 Map
for k, v := range m {
    fmt.Printf("key=%s, value=%d\n", k, v)
}

删除元素:

// 删除键值对
delete(m, "banana")

3.4 示例

package main

import "fmt"

func main() {
    var siteMap map[string]string /*创建集合 */
    siteMap = make(map[string]string)

    /* map 插入 key - value 对,各个国家对应的首都 */
    siteMap [ "Google" ] = "谷歌"
    siteMap [ "Runoob" ] = "菜鸟教程"
    siteMap [ "Baidu" ] = "百度"
    siteMap [ "Wiki" ] = "维基百科"

    /*使用键输出地图值 */ 
    for site := range siteMap {
        fmt.Println(site, "首都是", siteMap [site])
    }

    /*查看元素在集合中是否存在 */
    name, ok := siteMap [ "Facebook" ] /*如果确定是真实的,则存在,否则不存在 */
    /*fmt.Println(capital) */
    /*fmt.Println(ok) */
    if (ok) {
        fmt.Println("Facebook 的 站点是", name)
    } else {
        fmt.Println("Facebook 站点不存在")
    }
}

标签:Map,func,int,fmt,切片,numbers,数组,Go
From: https://www.cnblogs.com/jingzh/p/18638381

相关文章

  • Go基础之函数和方法讲解
    目录1自定义函数1.1函数定义1.2函数调用与返回多值1.2.1返回类型1.2.2命名返回值1.3函数参数1.3.1值传递1.3.2引用传递1.3.3不定参数1.4函数变量&回调1.4.1函数变量1.4.2函数回调1.5匿名函数1.6defer函数1.6.1定义1.6.2使用方式1.6.3参数求值1.6.4LIFO执行顺序......
  • Go基础之条件语句,For循环,错误处理
    目录1条件语句1.1if语句1.2switch1.2.1switch1.2.2TypeSwitch1.2.3fallthrough2for循环2.1简介2.2For-eachrange循环2.3示例3错误处理3.1简介3.2error接口3.2.1error接口3.2.2使用errors包创建错误3.2.3errors包原理3.3显式返回错误3.4自定义错误3.4.1......
  • Go基础之结构体,接口
    目录1结构体1.1简介1.2定义结构体1.3声明结构体1.3.1new声明1.3.2直接声明1.3.3与new的对比1.3.4new和&操作符的区别1.3.5结构体指针1.4结构体标签1.4.1标签的语法1.4.2标签的工作机制1.4.3常见用途1.4.3.1JSON序列化和反序列化1.4.3.2数据库映射1.4.3.3表......
  • Go 并发之goroutine和Channel讲解
    目录1并发1.1简介1.2Goroutine1.2.1简介1.2.2特点1.2.3检测数据访问冲突1.2.4示例1.3通道(Channel)1.3.1普通通道1.3.1.1简介1.3.1.2声明通道1.3.1.3普通通道示例1.3.2带缓冲区通道1.3.2.1简介1.3.2.2带缓冲区通道示例1.3.3遍历1.3.3.1for遍历1.3.3.2range遍历......
  • Go基础之指针和反射讲解
    目录1指针1.1简介1.2使用指针1.3指针优化输出1.3.1优化输出复杂类型1.3.2去掉优化1.3.3基本类型1.4指针数组1.4.1指针数组优化1.5指向指针的指针1.6向函数传递指针参数2反射2.1reflect2.1.1示例2.2获取变量值ValueOf2.3修改变量值Value.Set2.3.1Elem方法2.3.2......
  • Go IO之文件处理,TCP&UDP讲解
    目录1文件处理1.1打开和关闭文件1.2读取文件1.2.1简单示例1.2.2中文乱码1.2.2.1bufio1.2.2.2ioutil1.3写入文件1.3.1Write和WriteString1.3.2fmt.Fprintln1.3.2.1写入文件1.3.2.2写入标准输出1.3.3bufio.NewWriter1.3.4ioutil.WriteFile2TCP&UDP2.1TCP2.1.1服......
  • Go 并发之WaitGroup,并发锁,Context
    目录1Go并发1.1WaitGroup1.2并发锁1.2.1互斥锁1.2.2读写互斥锁1.2.3sync.Once1.2.4sync.Map1.3Context1.3.1简介1.3.2主要功能1.3.3使用示例1.3.3.1取消信号1.3.3.2设置超时1.3.3.3传递值1Go并发1.1WaitGroupsync.WaitGroup是Go标准库提供的一种同步原语,常......
  • client-go InClusterConfig方法
    InClusterConfig方法packagemainimport( "context" "test/signals" "time" "os" core_v1"k8s.io/api/core/v1" metav1"k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes......
  • 2024-12-28:求出出现两次数字的 XOR 值。用go语言,给定一个数组 nums,其中的数字出现的频
    2024-12-28:求出出现两次数字的XOR值。用go语言,给定一个数组nums,其中的数字出现的频率要么是一次,要么是两次。请找出所有出现两次的数字,并计算它们的按位XOR值。如果没有数字出现两次,则返回0。1<=nums.length<=50。1<=nums[i]<=50。nums中每个数字要么出现过一......
  • 介绍一下logos这个词法分析工具,它和nom相比如何?我看lalrpop官网给出的示例就是logos配
    UUUUUUUUUUUUUUUUUUUUUULogos简介Logos是一个用于词法分析的高效Rust库,其设计目标是简单、快速且高效。它通过声明式的方式定义词法规则,并利用Rust的强类型系统生成轻量级的词法分析器。Logos的特点声明式规则:使用Rust的枚举定义每种Token类型,并通过属性宏指定......