前面我们已经学习了一些简单的基本类型,现在学习复合类型,复合类型主要包括了数组,指针,切片,结构体等。现在先来学习数组.
如果要存储班级里所有学生的数学成绩,应该怎样存储呢?可能有同学说,通过定义变量来存储。但是,问题是班级有80个学生,那么要定义80个变量吗?
像以上情况,最好是通过数组的方式来存储。
所谓的数组:是指一系列同一类型数据的集合。
var a [10]int
数组定义也是通过var 关键字,后面是数组的名字a,长度是10,类型是整型。表示:数组a能够存储10个整型数字。也就是说,数组a的长度是10。
我们可以通过len( )函数测试数组的长度,如下所示:
func main() {
var a [10]int
fmt.Println(len(a))
}
执行如下:
$ go run 01_数组.go
10
当定义完成数组a后,就在内存中开辟了10个连续的存储空间,每个数据都存储在相应的空间内,数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数组的长度。
注意:数组的长度只能是常量。以下定义是错误的:
var n int = 10
var a [n]int
数组定义完成后,可以对数组进行赋值操作。
数组是通过下标来进行操作的,下标的范围是从0开始到数组长度减1的位置。
var a[10] int // 表示的范围是a[0],a[1],a[2].......,a[9]
如果现在给a[10]=29, 会出现什么情况呢?
可以看到报错:数组越界
# command-line-arguments
.\01_数组.go:14:3: invalid array index 10 (out of bounds for 10-element array)
.\01_数组.go:19:24: invalid array index 10 (out of bounds for 10-element array)
但是这种赋值方式比较麻烦,所以可以使用第二种赋值方式,如下所示:
package main
import "fmt"
func main() {
var a [10]int // 定义数组
// 数组赋值
for i := 0; i < 10; i++ {
a[i] = i + 1
}
// 打印数组的值
for i := 0; i < 10; i++ {
fmt.Printf("a[%d]=%d\n", a[i], a[i])
}
}
执行如下:
a[1]=1
a[2]=2
a[3]=3
a[4]=4
a[5]=5
a[6]=6
a[7]=7
a[8]=8
a[9]=9
a[10]=10
通过for循环完成数组的赋值与输出。注意:循环的条件,如果将循环条件修改成 i<=10是否正确。
在上一节中,我们说过可以通过len( )函数来获取 数组的长度,所以也可以对上面的程序,进行如下的修改:
对数组中的数据输出,也可以使用range.如下所示:
// 使用 range 遍历数组的值
for i, data := range a{
fmt.Printf("a[%d]=%d\t", i, data)
}
输出如下:
a[0]=1 a[1]=2 a[2]=3 a[3]=4 a[4]=5 a[5]=6 a[6]=7 a[7]=8 a[8]=9 a[9]=10
i 变量存储的是数组的下标,data变量存储的是数组中的值。
如果只想输出数组中的元素值,不希望输出下标,可以使用匿名变量 _
// 使用 range 遍历数组的值
for _, data := range a{ // 使用匿名变量 _
//fmt.Printf("a[%d]=%d\t", i, data)
fmt.Printf("元素的值=%d\t", data)
}
上面的案例中,首先完成了数组的赋值,然后再输出数组中的值。但是,如果定义完成数组后,没有赋值,直接输出会出现什么样的问题呢?
var a [10]int // 定义数组
// 使用 range 遍历数组的值
for _, data := range a{
fmt.Printf("元素的值=%d\t", data)
}
a数组中的元素类型是整型,定义完成后,直接输出,结果全部是0.
当然数组中存储的元素类型也可以是其它类型,如下所示:
var a [10]float64 //如果不赋值,直接输出,结果默认全部是0
var a [10]string //如果不赋值,直接输出,结果默认全部是空字符
var a [10]bool //如果不赋值,直接输出,结果默认全部是false.
上一小节中,首先先定义数组,然后再完成数组的赋值。其实,在定义数组时,也可以完成赋值,这种情况叫做数组的初始化。
具体案例如下:
package main
import "fmt"
func main() {
//1. 全部初始化
var a [5]int = [5]int{1, 2, 3, 4, 5}
fmt.Println("a = ", a)
b := [5]int{1, 2, 3, 4, 5}
fmt.Println("b = ", b)
// 部分初始化,没有初始化的元素,自动赋值为 0
c := [5]int{1, 2, 3}
fmt.Println("c = ", c)
// 指定某个元素初始化
d := [5]int{2:10, 4:20} // 设置下标2的值为10,下标4的值为20
fmt.Println("d = ", d)
}
执行如下:
a = [1 2 3 4 5]
b = [1 2 3 4 5]
c = [1 2 3 0 0]
d = [0 0 10 0 20]
还可以使用 ...
设置数组的长度,通过初始化确定数组的长度。
b := [...]int{1, 2, 3, 4, 5} // 通过...设置数组的长度
fmt.Println("b = ", b)
代码如下:
package main
import "fmt"
func main() {
//1.全部初始化
var a [6]int = [6]int{1, 2, 3, 4, 5, 0}
//2.声明两个变量来存储最大值与最小值
var max int
var min int
var sum int // 存储和
// 循环数组,将数组中的每个元素值与最大值和最小值进行比较
for i := 0; i < len(a); i++ {
if a[i] > max {
max = a[i]
}
if a[i] < min {
min = a[i]
}
sum += a[i]
}
fmt.Printf("最大值: %d, 最小值: %d, 和为: %d, 平均值: %d", max, min, sum, sum/len(a))
}
以上程序输出的结果是:
最大值: 5, 最小值: 0, 和为: 15, 平均值: 2
通过观察发现该程序输出的结果没有问题。
但是,现在将程序进行如下修改:将数组中的0元素删除,同时将数组的长度修改为5.
思考:数组中没有0,为什么输出的结果中最小值为0呢?
现在,在将程序进行如下修改:将数组中的数据全部修改成负数。
思考:数组中没有0,为什么输出的结果中最大值为0呢?
**应该怎样解决如上的问题呢?**将程序修改如下:
image-20210507083531212
// 练习2:计算一个整数数组的所有元素的和。
var a [6]int = [6]int{1, 2, 3, 4, 5, 0}
var sum int // 存储和
for i := 0; i < len(a); i++ {
sum += a[i]
}
fmt.Printf("和为: %d", sum)
运行结果如下:
和为: 15
// 练习3:数组里面都是人的名字,分割成:例如:老王|王叔|王哥…”
var name [3]string = [3]string{"老王", "王叔", "王哥"}
var str1 string
// 拼接字符串
for i := 0; i < len(name); i++ {
str1 += name[i] + "|"
}
fmt.Printf(str1)
该程序最终的输出结果:
老王|王叔|王哥|
现在将最后一个“|”去掉,程序应该怎样进行修改?
具体思路:首先通过循环的方式取出数组中前两个元素,分别链接”|” ,存储到变量str中。然后获取最后一个元素,不需要链接“|”,直接与str链接就可以了。获取names数组中最后一个元素的方式:
通过len(name)计算出数组的长度,然后减去1, 就是数组中最后一个元素的下标(数组的下标是从0开始计算)。所以取出最后一个元素的方式为:names[len(name)-1]
// 练习3:数组里面都是人的名字,分割成:例如:老王|王叔|王哥…”
var name [3]string = [3]string{"老王", "王叔", "王哥"}
var str1 string
// 拼接字符串,最拼接最后一个字符之前的所有字符
for i := 0; i < len(name)-1; i++ {
str1 += name[i] + "|"
}
fmt.Printf(str1 + name[len(name)-1]) // 拼接最后一个字符,并打印
执行如下:
老王|王叔|王哥
// 练习4:将一个整数数组的每一个元素进行如下的处理:
// 如果元素是正数则将这个位置的元素的值加1,如果元素是负数则将这个位置的元素的值减1,如果元素是0,则不变。
num := [...]int{10,8,0,7,-1,-3,-6,99}
fmt.Println("处理前的数组: ", num)
for i,v := range num{
// 如果元素是正数则将这个位置的元素的值加1
if v > 0 {
num[i] += 1
}
// 如果元素是负数则将这个位置的元素的值减1
if v < 0 {
num[i] -= 1
}
// 如果元素是0,则不变
if v == 0 {
}
}
fmt.Println("处理后的数组: ", num)
执行如下:
处理前的数组: [10 8 0 7 -1 -3 -6 99]
处理后的数组: [11 9 0 8 -2 -4 -7 100]
分析如下:
image-20210507234711950
代码如下:
// 练习5:将一个字符串数组的元素的顺序进行反转。{“我”,“是”,”好人”} {“好人”,”是”,”我”}。第i个和第length-i-1个进行交换。
names := [...]string{"我", "是", "好人"}
fmt.Println("交换前的数组: ", names)
var temp string // 定义中间变量
for i := 0; i < len(names)/2; i++ {
temp = names[i]
names[i] = names[len(names)-1-i]
names[len(names)-1-i] = temp
}
fmt.Println("交换后的数组: ", names)
执行如下:
交换前的数组: [我 是 好人]
交换后的数组: [好人 是 我]
如何对数组中存储的数据,按照从大到小,或者从小到大进行排序?可以使用冒泡排序。
分析过程:
具体代码实现:
package main
import "fmt"
func main() {
var num [10]int = [10]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
var temp int
fmt.Println("冒泡排序前的数组: ", num)
// 冒泡排序
for i := 0; i < len(num)-1; i++ {
for j := 0; j < len(num)-1-j; j++ {
if num[j] > num[j+1] {
temp = num[j]
num[j] = num[j+1]
num[j+1] = temp
}
}
}
fmt.Println("冒泡排序后的数组: ", num)
}
执行如下:
冒泡排序前的数组: [9 8 7 6 5 4 3 2 1 0]
冒泡排序后的数组: [4 5 6 7 8 9 3 2 1 0]
数组也可以像变量一样,作为参数传递给函数,用法如下:
package main
import "fmt"
func modify(a [5]int) { // 数组 a [5]int 作为参数
a[0] = 666 // 修改传递过来的数组,修改第一个元素的值
fmt.Println("modify a=", a)
}
func main() {
a := [5]int{1, 2, 3, 4, 5} // 初始化
modify(a) // 数组传递过去
fmt.Println("main a=", a)
}
执行如下:
modify a= [666 2 3 4 5]
main a= [1 2 3 4 5]
注意:在main( )函数中,定义数组a, 然后调用modify( )方法传递数组,同时在modify( )方法中修改数组中第一个元素。最终输出结果发现,并不会影响main( )函数中数组a的值,这一点与其它编程语言是有区别的。
思路:
1:在main( )函数中定义该数组,并且传递到GetLongest( )方法中
2:定义一个max变量用来存储最长的字符串,并且假设数组中的第一个元素是最长的。
3:通过循环,将数组中的元素取出来与max变量进行比较,如果长度比max变量中存储的长度要长,赋值给max
4:将结果返回
package main
import "fmt"
// 练习1:用方法来实现:有一个字符串数组: { "马龙", "迈克尔乔丹", "雷吉米勒", "蒂姆邓肯", "科比布莱恩特" },请输出最长的字符串。
func GetLongest(names [5]string) string {
var max string
max = names[0]
for i := 1; i < len(names); i++ {
if len(names[i]) > len(max) {
max = names[i]
}
}
return max
}
func main() {
str := [...]string{"马龙", "迈克尔乔丹", "雷吉米勒", "蒂姆邓肯", "科比布莱恩特"}
name := GetLongest(str)
fmt.Println(name)
}
执行如下:
科比布莱恩特
思路:该题主要是通过循环的方式,将数组中的每个元素取出来进行累加,最后求平均数。注意输出格式
package main
import "fmt"
//练习2:用方法来实现:请计算出一个整型数组的平均值。保留两位小数
func GetAvg(numbers [3]int) float64 {
var sum int
for _, v := range numbers {
sum += v
}
return float64(sum) / float64(len(numbers))
}
func main() {
num := [...]int{1, 2, 7}
avg := GetAvg(num)
fmt.Printf("%.2f", avg)
}
执行如下:
3.33
在GO语言中,产生随机数的语法如下:
package main
import (
"fmt"
"math/rand"
)
func RandomFunc() {
//设置种子,只需一次
rand.Seed(123)
for i := 0; i < 5; i++ {
// 产生随机数
fmt.Println("rand = ", rand.Int())
}
}
func main() {
RandomFunc()
}
执行如下:
rand = 5361704182646325489
rand = 241876450138978746
rand = 2305561650894865143
rand = 5680255032342805883
rand = 2116592265311177487
要产生随机数,还需要导入math/rand
包,
rand.Seed(123)
,设置随机种子,由于随机种子是固定的,所以运行该程序,发现每次生成的随机数是一样的。
“所谓的随机种子:随机数是由随机种子根据一定的计算方法计算出来的数值。所以,只要计算方法一定,随机种子一定,那么产生的随机数就不会变。 为了解决这个问题,可以让随机种子随时发生变化,所以可以利用时间来作为随机种子。如果用时间来做随机种子,需要导入“time”包。 ”
rand.Int( );
每次产生的整型随机数都非常的大,所以可以限制范围,使用的是rand中的Intn()
方法。例如:rand.Intn(100)
,限制100内的随机数。
最终程序可以进行如下的修改:
package main
import (
"fmt"
"math/rand"
"time"
)
func RandomFunc() {
//设置种子,只需一次
//rand.Seed(123)
rand.Seed(time.Now().UnixNano()) // 以当前系统时间作为种子参数
for i := 0; i < 5; i++ {
// 产生随机数
//fmt.Println("rand = ", rand.Int())
fmt.Println("rand = ", rand.Intn(100)) // 限制在100内的数
}
}
func main() {
RandomFunc()
}
最终,完整代码如下,该程序中涉及到了数组,函数嵌套调用,随机数等知识。通过该程序,体会出函数的一个很重要的优势,职责明确,RandomFunc( )函数是负责产生随机数,GetAvg( )负责对产生的数据进行计算。
func RandomFunc(numbers [3]int) [3]int{
//设置种子,只需一次
rand.Seed(time.Now().UnixNano()) // 以当前系统时间作为种子参数
for i := 0; i < len(numbers); i++ {
// 产生随机数
numbers[i] = rand.Intn(100)// 限制在100内的数
}
return numbers
}
func GetAvg(numbers [3]int) float64 {
var sum int
for _, v := range numbers {
sum += v
}
return float64(sum) / float64(len(numbers))
}
func main() {
// 随机生成数组
var num [3]int
num = RandomFunc(num)
// 计算数组的平均值
avg := GetAvg(num)
fmt.Printf("%.2f", avg)
}
func Reverse(str [5]string) [5]string {
var temp string
for i := 0; i < len(str)/2; i++ {
temp = str[i]
str[i] = str[len(str)-1-i]
str[len(str)-1-i] = temp
}
return str
}
func main() {
counties := [...]string{"中国", "美国", "巴西", "澳大利亚", "加拿大"}
array := Reverse(counties)
fmt.Println("反转前的数组: ", counties)
fmt.Println("反转后的数组: ", array)
}
执行如下:
反转前的数组: [中国 美国 巴西 澳大利亚 加拿大]
反转后的数组: [加拿大 澳大利亚 巴西 美国 中国]
注意:该程序中定义的函数,返回的是数组。
思路:
1:可以先计算出数组a和数组b的长度,如果长度不一致,数组肯定不一致。
2:如果长度一致,可以通过循环将每个数组中元素取出来,进行比较。如果有一个元素不一致就退出循环。
根据以上分析的思路,代码案例如下:
func main() {
a := [5]int{1, 2, 3, 4, 5}
b := [5]int{5, 2, 3, 4, 5}
if len(a) == len(b) {
for i := 0; i < len(a); i++ {
if a[i] == b[i] {
continue // 如果元素相同,直接进入下一次循环
} else {
fmt.Println("两个数组元素不一致")
break // 元素不一致,跳出循环
}
}
} else {
fmt.Println("两个数组的元素不一致")
}
}
在上面的代码中,重点体会continue
和break
的用法。
虽然以上代码能够满足需求,但是问题是比较麻烦,GO语言中提供了另外一种方式进行比较,
直接使用“==
”或”!=
”进行比较。但是具体的要求是:
1:只支持 ==
或 !=
, 比较是不是每一个元素都一样
2:两个数组比较,数组类型要一样
func main() {
a := [5]int{1, 2, 3, 4, 5}
b := [5]int{1, 2, 3, 4, 5}
c := [5]int{1, 2, 3}
d := [5]int{5, 1, 2, 3, 4}
f := [3]int{1, 2, 3}
fmt.Println("a == b", a == b)
fmt.Println("a == c", a == c)
fmt.Println("a == d", a == d)
fmt.Println("a == f", a == f) // 长度不一致,直接异常
}
除了可以进行比较以外,同类型的数组可以赋值
var d [5]int
d = a
fmt.Println("d = ", d)
前面定义的数组只有一个下标,称之为一维数组,如果有两个下标,称之为二维数组。
关于二维数组,只要了解其基本使用就可以。
二维数组的定义如下:
b := [3][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}
//可以理解数组b有3行4列构成,共能够存储12组数据。
//部分初始化,没有初始化的值为0
c := [3][4]int{{1, 2, 3}, {5, 6, 7, 8}, {9, 10}}
d := [3][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
e := [3][4]int{1: {5, 6, 7, 8}}//对第二列进行初始化,其它采用默认值。
由于二维数组是有行与列构成的,所以通过for循环进行初始化,需要循环嵌套,如下所示:
func main() {
var a [3][4]int
k := 0
for i := 0; i < 3; i++ { // 对行进行循环
for j := 0; j < 4; j++ { // 对列进行循环
k++
a[i][j] = k
fmt.Printf("a[%d][%d] = %d, ", i, j, a[i][j])
}
fmt.Printf("\n")
}
fmt.Println("a = ", a)
}
执行如下:
a[0][0] = 1, a[0][1] = 2, a[0][2] = 3, a[0][3] = 4,
a[1][0] = 5, a[1][1] = 6, a[1][2] = 7, a[1][3] = 8,
a[2][0] = 9, a[2][1] = 10, a[2][2] = 11, a[2][3] = 12,
a = [[1 2 3 4] [5 6 7 8] [9 10 11 12]]
“总结:有多少个[]就是多少维,有多少个[]就用多少个循环 ”