Go语言函数、数组、切片

Go语言中函数,数组这些都是派生类型,也可以说是复杂类型,能够处理更加复杂的数据。

一、函数

函数是基本的代码块,用于执行一个任务。

Go 语言最少有个 main() 函数。

函数声明告诉了编译器函数的名称,返回类型,和参数。

Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数可以接受不同类型参数并返回该类型的长度。如果我们传入的是字符串则返回字符串的长度,如果传入的是数组,则返回数组中包含的元素个数。

函数声明
  • func:函数由 func 开始声明
  • function_name:函数名称,函数名和参数列表一起构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。
func function_name( [parameter list] ) [return_types] {
   函数体
}
package main

import "fmt"

func main()  {

	fmt.Printf("函数类型为%T\n",sum)
	fmt.Printf("函数值为%d\n",sum(1,2))
	fmt.Printf("函数地址为%p",sum)
	display()

}

func display()  {
	fmt.Println("这是一个无参数,也无返回值的函数")
}


func sum(a ,b int)  int{

	s:=a+b
	fmt.Println("这是一个含有两个参数,返回一个int值的函数")
	return  s
}

结果为:

函数类型为func(int, int) int
这是一个含有两个参数,返回一个int值的函数
函数值为3
函数地址为0x498990
这是一个无参数,也无返回值的函数

二、数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在GO中很少直接使用数组。

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

数组声明
var name [SIZE] type

var arr[4] string//名为arr的长度为4的string型数组
初始化
var name [SIZE] type=[SIZE] type{...}

var s  =[7]string{"Hello"," World","  Can ","you ","help ","me " ," ?"}
访问元素

直接通过下标访问或者for,range遍历访问

//k为索引,v为元素值
for k,v:=range s{}
package main

import "fmt"

func main()  {
	var s  =[7]string{"Hello"," World","  Can ","you ","help ","me " ," ?"}
	//这里的[]里面如果不填,相当于另一种类型-----切片
	//s:= [7]string{"Hello"," World","  Can ","you ","help ","me " ," ?"}

	//var s[7]string
	//	s= [7]string{"Hello"," World","  Can ","you ","help ","me " ," ?"}

	fmt.Printf("数组类型为%T\n",s)
	fmt.Printf("数组地址为%p\n",s)
	fmt.Printf("数组长度为%d\n",len(s))
	fmt.Printf("数组第一位长度为%d\n",len(s[0]))
	fmt.Println("数组为",s)

 fmt.Println("循环访问数组:")
	for i:=0;i<len(s);i++{
		fmt.Print("  "+s[i])
	}

	fmt.Println("\nrange循环访问数组元素及其下标:")

	for k,v:=range s{
		fmt.Print("  K: ",k)
		fmt.Print("  V: ",v)
	}

}
数组类型为[7]string
数组地址为%!p([7]string=[Hello  World   Can  you  help  me   ?])
数组长度为7
数组第一位长度为5
数组为 [Hello  World   Can  you  help  me   ?]
循环访问数组:
  Hello   World    Can   you   help   me    ?
range循环访问数组元素及其下标:
  K: 0  V: Hello  K: 1  V:  World  K: 2  V:   Can   K: 3  V: you   K: 4  V: help   K: 5  V: me   K: 6  V:  ?

多维数组

数组可以有多维数组,如下二维数组

package main

import "fmt"

func main()  {

	var arr [3] [2]int= [3][2]int{{1,2},{2,3},{3,5}}
    
	for _,v:=range arr{
		fmt.Println("v的值为 ",v)
		for _,s:=range v{
			fmt.Print("  s的值为:",s)
		}
		fmt.Println()
	}
}
v的值为  [1 2]
  s的值为:1  s的值为:2
v的值为  [2 3]
  s的值为:2  s的值为:3
v的值为  [3 5]
  s的值为:3  s的值为:5

三、切片

Go 语言切片是对数组的抽象。能增长的数组。

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

声明及初始化

基本等同数组,只是不需要声明长度

var s[]string
	s= []string{"Hello"," World","  Can ","you ","help ","me " ," ?"}

或者使用make函数make([]T, length, capacity),len是初始长度,capacity可选,指容量(容量不代表最大长度,只是声明一下)

s:=make([]string,3,10)
访问

和数组一样,使用下标访问,也可以使用切片截取[start:end]

