今天来看看数组。C/C++还有其他一些语言基本都用方括号 [] 来表示数组,另外C++还有array数组容器以及vector动态顺序表(这其实相当于后面要说到的切片)。
当然了,今天的主题是GO的数组。
GO也是用方括号 [] 来表示数组,先来看下它的一些定义方式
func main() {
//1. 声明了一个包含5个int的数组,每个元素被赋零值0
var arr1 [5]int
//2. 定义了一个包含5个int的数组,前三个元素分别被赋值为1,2,3,后两个元素被赋零值0
arr2 := [5]int{1,2,3}
//3. 用...来省略个数,这里就是定义了一个包含5个int的数组,编译器帮我们计算这个数组的大小
arr3 := [...]int{1,2,3,4,5}
//4. 定义一个四行五列的二维数组,且第一行被赋值为全1
arr4 := [4][5]int{{1,1,1,1,1}}
fmt.Println(arr1)
fmt.Println(arr2)
fmt.Println(arr3)
fmt.Println(arr4)
}
[0 0 0 0 0]
[1 2 3 0 0]
[1 2 3 4 5]
[[1 1 1 1 1] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
以上几种定义方式是较为常见的定义方式,总结一下
- 遵循GO基本的变量命名规则,数组名现在前面,数组的元素类型写在后面
- 数组的元素个数写在类型前面,用 [n]type 表示
- []内可用...表示让编译器计算数组个数
下面来说一些数组使用的技巧
遍历数组的range关键字
一般在C语言中,遍历一个数组都是用类似如下的遍历方式,GO语言中也是一样
func main() {
arr := [5]int{1,2,3,4,5}
for i := 0; i < 5; i++ {
fmt.Print(arr[i])
}
}
//输出结果
12345
但是GO语言中有一个专门用于遍历数组的关键字range,可以方便我们遍历数组,用法如下
func main() {
arr := [5]int{1,2,3,4,5}
for i,v := range arr {
fmt.Printf("第%d个元素是: %d\n",i,v)
}
}
//输出结果
第0个元素是: 1
第1个元素是: 2
第2个元素是: 3
第3个元素是: 4
第4个元素是: 5
这就有点类似于C++11中引入的范围for循环了,其实很多语言都引入了这种类似的范围for循环来简化传统for循环的写法。
range返回两个值,它返回的第一个参数是索引,第二个参数是该索引的值,所以我们可以像上面一样用两个变量来接收它们。
正式因为range能够同时返回索引以及索引的值的特性,所以在遍历数组的时候,基本上都使用range来遍历,方便直观。
当然,这里有一个问题,有时候我们只想要索引的值,而不想要该索引,那么怎么办,那么就可以使用下划线_了。
func main() {
//用下划线来不接收索引,而只接收索引的值
arr := [5]int{1,2,3,4,5}
for _,v := range arr {
fmt.Println(v)
}
}
//输出结果
1
2
3
4
5
如果只要索引,那么就不用这么麻烦了,直接用一个返回值来接收就可以了
func main() {
//用下划线来不接收索引,而只接收索引的值
arr := [5]int{1,2,3,4,5}
for i := range arr {
fmt.Println(arr[i])
}
}
//输出结果
1
2
3
4
5
数组的长度
可以通过len函数来获得数组的长度
func main() {
arr := [5]int{1,2,3,4,5}
fmt.Println(len(arr))
}
输出结果
5
数组是值类型的
先上一段C++的代码
#include <iostream>
using namespace std;
void func(int *arr){
arr[0] = 100; //这里将数组的第一个元素改为100
}
int main(){
int arr[] = {1,2,3,4,5};
func(arr);
for(int i = 0; i < 5; ++i)
cout << arr[i] << " ";
cout << endl;
return 0;
}
输出结果
100 2 3 4 5
可以看到,我们将这个数组作为参数传给了函数func,并在函数体内修改了第一个数组元素的值,我们发现函数执行完以后,原来的数组的第一个元素被改变了,这就是引用类型的。C++函数参数类型要传数组的时候,会弱化成数组首元素的地址传进去,也就是说,函数拿到的是原来的数组的首元素的地址,也就是一个指针,这样进行改动,当然会改变原数组的值。
但是GO就不是了,GO的数组是值类型的。先把上述代码改成GO版本的。
//注意下面必须是[5]int,GO编译器认为[3]int和[5]int是两种不同的数据类型
func fun(arr [5]int){
arr[0] = 100
}
func main() {
arr := [5]int{1,2,3,4,5}
fun(arr)
for i := range arr {
fmt.Printf("%d ",arr[i])
}
}
输出结果
1 2 3 4 5
这边GO就没有对元素进行改变,也就是说,在GO语言中,数组是值类型的。其实,在数组传参的时候,因为只有值传递,所以是拷贝了一个一模一样的数组作为临时的参数传给函数的,所以这是两个数组,临时数组改变不影响原来的数组。
那么我要是想改变原来的数组内容怎么办呢?很简单,用指针,传参数的时候传一个指针就好了。
//注意下面必须是[5]int,GO编译器认为[3]int和[5]int是两种不同的数据类型
func fun(arr *[5]int){ //这里传一个指针
arr[0] = 100
}
func main() {
arr := [5]int{1,2,3,4,5}
fun(&arr) //这里传数组的的地址
for i := range arr {
fmt.Printf("%d ",arr[i])
}
}
输出结果
100 2 3 4 5
可以看到,这样原数组就被改变了。
所以,这里关于数组给函数传参的时候的用法,需要注意,到底是要传值呢,还是要传指针,需要根据程序来不同设计。
但是,这样传参数还是很麻烦啊,我们必须得提前知道数组的个数,因为不同个数的数组是被认为不同类型的。
所以在GO语言中一般不直接使用数组,而是使用切片,关于切片的部分,下一章再来讲。