s:=[]string{"Hi ","My ","Friend ","!"}

	fmt.Println("数组:",s)

	//访问元素,slice [开始位置:结束位置]
	fmt.Println("切片:",s[0:2])
	//访问元素,类似数组
	fmt.Println("切片:",s[0])
数组: [Hi  My  Friend  !]
切片: [Hi  My ]
切片: Hi
append()元素追加,cap()计算容量

追加新元素使用 append 方法,追加元素,非原地操作,会生成新切片

package main

import "fmt"

func main()  {

	s:=[]string{"Hi ","My ","Friend ","!"}

	fmt.Println("数组:",s)

	//访问元素,slice [开始位置:结束位置]
	fmt.Println("切片:",s[0:2])
	//访问元素,类似数组
	fmt.Println("切片:",s[0])

	fmt.Println("切片后:",s)
	fmt.Println("for---range遍历数组")
	for _,v:=range s{
		fmt.Print("   ",v)
	}

	fmt.Println("此时切片容量为",cap(s))

	//追加元素,非原地操作,会生成新切片
	fmt.Println("append方法追加元素: ",append(s, " yes"))
	//追加多个元素
	fmt.Println("append方法追加元素: ",append(s, " it's ","is ","good!"))

	fmt.Println("此时切片容量为",cap(s))

	n:=append(s, " it's ","is ","good!")

	//当 s追加元素时,append(numbers, 2, 3, 4) 为什么 cap 从 4 变成 8 ?
	//
	//经过实践得知,append(list, [params]),先判断 list 的 cap 长度是否大于等于 len(list) + len([params]),
	// 如果大于那么 cap 不变,否则 cap 等于 2*max{cap(list), cap[params]},所以当 append(s, 2, 3, 4) cap 从 2 变成 6。

	fmt.Printf("此时切片容量为%d\n",cap(n))
	fmt.Println("值是:",n)

}
数组: [Hi  My  Friend  !]
切片: [Hi  My ]
切片: Hi 
切片后: [Hi  My  Friend  !]
for---range遍历数组
   Hi    My    Friend    !此时切片容量为 4
append方法追加元素:  [Hi  My  Friend  !  yes]
append方法追加元素:  [Hi  My  Friend  !  it's  is  good!]
此时切片容量为 4
此时切片容量为8
值是: [Hi  My  Friend  !  it's  is  good!]

切片在扩容时,容量的扩展规律是按容量的 2 倍数进行扩充,例如 1、2、4、8、16……,代码如下:

var numbers []intfor i := 0; i < 10; i++ {    numbers = append(numbers, i)    fmt.Printf("len: %d  cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers)}

代码输出如下:

len: 1 cap: 1 pointer: 0xc0420080e8
 len: 2 cap: 2 pointer: 0xc042008150
 len: 3 cap: 4 pointer: 0xc04200e320
 len: 4 cap: 4 pointer: 0xc04200e320
 len: 5 cap: 8 pointer: 0xc04200c200
 len: 6 cap: 8 pointer: 0xc04200c200
 len: 7 cap: 8 pointer: 0xc04200c200
 len: 8 cap: 8 pointer: 0xc04200c200
 len: 9 cap: 16 pointer: 0xc042074000
 len: 10 cap: 16 pointer: 0xc042074000

代码说明如下:

  • 第 1 行,声明一个整型切片。
  • 第 4 行,循环向 numbers 切片中添加 10 个数。
  • 第 5 行,打印输出切片的长度、容量和指针变化,使用函数 len() 查看切片拥有的元素个数,使用函数 cap() 查看切片的容量情况。

往一个切片中不断添加元素的过程,类似于公司搬家,公司发展初期,资金紧张,人员很少,所以只需要很小的房间即可容纳所有的员工,随着业务的拓展和收入的增加就需要扩充工位,但是办公地的大小是固定的,无法改变,因此公司只能选择搬家,每次搬家就需要将所有的人员转移到新的办公点。

通过查看代码输出,可以发现一个有意思的规律:切片长度 len 并不等于切片的容量 cap。

  • 员工和工位就是切片中的元素。
  • 办公地就是分配好的内存。
  • 搬家就是重新分配内存。
  • 无论搬多少次家,公司名称始终不会变,代表外部使用切片的变量名不会修改。
  • 由于搬家后地址发生变化,因此内存“地址”也会有修改。